《王道程序员求职宝典》笔记 - 第8章 类
by 宋强
- 内置或复合成员在类定义在全局空间时被初始化,而在被定义在局部参数时并不被初始化。
- 类内部定义的成员函数默认为内联函数,平常我们都是将成员函数的定义定义在类外面加上类前缀的。
- 定义了非空构造函数的类
- 系统不会为其定义默认的空构造函数。
- 不能用作动态分配数组的元素类型(??)。
- 如果有一个保存他的容器,如vector,不可以只有容器大小而无初始化。
成员初始化列表
成员初始化列表跟在构造函数的形参表之后,以冒号开始,为每一个成员给出初值,形如:
class A{
private:
int i;
int j;
public:
A(): j(0), i(j+2){}
};
构造函数初始化函数顺序按照参数定义顺序,而不是初始化列表顺序,也就是说这个示例的情况下i仍然先被初始化之后j才被初始化,所以i被初始化成一个垃圾值而j被初始化成0。
必须在成员初始化列表中初始化的类型
- 没有默认构造函数的类类型的成员。
- const类型的成员变量。
- 引用类型的成员变量。
复制构造函数(拷贝构造函数)
定义:只有单个形参,而且该形参是对本类类型对象的引用的函数。并且通常用const修饰。
如果复制构造函数不用引用,会变成值传递,值传递会导致参数的传递过程中会构造,而且仍然调用的是拷贝构造函数,就会导致拷贝构造函数调用拷贝构造函数,vs和gcc中都会编译出错。
使用形式:
string book1("123456789");
string book2 = book1;
string book3(book1); //这两个分别代表两种拷贝构造的使用方式。
只有在初始化时才是复制构造函数,否则不是,
比如:
string book1("123456789");
string book2;
book_2 = book1;
就不属于拷贝构造,主要是他不属于构造。
函数形参被赋值也是调用的拷贝构造 P141,例1
初始化顺序容器的元素时,编译器会使用默认构造函数构造第一个类,之后使用拷贝构造函数构造剩下的类。
例如:
vector<string> svec(5);
深复制与浅复制 浅复制与深复制的差别在于复制完所有类的内容之后是否复制类所引用的对象,深复制复制,而浅复制不复制类所引用的对象,容易在创建和释放对象的过程中造成运行时错误。
析构函数
编译器将自动构造的一个析构函数,叫做‘合成析构函数’调用所有非static变量的析构函数,顺序为声明顺序的逆序。
无论类是否定义了自己的析构函数,编译器都会定义一个析构函数,如果类定义了,那么编译器定义的析构函数将在类自己的析构函数之后执行。
如果析构函数不是虚函数,那么基类指针指向子类对象时执行析构函数会调用基类析构函数,造成潜在的资源没有释放。
构造函数与析构函数的调用顺序
单继承情况下
派生类的构造函数需要在成员初始化列表中使用基类的构造函数初始化继承的基类对象,如果没有的话系统会调用默认的基类构造函数。
派生类的析构函数执行时会先调用派生类的析构函数,之后调用基类的析构函数(派生类中无须显式调用基类析构函数)。
P144 例1与例2 这种题的关键点在于构造顺序与析构顺序,按照这两个顺序一个一个推就能够推出来。
构造顺序:
- 基类构造函数。
- 成员初始化列表。
- 未初始化的非static变量的构造函数。
- 函数体。
析构顺序:
- 派生类的析构函数函数体。
- 派生类非static变量的析构函数。
- 基类析构函数。
还有就是所有具有实例的对象依照被构造的顺序写在纸上,防止忘记构造或者析构哪一个实例。
多继承情况下
此时派生类会依次调用各个基类的构造函数,并且调用顺序取决于基类在类派生列表中的顺序,而不是构造参数列表的顺序。
操作符重载
形式:名字为operator后跟着所定义的操作符的符号,如:
operator +(){}
参数的书目取决于操作符是几元操作符,还取决于是否是成员函数,如果是成员函数的话在最开始多一个this指针,但是是隐式声明的。
可重载操作符表:
| + | - | * | / | % | ^ |
|---|---|---|---|---|---|
| & | | | ~ | ! | , | = |
| < | > | <= | >= | ++ | -- |
| « | » | == | != | && | || |
| += | -= | /= | %= | ^= | &= |
| |= | *= | «= | »= | [] | () |
| -> | ->* | New | new[] | delete | delete[] |
不可重载的操作符:
| :: | * | . | ?: |
带’.’的都不能重载
’=’赋值操作符重载
赋值操作符重载的问题
- 是否需要把返回值声明为该类型对象的引用,这样允许连续赋值,a = b = c。
- 是否需要将输入的参数声明为常量引用。
- 是否释放了被赋值的实例内部指针指向的旧内存。
- 是否判断了传入参数是不是和当前的实例是同一个,如果是同一个则不需要进行赋值操作,否则释放了旧内存就会丢失赋值源内容。
复制构造函数与赋值运算符的区别
- 赋值构造函数只能在新实例被复制构造的过程中调用,要求被赋值的实例此时不存在。
- 赋值运算符要求被赋值的实例此时是存在的。
构造函数、拷贝构造函数、析构函数和赋值重载函数都不可以被继承。
c++中的空类默认产生的成员函数 构造函数、复制构造函数、析构函数、赋值运算符重载函数、取址运算符重载函数和const取址运算符重载函数。
‘«’输出操作符重载
- 第一个参数为’ostream &’类型,第二个参数为’对本类型的const 引用’。
- 返回’ostream &’。
‘operator new’和’operator delete’的重载
c++中new的执行过程实际为:
- 调用名为’operator new’的标准库函数,分配足够大小空间。
- 运行构造函数,将内容放在新空间中。
- 将分配空间的首地址返回。
delete类似:
- 调用析构函数。
- 调用’operator delete’释放内存。
- ‘operator new’返回void*类型,只有一个类型为size_t的参数。
- ‘operator delete’返回void类型,只有一个void*参数。
- 重载时可以添加参数。
限制用户使用堆空间和栈空间 将operator new设为私有禁止用户申请堆空间,通过将对象的构造函数设为私有阻止对象使用栈空间。(但是函数调用不是还会使用吗)
成员函数的重载、覆盖与隐藏
成员函数的重载
成员函数重载特征:
- 相同范围(同一个类)。
- 相同函数名。
- 不同的参数列表。
- 是否virtual不影响。
成员函数的覆盖
覆盖是指子类类型函数覆盖了积累的虚函数,要求如下:
- 与基类的虚函数有相同的参数个数。
- 与基类的虚函数有相同的参数类型。
- 与积累的徐函数有相同的返回类型,或者返回指针或引用时,指针或引用的类型是基类函数返回的指针或引用类型的子类。
成员函数的隐藏
隐藏就是子类中的方法覆盖了基类中的方法,导致基类中的方法不可见的请款。
这个分为两种情况:
基类子类中两个函数名字且参数列表相同,且基类不是虚函数 子类会覆盖父类的方法,若基类是虚函数就变成了覆盖了。
基类子类中两个函数名字相同但参数列表不同 此时无论基类中的方法是不是虚函数都会被隐藏。
习题
3
复制构造函数也属于构造函数?应该是调用了一次构造函数9次复制构造函数,或者是数组不属于顺序容器? (编程试一下)
tags: c++ - 笔试