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