目录
[1. 初始化列表](#1. 初始化列表)
[1.1 基本语法](#1.1 基本语法)
[1.2 为什么使用初始化列表?](#1.2 为什么使用初始化列表?)
[1.3 初始化顺序](#1.3 初始化顺序)
[2. 创建和实例化对象](#2. 创建和实例化对象)
[2.1 栈上分配(自动存储期)](#2.1 栈上分配(自动存储期))
[2.2 堆上分配(动态存储期)](#2.2 堆上分配(动态存储期))
[2.3 栈 vs 堆:Cherno 的建议](#2.3 栈 vs 堆:Cherno 的建议)
[3. new 关键字](#3. new 关键字)
[3.1 new 的作用](#3.1 new 的作用)
[3.2 new 与 malloc 的区别](#3.2 new 与 malloc 的区别)
[3.3 数组形式的 new 与 delete](#3.3 数组形式的 new 与 delete)
[3.4 定位 new(placement new)](#3.4 定位 new(placement new))
[4. 隐式构造与 explicit 关键字](#4. 隐式构造与 explicit 关键字)
[4.1 隐式构造(隐式类型转换)](#4.1 隐式构造(隐式类型转换))
[4.2 explicit 关键字](#4.2 explicit 关键字)
[5. 运算符与运算符重载](#5. 运算符与运算符重载)
[5.1 基本概念](#5.1 基本概念)
[5.2 成员函数 vs 全局函数](#5.2 成员函数 vs 全局函数)
[5.3 常用运算符重载示例](#5.3 常用运算符重载示例)
1. 初始化列表
1.1 基本语法
初始化列表位于构造函数参数列表之后、函数体之前,以冒号 : 开头,多个成员用逗号分隔。
cpp
class Entity {
private:
std::string m_Name;
int m_Age;
public:
Entity(const std::string& name, int age)
: m_Name(name), m_Age(age) // 初始化列表
{
// 构造函数体
}
};
1.2 为什么使用初始化列表?
-
效率:成员变量在进入构造函数体之前已经完成初始化(直接调用其构造函数),而不是先默认构造再在函数体内赋值。
-
必须使用初始化列表的场景:
-
const成员变量 -
引用成员变量
-
没有默认构造函数的成员对象
-
基类构造函数(尤其是需要带参数的基类)
-
cpp
class Base {
public:
Base(int x) {}
};
class Derived : public Base {
private:
const int m_ConstVal;
int& m_Ref;
public:
Derived(int a, int& ref)
: Base(a), m_ConstVal(a), m_Ref(ref) // 必须使用初始化列表
{
}
};
1.3 初始化顺序
初始化顺序 按照成员在类中声明的顺序,而不是初始化列表的顺序。为避免依赖未初始化的成员,应保证顺序一致。
cpp
class Example {
int m_A;
int m_B;
public:
Example() : m_B(2), m_A(m_B) {} // 危险:m_A 先初始化,此时 m_B 尚未初始化
};
2. 创建和实例化对象
2.1 栈上分配(自动存储期)
-
语法 :
Entity e;或Entity e("name"); -
生命周期:在作用域结束时自动调用析构函数并释放内存。
-
优点:极快(仅栈指针移动),无需手动管理内存。
-
缺点:生命周期局限于作用域;对象大小受栈空间限制(通常几 MB)。
cpp
void func() {
Entity e; // 栈上创建,自动调用构造函数
e.DoSomething();
} // 自动调用析构函数
2.2 堆上分配(动态存储期)
-
语法 :
Entity* e = new Entity();或new Entity("name") -
生命周期 :需显式调用
delete e;释放,否则内存泄漏。 -
优点:对象生命周期可控,可跨作用域存在;适合大型对象或需要动态创建的场景。
-
缺点:速度慢(需堆分配、内存管理开销),容易造成内存泄漏或悬空指针。
cpp
Entity* createEntity() {
Entity* e = new Entity("Cherno");
return e; // 对象在堆上,函数返回后仍然存在
}
void use() {
Entity* e = createEntity();
e->DoSomething();
delete e; // 必须手动释放
}
2.3 栈 vs 堆:Cherno 的建议
-
优先使用栈分配,除非确实需要动态生命周期(如多态、对象数量不确定、跨函数传递所有权)。
-
如果需要动态分配,现代 C++ 应使用 智能指针 (
std::unique_ptr、std::shared_ptr)代替裸指针,避免手动delete。
3. new 关键字
3.1 new 的作用
-
分配内存 :调用
operator new(类似malloc)分配足够的内存。 -
调用构造函数:在该内存上构造对象。
-
返回指向该对象的指针。
3.2 new 与 malloc 的区别
| 特性 | new |
malloc |
|---|---|---|
| 内存来源 | 自由存储区(通常是堆) | 堆 |
| 是否调用构造函数 | ✅ | ❌ |
| 返回类型 | 类型安全的指针(T*) |
void* |
| 内存大小 | 自动计算(sizeof(T)) |
需手动指定字节数 |
| 失败处理 | 抛出 std::bad_alloc 异常 |
返回 nullptr |
3.3 数组形式的 new 与 delete
cpp
int* arr = new int[10]; // 分配 10 个 int 的数组
delete[] arr; // 必须使用 delete[],保证调用每个元素的析构函数
3.4 定位 new(placement new)
可以在已分配的内存上构造对象,常用于自定义内存管理。
cpp
char* buffer = new char[sizeof(Entity)];
Entity* e = new (buffer) Entity("Cherno"); // 在 buffer 位置构造对象
e->~Entity(); // 手动调用析构
delete[] buffer;
4. 隐式构造与 explicit 关键字
4.1 隐式构造(隐式类型转换)
-
隐式构造:当构造函数只接受一个参数(或除第一个参数外都有默认值),编译器允许在需要该类型对象的地方使用该参数类型,自动调用构造函数进行隐式转换。
-
隐式转换可能造成意料之外的对象构造,降低代码可读性,甚至引发隐蔽的错误。
4.2 explicit 关键字
- 作用 :在构造函数前加
explicit,禁止该构造函数参与隐式类型转换。
cpp
class Object {
private:
std::string m_Name;
int m_Age;
public:
explicit Object(const const std::string name) :
m_Name(name), m_Age(-1) {}
Object(int age)
: m_Name("Unknown"), m_Age(age) {}
};
void PrintObj(const Object& obj) {
}
int main() {
//隐式构造
PrintObj(1);
Object obj = 22;
//explicit 会禁止隐式构造
//PrintObj(std::string("Cheer"));会报错
//Object obj = std::string("Cheer");
std::cin.get();
}
5. 运算符与运算符重载
5.1 基本概念
运算符重载允许为用户定义的类型提供自定义的运算符行为。可以重载的运算符包括算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符、下标运算符、函数调用运算符等。某些运算符不能重载(如 ::、.、.*、?:、sizeof 等)。
5.2 成员函数 vs 全局函数
-
成员函数 :左操作数是当前对象(
this)。 -
全局函数:可灵活支持隐式类型转换,通常需要声明为友元。
cpp
class Vector2 {
public:
float x, y;
Vector2 operator+(const Vector2& other) const { // 成员函数
return {x + other.x, y + other.y};
}
};
// 全局函数版本(通常用于对称性,如实现 operator<<)
Vector2 operator-(const Vector2& a, const Vector2& b) {
return {a.x - b.x, a.y - b.y};
}
5.3 常用运算符重载示例
赋值运算符 =
cpp
class String {
char* m_Data;
public:
String& operator=(const String& other) {
if (this != &other) { // 自赋值检查
delete[] m_Data;
m_Data = new char[strlen(other.m_Data) + 1];
strcpy(m_Data, other.m_Data);
}
return *this;
}
};
流插入 << 与流提取 >>
cpp
class Point {
int m_X, m_Y;
public:
friend std::ostream& operator<<(std::ostream& os, const Point& p);
};
std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.m_X << ", " << p.m_Y << ")";
return os;
}
下标运算符 []
cpp
class Array {
int data[100];
public:
int& operator[](int index) { return data[index]; }
const int& operator[](int index) const { return data[index]; }
};
函数调用运算符 ()
cpp
class Multiply {
public:
int operator()(int a, int b) const { return a * b; }
};
Multiply mul;
int result = mul(3, 4); // 12