前言:什么是默认构造函数?
满足默认构造的唯一条件 :可以不用传任何参数就能创建对象 三种情况:
1.全缺省参数的构造函数
cpp
#include <iostream>
using namespace std;
class Time {
public:
// 这是一个默认构造函数(全缺省参数)
Time(int hour = 0) // 有默认值 = 可以不传参
{
_hour = hour;
}//
void print() {
cout << "The time is " << _hour << endl;
}
private:
int _hour;
};
int main() {
Time t1; // 有默认值 = 可以不传参
t1.print();
Time t2(10);//int hour = 10; 过程:你传的 10 覆盖了默认值 0 _hour = hour=10;
t2.print();
}
- 无参构造函数
cpp
#include <iostream>
using namespace std;
class Time {
public:
// 这是一个默认构造函数(全缺省参数)
Time()
{
_hour = 99;
}//
void print() {
cout << "The time is " << _hour << endl;
}
private:
int _hour;
};
int main() {
Time t1; // 有默认值 = 可以不传参
t1.print();
return 0;
}
- 编译器自动生成的默认构造函数(如果用户没写任何构造函数)但对int ,char等内置的类型不初始化,和未初始化的局部变量一样是随机值。
cpp
#include <iostream>
using namespace std;
class Time {
public:
// 这是一个默认构造函数(全缺省参数)
void print() {
cout << "The time is " << _hour << endl;
}
private:
int _hour;
};
int main() {
Time t1; // 有默认值 = 可以不传参
t1.print();
return 0;
}

一.再探构造函数:初始化列表
1. 什么是初始化列表?
初始化列表是构造函数的一种特殊语法,用于在进入构造函数体之前初始化成员变量。
cpp
class Date {
public:
// 初始化列表:冒号后面跟着成员变量(初始值)
Date(int year, int month, int day)
: _year(year) // 用 year 初始化 _year
, _month(month) // 用 month 初始化 _month
, _day(day) // 用 day 初始化 _day
{
// 构造函数体,此时成员已经初始化完成
}
private:
int _year;
int _month;
int _day;
};
二. 为什么需要初始化列表?
核心原因:有些成员必须在初始化列表中初始化,不能在函数体内赋值。
1必须使用初始化列表的三种情况:
cpp
class Time {
public:
Time(int hour) : _hour(hour) {} // 没有默认构造函数
private:
int _hour;
};
class Date {
public:
Date(int& ref, int n)
: _t(12) // 1. 没有默认构造的类类型成员
, _ref(ref) // 2. 引用成员变量
, _n(n) // 3. const成员变量
{
// 如果上面不在初始化列表中初始化,这里会报错
}
private:
Time _t; // 没有默认构造函数,必须在初始化列表初始化
int& _ref; // 引用,必须在初始化列表初始化
const int _n; // const,必须在初始化列表初始化
};
2 初始化列表的执行顺序
重要:初始化顺序按成员在类中的声明顺序,而不是初始化列表中的顺序!
cpp
class A {
public:
A(int a)
: _a2(a) // 虽然先写 _a2,但实际先初始化 _a1
, _a1(_a2) // 因为 _a1 在类中先声明
{
// _a1 会用 _a2 初始化,但此时 _a2 还未初始化!
// 所以 _a1 的值是随机值
}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
int _a1 = 2; // 先声明
int _a2 = 2; // 后声明
};
int main() {
A aa(1);
aa.Print(); // 输出:随机值 1
return 0;
}

