C++《类和对象》(下)

news2024/11/18 15:27:09

在之前类和对象(中)我们学习了类当中的6大默认成员函数,我们了解了6大成员函数的结构特征和特点以及在不同情况各个成员函数是如何调用的,那么接下来我们在本篇当中将继续学习之前在学习构造函数中未了解的初始化列表,并且另外还要学习类和对象当中支持的支持的一些功能:类型转换、友元、static成员和函数、内部类和匿名对象。接下来开始本篇的学习吧!


1.再探构造函数 

在之前C++《类和对象》(中)我已经将类当中的构造函数大体已经了解,但是在当时我们还有一个问题没有解决,就是若一个类的成员变量有自定义类型时,但如果这个自定义类型无对应的构造函数时在这个类实例化时编译器就会报错。因此在之前的篇章中就提到了这种情况下要使用初始化列表才能解决,那么接下来就先来了解初始化列表的结构特征

之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有一种方
式,就是初始化列表

例如以下示例: 

#include<iostream>
using namespace std;
class Time
{
public:
	Time(int hour,int minute)
		:_hour(hour)
        ,_minute(minute)
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
    int _minute;
};

通过以上的示例就可以看出初始化列表的使用方式是以一个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。

在了解了初始化列表该如何实现接下来就来实现之前的MyQuue中的类Stack无默认构造函数时,MyQueue的构造函数形式

#include<iostream>
using namespace std;
typedef int STDataType;
class Stack
{
public:
	Stack(int n)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}
	// ...
private:
	STDataType* _a;
	size_t _capacity;
	size_t _top;
};

class MyQueue
{
public:

	MyQueue(int x,inty)
		:pushst(x)
		, popst(y)
	{	

	}
private:
	Stack pushst;
	Stack popst;

};

int main()
{
	MyQueue st(10,10);
	
	return 0;
}

其实除了以上的无默认构造的自定义类型必须使用初始化列表外,还有const修饰的成员变量和引用成员变量也是必须要在构造函数的初始化列表内定义的,而不能再构造函数的函数体内定义,这是因为这两种类型的成员变量都必须在定义的时候初始化,并且在定义之后就无法再修改了,所以如果我们在初始化列表内初始化,而是在构造函数内定义的话由于函数体内可以多次赋值这就无法判断那一条赋值语句才是初始化

在此你可以理解为每个成员变量在初始化列表中只能出现一次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。

例如以下示例

#include<iostream>
using namespace std;
class Time
{
public:
	Time(int hour)
		:_hour(hour)
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
};

class Date
{
public:
	Date(int& x, int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
		, _t(12)
		, _ref(x)
		,_n(1)
	{
		
	}

private:
	int _year;
	int _month;
	int _day;
    // 没有默认构造
	Time _t;
	// 引⽤
	int& _ref;
	// const
	const int _n; 

};
int main()
{
	int i = 0;
	Date d1(i);
	
	return 0;
}

在我们之前声明类的成员变量时,都是只声明了变量的名,其实在C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。在此给成员函数给缺省值就可以看成是将该成员变量在初始化列表内进行定义,在此的初始化值就是缺省的参数。

注意:在此成员函数的缺省值只有在没有显示在初始化列表初始化的成员才会在实例化出的类对象时在初始化时才会使用缺省值来初始化对应的成员变量,而如果初始化列表内有将该成员函数显示的初始化这时我们之前创建的缺省值就不会起作用。其实我们可以把成员变量的缺省值看作备胎

通过以上的讲解以及示例的说明我们需要在之后的创建类的构造函数时尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显示在初始化列表初始化的⾃定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。

下流程图就是成员变量走初始化列表的逻辑:

注意初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持一致。

来看以下示例:

#include<iostream>
using namespace std;
class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}
	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2 = 2;
	int _a1 = 2;
};
int main()
{
	A aa(1);
	aa.Print();
}

以上程序的运行结果是什么()
A. 输出 1 1
B. 输出 2 2
C. 编译报错
D. 输出 1 随机值
E. 输出 1 2
F. 输出 2 1

