【C++入门】类和对象Ⅱ:默认成员函数、构造函数、初始化列表、析构函数、拷贝构造函数、赋值运算符重载函数、取地址及const取地址操作符重载(含 时间计算器 部分实现)

文章介绍了C++中类和对象的核心概念,包括构造函数的作用、默认构造函数的生成以及如何自定义构造函数。接着讨论了析构函数,用于对象销毁时的资源清理。拷贝构造函数和赋值运算符重载是两个重要概念,前者用于创建对象的副本,后者用于对象间的赋值。文章还提到了const成员函数和取地址重载的默认行为,并强调了初始化列表在构造过程中的重要性。
摘要由CSDN通过智能技术生成


🔗接上篇【Ⅰ】类和对象的定义、构成、this指针

👉【C++入门】类和对象Ⅰ:类的定义、访问限定符、实例化、对象的大小、this 指针(含面试题)


三、六个默认成员函数

如果我们不写这些函数,编译器会自动生成一个默认的,但如果我们写了,编译器就不会生成了。

在对应位置,会自动调用这些函数。

函数作用备注
✳️构造函数初始化
✳️析构函数清理
*️⃣拷贝构造使用同类对象 初始化 创建对象=,针对初始化,用一个对象初始化另一个
*️⃣赋值重载把一个对象 赋值 给另一个对象=,针对都已经实例化了的对象
取地址重载普通对象取地址
const 取地址重载const 对象取地址

默认生成的 构造 / 析构:

  1. 内置类型 ,不做处理
  2. 自定义类型 ,会去调用对应的 构造 / 析构

默认生成的 拷贝构造 / 赋值重载:

  1. 内置类型,完成浅拷贝 / 值拷贝(按 byte 一个一个拷贝)
  2. 自定义类型 ,调用这个成员的 拷贝构造 / 赋值重载

注意:两两一组函数之间💑有很极强的规律性,掌握规律后其实并不复杂。个别细节的问题,在相应函数章节中会详细标记并介绍,目录可查~


3.1 构造函数

构造函数是特殊的成员函数,作用是 初始化对象

特征如下:

  1. 函数名与类名相同
  2. 无返回值
  3. 对象实例化时,编译器 自动调用 对应的构造函数(选择哪一个进行初始化,完全看用户的传参情况)
  4. 构造函数可以重载,即,可以有多个初始化方式
  5. 单参数 的构造函数支持 隐式类型转换

细节 & 实例1

  1. 对于不传参的对象,在定义时,不能加括号!!!加了会报错,因为这个写法和无参数函数的声明无异,编译器会分不清。

C 语言编写的 Stack 中,忘记 StackInit() 会导致报错
C++ 中,在类里面封装【构造函数】,会大大简化程序,方便程序员关注和实现更有价值的部分

/***************** Stack 类中,构造函数的定义 *******************/
class Stack
{
public:
	Stack()		                 ----> 构造函数 1
	{ 
		//_a = nullptr;
		//_size = _capacity = 0;
	}

	Stack(int n)                 ----> 构造函数 2
	{
		//_a = (int*)malloc(sizeof(int) * n);
		//if (_a == NULL)
		//{
		//	perror("malloc fail");
		//	exit(-1);
		//}
		//_size = 0;
		//_capacity = n;
	}

	void Push(int n)
	{
		// ..
		//_a[_size++] = n;
	}

private:
	int* _a;
	int _size;
	int _capacity;
};
 
/********************** 构造函数的调用 ************************/
int main()
{	
	Stack s;        // 程序会自动调用第一个构造函数,给对象 s 初始化
	//Stack s();  --> err...
	Stack st(4);    // 传了 int 类型参数,调用第二个构造函数
	
	st.Push(1);
	st.Push(2);
	st.Push(3);
	st.Push(4);

	return 0;
}

细节 & 实例2

  1. 无参的构造函数全缺省的构造函数 都称为 默认构造函数,默认构造函数只能存在一个,因为调用时会存在歧义。
class Date
{ ...
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}

	// 建议多用全缺省~
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 成员变量 略
	...
};

【默认】之 - - 构造函数

如果类中没有显式定义构造函数,则 C++ 编译器会自动生成一个无参的默认构造函数。 一旦用户显式定义编译器将不再生成,各种原因导致调用失败(或未初始化),编译器会报错。

