文章目录
- [C++ 类和对象(下)复习总结](#C++ 类和对象(下)复习总结)
-
- 一、本讲核心主线
- 二、再探构造函数:初始化列表
- [1. 构造函数体赋值 vs 初始化列表](#1. 构造函数体赋值 vs 初始化列表)
- [2. 初始化列表的本质](#2. 初始化列表的本质)
- [3. 每个成员变量只能在初始化列表中出现一次](#3. 每个成员变量只能在初始化列表中出现一次)
- [4. 必须使用初始化列表的成员](#4. 必须使用初始化列表的成员)
- [5. 为什么引用和 const 必须初始化?](#5. 为什么引用和 const 必须初始化?)
-
- 引用成员
- [const 成员](#const 成员)
- 没有默认构造的自定义类型成员
- [6. C++11 支持成员变量声明处给缺省值](#6. C++11 支持成员变量声明处给缺省值)
- [7. 即使不写初始化列表,成员也会走初始化列表](#7. 即使不写初始化列表,成员也会走初始化列表)
- [8. 初始化顺序由成员声明顺序决定(常考)](#8. 初始化顺序由成员声明顺序决定(常考))
- [9. 初始化列表使用建议](#9. 初始化列表使用建议)
- 三、类型转换
- [1. 内置类型可以隐式转换为类类型](#1. 内置类型可以隐式转换为类类型)
- [2. const 引用可以绑定转换出的临时对象(并且只能用const)](#2. const 引用可以绑定转换出的临时对象(并且只能用const))
- [3. C++11 支持多参数隐式转换](#3. C++11 支持多参数隐式转换)
- [4. 类类型之间也可以隐式转换](#4. 类类型之间也可以隐式转换)
- [5. explicit 禁止隐式类型转换](#5. explicit 禁止隐式类型转换)
- [四、static 成员](#四、static 成员)
- [1. 静态成员变量](#1. 静态成员变量)
- [2. 静态成员变量不走构造函数初始化列表](#2. 静态成员变量不走构造函数初始化列表)
- [3. 静态成员函数](#3. 静态成员函数)
- [4. 静态成员的访问方式](#4. 静态成员的访问方式)
- [5. 静态成员也受访问限定符限制](#5. 静态成员也受访问限定符限制)
- [6. 用 static 统计对象数量](#6. 用 static 统计对象数量)
- [7. 牛客题:求 1+2+...+n](#7. 牛客题:求 1+2+...+n)
- 五、友元
- [1. 什么是友元?](#1. 什么是友元?)
- [2. 友元函数](#2. 友元函数)
- [3. 友元函数声明位置不受访问限定符限制](#3. 友元函数声明位置不受访问限定符限制)
- [4. 一个函数可以是多个类的友元](#4. 一个函数可以是多个类的友元)
- [5. 友元类](#5. 友元类)
- [6. 友元关系的特点](#6. 友元关系的特点)
- 六、内部类
- [1. 什么是内部类?](#1. 什么是内部类?)
- [2. 内部类是独立的类](#2. 内部类是独立的类)
- [3. 内部类受外部类类域限制](#3. 内部类受外部类类域限制)
- [4. 内部类默认是外部类的友元类](#4. 内部类默认是外部类的友元类)
- [5. 内部类的使用场景](#5. 内部类的使用场景)
- [6. 用内部类改写求和题](#6. 用内部类改写求和题)
- 七、匿名对象
- [1. 什么是匿名对象?](#1. 什么是匿名对象?)
- [2. 匿名对象生命周期(当前行)](#2. 匿名对象生命周期(当前行))
- [3. 匿名对象适合临时用一下](#3. 匿名对象适合临时用一下)
- [4. 匿名对象和函数声明易混点](#4. 匿名对象和函数声明易混点)
- 八、对象拷贝时的编译器优化
- [1. 为什么会有拷贝优化?](#1. 为什么会有拷贝优化?)
- [2. 优化规则不是完全固定的](#2. 优化规则不是完全固定的)
- [3. 传值传参场景](#3. 传值传参场景)
- [4. 隐式类型转换 + 传值传参可能被优化](#4. 隐式类型转换 + 传值传参可能被优化)
- [5. 匿名对象传参也可能被优化](#5. 匿名对象传参也可能被优化)
- [6. 传值返回场景](#6. 传值返回场景)
- [7. 接收返回值时的优化](#7. 接收返回值时的优化)
- [8. 赋值场景通常难以完全消除赋值](#8. 赋值场景通常难以完全消除赋值)
- [9. 如何看待拷贝优化?](#9. 如何看待拷贝优化?)
- 九、知识点汇总
-
- [知识点 1:必须使用初始化列表的成员](#知识点 1:必须使用初始化列表的成员)
- [知识点 2:初始化顺序规则](#知识点 2:初始化顺序规则)
- [知识点 3:成员缺省值作用](#知识点 3:成员缺省值作用)
- [知识点 4:`explicit` 的作用](#知识点 4:
explicit的作用) - [知识点 5:静态成员变量初始化规则](#知识点 5:静态成员变量初始化规则)
- [知识点 6:静态成员函数特点](#知识点 6:静态成员函数特点)
- [知识点 7:友元关系特点](#知识点 7:友元关系特点)
- [知识点 8:内部类本质](#知识点 8:内部类本质)
- 十一、总结与回顾
- 下期预告
- 结语

◆ 博主: @此生决int
分享编程知识!深挖底层原理!持续原创更新!
热门专栏:
📌 本系列知识点前后关联较强,建议按照专栏顺序阅读哦
本系列主要面向的阅读人群
1,学完 C 语言,准备系统学习 C++ 的同学
2,已经学习过 C++,希望重新梳理知识体系的同学
3,想深入理解 C++ 设计思想与底层原理,而不仅仅停留在语法层面的同学
不止教你会写代码,更带你理解代码为什么这样设计!
C++ 类和对象(下)复习总结
一、本讲核心主线
这一讲继续深入类和对象,主要解决几个问题:
- 构造函数除了函数体赋值,还有什么更规范的初始化方式?
- 为什么有些成员必须放到初始化列表?
- 内置类型能不能隐式转换成类类型?如何禁止?
static成员变量和成员函数到底属于谁?- 友元为什么能访问私有成员?它会不会破坏封装?
- 内部类和外部类是什么关系?
- 匿名对象生命周期有多长?
- 对象拷贝时编译器会做哪些优化?
一句话概括:
类和对象(下)重点是在对象初始化、类级别成员、封装边界和对象临时性行为上继续加深理解。
二、再探构造函数:初始化列表
1. 构造函数体赋值 vs 初始化列表
之前写构造函数时,经常在函数体内给成员变量赋值:
cpp
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
但更规范的初始化方式是初始化列表:
cpp
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{
}
初始化列表格式:
cpp
构造函数名(参数列表)
: 成员1(初始值)
, 成员2(初始值)
, 成员3(初始值)
{
// 构造函数体
}
2. 初始化列表的本质
初始化列表可以理解为:
成员变量真正定义初始化的地方。
函数体内赋值并不是初始化,而是:
成员已经初始化完成后,再进行赋值。
这点非常重要。或许你还是有疑问,他们到底有什么区别?注释1
例如:
cpp
Date(int year)
: _year(year) // 初始化
{
_month = 1; // 赋值
}
注释1
初始化就是创建对象并赋值,赋值就是赋值,所以,初始化列表就是让对象成员在创建的同时进行初始化,那这有什么意义呢?先创建对象再进行初始化不是也可以吗?一般情况是这样的,但是对于一些必须在创建的时候就进行初始化的成员来说,就必须到初始化列表,例如:
- 1,const修饰的变量,
(const 变量只能初始化一次
不能赋值) - 2,引用变量
(引用必须定义时初始化) - 3,没有默认构造的成员对象
(因为要初始化就要先创建对象,但是他没有默认构造函数)
3. 每个成员变量只能在初始化列表中出现一次
错误:
cpp
Date(int year)
: _year(year)
, _year(2024)
{
}
一个成员不能被初始化两次。
4. 必须使用初始化列表的成员
下面三类成员必须放在初始化列表中初始化:
1. 引用成员变量
2. const 成员变量
3. 没有默认构造函数的自定义类型成员变量
示例:
cpp
class Time
{
public:
Time(int hour)
: _hour(hour)
{}
private:
int _hour;
};
class Date
{
public:
Date(int& x, int year = 1, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
, _t(12)
, _ref(x)
, _n(1)
{}
private:
int _year;
int _month;
int _day;
Time _t; // 没有默认构造
int& _ref; // 引用
const int _n; // const
};
如果不放初始化列表,会报错:
Time没有默认构造函数。- 引用成员必须初始化。
const成员必须初始化。
5. 为什么引用和 const 必须初始化?
引用成员
引用定义时必须初始化,且不能重新绑定:
cpp
int& _ref;
所以只能在初始化列表中完成初始化。
const 成员
const 成员一旦初始化后不能修改:
cpp
const int _n;
如果不在初始化阶段给值,进入函数体后就不能再赋值了。
没有默认构造的自定义类型成员
cpp
Time _t;
如果 Time 只有:
cpp
Time(int hour);
没有:
cpp
Time();
那么编译器没办法默认构造 _t,必须在初始化列表中显式传参:
cpp
_t(12)
6. C++11 支持成员变量声明处给缺省值
示例:
cpp
class Date
{
private:
int _year = 1;
int _month = 1;
int _day;
Time _t = 1;
const int _n = 1;
int* _ptr = (int*)malloc(12);
};
注意:
这里不是初始化,而是给初始化列表提供缺省值。
如果某个成员没有在初始化列表中显式初始化,就会使用声明处给的缺省值。
例如:
cpp
Date()
: _month(2)
{
}
则:
_month使用初始化列表中的2。_year使用声明处缺省值1。_t使用声明处缺省值1构造。_n使用声明处缺省值1。_day如果没有缺省值,则是否初始化不确定。
7. 即使不写初始化列表,成员也会走初始化列表
这里的总结非常重要:
无论是否显式写初始化列表,每个构造函数都有 初始化列表。
无论是否在初始化列表中显式初始化成员变量,每个成员变量都要走初始化列表初始化。
也就是说:
cpp
Date()
{
}
并不代表成员不初始化,而是它们仍然经过默认初始化逻辑。
8. 初始化顺序由成员声明顺序决定(常考)
这是本节最容易考的点之一。
初始化列表中成员的书写顺序不决定真正初始化顺序。
真正顺序 由类中成员声明顺序决定。
示例:
cpp
class A
{
public:
A(int a)
: _a1(a)
, _a2(_a1)
{}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2 = 2;
int _a1 = 2;
};
虽然初始化列表写的是:
cpp
_a1(a), _a2(_a1)
但成员声明顺序是:
cpp
int _a2 = 2;
int _a1 = 2;
所以实际先初始化 _a2,再初始化 _a1。
此时 _a2(_a1) 中的 _a1 还没有初始化,结果可能是随机值。
因此运行结果不是直觉中的 1 1。
结论:
成员初始化顺序只看声明顺序,不看初始化列表顺序。建议声明顺序和初始化列表顺序保持一致。
9. 初始化列表使用建议
建议:尽量用初始化列表初始化
尽量使用初始化列表初始化成员变量。
原因:
- 有些成员必须用初始化列表。
- 初始化列表比函数体赋值更符合对象初始化语义。
- 对自定义类型成员来说,初始化列表可以避免"先默认构造再赋值"的额外成本。
- 代码风格更统一。
三、类型转换
1. 内置类型可以隐式转换为类类型
如果一个类有以某个内置类型为参数的构造函数,那么 C++ 支持从该内置类型隐式转换为类类型对象。
示例:
cpp
class A
{
public:
A(int a1)
: _a1(a1)
{}
private:
int _a1 = 1;
int _a2 = 2;
};
可以写:
cpp
A aa1 = 1;
本质:
- 用
1构造一个A的临时对象。 - 再用临时对象拷贝构造
aa1。 - 编译器通常会优化为直接构造。
2. const 引用可以绑定转换出的临时对象(并且只能用const)
cpp
const A& aa2 = 1;
本质:
1先隐式转换成A临时对象。const A&绑定这个临时对象。
这和前面学过的 const 引用绑定临时对象是一致的。
3. C++11 支持多参数隐式转换
例如:
cpp
class A
{
public:
A(int a1, int a2)
: _a1(a1)
, _a2(a2)
{}
};
C++11 后可以写:
cpp
A aa3 = { 2, 2 };
4. 类类型之间也可以隐式转换
示例:
cpp
class B
{
public:
B(const A& a)
: _b(a.Get())
{}
private:
int _b = 0;
};
如果 B 有接收 A 的构造函数,就可以写:
cpp
A aa3 = { 2, 2 };
B b = aa3;//隐式转换后调用构造函数进行初始化。
const B& rb = aa3;
注释1:
cpp
const B& rb = aa3;
≈
B temp(aa3);
const B& rb = temp;
即,aa3先隐私类型转换为B类型,即创建一个临时变量tmp,tmp是用aa3进行初始化,正常情况下临时变量这一行就会销毁,但是,const引用可以延长临时对象生命周期,所以,它没有立即销毁,
注意点:
- temp 是编译器生成的临时 B 对象;
- rb 是 temp 的别名;
- rb 不是 aa3 的别名;
- const 的作用是允许绑定临时对象,并延长其生命周期
本质:
通过构造函数把
A对象转换成B对象。
5. explicit 禁止隐式类型转换
如果不希望构造函数支持隐式转换,可以在构造函数前加 explicit。
cpp
class A
{
public:
explicit A(int a1)
: _a1(a1)
{}
};
此时不允许:
cpp
A aa1 = 1; // 错误
但可以显式构造:
cpp
A aa1(1);
A aa2 = A(1);
结论:
单参数构造函数容易触发隐式类型转换,不希望发生时加
explicit。
四、static 成员
1. 静态成员变量
用 static 修饰的成员变量叫静态成员变量。
cpp
class A
{
private:
static int _scount;
};
特点:
1. 静态成员变量属于整个类,不属于某个具体对象。
2. 所有对象共享同一份静态成员变量。
3. 静态成员变量不存储在对象中。
4. 静态成员变量存放在静态区。
5. 静态成员变量一定要在类外定义初始化。
类内声明:
cpp
static int _scount;
类外定义初始化:
cpp
int A::_scount = 0;
2. 静态成员变量不走构造函数初始化列表
静态成员变量不属于某个对象,因此:
它不会进入构造函数初始化列表。
所以不能像普通成员那样依赖声明处缺省值给构造函数用。
在此强调:
静态成员变量不能在声明位置给缺省值初始化,因为缺省值是给构造函数初始化列表用的,而静态成员不走初始化列表。
注意:现代 C++ 对 static const 整型、inline static 有更多规则,但按本课当前阶段记这些结论即可。
3. 静态成员函数
用 static 修饰的成员函数叫静态成员函数。
cpp
static int GetACount()
{
return _scount;
}
特点:
- 静态成员函数没有
this指针。 - 静态成员函数可以访问静态成员变量和静态成员函数。
- 静态成员函数不能直接访问非静态成员变量。
- 非静态成员函数可以访问静态成员和非静态成员。
为什么静态成员函数不能访问非静态成员?
因为非静态成员属于具体对象,访问它们需要
this指针;而静态成员函数没有this。
4. 静态成员的访问方式
静态成员可以通过类名访问:
cpp
A::GetACount();
也可以通过对象访问:
cpp
a1.GetACount();
但更推荐用类名访问,因为静态成员本来就属于类。
5. 静态成员也受访问限定符限制
例如:
cpp
class A
{
private:
static int _scount;
};
外部不能直接访问:
cpp
cout << A::_scount << endl; // 错误,private
可以提供公共静态函数访问:
cpp
static int GetACount()
{
return _scount;
}
6. 用 static 统计对象数量
cpp
class A
{
public:
A()
{
++_scount;
}
A(const A& t)
{
++_scount;
}
~A()
{
--_scount;
}
static int GetACount()
{
return _scount;
}
private:
static int _scount;
};
int A::_scount = 0;
逻辑:
- 构造对象:数量加一。
- 拷贝构造对象:数量加一。
- 析构对象:数量减一。
- 静态成员变量
_scount记录当前对象数量。
7. 牛客题:求 1+2+...+n
利用静态成员变量和对象数组:
cpp
class Sum
{
public:
Sum()
{
_ret += _i;
++_i;
}
static int GetRet()
{
return _ret;
}
private:
static int _i;
static int _ret;
};
int Sum::_i = 1;//静态变量在类外进行初始化,如果没写,就会出现链接错误!
int Sum::_ret = 0;
调用:
cpp
Sum arr[n];
return Sum::GetRet();
创建 n 个对象,每次构造累加一次。
注意:
这个题利用的是构造函数自动调用和静态成员共享状态。
五、友元
1. 什么是友元?
友元提供了一种突破类访问限定符的方式。
友元分为:
- 友元函数
- 友元类
声明方式是在类内部写:
cpp
friend 函数声明;
friend class 类名;
友元可以访问类的 private 和 protected 成员。
2. 友元函数
作用:友元函数不是成员函数 ,但拥有访问类私有成员和保护成员的权限 。
示例:
cpp
class B;//这个是不是写错了?
class A
{
friend void func(const A& aa, const B& bb);
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
friend void func(const A& aa, const B& bb);
private:
int _b1 = 3;
int _b2 = 4;
};
void func(const A& aa, const B& bb)
{
cout << aa._a1 << endl;
cout << bb._b1 << endl;
}
注意:
func可以访问A和B的私有成员。func不是A或B的成员函数。- 它只是被声明为友元的普通函数。
3. 友元函数声明位置不受访问限定符限制
友元声明写在:
cpp
public:
private:
protected:
任何位置都可以。
因为友元声明不是类成员,不受访问限定符限制。
4. 一个函数可以是多个类的友元
如上例:
cpp
friend void func(const A& aa, const B& bb);
func 同时是 A 和 B 的友元函数,因此可以同时访问两者的私有成员。
5. 友元类
示例:
cpp
class A
{
friend class B;
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
void func1(const A& aa)
{
cout << aa._a1 << endl;
}
void func2(const A& aa)
{
cout << aa._a2 << endl;
}
};
含义:
B是A的友元类,所以B的所有成员函数都可以访问A的私有和保护成员。
6. 友元关系的特点
特点一:友元关系是单向的
如果:
cpp
class A
{
friend class B;
};
表示:
B 可以访问 A 的私有成员。
但不表示:
A 可以访问 B 的私有成员。
特点二:友元关系不具有传递性
如果:
text
A 是 B 的友元
B 是 C 的友元
不能推出:
text
A 是 C 的友元
特点三:友元会破坏封装
友元确实方便,但它允许外部函数或其他类访问私有成员,会增加耦合度。
结论:
友元不宜多用,只在确实能明显简化设计时使用。
典型合理场景:
cpp
operator<<
operator>>
因为它们通常需要作为全局函数访问类的私有成员。
六、内部类
1. 什么是内部类?
如果一个类定义在另一个类内部,这个类叫内部类。
cpp
class A
{
public:
class B
{
public:
void foo(const A& a)
{}
};
};
这里 B 是 A 的内部类。
2. 内部类是独立的类
重点:
内部类是一个独立的类,不是外部类对象的一部分。
例如:
cpp
class A
{
public:
class B
{
int _b1;
};
private:
int _h = 1;
};
sizeof(A) 不会因为 B 中有 _b1 就增加。
因为:
外部类对象中不包含内部类对象。
3. 内部类受外部类类域限制
如果 B 是 A 的内部类,外部使用时要写:
cpp
A::B b;
如果 B 放在 private 或 protected 区域,则类外不能直接使用。
4. 内部类默认是外部类的友元类
重点:
内部类默认是外部类的友元类。
因此内部类可以访问外部类的私有成员。
示例:
cpp
class A
{
private:
static int _k;
int _h = 1;
public:
class B
{
public:
void foo(const A& a)
{
cout << _k << endl; // 可以访问静态私有成员
cout << a._h << endl; // 可以通过对象访问非静态私有成员
}
};
};
注意:
- 访问静态成员
_k不需要对象。 - 访问非静态成员
_h需要通过外部类对象a。
5. 内部类的使用场景
当一个类主要服务于另一个类,并且二者关系非常紧密时,可以考虑内部类。
例如:
text
A 类实现出来主要就是给 B 类内部使用
可以把 A 设计成 B 的内部类。
如果放到 private 区域,就变成 B 的专属内部实现,外部无法直接使用。
这也是一种封装。
6. 用内部类改写求和题
cpp
class Solution
{
class Sum
{
public:
Sum()
{
_ret += _i;
++_i;
}
};
static int _i;
static int _ret;
public:
int Sum_Solution(int n)
{
Sum arr[n];
return _ret;
}
};
这里 Sum 只服务于 Solution,放成内部类更封装。
七、匿名对象
1. 什么是匿名对象?
普通有名对象:
cpp
A aa1;
A aa2(2);
匿名对象:
cpp
A();
A(1);
定义方式:
cpp
类型(实参)
没有对象名,所以叫匿名对象。
2. 匿名对象生命周期(当前行)
匿名对象生命周期:
只在当前这一行。
示例:
cpp
A();
A(1);
A aa2(2);
A(); 和 A(1); 创建出的匿名对象,在当前语句结束后立即析构。
3. 匿名对象适合临时用一下
例如:
cpp
Solution().Sum_Solution(10);
含义:
- 临时构造一个
Solution匿名对象。 - 调用它的
Sum_Solution(10)。 - 当前语句结束后匿名对象销毁。
适合场景:
只需要临时调用一次成员函数,不想专门定义一个对象名。
4. 匿名对象和函数声明易混点
不能这样定义无参对象:(之前反复说过)
cpp
A aa1();
这会被理解为函数声明。
但匿名对象可以这样写:
cpp
A();
因为它没有对象名,不会被解释成函数声明。
八、对象拷贝时的编译器优化
1. 为什么会有拷贝优化?
对象传参和返回值过程中可能产生很多临时对象和拷贝构造。
现代编译器为了提高效率,会在不影响程序正确性的前提下:
尽可能减少不必要的拷贝。
2. 优化规则不是完全固定的
这里强调:
C++ 标准没有严格规定所有优化方式,不同编译器会根据情况自行处理。
不同环境可能看到不同输出:
- VS2019 Debug
- VS2022 Debug
- g++
- clang++
都可能不完全一样。
在 Linux 下可以用:
bash
g++ test.cpp -fno-elide-constructors
关闭构造相关优化,观察原始拷贝过程。
3. 传值传参场景
示例:
cpp
void f1(A aa)
{}
调用:
cpp
A aa1;
f1(aa1);
一般语义:
aa1已经存在。- 传值给形参
aa。 - 需要调用拷贝构造。
4. 隐式类型转换 + 传值传参可能被优化
cpp
f1(1);
语义上可能是:
- 用
1构造一个临时A对象。 - 再拷贝构造形参
aa。
但编译器通常会把:
text
构造 + 拷贝构造
优化为:
text
直接构造形参
5. 匿名对象传参也可能被优化
cpp
f1(A(2));
语义上:
- 创建匿名对象
A(2)。 - 拷贝构造形参
aa。
编译器可能优化为直接构造形参。
6. 传值返回场景
cpp
A f2()
{
A aa;
return aa;
}
语义上:
- 函数内部构造局部对象
aa。 - 返回时可能拷贝构造返回临时对象。
但现代编译器可能进行返回值优化,把局部对象和返回对象合并。
7. 接收返回值时的优化
cpp
A aa2 = f2();
语义上可能涉及:
f2内构造局部对象。- 拷贝构造返回临时对象。
- 用返回临时对象拷贝构造
aa2。
编译器可能把多次拷贝合并为更少的构造,甚至直接构造 aa2。
8. 赋值场景通常难以完全消除赋值
cpp
aa1 = f2();
这里 aa1 已经存在。
即使返回值过程可以优化,最终仍然要把返回结果赋给 aa1,所以赋值运算符重载通常仍会参与。
9. 如何看待拷贝优化?
重点不是死背某个编译器打印几次构造、几次拷贝,而是理解:
- 从语义上看,哪些地方可能调用构造、拷贝构造、赋值。
- 从实际运行看,编译器可能优化掉一些临时对象。
- 不同编译器、不同模式下输出可能不同。
结论:
学习时先按语义理解调用过程,再知道编译器可能优化,不要用某一次打印结果反推语言规则。
九、知识点汇总
知识点 1:必须使用初始化列表的成员
cpp
int& _ref;
const int _n;
Time _t; // Time 没有默认构造
这些成员必须放初始化列表。
知识点 2:初始化顺序规则
初始化顺序只看成员声明顺序,不看初始化列表顺序。
知识点 3:成员缺省值作用
cpp
int _year = 1;
它是给初始化列表提供缺省值。
知识点 4:explicit 的作用
用于禁止单参数构造函数的隐式类型转换。
知识点 5:静态成员变量初始化规则
类内声明:
cpp
static int _scount;
类外定义:
cpp
int A::_scount = 0;
知识点 6:静态成员函数特点
静态成员函数没有
this指针。
知识点 7:友元关系特点
友元关系:
- 单向
- 不传递
知识点 8:内部类本质
内部类是定义在类域中的独立类。
十一、总结与回顾
- 初始化列表是成员真正初始化的地方,构造函数体内是赋值。
- 引用成员、
const成员、无默认构造的自定义类型成员必须放初始化列表。 - 每个构造函数都有初始化列表,每个成员都会走初始化列表。
- 成员初始化顺序只看声明顺序,不看初始化列表书写顺序。
- C++11 成员声明处的值是给初始化列表使用的缺省值。
- 单参数构造函数可能导致隐式类型转换,不想允许就加
explicit。 - 静态成员变量属于类,所有对象共享,不存在对象中,类外初始化。
- 静态成员函数没有
this,不能直接访问非静态成员。 - 友元可以访问私有成员,但不是类成员,会破坏封装,不宜滥用。
- 友元关系单向、不传递。
- 内部类是独立类,不占外部类对象空间,默认是外部类友元。
- 匿名对象生命周期只在当前语句。
- 编译器会优化对象拷贝,打印次数可能因编译器不同而不同。
下期预告
结语
** 本文到此结束,感谢大家的阅读!如果觉得本文对你有所帮助,欢迎点赞、收藏、关注,也欢迎在评论区一起交流讨论。**
** 也欢迎订阅我的**
深入理解 C++系列:从语法入门到底层原理,系统掌握现代 C++
算法系列:从入门到精通,蓝桥杯、ACM、LeetCode 与面试算法全路线
快速复习系列:知识梳理、查漏补缺,考前冲刺必备
Java 速通系列:已学 C 语言,快速上手 Java,轻松备战期末考试
** 愿每一次敲下键盘,都比昨天更进一步!** ** 愿每一行代码落下,都让未来多一种可能!**