C++知识整理day3类与对象(下)——赋值运算符重载、取地址重载、列表初始化、友元、匿名对象、static

文章目录

  • 1.赋值运算符重载
    • 1.1 运算符重载
    • 1.2 赋值运算符重载
  • 2.取地址重载
    • 2.1 const成员函数
    • 2.2 取地址运算符重载
  • 3.类与对象的补充
    • 3.1 再探构造函数---初始化列表
    • 3.2 类型转换
    • 3.3 static成员
    • 3.4 友元
    • 3.5 内部类
    • 3.6 匿名对象
    • 3.7 对象拷贝时的编译器优化

1.赋值运算符重载

赋值运算符重载是六个默认成员函数之一,在讲解这个我们要先了解一下运算符重载。

1.1 运算符重载

当运算符被用于类类型的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符的时候,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会报编译错误。

注意:对于下面的例子中,都是使用的Date类。

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;
};
  1. 运算符重载是具有特别名字的函数,他的名字是由operator和后面要定义的运算符共同构成。和其他函数一样,他也具有其返回类型和参数列表以及函数体。
  2. 重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符有⼀个参数,⼆元运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数。(注意:成员函数的第一个参数默认是this指针。

示例:
如果我们写了这行代码,那么毫无疑问编译器会报错,因为对于类类型判断相等的符号编译器是无法识别的(只有内置类型才可以使用):
在这里插入图片描述
那么我们就需要写一个赋值运算符重载了,如下:
在这里插入图片描述
Q1:上面的代码为什么会报错?
A1:我们是定义了一个全局函数,而类内的成员变量是私有的,即类外是无法访问的。

Q2:我们如何解决上面错误呢?
A2:我们有四个方法:

  • 成员变为公有(不建议,不安全)
  • Date提供GetYear之类的函数
  • 使用友元函数(之后会介绍)
  • 重载为成员函数(推荐,下面第3点就会讲到)
  1. 如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数⽐运算对象少⼀个。

示例:
在这里插入图片描述

  1. 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致。
  2. 不能通过连接语法中没有的符号来创建新的操作符:⽐如operator@。
  3. ⼀个类需要重载哪些运算符,是看哪些运算符重载后有意义,⽐如Date类重载operator-就有意义(两个日期相减得到天数),但是重载operator+就没有意义(两个日期相加没有意义)。
  4. 重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,⽆法很好的区分。C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,⽅便区分。
  5. 重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调⽤时就变成了 对象<<cout,不符合使⽤习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对象。

示例:

//流插入
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

//流提取
istream& operator>>(istream& in, Date& d)
{
	cout << "请依次输入年月日:>";
	in >> d._year >> d._month >> d._day;
	return in;
}

Q:为什么返回值要是ostream&和istream&?
A:因为我们他的对象是为了让他支持连续输入输出 的特性,对于&是因为流插入和流提取是不允许被修改的。

对于上面的讲解,下面我会依次给出例子:

示例①:重载运算符+和+=

// d1 += 50
Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		return *this -= (-day);
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}

// d1 + 50
Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp._day += day;
	while (tmp._day > GetMonthDay(tmp._year, tmp._month))
	{
		tmp._day -= GetMonthDay(tmp._year, tmp._month);
		tmp._month++;
		if (tmp._month == 13)
		{
			tmp._year++;
			tmp._month = 1;
		}
	}
	return tmp;
}

由于每月的天数不一样,并且闰年的二月会少一天,所以定义GetMonthDay是获取某年某月的天数,如下:

int GetMonthDay(int year, int month)
{
	static int day[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };
	if (month == 2 && (year % 4 == 0 && year % 100 != 0) && year % 400 == 0)
		return 29;
	return day[month];
}

但是对于上面的重载运算符+和+=,其实我么你只需要实现一个就可以了,另外一个只需要通过已经重载的那个运算符来使用它即可,如下是两种不同重载对比:
在这里插入图片描述
我们可以发现,我们先重载运算符+=,在用+=就可以间接使用运算符重载+,这样的效率更高,只需要调用两次拷贝构造。

示例②:同上,重载运算符-和-=