具体展开之前,需要了解到 C++ 把类型分为两类:

内置类型 / 基本类型自定义类型
int、char、double…各种指针…class、struct、union…

编译器生成默认的构造函数:

  1. 内置类型 成员,不做处理
  2. 自定义类型 成员,会去调用它的 默认构造函数(即不用传参的构造函数 - - > 全缺省构造函数、无参构造函数、我们没写编译器默认生成的构造函数)

注意:C++11 中针对内置类型成员不初始化的缺陷,打了个补丁,即 内置类型成员变量在类中声明时可以给默认值 / 缺省值,跟成员函数中给出的缺省参数一样。

/* 例1 */
class Date
{
 public:
/*
// 如果用户显式定义了构造函数,编译器将不再生成
Date(int year, int month, int day)
{
	 _year = year;
	 _month = month;
	 _day = day;
}
*/

void Print()
{
	cout << _year << "-" << _month << "-" << _day << endl;
}
 
private:
	int _year;
	int _month;
	int _day;
};
 
int main()
{
 // 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
 // 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
 // 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
Date d1;
return 0;
}
/* 例2 */
class Date
{
public:
	void print()
	{
		cout << _year << " 年 " << _month << " 月 " << _day << " 日" << endl;
	}
private:	 //------> 成员都是内置类型,不处理
	int _year;
	int _month;
	int _day;
};

未定义构造函数,实例化对象后...
------
输出结果:
-858993460-858993460-858993460
/*  例3 -- 题目【用两个栈实现队列】截取  */
class MyQueue
{
public:
	void push(int x)
	{}
	//...
	Stack _pushST;	// ------> 成员为自定义类型,会调用其构造函数
	Stack _popST;
}:

LeetCode 链接:【用两个栈实现队列】


初始化列表(详见3.7)

(初始化列表是构造函数包括拷贝构造的继续完善,在学习体系中,我们选择在学习完六个默认成员函数后,再回过头来学习初始化列表,会更好理解~)


3.2 析构函数

构造函数,简化了初始化对象的步骤。

析构函数,与构造函数功能相反,会 在对象销毁时自动调用,完成对象中 资源的清理工作

特征如下:

  1. 析构函数名是在类名前加上字符 ~
  2. 无参数无返回值类型
  3. 一个类只能有一个析构函数,不能重载。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,C++ 编译系统 自动调用 析构函数。
class Stack
{
	...

	// 【析构函数】
	~Stack()
	{
		//cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_size = _capacity = 0;
	}
	
	...
};

注意:对开辟空间的类需要析构处理,普通的类随着栈的销毁而销毁的就不需要析构了,编译器也不会生成相应的析构函数。

LeetCode 链接:【括号匹配】

(本题用 C 语言写需要大量的 StackDestroy() ,C++ 直接在类中定义析构函数即可,可以深刻体会 C++ 语法的便利~~)

【默认】之 - - 析构函数

同编译器默认生成的构造函数类似

编译器生成默认的析构函数:

  1. 内置类型 成员,不做处理
  2. 自定义类型 成员,会去调用它的默认析构函数

LeetCode 链接:【用两个栈实现队列】


3.3 拷贝构造函数

拷贝构造函数 是 构造函数的一个重载 形式

可以说,当自己实现了析构函数释放空间,就需要自己实现拷贝构造

实现场景:显示使用拷贝构造(注意写法不要跟赋值搞混了)、传值传参(能用 引用 就用 引用)

  1. 参数类型一定是要是 引用类型
  2. 加一个 const 保护引用进来的对象,防止写反 误改
/***********************  类  ***************************/
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{//...}

	// 【拷贝构造】
	Date(const Date& d)	
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	//成员变量 略
};
/********************  对象的拷贝  *************************/
int main()
{
	Date d1;
	// 写法一
	Date d2(d1);
	// 写法二
	Date d3 = d1;
	
	return 0;
}

加深

下面笔者将从一个错误写法,引出自定义类型的默认拷贝原理,同时为我们加强 拷贝构造 需引用 这个印象:

