【C++初阶】类和对象终极篇

news2024/11/29 8:39:19

文章目录

  • 一.加const修饰this指针
  • 二.cout<<自定义类型的对象的实现
    • 1.深入理解cout和<<
    • 2流插入和流提取
    • 3.友元函数的来源
        • a.作为ostream成员函数
        • b.作为全局函数
        • c.作为Date类的成员函数
        • d.作为Date类的友元函数
  • 三.再谈构造函数之初始化列表
  • 四.隐式类型转换&explicit
  • 五.静态成员
    • 1.静态成员变量
    • 2.静态成员函数
  • 六.友元类
  • 七.内部类
  • 八.匿名对象
  • 九.编译器优化问题

一.加const修饰this指针

学完这一节,你将更加理解什么是const引用,为什么要在this指针前面加上const修饰.

先给大家复习一下什么是const引用:

我们知道在函数传参的时候,为了减少拷贝,所以我们一般选择传引用的方式,但是传引用有一个问题,假如我不想任何人在函数内部改引用值,从而改变实参.我们一般都会选择在引用的基础上加上const修饰,这样的话,有两个优点:

  1. 非const和const的对象都能调用这个函数
  2. 引用值受到const保护,在函数内部不能被修改.

总而言之:只要是在传引用的参数,且在函数内部不想改变引用值的函数,我们都建议使用const引用.image-20221015220857526

我们之前学过成员函数的第一个参数是this指针,他的类型是Date* 类型,const修饰的是指针,也就是this指针的指向不能改,但是this指针指向的内容是可以改的,以成员函数–打印函数为例:

Print(/*Date* const this*/)

类比我们上面的const引用,这里的this指针也是建议加上const,也就是使this指针也能达到上面列举的两个优点.

下面是Print()中this指针没有加上const修饰前,主函数中一个const修饰的对象调用Print()函数失败的报错信息:

image-20221015221828272

但是这里又有一个问题:这里的this指针是隐含的,你先显式的写出this都不可以,更别提加上const修饰了

所以C++就提出了一个解决问题的语法支持:在函数头和函数体中间加上一个const修饰,这个const就在C++语法上加到了this指针类型的最前面,也就是:

Print() const
Print(/*const Date* const this*/) const

加上后:

image-20221015221936489

二.cout<<自定义类型的对象的实现

学完了这一节,你将会使用cout<<输出自定义类实例化的对象,可以代替Print()函数的功能了.🐼🐼🐼

1.深入理解cout和<<

问题a: 首先cout是什么东西?

其实cout是ostream类实例化出来的对象,我们并没有写ostream和cout但是它们都被包含在了头文件iostream中,所以可以直接使用.同理cin就是istream实例化出来的对象.

问题b: 那为什么我们cout可以使用<<连续输出所有内置类型的值呐?

ps:<<好像有两个身份:1.左移操作符 2.C++的输出操作符

ps:&好像也有两个身份:1.取地址 2.引用

这里有三个点:第一:之所以cout可以使用<<输出,那是因为ostream类中对运算符<<重载了
第二:之所以cout<<可以输出所有内置类型的值,也就是cout可以自动识别int类型还是char类型,不需要我们类似C语言printf格式化输出,那是因为C++中在对所有内置类型都使用了运算符<<重载,多个函数名相同,输出参数类型不同的运算符重载函数就构成了函数重载.第三:之所以cout可以连续输出(cout<<a<<“hello”),那是因为cout在从左往右执行的过程中,cout<<a调用的函数结束后返回值的是ostream类型的对象,所以可以链式输出.image-20221015131855871

//cout<<5<<'h'的实现框架:

ostream& operator<<(ostream& out, int val)
{
	//输出整型值val的代码
	return out;
}

ostream& operator<<(ostream& out, char ch)
{
	//输出字符类型的值ch的代码
	return out;
}

//链式输出的实际调用:
cout.operator<<(5).operator<<'h';

2流插入和流提取

int a=100;
cout<<a;//流插入
cin>>a;//流提取

image-20221015142809433

3.友元函数的来源

