目录
一、类的6个默认成员函数
二、构造函数
⭕构造函数概念
⭕构造函数的特点
⭕常见构造函数的几种类型
三、析构函数
⭕析构函数概念
⭕析构函数的特点
⭕常见析构函数的几种类型
四、温馨提示
前言
这一篇文章是上一篇的续集(这里有上篇链接)前面我们讲了C语言的基础知识,也了解了一些数据结构,并且讲了有关C++的命名空间的一些知识点以及关于C++的缺省参数、函数重载,引用 和 内联函数。也相信大家都掌握的不错,接下来博主将会带领大家继续学习有关C++比较重要的知识点——类和对象。下面话不多说坐稳扶好咱们要开车了。
一、类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数
1. 默认构造函数(Default Constructor):如果类没有定义任何构造函数,编译器会自动生成一个默认构造函数。默认构造函数没有参数,用于创建一个对象时进行初始化操作。
2. 析构函数(Destructor):析构函数在对象被销毁时调用,用于释放对象所占用的资源,如释放动态分配的内存、关闭文件等。析构函数没有参数,且其名称以波浪线(~)开头,后跟类名。
3. 拷贝构造函数(Copy Constructor):拷贝构造函数用于将一个已经存在的对象的数据成员复制到另一个对象中。当使用一个对象初始化另一个对象、函数参数以值传递方式传递对象、或者以值返回对象时,拷贝构造函数会被自动调用。拷贝构造函数有一个参数,类型为同类对象的引用。
4. 拷贝赋值运算符(Copy Assignment Operator):拷贝赋值运算符用于将一个已经存在的对象的数据成员复制到另一个对象中。当使用赋值运算符=给一个已经初始化的对象赋值时,拷贝赋值运算符会被自动调用。拷贝赋值运算符有一个参数,类型为同类对象的引用,返回类型为引用。
5. 移动构造函数(Move Constructor):移动构造函数用于将一个临时对象或者将要被销毁的对象的资源(如动态分配的内存)“移动”给另一个对象,避免了不必要的数据拷贝。移动构造函数有一个参数,类型为同类对象的引用。
6. 移动赋值运算符(Move Assignment Operator):移动赋值运算符用于将一个临时对象或者将要被销毁的对象的资源(如动态分配的内存)“移动”给另一个对象,避免了不必要的数据拷贝。移动赋值运算符有一个参数,类型为同类对象的引用,返回类型为引用。
需要注意的是:如果自定义了析构函数、拷贝构造函数、拷贝赋值运算符、移动构造函数或移动赋值运算符中的任何一个,编译器将不再为该类生成相应的默认成员函数。
二、构造函数
⭕构造函数概念
C++中的构造函数是一种特殊的成员函数,用于创建和初始化类的对象。构造函数在对象被创建时自动被调用,主要目的是为对象的数据成员提供初始值,确保对象在创建后处于合适的状态。
⭕构造函数的特点
1. 构造函数的名称必须与类名完全相同,并且没有返回类型(连void也没有)。
2. 构造函数可以有参数,用于在创建对象时提供初始化值。这些参数可以有默认值,也可以是任意类型。
3. 构造函数可以被重载,也就是可以定义多个具有相同名称但参数列表不同的构造函数。这样可以根据不同的初始化需求选择合适的构造函数。
4. 在创建对象时,会自动调用与对象匹配的构造函数。也就是说,构造函数在对象被创建的时候自动被调用,无需手动调用。
5. 构造函数可以执行一些必要的初始化操作,如动态分配内存、打开文件、初始化数据成员等。
⭕常见构造函数的几种类型
1. 默认构造函数(Default Constructor)
没有参数的构造函数,用于创建对象时提供默认的初始值。
2. 带参构造函数(Parameterized Constructor)带有参数的构造函数,用于根据提供的参数初始化对象的数据成员。
3. 复制构造函数(Copy Constructor)以一个同类对象作为参数,用于根据已有对象创建新的对象。
4. 移动构造函数(Move Constructor)以一个临时对象或将要被销毁的对象作为参数,用于“移动”资源(如动态分配的内存)而不是执行数据拷贝。
5. 类型转换构造函数(Conversion Constructor)以其他类型的对象作为参数,用于将其他类型的对象转换为该类的对象。
构造函数在类的定义中是以公有(public)访问权限声明的,因为它们需要被外部代码调用来创建对象。一个类可以有多个构造函数,并且可以选择性地定义它们,如果没有定义任何构造函数,编译器会自动生成一个默认构造函数。
下面是一个简单的示例,展示了一个类 ‘ Person ’ 的构造函数的定义和使用:
#include <iostream>
#include <string>
class Person {
public:
// 默认构造函数
Person() {
name = "Unknown";
age = 0;
}
// 带参构造函数
Person(const std::string& n, int a) {
name = n;
age = a;
}
// 打印信息的成员函数
void printInfo() {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
private:
std::string name;
int age;
};
int main() {
// 使用默认构造函数创建对象
Person p1;
p1.printInfo(); // 输出:Name: Unknown, Age: 0
// 使用带参构造函数创建对象
Person p2("John", 25);
p2.printInfo(); // 输出:Name: John, Age: 25
return 0;
}
在上面的示例中,Person 类包含了一个默认构造函数和一个带参构造函数。通过这两个构造函数,可以选择使用不同的方式创建 Person 对象,从而灵活地满足不同的需求。
三、析构函数
⭕析构函数概念
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。它的主要作用是确保对象使用的资源被正确释放,防止资源泄漏和内存泄漏的发生。
⭕析构函数的特点
1. 析构函数的名称与类名相同,但前面有一个波浪线(~)作为前缀。例如,如果类名为MyClass,则对应的析构函数名为~MyClass。
2. 析构函数没有返回类型,包括void,因为它不返回任何值。
3. 析构函数不接受任何参数,因此不能被重载,每个类只能有一个析构函数。
4. 析构函数不能手动调用,它会在对象生命周期结束时自动被触发执行。对象销毁的情况包括对象超出作用域、通过delete释放了对应的动态分配内存、容器中的对象被移除等。
5. 当对象的析构函数被调用时,它会自动调用成员对象的析构函数,并按照它们被定义的顺序进行销毁。
6. 如果没有显式定义析构函数,编译器会默认生成一个空的析构函数,即不做任何操作。
析构函数通常用于在对象销毁时执行一些必要的清理工作,比如释放动态分配的内存、关闭文件句柄、释放其他类型的资源等。通过析构函数,可以确保对象所使用的资源被正确释放,避免资源泄漏和内存泄漏的问题。
下面是一个简单的示例,展示了一个类FileHandler的析构函数的定义和使用
#include <iostream>
#include <fstream>
class FileHandler {
public:
FileHandler(const std::string& filename) {
file.open(filename);
if (!file) {
std::cout << "Failed to open file " << filename << std::endl;
}
}
~FileHandler() {
if (file.is_open()) {
file.close();
std::cout << "File closed." << std::endl;
}
}
void writeToFile(const std::string& data) {
file << data << std::endl;
}
private:
std::ofstream file;
};
int main() {
FileHandler handler("example.txt");
handler.writeToFile("Hello, world!");
return 0;
}
在上面的示例中,FileHandler类封装了一个文件句柄,其中构造函数用于打开文件并检查是否成功打开,而析构函数则负责在对象销毁时关闭文件。在main函数中,创建了一个FileHandler对象handler并写入数据,当对象handler超出作用域时,析构函数会被自动调用,关闭文件。
通过定义适当的析构函数,可以确保对象的资源在对象销毁时被正确释放,从而保证程序运行的安全性和可靠性。也值得注意的是,在使用动态分配的内存或其他相关资源时,需要显式定义析构函数,以免造成资源泄漏。
⭕常见析构函数的几种类型
根据特定的需求和情况,常见的析构函数可以分为以下四种类型:
1. 默认析构函数(Default Destructor):如果没有显式定义析构函数,编译器会隐式地生成一个默认的析构函数。默认析构函数没有任何实现内容,它的作用仅仅是调用成员对象的析构函数,并按照它们的定义顺序进行销毁。
class MyClass {
// 在这里没有显式定义析构函数
// 编译器会自动生成一个默认析构函数
};
2. 虚析构函数(Virtual Destructor):当一个类被继承时,通常应该考虑使用虚析构函数。虚析构函数在基类中声明为虚函数,可以确保当通过指向基类的指针删除一个基类对象时,实际上会调用到派生类的析构函数。这是为了确保析构函数正确释放所有的资源,包括派生类自己的资源。
class MyBaseClass {
public:
virtual ~MyBaseClass() { // 声明为虚函数
// 析构函数的实现
}
};
class MyDerivedClass : public MyBaseClass {
public:
~MyDerivedClass() {
// 派生类析构函数的实现
}
};
3. 纯虚析构函数(Pure Virtual Destructor):纯虚析构函数是一个没有具体实现的虚析构函数。它的存在是为了将基类定义为抽象类,即不能实例化的类。纯虚析构函数要求派生类必须提供具体的实现。
class AbstractClass {
public:
virtual ~AbstractClass() = 0; // 纯虚析构函数,没有实现
};
AbstractClass::~AbstractClass() {
// 纯虚析构函数的实现
}
class ConcreteClass : public AbstractClass {
public:
~ConcreteClass() {
// 派生类的析构函数实现
}
};
4. 自定义析构函数(Custom Destructor):自定义析构函数根据类的具体需求定义。它可以用于释放动态分配的内存、关闭文件句柄、释放其他资源等。自定义的析构函数应该根据类的设计在适当的时候进行资源的清理和回收。
class MyClass {
public:
~MyClass() {
// 自定义的析构函数实现
// 释放资源,关闭文件等
}
};
要根据你的需求选择适当的析构函数类型。大多数情况下,默认析构函数已足够满足基本需求,但当涉及到多态性和继承时,可能需要使用虚析构函数。对于抽象类,可以使用纯虚析构函数。自定义析构函数提供了更灵活的资源清理能力。
四、温馨提示
感谢您对博主文章的关注与支持!在阅读本篇文章的同时,我们想提醒您留下您宝贵的意见和反馈。如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我们计划在未来的更新中持续探讨与本文相关的内容。我们会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注我们的更新,不要错过任何精彩内容!
我们将会不断为大家带来更多精彩、有趣的文章和内容。没有你们的关注,我们的努力就失去了意义。与这篇文章相关的内容也将陆续推出,希望你们能够一直关注我们的动态。余下的拷贝构造函数、赋值运算符重载、const成员、取地址及const取地址操作符重载详见下回分解下一篇链接:
再次感谢您的支持和关注。我们期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!