目录
c++编译器: 从上往下, 从左往右
概述:
- 创建派生类对象时, 程序先调用基类构造函数, 再调用派生类构造函数;
- 释放对象的顺序与创建对象的顺序相反, 即首先执行派生类的析构函数, 然后自动调用基类的析构函数; 派生类构造函数总是调用一次; 可以使用初始化器列表语法指明要使用的基类构造函数, 否则将使用默认的基类构造函数; 除非是虚基类(不能被实例化, 或者重写所有的纯虚函数);
- 基类可以 指向|引用 派生类对象, 反过来不行;
- 继承可以在基类的基础上添加属性, 但不能删除基类的属性;
- 不能继承基类的: 构造函数、析构函数、赋值运算符、友元函数
组合和聚合:
组合理解:
电脑和CPU, 生命周期一致;
初始化: 初始化列表中使用成员名, 而不是类名; 构造函数调用顺序根据类中定义顺序有关, 谁先定义, 就先调用谁的构造函数
聚合理解:
电脑和音箱, 可有可无;
私有继承、保护继承, 多重继承:
私有继承(private):
1. 访问基类的友元函数, 可以通过显示地转换为基类来调用正确的函数;
2. 未进行显示类型转换的派生类 引用|指针, 无法赋给基类的 引用|指针;
保护继承(protected):
假设要让基类的方法在派生类外面可用:
方法一: 定义一个使用该基类方法的派生类方法;
方法二: 在派生类public部分中使用一个using声明来指出派生类可用使用特定的基类成员
注意, 使用using声明只使用成员名---没有圆括号、函数特征和返回类型; 声明只使用于继承, 而不适用于组合;
方法三: 使用作用域解析运算符来调用;
多重继承: //太多了, 去前面那个链接看
继承使用注意事项:
1. 重新定义继承的方法不是重载, 如果派生类中重新定义函数, 将不是使用相同的函数特征覆盖基类的声明, 而是隐藏同名的基类声明, 而是隐藏基类的同名方法, 不管参数特征如何
两条规则
(一)、 重新定义继承的方法, 应确保与原来的原型完全相同, 但如果返回类型是 指针|引用, 则可以修改为指向派生类的引用或指针(类型协变), 因为允许返回类型随类类型的变化而变化;
(二)、 如果基类声明被重载了, 则应在派生类中重新定义所有的基类版本, 避免被覆盖;
2. 当派生类存在与基类同名的成员变量时候,派生类的成员会隐藏基类成员(不可见), 使得基类这个成员是一个未初始化的值(尽量和基类的变量名区分开, 避免问题);
3. 如果派生类重写了基类的方法, 如 果想调用基类的方法, 在类方法中通过作用域解析运算符, 在外面通过 派生类.基类类名::方法
区分显示构造和隐士构造:
隐式构造: string a = "abc";
显示构造:string a = string("abc"); 或者 string a("abc");
CODE:
#include <iostream>
#include <string>
using namespace std;
class student {
public:
explicit student(int _age)//默认隐式构造implicit
{
age = _age;
cout << "age=" << age << endl;
}
student(int _age, const string _name)
{
age = _age;
name = _name;
cout << "age=" << age << "; name=" << name << endl;
}
~student()
{
}
int getAge()
{
return age;
}
string getName() {
return name;
}
private:
int age;
string name;
};
int main(void) {
student xiaoM(18); //显示构造
//student xiaoW = 18; //隐式构造
//student xiaoHua(19, "小花"); //显示构造
//student xiaoMei = { 18, "小美" }; //隐式构造 初始化参数列表,C++11 前编译不能通过,C++11新增特性
system("pause");
return 0;
}
多态(virtual):
程序将根据 引用|指针 指向的对象的类型来选择方法, 如果对象的类型是基类, 则调用基类的方法, 如果对象类型是派生类, 则调用派生类的方法;
虚析构函数: 基类声明一个虚析构函数, 这样为了确保释放派生对象时, 按正确的顺序析构函数
如果用基类的 指针|引用 指向派生派生类对象, 如果基类析构函数不是虚的, 则将只能调用对应于指针类型(基类)的析构函数
在派生类方法中, 标准技术是作用作用域解析运算符来调用基类的方法了。
如果某个成员函数被声明为虚函数,那么它的子类【派生类】,以及子类的子类中,所继承的这个成员函数,也自动是虚函数
如果在子类中重写这个虚函数,可以不用再写virtual, 但是仍建议写virtual, 更可读!
CODE1:
#include<iostream>
using namespace std;
class A
{
public:
A() { cout << "A::构造函数" << endl; };
A(int& _x, double& _y) : x(_x), y(_y) { cout << "A::构造函数" << endl; };
virtual ~A() { cout << "A::析构函数" << endl; };
void getX() const { cout << "A::getX" << endl;};
static void printfs(){ cout << "A::printfs" << endl; }
public:
int x;
double y;
static int z;
};
int A::z = 520;
class B : public A
{
public:
B() { cout << "B::构造函数" << endl; };
B(int& _x, int& _z, double& _y) :A(x, y), z(_z)
{
x = _x;
y = _z;
cout << "B::构造函数" << endl;
};
virtual ~B() { cout << "B::析构函数" << endl; };
void test()
{
// 在派生类方法中, 标准技术是使用作用域解析运算符来调用基类的方法
A::getX();
cout << "A::z = " << A::z << endl;
cout << "A::x = " << A::x << endl;
};
static void test1() { cout << "B.test" << endl; };
public:
/*int x;
double y;*/
int z;
};
int main()
{
int x = 1, z = 3;
double y = 2.1;
B b(x, z,y);
b.test();
//B::x; // 错误
//B::test(); // 错误
//A::x; // 错误
//A::test() // 错误
B::test1();
A::printfs();
cout << A::z << endl;
cout << "x = " << b.x << endl;
cout << "y = " << b.y << endl;
cout << "z = " << b.z << endl;
system("pause");
return 0;
}
CODE2:
#include<iostream>
using namespace std;
class A
{
public:
A() { cout << " A构造" << endl; };
~A(){ cout << " A析造" << endl; }
};
class B : public A
{
public:
B() { cout << " B构造" << endl; };
~B() { cout << " B析构" << endl; }
};
int main()
{
//A a(); // !!!注意: 这样写的话,他把它认成定义函数了
A* a = new B;
delete a;
system("pause");
return 0;
}
静态联编和动态联编:
程序默认静态联编, 目的: 减少额外开销
静态联编: 编译过程, 编译器对非虚函数使用静态联编
动态联编: 程序运行
向上强制转换: 派生类 引用|指针 转换为基类 引用|指针 可传递的
向下强制转换: 基类 引用|指针 转换为派生类引用|指针 不可传递的
虚函数的工作原理(虚函数表):
对象内,首先存储的是“虚函数表指针”,又称“虚表指针”。然后再存储非静态数据成员;
对象的非虚函数,保存在类的代码中!对象的内存,只存储虚函数表和数据成员, 类的静态数据成员,保存在数据区中,和对象是分开存储的;
添加虚函数后,对象的内存空间不变!仅虚函数表中添加条目, 多个对象,共享同一个虚函数表!
补充:
子类的虚函数表的过程:
- 第一步: 直接复制父类的虚函数表;
- 第二步: 如果子类重写了某个虚函数, 那么就在这个虚函数表中进行相应的替换;
- 第三步: 如果子类增加了新的虚函数, 就把这个虚函数添加到虚函数表中(在尾部添加);
成本:
- 每个对象都将增大, 增大量为存储地址的空间;
- 对于每个类, 编译器都创建一个虚函数地址表(是数组, 不是链表);
- 对于每个函数调用, 都需要执行一项额外的操作, 即到表中查找地址;
注意事项:
- 友元函数不能是虚函数, 因为友元函数不属于类, 而只有成员才能是虚函数
- 解决方案: 可以通过友元函数使用虚成员函数来解决
- 参考:http://t.csdn.cn/2vgyo
抽象基类:
多学学设计模式: http://t.csdn.cn/k2pc9
- 包含纯虚函数的类,就称为抽象类, 不能被实例化
- 在原型中使用 =0 指出类是一个抽象基类, 在类中可以不定义该函数(也可以定义)
静态类成员及函数:
静态类成员:
无论创建多少对象, 程序都只创建一个静态类变量副本
为什么不能在类声明中初始化静态成员变量?
因为声明描述了如何分配内存, 但并不分配内存
为什么不能在类声明文件中初始化, 而在方法文件中初始化?
因为程序可能将头文件包括在其他几个文件中, 将出现多个初始化语句副本, 从而引发错误
静态类成员函数:
声明必须包含关键字static, 但函数定义是独立的, 则其中不能包含关键字static
静态成员只能调用静态成员函数, 不能使用this指针
可以使用类名和作用域解析运算符来调用它(静态成员函数和静态成员)