通过上节我们知道:内置类型的<<运算符重载函数已经是被大佬写入ostream对象中,但是对于自定义类型是未定义的,毕竟自定义类型的成员变量的类型和顺序结构是千变万化的,大佬也没法管,所以对于自定义类型的运算符重载函数得自己写.

其实对于自定义类型的运算符重载函数的代码实现问题不大吗,难点在于这个函数的是写成ostream类成员函数,全局函数,Date类的成员函数,Date类的友元函数的一步一步的优化.

a.作为ostream成员函数

这种方式把自定义类型的运算符重载函数也和内置类型的运算符重载函数一样,写入ostream类中,把&cout传给了隐藏的this指针.

但是这里有一个尴尬的问题:ostream是通过包含头文件中包含进来的,所以没办法再把自定义类型的运算符重载函数写到ostream类中.

b.作为全局函数

那么我们接着肯定就是考虑写成全局函数:

但是之前我们说过成员变量一般是封装成私有private修饰的:

private:
	int _year;
	int _month;
	int _day;

这就是告诉我们没法在Date类域外访问_year等成员变量,所以写成下面这种全局的形式显然不妥当,总不能丢了西瓜(封装) ,捡了芝麻(自定义类型的<<运算符重载函数)吧.

//全局函数:
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年"<< d._month << "月"<< d._day << "日" << endl;//_year等爆红
	return out;//链式输出
}

image-20221015140201369

c.作为Date类的成员函数

吸收了上次写成全局函数没法访问私有的日期类的成员变量的经验,这一次,我们决定使用利用好封装,以其人之道还置于其人之身,我们把自定义类型的运算符重载函数写成Date类的成员函数:

image-20221015140429100

但是我们发现这样还是没有解决问题,原因在于函数一旦写成Date类的成员函数,第一个参数一定时隐藏的this指针(Date* const this),this指针隐式的占用了<<操作符第一个参数的位置:

//	主函数调用代码:
    Date d1(2022, 1, 1);
	Date d1(2022, 12, 31);
	cout << d1 << d2;
	
//  Date类的实现代码:
	ostream& operator<<(const Date & d)
	{
		out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
		return out;
	}

但是这似乎还有一种反对初衷,不太合理补救措施——–调用的时候把d1对象和cout对象对调一下:

这样虽然能跑,但是有一些缺点:

  1. 反对初衷:增强可读性,贴合使用习惯
  2. 不太合理:<<运算符对自定义类型不在支持链式输出,和内置类型支持链式重载功能不符

image-20221015141337107

d.作为Date类的友元函数

到这里,如果大佬再不创造出语法上—-友元函数的支持,恐怕C++就不能支持自定义类型的流插入了.

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在 类的内部声明,声明时需要加friend关键字。

#include<iostream>
using namespace std;
class Date
{
	friend ostream& operator<<(ostream& out, const Date& d);
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;
	int _day;

};

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

int main()
{
	Date d1(2022, 1, 1);
	Date d2(2022, 12, 31);
	cout << d1 << d2;
	return 0;
}

image-20221015144142867

ps:frind友元函数的声明可以放在Date类内的任意位置

cin的实现原理和cout类似,这里就不过多赘述.

三.再谈构造函数之初始化列表

学会这一节,你将知道成员变量是在初始化列表中被定义的和在某三种成员变量必须在初始化列表中初始化的.

首先我们知道类实例化出对象是在主函数中完成的(对象是整体定义的,但是并没有定义一个一个的成员变量),但是对象中的成员变量是在哪里被定义的呐?

实际上,所有的成员变量都要在初始化列表中定义.

总结一下: Date d完成的是一个对象的定义,调用构造函数完成的是一个对象d的初始化,那么要完成一个对象的初始化,我们就得使对象里面的成员都进行定义并且初始化.

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

以日期类中的成员变量的定义并初始化为例,来给大家看看初始化列表的格式:

	//构造函数
	Date(int year = 1, int month = 1, int day = 1)
        //初始化列表
		:_year(year)
		,_month(month)
		,_day(day)
	{
		//_year = year;
		//_month = month;
		//_day = day;
	}