首先我们看以上类A的成员变量的声明顺序是先_a2、_a1,那么就可以得出在初始化列表成员变量的初始化顺序也是_a2、_a1,也就是在初始化列表一开始先执行_a2(_a1)语句,由于_a1这时未定义,那么_a2初始化就为随机值。在此之后就执行_a1(a),因为a形参接收的是值1,因此_a1初始化就为1

因此以上程序的输出结果就为D

初始化列表总的来说就有以下的特征:
• 之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有⼀种方式,就是初始化列表,初始化列表的使用方式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后面跟⼀个放在括号中的初始值或表达式。
• 每个成员变量在初始化列表中只能出现⼀次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。
• 引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则会编译报错。
• C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。
• 尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会⾛初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显示在初始化列表初始化的⾃定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。
• 初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持一致。

初始化列表总结:
无论是否显示写初始化列表,每个构造函数都有初始化列表
无论是否在初始化列表显示初始化,每个成员变量都要⾛初始化列表初始化

2. 类型转换 

在之前C语言阶段我们就了解过不同类型之间是可以转换的,例如int和double,这时在转换过程中需要两个转换的类型有一定的关联,例如指针和整型就有关联,这是因为指针表示的是地址也是就是内存单元的编号

其实在C++中支持内置类型隐式类型转换为类类型对象, 类类型的对象之间也可以隐式转换,需要有相关的构造函数。

注:当一个类当中如果你不想构造函数支持隐式类型转换就在构造函数前面加explicit在此之后就不再支持隐式类型转换。 

来看以下示例:

#include<iostream>
using namespace std;
class A
{
public:
	
	A(int a1)
		:_a1(a1)
	{}
	
	A(int a1, int a2)
		:_a1(a1)
		, _a2(a2)
	{}

	A(const A& aa)
		:_a1(aa._a1)
		,_a2(aa._a2)
	{

	}
	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
	int Get() const
	{
		return _a1 + _a2;
	}
private:
	int _a1 = 1;
	int _a2 = 2;
};

class B
{
public:
	B(const A& a)
		:_b(a.Get())
	{}
private:
	int _b = 0;
};

int main()
{
	//原本要进行以下的操作才能将1拷贝给对象aa1
    //A aa(1);
    // A aa1=aa;

//有了隐式类型转换就可以变为以下形式
	// 1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa3
	// 编译器遇到连续构造+拷⻉构造->优化为直接构造
	A aa1 = 1;
	aa1.Print();
	const A& aa2 = 1;
	// C++11之后才⽀持多参数转化
	A aa3 = { 2,2 };
	// aa3隐式类型转换为b对象
	// 原理跟上⾯类似
	B b = aa3;
	const B& rb = aa3;
	return 0;
}

在以上的示例当中我们创建的对象aa1可以直接使用A aa1 = 1;这一条语句来实现将对象aa1内的成员变量_a1初始化为1这是因为在此整型1先通过调用A的构造函数生成一个临时对象,之后再调用拷贝构造就将对象aa1实现了初始化

除此之外也可通过语句B b = aa3;可以看出类类型和类类型变量之间也支持隐式转换
注:并且通过语句const A& aa2=1;和语句const B& rb=aa3;就可以看出在进行隐式类型转换时由于生成的是临时变量具有常性,那么在引用时也要使用const引用否则就会出现权限放大的问题

3. static成员

在之前C语言的学习当中我们就了解了static修饰全局变量;修饰成员函数;修饰函数时被修饰的变量或者函数会产生什么样的效果,那么接下来我们就将继续了解static修饰类当中的成员时,类成员结构和功能上产生的改变

首先来看以下代码

若我们要创建一个变量来存储类示例话出对象的个数 ,这时你可能就会想到以下创建全局变量的方法来实现,但是这种方式就会有很大的局限性就是我们创建的变量_count是全局变量这时该变量在程序当中无论在什么位置都有权限修改其的值,这就会使得其健壮性不足

#include<iostream>
using namespace std;

 int _count;

class A
{
public:
	A()
	{
		++_count;
	}
	A(const A& a)
	{
		++_count;
	}
	~A()
	{
		--_count;
	}
		
private:

};
int main()
{

	A a1;
	A a2(a1);
	cout << _count;

	return 0;
}

