1 面向过程
关注的是如何解决问题,以及解决问题的步骤
2 面向对象
关注的是解决问题的对象
2.1 抽象
先找出(想象)能解决问题的“对象”,分析该对象解决问题所需要的属性(成员变量)和行为(成员函数)
2.2 封装
把抽象的结果封装成一个类,并给类成员变量、成员函数设置相应的访问控制属性(private/protected/public)
2.3继承
在封装类时,先考虑现有的类是否能够解决部分问题,如果有则把现有的类继承过来,在此基础上进行完善和扩展,从而缩短解决问题的时间
把一个复杂的问题拆分成若干个不同的小问题,每个问题封装一个类去解决,最后把这些类通过继承合并成一个能解决复杂问题的类,从而降低问题规模
2.4 多态
发出一个指令,系统会根据实际情况执行不同的相应操作,这种特征就称为多态(同一命令多种形态)
例如重载过的函数,当调用函数时,编译器会根据参数的类型、个数,决定调用哪个重载版本,这就是编译时多态
注意:面相对象的行为本质上还是面向过程,因此面向对象只是以更高的纬度去思考问题,而不是寻求解决问题的捷径
3 类和对象
3.1 什么是类
类是由程序员自己设计的一种数据类型,它里面可以包含成员变量、成员函数
3.2 什么是对象
对象是类的实例化,可以理解成使用类来创建的变量
① 类的设计和实例化
class 类名
{
成员变量; //类中默认属性是private
public:
成员函数;
};
② 实例化
方法1:类名 类对象;
方法2:类名* p = new 类名;
③ 类的声明、实现和调用
一般在头文件中声明:
class 类名
{
成员变量; // 类中的默认属性是private私有的
public:
返回值 成员函数(参数列表);
};
一般在源文件中实现成员函数:
返回值 类名::成员函数(参数列表)
{
// 在成员函数中可以直接使用成员变量,无需. ->
}
注意:如果类的成员函数内容不多,可以考虑直接在头文件中实现
④ 类中的访问控制属性
public | 公开的,被它修饰的成员变量、成员函数可以在类外任意位置访问,一般会把成员函数设置为public |
private | 私有的,被它修饰的成员变量、成员函数只能在类内访问,类的默认访问控制属性是private,一般为了确保属性的封装性会把成员变量设置为private |
protected | 保护的,被它修饰的成员变量、成员函数只能在类内和其子类中访问,但是不能在类外访问 |
4 构造函数
构造函数是类的同名成员函数,没有返回值,当实例化对象时它会自动被调用,一般负责对类对象进行初始化、分配资源。
class Test
{
int* p;
public:
Test(参数) // 可以重载
{
p = new int;
}
};
① 构造函数必须是public,否则无法创建对象
② 构造函数可以重载,可以有多个版本
③ 带参数的构造函数的调用
Test t(实参); //调用有参构造
Test* p = new Test(实参); //调用有参构造
注意:如果没有提供对应版本的构造函数则会报错
④ 默认情况下编译器会给类自动生成一个无参构造,该构造函数什么都不执行,一旦显示地实现了构造函数,则该无参构造不再自动生成,需要时得手动实现
Test t; // 可能调用无参构造
Test* p = new Test; // 可能调用无参构造
⑤ 也可以通过设置有参构造中的默认形参,形成调用无参构造的效果,实际调用的依然是有参构造,但是如果同时存在无参构造时,会产生歧义
⑥ 构造函数没有返回值
⑦ 不要使用malloc去给类对象申请内存,因为它不会自动调用类的构造函数
5 析构函数
析构函数负责对类对象进行收尾工作,例如:释放类中的资源、保存数据
当类对象销毁(自动、手动delete)时会自动调用
class Test
{
int* p;
public:
Test(参数) // 可以重载
{
p = new int;
}
~Test() // 析构函数
{
delete p;
}
};
① 析构函数也必须是public
② 析构函数没有参数、没有返回值、不能重载
③ 当类对象的生命周期到期前(栈、堆delete),会自动调用析构函数
④ 构造函数一定会执行,但是析构函数不一定会执行(没用delete)
⑤ 不要使用free释放类对象,它不会调用析构函数
⑥ 如果没有显式地实现析构函数,编译器会自动实现一个什么都不做的析构函数
6 初始化列表
初始化列表是类中构造函数的一种特殊语法,只能在构造函数中使用
class 类名
{
public:
类名():成员1(初始化数据),成员2(初始化数据)...
{
// 初始化的数据可以是常量、变量
}
};
① 在C++11之前语法标准中,类的成员变量不可以在定义时设置初始值,而在执行构造函数时成员变量已经定义完成了,因此带有const属性的成员变量就无法在构造函数中通过赋值设置初始值
② 初始化列表的执行先于构造函数,当执行初始化列表时类对象还没创建完成,此时通过初始化列表是C++98之前的语法中唯一一种给const属性的成员变量赋初值的方式,在C++11中初始化列表可以使用形参对成员变量初始化
③ 当形参名与成员名相同时,初始化列表可以自动分辨,可以同名
④ 如果有成员是类类型,它的有参构造可以在初始化列表中被调用
⑥ 如果有多个成员是类类型,它们在初始化列表中的构造函数调用顺序,只跟它们的成员定义顺序有关,与初始化列表中的顺序无关
7 对象的创建和销毁过程
7.1 对象的创建
① 给对象划分内存空间(栈/堆)
② 执行初始化列表
根据继承表中的继承顺序,默认执行父类的无参构造函数,也可以调用父类的有参构造,通过 :父类名(参数)
根据类类型成员的定义顺序,默认执行类类型成员的无参构造函数,也可以调用类类型成员的有参构造,通过 :类类型成员名(参数)
初始化其他普通成员
③ 执行构造函数,申请其他资源
7.2 对象的销毁
① 执行析构函数的内容,释放资源
② 根据类类型成员定义顺序的逆序,执行它们的析构函数
③ 根据继承表的继承顺序逆序,执行父类的析构函数
④ 释放对象的内存
成员函数是如何区别调用它的对象?(成员函数中有隐藏的this指针)
每个对象的内存中只存储了属于它的成员变量,没有存储成员函数指针
当对象调用成员函数时,编译器会自动把该对象的地址传递给成员函数的一个隐藏参数this指针,this指针参数是专门用于接收对象的地址
每个成员函数都有隐藏的this指针,this指针相当于拿到了调用对象的地址,就可以通过它访问该对象的成员,以此区分调用成员的对象是哪一个
this指针虽然是隐藏的,正常情况下不写就代表写了,但有些情况下,可以显式使用
注意:this指针参数不需要显式定义,直接使用即可