其实像日期类的“这种”成员变量,我们可以在初始化列表中完成定义并初始化,也可以在初始化列表中只完成定义(这样的话,其实也没有必要显式的写出来),在函数体中完成赋值,就像下面:

	//构造函数
	Date(int year = 1, int month = 1, int day = 1)
		//:_year()//这个编译器自己也会
		//,_month()
		//,_day()
	{
        //初始化列表只能对每一个成员变量初始化一次,但是可以在函数体内反复赋值
		_year = year;
		_month = month;
		_day = day;
	}

ps:初始化列表只能对每一个成员变量初始化一次,但是可以在函数体内反复赋值

那么如果所有的类型的变量都可以像上面日期类的成员变量那样可以在在初始化列表中只完成定义(不用写),在函数体中完成赋值,那么初始化列表存在也毫无意义.

但是并非所有类型都是和他们一样,有下面三种成员变量是必须在初始化列表定义的时候就得初始化,所以下面三种是只能在初始化列表中完成初始化的(谁让你初始化列表是完成我的定义的地方,那么你就得送佛送到西,也得帮我完成初始化的任务)

  1. 成员变量是引用类型(且声明时没有给缺省值)
  2. 成员变量是const修饰的(且声明时没有给缺省值)
  3. 成员变量是自定义类型(且它没有自己的默认构造函数)

回忆:

1.const类型的变量必须在定义的时候就初始化,之后就不能改了(const属性)

2.引用类型的变量必须在定义的时候初始化,之后就不能再引用其他变量了(之后就是赋值了)

image-20221016001122883

ps:这里澄清一个概念问题:构造函数和默认构造函数和编译器生成的默认构造函数三个概念是不一样的

image-20221016004846605

对成员变量分类:

内置类型:如果给了成员变量的缺省值,对象的默认构造函数就会在对象的默认构造函数的初始化列表中使用声明时给的缺省值,如果没给,就会时随机值

自定义类型:如果给了成员变量的默认构造函数,对象的默认构造函数就会在对象的默认构造函数的初始化列表中调用成员变量的默认构造函数,如果没给,就会报错.

的确会有点绕哈,但是看懂了就说明你学会了,哈哈哈哈

给了缺省值的情况,在初始化列表中使用缺省值的证明如下图:

image-20221016004509386

class A
{
	friend ostream& operator<<(ostream& out, const A& a);
public:
    //默认构造函数:不用传参数就可以调用的构造函数,三种:全缺省,无参,编译器自动生成的
    //如果不传参数,就没法调用,所以A(int a)这种半缺省的构造函数不是默认构造函数
	A(int a)
	{
		_a = a;
	}
private:
	int _a;
};
int x = 2;
class B
{
	friend ostream& operator<<(ostream& out, const B& d);
public:
	B()
	//三种特别的,如果内置类型的成员变量无缺省值
	//,自定义类型的成员变量无默认构造,就必须在初始化列表定义的时候顺便初始化
		:_b(1)
		, _rx(x)
		,_aa(3)
	{
		_ib = 4;
	}
private:
	int _ib;//普通类型
	const int _b;//const修饰的成员
	int& _rx;//引用类型的成员
	A _aa;//没有默认构造的自定义成员
};

inline ostream& operator<<(ostream& out, const A& a)
{
	out << a._a << endl;
	return out;
}

inline ostream& operator<<(ostream& out, const B& d)
{
    //内置类型的运算符重载在ostream类中已经定义了,直接用
	out << d._ib << "---" << d._b << "---" << d._rx << "---";
	//d._aa也是自定义类型,要输出也要写A类的运算符重载且定义成友元
	cout << "---" << d._aa << endl;
	return out;
}



int main()
{
	B b;
	cout << b << endl;
	return 0;
}

问题:一个类必须提供默认构造函数,没有提供默认构造就会报错吗?

解答:错误

class A
{
public:
	//这个是构造函数,但不是默认构造函数
	//因为自己写了构造函数,所以编译器也不自动生成默认构造了
	//也就是说这个A类没有提供默认构造函数,没有调用的话并没有报错,但是调用不传参就会报错.
	A(int a)
        :_a(a);
	{
	}
private :
	int _a;
};
int main()
{
	return 0;
}

建议:能用初始化列表,尽量使用初始化列表进行初始化.

