【编程】C++入门:类的6个默认成员函数——构造、析构、拷贝构造、赋值操作符重载、const成员函数、取地址及const取地址操作符重载

一、构造函数

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次
构造函数需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象

特性1-7

  1. 函数名与类名相同
  2. 无返回值
  3. 对象实例化时编译器自动调用对应的构造函数
  4. 构造函数可以重载
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
  6. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数全缺省构造函数我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
  7. 类成员包含内置类型成员和自定义类型成员,编译器生成默认的构造函数会对自定义类型成员调用的它的默认成员函数。
class Date
{
public :
	// 1.无参构造函数
	Date ()
	{}
 
	// 2.带参构造函数
	Date (int year, int month , int day )
	{	
		// 本质上为赋值操作,并非初始化操作
		_year = year ;
		_month = month ;
		_day = day ;
	}
private :
	int _year ;
	int _month ;
	int _day ;
}

void TestDate()
{
	Date d1; // 调用无参构造函数
	Date d2 (2015, 1, 1); // 调用带参的构造函数
 
	// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
	// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
	Date d3(); 
}

二、析构函数

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

特性1-5

  1. 析构函数名是在类名前加上字符 ~
  2. 无参数无返回值
  3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
  5. 系统生成的默认析构函数只会释放对象本身所占据的内存,对象通过其他方式如动态内存分配和打开文件等方式获得的内存和系统资源是不会被释放的,所以要自己重新定义来释放空间,防止资源浪费
typedef int DataType;
class SeqList
{ 
public :
	// 全缺省的构造函数
	SeqList (int capacity = 10)
	{
		_pData = (DataType*)malloc(capacity * sizeof(DataType));
		_size = 0;
		_capacity = capacity;
	}
 	
 	// 析构函数——清理资源
	~SeqList()
	{
		if (_pData)
		{
			free(_pData ); // 释放堆上的空间
			_pData = NULL; // 将指针置为空
			_capacity = 0;
			_size = 0;
		}
	}
 
private :
	int* _pData ;
	size_t _size;
	size_t _capacity;
};

三、拷贝构造函数

特性1-3

  1. 拷贝构造函数是构造函数的一个重载形式
  2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
  3. 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝
class Date
{
public:
	// 构造函数
 	Date(int year = 1900, int month = 1, int day = 1)
 	{
 		_year = year;
		_month = month;
 		_day = day;
 	}
 	// 必须使用引用传参,使用传值方式会引发无穷递归调用
 	Date(const Date& d)
 	{
 		_year = d._year;
 		_month = d._month;
 		_day = d._day;
 	}
private:
 		int _year;
 		int _month;
 		int _day;
};

int main()
{
	Date d1;
	// 拷贝构造,浅拷贝
 	Date d2(d1);
 	return 0;
}
class String
{
public:
	// 构造函数
	String(const char* str = "jack")
	{
		_str = (char*)malloc(strlen(str) + 1);
		strcpy(_str, str);
	}
	// 析构函数
	~String()
	{
		cout << "~String()" << endl;
 		free(_str);
 	}
private:
 	char* _str;
};

int main()
{
	String s1("hello");
	// 程序会崩溃,使用系统默认的拷贝构造函数只进行浅拷贝
	// 在后面调用析构函数的时候
	// 会出现同一片空间释放两次的情况
 	String s2(s1);	

	return 0;
}

四、赋值操作符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似

函数原型:返回值类型 operator操作符(参数列表)

注意事项1-3

  1. 重载操作符必须有一个类类型或者枚举类型的操作数
  2. 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
  3. 作为类成员的重载函数时,其形参看起来比操作数数目少1,成员函数的操作符有一个默认的形参this,限定为第一个形参

赋值操作符重载

  1. 参数类型
  2. 返回值
  3. 检测是否自己给自己赋值
  4. 返回*this
  5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝
class Date
{ 
public :
	// 构造函数
	Date(int year = 1900, int month = 1, int day = 1)
 	{
		_year = year;
 		_month = month;
 		_day = day;
 	}
 	// 拷贝构造函数
	Date (const Date& d)
 	{
		_year = d._year;
 		_month = d._month;
 		_day = d._day;
 	}
 	// 赋值操作符重载
 	// 如果返回不采用引用&,就会发生一个对临时变量进行拷贝构造的一个过程
 	// Date operator=(const Date& d)
 	// 函数返回结束后,临时变量自动销毁
	Date& operator=(const Date& d)
 	{
 		// 检测是否自己给自己赋值
		if(this != &d)
 		{
			_year = d._year;
 			_month = d._month;
 			_day = d._day;
 		}
 		// 返回左操作数,即被赋值结束后的对象
 		// return d也是可以的
 		// 此时的*this和d是相同值
 		return *this;
 	}
private:
	int _year ;
 	int _month ;
 	int _day ;
};

注意:下面的程序会出现什么情况?
下面的代码使用系统默认的赋值操作符,赋值后,指针s1的指向会和s2相同,即两个指针指向同一片空间,在对象销毁的时候,系统自动调用析构函数来释放空间会导致同一片空间被释放两次,所以此时程序会崩溃。

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决
class String
{
public:
	// 构造函数
	String(const char* str = "")
	{
		_str = (char*)malloc(strlen(str) + 1);
 		strcpy(_str, str);
 	}
 	// 析构函数
 	~String()
 	{
 		cout << "~String()" << endl;
 		free(_str);
 	}
private:
 	char* _str;
};

int main()
{
 	String s1("hello");
 	String s2("world");
 	// 系统默认的赋值操作符为浅拷贝
 	s1 = s2; 

	return 0;
}

五、取地址操作符重载

六、const修饰的取地址操作符重载

const成员函数

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

void test()
{
	int a=10;
	// pa:指向和内容都可以修改
	int* pa=&a;
	// cpa:内容可以修改,指向不能修改
	int* const cpa=&a;
	// ccpa:指向和内容都不能修改
	const int* const ccpa=&a;
}

注意:

  1. this的指向不能发生变化
  2. this指向的内存中的内容也不能发生变化
    在这里插入图片描述

思考1-4

  1. const对象可以调用非const成员函数吗?
    不能
  2. 非const对象可以调用const成员函数吗?
  3. const成员函数内可以调用其它的非const成员函数吗?
    不能
  4. 非const成员函数内可以调用其它的const成员函数吗?

取地址和const修饰的取地址操作符重载

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容

class Date
{ 
public :
	// 取地址操作符重载
	// this: Date* const
 	Date* operator&()
	{
		return this ;
 	}
 	// const修饰的取地址操作符重载
 	// this: const Date* const
 	// 非const对象可以调用const成员函数,但不能修改指针指向的内容
 	const Date* operator&()const
 	{
 		return this ;
 	}
private :
 		int _year ; // 年
 		int _month ; // 月
 		int _day ; // 日
};

int main()
{
	Date d1;
	Date* pa1=&d1;

	const Date d2;
	const Date* pa2=&d2;

	return 0;
}
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页