【C++】类与对象 (中篇)(6个默认成员函数)

本文详细介绍了C++中类的6个默认成员函数:构造函数、析构函数、拷贝构造、赋值运算符重载以及取地址/const取地址。重点讨论了浅拷贝和深拷贝的区别,以及何时需要自定义这些函数。
摘要由CSDN通过智能技术生成

在本篇博客中,作者将会讲解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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值