因为:

  1. 有些成员可以在函数体,也可以在初始化列表初始化,但是有三种成员必须在初始化列表初始化,所以在初始化列表进行初始化更安全.
  2. 你没有在初始化列表进行初始化,编译器还是要走初始化列表,所以干脆一气呵成.

规则: 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关.

猜一猜下面程序的执行结果是:

A. 输出 1 1

B .程序崩溃

C. 编译不通过

D .输出随机值 1

正确答案:D

class A
{
public:
	//成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后
	//次序无关


	//声明顺序先_a1再_a2
	//初始化顺序先_a1再_a2
	A(int a)
		:_a2(a)//后初始化_a2,此时a是1,所以_a2被初始化为1
		, _a1(_a2)//先初始化_a1,此时_a2还是随机值,所以_a1被初始化为随机值
	{
	}

	void Print()
	{
		cout << _a1 << "  " << _a2 << endl;
	}
private:
	int _a1;//声明顺序先_a1再_a2
	int _a2;
};

int main()
{
	A x(1);
	x.Print();
	return 0;
}

image-20221017113503567

四.隐式类型转换&explicit

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值 的构造函数【也就是可以只允许传入一个参数就能调用的】,还具有类型转换的作用。

class Date
{
public:
	//单参数的默认构造函数
	Date(int year)
		:_year(year)
	{
	}

	//第一个参数没有缺省值,其余参数都有默认值的默认构造函数
	//Date(int year, int month = 10, int day = 13)
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2022);
    //支持隐式类型转换
	Date d2 = 2022;
	return 0;
}

image-20221017114644976

这有点像之前的两种拷贝构造的形式(但不是,只是样子像):

Date d1(2022);
Date d2(d1);
Date d3=d1;//但是这里是同类型的对象的赋值(拷贝)

其实隐式类型转换我们再引用那一块学过:

不同类型之间的玩法:
Date d1(2022);
//隐式类型转换:小时候的版本:内置类型到内置类型的引用
int i=0;
double d=i//在这中间有一个const类型的临时变量,但这里是拷贝,可以赋值个d
const double& j=i;//但是在这里是引用,临时变量(const)到j不能权限放大,所以要加一个const修饰

//隐式类型转换:长大后的版本:内置类型到自定义类型的引用
Date d2(d1);
Date d3=2022;//在这中间有一个const类型的临时变量,但这里是拷贝,可以赋值个d3
const Date& d4=2022;//但是在这里是引用,临时变量(const)到d4不能权限放大,所以要加一个const修饰

这个隐式类型转换看上去有点别扭,但是在后面你就会发现这个东西好用的很,比如:

image-20221017121154528

更有甚者,可以一气呵成,简介多了:

#include<string>

void push_back(const string& s);
int main()
{
	string s1("hello");
	push_back(s1);

	//隐式类型转换:一气呵成
	push_back("hello");

	return 0;
}

但是如果我不想让这种隐式类型转换发生,我可以怎么做呐?

那就是在构造函数前加上一个关键字:explicit,也就是

	//单参数的默认构造函数
	explicit Date(int year)
		:_year(year)
	{
	}
	
	//第一个参数没有缺省值,其余参数都有默认值的默认构造函数
	//explicit Date(int year, int month = 10, int day = 13)
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}

加了explicit后看一下报错信息:

image-20221104160107318

前面是C++98支持的单参数构造函数,那么在C++11后,又支持了多参数构造函数。

image-20221104160707389

同样的加一个explicit就不能支持多参数构造了

五.静态成员

1.静态成员变量

没学之前如果我们想知道程序总共调用了几次构造函数【默认构造和拷贝构造】,我们可能写的是一个全局变量:

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

const A& func1(const A& a)
{
	return a;
}
A func2(A a)
{
	return a;
}

int main()
{
	A a1(1);//++N
	A a2 = 2;//优化后只有构造函数,++N
	A a3 = a1;//++N
	cout << N << endl;//3
	


	func1(a1);
	cout << N << endl;//验证传引用不要拷贝构造:3



	func2(a2); //验证传值传参要拷贝构造:5
	cout << N << endl;
	return 0;
}

