构造函数,析构函数

介绍

在C++中,构造函数和析构函数是类的重要组成部分,它们用于对象的初始化和清理。下面是对这两个概念的详细讲解:

构造函数(Constructor)

构造函数是一个特殊的成员函数,用于初始化对象。当创建对象时,构造函数会被自动调用。构造函数的名称与类名相同,并且没有返回值。

特点:

  • 名称相同:构造函数的名称与类名相同。
  • 没有返回值:构造函数不返回任何值,也不可以指定返回类型。
  • 可以重载:可以定义多个构造函数,参数不同以实现不同的初始化方式。
class Point {  
private:  
    int x, y;  

public:  
    // 默认构造函数  
    Point() {  
        x = 0;  
        y = 0;  
    }  

    // 带参数的构造函数  
    Point(int xVal, int yVal) {  
        x = xVal;  
        y = yVal;  
    }  

    void display() {  
        std::cout << "Point(" << x << ", " << y << ")" << std::endl;  
    }  
};  

int main() {  
    Point p1; // 调用默认构造函数  
    Point p2(10, 20); // 调用带参数的构造函数  

    p1.display(); // 输出: Point(0, 0)  
    p2.display(); // 输出: Point(10, 20)  

    return 0;  
}

析构函数(Destructor)

析构函数是一个特殊的成员函数,用于清理对象在其生命周期内所占用的资源。当对象的生命周期结束时,析构函数会被自动调用。析构函数的名称与类名相同,但前面加上一个波浪号(~),同样没有返回值。

特点:

  • 名称相同:析构函数的名称与类名相同,但前面加上~
  • 没有参数:析构函数不接受参数,也不能重载。
  • 自动调用:当对象的作用域结束或被删除时,析构函数会自动调用。
class Resource {  
public:  
    Resource() {  
        std::cout << "Resource acquired." << std::endl;  
    }  

    ~Resource() {  
        std::cout << "Resource released." << std::endl;  
    }  
};  

int main() {  
    Resource res; // 创建对象时调用构造函数  
    // 当res超出作用域时,析构函数会被调用  
    return 0;  
}

构造函数和析构函数的作用

  • 构造函数:用于初始化对象的状态,分配资源(如动态内存、文件句柄等)。
  • 析构函数:用于释放对象占用的资源,防止内存泄漏和资源浪费。

动态内存分配中的构造和析构

在使用动态内存分配(如使用new关键字)时,构造函数和析构函数的作用尤为重要。

class MyClass {  
public:  
    MyClass() {  
        std::cout << "Constructor called." << std::endl;  
    }  

    ~MyClass() {  
        std::cout << "Destructor called." << std::endl;  
    }  
};  

int main() {  
    MyClass* obj = new MyClass(); // 调用构造函数  
    delete obj; // 调用析构函数  
    return 0;  
}

用法

构造函数:

类相当于定义了一个新类型,该类型生成在堆或栈上的对象时内存排布和 c 语言相同。但是 c++规定,C++有在类对象创建时就在对应内存将数据初始化的能力,这就是构造函数。

#include <iostream>

class Test
{
public:
    // 类的函数 常用写法1 直接类内部实现
    Test() 
    {
        std::cout << "默认构造函数" << std::endl;
    }
    Test(int i_, int j_, int k_) : i(i_), j(j_), k(new int(k_)) 
    {
        std::cout << "普通构造函数" << std::endl;
    }
    Test(const Test& test) : i(test.i), j(test.j), k(new int(*test.k)) // 深拷贝写法
    {
        std::cout << "拷贝构造函数" << std::endl;
    }

    ~Test()
    {
        delete k;
    }

private:
    int i;
    int j;
    int* k;
};


int main() {
    Test t1;
    Test t2(1, 2, 3);
    
    //Test t3 = t1;    // 这里会报错,因为此时t1.k是nullptr,解引用会报错的
    
    // 拷贝构造函数的两种写法
    Test t4(t2);
    Test t5 = t4;
    return 0;
}

构造函数就是 C++提供的必须有的在对象创建时初始化对象的方法,(默认的什么都不做也是一种初始化的方式)

析构函数:

析构函数介绍:当类对象被销毁时,就会调用析构函数。栈上对象的销毁时机就是函数栈销毁时,堆上的对象销毁时机就是该堆内存被手动释放时,如果用new申请的这块堆内存,那调用 delete 销毁这块内存时就会调用析构函数。

当类对象销毁时有一些我们必须手动操作的步骤时,析构函数就派上了用场。所以,几乎所有的类我们都要写构造函数,析构函数却未必需要。

构造函数有哪些类型

在C++中,构造函数主要有以下几种类型,每种类型都有其特定的用途和特点。下面我将逐一介绍这些构造函数,并提供相应的示例代码。

默认构造函数

