从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象

本文详细介绍了C++中的构造函数、初始化列表的概念及注意事项,包括为何要在初始化列表中初始化const和引用成员。接着讨论了explicit关键字的作用,防止单参数构造函数的隐式类型转换。然后讲解了static成员,包括它们的特性、使用场景,以及如何通过静态成员实现类对象计数。最后,文章探讨了友元的概念,包括友元函数和友元类,以及它们在访问私有成员和流插入/提取操作中的应用。
摘要由CSDN通过智能技术生成

目录

1. 构造函数的初始化列表

1.1 初始化列表概念

1.2 初始化列表注意事项

2. 构造函数的explicit关键字

2.1 C语言的隐式类型转换

2.2 explicit 关键字使用

3. static成员

3.1 static的概念

3.2 static成员特性

3.3 static成员使用场景

4.  友元(friend)

4.1 引入:日期类的流提取

4.2 友元的概念

4.3 友元函数

4.3.1 完整流插入流提取重载:

4.3.2 友元函数注意事项:

4.4 友元类

5. 内部类(了解)

5.1 内部类的概念

5.2 内部类的特性

6. 匿名对象

7. 拷贝对象时的一些编译器优化

本篇完。


1. 构造函数的初始化列表

我们知道,引用在定义时必须初始化,常量也必须在定义时初始化,

因为常量只有一次初始化的机会,就是在定义的时候。

类里面哪里是初始化的地方?

我们之前学习创建对象时,编译器通过调用构造函数,给对象赋初值。

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,
但是不能将其称为对对象中成员变量 的初始化,构造函数体中的语句只能将其称为赋初值
而不能称作初始化。因为初始化只能初始 化一次,而构造函数体内可以多次赋值。

类里面给成员变量提供了一个初始化的地方:初始化列表

1.1 初始化列表概念

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,
每个"成员变量"后面跟一个放在括号中的初始值或表达式。
#include <iostream>
using namespace std;

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;
	}

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

int main()
{
	Date d(2023, 5, 7);
	d.Print();
	return 0;
}

1.2 初始化列表注意事项

每个成员变量再初始化列表中只能出现一次,即 初始化只能初始化一次。

② 类中包含以下成员,必须放在初始化列表位置进行初始化:
(编译器会把前两个在其它位置的“初始化”当做声明,即给初始化列表缺省值,

前面提到的C++11打的补丁时给内置类型的缺省值也是给初始化列表的)

1.  const成员变量                       const int _N;
2.  引用成员变量                         int& ref;
3.  没有默认构造函数的自定义类型成员变量     A _aa;  

③ 尽量显示使用初始化列表初始化,因为不管你是否使用初始化列表,编译器都会默认生成。

使用示例:

class Time
{
public:
	Time(int hour = 0)
	{
		_hour = hour;
	}
private:
	int _hour;
};

class Date
{
public:

	Date(int year, int hour, int& x)
		:_year(year)
		,_t(hour)
		, _N(10)
		, _ref(x)
	{

	}
private:
	int _year;
	Time _t;
	const int _N;
	int& _ref;
};

 成员变量在类中的声明顺序就是在初始化列表中的初始化顺序,

与其在初始化列表中出现的顺序无关。

下面的程序输出什么?

A . 输出 1   1
B . 程序崩溃
C . 编译不通过
D . 输出 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();
}

因为先声明的是 _a2,所以在初始化列表里我们先初始化的是 _a2,

因为这里是 _a2(_a1), _a1 此时还是没有得到传过去的1,

此时还是随机值,所以 _a2 就被初始化成随机值了。

按照声明顺序然后是 _a1, _a1 接收到了1,自然会初始化成1。

最后按顺序打印:1 和 随机值。

如果先声明_a1的话就会打印两个1。

2. 构造函数的explicit关键字

构造函数不仅可以构造与初始化对象,
对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用

2.1 C语言的隐式类型转换

 这里只是报了一个警告,为什么会支持隐式类型转换呢?

因为他们是意义相同的类型,比如 char、int、double 这些类型都是可以互相转,

因为它们都是表示数据大小的。这里 也不是直接转给 i,我们之前讲过,中间会生成一个临时变量。我们在讲引用的时候详细讲过这一点。

2.2 explicit 关键字使用

explicit 关键字只能用于类内部的构造函数声明上。

看一段代码:

class Date 
{
public:
	Date(int year = 1)
		: _year(year)
	{
		
	}

	void Print()
	{
		cout << _year << endl;
	}

private:
	int _year;
};

int main()
{
	Date d1(2022);
	Date d2 = 2023;    // 隐式类型转换
	d1.Print();
	d2.Print();

	return 0;
}

这里是隐式类型的转换,为什么支持一个整型转换成日期类相关的类型呢?

整型和日期类本来是没有关系的,但是你支持一个单参数的构造函数后,

整型就可以去构造一个日期类的对象,这个日期类的对象自然可以赋值给他了。

本来用 2023 构造成一个临时对象 Date(2023) ,在用这个对象拷贝构造 d2,

但是 C++ 编译器在连续的一个过程中,编译器为了提高效率,多个构造会被优化,合二为一。

所以这里被优化成,直接就是一个构造了。并不是所有的编译器都会这么做,

C++标准并没有规定,但是新一点的编译器一般都会这么做。

如果你不想让这种 "转换" 发生,C++提供了一种关键字:explicit 

构造函数不仅可以构造和初始化对象,对于单个参数的构造函数,还具有类型转换的作用。

用 explicit 关键字修饰构造函数,可以禁止单参构造函数的隐式类型转换:

3. static成员

如果我们要计算一个类中创建了多少个类对象,我们可以用全局变量计算一下。

int n = 0;  // 全局变量

class A 
{
public:
	A(int a = 0)
		: _a(a) 
	{
		n++;
	}
	A(const A& aa)
		: _a(aa._a)
	{
		n++;
	}

private:
	int _a;
};

void f(A a) 
{
	;
}

int main()
{
	A a1;
	A a2 = 1;
	f(a1);

	cout << n << endl;

	return 0;
}

输出了3,如果我不想让这个 n 可以被人在外面随便改呢?

有没有办法可以把 n 和类贴合起来呢?让这个 n 专门用来计算我 A 这个类的。

我们先试着把它定义成 —— 成员变量:

class A
{
public:
	A(int a = 0)
		: _a(a) 
	{
		_n++;
	}
	A(const A& aa)
		: _a(aa._a)
	{
		_n++;
	}

private:
	int _a;
	int _n = 0;  // 定义成成员变量
};

是这样还是不行!这样的话每个对象里面都有一个 n, 

我们是希望的是每个对象创建的时候去++的是同一个变量,而不是每个对象里面都有一个。

那该怎么办呢?

类里面可以定义静态成员,在成员变量前面加一个 static,就是静态成员。

3.1 static的概念

声明为 static 的类成员称为类的静态成员,用 static 修饰的成员变量,称为静态成员变量。

用 static 修饰的成员函数,称为静态成员函数,静态的成员变量一定要在类外进行初始化。

class A 
{
public:
	A(int a = 0)
		: _a(a) 
	{
		_sn++;
	}
	A(const A& aa)
		: _a(aa._a)
	{
		_sn++;
	}

private:
	int _a;

	// 静态成员变量属于整个类,所有对象,生命周期在整个程序运行期间。
	static int _sn;   // 这里以 _s 为前缀,是为了一眼就看出它是静态成员变量。
};

int A::_sn = 0;//静态的成员变量一定要在类外进行初始化。

3.2 static成员特性

① 静态成员为所有类对象所共享,不属于某个具体的实例。

② 静态成员变量必须在类外定义,定义时不添加 static 关键字。

③ 类静态成员即可用类名 :: 静态成员变量或者对象 . 来访问。

如果它是公有的,我们就可以在类外对它进行访问:

class A 
{
public:
	A(int a = 0)
		: _a(a) 
	{
		_sn++;
	}
	A(const A& aa)
		: _a(aa._a) 
	{
		_sn++;
	}

	 //private:
	int _a;
	static int _sn;
};

int A::_sn = 0;//静态的成员变量一定要在类外进行初始化。

void f(A a) 
{
	;
}

int main()
{
	A a1;
	A a2 = 1;
	f(a1);

	cout << A::_sn << endl;  // 使用类域对它进行访问

	// 这里不是说是在 a1 里面找,这里只是帮助他突破类域
	cout << a1._sn << endl;
	cout << a2._sn << endl;

	return 0;
}

但是如果它是私有的,我们可以提供一个公有的成员函数。

我们写一个公有的 Get_sn成员函数,让它返回 _sn 的值,

这样我们就可以在类外调用该函数,就可以访问到它了。

还有没有更好的方式?让我不用对象就可以访问到它呢?静态成员函数:

class A 
{
public:
	A(int a = 0)
		: _a(a) 
	{
		_sn++;
	}
	A(const A& aa)
		: _a(aa._a) 
	{
		_sn++;
	}
	static int Get_sn() 
	{
		return _sn;
	}
private:
	int _a;
	static int _sn;
};

int A::_sn = 0;//静态的成员变量一定要在类外进行初始化。

void f(A a) 
{
	;
}

int main()
{
	A a1;
	A a2 = 1;
	f(a1);

	cout << A::Get_sn() << endl;  // 使用类域对它进行访问

	// 这里不是说是在 a1 里面找,这里只是帮助他突破类域
	cout << a1.Get_sn() << endl;
	cout << a2.Get_sn() << endl;

	return 0;
}

④ 静态成员函数没有隐藏的 this 指针,不能访问任何非静态成员。

⑤ 静态成员和类的普通成员一样,也有 public、protected、private 三种访问级别,

静态成员函数也可以具有返回值。

3.3 static成员使用场景

如果有这么一个要求:设计一个只能在栈上定义对象的类。

class StackOnly
{
public:
	StackOnly(int x = 0, int y = 0)
		:_x(x)
		, _y(0)
	{
	
	}

private:
	int _x = 0;
	int _y = 0;
};

int main()
{
	StackOnly so1; // 栈
	static StackOnly so2; // 静态区

	return 0;
}

怎么设计一个只能在栈上定义对象的类?

应该不让类外面的人随便调用构造函数,所以我们把构造函数设置成私有,

那就要再设计一个类内的成员函数获取在栈上定义对象的函数:

 class StackOnly
{
public:
	StackOnly CreateObj()
	{
		StackOnly so;
		return so;
	}

private:
	StackOnly(int x = 0, int y = 0)
		:_x(x)
		, _y(0)
	{

	}

	int _x = 0;
	int _y = 0;
};

int main()
{
	//StackOnly so1; // 栈
	//static StackOnly so2; // 静态区
	CreateObj();

	return 0;
}

现在这里的代码是过不了的,CreateObj(); 需要对象调,创造对象又要调用CreateObj();

这就是一个先有鸡还是先有蛋的问题了。

这时我们的静态成员函数就能上场了:(因为静态成员用类域也能调)

 class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		StackOnly so;
		return so;
	}

private:
	StackOnly(int x = 0, int y = 0)
		:_x(x)
		, _y(0)
	{

	}

	int _x = 0;
	int _y = 0;
};

int main()
{
	//StackOnly so1; // 栈
	//static StackOnly so2; // 静态区
	StackOnly so3 = StackOnly::CreateObj();

	return 0;
}

(类和对象后面的OJ题还会有使用静态成员的场景)

这里有两个问题:

1. 静态成员函数可以调用非静态成员函数吗?

2. 非静态成员函数可以调用类的静态成员函数吗?

问题1是不可以的因为静态成员函数没有this指针。

问题2是可以的,因为静态的属于整个类。

4.  友元(friend)

4.1 引入:日期类的流提取

下面这个日期类,我们是调用 Print 成员函数来打印的:

#include <iostream>
using namespace std;

class Date 
{
public:
	Date(int year = 1, int month = 1, int day = 1) 
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() const 
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

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

int main()
{
	Date d1(2023, 5, 7);
	d1.Print();

	return 0;
}

 我们此时思考一个问题,我们能不能用 cout 输出一下 d1 呢? cout << d1;

这样当然是不行的,主要的原因还是这个是一个操作符。

是C++里面的 流插入 ,这里的意思就是要像流里面插入一个 d1。

我们说过,内置类型是支持运算符的,而自定义类型是不支持的,

它是不知道该怎么输出的,输入也是一样的道理,也是不知道该怎么去输入。

 那怎样才能向我们内置类型一样去用 流插入 和 流提取 呢?

依然可以使用重载这个运算符的方法来解决!

cout 其实是一个全局类型的对象,这个对象的类型是 ostream :

 内置类型之所以能直接支持你用,是因为 ostream 已经帮你写好了。

 所谓的 "自动识别类型" ,不过只是函数重载而已……

你是 int 它就匹配 int ,你是 char 它就匹配 char 。

我们现在知道了, cout 是一个 ostream 类型的对象了,我们来重载一下:

第一想法是这样吗?:

#include <iostream>
using namespace std;

class Date 
{
public:
	Date(int year = 1, int month = 1, int day = 1) 
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() const 
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

	void operator<<(ostream& out)
	{
		out << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023, 5, 7);
	//d1.Print();
	cout << d1;

	return 0;
}

这时我们发现  cout << d1 还是识别不了,调不动。 

这里不识别的原因是因为它是按参数走的,第一个参数是左操作数,第二个参数是右操作数。

双操作数的运算符重载时,规定第一个参数是左操作数,第二个参数是右操作数。

我们这里是成员函数,那第一个参数是隐含的this

所以,我们在调用这个流插入重载时就需要:

d1.operator<<(cout);

我们要直接写就会成这样:

#include <iostream>
using namespace std;

class Date 
{
public:
	Date(int year = 1, int month = 1, int day = 1) 
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() const 
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

	void operator<<(ostream& out)
	{
		out << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023, 5, 7);
	//d1.Print();
	//cout << d1;
	d1 << cout;

	return 0;
}

 可以打印出来了,但是这样看起来就变扭了:

这不符合我们对 "流" 的理解,我们正常理解流插入,是对象流到 cout 里面去。

因为被隐含的 this 指针参数给占据了,所以就一定会是左操作数,

这时如果写成成员函数,双操作数的左操作数一定是对象。

基于这样的原因,我们如果还是想让 cout 到左边去,就不能把他重载成成员函数了。

可以直接把它重载成全局的,在类外面,不是成员函数了就没有这些隐含的东西了!

这样的话就可以让第一个参数变为左操作数,即 out 在第一个位置,Date& d 在第二个位置:

void operator<<(ostream& out, const Date& d) 
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}

这个时候调用是肯定能调的动了,调的是全局函数。

但我们现在面临的问题是,不能访问私有的问题。

能访问私有的问题改如何解决?把 private 改为 public ?

这种方式肯定是不好的,当然我们可以写个 getYear getMonth getDay 去获取它们。

这样也可以,但是输入的时候怎么办?我们再实现 cin 流体去的时候是要 "写" 的。

这时候就麻烦了,你还得写一个 set,属实是麻烦,有没有更好地办法可以解决这种问题呢?

铺垫了这么久,终于来辣:C++ 引入了一个东西叫做 —— 友元。


4.2 友元的概念

一个全局函数想用对象去访问对象的 private 或者 protected,就可以用友元来解决。

友元分为 友元函数 和 友元类 。

比如刚才我们想访问 Date 类,就可以把它定义为 友元函数 ,友元的声明要放到类里面。

需要注意的是:友元破坏了封装,能不用就不用。


4.3 友元函数

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数。

它不属于任何类,但需要在类的内部进行声明,声明时要加 friend 关键字。

我们现在就可以去解决刚才的问题了:

#include <iostream>
using namespace std;

class Date 
{
	friend void operator<<(ostream& out, const Date& d);// 友元的声明

public:
	Date(int year = 1, int month = 1, int day = 1) 
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() const 
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

	void operator<<(ostream& out)
	{
		out << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

void operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}

int main()
{
	Date d1(2023, 5, 7);
	//d1.Print();
	cout << d1;
	//d1 << cout;

	return 0;
}

 如果我们想连续地输出呢?我想在这又输出 d1 又输出 d2。

cout << d1 << d2;

现在实现的不支持。这和连续赋值很像,只是连续赋值是从右往左,这里是从左往右。

连续插入 d1 和 d2 实际上就是两次函数的调用,这里先执行的是 cout << d1,

因为调用函数后返回值是 void,void 会做这里的左操作数,

所以当然不支持连续输出了,我们可以改一下,

我们把返回值改为 ostream 就行,把 out 返回回去。

解决了流插入,我们再来顺便实现一下流提取。

这样我们上一篇的大练习:日期类,基本上就完整了。

 流提取因为要把输入的东西写到对象里去,会改变,所以这里当然不能加 const 。

istream& operator>>(istream& in, Date& d) 
{
	in >> d._year >> d._month >> d._day;
	return in;
}

4.3.1 完整流插入流提取重载:

#include <iostream>
using namespace std;

class Date 
{
	friend ostream& operator<<(ostream& out, const Date& d);// 友元的声明
	friend istream& operator>>(istream& in, Date& d);

public:
	Date(int year = 1, int month = 1, int day = 1) 
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() const 
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

	void operator<<(ostream& out)
	{
		out << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}
istream& operator>>(istream& in, Date& d) 
{
	in >> d._year >> d._month >> d._day;
	return in;
}

int main()
{
	Date d1(2023, 5, 7);
	Date d2(2023, 5, 8);
	//d1.Print();
	cout << d1 << d2;
	//d1 << cout;

	Date d3;
	Date d4;
	cin >> d3 >> d4;
	cout << d3 << d4 << endl;

	return 0;
}

4.3.2 友元函数注意事项:

① 友元函数可以访问类的 private 和 protected 成员,但并不代表能访问类的成员函数。

② 友元函数不能用 const 修饰。

③ 友元函数可以在类定义的任何地方申明,可以不受类访问限定符的控制。

④ 一个函数可以是多个类的友元函数。

⑤ 友元函数的调用和普通函数的调用原理相同。

4.4 友元类

友元类的所有成员函数都可以是另一个类的友元函数,

都可以访问另一个类中的非公有成员。

friend class 类名;

① 友元关系是单向的,不具有交换性。

② 友元关系不具有传递性(朋友的朋友不一定是朋友)。

    如果 C 是 B 的友元,B 是 A 的友元,则不能说明 C 是 A 的友元。

③ 友元关系不能继承,在后面学习继承的时候再给大家详细介绍。

定义一个友元类:

#include<iostream>
using namespace std;

class Date;   // 前置声明

class Time
{
	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{}

private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	void GetTime()
	{
		// 直接访问Time类私有的成员变量
		cout << _t._hour << ":" << _t._minute << ":" << _t._second << endl;
	}

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

int main()
{
	Date d;
	d.GetTime();

	return 0;
}

这里 Date 是 Time 的友元,我们在日期类里就可以访问时间类的私有成员了。

但是时间类里不能访问日期类,因为这是 "单向好友" ,

如果想在时间类里访问日期类,我们可以在日期类里声明:

class Date 
{
    friend class Time;
    // ...
}

这样,它们之间就是 "双向好友" 了 -> 互相成为对方的友元。

5. 内部类(了解)

C++中不常用内部类,Java中用得多一点,所以我们了解一下就行。

5.1 内部类的概念

如果在 A 类中定义 B 类,我们称 B 是 A 的内部类。

class A 
{
	class B {
		;
	};
};
内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员。
外部类对内部类没有任何优越的访问权限。
注意: 内部类就是外部类的友元类 ,(我把你放在我心里,你就是我的友元)
参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。
但是外部类不是内部类的友元。
#include<iostream>
using namespace std;

class A
{
public:
	class B // B天生就是A的友元
	{
	public:
		void fuc(const A& a)
		{
			cout << g << endl;
			cout << a.r << endl;
		}
	};

private:
	static int g;
	int r = 19;
};

int A::g = 1;

int main()
{
	A a;
	A::B b;
	b.fuc(a);

	return 0;
}

5.2 内部类的特性

1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
    (同上代码,加上外部类的对象/类名也行)
3. sizeof(外部类)=外部类,和内部类没有任何关系。
#include<iostream>
using namespace std;

class A 
{
private:
	static int _s_a1;
	int _a2;

public:
	class B 
	{
	private:
		int _b1;
	};
};

int A::_s_a1 = 1;

int main()
{
	cout << "A的大小为: " << sizeof(A) << endl;

	return 0;
}

sizeof(外部类)=外部类,和内部类没有任何关系。

内部类 B 天生就是外部类 A 的友元,也就是 B 中可以访问 A 的私有(或保护),

A 不能访问 B 的私有(或保护)。

所以,A 类型的对象里没有 B,跟 B 没什么关系,计算 sizeof 当然也不会带上B。

加上静态成员属于整个类,是放在静态区的,所以这里只计算了int _a2的大小。

6. 匿名对象

匿名对象是指创建对象时,只有创建对象的语句,却没有把对象地址值赋值给某个变量。

产生匿名对象的三种情况:

① 以值的方式给函数传参;
  A(); —> 生成了一个匿名对象,执行完Cat( )代码后,此匿名对象就此消失。这就是匿名对象的生命周期。
   A aa = A(); —>首先生成了一个匿名对象,然后将此匿名对象变为了aa对象,其生命周期就变成了aa对象的生命周期。

② 类型转换;

③ 函数需要返回一个对象时;return temp;

#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}

private:
	int _a;
};

int main()
{
	A aa1;
	//A aa1(); 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
	// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
	// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
	A();
	A aa2;

	return 0;
}

7. 拷贝对象时的一些编译器优化

在传参和传返回值的过程中,新一点的主流的编译器都会做一些优化,减少对象的拷贝,
这个在一些场景下还是非常有用的。
#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a = aa._a;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

void f1(A aa)
{}

A f2()
{
	A aa;
	return aa;
}

int main()
{
	// 传值传参
	A aa1;
	f1(aa1);
	cout << endl;

	// 传值返回
	f2();
	cout << endl;

	// 隐式类型,连续构造+拷贝构造->优化为直接构造
	f1(1);
	cout << endl;

	// 一个表达式中,连续构造+拷贝构造->优化为一个构造
	f1(A(2));
	cout << endl;

	// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
	A aa2 = f2();
	cout << endl;

	// 一个表达式中,连续拷贝构造+赋值重载->无法优化
	aa1 = f2();
	cout << endl;
	return 0;
}

拷贝对象时的一些编译器优化就提醒我们能在一行写的就在一行写,

尽量往编译器的优化方面靠拢。关于匿名对象和这方面的题就放在下一篇了。

本篇完。

下一篇更一篇类和对象的笔试题和OJ题,类和对象就结束了。

然后更C++的动态内存管理,newdelete。

穿越回来改错顺便贴个下篇链接

从C语言到C++⑧(第二章_类和对象_下篇_续)笔试选择题和OJ题_GR_C的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GR鲸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值