为了解决以上的问题要让只能在类A当中对变量_count进行修改这就需要将该变量存放在类当中的并且设为私有,但这时又有一个问题了,就是如果将该变量存放在类A当中就会使得每个实例化出的A类型的对象都会有自己的_count变量,这就不符合我们的要求了,那要怎么解决呢?

在此要解决这个问题就要用到static修饰类当中的成员变量,在此用static修饰的成员变量,称之为静态成员变量静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区

注意:静态成员变量一定要在类外进行初始化,并且静态成员变量不能像普通的成员变量在声明位置给缺省值初始化,因为缺省值是给构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。

在此以上的代码就可以改写为以下形式:

#include<iostream>
using namespace std;

class A
{
public:
	A()
	{
		++_count;
	}
	A(const A& a)
	{
		++_count;
	}
	~A()
	{
		--_count;
	}
//返回静态成员变量的值
	int Getcount()
	{
		return _count;
	}
		

private:
//声明静态成员变量
	static int _count;

};
//在类外初始化静态成员变量
int A::_count = 0;


int main()
{

	A a1;
	A a2(a1);
	cout << a1.Getcount();
	return 0;
}

注意:静态成员也是类的成员,受public、protected、private 访问限定符的限制。 

在以上的代码当中由于将_count修改为类的成员变量后并且为私有,那么在类A外我们就不能在直接访问变量_count那么在此我们就通过再创建一个函数再类当中来将类的成员变量_count的值返回,之后在类外要得到_count的值就可以通过调用成员函数Getcount来实现

但在以上的代码中我们是通过对象.静态成员 来访问静态成员变量的,如果我们通过类名::静态成员就会出现以下的报错

要解决以上的问题就需要我们再来了解静态成员函数

 首先来了解静态成员变函数的结构特征:
静态成员函数相比普通的成员函数只需要在函数的返回值类型前加上关键字static即可

 接下来来了解静态成员函数相比普通成员函数在功能和函数参数上有什么区别:
静态成员函数,静态成员函数参数是没有this指针的,这也就使得静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,而非静态的成员函数由于拥有this指针,这就可以访问任意的静态成员变量和静态成员函数。

以上的代码加上静态成员函数,就变为以下形式

#include<iostream>
using namespace std;

class a
{
public:
	a()
	{
		++_count;
	}
	a(const a& a)
	{
		++_count;
	}
	~a()
	{
		--_count;
	}
	static int getcount()
	{
		return _count;
	}
		

private:
	static int _count;

};
int a::_count = 0;

int main()
{
	a a1;
	a a2(a1);
	cout << a::getcount();

	return 0;
}

这样就可以直接通过类名::静态函数来访问静态成员变量和静态成员函数。

练习1:

在学习了static成员之后接下来来看一个有趣的题

求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)

通过以上的题目描述就可以了解到该题要我们实现的是将1到n的数字累加,但是不能使用以上乘除法、for、while等,那么我们要使用什么方法才能实现呢?

我们知道在类每一次实例化过程中都会调用一次构造函数,其实有了这个特点再加上类的static成员就可以解决该题了

以下就是代码:

class Sum
{
    public:
    Sum()
    {
        _sum+=_i;
        ++_i;
    }
    static int Getsum()
    {
        return _sum;
    }
    private:
    static int _i;
    static int _sum;

};
int Sum::_i=1;
int Sum::_sum=0;

class Solution 
{

public:
    int Sum_Solution(int n) 
    {
        Sum arr[n];
        return Sum::Getsum();
    }
};

以上通过创建n个Sum类类型的对象,在此也就是创建一个长度为n的变长数组,就会调用n次Sum的构造函数。并且再类Sum内创建静态成员变量_i和_sum这样就可以保证每个对象的这两个变量都存储在同一块内存空间

注:像VS等不支持变长数组这就需要使用到之后内存管理章节会学习到的new

练习2:

设已经有A,B,C,D 4个类的定义,程序中A,B,C,D构造函数调用顺序为?()
设已经有A,B,C,D 4个类的定义,程序中A,B,C,D析构函数调用顺序为?()

A:D B A C
B:B A D C
C:C D B A
D:A B D C
E:C A B D
F:C D A B


C c;
int main()
{
    A a;
    B b;
    static D d;
return 0;
}

