深入篇【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.天生友元

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

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+…+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();
    }
};

这个案例我们就可以使用内部类来改造。
我们将原来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;

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;

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;
};
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;
};

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;
}

在这里插入图片描述
为什么是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.一行生命域

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

class Solution {
public:
	int Sum_Solution(int n) {
		cout << "Sum_Solution(int n)" << endl;
	}
};
int main()
{
	
	Solution().Sum_Solution(10);//匿名对象调用成员函数,一次性的
}

2.对象具有常性

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

A& a1 = A(1);

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

3.可强行续命

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

int main()
{
   const A& a1 = A(1);//OK
}

什么叫强行续命呢?就是原来匿名的生命周期就只是当前行,进入下一行后就销毁了,但用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)
{
}

int main()
{
	A aa1 = 2;//一个表达式中,构造+拷贝构造---->优化成构造
	
	Func2(5);//跟上面类似,属于隐式类型转化
	//一个表达式中,连续构造+拷贝构造--->优化为直接构造
	
	A aa3 = Func1();//一个表达式中,拷贝+拷贝-->优化成一次拷贝
	
	但对于不是同一行的连续构造,编译器是不会做出处理的。
	
	cout << "----------" << endl;
	
	A aa5;//构造函数
	aa3 = Func1();//拷贝函数+赋值运算符重载
	//编译器不会进行优化
}
  • 11
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小陶来咯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值