默认构造函数是指在没有提供任何参数的情况下被调用的构造函数。它可以是无参构造函数,也可以是带有默认参数的构造函数。

class Person {  
public:  
    Person() {  // 默认构造函数  
        name = "未知";  
        age = 0;  
    }  

    void display() {  
        std::cout << "Name: " << name << ", Age: " << age << std::endl;  
    }  

private:  
    std::string name;  
    int age;  
};  

int main() {  
    Person p;  // 调用默认构造函数  
    p.display();  // 输出: Name: 未知, Age: 0  
    return 0;  
}

带参数的构造函数

带参数的构造函数允许在创建对象时传递参数,以初始化对象的成员变量。

class Person {  
public:  
    Person(std::string n, int a) {  // 带参数的构造函数  
        name = n;  
        age = a;  
    }  

    void display() {  
        std::cout << "Name: " << name << ", Age: " << age << std::endl;  
    }  

private:  
    std::string name;  
    int age;  
};  

int main() {  
    Person p("Alice", 30);  // 调用带参数的构造函数  
    p.display();  // 输出: Name: Alice, Age: 30  
    return 0;  
}

拷贝构造函数

拷贝构造函数用于通过另一个同类对象来初始化新对象。它通常用于对象的复制。

class Person {  
public:  
    Person(std::string n, int a) {  // 带参数的构造函数  
        name = n;  
        age = a;  
    }  

    Person(const Person &other) {  // 拷贝构造函数  
        name = other.name;  
        age = other.age;  
    }  

    void display() {  
        std::cout << "Name: " << name << ", Age: " << age << std::endl;  
    }  

private:  
    std::string name;  
    int age;  
};  

int main() {  
    Person p1("Bob", 25);  // 调用带参数的构造函数  
    Person p2 = p1;  // 调用拷贝构造函数  
    p2.display();  // 输出: Name: Bob, Age: 25  
    return 0;  
}

移动构造函数

移动构造函数在C++11中引入,用于通过移动语义来初始化对象,通常用于优化性能,避免不必要的复制。

#include <iostream>  
#include <string>  

class Person {  
public:  
    Person(std::string n, int a) : name(n), age(a) {  // 带参数的构造函数  
        std::cout << "构造: " << name << std::endl;  
    }  

    Person(Person &&other) noexcept {  // 移动构造函数  
        name = std::move(other.name);  
        age = other.age;  
        std::cout << "移动构造: " << name << std::endl;  
    }  

    void display() {  
        std::cout << "Name: " << name << ", Age: " << age << std::endl;  
    }  

private:  
    std::string name;  
    int age;  
};  

int main() {  
    Person p1("Charlie", 40);  // 调用带参数的构造函数  
    Person p2 = std::move(p1);  // 调用移动构造函数  
    p2.display();  // 输出: Name: Charlie, Age: 40  
    return 0;  
}

委托构造函数

委托构造函数允许一个构造函数调用另一个构造函数,以减少代码重复。

class Person {  
public:  
    Person() : Person("未知", 0) {  // 委托构造函数  
        // 可以留空  
    }  

    Person(std::string n, int a) {  // 带参数的构造函数  
        name = n;  
        age = a;  
    }  

    void display() {  
        std::cout << "Name: " << name << ", Age: " << age << std::endl;  
    }  

private:  
    std::string name;  
    int age;  
};  

int main() {  
    Person p;  // 调用默认构造函数,实际上委托给了带参数的构造函数  
    p.display();  // 输出: Name: 未知, Age: 0  
    return 0;  
}

explicit构造函数

使用explicit关键字可以防止构造函数被隐式调用,避免不必要的类型转换。

class Person {  
public:  
    explicit Person(int a) {  // explicit构造函数  
        age = a;  
    }  

    void display() {  
        std::cout << "Age: " << age << std::endl;  
    }  

private:  
    int age;  
};  

int main() {  
    Person p(30);  // 正确,调用构造函数  
    p.display();  // 输出: Age: 30  

    // Person p2 = 40;  // 错误,不能隐式转换  
    return 0;  
}

类中没有显示定义默认构造函数,什么时候生成默认构造函数

视频讲解:

https://www.bilibili.com/video/BV1AixqeNE6y?spm_id_from=333.788.videopod.sections&vd_source=cb02f779bd17a3aad9801e0c4464dfc9

编译器只会在有必要的时候生成默认构造函数,有必要具体看是否能正确初始化对象(成员变量)

类中没有显示定义默认拷贝构造函数,什么时候生成默认拷贝构造函数

视频讲解:

https://www.bilibili.com/video/BV1LVxje8EF4/?spm_id_from=333.788.comment.all.click&vd_source=cec2e4e6aff81caf6c36bcd4265ba034

编译器只会在有必要的时候生成默认拷贝构造函数,有必要具体看位拷贝语义是否能正确初始化对象(成员变量)