问题引入 🧠 编译器默认生成哪些函数?

默认构造函数(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)析构函数可以是虚函数,基类的析构函数尽量写成虚函数,这样做的目的是确保在删除一个指向派生类对象的基类指针时,可以正确调用派生类对象的析构函数,避免资源泄露