目录
const成员函数和this指针this指针,在默认情况下,是类类型非常量版本的常量指针,例如下面的Person类的this指针默认是Person *const this。
意思就是:默认情况下,不能修改this指针指向的对象是谁,但可以通过this指针修改指向对象的值。
然而,如果有一个成员变量是const类型,例如下面的const int m_a,而成员函数func要用到这个成员变量,但又不希望修改它,因此需要在函数后面加一个const:
- class Person
- {
- public:
- void func(int a) const
- {
- this->m_a = a; // 错误,因为上面声明了const,因此this指向的成员不可修改
- cout << m_a;
- }
-
- const int m_a = 10;
- };
-
- void test()
- {
- Person p;
- p.func(20);
- }
this对象- class Person
- {
- public:
- Person& add(Person p) // 返回Person引用,也对应了 return *this;
- {
- this->Age += p.Age;
- return *this;
- }
- int Age = 10;
- };
-
- void test()
- {
- Person p1, p2;
- p1.Age = 10;
- p2.add(p1);
- cout << p2.Age;
- }
return *this返回的就是当前这个对象,例如p2.add(p1),那么这个this指的是p2这个对象,因此第6-7行等价于:p2.Age += p1.Age; return &p2;
或者说,add()成员函数的返回值是调用add()的对象的引用,第16行是p2调用add,因此add()返回的那个*this就是p2的引用。通过输出地址,可以发现,this的地址和p2的地址是同一个。
const型。当声明一个const对象时,需要通过构造函数向其写值(初始化过程),只有在初始化完成之后,这个对象才具有const属性。概念:如果我们没有显式地写一个构造函数,那么编译器会帮我们自动创建一个隐式的构造函数,就叫做“合成的默认构造函数”。
default显式补充默认构造函数,不然的话,有了其它构造函数后,就会丢失默认构造函数。
- class Person
- {
- public:
- Person() = default; // 显式定义一个默认构造函数
- // Person() {}; // 和上面等价,但只能二选一
- Person(int age, int id, string name) : Age(age), Id(id), Name(name) {}; // 自己定义其它构造函数
-
- int Age = 10;
- int Id = 20;
- string Name = "wind";
- };
-
- void test()
- {
- Person p1(20, 101, "wind");
- Person p2; // 调用第4行的默认构造函数
- }
如果没有第4行,则第15行就出错,因为我们已经在第5行定义了一个构造函数,所以编译器就不会自动创建一个合成的默认构造函数!因此,需要自己加上一个默认构造。
格式如下:
- class Person
- {
- public:
- // 初始化列表格式:
- Person(int age, int id, string name) : Age(age), Id(id), Name(name) {};
-
- int Age = 10;
- int Id = 20;
- string Name = "wind";
- };
作用:其它类或者函数能够访问类内部私有成员。
注意:
public还是private都行,不过建议在类定义开始或结束前集中声明。- class Person
- {
- public:
- Person() = default;
- Person(int age, int id, string name) : Age(age), Id(id), Name(name) {};
- friend void test(int a); // friend + 函数声明(要和函数声明一模一样)
-
- private:
- int Age = 10;
- int Id = 20;
- string Name = "wind";
- };
-
- void test(int a)
- {
- Person p1(20, 101, "wind");
- Person p2;
- cout << p2.Age; // 如果test函数不是友元,那么这句就报错,因为无法访问私有成员Age
- }
因为友元只是制定了访问权限,并不是真正的函数声明,如果其它函数需要调用这个友元函数,那么必须在专门针对这个函数做一个声明。通常情况,把针对友元的声明和类本身放在同一个头文件中。
要注意几点:
- class Student{public:void func();};
- class Person
- {
- friend void Student::func(); // 必须有Student::
- }
- class Student
- {
- public:
- void func();
- void func(int a);
- };
- class Person
- {
- friend void Student::func();
- friend void Student::func(int a); // 如果不写这个,那么这个版本的func就无法访问Student的私有成员
- }

inline(隐式内联)inline的即使const,也能修改变量值:mutable
关键字:mutable
作用:把一个变量变成“可变成员”
- class Person
- {
- public:
- void func(int a) const
- {
- this->m_a = a; // 正确,即使函数是const, 但m_a是mutable的
- this->m_b = a; // 错误,因为m_b不是mutable, 因此不可被修改
- cout << m_a;
- }
-
- mutable int m_a = 10;
- int m_b = 10;
- };
*this的成员函数一大作用:可以把一系列操作串起来。
- 伪代码:
- Person& add(int a)
- {
- ...
- return *this;
- }
-
- Person& sub(int a)
- {
- ...
- return *this;
- }
-
- Person p;
- p.add(1).sub(2); // 串起来操作,意思是先加1,再减2
注意:针对返回的函数定义为const,那么返回的*this也会变成const对象。
const重载函数直接看例子:
- class Person
- {
- public:
- Person() = default;
- Person(int num) : Num(num) {};
-
- // 非const重载的display()
- Person& display()
- {
- cout << "非const: " << this->Num << endl;
- return *this; // 返回非const对象
- }
- // const重载的display()
- const Person& display() const
- {
- cout << "const: " << this->Num << endl;
- return *this; // 返回const对象
- }
-
- Person& add(int a)
- {
- this->Num += a;
- return *this;
- }
-
- int Num;
- };
-
- void test()
- {
- Person p1(10); // 非const对象
- const Person p2(20); // const对象
- p1.display(); // 调用非const的display()
- p1.add(10).display(); // 调用非const的display()
- p2.display(); // 调用const的display()
- }
如果没有写非const重载的display(),那么最后p1就会调用const重载的display(),对应《C++ Primer》第248页最上面:

如果让add变成const,Num变成mutable,那么p1.add(10).display()就会调用const的display(),因为此时p1.add(10)返回的是const对象,自然会首选const的display()。
对于公用代码使用私有功能函数,就像上面为输出结果而专门写的小函数display(),原因如下:
inline,因此后面调用它的时候就不会带来额外的运行开销。《C++ Primer》第243页讲到定义一个类型成员:

也就意味着,这个pos的作用域就是类内部,如果在外面访问,需要加上Person::表面其作用域;同理,类内声明的函数也是如此:
- class Person
- {
- public:
- Person() = default;
- Person(int num) : Num(num) {};
- using type1 = int; // 定义类型
-
- type1 func(type1 a); // 声明函数
- };
-
- Person::type1 Person::func(Person::type1 a)
- {
- cout << a;
- }
重点在第11行:
如果type1前不加Person::,那么会报错"未定义标识符"。
如果func前不加Person::,那么这里定义的func就不是第8行声明的那个func,而是一个新的函数,返回类型是type1,也就是int,等价于:int func(int a){cout << a;},和Person完全无关。
如果保持11行的代码,那么调用int a = p1.func(1);的时候,就会报错,因为func重定义了。

即,把类似using xxx = xxx;和typedef xxx yyy;放在类的开始处。
成员的初始化顺序与它们在类定义中的出现顺序一致。
- class Person
- {
- public:
- Person() = default;
- int Age;
- int Id;
- Person(int val) : Id(val), Age(Id) {}; // 错误
- Person(int val) : Age(val), Id(Age) {}; // 正确
- };
-
- void test()
- {
- Person p1(10);
- cout << p1.Age << endl << p1.Id << endl;
- }
Age先定义,Id后定义,因此:
第8行是正确的:先用初始化Age--使用val,再初始化Id--使用Age;
第7行是错误的:先用初始化Age--使用Id,而此时Id还没有被初始化,因此输出p1.Id是个乱码。
注:应该尽量按顺序初始化,并且不要用某些成员去初始化其它成员(例如用Id去初始化Age)。
如果类A没有默认构造函数,类B有一个类A的成员,那么想要定义类B的默认构造函数,就必须显式调用A的带参构造函数初始化这个类A的成员:
- class Person
- {
- public:
- //Person() = default;
- Person(int a) {}; // 只有一个带参构造函数
- int Val = 10;
- };
-
- class C
- {
- public:
- Person p;
- C(int i = 0) : p(i) {}; // 写C的默认构造的时候,必须显式调用A的带参构造函数初始化成员p
- };
-
- void test()
- {
- C c;
- cout << c.p.Val;
- }
如果第13行改为:C(int i),那会第18行报错,提示不存在默认构造函数。也就是说,Person没有默认构造,因此无法用(int i) : p(i)的方式去初始化p,只能用值初始化的方式。
如果第4行存在,那么Person就有默认构造函数了,从而可以省略第13行。
大前提:只有“具有1个实参”的构造函数能够隐式转换,没有实参、有多个实参的构造函数都不行。

例如,下面第21-22行,就是通过实参调用构造函数,然后就会把这个int型转换为Person型。实际上是通过创建一个临时Person对象来过渡的:
- class Person
- {
- public:
- Person() = default;
- Person(int a) {};
-
- Person& add(const Person& p)
- {
- this->Val += p.Val;
- return *this;
- }
- int Val = 10;
- };
-
- void test()
- {
-
- Person p1(10), p2;
-
- int a = 10;
- p2.add(a);
- p2.add(int(10));
- cout << p2.Val;
- }
-
- 第20-21行等价于:
- int a = 10;
- Person temp(a); --> 临时对象
- p2.add(temp);
关键词:explicit
- class Person
- {
- public:
- Person() = default;
- explicit Person(int a) {};
- ...
- };
-
- void test()
- {
-
- Person p1(10), p2;
-
- int a = 10;
- p2.add(a); // 错误!因为第5行声明了explicit,不再允许!
- cout << p2.Val;
- }
注1:explicit只允许出现在类内的构造函数声明处,类外不行!
注2:explicit只是防止隐式转换,如果要强制转换,explicit就没有用了:
- class Person
- {
- public:
- Person() = default;
- explicit Person(int a) {};
- ...
- };
-
- void test()
- {
-
- Person p1(10), p2;
-
- int a = 10;
- p2.add(static_cast
(a)); --> 对其强制转换 - cout << p2.Val;
- }
关键字:static
主要为了调用方便,不需要生成对象就能调用:
- class X
- {
- public:
- void MethodA();
- static void MethodB();
- }
此时MethodB可以直接调用,X::MethodB();
MethodA必须先生成类对象才能调用,X x; x.MethodA();
注意:
this指针。const。static,所以生命周期是持续到程序结束。static关键字只能在类内部,如果在类外部定义静态成员,那么不能重复static关键字:- class Person
- {
- public:
-
- static Person& add(const Person& p)
- {
- this->Val += p.Val; // 错误,因为声明为static后,不包含this指针
- return *this;
- }
- static void func();
- int Val = 10;
- };
-
- static void Person::func() {} // 错误,不能在类外重复关键字static
- void Person::func() {} // 正确
const或constexpr。- class Person
- {
- public:
- static const int a = 10; // 正确,a是个静态常量成员
- static vector<int>v(a); // 错误
- static vector<int>v; // 正确,必须在类外初始化
- };
- class Person
- {
- public:
- static person p1; // 正确,静态数据成员可以是非完全类型
- Person *p2; // 正确,指针类型也可以是非完全类型
- Person p3; // 错误,非静态数据成员必须是完全类型
- }
小结:只要想到“不和任何对象绑定在一起”,再能理解这些了。