内部类 ,匿名对象,编译器优化和静态成员

一.静态成员

        1.1 了解静态成员

⽤static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进⾏初始化。
        
        报错了,是因为不能直接给它值,这里只是声明,这里的值是缺省值,静态成员是属于静态区的,所以不会走初始化列表,所以不能给它缺省值,必须在类外初始化
        这样就是正确的了。
静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。

        

        我们发现静态变量并不属于类本身,类的作用只是给静态变量了一个类域,使其访问受限。 

⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
        
        我们注释的那一行,当我们试图访问成员变量a,但是无法访问,因为类中的方法访问类中的变量都是通过this指针去访问的,但是静态方法没有this指针。
静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有this指针。
⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
突破类域就可以访问静态成员,可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量
和静态成员函数。
        
        就是如图的两种方式。
静态成员也是类的成员,受public、protected、private 访问限定符的限制。
静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员
变量不属于某个对象,不⾛构造函数初始化列表。

        1.2 应用

        
        我们来看一下题,题目要求限制了我们的所有解法,这时候就可以用静态成员来做了。
        
        这是我们的做法,通过静态变量来实现,全局也可以,但是没有静态变量好,全局谁都可以访问,可能导致数值不小心改变。
        
        大家可以看一下这个的打印顺序,就是CABD,先全局的,后面按照顺序打印。
        大家再想想如果是析构呢?
        这个我们的做法就是先看普通的不看静态和全局的,普通的是后定义的先析构,然后再看静态,最后是全局的,所以是BADC。

二.内部类

2.1 了解内部类

        如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独⽴的类,跟定义在 全局相⽐,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。

        我来解释一下这句话,就是内部类也是独立的,只是受到了类域的限制,并不属于外部类。

        

        大家可以看这个例子就明白了,结果是4,并没有算内部类B中变量所占的空间。

内部类默认是外部类的友元类。

        

        内部可以访问到通过实例化访问外部的私有化变量,但是外部类不能访问内部的,可以说就是外部类就是为内部类服务的。

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



        2.2 应用

        我们上面的那个牛课题就可以进一步改善了。

        

        可以改成这样,外部类服务内部类,此时可以让它更加的安全和简洁。

        



        三.匿名对象

        3.1 了解匿名对象

        

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

        匿名对象顾名思义就是没有名字的对象,比如我们主函数中的前两行就是两个匿名对象,他的声明周期只在它的那一行,那一行运行完了,就析构了,和临时对象一样都具有常兴,不能被普通的引用指向,可以被const的引用指向

        下面的倒数第三行,const引用const会延长匿名对象的声明周期,匿名周期就变成了aa1这个引用的声明周期了,你可以理解为匿名对象完成使命后它的那一行结束之后,自己就关机了,const使他重新开机了,就是延长了声明周期了,匿名周期就变成了aa1这个引用的声明周期了。



        3.2应用

        就是我们上图中的最后一行,直接调用函数,不用使用实例化去调用,使得代码更加简洁。

        不仅有这一个作用,我们还可以做缺省值。

        

        如上图,做了缺省值。

        

四.编译器优化

        4.1 什么是编译器的优化?

        

        现代编译器会为了尽可能提⾼程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传返回值的过程中可以省略的拷⻉。

        我们也可以举个例子来看一下

        

        我们可以来看一下这个代码,主函数的第一行代码是直接构造,但是第二行是类型转换,我们都知道类型转换需要生成临时对象,正确的应该是我们先调用直接构造生成一个临时对象,再调用拷贝构造给这个aa1初始化,但是我们运行一下看看真的是这样吗?

        

        我们发现并没有调用拷贝构造啊,这是怎么回事呢?

        其中的原因就是编译器优化了,直接把构造加拷贝构造直接优化为直接构造了。

        vs没法演示,接下来我用虚拟机来演示一下不优化的输出结果。

        

        上面是优化的结构,下面是没有优化的结构,我们来分析一下吧,我们先通过1作为参数直接构造了一个临时对象,这是下面的第一行,再调用拷贝构造使用临时对象给aa1初始化,此时这一行完成了,临时对象就销毁了,所以就是第三行的析构,最后就是直接构造的那个和两个对象的析构。



        4.2  更多优化的例子

        我们可以来看一下主函数中的这几个。

        我们正常情况下,前两行没有什么问题,我们创建aa2对象调用了直接构造,因为形参是引用,所以我们传引用不会生成临时对象,第三行,正常情况下应该也是先生成临时对象的,但是我们可以看一下。

        

        我们发现有析构,生成临时对象了,但是我们正常传值的话,我们传的是副本,所以还会调用拷贝构造,但是,题目中并未调用,还有匿名对象也是,也是需要把副本传过去的,都需要调用拷贝构造,但是编译器把它优化掉了,直接通过我们所给的参数直接给形参初始化了,编译器优化了。

        正常应该是这样的。

        

        这样应该才是正确的。

        这个我们也可以看一下,返回值也应该是副本的,但是我们可以来看一下,我们将用vs2019的debug和vs2022的debug都看一下。

        我们上面主函数的第一行本来,应该是调用拷贝构造生成一个副本来返回了应该调用拷贝构造了,但是编译器看到没有变量去接受这个返回值,所以直接把它优化了。

        

        大家可以看一下我做的这个图,第一个是没有优化的,第二个是优化的一代,优化一代是直接通过aa直接调用拷贝构造给aa2初始化了,中间就没有产生临时对象了,第三个是初始化二代,编译器是怎么优化的呢?

        它是中间让aa作为了aa2的别名,只创建了一遍aa2并没有创建aa,所以只析构了一次,我们也可以通过打印地址来看一下。

        

        

        我们发现下面这两个对象的地址都是一样的,所以我们说的编译器底层把它优化了就是让aa变成了引用。

        

        还有这个我们可以看一下,这个的话编译器的优化程度就不如上面的好,上面的是直接就只创建aa4,但是这个的话就不行了,我们可以看一下 。

        编译器无法直接在创建aa4的时候直接让f2中的aa是aa4的别名,如果这样做的话,就会出问题了,所以这时候并不会怎么优化,我们可以看一下。

        

        vs2019没有优化,但是2022优化了,vs2019就是先创建了两个对象,再通过拷贝构造创建一个副本,再通过=运算符给aa2初始化(只要不是在定义的时候就初始化是不会调用拷贝构造的),第二个则是创建了两个对象,直接通过aa给aa2赋值了,没有生成临时对象。


        五.结束语

         感谢大家的查看,希望可以帮助到大家,做的不是太好还请见谅,其中有什么不懂的可以留言询问,我都会一一回答。  感谢大家的一键三连。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值