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

news2024/9/28 5:34:43

目录

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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/503954.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java 基础】类和对象 方法重载详解

《Java 零基础入门到精通》专栏持续更新中。通过本专栏你将学习到 Java 从入门到进阶再到实战的全套完整内容,所有内容均将集中于此专栏。无论是初学者还是有经验的开发人员,都可从本专栏获益。 订阅专栏后添加我微信或者进交流群,进群可找我领取 前端/Java/大数据/Python/低…

Linux 常用命令(1)

文章目录 Linux 常用命令格式 clear 清屏清屏获取当前目录的路径 pwd目录切换命令 cd进入上一级目录进入当前目录的文件夹 ta中(假设这里有一个文件夹ta)进入主目录进入根目录 显示目录内容 ls显示详细信息&#xff0c;包含文件属性显示全部内容&#xff0c;包含隐藏文件&#…

tiechui_lesson07_中断级和自旋锁

一、中断级IRQL 高级别可以打断低级别的调用&#xff0c;同级别不能打断同级别的调用。 中断级在软件层面分为三级&#xff0c;再高的级别是硬件发送的中断。 - 0 pass_level- 1 apc_level- 2 dpc_level 只有硬件中断能打断 1.获取中断级 DbgPrint("当前执行中断级为 %…

无法防范的网络攻击-DDOS

DDoS攻击&#xff08;Distributed Denial of Service Attack&#xff09;是一种网络攻击方式&#xff0c;攻击者通过利用大量的计算机或者网络设备向目标服务器发送大量的请求&#xff0c;使得目标服务器无法正常响应合法用户的请求&#xff0c;从而导致服务不可用或者服务质量…

M302H-YS-Hi3798MV300H/MV310-当贝纯净桌面卡刷固件包

M302H-YS-Hi3798MV300H&#xff0f;MV310-当贝纯净桌面卡刷固件包-内有教程及短接点提示 特点&#xff1a; 1、适用于对应型号的电视盒子刷机&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff1b; 4、大量精简…

LicheePi4A尝鲜开箱笔记

开发板介绍 LicheePi4A是以 TH1520 主控核心&#xff0c;搭载 4TOPSint8 AI 算力的 NPU&#xff0c;支持双屏 4K 显示输出&#xff0c;支持 4K 摄像头接入&#xff0c;双千兆 POE 网口和多个 USB 接口&#xff0c;音频由 C906 核心处理。 LicheePi4A详细介绍可以在https://wi…

SpringCloud 微服务系列——Spring Cloud Alibaba 微服务工具集

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

FreeRTOS内核:详解Task各状态(GPT4帮写)

FreeRTOS内核&#xff1a;详解Task各状态&#xff08;GPT4帮写&#xff09; 1. 背景2. Task顶层状态区分3. 运行状态&#xff08;Running&#xff09;4. 非运行状态4.1 阻塞态&#xff08;Blocked&#xff09;&#xff1a;4.2 挂起态&#xff08;Suspended&#xff09;4.3 就绪…

K8s基础8——svc基础使用、应用暴露、iptables代理、ipvs代理

文章目录 一、Service基本了解二、Service定义与创建2.1 相关命令2.2 yaml文件参数大全2.3 创建svc2.3.1 两种创建方式类比2.3.2 验证集群内A应用访问B应用2.3.3 将集群外服务定义为K8s的svc2.3.4 分配多个端口 2.4 常用三种类型2.4.1 ClusterIP&#xff08;集群内部访问&#…

如何解决Redis的双写一致性

目录 1.更新策略2.问题场景3.解决方案 1.更新策略 Redis和MySQL的默认的更新策略是旁路缓存策略&#xff0c;旁路缓存策略又有写策略和读策略 写策略&#xff1a;更新时&#xff0c;先更新数据库&#xff0c;再更新缓存 读策略&#xff1a;读取数据时&#xff0c;如果命中缓…

自动驾驶——Smooth Local Planning

7.1参数曲线 在本模块中&#xff0c;我们将讨论分层运动规划器的最低级别&#xff0c;即局部规划器。作为提醒&#xff0c;局部规划器是分层规划器的一部分&#xff0c;它以无碰撞、高效和舒适的方式执行行为规划器所要求的机动。这导致轨迹&#xff0c;即在给定时间空间中的一…

【C++入门】auto关键字(C++11) + 指针空值nullptr(C++11)

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

【谷粒商城之ThreadLocal用户身份鉴别】

本笔记内容为尚硅谷谷粒商城购物车ThreadLocal用户身份鉴别部分 目录 ThreadLocal 1.导入依赖 2.编写配置 3.配置Session 4.cookie中的user-key说明 5.编写To与常量 6.编写拦截器 7.添加拦截器的WebConfig配置类 8.Debug测试UserInfoTo中是否有数据 ThreadLocal T…

一篇文章搞定time_wait状态的诸多问题

今天聊聊 TIME_WAIT。 如果看过本文之后&#xff0c;你能够对如下“夺命连环问”做到胸中自有沟壑&#xff0c;则我心甚慰&#xff1a; 你觉得一台机器上看到多少 TIME_WAIT 属于不正常状态&#xff1f; 你觉得出现 TIME_WAIT 相关异常后&#xff0c;会对应用程序造成什么样的…

虚拟化技术简介

文章目录 前言一、什么是虚拟化&#xff1f;二、虚拟化的实现条件三、常见的虚拟化软件产品四、如何查看自己电脑是否支持虚拟化 前言 对于虚拟化技术的简单介绍。 一、什么是虚拟化&#xff1f; 将一台物理计算机虚拟为多台逻辑计算机。在一台计算机上同时运行多个虚拟机&am…

Oracle DataGuard奇怪的ORA-16494错误

Oracle数据库DataGuard数据无法同步&#xff0c;主库查询v$archive_dest出现ORA-16494错误。 数据库版本Oracle 12.1.0.2.0&#xff1a; SQL> select * from v$version;BANNER --------------------------------------------------------------------------------CON_ID --…

【深度学习】基于华为MindSpore和pytorch的卷积神经网络LeNet5实现MNIST手写识别

1 实验内容简介 1.1 实验目的 &#xff08;1&#xff09;熟练掌握卷积、池化概念&#xff1b; &#xff08;2&#xff09;熟练掌握卷积神经网络的基本原理&#xff1b; &#xff08;3&#xff09;熟练掌握各种卷积神经网络框架单元&#xff1b; &#xff08;4&#xff09;…

【Java笔试强训 32】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;淘宝网店…

初识Vue-事件 样式

目录 事件 事件修饰符 自定义事件 双向绑定 样式style class style Scoped(防止样式污染) 事件 <button click"add($event, 2)">add</button> add(event, num) {event.stopPropagation()// 防止冒泡this.count num; // this在方法里指向当前Vu…

YOLOv5改进系列(1)——添加SE注意力机制

前言 从这篇开始我们进入YOLOv5改进系列。那就先从最简单的添加注意力机制开始吧&#xff01;&#xff08;&#xffe3;︶&#xffe3;&#xff09;↗ 【YOLOv5改进系列】前期回顾&#xff1a; YOLOv5改进系列&#xff08;0&#xff09;——重要性能指标与训练结果评价及分…