详解C++类中的六个默认成员函数

在C++98中,如果一个类中什么都没有,简称为空类,系统会自动生成六个默认的成员函数,构造函数、析构函数、拷贝构造函数、赋值运算符重载、&操作符重载、const,在C++11中又加入了控制默认函数(=default , =delete

首先,大致了解一下六个默认构造函数的作用:
在这里插入图片描述

构造函数

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次

特点
  • 函数名与类型相同
  • 没有返回值(void都不行)
  • 对象实例化时编译器自动调用对应的构造函数
  • 构造函数可以重载
  • 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个
  • 创建类类型对象时,由编译器自动调用,并且在对象的生命周期内只调用一次
  • 有初始化列表(可带可不带)
  • 如果类中没有显式定义构造函数,编译器会自动生成一个无参的构造函数
  • 不能被const修饰
  • 没有this指针,因为构造函数是创建对象的,都没有对象哪来的this指针

对上述特点详解:


1. 没有返回值

构造函数和析构函数都是非常特殊的成员函数,都没有返回值,这和void是不一样的,void的返回值是空,而不是没有。在程序中创建和销毁一个对象非常特殊,就像人的出生和死亡,而且总是由编译器来来调用这些函数以确保他们的正常执行。如果存在返回值,要么编译器直到如何处理这个返回值,要么就必须由程序员来显式的调用构造函数和析构函数,这样一来,函数的安全性就被破坏了。

2.构造函数的重载

	class Date
	{
	public:
		Date()
		{}
	
		Date(int year, int month, int day)
		{
			_year = year;
			_month = month;
			_day = day;
		}
	
	private:
		int _year;
		int _month;
		int _day;
	};
	
	int main()
	{
		Date d1; //调用无参的构造函数
		Date d2(2018, 11, 30); //调用带参的构造函数
		return 0;
	}

注:上述调用无参的构造函数时,对象后面一定不能带括号,否则就成了函数声明

	//声明了d3函数,该函数无参,返回一个日期类型的对象
	Date d3();

3.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个

class Date
{
public:
	Date()
	{}

	Date(int year = 2018, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(2018, 11, 30);
	
	system("pause");
	return 0;
}

一编译就会报错:
在这里插入图片描述

4.初始化列表

class Date
{
public:
	Date()
	{}

	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(2018, 11, 30);
	
	system("pause");
	return 0;
}

初始化列表这块这里不做详细解释,以后的博客中会做详细解释

5.不能被const修饰

因为const修饰类的成员函数时,表示该函数不能修改成员变量,而构造函数就是要修改成员变量

析构函数

顾名思义,构造函数是告诉我们一个对象是怎么来的,而析构函数就是告诉我们一个对象是怎么没的

析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁的工作是由编译器完成的,而对象在销毁时,自动调用析构函数,完成类的一些资源清理工作

特点
  • 析构函数名是在类名前加上~
  • 没有参数,没有返回值、
  • 一个类有且只有一个析构函数
  • 如果显式没有定义构造函数,则编译器自动生成一个构造函数
  • 对象生命周期结束后,编译器自动调用析构函数
  • 析构函数不是销毁对象,而是完成对象资源的清理工作
  • 如果类中涉及资源的管理,析构函数一定要显式给出
  • 后构造的对象先释放
  • 有this指针

对上述特点详解:
1.一个类只有一个析构函数

因为析构函数没有参数,所以不存在函数重载的问题

2.析构函数不是销毁对象,而是完成对象的清理工作

这里的清理工作指的是如果有动态申请来的空间,析构函数就负责把这部分空间的资源清理,但这个对象直到函数生命周期结束,才会被销毁

3.如果类中涉及资源的管理,析构函数一定要显式给出

因为编译器自己生成的析构函数不会帮你去释放堆上的空间

class SeqList
{
public:
	SeqList()
	{
		_array = NULL;
		_capacity = 0;
		_size = 0;
	}

	SeqList(int n, int data)
	{
		_array = (int *)malloc(sizeof(int) * n);
		for (int i = 0; i < n; ++i)
		{
			_array[i] = data;
		}
		_capacity = n;
		_size = n;
	}

	~SeqList()
	{
		if (_array)
		{
			free(_array);
			_capacity = 0;
			_size = 0;
		}
	}

private:
	int* _array;
	size_t _capacity;
	size_t _size;
};

void test()
{
	SeqList s1;
	SeqList s2(10, 5);
}

int main()
{
	test();
	system("pause");
	return 0;
}

4.后构造的对象先释放

在上述代码中加几句话即可

class SeqList
{
public:
	SeqList()
	{
		cout << this << endl;
		_array = NULL;
		_capacity = 0;
		_size = 0;
	}

	SeqList(int n, int data)
	{
		_array = (int *)malloc(sizeof(int) * n);
		for (int i = 0; i < n; ++i)
		{
			_array[i] = data;
		}
		_capacity = n;
		_size = n;
		cout << this << endl;
	}

	~SeqList()
	{
		cout << this << endl;
		if (_array)
		{
			free(_array);
			_capacity = 0;
			_size = 0;
		}
	}

private:
	int* _array;
	size_t _capacity;
	size_t _size;
};

void test()
{
	SeqList s1;
	SeqList s2(10, 5);
}

int main()
{
	test();
	system("pause");
	return 0;
}

在这里插入图片描述

拷贝构造函数

顾名思义,就是创建一个和原对象一模一样的对象,比如双胞胎。

构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),已经存在的类型对象创建新对象时由编译器自动调用

特点
  • 只有一个形参
  • 拷贝构造函数是构造函数的一种重载形式
  • 若显示未定义,系统会默认生成拷贝构造函数(浅拷贝)
  • 涉及资源管理的问题,一定要显示定义拷贝构造函数
  • 拷贝构造函数的参数必须使用引用,如果使用传值会无穷递归调用
  • 拷贝构造函数可以连续赋值

对上述特点详解:


1.若显示未定义,系统会默认生成拷贝构造函数(浅拷贝)

class Date
{
public:
	Date()
	{}

	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{
		cout << this << endl;
	}

	~Date()
	{
		cout << this << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2108,12,12);
	Date d2(d1);
	
	system("pause");
	return 0;
}

在这里插入图片描述

对于我们的日期类,不存在资源管理的问题。所以不会出现问题
但如果是链表尼?存在资源的管理

class SeqList
{
public:
	SeqList(int n, int data)
	{
		_array = (int *)malloc(sizeof(int) * n);
		for (int i = 0; i < n; ++i)
		{
			_array[i] = data;
		}
		_capacity = n;
		_size = n;
	}

	~SeqList()
	{
		if (_array)
		{
			free(_array);
			_capacity = 0;
			_size = 0;
		}
	}

private:
	int* _array;
	size_t _capacity;
	size_t _size;
};

void test()
{
	SeqList s1(10, 5);
	SeqList s2(s1);
}

在这里插入图片描述

为什么会这样??

因为我们编译器自己生成的拷贝构造函数时浅拷贝的方式,也叫值拷贝。
浅拷贝:按内存存储字节序完成拷贝
通俗易懂的说就是,你给我什么东西,我就拷贝什么,你给我地址,我就拷贝地址

在这里插入图片描述

所以,如果类中涉及资源的管理,就一定要显示定义拷贝构造函数

2.拷贝构造函数的参数必须使用引用,如果使用传值会无穷递归调用

如果是传值,首先编译器都不会通过
在这里插入图片描述
其次,如果传值,就必须要创建一个临时对象。
在这里插入图片描述

3.拷贝构造函数可以连续赋值

int a = 10;
int b = 20;
int c = 30;
a = b = c;

对于上述的连续赋值,有没有考虑过是谁赋值谁,然后载赋值谁?

先 c 赋值给 b ,然后 c 再赋值给 a

class Date
{
public:
	Date()
	{}

	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{
	}

	~Date()
	{
	}

	Date(const Date& d)
	{
		_year = d._day;
		_month = d._month;
		_day = d._day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2108,12,12);
	Date d2;
	Date d3;
	d3 = d2 = d1;
	
	system("pause");
	return 0;
}

在这里插入图片描述

赋值运算符重载

这里直介绍 = 运算符的重载

特点
  • 参数类型最好是引用,可以提高效率
  • 检测是否存在自己给自己加赋值的情况
  • 一个类中如果显式没有给出赋值运算符的重载,编译器会自己生成,同样地,生成的也是按照浅拷贝的方式进行赋值
  • 至少要有一个自定义类型(类类型)的参数
class Date
{
public:
	Date()
	{}

	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{
	}

	~Date()
	{
	}

	Date(const Date& d)
	{
		_year = d._day;
		_month = d._month;
		_day = d._day;
	}

	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this; //为了连续赋值
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2108,12,12);
	Date d2;
	Date d3;
	d3 = d2 = d1;
	
	system("pause");
	return 0;
}

为什么一定要有一个自定义类型的参数??
因为如果都是内置类型的参数,比如两个int ,= 完全就可以不用重载,就可以很好的进行赋值。自己再重载,岂不是画蛇添足。

const

将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明该成员函数中不能对类的任何成员进行修改

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
	 //表示类成员变量都不可以被修改
	void Display() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2108,12,12);
	d1.Display();
	
	system("pause");
	return 0;
}

如果必须要修改类成员变量,可以加上multable关键字

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
	 
	void Display() const
	{
		_year += 1;
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	mutable int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2108,12,12);
	d1.Display();
	
	system("pause");
	return 0;
}

在这里插入图片描述

&及const &重载

class Date
{
public:
	Date* operator&()
	{
		return this;
	}
	//因为对象被const修饰不能更改,所以返回值也要被const修饰
	const Date* operator&() const
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值