在以上的当中会先将main函数外的c对象调用构造函数,之后再在main函数内进行对象的初始化,先初始化a,之后初始化b,最后的static变量由于是局部变量因此会在定义的位置初始化,因此A,B,C,D 4个类的构造函数调用顺序为C A B D

之后在析构时由于在函数内后定义的会先析构因此会先调用b的析构,再调用a的析构,之后由于d对象是static修饰的静态对象因此d的生命周期是全局的在main函数结束时也不会被销毁,之后在整个程序结束时会先调用静态对象的析构,因此d和c会先调用d的析构,之后再调用c的析构,因此综上所述A,B,C,D4个类的析构函数调用顺序是B A D C

因此以上的题目选择E B

4. 友元

在之前类和对象(中)的学习中在实现日期类时我们就已经使用到了友元,但是在之前我们没有系统的来了解友元的相关概念,只是片面的了解了友元的作作用,接下来我们将详细的了解友元的概念和使用方法

友元提供了一种突破类访问限定符封装的方式在函数声明或者类,声明的前面加friend,并且把友元声明放到一个类的里面

友元函数会有以下的特征: 

在友元函数当中外部友元函数可访问类的私有和保护成员,友元函数仅仅是一种声明,他不是类的成员函数。友元函数可以在类定义的任何地方声明,不受类访问限定符限制,因此友元函数在类内的声明可以在public内也可以在private内也可以都不在这两个访问限定符内。并且一个函数可以是多个类的友元函数。

 例如以下示例:

#include<iostream>
using namespace std;
// 前置声明,否则A的友元函数声明编译器不认识B
class B;
class A
{
	// 友元声明
	friend void func(const A& aa, const B& bb);
private:
	int _a1 = 1;
	int _a2 = 2;
};
class B
{
	// 友元声明
	friend void func(const A& aa, const B& bb);
private:
	int _b1 = 3;
	int _b2 = 4;
};
void func(const A& aa, const B& bb)
{
	cout << aa._a1 << endl;
	cout << bb._b1 << endl;
}
int main()
{
	A aa;
	B bb;
	func(aa, bb);
	return 0;
}

在以上代码中函数func就既是类A的友元函数又是类B的友元函数,因此就可以在函数func内部访问类A和类B的成员变量
 

在友元当中其实除了友元函数外还存在友元类

在一个类的友元类当中类的函数都可以看作是原来类的友元函数,都可以访问原来类当中的成员变量

例如以下示例:


#include<iostream>
using namespace std;
class A
{
	// 友元声明
	friend class B;
private:
	int _a1 = 1;
	int _a2 = 2;
};

class B
{
public:
	void func1(const A& aa)
	{
		cout << aa._a1 << endl;
		cout << _b1 << endl;
	}
	void func2(const A& aa)
	{
		cout << aa._a2 << endl;
		cout << _b2 << endl;
	}
private:
	int _b1 = 3;
	int _b2 = 4;
};
int main()
{
	A aa;
	B bb;
	bb.func1(aa);
	bb.func1(aa);
	return 0;
}

在以上代码中类B就是类A的友元类,因此在类B当中的成员函数func1和func2函数内部都就可以访问A的成员成员变量

注意:友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。且友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是C的友元。

 

以上在使用了友元函数或者友元类后可以让不在类内部的函数或者类也可以访问类私有的成员,这就在一些情况下能很便捷的解决我们的问题,但使用了友元后会使得各个部分增加耦合度也就是使得各个类或者函数的关系变得更加密切,这就破坏了装,所以友元不宜多用。

 

友元总的来看有以下的特征:
• 友元提供了⼀种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前⾯加friend,并且把友元声明放到一个类的里面。
• 外部友元函数可访问类的私有和保护成员,友元函数仅仅是一种声明,他不是类的成员数。
• 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
• 一个函数可以是多个类的友元函数。
• 友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一个类中的私有和保护成员。
• 友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
• 友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是C的友元。
• 有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

 5. 内部类

首先来了解内部类的定义:
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。

接下来先来看以下的示例:

#include<iostream>
using namespace std;
class A
{
private:
	static int _k;
	int _h = 1;
public:
	class B // B默认就是A的友元
	{
	public:
		void foo(const A& a)
		{
			cout << _k << endl;
			//OK
			cout << a._h << endl;
			//OK
		}
	};
};
int A::_k = 1;

