c++类 订阅
在现实世界中,经常有属于同一类的对象的总称。系统在第一次在程序中遇到一个类时为这个类建立它的所有类变量的拷贝 - 这个类的所有实例共享它的类变量。 展开全文
在现实世界中,经常有属于同一类的对象的总称。系统在第一次在程序中遇到一个类时为这个类建立它的所有类变量的拷贝 - 这个类的所有实例共享它的类变量。
信息
定    义
属于同一类的对象
对象的变量和方法
区    别
类和对象
中文名
C++类
外文名
class
C++类定义
在现实世界中,经常有属于同一类的对象。例如,你的自行车只是世界上很多自行车中的一辆。在面向对象软件中,也有很多共享相同特征的不同的对象:矩形、雇用记录、视频剪辑等。可以利用这些对象的相同特征为它们建立一个集合。而这个集合就称为类。类是定义同一类所有对象的变量和方法的蓝图或原型。例如,可以建立一个定义包含当前档位等实例变量的自行车类。这个类也定义和提供了实例方法(变档、刹车)的实现。实例变量的值由类的每个实例提供。因此,当你创建自行车类以后,必须在使用之前对它进行实例化。当创建类的实例时,就建立了这种类型的一个对象,然后系统为类定义的实例变量分配内存。然后可以调用对象的实例方法实现一些功能。相同类的实例共享相同的实例方法。除了实例变量和方法,类也可以定义类变量和类方法。可以从类的实例中或者直接从类中访问类变量和方法。类方法只能操作类变量 - 不必访问实例变量或实例方法。系统在第一次在程序中遇到一个类时为这个类建立它的所有类变量的拷贝 - 这个类的所有实例共享它的类变量。
收起全文
精华内容
下载资源
问答
  • C++类的介绍

    万次阅读 多人点赞 2018-02-09 16:11:57
    最近在学习SLAM,顺便将C++类的知识复习一下。(其中部分官方定义和程序设计方法来源于西北工业大学魏英老师)1.类的定义:是用户自定义的数据类型。C++一个类定义的形式如下:class 类名{ 成员列表};成员列表是类成员...

    最近在学习SLAM,顺便将C++类的知识复习一下。(其中部分官方定义和程序设计方法来源于西北工业大学魏英老师)

     

    1.类的定义:

    是用户自定义的数据类型。
    C++一个类定义的形式如下:
    class 类名 
    {
            成员列表
    };
    成员列表是类成员的集合,数目可以任意多, 一对 { } 是成员列表边界符,与成员列表一起成为类体。类体后面必须用 ; 结束。
    1.每个类可以没有成员,也可以有多个成员。
    2.类成员可以是数据或函数。
    3.所有成员必须在类内部声明,一旦类定义完成后,就没有任何其他方式可以再增加或减少成员。
     
    在面向对象程序设计中,一般将变量(数据)隐蔽起来,外部不能直接访问。把成员函数作为对外界的接口,通过成员函数访问数据,可能一开始学习的时候不太理解,这个我们在后面会经常用到,请耐心观看。
     
    类中如果有成员函数,则声明是必须的,而定义是可选的,什么意思呢,请看下例:
     
    在类内部定义函数体
    class 类名
    {
             返回类型   函数名(形参列表)
             {
                      函数体
             }
    };
     
    在类外部定义函数体
    class 类名
    {
             返回类型   函数名(形参列表);
    };
    返回类型   类名 :: 函数名(形参列表)
             函数体
     
    看到这里会产生一个问题,那就是这两种定义方法到底有什么区别,或者根本没有区别。
    其实它们还是有区别的,类内部定义的函数,程序在要调用它的时候会把它当作是一个内联函数,内联函数的好处是调用速度更快,但是会占用额外的内存空间,每调用一次都相当于定义一次。而外部定义的函数,就不会被当作内联函数。对于一些要用到递归的函数,定义成内联函数肯定是不合理的。因此建议使用第二种方法定义成员函数。
     
    类的定义一般放在程序文件开头,或者放到头文件中被程序文件包含,当然也可以放在局部作用域里。这里有必要提一下,c++规定,在局部作用域中声明的类,成员函数必须是函数定义形式,不能是原型声明。
     
    类相当于一种新的数据类型,数据类型不占用存储空间,用类型定义一个实体的时候,才会为它分配存储空间。
     
     

    2.类成员的访问控制:

    对类的成员进行访问,有两个访问源:类成员和类用户。
    类成员指类本身的成员函数,类用户指类外部的使用者,包括全局函数,另一个类的成员函数等。
    在C++中,类的每个成员都有访问控制属性:public(公有的)、private(私有的)、protected(保护的)
    类用户想要访问类的数据成员,必须通过公有成员访问。
    上面说过,面向对象程序设计过程中一般将数据隐蔽起来,也就是说一般的变量(数据)都声明为private,而成员函数声明为public,protected在后面我们会用到,不考虑继承的话,和private的性质一致。如果在声明的时候不写访问控制属性,则类会默认它为private。
     
    在刚才类定义的基础上进行扩展:
    class 类名
    {
    public:
         公有的数据成员和成员函数
    protected:
         保护的数据成员和成员函数
    private:
         私有的数据成员和成员函数
    };
     
    类的成员函数和普通函数一样,也可以进行重载,设置默认参数,显式的指定为内联函数等。
    这里有个小问题,请看下例:
    class Test
    {
    public:
        void Sum(int a=0,int b=0);
    };
    void Test::Sum(int a=0,int b=0)
    {
        cout<<a+b;
    }
    这是一个设置了默认参数的函数,但是很遗憾,这是错误的,下面这样则是正确的:
    class Test
    {
    public:
        void Sum(int a=0,int b=0);
    };
    void Test::Sum(int a,int b)
    {
        cout<<a+b;
    }
    原因是C++中对于特定的某个函数,设置默认形参这个动作只能有一次
     
     

    3.对象的定义和使用:

    说了这么多,怎么样才能实现在外部实现对类成员的访问呢?这就是我们要讨论的对象。
     
    对类的定义就是定义了一个具体的数据类型,要使用它必须将类实例化,即定义该类的对象。
    以下两种定义类对象的方法都是合法的(假定有一个Test类):
    Test test1 , test2;
    class Test test1 , test2;
    之前说过,定义类型时不会分配存储空间,当定义一个对象的时候,将为其分配存储空间。
     
    当然,有时候人们也希望可以动态的为其分配内存,当不用的时候再销毁它,就有了如下定义方式:
    Test *p;
    p = new Test;
    当不再使用此动态对象的时候,必须用delete:
    delete p;
    现在关心的应该是怎么通过对象调用类的成员?
    访问对象中的成员有三种方法:
    通过对象名和对象成员引用运算符 (.) 
    通过指向对象的指针和指针成员引用运算符 (->)
    通过对象的引用变量和对象成员引用运算符 (.) 
     
     
    假定有一个Test类,类中有一个公有的Sum()函数,则在外部调用Sum()的方法有:
    Test test;
    test.Sum();
     
    Test *p;
    p = new Test;
    p->Sum();

    Test test, &r = test;
    r.Sum();
    这些方式都是合法的。
     
     
     

    4.构造函数与析构函数:

    建立一个对象的时候,通常最需要做的工作就是初始化对象,如对数据成员赋初值,而构造函数就是用来在创建对象时初始化对象,为对象数据成员赋初值。为什么非得这么做呢?因为在类里面,数据成员不能够进行初始化。即:
    class  Test
    {
         int x = 0;
         ...
    };
    这样做是错误的。
    想想为什么不可以,还是上面说过的,类只是定义了一个数据类型,不会占用存储空间,而在类里面对数据成员赋初值则会占用存储空间,因此自相矛盾。
     
    如果数据成员是公有的,那么可以在类外直接对它初始化,但如果是私有的,那么就不能直接访问它,这就要用到构造函数。构造函数就是用来处理对象的初始化问题,构造函数是一种特殊的成员函数,不需要人为调用,而是在对象建立的时候自动被执行。
     
    C++规定构造函数的名字要与类名保持一致,而且不能指定返回类型。请看下面程序:
    #include <iostream>
    
    using namespace std;
    
    class Test
    {
    public:
        Test ();
        Test (int x,int y);
        void Sum();
    private:
        int a,b;
    };
    Test::Test()
    {
    
    }
    Test::Test(int x,int y)
    {
        a=x;
        b=y;
    }
    void Test::Sum()
    {
        cout<<a+b;
    }
    int main()
    {
        Test test(3,4);
        test.Sum();
        return 0;
    }
    
    这里定义了两个构造函数 Test() 和 Test(int x,int y),由于创建对象一般是在类外部进行,因此构造函数声明为public。

    第一个为无参构造函数或默认构造函数,写这个函数的好处是当你在创建对象的时候并不想立即对它初始化,而是在后续的工作中再进行赋初值,即:
    Test test;
    如果没有默认构造函数则会报错。那么问题来了,之前的例子根本没写构造函数,却不会报错,这是为什么?
     
    因为在IDE里(ex:codeblocks)不会报错是因为IDE会自动生成一个默认构造函数。当然,如果你已经定义了一个有参的构造函数,它就不再为你自己生成一个默认构造函数,也就是说如果现在把这个Test类里的默认构造函数删除了,
    Test test;
    就会报错。
     

    第二个构造函数就完成了我们的初始化工作,它有两个形参,分别给数据成员a,b进行初始化,定义对象的时候传入了 3和4,则 a和b 被初始化为 3 和 4 。因此程序运行的结果是 打印出了 7。
     
    构造函数初始化列表
     
    所谓初始化列表,它的功能和我们写在函数体里的赋初值是一样的,也就是说可以写成如下形式:
     
    #include <iostream>
    
    using namespace std;
    
    class Test
    {
    public:
        Test ();
        Test (int x,int y);
        void Sum();
    private:
        int a,b;
    };
    Test::Test()
    {
    
    }
    Test::Test(int x,int y):a(x),b(y)
    {
    
    }
    void Test::Sum()
    {
        cout<<a+b;
    }
    int main()
    {
        Test test(3,4);
        test.Sum();
        return 0;
    }
    你可以选择写的更简洁一点:
    #include <iostream>
    
    using namespace std;
    
    class Test
    {
    public:
        Test () {}
        Test (int x,int y):a(x),b(y) {}
        void Sum();
    private:
        int a,b;
    };
    
    void Test::Sum()
    {
        cout<<a+b;
    }
    int main()
    {
        Test test(3,4);
        test.Sum();
        return 0;
    }
    那么这样做和普通的赋值有区别吗?
    当然是有的,对于一般的变量,两种都可行,但是如果需要初始化的是类类型的成员,则必须使用构造函数初始化列表。比如:
     
    #include <iostream>
    
    using namespace std;
    
    class Test
    {
    public:
        Test () {}
        Test (int x,int y):a(x),b(y) {}
        void Sum();
    private:
        int a,b;
    };
    
    void Test::Sum()
    {
        cout<<a+b;
    }
    class AnotherTest
    {
    public:
        AnotherTest(int i,int j):test(i,j) {test.Sum();}
    private:
        Test test;
    };
    int main()
    {
        AnotherTest test(3,4);
        return 0;
    }
    
     
    之前说过,类的成员函数可以重载,带默认参数等,那么构造函数呢?
    构造函数也是可以的,刚才那个例子就是构造函数的重载,默认构造和有参构造。

    下面是一个带默认参数的构造:
    #include <iostream>
    
    using namespace std;
    
    class Test
    {
    public:
        Test () {}
        Test (int x = 0,int y = 0):a(x),b(y) {}
        void Sum();
    private:
        int a,b;
    };
    
    void Test::Sum()
    {
        cout<<a+b;
    }
    
    int main()
    {
        Test test(3);
        test.Sum();
        return 0;
    }
    一旦指定了 x = 0,就必须指定 y 的值。
     
    Test (int x = 0,int y):a(x),b(y) {}
    这样是错误的。


    复制构造函数
     
    复制构造函数也称为拷贝构造函数,它的作用是用一个已经生成的对象来初始化另一个同类的对象。
    即实现如下功能:
    Test test1(3,4);
    Test test2 = test1;
    复制构造函数的写法:
     
    类名 (const 类名& obj)
    {
           函数体
    }
     
    例如:
    #include <iostream>
    
    using namespace std;
    
    class Test
    {
    public:
        Test () {}
        Test (int x ,int y):a(x),b(y) {}
        Test (const Test& t):a(t.a),b(t.b) {}
        void Sum();
    private:
        int a,b;
    };
    
    void Test::Sum()
    {
        cout<<a+b;
    }
    
    int main()
    {
        Test test1(3,4);
        Test test2 = test1;
        test2.Sum();
        return 0;
    }
    
    程序运行的结果是 7 ,它完成了给test2进行初始化。
    当然也可以用如下语句:
    Test test2(test1);
    深复制和浅复制:
     
    如果不定义复制构造函数,以上对象也可以这样进行初始化,原因就是系统也会自己生成一个复制构造函数。
    现在存在这样一个类:
    #include <iostream>
    #include <cstring>
    
    using namespace std;
    
    class Test
    {
    public:
        Test (int x,char *ptr)
        {
            a = x;
            p = new char [x];
            strcpy(p,ptr);
        }
        Test (const Test& C)
        {
             a = C.a;
             p = new char [a];
             p = C.p;
        }
        void Print();
    private:
        int a;
        char *p;
    };
    
    void Test::Print()
    {
        int i = 0;
        while(p[i] != '\0')
        {
            cout<<p[i];
            i++;
        }
    
    }
    
    int main()
    {
        char p[5] = "test";
        Test a(10,p);
        Test b(a);
        b.Print();
        return 0;
    }
    

     
    因为对象 a 和 b 指向的是同一段内存区域,如果我们在完成复制(浅复制)之后删除了 a,它指向的内存区域同样也被删除了,而此时 b 此时仍然指向的是这片区域,如果再把 b 删除掉,同一片内存区域被释放两次,这明显是错误的。也就是说,浅复制只是简单的将 a 中p 的值给了 b 中的 p。那么要解决这个问题就得用到深复制:
    Test (const Test& C)
        {
             a = C.a;
             p = new char [a];
             if(p != 0)
               strcpy(p,C.p);
        }
     
     
    析构函数:
     
    析构函数在类里起了一个“清理”的作用,比如类中有需要动态开辟内存的成员,而在程序结束之后我们需要释放内存,这时只要将释放内存的语句写在析构函数中,而系统在程序运行结束之后会自动执行析构函数,进行内存的释放以及对象的销毁。
    以下是一个例子:
    #include <iostream>
    #include <cstring>
    
    using namespace std;
    
    class Test
    {
    public:
        Test (int x,char *ptr)
        {
            a = x;
            p = new char [x];
            strcpy(p,ptr);
        }
        Test (const Test& C)
        {
             a = C.a;
             p = new char [a];
             if(p != 0)
               strcpy(p,C.p);
        }
        ~Test()
        {
            delete (p);
            cout<<"p has been destroyed"<<endl;
        }
        void Print();
    private:
        int a;
        char *p;
    };
    
    void Test::Print()
    {
        int i = 0;
        while(p[i] != '\0')
        {
            cout<<p[i];
            i++;
        }
        cout<<endl;
    }
    
    int main()
    {
        char p[5] = "test";
        Test a(10,p);
        Test b(a);
        b.Print();
        return 0;
    }
    
    程序运行结果如下:
     
    由此可见,a 和 b 的析构函数都被调用了。 
     
     
     

    5.友元机制:

    C++提供了友元机制,允许一个类将其非公有成员的访问权限授予指定的函数或类。友元的声明只能在类定义的内部,因此,访问类非公有成员除了自身成员,还有友元。
    有如下程序:
    #include <iostream>
    #include <cstring>
    
    using namespace std;
    
    class Test
    {
    public:
        Test (int a)
        {
            x = a;
        }
        ~Test()  //析构函数
        {
    
        }
        friend void Print(Test& a,Test& b);
    private:
        int x;
    };
    
    void Print(Test& a,Test& b)
    {
        cout<<a.x*b.x;
    }
    
    int main()
    {
        Test a(10);
        Test b(3);
        Print(a,b);
        return 0;
    }
    
    输出结果为 30 ,完成了求两个对象内的数据之积。
     
    下面介绍友元类:
    #include <iostream>
    #include <cstring>
    
    using namespace std;
    
    class B;  //类的前向声明
    class A
    {
    public:
        A(){}
        ~A()  //析构函数
        {
    
        }
        void Print(B& a);
    };
    
    class B
    {
    public:
        B (int a)
        {
            x = a;
        }
    private:
        int x;
        friend void A::Print(B& a);
    };
    void A::Print(B& a)
    {
        cout<<a.x;
    }
    int main()
    {
        B test1(3);
        A test2;
        test2.Print(test1);
        return 0;
    }
    
     类A成功的访问了类B的私有成员,并且打印出来。输出结果为 3。

    友元类的关系是单向的,即 A 是 B 的友元,B 不是 A 的友元,类 B 不能访问 A 的数据成员。此外,友元的关系不能传递或继承,类 B 是类 A 的友元,C 是 B 的友元,那么 C 不是 A 的友元,除非另外声明一次。
     

    6.继承与派生:

     
    继承是面向对象程序设计的一个重要特性,继承允许在原有类的基础上创建新的类,举个例子,现在有一个平行四边形类,而菱形类,矩形类,正方形类 都属于平行四边形类,它们有一个共同点,那就是需要两条边长来描述图形,如果不采用继承,我们需要在每个类中定义两个数据成员,那样会显得很繁琐。下面我们看一个计算矩形面积的例子:
    #include <iostream>
    
    using namespace std;
    
    class Parallelogram
    {
    public:
        Parallelogram(int a,int b):length(a),width(b) {}
        int getLength(){return length;}
        int getWidth() {return width;}
    private:
        int length,width;
    };
    class Rectangle : public Parallelogram  //公有继承
    {
    public:
        Rectangle(int a,int b):Parallelogram(a,b) {}  //先对基类中的数据成员进行初始化
        void Area()     //计算面积
        {
            cout<<getLength()*getWidth();
        }
    };
    int main()
    {
        Rectangle r(3,4);
        r.Area();
        return 0;
    }
    
    首先说一下继承方式,c++提供了三中继承方式。
    public(公有继承),基类中的公有和保护成员保持原属性,私有成员为基类私有。
    private(私有继承),基类中的所有成员在派生类中都是私有的。
    protected(保护继承),基类的公有成员和保护成员在派生类中成了保护成员,私有成员仍为基类私有。
     
    现在我们说如何设计一个派生类:
    ①从基类接收成员,除了构造函数和析构函数,派生类会把全部的成员继承过来,这是没有选择的。
    ②调整基类成员的访问。
    ③修改基类成员,可以在派生类中声明一个与基类同名的成员,此操作会覆盖基类的同名成员。
    ④在定义派生类的时候定义新的成员,定义构造函数和析构函数,初始化的时候必须先将基类的成员初始化(因为并没有继承基类的构造函数)之后才可以对派生类的成员进行初始化。析构函数也一样,需要在派生类中释放基类的数据成员(调用基类的析构函数)。
     
    上面这个程序定义了一个基类(平行四边形类),它含有两个数据成员,代表两个边长,而矩形在计算面积的时候需要两个边长的长度,也就是长和宽,因此继承平行四边形类,并且新添加了计算面积的函数,程序输出结果 12。
     
     
    多重继承和虚基类:
    C++还支持一个派生类同时继承多个基类。
    多重继承派生类的定义:
     
    class  派生类名 : 访问标号1 基类名1 , 访问标号2 基类名2 , ....

    {

             成员列表

    }

    同样,派生类的构造函数初始化列表在调用基类构造函数也应该按定义时的先后次序。

    接下来看个例子:

     

    #include <iostream>
    
    using namespace std;
    
    class BaseOne
    {
    public:
        BaseOne() {cout<<"This is BaseOne"<<endl;}
        BaseOne(int a):data(a) {cout<<"BaseOne's data is "<<data<<endl;}
    private:
        int data;
    };
    
    class BaseTwo
    {
    public:
        BaseTwo() {cout<<"This is BaseTwo"<<endl;}
        BaseTwo(int a):data(a) {cout<<"BaseTwo's data is "<<data<<endl;}
    private:
        int data;
    };
    
    class BaseThree
    {
    public:
        BaseThree() {cout<<"This is BaseThree"<<endl;}
    };
    
    class Derive:public BaseOne,public BaseTwo,public BaseThree
    {
    public:
        Derive () {cout<<"This is Derive"<<endl;}
        Derive (int a,int b,int c,int d,int e):BaseOne(a),BaseTwo(b),dataOne(c),dataTwo(d),data(e)
        {cout<<"Derive's data is "<<data<<endl;}
    private:
        BaseOne dataOne;
        BaseTwo dataTwo;
        int data;
    };
    
    int main()
    {
        Derive r1;
        cout<<endl;
        Derive r2(1,2,3,4,5);
        return 0;
    }
    

     

    程序运行结果如下:

     

    在调用派生类的默认构造函数时,即使没有写出调用基类的默认构造函数,系统也会调用基类的默认构造函数,而在结果的第4 , 5行还调用了一次,原因是派生类里有两个基类的数据成员,因此我们可以观察到,程序先调用了基类的构造函数,然后调用派生类中子对象的构造函数,最后调用派生类的构造函数。

    在调用构造函数的时候,先调用基类的构造函数,虽然BaseThree没有参数,但是仍然会调用它的构造函数,然后初始化子对象,调用构造函数,最后调用派生类的构造函数。

     

    二义性问题:

    假定我们有如下程序:

     

    #include <iostream>
    #include <cstring>
    
    using namespace std;
    
    class A
    {
    public:
        void fun() {cout<<"This is A"<<endl;}
    };
    
    class B
    {
    public:
        void fun() {cout<<"This is B"<<endl;}
    };
    
    class C:public A,public B
    {
    public:
        void hun() {fun();}  //产生二义性
    };
    
    int main()
    {
        C c;
        c.hun();
        return 0;
    }
    

     

    相信看到这里大家都知道二义性问题的产生原因了吧,就是两个基类存在名称相同的数据成员,而派生类在调用基类数据成员的时候如果没有显式的指出它属于谁,那么程序就会产生错误,现在做如下修改:

     

     

    void hun() {A::fun(); B::fun();}

     

    这次程序会分别调用 A 和 B 的 fun() 函数。我们要做的只是在它前面写上 基类名加上域运算符 :: ,当然也可以通过 对象名.基类名 :: 和 对象指针名.基类名 :: 这两种方式。

     

     

    虚基类:

    假定现在有这样一种继承关系:

     

    #include <iostream>
    
    using namespace std;
    
    class A
    {
    public:
        void fun() {cout<<"This is A"<<endl;}
    };
    
    class B:public A
    {
    public:
        void gun() {cout<<"This is B"<<endl;}
    };
    
    class C:public A
    {
    public:
        void hun() {cout<<"This is C"<<endl;}
    };
    
    class D:public B,public C
    {
    public:
        void kun() {fun();} //产生二义性
    };
    
    int main()
    {
        D d;
        d.kun();
        return 0;
    }
    

     

    A 是基类,B 是 A 的派生类,C 也是 A 的派生类,而 D 是 B 和 C 的派生类,因此 D 可以访问 A 的数据成员,但现在会产生二义性问题,我们必须显式的指出 fun() 是来自 B 的 还是来自 C 的,但是我们都知道它来自 A , 因此我们希望找到一种方式,使得在继承间接共同基类时只保留一份成员,这就用到了虚基类的机制。

     

    虚基类是在派生类定义时,指定继承方式时声明的。声明的一般形式:

     

    class  派生类名 : virtual  访问标号 虚基类名 , ...

    {

            成员列表

     

    为了保证虚基类在派生类中只继承一次,应当在所有直接派生类中声明为虚基类。依然是上面那个程序,只需要:

     

    class B:virtual public A

     

    class C:virtual public A

     

    这样在类 D 中调用 fun() 函数,就不用指出它究竟属于谁。

     

    接下来看看虚基类构造函数和析构函数的一些特性。

    有这样一个程序:

     

    #include <iostream>
    
    using namespace std;
    
    class A
    {
    public:
        A() {cout<<"This is Grandpa"<<endl;}
        A(int a):One(a) {cout<<"Grandpa is "<<One<<" years old"<<endl;}
        ~A() {cout<<"A is over"<<endl;}
    private:
        int One;
    };
    
    class B:virtual public A
    {
    public:
        B() {cout<<"This is father"<<endl;}
        B(int a,int b):A(a),Two(b) {cout<<"father is "<<Two<<" years old"<<endl;}
        ~B() {cout<<"B is over"<<endl;}
    private:
        int Two;
    };
    
    class C:virtual public A
    {
    public:
        C() {cout<<"This is mother"<<endl;}
        C(int a,int b):A(a),Three(b) {cout<<"mother is "<<Three<<" years old"<<endl;}
        ~C() {cout<<"C is over"<<endl;}
    private:
        int Three;
    };
    
    class D:public B,public C
    {
    public:
        D() {cout<<"This is me"<<endl;}
        D(int a,int b,int c,int d):A(a),B(a,b),C(a,c),Four(d) {cout<<"I am "<<Four<<" years old"<<endl;}
        ~D() {cout<<"D is over"<<endl;}
    private:
        int Four;
    };
    
    int main()
    {
        D d1;
        cout<<endl;
        //D d2(65,40,39,13);
        return 0;
    }
    

    首先看默认构造函数(先把d2注释掉),程序运行结果如下:

    程序会自动调用构造函数,首先调用虚基类的构造函数,然后再根据派生类继承的次序调用构造函数,如果先继承了 C 类,那么先调用 C 类的构造函数。析构函数的调用则正好相反。

     

    接下来看有参的,给 d1 加上注释,去掉 d2 的注释,程序运行结果如下:

    调用次序是一样的,因此虚基类的构造函数优先于非虚基类的构造函数进行执行,如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有的派生类里(直接和间接)中,都必须通过构造函数的初始化列表对其进行初始化,在最后的派生类中不单单对直接继承的类进行初始化,还要对虚基类进行初始化。

     

     

    7.多态性和虚函数:

     

    首先介绍一下多态性,多态是指同样的消息被不同类型的对象接收时导致不同的行为,举个通俗易懂的例子,假定现在有一个模具,这个模具是一个人型模具,根据倒入里面金属液体的不同,它最终会形成不同类型的器件,如果倒入的是液体黄金,那么它会形成一个小金人,如果倒入的是铁水,那就会形成一个小铁人,多态大概就是这样的意思。

     

    多态性可以通过很多方法实现,而我们要说的是 包含多态 实现多态性,C++采用虚函数实现包含多态,至少含有一个虚函数的类成为多态类。

    在介绍虚函数之前,我们介绍两种联编。联编就是将模块或者函数合并在一起生成可执行代码的处理过程,同时对每个模块或者函数分配内存地址,并且对外部访问也分配正确的内存地址。

    在编译阶段就将函数实现和函数调用绑定起来称为静态联编,程序运行的时候才进行函数实现和函数调用的绑定称为动态联编。

    举个例子:

     

    #include <iostream>
    
    using namespace std;
    
    class A
    {
    public:
        void fun() {cout<<"Use A"<<endl;}
    };
    
    class B : public A
    {
    public:
        void fun() {cout<<"Use B"<<endl;}
    };
    
    int main()
    {
        B b;
        A *p = &b;
        p->fun();
        return 0;
    }
    

     

    我们声明了一个指向类 B 的指针,但是程序的输出结果是:

     

    之所以会这样是因为将其定义为 A 类型,程序在编译阶段就已经确定 A 类型的指针调用的 fun() 是 A 类的成员。

    接下来看动态联编:

    给刚才类 A 的 fun() 函数前面加上 virtual ,

     

    virtual void fun() {cout<<"Use A"<<endl;}

    将其定义为了虚函数,再次运行:

    当编译器编译含有虚函数的类时,将为它建立一个虚函数表,相当于一个指针数组,存放每个虚函数的入口地址,编译器为该类增加一个额外的数据成员,这个数据成员是一个指向虚函数表的指针,通常称为vptr。这个例子中,A 类有一个虚函数 fun() , 所以虚函数表里只有一项,如果派生类没有重写这个虚函数,那么虚函数表里的元素所指向的地址就是基类虚函数的地址,重写之后,则 vptr 指向派生类的虚函数地址。

    派生类可以继承基类的虚函数表,而且只要和基类同名的成员函数,无论前面加不加 virtual ,都会自动成为虚函数,虚函数的调用规则是,根据当前对象,优先调用对象本身的成员函数。

     

    虚析构函数:

    如果将基类的析构函数声明为虚函数,那么其派生类的析构函数也变为虚析构函数,即使名字不同,当基类的析构函数是虚析构函数时,无论指针指的是同一类族中的哪一个类对象,系统总会采用动态联编,调用正确的析构函数,对该对象进行清理。C++中,不支持虚构造函数。

     

    纯虚函数:

    许多情况下,不能在基类中为虚函数给出一个有意义的定义,那就将其声明为纯虚函数,具体怎么实现交给派生类去做,纯虚函数的定义形式为:

     

    virtual   返回类型  函数名 (形式参数列表) = 0;

     

    纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对其进行定义,如果一个类里声明了虚函数,而在其派生类中没有对该函数定义,那么该函数在派生类中仍然为纯虚函数。含有纯虚函数的类成为抽象类,抽象类不能定义对象,如果派生类里给出了抽象类中纯虚函数的实现,那么该派生类不再是抽象类,否则仍然是抽象类。抽象类至少含有一个纯虚函数。

     

    接下来看一个计算圆形面积和圆柱体体积的程序:

     

    #include <iostream>
    
    using namespace std;
    
    class Sharp
    {
    public:
        virtual double area() = 0;
        virtual double volumn() = 0;
    };
    
    class Circle : public Sharp
    {
    public:
        Circle(double r):R(r) {}
        virtual double area() {return 3.1415926*R*R;}
        virtual double volumn() {return 0;}
    private:
        double R;
    };
    
    class Cylinder : public Circle
    {
    public:
        Cylinder(double a,double b):Circle(a),H(b) {}
        virtual double volumn() {return area()*H;}
    private:
        double H;
    };
    
    int main()
    {
        Circle a(20.0);
        Cylinder b(10.0,2.0);
        cout<<a.area()<<endl;
        cout<<b.volumn()<<endl;
        return 0;
    }
    

     

    程序运行结果:

     

    展开全文
  • C++类大小详尽讲解

    万次阅读 多人点赞 2018-05-04 17:37:04
    C++类的大小,是一个比较经典的问题,学过C++后,应该对类大小有清晰的认识,长话短说,本文精简凝练,我们进入正题!!!1.类的大小与什么有关系? 与类大小有关的因素:普通成员变量,虚函数,继承(单一继承,...

            C++类的大小,是一个比较经典的问题,学过C++后,应该对类大小有清晰的认识,长话短说,本文精简凝练,我们进入正题!!!

    1.类的大小与什么有关系?

         与类大小有关的因素:普通成员变量,虚函数,继承(单一继承,多重继承,重复继承,虚拟继承)

         与类大小无关的因素:静态成员变量,静态成员函数及普通成员函数

    2.空类

         空类即什么都没有的类,按上面的说法,照理说大小应该是0,但是,空类的大小为1,因为空类可以实例化,实例化必然在内存中占有一个位置,因此,编译器为其优化为一个字节大小。

          某类继承自空类:

    class base
    {
    };
    class derived:public base
    {
     private:
        int a;
    }
    

        此时,derived类的大小为4,derived类的大小是自身int成员变量的大小,至于为什么没有加上父类base的大小1是因为空白基优化的问题,在空基类被继承后,子类会优化掉基类的1字节的大小,节省了空间大小,提高了运行效率。

    3.一般类的大小(注意内存对齐)

    首先上两个类的示例:

    class base1
    {
    private:
        char a;
        int b;
        double c;
    };
    class base2
    {
    private:
        char a;
        double b;
        int c;
    };

    虽然上述两个类成员变量都是一个char,一个int,一个double,但是不同的声明顺序,会导致不同的内存构造模型,对于base1,base2,其成员排列是酱紫的:

    base1:


    base2:


    base 1类对象的大小为16字节,而base 2类对象的大小为24字节,因为不同的声明顺序,居然造成了8字节的空间差距,因此,我们将来在自己声明类时,一定要注意到内存对齐问题,优化类的对象空间分布。

    4.含虚函数的单一继承

    首先呈上示意类:(64位,指针大小8字节)

    class Base
    {
    private:
        char a;
    public:
        virtual void f();
        virtual void g();
    };
    class Derived:public Base
    {
    private:
        int b;
    public:
        void f();
    };
    class Derived1:public Base
    {
    private:
        double b;
    public:
        void g();
        virtual void h();
    };

    基类Base中含有一个char型成员变量,以及两个虚函数,此时Base类的内存布局如下:


    内存布局的最一开始是vfptr(virtual function ptr)即虚函数表指针(只要含虚函数,一定有虚函数表指针,而且该指针一定位于类内存模型最前端),接下来是Base类的成员变量,按照在类里的声明顺序排列,当然啦,还是要像上面一样注意内存对齐原则!


    继承类Derived继承了基类,重写了Base中的虚函数f(),还添加了自己的成员变量,即int型的b,这时,Derived的类内存模型如下:

     

    此种情况下,最一开始的还是虚函数表指针,只不过,在Derived类中被重写的虚函数f()在对应的虚函数表项的Base::f()已经被替换为Derived::f(),接下来是基类的成员变量char a,紧接着是继承类的成员变量int b,按照其基类变量声明顺序与继承类变量声明顺序进行排列,并注意内存对齐问题。


    继承类Derived1继承了基类,重写了Base中的虚函数g(),还添加了自己的成员变量(即double型的b)与自己的虚函数(virtual h() ),这时,Derived1的类内存模型如下:


    此种情况下,Derived1类一开始仍然是虚函数表指针,只是在Derived1类中被重写的虚函数g()在对应的虚函数表项的Base::g()已经被替换为Derived1::g(),新添加的虚函数virtual h()位于虚函数表项的后面,紧跟着基类中最后声明的虚函数表项后,接下来仍然是基类的成员变量,紧接着是继承类的成员变量。

    5 含虚函数的多重继承

    首先上示意类:

    class Base1
    {
    private:
        char a;
    public:
        virtual void f();
        virtual void g1();
    };
    class Base2
    {
    private:
        int b;
    public:
        virtual void f();
        virtual void g2();
    };
    class Base3
    {
    private:
        double c;
    public:
        virtual void f();
        virtual void g3();
    };
    class Derived:public Base1, public Base2, public Base3
    {
    private:
        double d;
    public:
        void f();
        virtual void derived_func();
    }

    首先继承类多重继承了三个基类,此外继承类重写了三个基类中都有的虚函数virtual f(),还添加了自己特有的虚函数derived_func(),那么,新的继承类内存布局究竟是什么样子的呢?请看下图!先来看3个基类的内存布局:


    紧接着是继承类Derived的内存布局:


    首先,Derived类自己的虚函数表指针与其声明继承顺序的第一个基类Base1的虚函数表指针合并,此外,若Derived类重写了基类中同名的虚函数,则在三个虚函数表的对应项都应该予以修改,Derived中新添加的虚函数位于第一个虚函数表项后面,Derived中新添加的成员变量位于类的最后面,按其声明顺序与内存对齐原则进行排列。

    6. 菱形继承的问题及解决方案:虚拟继承

    首先在讲这一节之前,先贴出几个重要的信息(干货):

    (1)不同环境下虚拟继承对类大小的影响

    在vs环境下,采用虚拟继承的继承类会有自己的虚函数表指针(假如基类有虚函数,并且继承类添加了自己新的虚函数

    在gcc环境下及mac下使用clion,采用虚拟继承的继承类没有自己的虚函数表指针(假如基类有虚函数,无论添加自己新的虚函数与否),而是共用父类的虚函数表指针

    关于以上这一点请详见我的博客:https://blog.csdn.net/longjialin93528/article/details/79874558  ,这里对此进行了超级详细的讲解。

    (2)虚拟继承会给继承类添加一个虚基类指针(virtual base ptr 简称vbptr),其位于类虚函数指针后面,成员变量前面,若基类没有虚函数,则vbptr其位于继承类的最前端

    关于虚拟继承,首先我们看看为什么需要虚拟继承及虚极继承解决的问题。

    虚极继承主要是为了解决菱形继承下公共基类的多份拷贝问题:

    class Base
    {
    public:
        int a;
    }
    class Base1:virtual public Base
    {
    }
    class Base2:virtual public Base
    {
    }
    class Derived:public Base1,public Base2
    {
    private:
        double b;
    public:
    }


    Base1与Base2本身没有任何自身添加的数据成员与虚函数,因此,Base1与Base2都只含有从Base继承来的int a与一个普通的方法,然后Derived又从Base1与Base2继承,这时会导致二义性问题重复继承下空间浪费的问题:

    二义性问题:

    Derived de;
    de.a=10;//这里是错误的,因为不知道操作的是哪个a

    重复继承下空间浪费:

    Derived重复继承了两次Base中的int a,造成了无端的空间浪费


    虚拟继承是怎么解决上述问题的?

    虚基继承可以使得上述菱形继承情况下最终的Derived类只含有一个Base类,Base类在虚拟继承后,位于继承类内存布局最后面的位置,继承类通过vbptr寻找基类中的成员及vfptr。

    虚拟继承对继承类的内存布局影响可以先看以下示例代码,理解以后,我们在最后列出上述菱形虚拟继承情况下Base1,Base2与Derived代码内存布局,看到虚拟继承起的作用。

    class base
    {
    public:
        int a
        virtual void f();
    }
    class derived:virtual public base
    {
    public:
        double d;
        void f();
    }

    Derived类内存布局如下图,由于虚拟继承,Derived只会有一个最初基类的拷贝,该拷贝位于类对象模型的最下面,而想要访问到基类的元素,需要vbptr指明基类的位置(vbptr作用),假如Base中含有虚函数,而继承类中没有增添自己的新的虚函数,那么Derived类统一的布局如下:


    如果添加了自己的新的虚函数(代码如下):

    class base
    {
    public:
        int a
        virtual void f();
    }
    class derived:virtual public base
    {
    public:
        double d;
        void f();
        virtual void g();//这是Derived类自己新添加的虚函数
    }

    那么Derived在VC下继承类会有自己的虚函数指针,而在Gcc下是共用基类的虚函数指针,其分布如下


        

    现在有了上述代码的理解我们可以写出菱形虚拟继承代码及每个类的内存布局:

    class Base
    {
    public:
        int a;}
    class Base1:public virtual Base
    {
    }
    class Base2:public virtual Base
    {
    }
    class Derived:public Base1,public Base2
    {
    private:
        double b;
    public:
    }


    带实线的框是类确确实实有的,带虚线是针对Base,及Base1,Base2做了扩展后的情况:

    Base有虚函数,Base1还添加了自己新的虚函数,Base1也有自己成员变量,Base2添加了自己新的虚函数,Base2也有自己成员变量,则上图全部虚线中的部分都将存在于对象内存布局中。


    码字画图不易,点个赞再走呗~~~也是给我继续创作的动力!










    展开全文
  • C++类模板

    万次阅读 2016-02-13 20:49:51
    C++类模板 #include using namespace std; templateclass numtype> //声明一个模板,虚拟类型名为numtype class compare //类模板名为compare { public: compare(numtype x,numtype y)

    与函数模板相同,往往有时候对好几个功能相似的类,可以使用类模板。

    C++类模板

    #include <iostream>
    using namespace std;
    template<class numtype> //声明一个模板,虚拟类型名为numtype
    class compare //类模板名为compare
    {
    public:
        compare(numtype x,numtype y)
        {
            this->x=x;
            this->y=y;
        }
    
    
        numtype max()
        {
            return x>y?x:y;
        }
    
    
        numtype min()
        {
            return x<y?x:y;
        }
        numtype mean();
    private:
        numtype x,y;
    };
    template<class numtype>
    numtype compare<numtype>::mean()
    {
        return (x+y)/2;
    }
    
    
    int main()
    {
        compare<int> comp1(1,2);
        cout << "max is" << comp1.max() << endl;
        cout << "min is" << comp1.min() << endl;
        compare<float> comp2(1.5,2.1);
        cout << "max is" << comp2.max() << endl;
        cout << "min is" << comp2.min() << endl;
        cout << "mean is" << comp2.mean() << endl;
        compare<char> comp3('a','b');
        cout << "max is" << comp3.max() << endl;
        cout << "min is" << comp3.min() << endl;
        return 0;
    }
    
    

    1.1在声明类模板前要增加一行 template<class 类型参数名>
    1.2 numtype只是一个虚拟类型参数名。
    1.3在建立对象时,如果指定为int则为int型,指定为float,则为float型。实现“一类多用”
    1.4因为类模板包含参数,所以被称为参数化的类。
    1.5如果说类是对象的抽象,那么类模板是类的抽象,类是类模板的实例。
    1.6使用类模板:
    compare <int> cmp1(1,2);
    1.7在类外使用类模板函数
    template <class numtype>
    numtype compare<numtype>::mean()
    {
    return (x+y)/2;
    }

    C++类模板的使用步骤

    1.1先写出一个实际的类,而不是类模板。
    1.2将此类中的int等改成一个指定的虚拟类型名。
    1.3在类的声明前加一行:
    template <class 虚拟化参数(如:numtype)>
    1.4用类模板定义对象时用以下形式:
    compare <int> comp1(1,2);
    1.5在模板外定义成员函数,应写成类模板的形式。
    template<class numtype>
    numtype compare<numtype>::mean(){}

    C++类模板其他

    1.1类模板的类型参数可以有一个或者多个,每个类型前面都必须加class,如:
    template<class T1,class T2>
    class someclass
    {...};
    在定义对象的时候分别带入实际的类型名,如:
    someclass <int,double> obj;
    1.2和使用类一样,在使用类模板的时候要注意其作用域,只能在其有效作用域内用它定义对象。
    1.3模板可以有层次,一个类模板可以作为基类,派生出派生类的模板类。




    
    
    
    
    展开全文
  • python类与c++类对比

    千次阅读 2018-08-24 22:05:05
    写在前面 本文将从类的角度讨论python和c++在...先看c++类的一个简单的例子: class A { public: int i; void test() { std::cout &lt;&lt; i &lt;&lt; endl; } }; ..... A a; a.i = 1...

    写在前面

    本文将从类的角度讨论python和c++在语法层面和使用层面的不同

    主要内容

    语法方面:

    先看c++类的一个简单的例子:

    class A {
    public:
        int i;
        void test() {
            std::cout << i << endl;
        }
    };
    
    .....
    A a;
    a.i = 100;
    a.test();
    
    输出100

    这个A类很简单,只有一个数据成员i和一个test函数用于输出i的值。
    但是从语法上当我们以实例对象a调用test成员函数的时候,这个函数内部是如何找到这个i的?也就是说当执行test这段函数的代码的时候如何找到这个和a对象绑定的i的?

    原因很简单就是我们经常说的this指针。c++的编译器会将上述的代码加工出现以下的半成品:

    class A {
    public:
        int i;
        void test(this) {
            std::cout << this->i << endl;
        }
    };
    
    .....
    A a;
    a.i = 100;
    a.test(&a);

    编译器会将默认的this指针当做非静态成员函数的一个默认的参数,帮我们添加上去,在使用对象调用的时候将对象的地址传递进去,在成员函数内部对类内部的成员都会添加上this->调用,这样就很容易区分不同对象的调用访问的是各自对象的空间的内容。

    c++ 的编译器帮助我们完成this指针的传参,添加修饰的工作,不需要我们去写。

    对于这个情况python是如何处理的呢?

    如果按照c++的方式将得到以下的代码:

    class A:
        i = 10;
        def test():
            print(i)
    
    a = A()
    
    a.test()

    以上的代码是无法通过的,首先一个问题就是在方法的调用上出错。执行报错会显示,test()实际上是没有参数的但是我们传递给他了1个参数。这是为什么?

    原因在于如果你是采用对象.方法的方式调用对象的方法python也会像c++一样帮你传入一个标识是哪个对象调用的变量叫做self,这个self是当前对象a的引用,也就是他也指向a所指向的内存空间上的那个对象。虽然看起来我们什么参数也没有传递但是python的解释器也会帮我们做这样的事情以协助其区分不同的对象。而上面这段代码在定义test函数的时候就没有给test写上self参数,结果我们也可以看出python虽然帮助我们在调用的时候传入引用给self但是其并没有在定义的时候帮助我们添加这个参数,这和c++是不一样的。更改之后我们得到如下的版本:

    class A:
        i = 10;
        def test(self):
            print(i)
    
    a = A()
    
    a.test(

    执行这段代码之后还是报错:在test函数当中并没有找到变量i的定义。这又是为什么的?

    还是同样的原因,因为在定义类的方法的时候,python没有帮助我们添加self在变量i的前面,当然self是test的参数这个self是约定俗成的名称,可以换,所以既然在函数的参数当中python就没有给你添加就自然也不会再函数内部添加了。所以如果这个i指的是类当中定义的变量那么就必须加上self.修饰告诉python解释器。
    最终修改后得到版本:

    class A:
        i = 10;
        def test(self):
            print(self.i)
    
    a = A()
    
    a.test(

    成功的输出了10。

    python和c++在类对象调用方法上都是采用传入实例对象的指针或者引用的方式区分不同的调用对象的,但是在实际的语法形式上是略有不同的,尤其是python,这一点需要注意。

    一个有意思的现象:

    以下这段代码会输出什么?1还是2?买定离手~

    class Test(object):
      i = 1
      def test_print(self):
        print(i)
    
    t = Test()
    i = 2
    t.test_print()

    答案是输出2。

    反正我第一次看到是想错了,对于用c++比较多的我来说先入为主的观念还是比较的重的,首先这个i是没有加self.修饰的这个i就不是指类Test当中定义的那个i,所以当对象t调用test_print方法的时候虽然将t的引用传给了self但是此时在函数的内部是没办法精确找到这个i的,因为这个i在函数内部是没有被定义的。

    此时就要扯到python的另一个语言特性了,访问引用的方式:
    对于一个引用符号python有两种访问的方式
    - 直接引用:直接使用引用的名字进行访问,Python按照搜索LEGB作用域的方式搜索名字。
    - 间接访问:使用objname.attrname的方式引用名字attrname,Python不搜索作用域,直接去对象里找属性。

    上述的方式就是对i进行直接访问!直接访问是怎么访问的?不知道的可以看这篇博客:ython进阶_关于命名空间与作用域(详解)

    总之这个i是往函数外层的嵌套作用域去寻找这个变量的定义自然就在全局区域找到这个i,所以输出的是2。

    上面这个性质是个坑,大家小心避雷。

    python 当中的方法和函数的概念是c++当中不一样的概念。

    c++类的成员函数分为静态成员函数和普通成员函数(先不考虑访问限定)。通过类名可以直接访问静态的成员(变量和函数),但是不能访问普通的成员和函数,普通的函数和成员是属于对象的需要this指针才能调用,而类名是没有的。

    对于python,是没有静态非静态的概念的,python的类当中定义的函数,通过实例调用就是实例的方法,通过类名调用就是一个函数。函数和方法不是一个类型。方法是被实例对象调用的,会被默认传入一个对象本身的引用给self参数,而通过类名调用的是从属于类的函数对象,这个函数在被调用的时候需要显示的传入实例对象作为参数。这两种调用的方式产生的结果是一样的。x.f() 等价于 MyClass.f(x)。

    python当中类是一个抽象的对象,实例是类对象。python当中一切皆对象所以类本身也是对象,c++当中类就是一段抽象的代码段,类并不是对象,c++当中的类是一个作用域,通过这个作用域只能访问到静态的信息。python当中的类就是一个抽象对象,他描述整个类的抽象信息。
    做一个测试:

    class A:
        pass
    a = A()
    print(type(A))
    print(type(a))
    
    输出
    <class 'type'>
    <class '__main__.A'>

    输出的结果我们可以看出 类A的类型是一个类型类,a的类型是A类。也就是说A这个类型对象是包含类信息的对象他的使用自然和实例对象是不同的。

    构造函数,析构函数是c++类的特色,c++编译器会给类添加默认的无参构造函数,拷贝构造函数等等,python呢?

    python并没有构造函数析构函数这一概念毕竟内存管理是交给GC的不是程序员管理的,但是python有一个默认的初始化方法__init__,默认python会提供一个无参的__init__方法(这里无参不包含self),但是我们可以改写这个方法,定义自己的方法,添加参数进行合理的初始化,但是切记python只有重写没有重载,一个变量名只能引用一个对象,所以使用相同的符号根据参数不同调用不同的函数在python当中是不存在的!!!所以一旦重写原来的就没有了!!!而c++当中则不是虽然我们一旦重载了构造函数编译器就不提供无参构造函数,但是没关系我们还可以继续重载写多少个随意。

    值得一提的是这个__init__ 方法的调用和c++调用构造函数是一样的都是在实例化对象的时候通过类的名字调用的。

    c++的继承和python的继承有啥不一样?

    一个有 “class” 的语言如果没有继承就没有多大的价值了,所以python当然支持继承,不但支持继承还支持多继承这和c++ 是一样的不像Java果断的舍去了类的多继承开辟接口去了。

    python的继承语法如下:

    class DerivedClassName(BaseClassName):
        <statement-1>
        .
        .
        .
        <statement-N

    BaseClassName 的定义对于派生类而言必须是可见的.

    python的继承有几个特点语法上和c++略有不同,同时在派生类对象调用方法的时候和c++是一样的先看派生类本身有没有定义没有就去父类找。
    其他的性质和c++基本一样。

    Python 支持多重继承. 一个多重继承的类定义看起来像这样:

    class DerivedClassName(Base1, Base2, Base3):
        <statement-1>
        .
        .
        .
        <statement-N>

    python当中所有的类都是从object派生的,c++是没有这样的一种概念的因为c++并不是一个纯面向对象的语言,而python和Java一样通过一个所有类的基类来抽象出所有类的抽象信息方便用户使用也方便实现一些高级的机制。这是c++所不具备的高级的机制,但是这样的机制也使得这些语言需要给每个对象付出额外的空间去保存这些信息同时也使得执行的效率降低。

    对于访问控制两者的区别在哪里?

    c++自不用多说,可以通过public private protected 这样的访问控制关键字来限定成员的访问权限。python当中并没有这样的限定,并不存在那种无法访问的私有的变量,但是python当中有一个约定:

    以一个下划线带头的名字 (如 _spam) 应该作为非公共的 API (不管是函数, 方法或者数据成员). 这应该作为具体的实现, 而且变化它也无须提醒.

    也就是说这样的成员你想访问还是可以访问到也可以修改,但是他本身不是提供给外界使用的只是实现这个类的人员为了实现某些功能时定义的子函数或者变量。

    python类的特殊性质

    python毕竟是动态语言,可以动态往对象里添加属性,这是python作为高级语言的特色是c++类所不具备的。往对象你添加的属性属于对象。

    class A:
        pass
    
    
    a = A();
    
    a.name = "kobe"
    a.age = 40
    a.like = "basketball"

    迭代器

    这就比较的有意思了,c++的类是不支持迭代器,c++只有STL当中的容器是支持迭代器的,迭代器就是一种封装了指针的智能指针,提供遍历容器的一种方式。在c++当中迭代器是属于容器的一个概念。

    Python当中呢,迭代器不仅可以作用于容器还可以作用于类。你还可以自定义遍历一个类对象时迭代器迭代的方式得到的结果。而且作为高级语言python将迭代器直接嵌入到for …in…的语法当中。
    具体语法看文档

    展开全文
  • C#调用托管C++类

    千次阅读 2020-04-02 11:37:28
    由于C#编写的是托管代码,编译生成微软中间语言,而C++代码则编译生成本地机器码(这种C++也有叫做本地C++或者非...但是用过这种方法的人都知道这种方法对于导出函数还可以但是却没法导出非托管C++类!非常的要命。...
  • C++类方法调用

    千次阅读 2019-10-29 09:23:09
    C++类内部方法调用时,一般都是先去定义一个类的变量,实例化之后再通过类对象去调用类内部的函数。在项目中发现另一种比较方便的方法,不需要定义类对象,话不多说直接上代码。 实例代码如下: #include <...
  • C++类和对象数组

    万次阅读 多人点赞 2018-05-14 23:47:15
    C++类和对象数组 【1】对象数组 1:对象数组的定义:类名 数组名[元素个数] Eg: student asa[10];//有10个元素的学生类对象数组 2:对象数组的访问形式:数组名[下标].成员名; Eg: asa[j].print(); 3:对象...
  • C 调用 C++

    千次阅读 2018-09-14 15:57:44
    现在有个Person,其中成员函数分别用于获取 ...我们希望可以在 C 代码中调用 C++ 的方法。 person.h class Person { public: Person(std::string name, int age); ~Person() {} const char *GetName...
  • C#调用C++类指南

    千次阅读 2017-06-29 10:58:33
    C#调用C++类比较麻烦,有两种方法,一种是原生C(Native C),即必须把C++的成员函数封装成C的全局函数(称为C bindings),然后再供C#调用。一种是托管C++(Managed C++)或者叫C++/CLI,可以识别C++的类和指针,...
  • 认识和理解C++类(或C++类浅识)

    千次阅读 2013-09-26 11:26:20
    认识和理解C++类(或C++类浅识) 参考资料:《Visual C++教程》 郑阿奇主编 类是面向对象程序设计的核心,它实际上是一种新的数据类型。类是对某一类对象的抽象,而对象是某一种类的实例。因此,类和对象是密切...
  • C++类和结构体 | 类与结构体类型

    千次阅读 2021-01-17 15:45:40
    C++类与结构体类型 C++与C语言不同,在C语言的基础上增加了class类型后,仍保留了结构体类型struct,而且把它的功能也扩展了,允许用struct来定义一个类型,可以将前面用关键字class声明的类类型改为用关键字struct...
  • C++类对象和类指针的区别

    千次阅读 多人点赞 2019-04-17 20:57:28
    一篇转载率较高的帖子:C++ 对象和 指针的区别 大佬都不贴结果。。。。所以这里我给出代码和结果: #include<iostream> using namespace std; class Test{ public: int a; Test(){ a = 1; } }; ...
  • c++类的不同继承方式

    千次阅读 2017-12-23 15:53:50
    c++类中成员有3种访问属性:public、private和protected,派生类对基类的继承方式同样是这3种。c++类对一个继承而来的成员的访问权限受两个因素影响:   (1) 该成员在基类中的访问权限说明符   (2) 派生类的...
  • #c++类的实例

    千次阅读 2018-09-24 23:53:33
    #c++类的实例 这是一个c++类的简单实例 该实例实用类实现两个值的交换 虽然非常low 但对于理解类是很有帮助的 #include &lt;iostream&gt; #include &lt;string&gt; using namespace std; class tran...
  • QML 与 C++交互 - 01QML实例化C++类

    千次阅读 2018-08-21 15:16:33
    本系列会介绍几种QML与C++进行数据交互的方法,包括信号槽的链接,QML调用C++类的方法等。 本文为第一篇:QML访问C++属性。可以点击这里访问官方示例。 在QML页面输入内容时,可以发现C++函数被触发: 优点: ...
  • 1.软件环境: 版本:5.6.1 操作系统:win10家庭版64位 MSVC版本:2013(32bit)2.问题描述:C++类向导基类选择时,无“QDialog&amp;quot;选项 3.解决办法:在类名一栏填上自己QDialog就可以了...
  • c++类的嵌套定义

    千次阅读 2017-09-26 12:26:22
    c++类的定义及嵌套定义和使用
  • C++ 详解(Plus)

    万次阅读 多人点赞 2020-09-10 22:23:43
    如果了解其他OOP术语,就知道C++类对应于某些语言中的对象类型,而C++对象对应于对象实例或实例变量。 下面更具体一一些。前文讲述过下面的变量声明: int carrots; 上面的代码将创建一个类型为int 的变量(carrots)。...
  • VScode中添加C++类头文件

    千次阅读 2020-03-16 10:09:02
    VScode中添加C++类头文件 VScode添加类的头文件时,编译器不会给我们自动链接到指定的头文件,因此需要在配置一下编译文件。配置好的C++环境有launch.json, settings.json和tasks.json三文件,只需要在tasks.json...
  • C++类的存储及类对象内存结构

    万次阅读 多人点赞 2016-07-22 16:06:12
    C++类的存储 c++中最重要的就是类,那么一个类的对象,它在内存中如何存储的?它占 内存中多少个字节? 首先确定类的构成: 1,数据成员:可以是内置类型,类类型。 2,函数成员:虚函数,非虚函数 1)数据...
  • C结构体、C++结构体 和 C++类的区别

    千次阅读 多人点赞 2016-06-14 09:34:32
    C结构体、C++结构体基本相同,C++类主要是方法的实现。 结构体是数据类型的集合 类是数据类型加方法的集合,基本如此,更注重方法。 1.C的结构体和C++结构体的区别 (1) C的结构体内不允许有函数存在,...
  • C++类指针初始化

    千次阅读 2017-12-29 14:22:17
    C++ 指针定义的时候没有初始化的时候,居然可以安全的调用内部的成员函数而不出错。 在网上查了一下: 初始化为NULL的指针可以安全的调用不涉及成员变量的成员函数而不出错,但是如果成员函数中...
  • C++ Class Size (C++ 大小)

    千次阅读 2014-07-24 09:55:35
    C++ Class Size (C++ 大小)  2013-03-17 20:05:34| 分类: C++ | 标签:c++ class size 大小  |举报 |字号 订阅 最近在写B+Tree的时候突然发现对节点的大小没算准,导致不知道固定...
  • 删除ue4中c++类

    千次阅读 2017-09-13 15:14:06
    删除一个C++类 该方法是从UE4的answerhub上摘选的。本教程介绍了从项目中删除一个C++类所需要的步骤。 删除一个指定的C++类 为了从你的项目中删除一个C++类,请遵循以下步骤: 1. 关闭Visual Studio 2. 关闭UE4...
  • C++类(Class)的定义与实现

    万次阅读 多人点赞 2018-08-01 17:41:17
     在C++中, 用 "" 来描述 "对象", 所谓的"对象"是指现实世界中的一切事物。那么就可以看做是对相似事物的抽象, 找到这些不同事物间的共同点, 如自行车和摩托车, 首先他们都属于&...
  • 8.DLL导出C++类

    千次阅读 多人点赞 2018-08-31 00:13:23
    DLL中不仅可以导出函数和变量,也可以导出C++类。只需要在导出类名前关键字class后加上_declspec(dllexport),就可以实现导出类 1.DLL简单导出类代码 class _declspec(dllexport) Stu { public: Stu(int a); ...
  • JNA调用c++类方法

    千次阅读 2018-10-12 17:21:12
    对于java调用dll来说,JNA是JNI的超级升级版,这意味着可以抛开沉重的模板,基本可以实现直接调用dll 网上对于JNA的使用,大多集中于调用直接方法,以及模拟结构体等问题,... 关于JNA如何调用c++类方法,找了半天...
  • 主要参考: http://c.biancheng.net/view/215.html ... 一、C++类的定义和使用 #include "stdafx.h" #include &lt;iostream&gt; using namespace std; class CRectangle ...
  • C++类模板和模板类

    万次阅读 多人点赞 2018-11-29 14:32:35
    C++通过模板来实现泛型支持。 1 基础的模板 模板,可以定义相同的操作,拥有不同数据类型的成员属性。 通常使用template来声明。告诉编译器,碰到T不要报错,表示一种泛型. 如下,声明一个普通的模板: ...
  • 【C++】C++类的学习(一)——初识类

    万次阅读 多人点赞 2018-03-24 13:57:55
    ... 前言  C++在C语言的基础上做了一些改进,使得C++具有了面向对象编程(Object Oriented Programming,OOP)的特性。...可以说学习了C++却不会使用的话,那么就没有学习到C++的精髓。  在接...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 209,984
精华内容 83,993
关键字:

c++类

c++ 订阅