:建议:声明顺序和初始化列表顺序保持一致,避免这种陷阱。
3 成员变量缺省值(C++11)
cpp
class Date {
private:
// 声明时直接给缺省值,供初始化列表使用
int _year = 1900;
int _month = 1;
int _day = 1;
const int _n = 1; // const 也可以给缺省值
int* _ptr = (int*)malloc(12);
};
// 如果初始化列表没有显示初始化,就使用缺省值
Date::Date(int year, int month, int day)
: _year(year) // _year 用参数,_month 和 _day 用缺省值
{
}
4初始化列表总结
cpp
每个构造函数都有初始化列表(即使没写)
↓
每个成员变量都要走初始化列表初始化
↓
┌─────────────────────────────────────┐
│ 显示在初始化列表 → 用指定的值初始化 │
├─────────────────────────────────────┤
│ 未显示在初始化列表 │
│ ├─ 有缺省值 → 用缺省值初始化 │
│ └─ 无缺省值 → 内置类型:随机值 │
│ 自定义类型:调用默认构造│
└─────────────────────────────────────┘
三.终极结论
成员变量的初始化 = 只能在初始化列表完成!
构造函数体 {} 里面 = 只能赋值,不能初始化!
这是 C++ 铁律!不能改!
1一句话讲透本质
- 初始化(只能在初始化列表): 变量诞生的时候就给值
- 赋值(在函数体
{}): 变量已经诞生了,再改它的值
2. 流程是固定的,编译器强制按这个顺序走
cpp
Date()
: _a(10) // 1. 先执行初始化列表【所有成员必须在这里诞生!】
{
_a = 20; // 2. 再执行函数体【这里只是赋值!】
}
3真正发生的事情:
- 进入构造函数前 → 必须先初始化所有成员 必须走初始化列表!躲不掉!
- 进入
{}后 → 只能赋值,不能再 "初始化"
为什么:因为 前面提到的const 成员,引用成员,没有默认构造的自定义类型,3 种成员只能初始化,不能赋值。
二、类型转换:隐式转换与 explicit
1. 内置类型 → 类类型
cpp
class A {
public:
A(int a) : _a(a) {} // 单参数构造函数
private:
int _a;
};
int main() {
// 隐式类型转换:1 → 临时A对象 → 拷贝构造aa
A aa = 1; // 等价于 A aa(1)
// 编译器优化:连续构造+拷贝构造 → 直接构造
// 所以上面代码实际只调用一次构造函数
const A& ref = 1; // 引用也可以绑定临时对象
}
3. 类类型之间的转换
cpp
class A {
public:
A(int a) : _a(a) {}
int Get() const { return _a; }
private:
int _a;
};
class B {
public:
B(const A& a) : _b(a.Get()) {} // 可以用 A 构造 B
private:
int _b;
};
int main() {
A aa(10);
B b = aa; // A → B 隐式转换
const B& rb = aa; // 引用也可以
}
4. explicit 关键字:禁止隐式转换
cpp
class A {
public:
explicit A(int a) : _a(a) {} // 加上 explicit
private:
int _a;
};
int main() {
A aa = 1; // ❌ 编译错误!explicit 禁止隐式转换
A aa(1); // ✅ 只能这样直接构造
}
三、static 成员:属于类的成员
1. static 成员变量
cpp
class A {
public:
A() { ++_count; } // 每构造一个对象,计数+1
~A() { --_count; } // 每析构一个对象,计数-1
static int GetCount() { return _count; } // 静态成员函数
private:
static int _count; // 静态成员变量,类内声明
};
// 类外定义和初始化(必须)
int A::_count = 0;
int main() {
cout << A::GetCount() << endl; // 0
A a1, a2;
A a3(a1);
cout << A::GetCount() << endl; // 3
cout << a1.GetCount() << endl; // 3(对象也可以调用)
}
2. static 成员的特点
| 特点 | 说明 |
|---|---|
| 存储位置 | 静态区,不属于任何对象 |
| 生命周期 | 程序开始到结束 |
| 访问方式 | 类名::成员 或 对象.成员 |
| 初始化 | 必须在类外定义和初始化 |
| 缺省值 | ❌ 不能在声明位置给缺省值 |
3. static 成员函数
cpp
class A {
public:
static void Func() {
// 静态成员函数没有 this 指针
_count = 10; // ✅ 可以访问静态成员
// _a = 10; // ❌ 不能访问非静态成员
}
private:
int _a;
static int _count;
};
4. 经典应用:计算1+2+...+n(不能使用乘除法、循环)
cpp
#include <iostream>
using namespace std;
class Solution {
private:
class Sum {
public:
Sum() {
_ret += _i;
_i++;
}
};
static int _i;
static int _ret;
public:
int Sum_Solution(int n) {
_i = 1;
_ret = 0;
// 修复:用new创建数组,解决"变量不能定义数组长度"报错
Sum* arr = new Sum[n];
delete[] arr; // 释放内存
return _ret;
}
};
int Solution::_i = 1;
int Solution::_ret = 0;
// 测试
int main() {
Solution sol;
cout << sol.Sum_Solution(3) << endl; // 6
cout << sol.Sum_Solution(5) << endl; // 15
cout << sol.Sum_Solution(10) << endl; // 55
return 0;
}
算法原理不变:创建 n 个对象 → 触发 n 次构造 → 自动累加 1~n
四、友元:突破封装
1. 友元函数
cpp
class A {
// 声明友元函数,可以访问私有成员
friend void Print(const A& a);
private:
int _data = 10;
};
// 友元函数不是成员函数,可以访问私有成员
void Print(const A& a) {
cout << a._data << endl; // ✅ 可以访问私有成员
}
int main() {
A a;
Print(a); // 输出 10
}
2. 友元类
cpp
class A {
friend class B; // B 是 A 的友元,B 可以访问 A 的私有成员
private:
int _data = 10;
};
class B {
public:
void Show(const A& a) {
cout << a._data << endl; // ✅ 可以访问 A 的私有成员
}
};
3. 友元的特性
| 特性 | 说明 |
|---|---|
| 单向性 | A 是 B 的友元,但 B 不是 A 的友元 |
| 不可传递 | A 是 B 的友元,B 是 C 的友元,但 A 不是 C 的友元 |
| 位置不限 | 可以在类中任意位置声明,不受访问限定符影响 |
| 破坏封装 | 谨慎使用,不要过度依赖 |
五、内部类
1. 什么是内部类?
内部类默认是外部类的友元
这是 C++ 规定:在一个类内部定义的嵌套类(内部类),默认可以直接访问外部类的所有成员(public/protected/private)!
cpp
#include <iostream>
using namespace std;
class Outer {
public:
// 内部类 Inner
class Inner {
public:
// 内部类可以直接访问外部类的 private 成员!
void Show(const Outer& o) {
cout << o._data << endl;
}
};
private:
int _data = 10; // 外部类私有成员
};
int main() {
Outer::Inner inner; // 内部类属于外部类作用域
Outer outer;
inner.Show(outer); // 输出:10
return 0;
}
2. 内部类的特点
-
独立存在,不包含在外部类对象中(
sizeof(Outer)不包含Inner) -
默认是外部类的友元
-
受外部类访问限定符限制(放在
private就成了专属内部类)
六、匿名对象
1. 什么是有名对象 vs 匿名对象
没有名字的临时对象 = 匿名对象
- 生命周期:只存在当前一行代码
- 执行完这一行,立刻自动调用析构函数销毁
- 作用:临时用一次,用完就扔
cpp
#include <iostream>
using namespace std;
class A {
public:
A(int a = 0) : _a(a) {
cout << "构造 A: " << _a << endl;
}
~A() {
cout << "析构 A: " << _a << endl;
}
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
cout << "调用 Sum_Solution: " << n << endl;
return n;
}
};
int main() {
cout << "--- 有名对象 ---" << endl;
A a1;
cout << "--- 匿名对象 ---" << endl;
A();
A(10);
cout << "--- 匿名对象调用方法 ---" << endl;
Solution().Sum_Solution(5);
cout << "--- main 结束 ---" << endl;
return 0;
}