int main()
{
	cout << sizeof(A) << endl;
	A::B b;
	A aa;
	b.foo(aa);
	return 0;
}

在以上的代码中类B就是类A的内部类,那么这时计算类A的大小sizeof的输出结果是什么呢?
要解决这个问题就要先了解内部类的相关的性质:内部类是一个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类

在类A当中类B不是其的对象,因此在计算类的A的大小时我们不需要计算类B,那么在计算A的大小时就只包含成员变量_h,再结合内存对齐的规则那么sizeof(A)的结果就为4字节

在此我们还要了解到的是在一个类当中其内部类默认是外部类的友元类

 

那么在了解了内部类的定义以及使用方法后那么接下来你可能就会有疑问内部类有什么作用呢?

内部类本质也是一种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使用,那么可以考
虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其
他地方都用不了。

例如之前的算法题求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)中就可以使用到内部类,在使用了内部类后就不再需要Getsum函数来得到类Sum中的成员变量_ret的值

class Solution {
	// 内部类
	class Sum
	{
	public:
		Sum()
		{
			_ret += _i;
			++_i;
		}
	};
	static int _i;
	static int _ret;
public:
	int Sum_Solution(int n) {
		// 变⻓数组
		Sum arr[n];
		return _ret;
	}
};
int Solution::_i = 1;
int Solution::_ret = 0;

6. 匿名对象

首先来了解匿名对象的定义:
用类型 (实参) 定义出来的对象叫做匿名对象,而之前我们定义的类型 对象名(实参) 定义出来的
叫有名对象

例如以下示例:

#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;
};
class Solution {
public:
	int Sum_Solution(int n) {
		//...
		return n;
	}
};
int main()
{
	A aa1;
	// 不能这么定义对象,因为编译器⽆法识别下⾯是⼀个函数声明,还是对象定义
	//A aa1();
	// 但是我们可以这么定义匿名对象,匿名对象的特点不⽤取名字,
	A();
	A(1);
	A aa2(2);
	// 匿名对象在这样场景下就很好⽤,当然还有⼀些其他使⽤场景,这个我们以后遇到了再说
	Solution().Sum_Solution(10);
	return 0;
}

在以上代码中我们创建了类A和类Solution,在之前我们将这两个类定义对象时都要在对象名后加上括号,如果写成A aa1();编译器无法识别这是一个函数的声明还是对象的定义

而正如以上代码所示当我们创建匿名对象时就不用再类的类型之后再创对象的名字。并且通过匿名对象我们还可以调用类的成员函数,例如以上的Solution().Sum_Solution(10);

以上代码的输出结果如下所示:

通过以上的输出结果就可以看出匿名对象的特点还有匿名对象生命周期只在当前一行,一般临时定义一个对象当前用一下即可,就可以定义匿名对象。

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

• 现代编译器会为了尽可能提高程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传返回值的过程中可以省略的拷贝。
• 如何优化C++标准并没有严格规定,各个编译器会根据情况⾃⾏处理。当前主流的相对新⼀点的编译器对于连续⼀个表达式步骤中的连续拷⻉会进⾏合并优化,有些更新更"激进"的编译器还会进行跨行跨表达式的合并优化。

例如以下示例:

#include<iostream>
using namespace std;
class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};
void f1(A aa)
{}
A f2()
{
	A aa;
	return aa;
}
int main()
{
	// 传值传参
	A aa1;
	f1(aa1);
	cout << endl;
	// 隐式类型,连续构造+拷⻉构造->优化为直接构造
	f1(1);
	// ⼀个表达式中,连续构造+拷⻉构造->优化为⼀个构造
	f1(A(2));
	cout << endl;
	cout << "***********************************************" << endl;
	// 传值返回
// 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019 debug)
// ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。(vs2022 debug)
	f2();
	cout << endl;
	// 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019 debug)
	// ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。(vs2022 debug)
	A aa2 = f2();
	cout << endl;
	// ⼀个表达式中,连续拷⻉构造+赋值重载->⽆法优化
	aa1 = f2();
	cout << endl;
	return 0;
}