或许是这样写的:

class A
{
public:
	A(int a = 10)
		:_a(a)
	{
		++N;
	}
	A(const A& a)
		:_a(a._a)
	{
		++N;
	}
	int GetN()
	{
		return N;
	}
private:
	int _a;
	static int N = 0; //N是局部的
	//这里还只是声明,必须有一个地方定义(初始化列表不能定义静态成员变量)
};

int A::N = 0;//N定义

int main()
{
	A a;
	cout << a.GetN() << endl;//1
	return 0;
}

但是C++不是很喜欢写全局变量,理由是在类外面有些人想改N很容易就能改掉,不安全,所以我们也要把封装的思想应用于此处,我们就把N定义在类里面,同时加上static;

一个知识点:加上static修饰后,N变量位于静态区,属于类,但是是每个类共享的。

类型作用域生命周期
局部+static局部整个程序
全局+static全局整个程序
类域+static类域整个程序

2.静态成员函数

接着上面的程序,把变量N封装好了,并提供了GetN()函数供我们使用,似乎一切完美

但是这个GetN()函数只能在通过对象来调用,如果我不想不实例化对象就调用这个GetN()怎么办呐?

那就要用到我们这里要讲的静态成员函数[在函数前面加上static修饰]

作用 :可以通过类名::静态成员函数的方式 直接调用

class A
{
public:
	A(int a = 10)
		:_a(a)
	{
		++N;
	}
	A(const A& a)
		:_a(a._a)
	{
		++N;
	}
    //静态成员函数
	static int GetN()
	{
		return N;
	}
private:
	int _a;
	static int N; 
};
int A::N = 0;
int main()
{
	cout << A::GetN() << endl;
	A a1;
	cout << a1.GetN() << endl;
	A a2;
	cout << a1.GetN() << endl;
	A a3;
	cout << a3.GetN() << endl;

	return 0;
}

ps:静态成员变量和静态全局变量都是位于静态区的,而非属于每一个栈上的对象的,在语法上,C++就规定不能在构造函数的初始化列表定义静态成员函数

image-20221104181802164

这里大家可以看到我试图在初始化列表定义静态成员变量,但是编译器就直接给我把它掐死了

到这里我想给大家区分一下C++和Java中调用静态成员函数的方式:

C++:类名::函数名

Java:类名.函数名

image-20221104190300343

牛客网用静态巧解题:JZ64 求1+2+3+…+nhttps://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=23248&ru=/exam/oj/ta&qru=/ta/coding-interviews/question-ranking&sourceUrl=%2Fexam%2Foj%2Fta%3Fpage%3D1%26tpId%3D13%26type%3D13

这个题就是拿来秀骚操作的,拿来练练手的:

class Sum {
    //调用n次构造函数
public:
    Sum()
    {
        _ret+=_i;
        ++_i;
    }
    static int GetSum()
    {
        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) {
        Sum arr[n];
        return Sum::GetSum();
    }
};

如果我有一个要求:要求对象只能定义在栈上:

(体现封装和静态成员变量的妙用)

//要求定义的对象只能在栈上
class A
{
public:
	static A GetObj(int a = 100)
	{
		A aa(a);
		return aa;
	}
private:
	A(int a = 10)
		:_a(a)
	{
		;
	}
private:
	int _a;
};

int main()
{
	
	//static A aa1;
	//A aa2;
	//A* ptr = new A;

	//你要调用这个GetObj()你得先创建对象,但是要创建对象必须得先调用GetObj()
	A aa4 = A::GetObj();

	return 0;
}

六.友元类

之前我们讲过了友元函数,今天我们再来聊一聊友元相关的问题

先来复习一下友元函数:

  • 友元函数可访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数 友元函数的调用与普通函数的调用原理相同

友元类 :我是你的友元,我就可以偷你的家

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

友元关系是单向的,不具有交换性。 比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接 访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。

友元关系不能传递 如果C是B的友元, B是A的友元,则不能说明C时A的友元。 友元关系不能继承,在继承位置再给大家详细介绍

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 = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	void SetTimeOfDate(int hour, int minute, int second)
	{
		// 直接访问时间类私有的成员变量

		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}


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

