个人主页:PingdiGuo_guo
收录专栏:C++干货专栏
大家龙年好呀,今天我们来学习一下C++构造函数和折构函数。
文章目录
1.6.1默认构造函数(Default Constructor)
1.6.2带参数构造函数(Parameterized Constructor)
1.6.5委托构造函数(Delegating Constructor)
1.6.6虚拟构造函数(Virtual Constructor)
1.构造函数
1.1构造函数的概念
构造函数是C++中的特殊成员函数,用于初始化新创建的对象。当一个类的对象被声明并实例化时,构造函数自动被调用。它的主要作用是确保对象在其生命周期开始时就处于有效状态。
1.2构造函数的思想
构造函数的思想是在创建对象时,用于初始化对象的成员变量和执行其他必要的操作。构造函数在对象创建时自动调用,不需要手动调用。构造函数的主要目的是确保对象被正确地初始化,以便在使用对象时不会出现未定义的行为或错误。
1.3构造函数的特点
1.构造函数的名称与类名称相同,并且没有返回类型(包括void类型),包括默认构造函数和带参数的构造函数。
2.构造函数可以重载,即在同一个类中可以定义多个具有不同参数列表的构造函数。
3.构造函数可以带有参数,在创建对象时,可以根据传递的参数值来初始化对象的成员变量。
4.构造函数可以进行一些初始化操作,例如分配内存、调用其他对象的构造函数等。
5.构造函数可以被继承,子类可以调用父类的构造函数来初始化父类的成员变量。
1.4构造函数的作用
1.对象的初始化:构造函数用于初始化对象的成员变量,确保对象在创建时具有正确的初始状态。
2.分配和初始化成员变量:构造函数可以分配并初始化类的成员变量,以便在对象创建时设置默认值或特定的初始状态。
3.进行必要的前置操作:构造函数可以执行一些必要的前置操作,例如打开文件、连接数据库等。
4.为对象分配内存:构造函数负责为对象分配内存空间,这通常发生在通过 new 运算符创建对象时。
5.链接到其他对象或资源:构造函数可以将对象链接到其他对象或资源,以便对象能够使用这些外部资源。
6.初始化对象的状态:构造函数可以设置对象的初始状态,例如设置指针成员变量为 null、设置计数器为 0 等。
7.执行其他必要的操作:构造函数还可以执行其他与对象创建相关的必要操作,如申请资源、注册回调函数等。
8.为派生类提供基类的初始化:在派生类的构造函数中,可以通过调用基类的构造函数来初始化继承自基类的成员变量。
1.5构造函数的操作
1. 定义类结构:
class Book {
private:
char* title; // 私有成员变量,用于存储动态分配的标题字符串
public:
// 构造函数声明
Book(const char* t);
};
2. 实现构造函数:
在类外部实现构造函数,它负责接收一个 C 风格字符串并将其复制到动态分配的内存中。
Book::Book(const char* t) {
// 计算字符串长度(包括终止符 '\0')
size_t len = strlen(t) + 1;
// 动态分配内存
title = new char[len];
// 使用标准库函数strcpy复制字符串
strcpy(title, t);
}
3. 构造函数操作:
当创建 Book 对象时,构造函数会被自动调用:
int main() {
Book book("The Title of the Book"); // 这里调用了构造函数
// ...
return 0;
}
在上述代码中,当我们创建 book对象时,传入的字符串 "The Title of the Book"将被复制到通过 new分配的内存空间中。
1.6构造函数的分类
构造函数可以分为以下几种分类:
1.6.1默认构造函数(Default Constructor)
如果一个类没有定义任何构造函数,那么编译器会自动生成一个默认构造函数。默认构造函数没有任何参数,并且不进行任何初始化操作。
class MyClass {
public:
// 默认构造函数
MyClass() {
// 进行初始化操作
}
};
int main() {
// 创建一个对象,调用默认构造函数
MyClass obj;
return 0;
}
1.6.2带参数构造函数(Parameterized Constructor)
带参数构造函数接收一定数量和类型的参数,用于对对象的成员变量进行初始化。通过传递不同的参数值,可以创建具有不同初始状态的对象。
class MyClass {
public:
// 带参数构造函数
MyClass(int value) {
// 进行初始化操作,使用参数value对对象进行初始化
}
};
int main() {
// 创建一个对象,调用带参数构造函数
MyClass obj(10);
return 0;
}
1.6.3拷贝构造函数(Copy Constructor)
拷贝构造函数是一种特殊的构造函数,用于创建一个新对象,并将现有对象的值复制到新对象中。拷贝构造函数通常用于对象之间的值传递或对象的复制。
class MyClass {
public:
// 拷贝构造函数
MyClass(const MyClass& other) {
// 进行拷贝操作,将other对象的值复制到当前对象中
}
};
int main() {
// 创建一个对象,调用拷贝构造函数
MyClass obj1;
MyClass obj2 = obj1; // 或者 MyClass obj2(obj1);
return 0;
}
1.6.4移动构造函数(Move Constructor)
移动构造函数是C++11引入的一种特殊构造函数,用于将右值引用参数转移或"窃取"其资源,而不是进行复制。移动构造函数可以提高程序的效率和性能。
class MyClass {
public:
// 移动构造函数
MyClass(MyClass&& other) {
// 进行移动操作,将other对象的资源移动到当前对象中
}
};
int main() {
// 创建一个对象,调用移动构造函数
MyClass obj1;
MyClass obj2 = std::move(obj1); // 或者 MyClass obj2(std::move(obj1));
return 0;
}
1.6.5委托构造函数(Delegating Constructor)
委托构造函数是C++11引入的一种特殊构造函数,它允许一个构造函数调用同一个类的另一个构造函数,以避免代码冗余。
class MyClass {
public:
// 默认构造函数
MyClass() : MyClass(0) {
// 委托给带参数的构造函数
}
// 带参数构造函数
MyClass(int value) {
// 进行初始化操作,使用参数value对对象进行初始化
}
};
int main() {
// 创建一个对象,调用默认构造函数
MyClass obj;
return 0;
}
1.6.6虚拟构造函数(Virtual Constructor)
虚拟构造函数是一种在基类中声明的构造函数,用于在派生类中动态创建基类对象。虚拟构造函数是通过在基类中声明纯虚函数来实现的。
class Base {
public:
// 虚拟构造函数
virtual Base* clone() const {
return new Base(*this);
}
// 其他成员函数和数据成员
};
class Derived : public Base {
public:
// 虚拟构造函数的重写
virtual Derived* clone() const {
return new Derived(*this);
}
// 其他成员函数和数据成员
};
int main() {
// 创建一个派生类对象,调用虚拟构造函数的重写
Base* basePtr = new Derived();
Base* copy = basePtr->clone();
// 其他操作
delete basePtr;
delete copy;
return 0;
}
1.7构造函数的练习
1.7.1题目
设计并实现一个 Person类,其中包含姓名和年龄属性,并编写一个自定义构造函数来初始化这些属性。
1.7.2步骤
1. 定义 `Person` 类结构,声明私有成员变量。
2. 编写构造函数,接受姓名(字符串类型)和年龄(整型)作为参数。
3. 在构造函数内,对传入的参数进行有效性检查(可选)。
4. 将传入的参数值赋给对应的成员变量。
1.7.3代码
// Step 1: 定义 Person 类
class Person {
private:
std::string name;
int age;
public:
// Step 2: 编写构造函数
Person(const std::string& n, int a) {
// Step 3: 参数有效性检查(例如年龄应该大于0)
if (a < 0) {
throw std::invalid_argument("Age must be positive.");
}
// Step 4: 赋值给成员变量
name = n;
age = a;
}
// 其他成员函数...
};
// 使用构造函数创建一个 Person 对象
int main() {
Person person("John Doe", 30);
return 0;
}
2.折构函数
2.1折构函数的概念
析构函数是C++中另一种特殊的成员函数,它与构造函数相反,在对象生命周期结束时(例如对象的作用域结束或者delete一个动态分配的对象时)自动调用。析构函数主要用于清理工作,如释放内存、关闭文件等。
2.2折构函数的思想
析构函数是一种特殊的成员函数,在对象销毁时自动调用,用于释放对象所占用的资源。
析构函数与构造函数相对应,构造函数用于对象的初始化,而析构函数用于对象的清理与释放。
2.3折构函数的特点
1.析构函数没有参数,且没有返回值。
2.析构函数的名称与类名相同,前面加上波浪号(~)作为前缀。
3.只能有一个析构函数,且不能被继承或重载。
4.析构函数不能被显式地调用,不能被重载。
5.对象销毁时,析构函数会自动调用,从而完成对象的销毁和资源的释放。
6.析构函数按照对象的创建顺序的逆序调用,即先创建的对象后销毁。
2.4折构函数的作用
1.释放动态分配的内存或资源。
2.关闭打开的文件、数据库连接等。
3.清理对象中的临时数据和状态。
4.执行其他清理和释放操作。
2.5折构函数的操作
1. 定义析构函数:
在类内部声明析构函数,它将在 Book 对象生命周期结束时(例如,超出作用域或显式销毁)自动调用。
class Book {
// ...
public:
// 析构函数声明
~Book();
};
2. 实现析构函数:
在类外部实现析构函数,它负责释放之前构造函数中动态分配的内存。
Book::~Book() {
// 检查指针是否已经分配过内存
if (title != nullptr) {
delete[] title; // 释放内存
title = nullptr; // 设置为 nullptr 防止悬挂指针
}
}
3. 析构函数操作:
析构函数的操作是自动的,不需要程序员显式调用。当 Book对象离开其作用域时,析构函数会自动执行。
int main() {
{
Book book("The Title of the Book"); // 创建对象时构造函数被调用
} // 这里 book 作用域结束,析构函数将被自动调用,释放 title 的内存
return 0;
}
在上述代码中,当 book 对象超出作用域时,析构函数将被调用,释放之前为 title分配的内存。这样就确保了程序不会出现内存泄漏的问题。
2.6折构函数的分类
析构函数的分类主要分为默认析构函数和有特定目的的析构函数。
2.6.1默认析构函数
如果在类中没有显式定义析构函数,编译器会自动生成一个默认的析构函数。默认析构函数会自动释放对象占用的内存,但不执行任何其他操作。
class MyClass {
public:
// 构造函数
MyClass() {
// 构造函数的代码
}
// 默认析构函数
~MyClass() {
// 自动生成的析构函数,无需显式定义
}
};
2.6.2有特定目的的析构函数
有些情况下,需要在对象销毁时执行特定的清理操作,例如释放资源、关闭文件、释放动态分配的内存等。这时可以自定义析构函数,并在其中编写相应的清理代码。
class MyClass {
private:
int* ptr; // 动态分配的内存指针
public:
// 构造函数
MyClass() {
ptr = new int[10]; // 动态分配内存
}
// 有特定目的的析构函数
~MyClass() {
delete[] ptr; // 释放动态分配的内存
}
};
在上述代码中,我们定义了一个具有特定目的的析构函数,用于释放动态分配的内存。当对象销毁时,析构函数会自动被调用,执行内存释放操作。
需要注意的是,每个类只能有一个析构函数,不能重载析构函数。析构函数没有参数,也没有返回值。析构函数的名称与类名称相同,并在前面加上波浪线(~)作为标识。
使用析构函数可以确保在对象生命周期结束时进行清理工作,避免内存泄漏和资源浪费的问题。同时,通过自定义析构函数,可以根据具体需求执行特定的清理操作。
3.构造函数和折构函数的利弊
3.1利
- 构造函数确保了对象从一开始就处于已知的良好状态。
- 析构函数提供了自动资源管理机制,减少内存泄漏和其他资源泄露的风险。
3.2弊
- 如果构造函数或析构函数内部逻辑复杂,可能影响程序性能或增加错误的可能性。
- 构造函数抛出异常会导致未初始化的对象,而析构函数如果在异常处理期间抛出异常,则可能导致程序行为不可预测。
4.总结
本篇博客到这里就结束了,感谢大家的支持与观看,如果有好的建议欢迎留言,谢谢大家啦!