// d1 -= 50
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += (-day);
	}
	_day -= day;
	while(_day <= 0)
	{
		//借位
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

// d1 - 50
Date Date::operator-(int day)
{
	Date tmp = *this;
	tmp -= day;
	return tmp;
}

示例③:重载运算符>(大于)

// >
bool Date::operator>(const Date& d)
{
	if (_year > d._year)
		return true;
	else if (_year == d._year && _month > d._month)
		return true;
	else if (_year == d._year && _month == d._month)
		return _day > d._day;
	return false;
}

示例④:运算符重载==(等于)

// == 
bool Date::operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

有了>和==运算符重载,可以衍生如下运算符重载:

  1. 大于等于
// >=
bool Date::operator>=(const Date& d)
{
	return *this > d || *this == d;
}
  1. 小于
// <
bool Date::operator<(const Date& d)
{
	return !(*this >= d);
}
  1. 小于等于
// <=
bool Date::operator<=(const Date& d)
{
	return !(*this >= d);
}
  1. !=
// !=
bool Date::operator!=(const Date& d)
{
	return !(*this == d);
}

示例⑤:前置++和后置++

// 前置++
Date& Date::operator++()
{
	*this += 1;
	return *this;
}

// 后置++
Date Date::operator++(int)
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}

注意:对于前置++,我们返回的是Date&,因为前置++,自增之后,我们返回的是+1的值。而后置++,我们返回的是没有+1之前的值,但是我们本身this指向的对象时自增了的。

1.2 赋值运算符重载

赋值运算符重载是⼀个默认成员函数,⽤于完成两个已经存在的对象直接的拷⻉赋值,这⾥要注意跟拷⻉构造区分,拷⻉构造⽤于⼀个对象拷⻉初始化给另⼀个要创建的对象。

赋值运算符的特点:

  1. 赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成const 当前类类型引⽤,否则会传值传参会有拷⻉。
  2. 有返回值,且建议写成当前类类型引⽤,引⽤返回可以提⾼效率,有返回值⽬的是为了⽀持连续赋值场景。
  3. 没有显式实现时,编译器会⾃动⽣成⼀个默认赋值运算符重载,默认赋值运算符重载⾏为跟默认构造函数类似,对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的赋值运算符重载。
  4. 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的赋值运算符重载就可以完成需要的拷⻉,所以不需要我们显⽰实现赋值运算符重载。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器⾃动⽣成的赋值运算符重载完成的值拷⻉/浅拷⻉不符合我们的需求,所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。像MyQueue这样的类型内部主要是⾃定义类型Stack成员,编译器⾃动⽣成的赋值运算符重载会调⽤Stack的赋值运算符重载,也不需要我们显⽰实现MyQueue的赋值运算符重载。这⾥还有⼀个⼩技巧,如果⼀个类显⽰实现了析构并释放资源,那么他就需要显⽰写赋值运算符重载,否则就不需要。(与拷贝构造函数类似)

示例:

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

	Date(const Date& d)
	{
		cout << " Date(const Date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << '-' << _month << '-' << _day << endl;
	}

	// 传引⽤返回减少拷⻉
	// d1 = d2;
	Date& operator=(const Date& d)
	{
		// 不要忘记检查⾃⼰给⾃⼰赋值的情况
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		// d1 = d2表达式的返回对象应该为d1,也就是*this
		return *this;
	}

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


int main()
{
	Date d1(2024, 11, 28);
	Date d2(d1);
	Date d3(2024, 11, 11);
	d1 = d3;

	// 需要注意这⾥是拷⻉构造,不是赋值重载
	// 请牢牢记住赋值重载完成两个已经存在的对象直接的拷⻉赋值
	// ⽽拷⻉构造⽤于⼀个对象拷⻉初始化给另⼀个要创建的对象
	Date d4 = d1;

	return 0;
}

2.取地址重载

2.1 const成员函数

  • 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面
  • const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进⾏修改。const 修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this 变为 const Date* const this

意思就是我们在成员函数的后面加上了const,就意味着不可以修改this指向的对象了。

注意:

  1. const对象必须调用const成员函数,这是权限的平移
  2. const对象若调用非const成员函数,会报错误,这是权限的放大
  3. 非const对象调用const成员函数,是允许的,这是权限的缩小

