在本篇博客中,作者将会讲解C++中的类的6个默认成员函数。
一.什么是默认成员函数
如果一个类中什么成员都没有,叫做空类,那么空类真的就什么都没有吗,其实并不是,类在什么成员都不写的时候,编译器会默认生成6个成员函数。
默认成员函数:在用户没有显示实现时,编译器会默认生成的成员函数就做默认成员函数。
6个默认成员函数
二.构造函数
构造函数就是对象在实例化的时候,自动调用该类的构造函数去初始化一个对象。
这个构造函数在我们没有显示实现的时候,编译器会默认生成一个构造函数,但是这个默认构造函数不会对内置类型(如:int,char等等) 进行处理,会对自定义类型进行处理。
构造函数的特点:没有返回值,函数名与类名相同,可以重载,对象实例化时自动调用。
如上图所示,没有显示定义构造函数,输出的都是没有初始化的值,但是编译器确实给我们默认生成了一个构造函数,那如何证明呢?
在上面提到,编译器默认生成的构造函数不会对内置类型做处理,但是会对自定义类进行处理(调用自定义类型的构造函数)。
在上图中,我们可以看到,自定义类Time调用了Time的构造函数,说明编译器默认生成的构造函数不会对内置类型处理,但会处理自定义类型。
构造函数的实现
通过上面的解释,既然编译器的构造函数不会对内置类型处理,那么我们只能自己实现一个(构造函数自己实现了,编译器就不会自己生成默认构造函数)。
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 0, int month = 0, int day = 1)//构造函数
{
_year = year;
_month = month;
_day = day;
}
void Show()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 3, 12);//实例化对象
d1.Show();
return 0;
}
三.析构函数
析构函数是完成对象的资源清理工作,即对对象所生成的资源空间进行释放。
同样的,当我们没有实现析构函数时,编译器会自动生成一个析构函数,同时,这个默认析构函数与默认构造函数一样,对内置类型不会进行处理,但是对自定义类型会进行处理(调用自定义类型的析构函数)。
析构函数的特点:没有返回值、函数名为 ~类名、不能重载,对象声明周期结束时自动调用。
析构函数的实现
#include<iostream>
using namespace std;
//类实现栈
class Stack
{
public:
Stack(int n = 10)//栈的构造函数
{
int* tmp = (int*)malloc(sizeof(int) * n);
_a = tmp;
_size = 0;
_capacity = 0;
}
~Stack()//栈的析构函数
{
free(_a);
_a = nullptr;
_size = _capacity = 0;
}
private:
int* _a;//指向栈的指针
int _size;//栈中元素个数
int _capacity;//栈的容量
};
int main()
{
Stack s1(4);
return 0;
}
在上面的程序中,当s1对象的声明周期结束的时候,会自动调用它的析构函数来完成资源清理。
注意:析构函数是用来完成动态内存开辟的空间的资源清理,当类中没有动态内存开辟的时候,通常可以不写。
四.拷贝构造
拷贝构造函数是通过一个已经实例化的对象来创建另一个对象。
拷贝构造函数的特点:拷贝构造是构造函数的重载,拷贝构造的参数只有一个且必须是类类型对象的引用。
拷贝构造的实现
#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 (const Date d) 错误写法
Date(const Date& d)//拷贝构造,正确写法
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void Show()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 3, 12);
//下面两种写法相同 Date d2(d1)等价于Date d3=d1
Date d2(d1);
Date d3 = d1;
d1.Show();
d2.Show();
d3.Show();
return 0;
}
拷贝构造函数形参为什么要用引用
深浅拷贝问题
在上面,我们自己实现了一个日期类的拷贝构造,而在前面提到,拷贝构造是一个默认成员函数,那是不是我们不自己写,编译器自动生成的默认拷贝构造函数也能实现拷贝呢?
答案是:是的,编译器自动生成的默认拷贝构造函数也能实现拷贝,不过这个默认生成的拷贝构造参数是浅拷贝。
在不主动实现拷贝构造函数时,编译器自动生成的默认拷贝构造函数也能实现拷贝,不过它实现的是浅拷贝。
什么是浅拷贝?浅拷贝就是就是将一个对象一个字节一个字节的拷贝给另一个对象。
在这看来,似乎浅拷贝好像很好,因为它是一个字节一个字节的拷贝过去的,肯定不会出错,那为什么有些类还有自己实现拷贝构造呢,因为这里涉及到深浅拷贝的问题。下面演示一下。
#include<iostream>
using namespace std;
class Stack//栈类
{
public:
Stack(int n = 10)//栈的构造函数
{
int* tmp = (int*)malloc(sizeof(int) * n);
_a = tmp;
_size = _capacity = 0;
}
~Stack()//栈的析构函数
{
free(_a);
_a = nullptr;
_size = _capacity = 0;
}
private:
int* _a;//指向栈的指针
int _size;//栈中元素个数
int _capacity;//栈的容量
};
int main()
{
Stack s1;
Stack s2(s1);
return 0;
}
在上面这段代码中,程序一运行就会崩溃。
s1的_a指针和s2的_a指针指向同一块空间,当s1和s2的作用域结束的时候,会自动调用它们的析构函数去释放空间,这时就是引发错误,因为同一块空间被释放了两次。
这就是浅拷贝的问题。
五.赋值运算符重载
在C++中,有一个功能,就是运算符重载,即在C++中,不仅可以重载函数,还能重载运算符。
注意:.* :: sizeof ?: . 这五个运算符不能重载。
重载运算符的具体形式是:operator关键字加运算符。如:operator+、operator+=等等。
那么运算符重载具体是什么,接下来讲解一下。
对于内置类型,如果我们想给变量直接互相赋值,非常简单,如下
int a=10;
int b=20;
b=a;
这样就完成了把a的值赋给了b。
那如果是自定义类型呢?如class Date,又该如何操作?具体如下。
#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)//赋值运算符重载
{
_year = d._year;
_month = d._month;
_day = d._day;
return (*this);
}
void Show()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 3, 13);
Date d2;
d2 = d1;//将d1的值赋给d2
d1.Show();
d2.Show();
return 0;
}
在上面的代码中,通过赋值运算符重载,可以将d1的值赋值给d2。
而在上面也讲到,赋值运算符重载是一个默认成员函数,不显式实现,编译器会默认生成一个,这个默认生成的运算符重载函数也能实现赋值,但是为什么要自己写呢,因为默认生成的赋值运算符重载函数和拷贝构造函数一样,都是浅拷贝,浅拷贝可能会带来问题。所以视情况而定。
当然,运算符重载还有很多,如operator==(判断是否相等)、operator>(判断是否大于)等等函数。
六.取地址及const取地址重载
在一个类中,还有两个默认成员函数,分别是取地址函数和const取地址函数,不过这两个默认函数一般不用自己实现重载,直接用就好了。
取地址函数
#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&()//取地址函数
{
return this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
cout << &d1 << endl;
return 0;
}
const取地址函数
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
const Date* operator&() const//const取地址函数
{
return this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
cout << &d1 << endl;
return 0;
}