问题引入 🧠 编译器默认生成哪些函数?
默认构造函数(ClassName())
拷贝构造函数(ClassName(const ClassName&))
拷贝赋值运算符(operator=(const ClassName&))
移动构造函数(ClassName(ClassName&&))
移动赋值运算符(operator=(ClassName&&))
析构函数(~ClassName())
1、默认构造函数
✅ ClassName();
(1)构造函数的函数名与类名相同
class A {
public:
A(); // 构造函数,名字与类名A一致
};
(2)构造函数无返回值(void也不对)
class A {
public:
void A(); // ❌ 这是普通函数,不是构造函数
};
(3)对象实例化时编译器自动调用构造函数
class A {
public:
A() { cout << "Constructor called"; }
};
A obj; // 实例化,自动调用 A()
说明:在创建 obj 的时候,编译器会立即自动调用构造函数,不需要你手动调用。你不能用 obj.A(); 这样调用构造函数,它只在对象创建时触发。
(4)构造函数支持重载
重载 = 函数名相同但参数不同
class Person {
public:
Person(); // 无参构造
Person(string name); // 带一个参数
Person(string name, int age); // 带两个参数
};
(5)默认构造函数的三种形式
只要不传实参也能调用的,都叫默认构造函数。一个类最多只能有一个默认构造函数。
①编译器生成的默认构造函数(你不写任何构造函数)
②你手写的无参构造函数
③你手写的“全默认参数”的构造函数
(6)如果你定义了构造函数,编译器就不会再生成默认构造函数
如果你没有写任何构造函数,编译器会自动帮你生成一个默认构造函数(无参)。
一旦你手动写了任何一个构造函数(无论是否带参),编译器就不再帮你生成默认构造函数了。
class A {
public:
A(int x) {} // 用户定义的带参构造函数
};
A a1; // ❌ 错误:编译器不会生成无参构造函数
A a2(5); // ✅ 正确
2、拷贝构造函数
✅ 正确的拷贝构造函数定义:ClassName(const ClassName& other);
拷贝构造函数是默认构造函数的一个重载形式,用已有对象去构造一个新对象
(1)必须接受一个同类对象的引用作为参数,不可以不接受参数,也不能接收多个参数;
如果你用传值方式来声明拷贝构造函数,会出现无限递归的情况。要调用这个构造函数,就得传进一个对象(按值);但“按值传参”意味着要先对传入对象做一次拷贝;而定义的拷贝构造函数就是在执行“拷贝”,所以这又会递归调用构造函数,最终 → 无限递归调用拷贝构造函数 → 栈溢出
(2)可以不写拷贝构造函数,但是编译器一般情况会默认生成一个“浅拷贝”的构造函数,它会把类的每个成员逐一拷贝。如果你定义了其他的构造函数,尤其是移动构造函数或赋值运算符,就不会自动生成拷贝构造函数;
【正确】像Date这样的类,需要的就是浅拷贝,那么编译器自动生成的拷贝构造函数就够用了,我们不需要自己写。
class A {
public:
int x;
std::string s;
};
// 默认生成如下拷贝构造函数:
A(const A& other) {
x = other.x;
s = other.s;
}
【错误】例如:
【1】栈(Stack)这样的类,编译器自动生成的拷贝构造函数就不能满足我们的需求。两个栈的地址是一样的,浅拷贝会导致析构两次、程序崩溃等问题。

【2】如果你的类含有指针成员或动态资源管理(比如手动 new 分配的内存),默认生成的拷贝构造函数会导致浅拷贝问题(两个对象指向同一块内存),可能造成资源冲突或重复释放。此时需要手动写一个深拷贝构造函数。
(3)拷贝构造函数的权限问题:没有写或者写在public中表示公有类型,可以拷贝对象;自定义为private或者使用=delete表示私有或者禁止生成,不可以拷贝对象;
(4)拷贝函数不可以重载,从重载的定义来看,重载需要参数不一样,但是拷贝构造函数的参数只有一种合法形式;
(5)必须写拷贝构造函数的情况:类中成员包含指针、需要日志跟踪对象创建、明确控制拷贝行为。
3、移动构造函数
✅ 移动构造函数定义:ClassName(ClassName&& other);
作用--用于将资源从一个优质对象转移到新对象中,避免不必要的新拷贝,右值引用(&&)
使用场景--
| 场景 | 是否触发移动构造 |
|---|---|
std::move(obj) |
✅ 明确调用移动构造 |
| 返回局部对象(编译器优化) | ✅ 通常会触发移动 |
emplace_back / push_back(std::move(obj)) |
✅ 容器中触发移动 |
a = std::move(b); |
❌ 不是构造,而是赋值,调用的是移动赋值运算符 |
移动构造函数和拷贝构造函数的区别
| 特性 | 拷贝构造函数 | 移动构造函数 |
|---|---|---|
| 参数类型 | const T& |
T&&(右值引用) |
| 调用条件 | 绑定左值 | 绑定右值 |
| 开销 | 通常涉及内存拷贝 | 通常只转移指针,速度快 |
| 适用于 | 普通对象复制 | 优化临时对象使用 |
4、拷贝赋值运算符(operator=(const ClassName&))
5、移动赋值运算符(operator=(ClassName&&))
问题1- 🧠什么时候会生成拷贝构造函数?
| 你写了什么 | 拷贝构造函数会生成吗? | 说明 |
|---|---|---|
| 什么都不写 | ✅ 会生成 | 自动按成员逐个拷贝 |
| 写了其他普通构造函数(如带参数) | ✅ 会生成 | 不影响拷贝构造函数的自动生成 |
| 写了 移动构造函数 或 移动赋值运算符 | ❌ 不再自动生成拷贝构造函数 | 编译器认为你要精细控制资源 |
| 写了 拷贝赋值运算符 或 析构函数 | ❌ 不一定生成,需要显式写 = default生成 |
问题2- 🧠 C++中构造函数可以是虚函数吗?
(1)构造函数不能是虚函数,虚函数的机制依赖虚函数表(编译时创建),而的调用构造函数完成后才能建立虚表对象。
构造函数用来初始化对象、设置虚函数表指针---->对象初始化后虚表才建立完成
(补充说明)
(1)除了构造函数,静态成员函数和友元函数也不能是虚函数,静态成员函数与类不适于某个对象相关联,而友元函数不属于类的成员变量
(3)析构函数可以是虚函数,基类的析构函数尽量写成虚函数,这样做的目的是确保在删除一个指向派生类对象的基类指针时,可以正确调用派生类对象的析构函数,避免资源泄露
浙公网安备 33010602011771号