七.内部类

这个内部类又叫做类中类,在C++中不怎么重要,但是在Java中还是很重要的

class A
{
private:
	int _a;
public:
	class B
	{
	private:
		int _b;
	};
};

int main()
{
	cout << sizeof(A) << endl;//4
	return 0;
}

这相当于两个独立的类,

但是B的访问收到A的类域和域作用限定符的限制

B天生就是A的友元

class A
{
private:
	int _a;
	static int k;
public:
	//如果定义成私有,这个B类就是A专属的类
	//这样内部类的话,B天生就是A的友元
	class B
	{
	public:

		void Print(const A& a)
		{
			cout << a._a << endl;
		}
	private:
		int _b;
	};
};

int main()
{
	cout << sizeof(A) << endl;//4
	A aa;
	/*
	* B bb;//找不到B类型,需指定
	*/
	A::B bb;
	bb.Print(aa);
	return 0;
}

用内部类改一改之前那个题:

(把Sum类作为Solution类的内部类)

class Solution {
public:
    class Sum
    {
    public://内部类+Sum_Solution
        Sum()
        {
            //类Sum为类Solution的友元
            _ret+=_i;
            ++_i;
        }
    };
    int Sum_Solution(int n) {
        //调用n次构造函数,static修饰记录调用次数,_ret累加
        Sum arr[n];
        return _ret;
    }
private:
        static int _i;
        static int _ret;
};
int Solution::_i=1;
int Solution::_ret=0;

内部类了解一下就可以了,不用深究,C++ 不推荐使用

八.匿名对象

懒人专用–哈哈哈,看到后面你就知道了

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

int main()
{
	//有名对象
	A aa0;
	A aa1(2);
	A aa2 = 2;
	//A aa3();

	//这下面两种方式也在定义对象--
	//匿名对象--生命周期只在当前这一行
	A();
	A(3);
}

image-20221104212449872

匿名对象一般没有生命价值,但是在一些特殊的情形有价值,比如:

  1. 拿上面那个牛客求1+2+3…+n的题例子:

  2. class Solution {
    public:
        class Sum
        {
        public://内部类+Sum_Solution
            Sum()
            {
                //类Sum为类Solution的友元
                _ret += _i;
                ++_i;
            }
        };
        int Sum_Solution(int n) {
            //调用n次构造函数,static修饰记录调用次数,_ret累加
            Sum arr[n];
            return _ret;
        }
    private:
        static int _i;
        static int _ret;
    };
    int Solution::_i = 1;
    int Solution::_ret = 0;
    
    int main()
    {
        //有名对象:要先单独创建对象再调用函数
        Solution su1;
        su1.Sum_Solution(3);
    
        //匿名对象:直接创建并使用
        Solution().Sum_Solution(3);
        return 0;
    }
    
    
    class A
    {
    public:
    	A(int a = 0)
    		:_a(a)
    	{
    		cout << "构造函数" << endl;
    	}
    private:
    	int _a;
    };
    
    A func()
    {
    	/*
    	A ret(10);
    	return ret;
    	*/
    	//匿名对象:名字都懒得起
    	return A();
    }
    
    int main()
    {
    	A aa = func();
    	return 0;
    }
    

九.编译器优化问题

当两个步骤合在一起走的时候,编译器会对一些场景做出优化

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

	A(const A& a)
		:_a(a._a)
	{
		cout << "拷贝构造" << endl;
	}
private:
	int _a;
};

void func1(A aa)
{
	;
}

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

int main()
{
	//优化场景1:
	A aa1 = 1;//A tmp(1)+A aa1(tmp) -->优化成A aa1(1)
	cout << "__________________________________" << endl << endl;

	//优化场景2:
	/*
	A aa2;
	func1(aa2);
	*/

	func1(A(1));//构造+拷贝构造--->优化成构造
	func1(1);//构造+拷贝构造--->优化成构造

	cout << "__________________________________" << endl << endl;

	//优化场景3:
	func2();//构造+拷贝构造

	A ret = func2();//构造+拷贝构造+拷贝构造---->优化成构造+拷贝构造
	return 0;
}