最终总结(背会这 3 句)
A()/A(10)= 匿名对象- 生命周期只有当前一行,执行完立刻销毁
- 最常用:
Solution().方法()临时调用一次成员函数
七、编译器优化
1. 常见的拷贝优化
现代编译器会在不影响正确性的前提下,尽量减少不必要的拷贝。
核心结论:
- 传值传参、传值返回 → 本来都要拷贝构造
- 匿名对象 / 隐式转换 / 局部返回 → 编译器会优化,消除拷贝,直接构造
- 优化规则:
- 无名临时对象 → 100% 优化
- 有名局部对象返回 → 大概率优化
cpp
#include <iostream>
using namespace std;
class A {
public:
A(int a = 0) : _a(a) {
cout << "构造 A: " << _a << endl;
}
A(const A& aa) {
cout << "拷贝构造" << endl;
}
private:
int _a;
};
void f1(A aa) {}
A f2() {
A aa;
return aa;
}
int main() {
cout << "==== f1(a1) ====" << endl;
A a1;
f1(a1); // 构造 + 拷贝
cout << "\n==== f1(1) ====" << endl;
f1(1); // 优化:直接构造
cout << "\n==== f1(A(2)) ====" << endl;
f1(A(2)); // 优化:直接构造
cout << "\n==== A a2 = f2() ====" << endl;
A a2 = f2(); // 优化:直接构造 a2
return 0;
}

分析:



