您现在的位置是:首页 >其他 >从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象网站首页其他

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

GR C 2024-06-11 18:01:02
简介从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象

目录

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 或者 public ,就可以用友元来解决。

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

比如刚才我们想访问 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。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。