C++ 的类的重要特性

C++类 赋值函数 和 拷贝构造函数

拷贝构造函数是初始化 未初始化的内存, 赋值是对现有已经初始化的对象操作

C struct, C++ struct,C++ class

  • C需要用 struct Foo foo 这样的方式来定义变量,经常使用 typedef FooType Foo 这样的语法来简化书写, C++中 struct 会被当成一种类型, 可以直接 Foo foo 来定义变量,

  • C不支持struct的 可见性限制(visibility restriction), 而C++支持可见性,C++中struct和class都支持

  • C++ 中 class 和 struct 几乎没有区别,除了默认继承和默认可见性,class默认继承和默认可见性都是 private,而struct默认继承和默认可见性都是 public

  • C 中的struct没有办法写构造函数、成员函数等,只能用其他函数来模拟

C++类的继承

首先了解三个概念

重载 (Overload)

同一个函数,返回值和参数列表需要同时不同

double f(int a);
int f(int a); // 这样是错误的,因为参数列表相同无法返回不同的值
覆盖 (Override)

父类是virtual的时候,子类不管是不是virtual都会重写该函数

隐藏 (Hide)

相当于屏蔽,父类子类如果没有virtual,父类和子类同名函数都会被子类屏蔽,不管参数列表是否相同

看下面的例子


class Base {  
public:  
    virtual void f(float x) { }
    void g(float x) { }
    void h(float x)  { }
};
class Derived : public Base {  
public:  
    virtual void f(float x) {
        // 子类父类的函数都是virtual,覆盖父类函数
    }  
    void g(int x) {
        // 和父类参数列表不同,隐藏父类函数
    }  
    void h(float x) {
        // 父类子类都是没有virtual修饰,同参数列表同返回值,隐藏父类函数
    }
};

没有virtual修饰的函数是在编译时就确定,而虚函数则在运行的时候才体现多态性

虚函数表 (Virtual-Table)

C++中的虚函数的实现一般是通过虚函数表, 大部分的编译器厂商都选择此方法

类的虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址。
注意的是,编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类所有对象共享。类的每个虚成员占据虚函数表中的一行。如果类中有N个虚函数,那么其虚函数表将有 N*4 字节的大小

虚函数(Virtual Function)是通过一张虚函数表来实现的。简称为V-Table。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中分配了指向这个表的指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数

编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

每个含有虚函数的类,都有一个虚函数表指针(virtual table pointer),指向virtual table(以下简称VT),VT是一个函数指针的数组,运行时就会在这里寻找该调用那个函数。

在32位机器中,指针是4byte,所以每个带有虚函数的类,是数据对齐后的大小加上虚函数大小之和,每个虚函数代表一个vptr,所以 sizeof(class) 的输出应该是alignedSize + vptrCount * 4,如果在64位机器里,指针大小是8byte,那就是乘以8啦

指针数组和数组指针

  1. 数组指针
int a[2][2] = {
    {1,2},{3,4}
};
int (*p)[2];
p = a;
cout << sizeof(p) << endl; // 指针的大小,32位机是4,64位是8
  1. 指针数组
int a[2][2] = {
    {1,2},{3,4}
};
int* p[2];
for (int i = 0; i < 2; i++)
    p[i] = a[i]; // 需要分别赋值
cout << sizeof(p) << endl; // 数组的大小,2个指针的大小

操作符

详情参考wiki

看一个有趣的例子, .* 操作符

reference

y is a pointer to a member-type inside the type of *x, see the example below.

struct Obj { int m; };


Obj        o;  
Obj *      p = &o; 

int Obj::* y = &Obj::m;

// 'y' can point to any int inside Obj, currently it's pointing at Obj::m
// do notice that it's not bound to any specific instance of Obj

o.m = 10; 

std::cout << (p->* y) << std::endl; 
std::cout << (o .* y) << std::endl;
output
10
10