对于优化场景3中:

	A ret = func2();//构造+拷贝构造+拷贝构造---->优化成构造+拷贝构造

image-20221104221253067

image-20221104215904946

如果两个步骤分开写,编译器就不敢随便优化,怕你中间还要用到,但是两个步骤合并起来写,就会极致优化.

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

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

相关文章

Linux命令从入门到实战----文件目录类

文章目录pwd显示当前工作路径的绝对路径ls列出目录的内容cd切换目录mkdir 创建一个新的目录删除一个空的目录touch创建新文件cp复制文件或目rm删除文件或目录mv移动文件与目录&#xff0c;重命名文件cat查看文件内容&#xff0c;创建新文件more文件内容分屏查看less分屏显示文件…

【C语言】字符串、字符数组

目录 写在开头 正文 一、字符串的本质 二、输入函数scanf和gets 三、输出函数printf和puts 四、字符串的长度——strlen 五、字符串的复制——strcpy 六、字符串的比较函数 七、实战练习 八、二维字符数组——字符串数组 写在最后 写在开头 看了标题&#xff0c;是…

Kotlin编程实战——类与对象(05)

一 概述 类与继承属性和字段接口(interface )函数式&#xff08;SAM&#xff09;接口可见性修饰符扩展数据类(data class)密封类泛型嵌套类与内部类枚举类对象表达式与对象声明类型别名内联类(inline class)委托委托属性 二 类与继承 类继承(open override )抽象类(abstract)…

MySQL去重中 distinct 和 group by 的区别

今天在写业务需要对数据库重复字段进行去重时&#xff0c;因为是去重&#xff0c;首先想到的是distinct关键字。于是一小时过去了。。。。&#xff08;菜鸟一个&#xff0c;大家轻点骂&#xff09; 我把问题的过程用sql语句演示给大家演示一下 首先我使用的是mybatis-plus&am…

数据结构之哈希表

文章目录 一、概念二、哈希冲突三、如何解决哈希冲突&#xff1f; 1.哈希函数设计2.负载因子调节3.闭散列4.开散列&#xff08;哈希桶&#xff09;四、模拟实现哈希桶总结一、概念 顺序结构以及平衡树中&#xff0c;元素与其存储位置之间没有对应的关系&#xff0c;因此在查找一…

C++多态学习笔记

C多态学习笔记一、多态概述二、多态的作用三、多态发生的三个条件四、多态实现的原理五、接口的定义六、模板方法模式七、虚析构函数和纯虚析构函数7.1 虚析构函数7.2 纯虚析构函数八、重写重载重定义九、父类引用子类对象一、多态概述 同一个操作作用于不同的对象&#xff0c;…

2014年848数据结构真题复习

求k频度K0; for&#xff08;i1;i<n;i&#xff09; 假如是1——8&#xff0c;执行了9次&#xff0c;8次有效&#xff0c;最后一次无效for&#xff08;ji;j<n;j&#xff09;k 我的理解&#xff1a;假设n为8我们看k频度实际上就是看内圈for的有效循环次数第一轮是1——8 八次…

基础算法 第七课——归并排序

文章目录导言归并排序的概念步骤说明逐步分析STEP1STEP2STEP3STEP4STEP5STEP6STEP0总结导言 这&#xff0c;是一篇现学现卖的文章。因为&#xff0c;我根本没学过归并排序。所以&#xff0c;这篇文章&#xff0c;绝对能让您学懂归并。如果不懂&#xff0c;那我就再学一遍&…

KVM Forum 2022应该关注的话题

1. QEMU 和 KVM 自动性能基准测试 QEMU & KVM Automated Performance Benchmarking SUSE - Dario Faggioli, SUSE SUSE正在开发一个框架&#xff0c;用于对虚拟化工作负载进行自动性能基准测试。它是围绕着MMTests&#xff08;已经在Linux内核社区使用了几年&#xff09;建…

2022-Java 后端工程师面试指南 -(SSM)

前言 种一棵树最好的时间是十年前&#xff0c;其次是现在 Tips 面试指南系列&#xff0c;很多情况下不会去深挖细节&#xff0c;是小六六以被面试者的角色去回顾知识的一种方式&#xff0c;所以我默认大部分的东西&#xff0c;作为面试官的你&#xff0c;肯定是懂的。 上面的…