通过以上的示例就可以看出在一条语句中出现构造+拷贝构造或者是出现多次的构造或者拷贝构造时编译器通常会进行优化,但最终是否优化还是要看编译器,不同的编译器情况是不确定的。

但有有一点确定的是构造或者是拷贝构造在同一条语句当中和赋值运算符重载是不会被优化的

以上就是本篇的全部内容了,希望能得到你的点赞、收藏!

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

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

相关文章

MySql基础-单表操作

1. MYSQL概述 1.1 数据模型 关系型数据库 关系型数据库(RDBMS)&#xff1a;建立在关系模型基础上&#xff0c;由多张相互连接的二维表组成的数据库。 特点&#xff1a; 使用表存储数据&#xff0c;格式统一&#xff0c;便于维护 使用SQL语言操作&#xff0c;标准统一&…

高性能微服务架构:Spring Boot 集成 gRPC 实现用户与订单服务即时交互

gRPC 是一种由 Google 开发的高性能、开源的远程过程调用&#xff08;Remote Procedure Call, RPC&#xff09;框架。它允许在不同的计算机系统或进程之间进行通信&#xff0c;使得分布式系统和微服务架构中的服务之间能够轻松地相互调用方法。gRPC 基于 HTTP/2 协议&#xff0…

django-admin自定义功能按钮样式

位置在原来的django-admin 栏中的上方【会因为屏幕大小而变换位置】 <!-- 这里是不会替换掉旧的 添加按钮 &#xff0c;而是添加多一个按钮【点击Crawl Data】--> <!-- /home/luichun/lc/Pyfile/Pywebback/app/paqu/templates/admin/yourmodel_changelist.html -->…

基于k8s手动部署rabbitmq集群(Manually Deploying RabbitMQ Cluster Based on k8s)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

安全基础设施如何形成统一生态标准?OASA 硬件安全合作计划启动 | 2024 龙蜥大会

近日&#xff0c;2024 龙蜥操作系统大会&#xff08;OpenAnolis Conference&#xff09;在北京盛大召开。 与此同时&#xff0c;由龙蜥社区运营委员会副主席、龙腾计划生态负责人金美琴&#xff0c;阿里云智能集团高级技术专家张天佳&#xff0c;海光信息技术生态技术总监李伟&…

系统架构设计师教程 第5章 5.2需求工程 笔记

5.2 需求工程 ★★★★★ 软件需求是指用户对系统在功能、行为、性能、设计约束等方面的期望。 软件需求包括3个不同的层次&#xff1a;业务需求、用户需求和功能需求(也包括非功能需求)。 (1)业务需求 (business requirement) 反映了组织机构或客户对系统、产品高层次的目标…

用SpringBoot进行阿里云大模型接口调用同步方法和异步方法

同步效果就不展示了,这里展示更常用的异步,多轮异步流式效果展示如下: 结果内容组合 1、同步版本环境准备以及代码 需要开通阿里大模型服务,如果没有开通服务,单独的去生成 key 是无效的。 阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台 生成你需要的 key 1、…

PHP智慧家政同城服务家政系统小程序源码

智慧家政&#xff0c;同城服务新篇章 —— 探索家政系统的无限可能 开篇&#xff1a;走进智慧家政时代 在这个快节奏的生活中&#xff0c;每一分每一秒都显得尤为珍贵。当忙碌成为常态&#xff0c;如何让家成为真正的避风港&#xff1f;答案或许就藏在“智慧家政同城服务家政…

【JavaScript】数据结构之链表

什么是链表&#xff1f; 多个元素存储的列表链表中的元素在内存中不是顺序存储的&#xff0c;而是通过“next”指针联系在一起的&#xff0c;这个“next”可以自定义。JS中的原型链原理就是链表结构&#xff0c;是通过__proto__指针联系在一起的。 链表和数组的区别 数组是…

c++207 运算重载

调入 op #include<iostream> using namespace std;class Complex { public:int a;int b; public:Complex(int a 0, int b 0){this->a a;this->b b;}void printfCom(){cout << a << "" << b << "i" << endl…

Django视图:构建动态Web页面的核心技术

