类和对象(下)


  • 点击表格内对应链接跳转对应内容⬇️⬇️⬇️
作者主页 吃透C语言专栏 数据结构 Gitee仓库 C++ Linux 算法

文章目录

一,初始化列表(构造函数)

情景模拟:之前学过的所有情况,包括我们写的日期类,都是成员变量为内置类型的情况,有关于成员变量是自定义类型的情况,只有当不显示写构造函数的时候,而且成员变量出现自定义类型,编译器生成的默认构造函数会去调用自定义类型的默认构造函数,而调用编译器默认构造函数的前提是,类中没有显示定义的构造函数。但是如果出现这样的情况,类中成员有自定义类型,而这个自定义类型中显示定义了一个构造函数,但是这个构造函数是需要传参的带参数的构造函数,不是默认构造函数(无需传参就可以调用:无参构造,全缺省构造,编译器的默认构造),就导致了自定义类型中没有默认构造了,而外部的类中没有构造函数,这种情况下,类中会自动调用编译器的默认构造函数,但是编译器的默认构造函数对自定义类型的处理是调用自定义类型自己的默认构造,但是这个自定义类型以及显示定义了带参构造,所以编译器就会报错,自定义类型没有默认构造,而我们一直都没有说,给自定义类型中带参构造传参的方法,给成员变量赋值的方法,以及当类中显示定义了构造函数,但是函数体中什么都没有写,那成员变量如果是自定义类型的话,没有默认构造的前提下,显示定义的构造也没对自定义类型进行处理,那自定义类型就没人管了吗?下面要引入一个概念叫做初始化列表


  • 定义:初始化列表是写在构造函数头后面、函数体{}之前,以冒号:开头的一段成员初始化代码
c 复制代码
class day
{
public:
	day(int day)
	{
		_day = day;
		std::cout << _day << std::endl;
	}

private:
	int _day;
};

class Date
{

public:
	Date(int n)
		:_year(1970),
		_month(8),
		_d(n)//初始化列表,(:)冒号开始 (,)逗号分隔 没有结束符号,到{ }自动结束
	{

	}
	void Print()
	{
		std::cout << _year <<" " << _month <<" ";
	}
private:
	int _year;
	int _month;
	day _d;
};