2.2 取地址运算符重载

取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器⾃动⽣成的就可以够我们⽤了,不需要去显⽰实现。除⾮⼀些很特殊的场景,⽐如我们不想让别⼈取到当前类对象的地址,就可以⾃⼰实现⼀份,胡乱返回⼀个地址。

示例:

class Date
{
public :
	Date* operator&()
	{
	return this;
	// return nullptr;
	}
	const Date* operator&()const
	{
	return this;
	// return nullptr;
	}
private :
	int _year ; // 年
	int _month ; // ⽉
	int _day ; // ⽇
};

3.类与对象的补充

3.1 再探构造函数—初始化列表

  • 之前我们实现构造函数时,初始化成员变量主要使⽤函数体内赋值,构造函数初始化还有⼀种⽅式,就是初始化列表,初始化列表的使⽤⽅式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式。

示例:

class Time
{
public:
	Time(int hour)
		:_hour(hour) //初始化列表
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
};
  • 每个成员变量在初始化列表中只能出现⼀次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地⽅。
  • 引⽤成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进⾏初始化,否则会编译报错
    Q:为什么这三种情况必须在初始化列表进行初始化呢?
    A:我们知道,①引用是不可以修改的,他不像指针可以修改,也不像指针初始化成空。在之前我们就提到过:引用必须初始化和不可以修改。②const修饰的成员变量是不可以进行修改的,他是一个常变量,存储到内存中的代码段(只读变量)。③对于自定义的类类型当中还存在自定义的类类型,我们必须调用它的默认构造函数,不然就会报错。

示例:

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)
	{
		// error C2512: “Time”: 没有合适的默认构造函数可⽤
		// error C2530 : “Date::_ref” : 必须初始化引⽤
		// error C2789 : “Date::_n” : 必须初始化常量限定类型的对象
	}
	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t; // 没有默认构造
	int& _ref; // 引⽤
	const int _n; // const
};

int main()
{
	int i = 0;
	Date d1(i);
	d1.Print();

	return 0;
}

在这里插入图片描述

  • C++11⽀持在成员变量声明的位置给缺省值,注意:这个缺省值只是是给没有显⽰在初始化列表初始化的成员使⽤的。
  • 尽量使⽤初始化列表初始化,因为那些你不在初始化列表初始化的成员也会⾛初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会⽤这个缺省值初始化。如果你没有给缺省值,对于没有显⽰在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显⽰在初始化列表初始化的⾃定义类型成员会调⽤这个成员类型的默认构造函数,如果没有默认构造会编译错误。

示例:

class Time
{
public:
	Time(int hour)
		:_hour(hour)
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
};

class Date
{
public:
	Date()
		:_month(2)
	{
		cout << "Date()" << endl;
	}
	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	// 注意这⾥不是初始化,这⾥给的是缺省值,这个缺省值是给初始化列表的
	// 如果初始化列表没有显⽰初始化,默认就会⽤这个缺省值初始化
	int _year = 1;
	int _month = 1;
	int _day;
	Time _t = 1;
	const int _n = 1;
	int* _ptr = (int*)malloc(12);
};
int main()
{
	Date d1;
	d1.Print();

	return 0;
}
  • 初始化列表中按照成员变量在类中声明顺序进⾏初始化,跟成员在初始化列表出现的的先后顺序⽆关。建议声明顺序和初始化列表顺序保持⼀致。

示例:下⾯程序的运⾏结果是什么()
A. 输出 1 1
B. 输出 2 2
C. 编译报错
D. 输出 1 随机值
E. 输出 1 2
F. 输出 2 1

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

在这里插入图片描述
所以这道题应该选D,这道题还是很重要的。

3.2 类型转换

  • C++⽀持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数
  • 构造函数前⾯加explicit就不再⽀持隐式类型转换

示例:

class A
{
public:
	// 构造函数explicit就不再⽀持隐式类型转换
	// explicit A(int a1)
	A(int a1)
		:_a1(a1)
	{}

	//explicit A(int a1, int a2)
	A(int a1, int a2)
		:_a1(a1)
		,_a2(a2)
	{}

	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}

private:
	int _a1 = 1;
	int _a2 = 2;
};

