泰山之霤穿石,单极之绠断干。
水非石之钻,索非木之锯,渐靡使之然也。
--诸君共勉。
1 复制构造函数的问题
如果没有显示的编写复制构造函数或者赋值运算符,编译器会自动生成默认的复制构造函数和赋值运算符。
如果有指针成员变量,那么默认的复制构造函数和赋值运算符,默认操作是拷贝指针对应的值,不会拷贝指针指向的底层数据(也即浅拷贝)。这样复制出来的对象都会指向同一个内存区域。有些时候是所需要的功能,但是有些时候,就会出现问题:
- 一个对象销毁以后,对应的内存区域就会被回收,但是另一个对象可能还指向该内存区域,成为野指针/悬挂指针);
- 对象a被赋予b的值以后,对象a原有指针指向的内存区域被丢弃,此时没有调用回收方法,会导致内存泄漏。
为了避免上述问题,复制构造函数和赋值运算符,必须执行深拷贝。
由于复制构造函数和赋值运算符执行的时候,前者数据成员并不存在,后者只是修改已存在的数据成员的值。所以两者会有一些差异。区别如下:
复制构造函数:
1)为数据成员分配内存;
2)执行深拷贝;
赋值运算符:
1)释放数据成员内存;
2)分配新的内存;
3)执行深拷贝;
由此可知:析构函数,复制构造函数,赋值运算符,只要编写了其中一个,就必须编写所有这三个方法。
2 禁止赋值和按值传递
在类中动态分配内存的时候,如果想禁止复制对象和为对象赋值,可以如下操作:
1)可以显示的将operator=和复制构造函数标记为delete即可。必须要提供delete复制构造函数和赋值运算符的实现。(因为编译器不允许代码调用这两者,所以链接器永远也不会查看它们)。
- 可以把复制构造函数和赋值运算符标记为private,且不需要提供任何实现。(如果编译器不支持显示的delete的话)
3 静态数据成员(static)
静态数据成员属于类而不是对象的数据成员。可将静态数据成员当做类的全局变量。
注意:
1)需要在类定义中列出static 类成员;
- 还需要在源文件中为其分配内存(也就是定义类方法的文件中)
3)静态数据成员和普通的变量/数据成员不同:静态数据成员默认会初始化为0或者nullptr,而不是随机值。
4)使用时,类外必须使用类名::静态变量的形式;类中可以像普通成员变量一样使用。
class A {
public:
static int sCounter;//1定义static类成员
}
int A::sCounter; //2源文件中分配内存,
//3默认初始化为0
A::sCounter = 9;//4通过类名::静态变量的形式使用
3 常量数据成员(const)
const可以修饰两种情况:基本数据变量,和指针。
const int a = 20;//a只读变量
int const b = 20;//和上面等价,b只读变量
b = 1;//error,不允许修改常量值
const int *p;//*p为常量;p为变量
int const *p1;// 和上面等价
int * const p2;//*p2为变量,p2为常量
const int * const p3;//*p3为常量;p3为常量
int const * const p4;//和上面等价
对于const常量,要么在类定义中定义的时候马上初始化,要么使用构造函数初始化器初始化。
//examle 1:类定义中定义const常量的时候马上初始化
class A {
public:
const double kDouble = 1.0;
}
//example 2:使用构造函数初始器初始化
class A {
public:
const double kDouble;
}
A::A() :kDouble(1.0) {
}
特别注意:
如果在定义的时候,没有初始化const常量,想在构造函数中初始化会报错。
4 诡异的static const
class A {
public:
static const int a = 1;//编译正确
static const double b = 1.0;//编译错误,static常量只有int才可以直接在类定义中初始化,其余均需要在实现函数或者方法的源文件中初始化
}
class A {
public:
static const double b;//定义变量,可以正确编译
}
const double A::b = 3.0;//初始化,注意:这里要写const,同时不要写static,static只会出现在类定义中
5 引用数据成员
如果两个类互相引用,此时两个类必须互相知道对方。此时会有一个循环引用问题。普通的#include头文件的方式无法解决。
解决方案:在其中一个类的头文件中使用前置声明。
class B;
class A {
public:
A(int a, B& b);
protected:
B& mB;
int ma;
}
A::A(int a, B&b):ma(a),mB(b) {
}
A::A(const A&a):mB(a.mB) {
ma = a;
}
说明:
1)以上类A引用类B,需要在类A的头文件中前置声明类B;
2)A的构造函数初始化的时候,必须使用构造函数初始化器初始化引用mB;
3)A的复制构造函数也必须使用构造函数初始化器初始化引用mB;
6 常量引用数据成员
常量引用和非常量引用的重要差别在于:
常量引用数据成员只能用于调用对应的常量方法;
不能通过常量引用调用非常量方法;
所以:建议将不改变数据成员的方法都标记为const(也即常量方法)
7 static方法
类的方法。
static方法只可以访问static数据成员,不能访问非static数据成员。
7 const方法
将方法标记为const相当于与客户代码定下契约,承诺不会在方法中改变对象内部的值。其原理为编译器将方法内部所有用到的数据成员都标记为const,一旦尝试修改数据成员,编译器就会报错。
8 mutable数据成员
有些方法虽然是逻辑上的const,但是还是会改变对象的数据成员,这个改动对用户可见的数据没有任何影响(比如统计方法调用次数)。
此时由于const方法的限制,编译器会报错。要解决该问题,可以将成员变量标记为mutable
9 方法重载
可以根据const重载方法:也即可以编写两个名称完全相同,参数完全相同的方法,一个为const,一个不是。如果是const对象,就只能调用const方法;如果是非const对象,就调用非const方法
10 默认参数
带有默认参数的方法,不应与重载的方法导致混淆。
比如:如果提供了一个默认无参构造函数。同时由提供了一个全部参数都由默认值的构造函数。那么此时由于会有歧义,编译器会报错。
11 inline方法
inline方法具体是否会内联,与编译器的判断也有关系。
12 嵌套类
C++中嵌套类没有Java那么方便,使用相对也少。
网友评论