// 错!误!写!法!
Date(Date d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

// 报错,识别到了无穷递归...
问题出在:正常的传值传参,传的是内置类型
这里传的参数类型 Date 是自定义类型,
自定义类型的拷贝规则规定,会调用自身的拷贝构造(这里就是调了他自己),造成了无穷递归的现象

我们都知道传值传参时, 形参是实参的临时拷贝,对于两种参数类型,编译器做出了区分:

  1. 内置类型,编译器知道有多大,可以直接拷贝
  2. 自定义类型 的拷贝,需要调用其中的 拷贝构造

分析 C++ 设置这一规则的由来:

首先 介绍一个概念所谓 深浅拷贝:

编译器可以自己完成 按字节拷贝 的我们称 浅拷贝值拷贝
拷贝时 另外开辟一块空间 的称为 深拷贝,编译器无法完成,需要程序员设计。

紧接着 分析自定义类型的拷贝类型:

  • 举例 Date 类:其成员变量均为内置类型,设置 浅拷贝 工作就可以完成
  • 举例 Stack 类:其中一个成员变量 _a 是一个指针,指向一块空间,如果只是浅拷贝会使两个类的 _a 指针指向同一个空间,就出 bug 啦(a. 插入数据会互相影响,b.析构两次,程序崩溃),所以需要一个 深拷贝 的拷贝构造(后面讲)。

最终呢 区分是否可以通过浅拷贝完成拷贝工作,对编译器来说过于复杂,于是 C++ 祖师爷一锤子定音!!自定义类型的拷贝必须使用拷贝构造!!


【默认】之 - - 拷贝构造

如果类中没有显式定义 拷贝构造函数,则编译器会自动生成拷贝构造函数。 编译器所完成的拷贝都是浅拷贝,即只拷贝了值,开空间的拷贝一律没法完成,所以很多时候需要我们自己完成

编译器生成默认的拷贝构造函数:

  1. 内置类型,完成浅拷贝 / 值拷贝(按 byte 一个一个拷贝)
  2. 自定义类型 ,调用这个成员的拷贝构造函数
// 【两个栈实现队列】
class myQueue
{
public:
	// 默认生成构造
	// 默认生成析构
	// 默认生成拷贝构造
private:
	Stack _pushST;
	Stack _popST;
	int _size = 0;
};

实例:时间计算器(部分实现 GetXDayAfter ) - - 拷贝构造的使用场景

在 Date 类中 实现一个函数:获取 x 天以后的一个日期

首先 分析、理清 题目关键逻辑:

  1. 需要传入一个 x 的整型值,返回一个 Date 类型赋给对象
  2. 传入的 x 是天数,天数相加,考虑进制到月份位,其次是进制年份位
  3. 月份本身天数不一,还需要考虑到闰年的情况。稍微复杂的实现,可以考虑建一个函数,传入年份和月份,输出 当月天数

主要函数 如下:

(该实现完整运行代码见后文)

/****************   【辅助函数 - 获取当月的天数】  ****************/ 
int GetMonthDay(int year, int month)
{
	assert(month > 0 && month < 13);
	
	int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	if (((year%4==0)&&(year%100!=0))||(year%400==0))
	{
		monthArray[2] = 29;
	}

	return monthArray[month];	// 返回当月天数
}
/****************  【实现函数 - 返回 x 天后的日期】  ***************/ 
// +
Date GetXDayAfter(int x)
{
	// this 是调用该函数的 对象的指针,*this 就是这个对象
	// 将存有原日期的对象 拷贝一份给 临时对象!!!!!
	// Date tmp(*this);	--->【拷贝构造函数】两种写法
	Date tmp = *this;	

	tmp._day += x;
	// 进位月份
	while (tmp._day > GetMonthDay(tmp._year, tmp._month))
	{
		tmp._day -= GetMonthDay(tmp._year, tmp._month);
		++tmp._month;
		// 进位年份
		if (tmp._month > 12)
		{
			tmp._month -= 12;
			++tmp._year;
		}
	}
	return tmp;	
}

到这里该函数便该完成了,尤其在学习【拷贝构造函数】的章节

除了处理题目的关键逻辑,最为重要的一步就是,理解 为什么要在该函数里 给传入对象 做一份临时拷贝


以下是一份不做临时拷贝处理的函数【错版!!!错版!!!错版!!!】:

看似没有太大的不同,但实际上,里面每一个对象成员参数的使用,都是本身,在函数执行的同时,自己的值也被修改了!!

所以这个写法不行,我们需要拷贝一份赋给 tmp,在 tmp 的基础上修改并传返回值,才可以 保留自己

/******************** 【这是一个错版函数!!】 ************************/
// +=
Date GetXDayAfter(int x)
{
	_day += x;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		++_month;
		if (_month > 12)
		{
			_month -= 12;
			++_year;
		}
	}
	return *this;	// this 是调用该函数的对象的指针,*this 就是这个对象
}
*/

