类的六个成员函数
class Date {}; //空类
构造函数
构造函数是一种特殊的成员函数,在C++中用于初始化对象的数据成员。当创建一个类的对象时,构造函数会自动调用,用于确保对象被正确初始化。构造函数的主要作用是为对象提供合适的初始状态,使其能够正常工作。
构造函数的概念可以总结为以下几点:
-
初始化对象:构造函数负责为对象的数据成员赋予合适的初始值,确保对象处于一个有效的状态。
-
与类同名:构造函数的名称与类名相同,因此可以用来标识对象的类型。
-
没有返回类型:与普通函数不同,构造函数没有返回类型,包括 void。因为它们的作用是初始化对象,而不是返回值。
-
自动调用:构造函数在对象创建时自动调用,无需手动调用。当对象被声明时,构造函数会立即执行。
-
可以有多个重载:同一个类可以有多个构造函数,它们可以根据参数的不同进行重载。这样可以提供多种不同的初始化方式。
-
可以带默认参数:构造函数可以像普通函数一样拥有默认参数,从而提供更灵活的对象初始化方式。
构造函数在类的设计中非常重要,它决定了对象在创建时的初始状态,为后续的操作提供了基础。
#include <iostream>
using namespace std;
class Date
{
public:
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; // 编译器将调用自动生成的默认构造函数对d1进行初始化
d1.Print();
return 0;
}
这段代码定义了一个简单的日期类 Date
,其中包含了三个私有数据成员 _year
、_month
和 _day
,以及一个公有成员函数 Print()
用于输出日期信息。
在 main()
函数中,创建了一个 Date
类型的对象 d1
,由于没有显式地定义构造函数,编译器会自动生成一个默认构造函数。默认构造函数会对对象的数据成员进行默认初始化,对于基本数据类型,如 int
,默认初始化会使其值未定义,即可能是随机值。然后调用对象的 Print()
函数输出日期信息。
这段代码的运行结果可能是这样的(由于 _year
、_month
和 _day
的值未定义,输出的结果会有所不同)
虽然在我们不写的情况下,编译器会自动生成构造函数,但是编译器自动生成的构造函数可能达不到我们想要的效果,所以大多数情况下都需要我们自己写构造函数。
析构函数
析构函数是一种特殊的成员函数,在C++中用于释放对象占用的资源,并在对象生命周期结束时自动调用。它的作用与构造函数相反,用于清理对象在生命周期中分配的资源,以防止资源泄漏和内存泄漏。
析构函数的概念可以总结为以下几点:
-
释放资源:析构函数负责释放对象占用的资源,包括动态分配的内存、打开的文件、建立的网络连接等。它确保在对象被销毁之前,所有分配的资源都被正确释放,以避免资源泄漏。
-
与类同名:析构函数的名称由波浪号(~)后跟类名构成,与构造函数相似,但前面有一个波浪号。
-
没有参数:析构函数不接受任何参数,也没有返回类型,包括 void。它只负责清理对象的资源,不返回任何值。
-
自动调用:析构函数在对象生命周期结束时自动调用,无需手动调用。当对象离开其作用域、被删除或程序结束时,析构函数会自动执行。
-
只有一个:每个类只能有一个析构函数,不允许重载。如果没有显式地定义析构函数,编译器会自动生成一个默认的析构函数。
析构函数在类的设计中非常重要,它确保对象在销毁时资源被正确释放,避免内存泄漏和资源泄漏问题。
class Date
{
public:
Date()// 构造函数
{}
~Date()// 析构函数
{}
private:
int _year;
int _month;
int _day;
};
-
析构函数
~Date()
:这是一个析构函数,用于在对象被销毁时释放资源。在这个示例中,析构函数也是一个空函数,没有实际的清理工作。因为在这个简单的示例中,Date
类并没有分配动态资源,所以不需要显式地释放资源。然而,通常情况下,析构函数会负责释放类对象可能持有的资源,如动态分配的内存、打开的文件句柄等。
拷贝构造函数
拷贝构造函数是一种特殊的成员函数,在C++中用于通过复制已存在对象的数据创建新对象。当一个对象被传递给函数参数、以值传递方式返回或者通过赋值操作符进行对象赋值时,拷贝构造函数会被调用。
拷贝构造函数的概念可以总结为以下几点:
-
复制对象:拷贝构造函数的主要作用是创建一个新对象,并将已存在对象的数据成员复制到新对象中。这样可以在不修改原始对象的情况下创建一个相同状态的新对象。
-
参数为同类对象的引用:拷贝构造函数的参数通常是对同类对象的引用,表示要复制的对象。
-
对象传递和赋值时调用:拷贝构造函数会在对象被传递给函数参数、以值传递方式返回或者通过赋值操作符进行对象赋值时自动调用。
-
默认浅拷贝:默认情况下,拷贝构造函数执行浅拷贝,即简单地将原始对象的数据成员复制到新对象中。如果类中存在指针成员等资源,可能需要手动编写拷贝构造函数来实现深拷贝,以防止资源重复释放或指针悬空等问题。
拷贝构造函数在类的设计中非常重要,它允许我们以简单、方便的方式创建对象的副本,并在对象传递和赋值过程中确保数据的正确复制。
#include <iostream>
class MyClass {
private:
int _data;
public:
// 构造函数
MyClass(int data) : _data(data) {
std::cout << "Constructor called for object with data: " << _data << std::endl;
}
// 拷贝构造函数
MyClass(const MyClass& other) : _data(other._data) {
std::cout << "Copy constructor called for object with data: " << _data << std::endl;
}
// 打印数据成员
void printData() const {
std::cout << "Data: " << _data << std::endl;
}
};
int main() {
// 创建一个对象并初始化
MyClass obj1(10);
std::cout << "Object 1: ";
obj1.printData();
// 使用拷贝构造函数创建一个新对象
MyClass obj2 = obj1;
std::cout << "Object 2: ";
obj2.printData();
return 0;
}
这个示例包含了以下特点:
-
构造函数:类
MyClass
包含一个构造函数,用于初始化对象的数据成员_data
。 -
拷贝构造函数:类
MyClass
包含一个拷贝构造函数,用于通过复制已存在对象的数据创建新对象。 -
参数为同类对象的引用:拷贝构造函数的参数是对同类对象的引用,即
const MyClass& other
。 -
对象传递时调用:拷贝构造函数会在对象被传递给函数参数时自动调用。在
main()
函数中,我们使用MyClass obj2 = obj1;
来复制obj1
,这时拷贝构造函数会被调用。 -
默认浅拷贝:拷贝构造函数默认执行浅拷贝,即简单地复制
_data
的值。 -
输出信息:在构造函数和拷贝构造函数中,我们打印了相应对象的数据成员
_data
的值。
运行结果
Constructor called for object with data: 10
Object 1: Data: 10
Copy constructor called for object with data: 10
Object 2: Data: 10
赋值运算符重载
赋值运算符重载是一种特殊的成员函数,用于定义用户自定义类型对象的赋值操作行为。通过重载赋值运算符,可以实现自定义类型对象之间的赋值操作,使其行为类似于内置类型。
赋值运算符重载的概念可以总结为以下几点:
-
定义对象赋值行为:赋值运算符重载允许程序员重新定义对象之间的赋值操作。这样,可以通过赋值运算符将一个对象的值赋给另一个对象,从而实现自定义类型对象之间的赋值行为。
-
成员函数:赋值运算符重载是一个成员函数,它可以在类的内部进行定义。其名称由关键字
operator
后跟赋值操作符=
构成。 -
返回类型为引用:赋值运算符重载通常返回对象的引用,以支持连续赋值操作。这样可以避免不必要的对象复制。
-
参数为同类对象的引用:赋值运算符重载的参数通常是对同类对象的引用。这样可以将右操作数的值复制到左操作数中。
-
自赋值检查:在实现赋值运算符重载时,通常需要检查是否为自赋值(即左操作数和右操作数指向同一个对象),以避免出现错误。
-
深拷贝:在进行赋值操作时,特别是对于包含动态分配资源的自定义类型,通常需要执行深拷贝,确保资源被正确释放和管理。
赋值运算符重载允许程序员自定义类型的赋值操作,使其更加灵活和符合实际需求。通过适当地重载赋值运算符,可以实现对象之间的赋值行为,从而提高代码的可读性和可维护性。
#include <iostream>
using namespace std;
class Date {
public:
// 构造函数
Date(int year = 0, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
// 赋值运算符重载函数
Date& operator=(const Date& d) {
if (this != &d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
// 打印函数
void Print() {
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
// 创建日期对象并打印
Date d1(2024, 5, 8);
cout << "Original date: ";
d1.Print();
// 使用赋值运算符重载将一个日期对象赋值给另一个日期对象
Date d2;
d2 = d1;
// 打印赋值后的日期对象
cout << "Assigned date: ";
d2.Print();
return 0;
}
重载了赋值运算符 =
,使得可以将一个 Date
对象的值赋给另一个 Date
对象。在函数中,我们首先进行自赋值检查,然后将右操作数对象的年、月和日分别赋值给左操作数对象。
const成员
onst修饰类的成员函数
我们将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰的是类成员函数隐含的this指针,表明在该成员函数中不能对this指针指向的对象进行修改。
例如,我们可以对类成员函数中的打印函数进行const修饰,避免在函数体内不小心修改了对象:
void Print()const// cosnt修饰的打印函数
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
思考下面几个问题(经典面试题):
1.const对象可以调用非const成员函数吗?
2.非const对象可以调用const成员函数吗?
3.const成员函数内可以调用其他的非const成员函数吗?
4.非cosnt成员函数内可以调用其他的cosnt成员函数吗?
答案是:不可以、可以、不可以、可以
解释如下:
1.非const成员函数,即成员函数的this指针没有被const所修饰,我们传入一个被const修饰的对象,用没有被const修饰的this指针进行接收,属于权限的放大,函数调用失败。
2.const成员函数,即成员函数的this指针被const所修饰,我们传入一个没有被const修饰的对象,用被const修饰的this指针进行接收,属于权限的缩小,函数调用成功。
3.在一个被const所修饰的成员函数中调用其他没有被const所修饰的成员函数,也就是将一个被const修饰的this指针的值赋值给一个没有被const修饰的this指针,属于权限的放大,函数调用失败。
4.在一个没有被const所修饰的成员函数中调用其他被const所修饰的成员函数,也就是将一个没有被const修饰的this指针的值赋值给一个被const修饰的this指针,属于权限的缩小,函数调用成功。