构造和析构次数:
A* p =new A[5];// 构造五次
delete[ ] p;// 析构五次
delete p ; // 若不使用 [ ] ,则析构一次。
假设 A 是一个类,像这样 A a(); 并不是表示调用构造函数创建对象,只是定义了一个函数而已。
绝不重新定义继承而来的缺省参数。
若父类没有无参的构造函数,子类需要在自己的构造函数中显式调用父类的构造函数。
构造函数中可以调用虚函数
class Base { public: Base() { Function();} virtual void Function() { cout << "Base::Function" << endl;} }; class A :public Base { public: A() { Function();} virtual void Function() { cout << "A::Function" << endl;} };
另外:这样定义一个 A 的对象, A a; 会输出
Base::Function
A::Function
而不是
A::Function
A::Function
要知道在 Base 的构造函数中调用 Fuction 的时候,调用的是 Base 的 Function。因为此时 A 还没开始构造。
静态成员变量可被该类的所有方法访问。
子类型必须是子类继承了父类的所有可继承特性,也即公有继承,才能说是子类型,否则就只是单纯的子类。
重载、重写、隐藏:
1. 重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。(它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!)
2. 重写(覆盖):是指子类重新定义父类虚函数的方法。其函数名、参数列表、返回值类型,所有都必须同基类中被重写的函数一致,只有函数体不同(花括号内)。(和多态真正相关)
3. 隐藏:隐藏是指派生类的函数屏蔽了与其同名的基类函数。注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏。(和多态无关)
重载和重写的区别:
1). 范围区别:重写和被重写的函数在不同的类中,重载和被重载的函数在同一类中。
2). 参数区别:重写与被重写的函数参数列表、返回类型一定相同;重载和被重载的函数参数列表一定不同,不关心函数返回类型。
class A { int function(int i); int function(int i) const; // 第一个函数的重载 int function(const int i);// 编译错误,这里不是重载第一个函数 };
分析:
首先 int function(inti)const; 这个函数的形参表和第一个函数是不一样的,因为在类中隐含this 形参的存在。这里加上 const 修饰的是 this 指针,则 this 指针的类型就变为指向const 对象的指针。因此,使用指针传参时,指向const 对象的指针和指向非const 对象的指针做形参的函数是不同的。
然后 int function(constint i); 这个函数无法重载第一个函数,因为这个函数和第一个函数对于重载来说是等价的。对于非引用或指针传参,形参是否const 是等价的,但对于引用或者指针形参来说,有无const 是不同的。
3).virtual的区别:重写的基类必须要有virtual 修饰,重载函数和被重载函数可以被virtual 修饰,也可没有。
隐藏和重写,重载的区别:
1). 与重载范围不同:隐藏函数和被隐藏函数在不同类中。
2). 参数的区别:隐藏函数和被隐藏函数参数列表可以相同,也可以不同,但函数名一定同;当参数不同时,无论基类中的函数是否被virtual 修饰,基类函数都是被隐藏,而不是被重写。
总的来说:隐藏和重写,要在不同的类中,如果不满足重写,那就是隐藏。
友元函数最后面不能加 const ,在函数后面加 const 只适用于成员函数。
虚函数:
虚函数的底层是通过虚函数表实现的。
虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数(static)
1. 直接继承
1)每一个具有虚函数的类都有 1 个虚函数表 VTABLE(如果子类有自己的虚函数,那么子类的虚表里面存放继承来的虚函数地址和自己虚函数的地址),里面按在类中声明的虚函数的顺序存放着虚函数的地址,这个虚函数表 VTABLE 是这个类的所有对象所共有的;
2)在每个具有虚函数的类的对象里面都有一个 VPTR 虚函数指针,这个指针指向它自己类的 VTABLE 的首地址,每个类的对象都有这么一种指针。
2. 虚继承
C++开发的时候,用来做基类的类的析构函数一般都是虚函数。可是,为什么要这样做呢?对于虚继承,若派生类有自己的虚函数,则它本身需要有一个虚指针,指向自己的虚表(虚继承的时候,子类自己的虚函数单独有个虚表,然后各个父类的的虚函数分别对应一个虚表)。另外,派生类虚继承父类时,首先要通过加入一个虚指针来指向父类,因此有可能会有两个虚指针。
类对象的大小 = 各非静态数据成员(包括父类的非静态数据成员)的总和+ vfptr指针(多继承下可能不止一个)+ vbptr指针(多继承下可能不止一个)+ 编译器额外增加的字节(对齐)。
虚析构函数的作用:当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。
多态需要通过父类的指针或者引用来实现。
如果父类里的虚函数是 private 的,子类依然可以实现多态。虽然父类这个虚函数是 private,但,父类指针或引用可以通过自己的public 方法调用这个私有的虚函数。当然,子类重写的这个虚函数并不是继承来自这个父类的,但是可以实现多态。
多继承问题:
class A {
public:
int x;
A(int X) : x(X) {}
virtual void print() {
cout << "A" <<endl;
}
};
class B {
public:
int x;
B(int X) : x(X) {}
virtual void print() {
cout << "B" <<endl;
}
};
class C : public A, public B {
public:
int x;
C() : A(1), B(2), x(3) {}
};
int main() {
C c;
cout << c.A::x << endl;// 输出 1
cout << c.B::x << endl;// 输出 2
cout << c.x <<endl;// 输出 3
c.print();// 这里会编译错误,歧义。因为继承了 A 和 B 的 print,无法确定调用哪个。此时,若在 C 中定义了一个 print,就可以这样使用
// 虽然 C 从 A 和 B 继承了 print 函数,但是由于 A 和 B 中都有 print,所以 C 的对象访问的时候要加上 A 或者 B 的域名
c.A::print();// 输出 A
c.B::print();// 输出 B
A* pA = &c;
B* pB = &c;
pA->print();// 输出 A
pB->print();// 输出 B
}
在重载 “+” 运算符中参数要加const,否则,连续相加会编译出错。同理 “- * / ” 也是。
class A {
int val;
public:
A(int v):val(v) {}
friend const A operator+(const A& L, const A& R) {
A temp(L.val + R.val);
return temp;
}
};
A a(1), b(2), c(3);
A d = a+b+c;
因为
operator+(…
) 返回值类型是
A,所以 a
+ b 求值后返回的是一个临时变量,接着这个临时变量再与 c 相加调用
operator+(…
),临时变量不能修改,所以需要在形参里用
const 引用的形式捕获。
返回值最好加上 const,否则, (a + b )= c ; 这样变态的代码也不会编译出错。同理 “- * / ” 也是。
不能被重载的操作符有:
“.” “::” “?:” “->” “sizeof” “#”(预处理符号)(小窍门:带点的操作符肯定不可以重载)