完整、可运行代码 如下:

class Date
{
public:
	// 构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	} 
	
	// 打印日期
	void print()
	{
		cout << _year << " 年 " << _month << " 月 " << _day << " 日" << endl;
	}
	
	// 获取当月的天数
	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);
		
		int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		if (((year%4==0)&&(year%100!=0))||(year%400==0))
		{
			monthArray[2] = 29;
		}

		return monthArray[month];
	}

	// 返回 x 天后的日期
	Date GetXDayAfter(int x)
	{
		Date tmp = *this;	// this 是调用该函数的对象的指针,*this 就是这个对象
		
		tmp._day += x;
		while (tmp._day > GetMonthDay(tmp._year, tmp._month))	// 进位月份
		{
			tmp._day -= GetMonthDay(tmp._year, tmp._month);
			++tmp._month;
			
			if (tmp._month > 12)	// 进位年份
			{
				tmp._month -= 12;
				++tmp._year;
			}
		}
		return tmp;	
	}
	
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2023, 3, 16);
	// 获取 100 天后的日期
	Date d2 = d1.GetXDayAfter(100);
	d1.print();
	d2.print();

	return 0;
}

------
输出结果:
20233162023624

3.4 赋值运算符重载

3.4.1 运算符重载

运算符重载:让 自定义类型对象 可以使用运算符,不能对内置类型的操作符重载
重载 这个词看着眼熟不 !!
函数重载:支持函数名相同,参数不同的函数,可以同时使用

运算符重载是 具有特殊函数名 的函数,其返回值类型与参数列表与普通函数类似:
返回值类型 operator操作符(参数列表)


注意:

  • 不能创建新的操作符 eg:operate♀
  • 参数数量符号操作数数量 一致,且参数左右关系依次定位(第一个参数就是左操作数…以此类推)
  • 全局函数写法 和 成员函数 写法和调用都不一样,因为,成员函数有一个隐形参数,需要注意参数数量 -1!!
  • 其参数必须有一个 class 类型
  • 五个 不能重载 的操作符:.* :: sizeof ?: .
/****************** 重载 == 判断日期是否相等 *****************/
/*--------------------   全局函数写法   --------------------*/
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}

/*--------------------   成员函数写法   --------------------*/
...
bool operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}
...
/********************* 实例化和函数调用 **********************/
int main()
{
	Date d1(2023, 2, 4);
	Date d2(2023, 2, 4);
	Date d3(8888, 8, 8);

	// 隐式调用全局函数 ---> 编译器会找运算符重载函数,并转换成调用 operate==(d1,d2)
	d1 == d2;
	cout << (d1 == d2) << endl;	
	
	// 调用成员函数
	d1.operator==(d3);
	cout << d1.operator==(d3) << endl;	
	return 0;
}
------
输出结果:
1
0
  1. bool 类型的输出,真为 1 假为 0
  2. << 的优先级较高,在需要的时候打括号
  3. C++ 设置运算符重载,除了便捷,更是为了提高代码的可读性,一切自定义设置都推荐基于原操作符作用进行设置

小拓展:运算符重载的 通用逻辑 – 复用生成

/**************                 【复用】                ****************/
------------------------------------------------------------------------
bool operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}
bool operator<(const Date& d)
{
	return _year < d._year
		|| ((_year == d._year) && (_month < d._month))
		|| ((_year == d._year) && (_month == d._month) && (_day < d._day));
}
------------------------------------------------------------------------
// step1:实现 <=
bool operator<=(const Date& d)
{
	return *this < d || *this == d;
}
// step2:实现 >, >= , !=
bool operator>(const Date& d)
{
	return !(*this <= d);
}
bool operator>=(const Date& d)
{
	return !(*this < d);
}
bool operator!=(const Date& d)
{
	return !(*this == d);
}

3.4.2 赋值运算符重载

赋值运算符是一个使用频率很高的运算符