int main()
{
	// 用1来构造一个A的临时对象,再用这个临时对象拷贝构造aa1
	// 编译器优化:遇到连续构造+拷贝构造 -> 优化为直接构造
	A aa1 = 1;
	aa1.Print();

	const A& aa2 = 1;

	// C++11之后开始支持多参数转化
	A aa3 = { 2, 2 };

	return 0;
}

3.3 static成员

  • ⽤static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进⾏初始化。

示例:

class A
{
private:
	// 类内声明
	static int _scount;
};

// 类外初始化
int A::_scount = 0;
  • 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。

示例:对于上面的示例中,我们计算类A的大小:cout << sizeof(A) << endl; 输出的结果是1,之前我们讲过1是为了占位使用的。

  • ⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
  • 静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针

示例:

在这里插入图片描述

  • 非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
  • 突破类域就可以访问静态成员,可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。
  • 静态成员也是类的成员,受public、protected、private 访问限定符的限制。
  • 注意:静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。

示例:实现一个类,计算程序共创建了多少个类对象

class A
{
public:
	A()
	{
		++_scount;
	}

	A(const A& a)
	{
		++_scount;
	}

	~A()
	{
		--_scount;
	}

	static int GetACount()
	{
		return _scount;
	}

private:
	// 类内声明
	static int _scount;
};

//类外初始化
int A::_scount = 0;

int main()
{
	//静态成员不属于某个对象,可以直接通过类域访问到
	cout << A::GetACount() << endl;
	A a1, a2;
	A a3(a1);
	cout << A::GetACount() << endl;
	cout << a1.GetACount() << endl;

	// 编译报错:error C2248: “A::_scount”: ⽆法访问 private 成员(在“A”类中声明)
	//cout << A::_scount << endl;

	return 0;
}

在这里插入图片描述
牛客网的一个例题,不允许使用各种循环和递归,实现 1 + 2 + 3 + … + n ?

我们可以通过创建类来实现,创建第几个类就让他加上几。示例:

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

int Sum::_i = 1;
int Sum::_ret = 0;

class Solution
{
public:
	int Sum_Solution(int n) {
		//VS不支持边长数组,会报错
		Sum arr[n];
		return Sum::GetRet();
	}
};

注意:VS不支持边长数组。
加粗样式
例题:有A、B、C、D四个类

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

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

Q1:程序中A,B,C,D构造函数调用顺序为?(E)
A1:这个很简单
Q2程序中A,B,C,D析构函数调⽤顺序为?(B)
A2:这里要注意一点,static修饰的变量是存放在静态区的,static修饰的变量生命周期会变长,即整个main函数栈帧销毁才会销毁D,所以辉县析构B和A,在析构D,由于C是在全局变量中,所以最后析构他(它是最先创建的,先创建的后析构)

3.4 友元

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

友元函数示例:

// 前置声明,否则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;
}

友元类示例:

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.func2(aa);
	return 0;
}

3.5 内部类

  • 如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独⽴的类,跟定义在全局相⽐,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
  • 内部类默认是外部类的友元类
  • 内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使⽤,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。

示例:

class A
{
public:
	class B //B默认就是A的友元
	{
	public:
		void func(const A& a)
		{
			cout << _k << ' ' << a.h << endl;
		}
	};
private:
	static int _k;
	int h = 1;
};

int A::_k = 1;

int main()
{
	//输出结果是4,所以内部类是不占空间的
	cout << sizeof(A) << endl;

	A::B b; //实例化B

	A aa;
	b.func(aa);

	return 0;
}

我们把上面那个牛客网的题目优化一下:

class Solution
{
public:
	class Sum
	{
		Sum()
		{
			_ret += _i;
			_i++;
		}
	};
public:
	int Sum_Solution(int n)
	{
		// 变长数组
		Sum arr[n];
		return _ret;
	}
private:
	static int _i;
	static int _ret;
};

int Solution::_i = 1;
int Solution::_ret = 0;

这样写是不是更简单、优美了。

3.6 匿名对象

  • 用类型(实参) 定义出来的对象叫做匿名对象,相比之前我们定义的 类型 对象名(实参) 定义出来的叫有名对象。
  • 匿名对象⽣命周期只在当前⼀行,⼀般临时定义⼀个对象当前用⼀下即可,就可以定义匿名对象。

