• 深入篇【C++】类与对象:详解内部类+匿名对象+编译器对拷贝的优化


    Ⅰ.内部类

    概念:一个类定义在另一个类内部,这个内部的类就叫做内部类。
    内部类是一个独立的类,它不属于外部类,更不能通过外部的对象来访问内部类的成员。外部类和内部类理论上没有什么关系,外部类对内部类没有任何优越的访问限定。

    class A
    {
    public:
        class B//B类写在A类里面,是内部类
        {
        public:
            void func(const A& a1)
            {
            }
        };
    private: 
       static int _a;
        int x=0;
    };
    int A::_a = 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    那内部类有什么特点呢?

    【特点】

    1.天生友元

    内部类天生就是外部类的友元类,内部类可以通过对象参数来访问外部类的成员变量,但是外部类不是内部类的友元。

    class A
    {
    public:
        class B//B为内部类,天生是A类的友元
        {
        public:
            void func(const A& a1)//友元可以通过对象参数来访问外部类的成员
            {
                cout << a1.x << endl;//没有问题
            }
        };
    private: 
       static int _a;
        int x=0;
    };
    int A::_a = 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    天生友元这个使用就很灵活了。
    外部类定义的成员变量,内部类可以访问,外部类也可以访问到的。
    所以一般当内部类和外部类配合使用时,就将内部类的成员定义到外部类去。
    这样的话,内部类可以访问,外部类也可以访问。

    比如下面这段代码求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
    我们可以通过定义n个sum对象,n个sum对象就会调用n次构造函数。
    我们只要在构造函数里进行计算即可,不过要注意的是,计算的变量必须是静态变量才可以。
    最后再写一个GetRet函数来获取最后的结果,因为在solution函数里无法直接访问sum对象中的私有结果。

    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) {
    
            //sum a[n];//调用n次构造函数。
            return sum::GetRet();
        }
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    这个案例我们就可以使用内部类来改造。
    我们将原来sum类中的成员变量写到外面的solution类中去,这样内部类可以使用,外部类也可以直接使用,这样最后就不用再写GetRet函数了,因为solution函数可以访问到_ret结果。

    
    class Solution {
        //默认是私有的,如果想让别人访问可以写public
        class sum {
        public:
            sum()//内部类,天生是外部类的友元,可以使用外部类的成员变量,因为为静态成员,所以不需要对象就可以使用
            {
                _ret += _i;
                ++_i;
            }
        };
    public:
        int Sum_Solution(int n) {
    
           // sum a[n];//调用n次构造函数。
            return _ret;//这里可以直接返回——ret因为它就是类的成员变量
        }
    private:
        static int _i;
        static int _ret;
    };
    int Solution::_i = 1;
    int Solution::_ret = 0;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2.直接访问static成员

    注意内部类是可以直接访问外部类的static成员变量的,因为static成员不需要this指针就可以访问,所以不需要外部类的对象/类名。

    class A
    {
    public:
        class B
        {
        public:
            void func(const A& a1)
            {
                cout << _a << endl;//内部类可以直接访问外部类的static成员
                //非常OK,不需要外部类的对象
                cout << a1.x << endl;//没毛病
            }
        };
    private: 
       static int _a;
        int x=0;
    };
    int A::_a = 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3.访问限制符限制

    内部类可以定义类外部类的public,protected,private,都是可以的,取决于使用者。当想被别人访问的就写在public中,不想被别人使用的就写在private中,所以又分为公有内部类和私有内部类。

    为什么会受访问限定符的限制呢?因为内部类定义的是一个类型,而访问限定符就是用来限定不同类型的访问的,不同于友元类,友元是不受访问限定符限制的,那是因为写在类里面的只是声明,就是一张图纸而已。

    所以当你有这样的想法时:希望这个类藏到类里面,别人访问不到时,就使用private限定。

    class A
    {
    public://公有内部类
        class B
        {
        public:
            void func(const A& a1)
            {
                cout << _a << endl;
                cout << a1.x << endl;
            }
        };
    private: 
       static int _a;
        int x=0;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    class A
    {
    private://私有内部类
        class B
        {
        public:
            void func(const A& a1)
            {
                cout << _a << endl;
                cout << a1.x << endl;
            }
        };
    private: 
       static int _a;
        int x=0;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    4.外部类的大小

    sizeof(外部类)=外部类,和内部类是没有任何关系的。

    class A
    {
    public:
        class B
        {
        public:
            void func(const A& a1)
            {
                cout << _a << endl;
                cout << a1.x << endl;
            }
        };
    private: 
       static int _a;
        int x=0;
    };
    int A::_a = 1;
    int main()
    {
        A a1;
        cout << sizeof(A) << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述
    为什么是4呢?
    首先我们要知道静态变量是不算进去的,因为静态变量就没有存到对象里,所以它是不算A类的大小的。
    然后内部类是不占空间的,因为它在类A中只是声明而已,并没有实例化,定义出对象出来,只有在A类中定义了对象B,这样才可以把B类的大小算进去。
    在这里插入图片描述
    在使用内部类之前记得要指定类域才可以正常使用。

    Ⅱ.匿名对象

    匿名对象就是没有名字的对象

    class A
    {
    public:
    	A(int a = 10)
    		:_a(a)
    	{
    		cout << "A(int a = 10)" << endl;
    	}
    
    	~A()
    	{
    		cout << "~A()" << endl;
    	}
    
    private:
    	int _a;
    };
    int main()
    {
    	A aa1(1);//有名对象
    	//A aa2(),可不能这样写哈,这样写编译器会无法识别这是在创建对象呢还是在声明一个函数呢,当不给参数时,后面就不要给括号。
    	A(2);//匿名对象
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    那匿名对象有什么用呢?有什么特性呢?

    【特点】

    1.一行生命域

    匿名对象与有名对象的区别在于其生命周期不同。
    1.有名对象—生命周期在当前函数局部域。
    2.匿名对象—生命周期就只在当前行。
    在这里插入图片描述
    在这里插入图片描述
    当我们想直接一次性的调用某个成员函数时,就可以使用匿名对象调用,当想重复调用某个成员函数时,就用有名对象。

    class Solution {
    public:
    	int Sum_Solution(int n) {
    		cout << "Sum_Solution(int n)" << endl;
    	}
    };
    int main()
    {
    	
    	Solution().Sum_Solution(10);//匿名对象调用成员函数,一次性的
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.对象具有常性

    我们知道临时变量是具有常性的,而匿名对象也具有常性。
    比如下面这个代码就可以验证出来。

    A& a1 = A(1);
    
    • 1

    在这里插入图片描述
    匿名对象具有常性,所以不可以用引用。但只要给引用前面加上const就可以了。

    3.可强行续命

    当我们给引用前面加上const后就可以给匿名对象引用了。而加上引用后就出现了一个神奇的现象:匿名对象的生命周期改变了。

    int main()
    {
       const A& a1 = A(1);//OK
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    什么叫强行续命呢?就是原来匿名的生命周期就只是当前行,进入下一行后就销毁了,但用const引用后延长了匿名对象的生命周期,生命周期就跟引用对象一样长了。
    为什么呢?可以这样解释:正常的匿名对象,使用完后,后面没有人要使用了,所以使用完就销毁掉了,而用const引用的对象因为还有一个引用一直在吊着它,它如果销毁了就不合理了,所以生命周期延长跟引用对象一样。

    Ⅲ.拷贝对象时编译器的优化

    在传参和传值返回的过程中,一些编译器会做一些优化,减少对象的拷贝。

    当在一行连续的构造时编译器就会进行优化。

    class A
    {
    public:
    	A(int a = 10)
    		:_a(a)
    	{
    	   cout << "A(int a = 10)" << endl;
    	}	
    	A(const A& aa)
    		:_a(aa._a)
    	{
    		cout << "A(const A& aa)" << endl;
    	}
    	A& operator=(const A& aa)
    	{
    		if (this != &aa)
    		{
    			_a = aa._a;
    		}
    		return *this;
       }
    	~A()
    	{
    	   cout << "~A()" << endl;
    	}
    private:
    	int _a;
    };
    A Func1()
    {
    	A aa2;
    	return aa2;
    }
    void Func2(A a)
    {
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    int main()
    {
    	A aa1 = 2;//一个表达式中,构造+拷贝构造---->优化成构造
    	
    	Func2(5);//跟上面类似,属于隐式类型转化
    	//一个表达式中,连续构造+拷贝构造--->优化为直接构造
    	
    	A aa3 = Func1();//一个表达式中,拷贝+拷贝-->优化成一次拷贝
    	
    	但对于不是同一行的连续构造,编译器是不会做出处理的。
    	
    	cout << "----------" << endl;
    	
    	A aa5;//构造函数
    	aa3 = Func1();//拷贝函数+赋值运算符重载
    	//编译器不会进行优化
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
  • 相关阅读:
    【我不熟悉的css 】02. 手动画一个svg图片
    视频删除怎么恢复找回,刚删除的视频怎么找回来
    2022/11/27[指针] 指针与函数基础
    Android 10 状态栏通知图标和下拉状态栏图标为白色问题
    海康威视Java实习面试
    C# 零基础入门知识点汇总
    信奥中的数学:抽屉原理
    C++ Reference: Standard C++ Library reference: C Library: cwctype
    【Java|golang】337. 打家劫舍 III---树上最大独立集
    前缀树的设计与实现
  • 原文地址:https://blog.csdn.net/Extreme_wei/article/details/130903628