注意:

  1. 设置返回值,即 原对象本身 ,是为了支持 连续赋值,保持运算符的特性
  2. *this 出了作用域还在,直接返回类会产生一次拷贝,所以最好返回 引用值
  3. 不排除有人写出 d1 = d1 的程序,判断地址,如果 地址相同,直接返回,这样的判断在深拷贝时,对提高运行效率非常奏效
/********************* 赋值运算符 的重载 *********************/
/*-------------------   成员函数的写法   --------------------*/
Date& operator=(const Date& d) // 在拷贝构造没问题的情况下,可以不引用,但从理解的角度来说还是应该加引用
{
	if (this != &d)	// 判断 地址是否相同
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	return *this;
}
// d1 = d2 的返回值应该是 d1
/********************* 实例化和函数调用 **********************/
int main()
{
	Date d1(1999, 9, 9);
	Date d2(2022, 2, 2);
	Date d3(2088, 8, 8);
	
	d1 = d2 = d3;	-------------> 都是已经实例化好了的对象,绝对的[赋值重载]
	d1.print();
	
	Date d4 = d1;	-------------> 用对象初始化另一个,这个便是[拷贝构造]// Date d4(1111) = d1;     会编译报错,是不能这样写的
	// 所以,放心使用他们吧,很容易区分的!!!!
	
	return 0;
}
------
输出结果:
208888

【默认】之 - - 赋值运算符重载

  1. 内置类型,完成浅拷贝 / 值拷贝(按 byte 一个一个拷贝)
  2. 自定义类型 ,调用这个成员的 赋值重载函数

还是我们熟悉的 myQueue

// 【两个栈实现队列】
class myQueue
{
public:
	// 默认生成构造
	// 默认生成析构
	// 默认生成拷贝构造
	// 默认生成赋值重载
private:
	Stack _pushST;
	Stack _popST;
	int _size = 0;
};

3.5 const 成员函数

先给出 使用建议 和 目的:

对于 内部不改变成员变量的成员函数,建议 加上 const(声明和定义分离时,两边都需要加)
目的是让 普通对象const 对象 都可以对其进行调用

我们将 const 修饰的 成员函数 称之为 const 成员函数,跟 const 修饰变量一样,这意味着该成员函数中 不能对类的任何成员进行修改


定义 const 成员函数 写法如下:

/*************************** const成员函数的定义 ******************************/
class A
{
public:
	void FUNC()			// 普通成员函数
	{ }
	
	void func() const	// 【const 成员函数】const 修饰 *this 对象,至此 this 指针的类型变成了const A* 
	{ }

private:
	int _a = 10;
};
int main()
{
	const A aa;
	aa.FUNC();	// 报错,因为出现 权限放大问题
	aa.func();	// 权限持平,可以正常运行
		
	A bb;
	bb.func();	// 权限缩小,也是可以正常运行的~
	
	return 0;
}
报错行分析:
	&aa 的类型:【const A*】,意味着 aa 自己指向的对象不能改变
	而成员函数的 FUNC() 中 this 指针的类型是:【A*】
	很明显的,权限被放大了。

处理分析:	
	按照权限处理的思路,我们可以将大权现缩小至权限持平
	但实际上隐含 this 指针的类型我们是无法直接显式改变的

C++ 给出解决方法:
	于是 C++ 语法给成员函数设计了一个加 const 的位置
	放在这里,就意味着 *this 这个对象被 const 修饰着
	如上,这样的函数便叫作 const 成员函数。

重申:对于 内部不改变成员变量的成员函数,建议 加上 const。为的是让 普通对象const 对象 都可以对其进行调用。

ps:const 修饰 变量 需要在定义的位置初始化!!!!


3.6 【默认】之二 - - 取地址重载函数

到这里,六个默认成员函数我们已经掌握了四个,还有两个默认生成的函数,就是 对普通实例化对象取地址对 const 对象取地址 的 & 取地址重载函数。

针对这两个默认成员函数,我们完全不需要自定义他们,系统会处理的很得当,如果你还是想看看他们,那么他们长如下这样:

class A
{
public:
	...
	
	A* operator&()	// 对普通对象取地址
	{
		return this;
	}