Mybatis之foreach

文章目录一、foreach属性二、使用foreach批量删除(法一)1.接口2.mapper文件3.测试类4.运行结果三、使用foreach批量删除(法二)1.mapper文件四、使用foreach批量插入1.接口2.mapper文件3.测试类4.运行结果一、foreach属性 collection&#xff1a;指定数组或者集合 item&#xf…

FPGA时序约束01——基本概念

前言1. 越来越多的时序问题 随着FPGA时钟频率加快与其实现的逻辑功能越来越复杂&#xff0c;开发者遇到的问题很多时候不再是代码逻辑的问题&#xff0c;而是时序问题。一些开发者可能有这样的经历&#xff0c;一个模块在100MHz时钟运行没问题&#xff0c;而将时钟频率改为150…

【仿牛客网笔记】 Spring Boot进阶,开发社区核心功能-事务管理

添加评论中会用到事务管理。 解决的程度不同&#xff0c;层级不同。我们一般选择中间的级别。 选择时既能满足业务的需要&#xff0c;又能保证业务的安全性&#xff0c;在这样的前提下我们追求一个更高的性能。 第一类丢失更新 图中是没有事务隔离的情况 第二类丢失更新 脏…

需求工程方法的学习

作业要求&#xff1a;总结尽可能多的需求工程的方法和技术&#xff0c;要求归纳总结各种方法的适用场景、优缺点等。说明&#xff1a;其中需求工程包括需求获取、需求分析、规格说明、验证、管理等。只要是用于需求工程相关的技术和方法都可以算。 软件需求工程划分为需求开发…

Linux 中 man手册中函数后面括号数字释义

文章目录简介参考资料简介 Linux手册页项目记录了用户空间程序使用的Linux内核和C库接口。 用man手册查看系统命令&#xff0c;系统调用&#xff0c;glibc函数时&#xff0c;会发现其后面会有个括号&#xff0c;括号里面是一个数字&#xff0c;比如&#xff1a; access(2), …

一文了解Spring框架

目录 SpringBoot VS Servlet Spring是什么&#xff1f; loC&#xff1a;控制反转 DI 创建一个Spring项目 创建一个Spring IOC容器 注册Bean对象 获取Bean对象 注意事项&#xff1a; 类注解 为什么有这么多类注解&#xff1f; 注册与注入 方法注解 Bean Spr…

《R语言数据分析》2022-2023第一学期课程分析报告

1 (30分)基本操作题 1.1 (10分) 请写出下面问题的R代码 1.(2分)安装并加载gtools扩展包。 install.packages(“gtools”) library(gtools) 2.(2分)查看当前已经加载的所有包。 as.data.frame(installed.packages())$Package 3.(2分)查看gtools包的帮助网页。 ?gtools…

《清单革命》内容梳理随笔

《清单革命》内容梳理&随笔 起 书即是将四散的知识按照逻辑和网状联系编排起来。你应该这样去读&#xff0c;高屋建瓴、层次有秩、显得貌似自己有经验&#xff08;褒义&#xff09;的读&#xff0c;读出一些感想和方法论&#xff0c;无论是读出书里的还是书外的&#xff…

【MySQL高级】SQL优化

5. SQL优化 5.1 大批量插入数据 环境准备 &#xff1a; CREATE TABLE tb_user_2 (id int(11) NOT NULL AUTO_INCREMENT,username varchar(45) NOT NULL,password varchar(96) NOT NULL,name varchar(45) NOT NULL,birthday datetime DEFAULT NULL,sex char(1) DEFAULT NULL,…

【数据库】实验五 数据库综合查询|多表查询、聚集函数、orderby、groupby

文章目录参考文章本文在实验四的基础上增加了orderby、聚集函数、groupby、多表查询的知识点&#xff0c;相较于上一次实验的难度变大了&#xff0c;嵌套表达更多了&#xff0c;逐渐开始套娃…… 其实可以看成一个偏正短语来拆分&#xff0c;再写成SQL语句&#xff0c;比如查询…