文章来源:https://justinwei.blog.youkuaiyun.com/article/details/32702601
1. 类的封装性和隐蔽性
将数据和这些数据有关的算法操作封装在一个类中,实现了类的封装性。
通过 private 和 public 将类中成员加上访问权限,外界只能通过 public访问,将private 成员实现了隐蔽;
2. 构造函数
- 创建对象时自动调用,对象的生命周期中只能调用一次;
- 如果没有显示定义构造函数,系统会自动生成一个无参数的构造函数,称为默认构造函数;当然所有的参数都有默认值时,该构造函数也称为默认构造函数(可以不传参数);一个类只能有一个默认构造函数;
- 构造函数名与类名必须相同;
- 构造函数可以在类体中定义;
另外,
- 在全局范围中定义的对象(即在所有函数之外定义的对象),它的构造函数在文件中的所有函数(包括main函数)执行之前调用。但如果一个程序中有多个文件,而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。全局对象在 main 函数执行完毕或者调用 exit 终止程序时,调用析构函数;
- 如果定义的是局部自动对象(例如在函数中定义对象),则在建立对象时调用其构造函数。如果函数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。
- 如果在函数中定义静态(static )局部对象,则只在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用析构函数。只在main函数结束或调用exit函数结束程序时,才调用析构函数。
3. 析构函数
- 析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作;
- 与类同名,在在前面加上 ~ 号;
- 析构函数不带任何参数;
- 一个类有且只有一个析构函数;
- 对象定义在函数内(自动局部变量),函数结束后,该对象的析构函数自动调用;
- static 局部对象只有在main 函数结束或调用 exit函数结束程序时,才对调用其析构函数;
- 对象通过 new 创建,在 delete 时会自动调用其析构函数;
- 编译器默认会创建一个析构函数,但不是 virtual 的;
另外,针对多态性,要求将基类的析构函数声明为 virtual:
- 基类的析构函数声明为虚函数,由该基类所派生的所有派生类的析构函数也自动称为虚函数;
- 将基类的析构函数声明为虚函数,保证对象撤销时系统采用动态关联,调用相应的析构函数;
4. 内置函数
用inline标志,有的地方称内嵌函数、内联函数。
- 函数声明或定义的地方在最左边加上inline就表示为内置函数。
- inline函数会缩小运行时间,但增加了目标程序的长度。
- inline函数适用于规模小但是会被频繁调用的函数。
- inline不能出现复杂的控制语句,如for、switch等。
在类体里面定义的函数默认认为是inline函数,类外定义的成员函数需要加上关键字inline,否则会认为类外的成员函数为非inline函数。
另外,如果在类外定义inline成员函数,需要跟类的声明在同一个文件中,一般放到头文件中。否则编译器无法进行置换(将代码拷贝嵌入到调用的地方)。
最后,真正是否为inline函数,需要看编译器,如果编译器认为函数比较复杂,不一定将函数作为inline。
5. 拷贝构造函数
拷贝构造函数需要满足两个条件:
- 构造函数的第一个参数为
- X &;
- const X &;
- volatile X&;
- const volatile X&;
- 没有其他参数或其他参数都有默认值;
使用拷贝构造函数的两种方式:
- X obj1 = obj2;
- X obj1(obj2);
哪些情况下是否拷贝构造函数:
- 一个对象以值传递的方式传入函数;
- 一个对象以值传递的方式从函数返回;
- 一个对象需要对另一个对象初始化;
详细可以查看:《c++ 拷贝构造函数》
6. this 指针
- 在定义对象的时候会产生一个 this 指针,指向对象的起始地址;
- this 指针不属于对象本身,因此不会改变sizeof();
- 在使用非静态成员函数时,会作为参数被隐式传递给成员函数;
- 静态成员函数只会访问静态成员,而静态成员不属于某一个对象,因此不会使用 this 指针;
- this 指针是编译器自动实现,不需要人为地在形参中增加this 指针,也不必将对象的地址传递给 this 指针;
- 在需要时可以显示使用this 指针;
7. 静态成员
7.1 静态成员变量
- 为了实现多个对象间的数据共享;
- 区别于全局变量,全局变量是整个程序都能看到,而静态成员只是实现对象间的共享;
- 如果静态成员变量为私有,必须通过公共的静态成员函数引用;
- 静态成员变量是针对所有对象的共享,而不是针对单个对象,必须要初始化,且在类外初始化(类中声明,类外初始化),初始化时无需加 static 关键字;
7.2 静态成员函数
- 可以通过对象访问,也可以通过类名访问;
- 静态成员函数只能访问静态成员(静态成员函数中没有this,而非静态成员函数中有this);
8. 对象的赋值和复制
赋值:
- 对象2 = 对象1;
对其中的数据成员赋值,赋值的过程是将一个对象的数据成员在存储空间的状态复制给另一个对象的数据成员所在的存储空间。
复制:
- 类名 对象2(对象1);
- 类名 对象2 = 对象1;
与赋值不同,赋值的两个对象是已经存在。而复制则是从无到有新建一个对象。
9. 友元
友元函数:
friend 返回类型 函数名(参数列表);
- 一般在类体中声明,加关键字 friend。在类体外定义时不需要加关键字;
- 函数名有两种情况:普通函数、类名::函数名(某个类的成员函数是友元函数);
- 虽然在类体中声明,但不是类的成员函数;
- 友元函数可以直接访问类的私有变量;
友元类:
friend class 类名;
- 友元类可以访问类中的所有成员;
- 友元不存在交换、传递性;
10. 继承和派生
10.1 基本概念
- A 继承 B,B 为 A 的基类,A 为 B 的派生类;
- 继承分单继承、多继承(基类之间用 逗号 隔开);
- 派生类是基类的具体,基类为派生类的抽象;
10.2 继承方式
- public 继承;
- protected 继承;
- private 继承;
基类的 private 成员是无法继承的。只能继承 public、protected 成员。
public 成员 | protected 成员 | private 成员 | |
public 继承 | public | protected | 无法继承 |
protected 继承 | protected | protected | 无法继承 |
private 继承 | private | private | 无法继承 |
派生类继承基类的所有方法,除:
- 基类的构造;
- 基类的析构;
- 基类的拷贝构造;
- 基类重载运算符;
- 基类的友元函数;
10.3 派生类构造函数的调用顺序
- 基类构造函数按照继承顺序调用;
- 调用成员对象的构造函数,按照对象在类中的声明顺序调用(与初始化列表中无关);
- 派生类的构造函数;
- 派生类的构造函数中对象,按照顺序调用;
更多的初始化信息,可以查看下面的第 11 点。
10.4 静态成员继承
- 继承方式不变,public 和 protected 会被继承,private 不会;
- 静态成员基类和派生类共享;
10.5 友元的继承
- 基类中的友元函数和友元类不能被继承;
- 基类中一个成员函数(不能private)是另一个类的友元,派生类依然是该类的友元;
11. 初始化列表
首先派生类构造函数的调用顺序:
- 基类的构造函数,按照声明时的顺序逐个构造;
- 按类中对象的声明顺序构造对象,与初始化列表中的顺序无关;
- 类自身的构造;
- 构造函数中对象按顺序构造;
基类中如果没有无参的构造函数,就必须在初始化列表中进行初始化。
如上面第 2 点,类中对象最好在初始化列表中赋初值,因为在类自身的构造函数中不是初始化,而是赋值,需要调用默认构造函数或重载赋值。那么,第 2 点中的构造就白做了,影响效率。
有些成员必须要在初始化列表中初始化:
- const 成员;
- 引用;
- 没有默认构造函数的成员;
12. 重载函数
重载函数的参数个数、类型可以都不同,但是不能只有函数的返回类型不同,而参数的个数、类型都相同。
int max(int)
long max(int)
这个调用哪个?编译系统无法判别使用哪个函数。
重载函数的参数个数、类型、顺序至少要有一个不相同。
13. 模板函数
template <typemane T> 或 template <class T>
T max(T a, T b)
函数模板只适用于参数个数相同,但类型不同的函数
详细查看:《c++ 中模板详解》
14. 有默认值的函数
int max(int a, int b=10)
调用的时候可以max(5),表示参数b为默认值10。或者是max(5, 15),重新传入b的值。
由于实参和形参是从左到右对应匹配的,所以,带默认值的参数必须要在所有参数列表的右边。
15. 存储类别有auto、static、register、extern
其中auto、static、register是用于变量定义的,而extern只能用在声明,不能用于定义
16. 引用
引用是C++对C的一个重要扩充。引用,也就是跟之前定义好的变量共用一个内存。
例如,int a = 4;int &b = a;那么剩下来的code中a和b都代表一个地址,即&a,a和b的值是完全一样的。
注意:
- 必须在声明的时候就初始化,而且在函数执行过程中不能重新赋值;
- 引用作为函数参数,传的其实是变量的地址。比较之前的值传递或者是址传递,引用不用重新给形参分配内存。
详细看博文:深入了解C++引用
17. 指针与指针引用
如上所述,引用代表共用一个内存,不需要重新分配内存
指针是一种变量的类型,所以也存在引用,代表着是指针变量的别名,指向同一个内存。
void test(int *param);
void test(int*& param);
第一个函数中参数param是需要重新分配内存,里面存放的是实参的指针变量值;
第二个函数中参数param是不需要重新分配内存,param值就是实参的指针变量值;
18. new / delete 和 malloc / free
new、delete是运算符,而不是函数。
new 类型 [初值]
例如,new int(100);new char[10];如果内存不够,会返回一个空指针NULL
delete [] 指针变量
指针变量前面的方括号是对数组空间的操作
19. extern "C"
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。
加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。
可以把其看成extern 和 “C”:
19.1 extern
代表是声明为外部,即定义的地方可能不在当前作用域。
例如,在函数内部声明:
int func()
{
extern int a;
...
...
}
变量 a 定义在别的地方,可能是func 下面,也可能在func所在文件外面。
extern 修饰的函数也是一样,不过函数定义的地方就是有函数体的地方。一般extern修饰的函数在头文件中,函数体放在c文件中,这样就实现了声明和定义分离。
19.2 “C”
这里表示声明的部分是C部分。C++是C的超集,当时C和C++编译器不同,因为C++中存在函数的重载,所以在编译的时候会变成函数名加上参数类型,而C编译的时候只是函数名。
例如:
void func(int, int);
在C++编译后,目标文件中函数符号名变成__func_int_int,而C编译后仅为__func。
所以,如果C++中使用C中的函数,不做声明的时候是提示找不到该函数,因此,C++中想要使用C接口(在C的编译工具链编译出来的目标文件中),需要加上声明,如下:
#ifdef __cplusplus
extern "C" {
#endif
void fun(int, int);
#ifdef __cplusplus
}
#endif
20. 关键字explicit
This keyword is a declaration specifier that can only be applied to in-class constructor declarations . An explicit constructor cannot take part in implicit conversions. It can only be used to explicitly construct an object 。
从官方的解释来看explicit 有如下几点:
- explicit 是个关键字
- 关键字是适用于类构造函数的声明时
- explicit 的构造函数不能参与隐式转换
- explicit 只用于显示地构造对象
如果声明了explicit 的构造函数,再次参与隐式转换,编译器会报出如下的错误:
error: conversion from ‘int’ to non-scalar type ‘Explicit’ requested
error: conversion from ‘const char [10]’ to non-scalar type ‘Explicit’ requested
另外,explicit 不用于拷贝构造函数,explicit 用于只有一个参数的构造函数,或者是多个参数,但从第二个参数开始有默认值的构造函数。
详细查看:C++中关键字explicit
21. 重载、重写、隐藏
重载(overload):
在同一作用域中,同名函数的形式参数(参数个数、类型或者顺序)不同时,构成函数重载。
- 在同一作用域,如在一个类中
- 与返回类型无关
- 与是否为virtual 无关
- 函数名相同,参数个数、类型、顺序不同
重写/覆盖(override):
派生类中与基类同返回值类型、同名和同参数的虚函数重定义,构成虚函数覆盖,也叫虚函数重写。
- 在不同作用域,明确的指派生类和基类两个作用域
- 函数名、参数个数、参数顺序、参数类型、返回类型都相同
- 基类成员函数为virtual
隐藏(hiding):
指不同作用域中定义的同名函数构成隐藏(不要求函数返回值和函数参数类型相同)。比如派生类成员函数隐藏与其同名的基类成员函数、类成员函数隐藏全局外部函数。
- 在不同作用域
- 函数名相同
- 参数个数、参数类型、返回值类型,可同可不同
调用一个类成员函数的时候,编译器会沿着类的继承链逐级的向上查找函数的定义,如果找到了,那么就停止查找了。所以如果一个派生类和它的基类都有同一个同名的函数,编译器最终选择派生类中的函数,那么就说派生类中的成员函数隐藏了基类中的成员函数,也就是说它阻止了编译器继续向上查找的行为。
22. 多态性
多态分两类:静态多态性和动态多态性;
- 静态多态性是通过函数的重载实现(包括运算符重载),又称编译时的多态性;
- 动态多态性是在程序运行过程中才动态确定操作所针对的对象,又称运行时的多态性(通过虚函数实现);
22.1 纯虚函数
- 纯虚函数没有函数体,将函数体变成 =0;
- 纯虚函数只有函数名,没有函数体所以不能被调用,所以不能实例对象;
- 纯虚函数只是为了派生类保留一个函数名,用以实现多态;
- 如果一个类中声明了纯虚函数,这个类称为抽象类;如果派生类没有实现这个纯虚函数,则这个派生类仍然保留这个纯虚函数,这个派生类也称为抽象类;