	const A* operator&() const	// 对 const 对象取地址
	{
		return this;
	}

private:
	...
};
// 实例化 及 使用
	A aa;
	const A bb;
	
	cout << &aa << endl;
	cout << &bb << endl;

------
输出结果:	// 至于,若我们没有在 A 类中主动定义两个取地址重载函数,也能输出两个地址~~
00CFFB14
00CFFB08

(ps:不从实际功能考虑,如果你想捣乱,自定义让别人拿不到地址或者一串自定义地址,也不是不可以 😛 …)


3.7 再谈构造函数

之前的构造函数,我们称他做 函数体内的赋值初始化

由问题引入:
	在实例化对象的时候,对象被整体定义,那每一个成员变量在何时被定义的呢?
	比如 const 修饰的成员变量,只有一次赋值就是定义的时候,这个变量是何时被定义的呢?(在类中值的设定是缺省值。不是定义~)

所以我们必须给每个成员变量找一个定义的位置,构造函数给出了一个新的解决初始化的方式

初始化列表

以一个 冒号开始,接着是一个以 逗号分隔的数据成员列表,每个 成员变量 后都跟一个 放在括号中的初始值 或 表达式`

注意事项:

  1. 初始化列表中各个变量只能初始化一次,使用时 建议所有成员在初始化列表中初始化。

  2. 三种 必须要在初始化列表中初始化 的成员变量:

    • const 成员变量

    • 引用 成员变量

    • 自定义类型成员(且该类 没有默认构造函数 时)

案例1:

class B
{
public:
	B(int b)
		:_b(0)
	{}
private:
	int _b;
};


class A
{
public:
	A()
		:_x(1)
		,_ref(_a1)
		,_a2(1)
		,_bb(0)
	{
		_a1++;
		_a2--;
	}

private:
	int _a1 = 1;	// 声明、缺省
	int _a2 = 2;
	
	const int _x;	--------> 必须要在初始化列表初始化的三个成员
	int& _ref;      -------->
	B _bb;          -------->
};
int main()
{
	A aa;		
	return 0;
}
------
初始化结果:
_a1=2 (缺省值++) 
_a2=0 (初始化列表给的值--) 
_x=1 
_ref=2 
_bb(_b=0)

案例2:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	:_year(year)
	, _month(month)
	, _day(day)
	{}
private:
const int _x;
	...
};
int main()
{
Date d;
return 0;
}
  1. 成员变量在类中 声明次序 就是其在初始化列表中的 初始化顺序,与其在初始化列表中的先后次序无关
class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}

	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};
int main() {
	A aa(1);
	aa.Print();
}
------
输出结果:
1 随机值
  1. 拷贝构造 也可以使用 初始化列表

补充:隐式类型转换 和 explicit

补充一个对于单 \ 多参数类型的函数,支持隐式类型转换

主动定义 不允许隐式转换,关键字为:explicit

class AA
{
public:
	/*explicit*/AA(int a)
		:_a1(a)
	{
		cout << "AA(int a)" << endl;
	}

	AA(int a1, int a2)
		:_a1(a1)
		:_a2(a2)
	{}
	
	AA(const AA& aa)
		:_a1(aa._a1)
	{
		cout << "AA(const A& aa)" << endl;
	}

private:
	int _a1;
	int _a2;
};
int main() {
	// 【单参数类型构造函数】C++98开始支持
	AA a1(1);	// 构造函数
	AA a2 = 1;	// 隐式类型转换

	//AA& ref = 10; // err..         引用的时候没有调用构造不能优化
	const AA& ref = 10;	// 成功运行,因为产生了临时变量,即调用了构造
	
	int i = 1;
	double d = i;	// 隐式类型转换

	// 【多参数类型构造函数】C++11开始支持
	AA aa1(1, 2);
	AA aa1 = { 1,2 };
	return 0;
}
------
 原本的隐式转换:i-->double类型临时变量-->d
              1-->AA类型的临时变量-->a2 (第一次是构造,第二次是拷贝构造)
 编译器优化成,直接构造

🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~


🔗接下篇【Ⅲ】补充篇:Static成员、匿名对象、友元、内部类、拷贝对象时的一些编译器优化、一些 oj 题

👉【C++入门】类和对象Ⅲ:构造函数加强、Static成员、匿名对象、友元、内部类、拷贝对象时的一些编译器优化、再次理解封装(含 oj 题)

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值