个人主页:PingdiGuo_guo
收录专栏:C++干货专栏
大家龙年好呀,今天我们来学习一下C++构造函数和折构函数。
文章目录
1.构造函数
1.1构造函数的概念
1.2构造函数的思想
1.3构造函数的特点
1.4构造函数的作用
1.5构造函数的操作
1.6构造函数的分类
1.6.1默认构造函数(Default Constructor)
1.6.2带参数构造函数(Parameterized Constructor)
1.6.3拷贝构造函数(Copy Constructor)
1.6.4移动构造函数(Move Constructor)
1.6.5委托构造函数(Delegating Constructor)
1.6.6虚拟构造函数(Virtual Constructor)
1.7构造函数的练习
1.7.1题目
1.7.2步骤
1.7.3代码
2.折构函数
2.1折构函数的概念
2.2折构函数的思想
2.3折构函数的特点
2.4折构函数的作用
2.5折构函数的操作
2.6折构函数的分类
2.6.1默认析构函数
2.6.2有特定目的的析构函数
3.构造函数和折构函数的利弊
3.1利
3.2弊
4.总结
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.总结
本篇博客到这里就结束了,感谢大家的支持与观看,如果有好的建议欢迎留言,谢谢大家啦!