1. 字面量
1.1 宏替换——与类型别名对比
#define Monday 1
自定义字面量
// 有错!!!! int operator""_$ (int a) { return a; } printf("%d", 5_$);
2. 类型
2.1 枚举
-
非匿名枚举类:
enum Week{Monday=1, Tuesday}
-
作用域:整个空间内,而非“enum Week”中
-
-
匿名枚举类:
enum {Monday, Tuesday}
-
作用域同上
-
-
强类型枚举:
enum class Week {Monday, Tuesday}
-
能够进行作用域隔离,引用时
Week::Monday
-
3. 类型基础行为
3.1 初始化
3.1.1 构造式初始化
struct A { A() { a = 0; } A(int a) { this->a = a; } int a; } // 栈对象 A a; A b(1); // 堆对象 A *a = new A; A *b = new A(); A *c = new A(1);
3.1.2 赋值式初始化
struct A { A(int a) { this->a = a; } int a; }; int main() { A a = 1; }
问题:
-
看起来容易误解成赋值
-
不支持多个入参的构造
-
不支持堆对象的双重初始化:
// 赋值式:error! *(int* ptr = new int) = 5; // 构造式: int *ptr = new int(5);
如果想让构造函数仅支持构造式,不支持赋值式:关键字explicit
struct A { explicit A(int a) { this->a = a; } int a; }; int main() { A a(1); // error! // A a = 1; }
3.1.3 列表式初始化
struct A { A(int a, int b) { this->a = a; this->b = b; } int a, b; }; int main() { A a {1, 1}; int *pa {new int {1}}; }
3.2 自动类型识别
关键字:auto
auto i = 120; // int
3.3 初始化类成员
-
在构造函数中赋值,是为成员数据“赋值”,而非“初始化”
-
成员初始化列表:
-
位于构造函数函数名与函数体之间,并非“赋值”;
-
可以为构造式或列表式,不能是赋值式
-
初始化顺序:成员变量声明顺序,而非初始化列表中顺序
-
struct A { A(int a, int b) : a {a}, b(b) {} int a, b; };
-
成员声明式初始化:
-
可以为赋值式或列表式,不能是构造式
-
C++ 98不允许;C++ 11允许
-
struct A { int a = 1; int b {3}; };
3.4 复制构造行为
-
浅拷贝:仅复制目标对象的对应内存,即引用本身的地址
-
深拷贝:拷贝引用的目标对象的全部内容
-
C++默认的拷贝行为是将来源对象的内存内容拷贝
-
如对于栈中变量:
int i;A a;
等,进行A b = a;
后,b复制了a的全部内容,即深拷贝 -
如对于堆中变量:
A *a = new A();
,由于a是指针,b复制的仅是a的指针内容,即浅拷贝
-
3.4.1 定制拷贝代码
struct A { A(int a) : a(1) {} A(A const& other) : a(other.a) {} int a; };
3.4.2 堆变量的深拷贝
下类中声明了一个指针,并在堆中为其开空间
struct A { explicit A(int a) : pa(new int(a)) {} ~A() { delete pa; } int* pa; }; // test A a(1); A b(a); cout << a.pa << endl; cout << b.pa << endl;
在进行test后会发现,b变量的pa地址和a的地址一样,也就是进行了浅拷贝;虽然程序会无法正常推出,这可能是因为A并没有实现A(A const& a){}
构造函数。
接下来为A实现深拷贝构造函数:
struct A { explicit A(int a) : pa(new int(a)) {} A(A const& other) : pa(new int(*(other.pa))) {} ~A() { delete pa; } int* pa; }; // test A a(1); A b(a); // 或:A b = a; cout << a.pa << endl; cout << b.pa << endl;
在Clang中,新实现的拷贝函数被称为“Copy constructor”,同时,Clang-Tidy希望“Clang-Tidy: Copy constructor should not be declared explicit”。
3.4.3 禁用拷贝构造
struct A { explicit A(int a) : pa(new int(a)) {} A(A const&) = delete; ~A() { delete pa; } int* pa; };
3.5 赋值行为
3.5.1 初始化与赋值——拷贝构造函数和赋值函数
A a; // 1 A b = a; //2 A b; b = a;
在1中,是对b通过“拷贝构造函数”进行初始化;在2中,第一行完成初始化,第二行利用“赋值函数”进行赋值。
因此上节禁用拷贝构造函数,但仍可以进行赋值(但是进行堆变量的浅拷贝)。
3.5.2 重载“operator=”实现赋值函数深拷贝
struct A { explicit A(int a) : pa(new int(a)) {} A(A const& other) : pa(new int(*(other.pa))) {} A& operator= (A const& other) { // 1 if (this == &other) return *this; if (other.pa != this->pa) { delete this->pa; pa = new int(*other.pa); return *this; } } ~A() { delete pa; } int* pa; }; A a(1); A b(2); b.operator=(a); // 更为常用的:b = a; cout << *a.pa << endl; cout << *b.pa << endl;
1部分是为了处理自我赋值;
之所以赋值函数返回类型是A&
,是为了支持语法a=b=c
3.5.3 禁止赋值
struct A { explicit A(int a) : pa(new int(a)) {} A(A const&) = delete; A& operator= (A const&) = delete ~A() { delete pa; } int* pa; };
3.5.4 拷贝构造函数和赋值函数的关系
struct A { explicit A(int a) : pa(new int(a)) {} A(A const& other) : pa(new int(*(other.pa))) { cout << "constructor" << endl; } ~A() { delete pa; } int* pa; };
如果没有使用“explicit”修饰拷贝构造函数(使用是不被建议的),那么可能会用拷贝构造函数来进行赋值操作(至少我的编译器不会),一些解决方式:
-
禁用拷贝构造函数
-
将拷贝构造函数使用“explicit”修饰
-
明确重载赋值函数“operator=”
-
将赋值函数“operator=”标为delete