示例:

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

class Solution
{
public:
	int Sum_Solution(int n) {
		return n;
	}
};

int main()
{
	A aa1;

	//不可以这样定义对象,因为编译器⽆法识别下⾯是⼀个函数声明,还是对象定义
	//这里VS没有报错,也没有调用它的构造和析构函数,说明编译器把它当做是函数声明
	//A aa2();

	//但是我们可以这么定义匿名对象,匿名对象的特点不⽤取名字
	//但是他的⽣命周期只有这⼀⾏,我们可以看到下⼀⾏他就会⾃动调⽤析构函数
	A();
	A(1);

	// 匿名对象在这样场景下就很好⽤,当然还有⼀些其他使⽤场景
	Solution().Sum_Solution(10);

	return 0;
}

3.7 对象拷贝时的编译器优化

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

这一点未完待续!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

自然语言处理:基于BERT预训练模型的中文命名实体识别(使用PyTorch)

命名实体识别&#xff08;NER&#xff09; 命名实体识别&#xff08;Named Entity Recognition, NER&#xff09;是自然语言处理&#xff08;NLP&#xff09;中的一个关键任务&#xff0c;其目标是从文本中识别出具有特定意义的实体&#xff0c;并将其分类到预定义的类别中。这…

面试题-RocketMQ的基本架构、支持的消息模式、如何保证消息的可靠传输

相关问题 1、RocketMQ的基本架构是怎样的&#xff1f;请简述各组件的作用。 2、RocketMQ支持哪几种消息模式&#xff08;如点对点、发布/订阅&#xff09;&#xff1f;请简要说明它们的区别。 3、如何使用Java客户端实现一个简单的消息生产者和消费者&#xff1f; 4、RocketMQ…

【力扣】3274. 检查棋盘方格颜色是否相同

一、题目 给你两个字符串 coordinate1 和 coordinate2&#xff0c;代表 8 x 8 国际象棋棋盘上的两个方格的坐标。以下是棋盘格的参考图&#xff1a; 如果这两个方格颜色相同&#xff0c;返回 true&#xff0c;否则返回 false。坐标总是表示有效的棋盘方格。坐标的格式总是先字…

mysql 5.7安装及安装后无法启动问题处理

下载安装包&#xff0c;直接解压 配置环境变量 创建my.ini文件 [mysqld] #端口号 port 3306 #mysql-5.7.27-winx64的路径 basedirD:/soft/mysql57 #mysql-5.7.27-winx64的路径\data datadirD:/soft/mysql57/data #最大连接数 max_connections200 #编码 character-set-server…

2023年第十四届蓝桥杯Scratch国赛真题—推箱子

推箱子 程序演示及其源码解析&#xff0c;可前往&#xff1a; https://www.hixinao.com/scratch/creation/show-188.html 若需在线编程&#xff0c;在线测评模考&#xff0c;助力赛事可自行前往题库中心&#xff0c;按需查找&#xff1a; https://www.hixinao.com/ 题库涵盖…

级联树结构TreeSelect和上级反查

接口返回结构 前端展示格式 前端组件 <template><div ><el-scrollbar height"70vh"><el-tree :data"deptOptions" :props"{ label: label, children: children }" :expand-on-click-node"false":filter-node-me…

28.100ASK_T113-PRO Linux+QT 显示一张照片

1.添加资源文件 2. 主要代码 #include "mainwindow.h" #include "ui_mainwindow.h" #include <QImage> #include <QPixmap>MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow) {ui->setupUi(this);QIm…

基于PySpark 使用线性回归、随机森林以及模型融合实现天气预测

基于PySpark 实现天气预测与模型集成 在大数据分析与机器学习领域&#xff0c;Spark 提供了强大的计算能力和灵活的扩展性。本文将介绍如何利用 PySpark 完成以下任务&#xff1a; 1、数据预处理&#xff1a;清洗和编码天气数据。 2、特征工程&#xff1a;合并数值和分类特征…

【MySQL — 数据库基础】深入理解数据库服务与数据库关系、MySQL连接创建、客户端工具及架构解析

