
大家好啊,我是云泽Q,欢迎阅读我的文章,一名热爱计算机技术的在校大学生,喜欢在课余时间做一些计算机技术的总结性文章,希望我的文章能为你解答困惑~


成员变量的整体定义也可以理解为是d1对象的整体定义 补充一下:声明是不开空间的,定义才会开空间,初始化列表是这三个特殊成员变量定义的地方
初始化成员列表也解决了之前的另一个问题,之前写两个栈实现一个队列的代码时,队列是不用写构造的,因为队列里面两个栈会自动调用栈的构造(只能调用默认构造),但这是一个理想的情况,若栈没有默认构造就会报错
这时候队列就必须自己去显式的写构造,就要借助初始化列表了



这里调试的时候也比较有意思,可以自己下去试一试
再展示一个缺省值奇怪的写法,Time _t是自定义类型,初始化时没有默认构造理应要在初始化列表初始化,但是它也可以给缺省值来达到一个初始化的效果

const也类似,给了缺省值就会用缺省值在初始化列表初始化 甚至缺省值还可以调函数,给个表达式 补充:给缺省值和函数参数一样。一般只能给常量对象,或者是全局对象

下面给两个缺省值和在初始化列表初始化混着用的场景

这个场景下不加size就可以不写MyQueue的构造或初始化列表,初始化的时候直接会去调用Stack的默认构造,若加了size,就要加MyQueue的构造了,也可以給size加一个缺省值

这里给_a开辟了一些空间,但是malloc也有可能失败,需要检查,初始化列表就做不了检查的事情,只有函数体才能做。其次,初始化还有可能在开空间后给数组赋值,或者使用memset将数组中的内容全部初始化为0。所以有些场景下初始化列表也不能解决所有问题
最后来看一道题目

正常情况下,如果初始化列表显式初始化了就和缺省值没什么关系了,B,E,F就排除了 想解决这个题还需要补充一个点
这里a初始化a1不会先执行,因为声明的时候a2在前面,所以会先会用a1初始化a2,但a1是随机值(对象被整体定义出来的时候空间已经被开出来了,初始化列表只是界定定义的地方,但是a1现在没有值是随机值),所以a2也是随机值了,所以该题选D


这是内置类型和内置类型之间的转换,类型之间有一定关联才能转换

到这再看C++这里的类型转换

这里A类型对象的初始化有两种写法,一个是语法上调用构造,另一个就是隐式类型转换,这里能进行隐式类型转换的原因就是其构造函数用整型做参数构造了A对象
其语法逻辑上1也是先构造一个临时对象,临时对象再拷贝构造给a2

下面第一个就是正常的引用,第二个引用的是隐式类型转换中间产生的临时对象

这样设计的意义在下面两个场景体现:

C++中类类型在做函数形参的时候就不建议用传值传参了,传值传参会调用拷贝构造是一种性能浪费,要尽可能使用引用,但是普通的引用传不了const对象,会造成权限放大,所以在使用时会形参的引用要尽量加const,但是除此之外还有一个原因,这样形参是A类型的const引用,不仅可以传A类型,也可以传1
另一个场景就是如果在数据结构中存一个自定义类型(类类型),现在往栈中插入A类型的两个数据,也可以用隐式类型转换了


第一种写法就是自己构造一个有名对象,再将有名对象传过去,第二种写法本质上a引用的不是3,而是上面支持用int去构造A
void func(const A& aa = 1)
{
}这里还可以直接给缺省值更加方便

后面写到STL库的时候会细节讲一个类,string。使用它可以达到用字符串初始化的目的,若在栈里面存一个字符串,参数就改为string,也可以使用隐式类型转换

下面再说一说编译器的优化

由于C++是一门比较注重效率的语言,所以编译器会在这里进行优化,这里构造加拷贝构造会有性能的浪费,所以编译器处理会把这两个步骤省略掉,用2直接构造a2,这个优化是在C++11之后规定的
第三个没有打印"A(const A& aa)"的原因是引用不是对象,只是对已有对象的 “别名”。绑定引用的过程不会创建新的 A 对象,也就不需要 “用已有对象拷贝构造新对象”,因此完全不会触发拷贝构造函数 A(const A& aa)

这就有人要问了,a2这串代码不是优化为直接构造吗
但这串代码的语法编译逻辑还是2转换为A,A再去拷贝构造,然后再去优化,所以会报错
多参数隐式类型转换也可以,只不过写法稍有改变

注意,上面A a4 = (1, 1);的写法运行的是第一个的构造逻辑,因为括号内是一个逗号表达式,逗号表达式取逗号右边的值传过去,如果没有单参数的构造函数,编译就会报错
正确写法:
A a3(1, 1);//A(int a1, int a2)
A a4 = { 1, 1 };//A(int a1, int a2)
const A& ref2 = { 1, 1 };//ref2引用的是临时对象
Stack st1;
st1.Push(a4);
st1.Push({2,2});这里最重要的就是要有对应的构造进行支持

默认情况下A,B对象没有关联是不能转换的,哪怕用强制类型转换也一样,C++的强制也要有一定关联
内置类型之间的关联就是意义上的,内置类型和自定义类型之间是通过构造来关联的(该构造可以使得一个对象去创建初始化另一个对象)
若构造函数的参数是一个类类型的话,这个类类型就可以转换为另外一个类类型了

静态成员本质上可以理解为是类中定义的全局变量,其生命周期是全局的

A对象之中是没有count的
如图直接访问不能访问_count,就可以提供成员函数进行访问

补充一下成员函数和对象之间的联系 这里要核心区分一下对象的 “状态”(成员变量) 和对象的 “行为”(成员函数) 的存储逻辑。成员函数并不存储在单个对象的内存空间中,而是由该类的所有对象共享。
对象的本质是 “类的实例”,其内存空间仅用于存储描述对象 “独有状态” 的成员变量(非静态成员变量)。



图中无论用aa.还是aa类型的指针箭头,并不是说这个变量就一定存在这个对象之中,这种写法代表的是一种类域 例如this指针即使为空,也可以用空指针调用成员函数,由于该场景下cout和成员函数并不存在对象之中,所以这里并不存在解引用,程序并不会崩溃
然而581行和582行这里的代码运行会报错的原因是链接不通过,找不到_cout。只有_cout的声明只有编译的时候能通过,相当于告诉了编译器有_cout这个变量,然而这个变量可能在其他文件定义,当前文件定义就不会存在链接了。其他文件定义,当前文件声明,就需要去链接,这就涉及声明与定义的分离了

这里统计一下图中A类型的对象创建了多少个,比如前面隐式类型转换是否优化是不确定的,不同的编译器决策可能不同,这里就能很好的解决

A类型所有的对象一定是构造或拷贝构造出来的,这段代码的流程就是
这里统计构造/拷贝构造次数用全局变量的原因是不期望该变量被修改,期望统计个数的变量是该类专属的,是所有对象共享的,所有对象去访问成员函数的时候都是它,就可以用来计数了。如果定义一个成员变量来统计的话,那么每个对象都有一个,就不好统计

定义n个对象的sum数组,就会调用n次sum类的构造。这个写法在VS中编译不通过,其他编译器支持(如g++),这个语法属于C99标准支持的变长数组,牛客的底层是使用的g++编译器
而要进行累加,调用n次就不断加等第一次的结果,然后调用第n次构造函数时,加等到第n次,_i就变为n了,就实现了1+到n,结果要放在Solution的类中,成员私有不能传过去,用一个公有的成员函数把结果传过去

接下来写一个在VS下的代码,VS下边长数组用不了就用new,new类似malloc,要去堆上动态开辟,只不过它要去调用构造函数。后面的文章我会细讲,new之后要delete,将数组delete释放掉也不会有影响,因为结果_ret存在静态区,不存在对象之中


首先看构造,全局变量是在main函数之前就要初始化的,这里c的初始化就要调用构造,所以第一个必然选C,然后局部的静态变量是在第一次运行它的时候才去初始化,所以第一个选项是E
再看析构,首先b在a之前,c在main函数结束以后才会析构,同时定义在栈中的变量,后定义的先析构,然而D并不在栈中,D在静态区,main函数结束之后,局部的静态的D会先析构,所以选B