int main()
{
	Date d1(8);
	d1.Print();

	ret

  • 初始化列表:1:冒号开始 2:逗号分隔3:到{}自动结束4:写在类名和函数体中间5:传任何值必须用()
  • 初始化列表中会给对应的内置类型初始化,如:_year(1970)就会给_year初始化1970,而自定义类型_d(n)则会给_d这个对象的类中的带参构造函数传n

  • ❗❗❗要点
知识点分类 内容说明
初始化列表阶段 才是真正的【初始化】(创建对象的时候直接给初值)
构造函数大括号内部 是【赋值】(成员已经存在了,之后修改它的值)
✅三种成员必须使用初始化列表初始化(不能在构造函数体内赋值) 1. const修饰的成员变量(const变量定义时必须初始化,之后不能赋值) 2. 引用类型成员(引用必须在定义时绑定对象,之后不能改绑定) 3. 没有默认构造函数的自定义类类型成员(对象成员)
⚠️踩坑点:成员初始化顺序(致命考点) 成员的初始化顺序不是初始化列表书写的顺序!!而是成员在类中声明的顺序!!
默认初始化列表(编译器隐式行为) 1.完全不手写构造函数:编译器自动生成默认构造函数 2.写了构造函数,但是没有手写初始化列表 ,编译器依然会隐式初始化成员: •内置类型(int等):不初始化,值是随机垃圾值 •自定义类成员:调用它的默认无参构造

1 初始化列表初始化,函数体赋值

  • 1:初始化列表是给成员初始化的地方,(通过类型这个模板实例化对象的时候编译器分配了空间,这时候,初始化列表中()内的值就给有空间了的成员初始化),2:而函数体中是给成员赋值的地方,这时候成员已经存在了,但是没有被初始化,而函数体中的操作则是给没有被初始化的成员赋值

2 必须初始化的三个成员

  • 2:初始化列表才是构造函数中真正的初始化的地方,那C++中有些东西是必须初始化的,而这些东西就必须在初始化列表中,而不是在函数体中未初始化而直接赋值。1:const成员 2:引用成员 3:没有默认构造的自定义类型成员

c 复制代码
class day
{
public:
	day(int x)
	{
		_month = x;
	}

private:
	int _month;
};

class Date
{
public:
	Date(int& y1)
		:_x(100),
		_y(y1),
		_day(3)
	{

	}

private:
	const int _x;
	int& _y;
	day _day;
};

int main()
{
	int yy = 10;
	Date d1(yy);

	return 0;
}
  • 1:const成员声明const int _x的时候的时候,const成员只能初始化,不能赋值,是只读不可被修改的,所以如果在初始化列表中没有给const成员初始化的话 ,就没地方给const成员初始化了,函数体中的操作是给const成员赋值,而const成员只能初始化,不能被修改,不能被赋值

  • 2:引用成员 int& _y 引用的意思就是给别的对象取别名,如果不初始化的话,那这个引用是没有任何意义的,所以引用必须初始化,而int yy = 10的变量yy传引用给构造函数y1是yy的引用,这时候的y1就代表了yy而y1又作为参数传给了_y这时候_y就是yy的引用了

  • 3:没有默认构造的自定义类型成员自定义类型成员只有在显示定义构造函数的时候,编译器就不会生成编译器自己的默认构造函数,而如果显示定义构造函数是无参构造,或者全缺省构造,这些都属于默认构造函数,当外部类中有构造函数的时候,在初始化列表识别到自定义类型成员的时候,会自定调用自定义类型成员中的默认构造函数,但是自定义类型中没有默认构造,也就是没有无参或者全缺省构造,而是显示定义了一个带参构造,这时候,你显示定义了一个带参构造导致了自定义类型成员中没有任何默认构造,所以就必须手动在初始化列表中传参给带参构造和如果外部类中没有写构造函数,编译器自动调用自定义类中的构造,结果发现显示定义了一个带参构造导致,自定义类中没有任何默认构造,只能手动传参给自定义构造这种没有默认构造的自定义成员就必须初始化,否则自定义类型成员无法初始化,即没有默认构造,带参构造又不初始化传值


3 初始化列表顺序要对齐声明顺序

c 复制代码
class A
{
public:
	A(int a)
		:_a1(a)
		,_a2(_a1)
	{
		std::cout << "_a1 = " << _a1 << std::endl << "_a2 = " << _a2 << std::endl;
	}
private:
	int _a2;
	int _a1;
};


int main()
{
	A a(1);
	return 0;
}
  • 类型声明顺序是先_a2 后_a1,但是初始化列表顺序是先_a1(a) 后_a2(_a1)但是初始化结果_a = 1和_a2 =随机值因为初始化列表的初始化顺序是按照声明顺序初始化的,所以先初始化_a2再初始化_a1,而初始化_a2(_a1)的时候,_a1还未被初始化,还是随机值,所以把_a1作为_a2的初始值,所以_a2被初始化成随机值,而_a1(a)则被初始成a

总结要点

  • 总结:⬇️⬇️⬇️
序号 要点
1️⃣ 初始化列表标准格式:函数名和函数体之间,冒号开始,逗号分隔,到函数体自动结束
2️⃣ const成员和引用成员和没有默认构造的自定义类型成员必须初始化
3️⃣ 初始化顺序是按照声明顺序初始化
4️⃣ 成员只能初始化一次,初始只能发生一次
5️⃣ 成员变量的缺省值是给初始化列表用的
6️⃣ 只要显示写了构造函数,无论有没有显示写初始化列表,编译器最先执行初始化列表阶段,如果不显示写初始化列表,编译器会生成隐式的初始化列表,而隐式初始化列表,对于内置类型成员不做处理,对自定义类型成员,则调用自定义类型成员的默认构造函数

  • 5️⃣6️⃣代码演示
c 复制代码
class Day
{
public:
	Day()
	{   
		std::cout << "自动调用自定义类型的默认构造" << std::endl;
	}

private:
	
};

class Date
{
public:
	Date()
	{
		std::cout << "_year = " << _year << std::endl;
	}
private:
	int _year = 1;
	Day d2;
};

int main()
{
	Date d1;

	return 0;
}

  • Date类中有内置类型成员int _year = 1和 自定义类型成员Day d2,有构造函数Date但是没有显示初始化列表,这时候编译器生成隐式初始化列表,而编译器对于构造函数先执行初始化列表的内容再执行函数体,隐式初始化列表对于内置类型是不管的,但是_year = 1这里的内置类型_year有缺省值,而缺省值本身就是给初始化列表用的,所以_year被初始化成1,而初始化列表对于自定义类型Dat d2则会调用自定义类型自己的默认构造,所以调用Day打印,最后执行完了初始化列表,才去执行函数体,所以先打印话,再打印_year = 1

  • 补充:const int a = 1;作为成员的话,=1缺省值最后给到的是初始化列表,所以隐式初始化列表,会给const int a 初始化成1,所以编译器不会报错,const成员是必须初始化的,而没有显示初始化列表,但是隐式初始化列表通过缺省值给const成员完成了初始化

  • 补充:1:类中没有显示定义构造:编译器会生成默认构造,而默认构造对内置类型不处理,对自定义类型则调用自定义类型自己的默认构造。2:类中显示定义了构造,这时候有初始化列表,编译器是最先执行初始化列表的,而初始化列表不显示定义的话,初始化列表对于内置类型不处理,对于自定义类型则调用自定义类型自己的默认构造。所以自己没显示构造的时候,默认构造会处理内置类型和自定义类型,显示定义构造的时候,最先执行的初始化列表,如果不显示定义初始化列表,对于内置类型不处理,对于自定义类型调用自定义类型自己的默认构造,所以无论哪种情况,自定义类型都有人管的,因为有默认构造,但是如果写了一个带参构造导致没有默认构造自己又不传参就会报错,导致无法初始化

构造函数重点总结

  • 前面的都是错的,现在才是正确的,编译器生成的默认构造函数,存在隐式初始化列表,隐式初始化列表对于内置类型成员,不会去处理,对于自定义类型成员,则调用自定义类型成员自己的默认构造,这都是初始化列表干的事情,而默认构造函数的函数体中,啥都没有,什么都不会干,之前说过的默认构造函数的行为,确实是默认构造函数的行为,但是不是函数体的行为,是初始化列表的行为,而初始化列表本来就是构造函数的一部分,所以才会说默认构造函数的处理方法,其实都是默认构造函数中的隐式初始化列表干的事情,而函数体啥都没有

  • 缺省值多种赋值法,只要有值就行

二,explicit

1 隐式类型转换(单参数)

  • C++中内置类型可以隐式转化成自定义类型,原理是当检测到需要的类型是自定义类型,而传的参数是内置类型,这样的话,自定义类型中的单参构造,就会将内置类型作为参数,单参构造接收以后通过初始化列表,把内置类型作为参数生成由内置类型初始化的同类临时对象,而谁需要这个由类单参构造构造成的临时对象,就接收即可。这就是内置类型隐式转化成自定义类型,本质是通过,那个自定义类型中的构造函数把这个内置类型作为参数进行初始化列表构造成临时同类对象

c 复制代码
//单参数构造
//隐式类型转换为自定义类型
//编译器遇到连续构造+拷贝构造 ->优化为直接构造
class A
{
public:
	A(int a)
		:_a(a)
	{
		std::cout << "A(int a)" << std::endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		std::cout << "A(const A& aa)" << std::endl;
	}
private:
	int _a;
};


int main()
{
	A aa1(1);//类实例化对象自动调用构造

	A aa2 = aa1;//拷贝构造

	A aa3 = 3;
	//int类型3要变成A类 aa3,类型转换,触发隐式类型转换,
	//3先进行构造函数构造,构造成A类临时对象,再由临时对象A类(同类)进行拷贝构造A aa3 = A(对象)
    //但是编译器会进行优化,连续的构造+拷贝构造 ->直接构造

	const A& aa4 = 3;
	//3会先触发隐式类型转换->构造成A类对象(调用单参数构造)->形成临时对象(具有常性)-> 所以引用必须加const
	
	return 0;
}

  • 解析:1️⃣A aa1(1)是类实例化对象自动调用构造函数 2️⃣A aa2 = aa1;是拷贝构造,aa2初始化成同类对象aa1,调用拷贝构造 3️⃣ A aa3 = 3 中=号是赋值的意思,A aa3是A类实例化,初始化的意思,那A aa3 = 构成的语法叫做拷贝构造,但是操作数是内置类型3而A类对象aa3需要的是同类对象,这时候就会发生隐式转化,通过A类中的单参构造,初始化列表将3构造成A类的临时同类对象(具有常性),然后A aa3 = 同类临时对象进行拷贝构造,同类临时对象符合参数标准,但是3是先进行构造函数构造成A类,在进行同类对象拷贝构造,而连续进行 构造 + 拷贝构造,编译器会直接优化成直接构造,在编译器眼中内置类型3需要作为同类对象拷贝初始化aa3,需要构造成同类再拷贝初始化,最终的目的就是拷贝成由3初始化的同类对象,那还不如直接拿3作为aa3的参数调用构造直接初始化,更方便更快达到目标4️⃣const A& aa4 = 3中const A& aa4 =的意思是引用,给同类取别名,但是要求肯定要是同类而3是内置类型,我们需要的是同类对象,3怎么才能变成我们的同类对象A类呢,这时候发生隐式转化,将3作为参数传给A类的单参构造初始化列表通过3初始化成同类临时对象(具有常性)这时候A& aa4 = 就可以引用同类对象了,但是这个同类对象是具有常性的,所以要引用需要加const


  • 解析:Stack 类实例化对象st,需要调用成员函数Push,Push的形参是A类的引用,所以可以A类实例化一个对象a1,然后作为Push的参数,但是我们可以借助隐式转换,当一个需要自定义类型,并且他需要的自定义类型有单参数构造,这时候我们可以传内置类型,因为内置类型会发生隐式转换,作为A类构造函数的参数构造成同类临时对象,然后进行引用,临时对象具有常性所以引用加const。

2 隐式类型转换(多参数)

  • 单参构造会触发隐式类型转换,多参构造也是可以的,但是格式规定好了,必须是{}括起来
c 复制代码
class A
{
public:
	//单参构造
	A(int a)
		:_a(a)
	{
		std::cout << "A(int a)" << std::endl;
	}
    //多参数构造
	A(int a1, int a2)
		: _a(0),
		_a1(a1),
		_a2(a2)
	{
		std::cout << "A(int a1, int a2)" << std::endl;
	}

	//拷贝构造
	A(const A& aa)
		:_a(aa._a )
	{
		std::cout << "A(const A & aa)" << std::endl;
	}

private:
	int _a;
	int _a1;
	int _a2;
};

int main()
{
	A aaa1(1, 2);
	A aaa2 = { 1,2 };
	const A& aaa3 = { 1,2 };

	return 0;
}
  • 1️⃣A aaa1(1,2)是实例化对象自动调用构造函数。2️⃣A aaa2 = {1,2}中,A aaa2 是初始化,而 = 是赋值的意思,组合就成了A aaa2需要同类对象进行拷贝构造,而{1,2}是内置类型,不是同类对象,但是类中的构造函数可以把{1,2}作为参数进行构造临时对象,而满足拷贝构造需要同类构造的需求。所以先通过A类中的构造,构造成同类对象,再进行拷贝构造,但是连续 + 构造 + 拷贝构造,编译器会自动优化成构造3️⃣A& aaa3 = 是引用的意思,引用必须是同类对象才能引用,而传过来{1,2}内置类型,需要的是A类对象,那就调用A类中的构造函数把{1,2}作为参数,构造成A类临时对象,然后有了同类对象以后,再进行引用,但是临时对象具有常性,所以需要加const

c 复制代码
A aaa2 {1,2};

const A& aaa3 {1,2};
  • 可以把=去掉,也支持

隐式转换总结

  • 隐式类型转换把内置类型转换为自定义类型,核心逻辑就是当这个地方,比如拷贝构造,引用,或者函数形参,需要的是同类对象,但是你传的是内置类型,我们就可以隐式把内置类型转换成同类,也就是自定义类型,因为同类本身的初始化,创建,就是通过构造函数的初始化列表创建的,而我需要同类,你给的是内置,内置就可以调用同类中构造函数,初始化列表初始化成同类的临时对象,然后再提供给拷贝构造,引用,或者函数形参。所以本质就是任何对象都是初始化列表初始化成的对象,通过初始化才产生了对象,而单参和多参都是需要传参才能初始化成对象,而我们需要同类对象的时候,你给一个内置类型,我们就可以把你给的内置类型通过我们类中的构造函数初始化成对象,这样就满足了我们需要同类对象的需求

  • 一句话,我们需要同类对象,对象都是通过类中构造函数初始化列表初始化成的,你给的内置类型,刚刚好可以作为参数,让我们类中的构造函数初始化列表初始化成同类对象,来满足我们的需求。

序号 要点
1️⃣ 单参构造在需要自定义类型的条件下,内置类型会被类中的构造函数初始化成同类对象然后使用
2️⃣ 被类中构造隐式初始化成的同类临时对象具有常性,如果是引用需要加const
3️⃣ 多参构造也支持隐式类型转换,但是必须把内置类型用{ }括起来
4️⃣ 连续的构造+ 拷贝构造会优化成直接构造
5️⃣ 如果不想触发隐式转换,可以在构造函数前加explicit

  • 4️⃣连续的构造+拷贝构造会直接优化成构造,拷贝构造本身就是同类作为参数构造,是构造的重载,而如果要进行构造再进行拷贝构造,流程是内置类型参数->构造->构造作为参数->拷贝构造,还不如直接把最开始的参数直接构造,因为拷贝构造也是构造,就是拷贝同类对象,我现在不拷贝同类对象了,直接拿参数变成同类对象就行了

c 复制代码
//单参构造
explicit A(int a)
	:_a(a)
{
	std::cout << "A(int a)" << std::endl;
}
//多参数构造
explicit A(int a1, int a2)
	: _a(0),
	_a1(a1),
	_a2(a2)
{
	std::cout << "A(int a1, int a2)" << std::endl;
}
  • explicit修饰在构造前,这个构造就不会在需要同类对象的时候,拿自定义类型隐式转换成同类对象来用了

三, static成员

1 static 修饰成员变量

序号 要点
1️⃣ static修饰的成员变量,更准确来说是,static修饰成员声明,成员的定义必须在类外
2️⃣ 成员内存空间变到了静态区,不在类中了,静态区成员生命周期和程序一致
3️⃣ 依然受到public和private的访问控制
4️⃣ 不属于对象空间,在静态区,所以sizeof(对象)不包括static成员
5️⃣ 和成员函数类似,所有对象共同使用同一份
6️⃣ 不可以只声明不定义
7️⃣ 声明的时候不能给默认值,因为只能在类外定义的时候给值

c 复制代码
class A
{
public:
	static int _sum;
private:
	int _a;
};
int A::_sum = 100;

int main()
{
	A a1;
	std::cout << A:: _sum << std::endl;
	std::cout << a1._sum << std::endl;
	std::cout << sizeof(a1) << std::endl;

	return 0;
}
  • 1:public修饰的 static int _sum ,空间已经不在对象中了,在静态区,生命周期和程序相同,声明在类中,就是类的成员,而类的成员初始化在初始化列表,但是static成员在静态空间,空间都不在类中,所以初始化列表无法初始化,而默认值就是给初始化列表使用的,所以声明static成员的时候不能给默认值,因为类中不能初始化,static成员定义在类外,在类外定义的时候初始化。如int A::_sum = 100,也正因为static成员空间不在类中,所以sizeof(a1)的结果是4

  • 2:_sum空间不在类中,但是依然受到类域和private的限制,如果是private修饰的static成员,类外依然无法访问,但是如果是public修饰的static,只受类域限制,所以只要指定类域就可以访问public:static成员如,A:: _ sum,因为static在静态区,是公共的。也可以用a1._sum访问

  • 总结:static成员变量可以这样理解,空间到了静态区,是公共的了,所有对象共用一份,但是这个名字,依然受到了类域和private的限制,只要指定类域,就可以使用,只要类实例化对象,就可以使用,因为空间不在类中,所以要在类外定义,类中只能定义空间在类中的成员

  • 如果私有static成员变量的,被private限制住,只能通过成员函数来访问了

2 static 修饰成员函数

序号 要点
1️⃣ *static成员函数没有tihs指针
2️⃣ 只能访问static成员变量,不能访问非静态的成员变量
3️⃣ 属于类,不属于对象,因为没了this指针,只受类域限制

  • this指针是连接对象和对应成员变量的唯一渠道,没有了this对象各自的成员变量无法匹配,所以static修饰的成员函数也就无法访问成员变量了

  • this指针是连接对象和对应成员变量的唯一渠道,同时static和对象没有关系了,但是作为成员函数,依然受到类域限制,所以对象依然可以调用,只是没有this指针罢了,无法访问自己对应的成员,但是可以访问公有成员,如static修饰的成员,这种静态区的公共成员不是对象专属的,是公有的,不需要this指针指定,所以可以访问

c 复制代码
class A
{
public:
	static int  Get()
	{
		return _sum;
	}
		
	
private:
	static int _sum;
	int _a;
};
int A::_sum = 100;

int main()
{
	A a1;
	std::cout << a1.Get() << std::endl;
	std::cout << A::Get() << std::endl;
	
	return 0;
}
  • static int Get()是static修饰的成员函数,没有this指针,只受类域控制,所以和对象没有关系,因为没this指针,也无法访问成员,没有专属属性,但是可以访问公共成员,如static修饰的变量_sum,而只受类域控制,所以A::指定类域就可以调用A::Get(),但是依然是成员函数,所以对象依然可以调用a1.Get()

  • 总结:static成员函数只要记住没了this指针就行,没了this,和对象没关系了,但是因为是成员函数,所以依然可以被对象调用,没了this,则无法访问非const成员变量,因为无法通过对象匹配到对应非const成员变量,但是可以访问公共的const成员变量(公共成员变量)

四,友元

1 友元函数

规则 说明
友元非成员函数访问权限 在类中用 friend 声明的非成员函数,可以访问该类的 private 和 protected 成员。
不属于类成员 它不是类的成员函数,没有 this 指针。
调用方式 调用方式与普通函数完全相同。
声明位置无影响 不受访问说明符影响:写在 public、private 还是 protected 区域都一样。
友元单向性 友元关系是单向的:你声明我是友元,不代表我也是你的友元。

c 复制代码
//友元函数
class A
{
	friend void Print(const A& a);
public:
	A()
		:_x(10)
	{

	}
private:
	int _x;
};

void Print(const A& a)
{
	std::cout << a._x << std::endl;
}

int main()
{
	A a1;
	Print(a1);
	return 0;
}
  • 全局函数Print()想要使用A类中的成员_x,但是有类域+private限制,私有成员变量只能在A类中访问,外部无法访问A类中的成员,但在A类中友元声明全局函数Print(),全局函数Print()就可以使用A类中的成员变量的,因为相当于Print()在A类中,变成了A类中的成员函数

2 友元类

规则 说明 结论
友元的作用 在类 A 中声明 friend class B; B 的所有成员函数 都可以访问 A 的 private 和 protected 成员
单向性 B 是 A 的友元 A 不一定是 B 的友元(友元只单向生效)
不传递 A 友元 B,B 友元 C C 不能访问 A 的私有成员
不被继承 B 是 A 的友元,C 继承 B C 不是 A 的友元

c 复制代码
//友元类

class A
{
	friend class B;
public:
	A()
		:_a(10)
	{

	}

private:
	int _a;
};

class B
{
public:
	B(const A& a)
		:_b(100)
	{
		std::cout << a._a<< std::endl;
	}

private:
	int _b;
};


int main()
{
	A a1;
	//想让B类可以访问A类的成员
	B b1(a1);

	return 0;
}
  • **A类中声明friend class B,代表声明B类是A类的友元,B类可以访问A类中的所有成员,所以A类实例化对象a1,传入B类构造函数中,就可以通过对象a1在B类中访问A类的私有成员_a,其实A类的私有成员_a本身只能在A类中访问,在类外的任何形式都无法进行访问的,但是B类是A类的友元,相当于B类在A类中了,在A类中,自然可以访问A类中的私有成员 **

五,内部类

  • 如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。

  • 总结:一个类定义在另一个类的内部,内部的这个类就叫作内部类
序号 要点
1️⃣ 内部类和外部类是相互独立的,平行的
2️⃣ 内部类是外部类的天生友元
3️⃣ 友元关系是单向的,不是双向的

c 复制代码
class A
{
public:
	class B//B是A的天生友元,毕竟B都在A里面了
	{
	public:

	private:
		int _x;
		int _y;
	};

private:
	int _x;
};

int main()
{
	std::cout << sizeof(A) << std::endl;
	std::cout << sizeof(A::B) << std::endl;
}
  • class B 在 class A 中定义,则B是A的天生友元类,B可以访问A的成员

  • B受到A的类域控制,所以需要指定类域才能访问,但是仅仅是类域控制,B和A是平行关系,相互独立,所以sizeof(A)的大小是4,sizeof(A::B)的大小是8,如果B是A的private的话,则类外都无法访问B类

六,匿名对象

  • 没有名字对象,不写类名,所以只能就地使用,生命周期只在当前这一行,即用即销毁
c 复制代码
class A
{
public:
	A(const int& a)
		:_x()
	{
		std::cout << "构造函数" << std::endl;
	}
	~A()
	{
		std::cout << "析构函数" << std::endl;
	}
private:
	int _x;
};

int main()
{
	A a1(1);
	A(2);
	A a3(3);
	A a4(4);

	return 0;
}
  • A(2)是匿名对象,即用即销毁

七,拷贝对象时的一些编译器优化

序号 要点
1️⃣ 一个表达式中,连续的构造+拷贝构造可能会优化成直接构造
2️⃣ 连续的拷贝构造+拷贝构造可能会优化成一个拷贝构造
c 复制代码
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);
 // 一个表达式中,连续构造+拷贝构造->优化为一个构造
 f1(A(2));
 cout << endl;
 // 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
 A aa2 = f2();
 cout << endl;
 // 一个表达式中,连续拷贝构造+赋值重载->无法优化
 aa1 = f2();
 cout << endl;
 return 0;
}
  • 有些更加高级的优化可能连函数栈帧都没有,高等级的优化,经过语法分析,避免多余的步骤,一步到位,减少中间因为创建临时对象的消耗

  • 补充:👇🏻👇🏻👇🏻