目录 1. 数据库服务&#xff06;数据库&#xff06;表之间的关系 1.1 复习 my.ini 1.2 MYSQL服务基于mysqld启动而启动 1.3 数据库服务的具体含义 1.4 数据库服务&数据库&表之间的关系 2. 客户端工具 2.1 客户端连接MySQL服务器 2.2 客…

各种类型无人机性能及优缺点技术详解

无人机系统种类繁多、用途广泛&#xff0c;且特点鲜明&#xff0c;致使其在尺寸、质量、航程、航时、飞行高度、飞行速度以及任务载荷等多方面都有较大差异。以下是对几种常见类型无人机的性能、优缺点技术的详细解析&#xff1a; 一、固定翼无人机 1.性能&#xff1a; 固定翼…

yolo辅助我们健身锻炼

使用软件辅助健身能够大大提升运动效果并帮助你更轻松地达成健身目标。确保每次锻炼都更加高效且针对性强,精确记录你的训练进度,帮助你更清晰地看到自己的进步,避免无效训练。 借助YOLO11的尖端计算机视觉技术,跟踪和分析锻炼变得异常简单。它可以无缝检测和监控多种锻炼…

Linux修改系统及终端命令行中的用户名

0、前言 最近捣鼓了一下一个很久之前的用过的ubuntu系统&#xff0c;但是之前随意设置了一个用户名&#xff0c;突发奇想地去修改了一下这个ubuntu系统的系统用户名&#xff0c;发现修改起来还是有些麻烦&#xff0c;并没有那种一键修改的选项&#xff0c;所以在这篇博客下面记…

基于智能语音交互的智能呼叫中心工作机制

在智能化和信息化不断进步的现代&#xff0c;智能呼叫中心为客户提供高质量、高效率的服务体验&#xff0c;提升众多品牌用户的满意度和忠诚度。作为实现智能呼叫中心的关键技术之一的智能语音交互技术&#xff0c;它通过集成自然语言处理&#xff08;NLP&#xff09;、语音识别…

Linux条件变量线程池详解

一、条件变量 【互斥量】解决了线程间同步的问题&#xff0c;避免了多线程对同一块临界资源访问产生的冲突&#xff0c;但同一时刻对临界资源的访问&#xff0c;不论是生产者还是消费者&#xff0c;都需要竞争互斥锁&#xff0c;由此也带来了竞争的问题。即生产者和消费者、消费…

「Mac畅玩鸿蒙与硬件38」UI互动应用篇15 - 猜数字增强版

本篇将带你实现一个升级版的数字猜谜游戏。相比基础版&#xff0c;新增了计分和历史记录功能&#xff0c;用户可以在每次猜测后查看自己的得分和猜测历史。此功能展示了状态管理的进阶用法以及如何保存和显示历史数据。 关键词 UI互动应用数字猜谜状态管理历史记录用户交互 一…

【IMF靶场渗透】

文章目录 一、基础信息 二、信息收集 三、flag1 四、flag2 五、flag3 六、flag4 七、flag5 八、flag6 一、基础信息 Kali IP&#xff1a;192.168.20.146 靶机IP&#xff1a;192.168.20.147 二、信息收集 Nmap -sP 192.168.20.0/24 Arp-scan -l nmap -sS -sV -p- -…

【C#】书籍信息的添加、修改、查询、删除

文章目录 一、简介二、程序功能2.1 Book类属性&#xff1a;方法&#xff1a; 2.2 Program 类 三、方法&#xff1a;四、用户界面流程&#xff1a;五、程序代码六、运行效果 一、简介 简单的C#控制台应用程序&#xff0c;用于管理书籍信息。这个程序将允许用户添加、编辑、查看…

【Code First】.NET开源 ORM 框架 SqlSugar 系列

.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列【数据事务…

CLIP模型也能处理点云信息

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

配置宝塔php curl 支持http/2 发送苹果apns消息推送

由于宝塔面板默认的php编译的curl未加入http2的支持&#xff0c;如果服务需要使用apns推送等需要http2.0的访问就会失败&#xff0c;所以重新编译php让其支持http2.0 编译方法&#xff1a; 一、安装nghttp2 git clone https://github.com/tatsuhiro-t/nghttp2.git cd nghttp…