|
65 | 65 | 2. 若派生类中包含对象成员,还要进行对象成员初始化。如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序。
|
66 | 66 | 3. 派生类构造函数。
|
67 | 67 |
|
68 |
| -析构函数正好和构造函数相反。 ([constructor_derived_class.cpp](../Coding/constructor_derived_class.cpp)) |
69 |
| - |
70 |
| -[[构造函数中调用虚函数](http://www.nowcoder.com/questionTerminal/adb540e6222b401eb294b093b9fc6f0e)] |
71 |
| -[[构造函数调用次数](http://www.nowcoder.com/questionTerminal/bf70aadeb78949c2a61b1b561a0ee784)] |
72 |
| -[[析构的顺序](http://www.nowcoder.com/questionTerminal/ad46fe08266341b694d2ab8a78aa071f)] |
73 |
| -[[析构函数调用delete](http://www.nowcoder.com/questionTerminal/2dc097fd196343f88d7efde6e9447f91)] |
| 68 | +析构函数正好和构造函数相反。具体看下面程序 ([constructor_derived_class.cpp](../Coding/constructor_derived_class.cpp)) |
74 | 69 |
|
75 | 70 | 关于异常抛出问题:
|
76 | 71 |
|
77 | 72 | 1. 不建议在构造函数中抛出异常。构造函数抛出异常时,析构函数将不会被执行,需要手动的去释放内存;
|
78 | 73 | 2. **析构函数不应该抛出异常**。当析构函数中会有一些可能发生异常时,那么就必须要把这种可能发生的异常完全封装在析构函数内部,决不能让它抛出函数之外。因为**如果对象抛出异常了,异常处理模块为了维护系统对象数据的一致性,避免资源泄露,有必要调用析构函数释放资源**,这时如果析构过程又出现异常,那么谁来保证新对象的资源释放呢?前面的异常还没处理完又来了新的异常,这样可能会陷入无限的递归嵌套中。所以,从析构函数抛出异常,C++运行时系统会处于无法决断的境遇,因此C++语言担保,当处于这一点时,会调用 terminate()来杀死进程。
|
79 | 74 |
|
| 75 | +[[构造函数中调用虚函数](http://www.nowcoder.com/questionTerminal/adb540e6222b401eb294b093b9fc6f0e)] |
| 76 | +[[构造函数调用次数](http://www.nowcoder.com/questionTerminal/bf70aadeb78949c2a61b1b561a0ee784)] |
| 77 | +[[析构的顺序](http://www.nowcoder.com/questionTerminal/ad46fe08266341b694d2ab8a78aa071f)] |
| 78 | +[[析构函数调用delete](http://www.nowcoder.com/questionTerminal/2dc097fd196343f88d7efde6e9447f91)] |
| 79 | + |
80 | 80 | ## 禁止对象产生在堆(栈)中
|
81 | 81 |
|
82 | 82 | 一般情况下,编写一个类,是可以在栈或者堆分配空间。但有些时候,你想编写一个只能在栈或者只能在堆上面分配空间的类。例如说在嵌入式系统中工作,为了保证不发生内存泄漏,最好保证没有任何一个类型的对象可以从 heap 中分配出来。
|
|
153 | 153 |
|
154 | 154 | ## 构造函数初始值列表
|
155 | 155 |
|
156 |
| -为变量分配地址和存储空间的称为`定义`,不分配地址的称为`声明`。一个变量可以在多个地方声明,但是只在一个地方定义。加入 extern 修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义。 |
157 |
| - |
158 | 156 | 定义变量时习惯对其初始化,而非先声明、再赋值。
|
159 | 157 |
|
160 | 158 | string foo = "Hello"; // 定义并初始化
|
|
179 | 177 | int &ref_j;
|
180 | 178 | };
|
181 | 179 |
|
182 |
| -构造函数初始值列表只说明用于初始化成员的值,而不限定初始化的具体执行顺序。成员的初始化顺序与它们`在类定义中的出现顺序一致`,第一个成员先被初始化,然后第二个,以此类推。如果一个成员是用另一个成员来初始化的,那么两个成员的初始化顺序就很关键了!(可能的话,**尽量避免使用某些成员初始化其他成员**)。 |
| 180 | +**构造函数初始值列表只说明用于初始化非静态成员的值**,而不限定初始化的具体执行顺序。成员的初始化顺序与它们`在类定义中的出现顺序一致`,第一个成员先被初始化,然后第二个,以此类推。如果一个成员是用另一个成员来初始化的,那么两个成员的初始化顺序就很关键了!(可能的话,**尽量避免使用某些成员初始化其他成员**)。 |
183 | 181 |
|
184 | 182 | [[初始化顺序](http://www.nowcoder.com/questionTerminal/fb01e2436c6d453abbbf9801f794165b)]
|
185 | 183 | [[类定义static与const](http://www.nowcoder.com/questionTerminal/5ab084ff358e43f392833dbcd2963872)]
|
|
189 | 187 |
|
190 | 188 | ## 数据成员
|
191 | 189 |
|
| 190 | +类的成员变量(数据成员)和普通变量一样,也有数据类型和名称,占用固定长度的内存空间,一般有以下几种成员变量: |
| 191 | + |
| 192 | +* 普通变量:可以在构造函数中进行赋值,也可以在构造函数的初始化列表中进行初始化。 |
| 193 | +* 静态变量(static):属于类所有,而不属于类的对象,因此不管类被实例化了多少个对象,该变量都只有一个。 |
| 194 | +* 常量变量(const):需要进行类内进行初始化,可以在定义时初始化,或者在构造函数的初始化列表中进行。 |
| 195 | +* 引用型变量:和const变量类似,需要在类内进行初始化。 |
| 196 | +* static const integral 变量:对于既是const又是static 而且还是整形变量,可以直接在类的定义中初始化。short可以,但float的不可以哦。(short可以,但float的不可以) |
| 197 | + |
| 198 | +static const integral 变量的示例如下: |
| 199 | + |
| 200 | +```c++ |
| 201 | +class A{ |
| 202 | +public: |
| 203 | + static const int a=1; |
| 204 | + static const int b; |
| 205 | + // non-const static data member must be initialized out of line |
| 206 | + // static int c = 3; |
| 207 | + static int d; |
| 208 | + static const float e; |
| 209 | + // static const float f=1; |
| 210 | +}; |
| 211 | + |
| 212 | +const int A::b = 2; |
| 213 | +int A::d = 4; |
| 214 | +const float A::e = 5; |
| 215 | +``` |
| 216 | + |
| 217 | +特别注意的是在继承的时候,允许子类存在与父类同名的成员变量,但是并不覆盖父类的成员变量,他们同时存在。 |
| 218 | + |
| 219 | +[[派生类重复定义基类数据成员](http://www.nowcoder.com/questionTerminal/ade233b99dfc4f03aba0335f9f2a3f35)] |
| 220 | + |
192 | 221 | ## 类的静态成员
|
193 | 222 |
|
194 | 223 | 在类成员的声明之前加上关键字 static 使得成员与类本身直接相关,而不是与类的各个对象保持关联。和其他成员一样,静态成员可以是 public 或 private 的,静态数据成员的类型可以是常量、引用、指针、类类型等。类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据。
|
195 | 224 |
|
196 |
| -类的静态数据成员不属于类的任何一个对象,所以它们不是在创建类的对象时被定义的,也就是说不是由类的构造函数初始化的(`不能用构造函数初始化静态数据成员`)。通常情况下,类的静态成员不应该在类的内部初始化,必须在类的`外部定义和初始化`每个静态成员。一种例外情况是,为静态数据成员提供 const 整数类型的类内初始值(**即使是 const static 也不能用构造函数初始化列表来进行初始化**)。 |
| 225 | +类的静态数据成员不属于类的任何一个对象,所以它们不是在创建类的对象时被定义的,也就是说不是由类的构造函数初始化的(**不能用构造函数初始化静态数据成员**)。通常情况下,类的静态成员不应该在类的内部初始化,必须在类的`外部定义和初始化`每个静态成员。一种例外情况是,为静态数据成员提供 const 整数类型的类内初始值。**不过即使是 static const 也不能用构造函数初始化列表来进行初始化**,这是因为: |
| 226 | + |
| 227 | +1. static属于类,它在未实例化的时候就已经存在了,而构造函数的初始化列表,只有在实例化的时候才执行。 |
| 228 | +2. static成员不属于对象。我们在调用构造函数自然是创建对象,一个跟对象没直接关系的成员要它做什么呢。 |
197 | 229 |
|
198 | 230 | 类似的,静态成员函数也不与任何对象绑定在一起,不包含 this 指针。`静态成员函数不能声明为 const 的(本来就不会去改变对象的值,所以没有必要定义为const),而且不能在 static 函数体内使用 this 指针`。既可以在类的内部定义静态成员函数,也可以在外部定义静态成员函数。在类的外部定义静态成员函数时,不能重复关键字 static。
|
199 | 231 |
|
|
212 | 244 |
|
213 | 245 | `函数隐藏`是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
|
214 | 246 |
|
215 |
| -1. 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏(注意别与重载混淆)。 |
| 247 | +1. 如果派生类的函数与基类的函数同名,但是**参数不同**。此时,不论有无 virtual 关键字,基类的函数将被隐藏(注意别与重载混淆)。 |
216 | 248 | 2. 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有 virtual 关键字(否则是覆盖)。此时,基类的函数被隐藏(注意别与覆盖混淆)
|
217 | 249 |
|
218 | 250 | 当然,派生类还可以`覆盖`基类函数,以实现多态。这三种情况的执行可以总结为以下:
|
|
252 | 284 |
|
253 | 285 | [[派生类存储,隐式转换](http://www.nowcoder.com/questionTerminal/c85f9e15e6a4410a930581ae12b9a341)]
|
254 | 286 | [[子类继承父类所有对象](http://www.nowcoder.com/questionTerminal/ff91c410e28745e8ae01537d8a888283)]
|
255 |
| -[[派生类重复定义基类数据成员](http://www.nowcoder.com/questionTerminal/ade233b99dfc4f03aba0335f9f2a3f35)] |
256 | 287 |
|
257 | 288 | ## 虚拟继承
|
258 | 289 |
|
|
0 commit comments