c 复制代码
void f()
{
static A aa3;
}
A aa0;
int main
{
   A aa1;
   A aa2;
      f();
      return 0;
}

序号 要点
1️⃣ A aa1和A aa2中aa1先定义后析构,aa2后定义先析构
2️⃣ aa3局部静态构造发生第一次调用的时候,因为只有调用函数,才会执行构造,而且只构造一次,因为static成员只初始化一次
3️⃣ 全局对象在进入main函数之前就完成构造了
  • 点击表格内对应链接跳转对应内容⬇️⬇️⬇️
作者主页 吃透C语言专栏 数据结构 Gitee仓库 C++ Linux 算法

相关推荐
傲世仙尊1 小时前
拷贝构造-传值传参的连锁反应与隐式函数的三个局限
c++
垆边人似月.2 小时前
华为机试题 :最大数
c++·算法·华为
方便面不加香菜2 小时前
C++ 关于多态的知识点讲解
开发语言·c++
jimy13 小时前
C++ 类成员函数的内联定义(inline)详解
开发语言·c++
sanqima3 小时前
CreateThread、_beginthreadex、AfxBeginThread三种线程创建的区别与联系
c++·tls·线程创建
sanqima3 小时前
依赖TLS和模块状态的MFC类
c++·mfc·tls·module state
All for pursuit.3 小时前
【动态规划-11】279.完全平方数
数据结构·c++·算法·leetcode·动态规划
瑶山4 小时前
Windows + WSL2 Ubuntu + VS Code配置C++开发环境
开发语言·c++·windows·vscode·wsl
小米里的大麦4 小时前
19 C、C++ 操作 MySQL
c语言·c++·mysql