Django&#xff0c;作为一个强大的Python Web框架&#xff0c;提供了一套完整的工具来构建这些动态页面。在Django的架构中&#xff0c;视图&#xff08;Views&#xff09;是处理用户请求并生成响应的关键组件。本文将深入探讨Django视图的工作原理&#xff0c;以及如何使用它们…

科技与艺术完美融合的LED异形创意圆形(饼/盘)显示屏横空出世

随着LED技术的飞速发展&#xff0c;这款集科技与艺术于一体的异形创意圆形&#xff08;饼/盘&#xff09;显示屏&#xff0c;不仅以其独特的形态打破了传统显示屏的界限&#xff0c;更在视觉呈现上开启了前所未有的新篇章。它不再仅仅是信息传递的载体&#xff0c;而是成为了空…

外观模式详解:如何为复杂系统构建简洁的接口

&#x1f3af; 设计模式专栏&#xff0c;持续更新中 欢迎订阅&#xff1a;JAVA实现设计模式 &#x1f6e0;️ 希望小伙伴们一键三连&#xff0c;有问题私信都会回复&#xff0c;或者在评论区直接发言 外观模式 外观模式&#xff08;Facade Pattern&#xff09;为子系统中的一组…

AI重塑视觉体验:将图像与视频转化为逼真可编辑的3D虚拟场景

在这个数字化飞速发展的时代&#xff0c;AI技术正以前所未有的方式重塑我们的视觉体验。特别是当AI能够轻松将普通的照片和视频转化为高度逼真、可交互的3D虚拟场景时&#xff0c;它不仅简化了3D内容创作的复杂性&#xff0c;还极大地拓宽了应用场景的边界。今天&#xff0c;我…

医学数据分析实训 项目二 数据预处理作业

文章目录 项目二 数据预处理一、实践目的二、实践平台三、实践内容任务一&#xff1a;合并数据集任务二&#xff1a;独热编码任务三&#xff1a;数据预处理任务四&#xff1a;针对“项目一 医学数据采集”中“3. 通过 UCI 机器学习库下载数据集”任务所下载的数据集进行预处理。…

如何判断硬盘是不是固态硬盘?介绍几种简单有效方法

随着科技的发展&#xff0c;固态硬盘&#xff08;SSD&#xff09;因其读写速度快、噪音小、抗震能力强等优点&#xff0c;逐渐取代了传统的机械硬盘&#xff08;HDD&#xff09;。然而&#xff0c;对于普通用户来说&#xff0c;如何判断自己的硬盘是否为固态硬盘可能是一个难题…

10分钟在网站上增加一个AI助手

只需 10 分钟&#xff0c;为您的网站添加一个 AI 助手&#xff0c;以便全天候&#xff08;7x24&#xff09;回应客户咨询&#xff0c;提升用户体验、增强业务竞争力。 方案概览 在网站中引入一个 AI 助手&#xff0c;只需 4 步&#xff1a; 创建大模型问答应用&#xff1a;我们…

prometheus监控k8s1.24以上版本pod实时数据指标

8s组件本身提供组件自身运行的监控指标以及容器相关的监控指标。通过cAdvisor 是一个开源的分析容器资源使用率和性能特性的代理工具&#xff0c;集成到 Kubelet中&#xff0c;当Kubelet启动时会同时启动cAdvisor&#xff0c;且一个cAdvisor只监控一个Node节点的信息。cAdvisor…

Transformer学习记录(6):Vision Transformer

背景 Transformer模型最初是使用在NLP中&#xff0c;但近几年Transformer模型在图像上的使用越来越频繁&#xff0c;最新的模型也出现了很多基于Transfomer的&#xff0c;而其中经典的是Vision Transformer(ViT)&#xff0c;它是用于图像分类的&#xff0c;这里就以ViT-B/16这…

【已解决】SpringBoot3项目整合Druid依赖:Druid监控页面404报错

文章标题 问题描述原因分析解决方案参考资料 问题描述 最近&#xff0c;笔者在SpringBoot3项目中整合Druid连接池时&#xff0c;偶然翻到一条介绍Druid监控的短视频&#xff0c;兴致盎然之下尝试设置了一下Druid监控。 But&#xff0c;按照视频中提供的yml参数对照设置&#x…