内部类
• 如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独⽴的类,跟定义在 全局相⽐,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
#include<iostream>
using namespace std;
class A
{
private:
static int _k;
int _h = 1;
public:
class B // B默认就是A的友元
{
public:
void foo(const A& a)
{
cout << _k << endl; //OK
cout << a._h << endl; //OK
}
};
};
int A::_k = 1;
int main()
{
cout << sizeof(A) << endl;
A::B b;
A aa;
b.foo(aa);
return 0;
}
我们来计算一下对象A的大小:
static修饰的成员变量,也就是静态成员变量并没有存储在空间里面,所以只算了int _h = 1;的大小,但是B不算,因为内部类不是附属关系,只是说B的类域受到了A的限制
这里还要再说明的是,也会受到访问限定符的限制,如果把class B前面的public换成private也就会报错
• 内部类默认是外部类的友元类。
看上面的代码,也就是说B可以访问A的私有成员
• 内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使⽤,那么可以考 虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其 他地⽅都⽤不了。
总的来说,C++不喜欢用内部类,用的也不是很多,但是JAVA会用的比较多
再来回顾上次做过的一道OJ题
求1+2+3+…+n
描述
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
数据范围: 0<n≤2000<n≤200
进阶: 空间复杂度 O(1)O(1) ,时间复杂度 O(n)O(n)
示例1:
输入:
5返回值:
15
示例2
输入:
1返回值:
1
用内部类的情况下可以这样做:
class Solution {
// 内部类
class Sum
{
public:
Sum()
{
_ret += _i;
++_i;
}
};
static int _i;
static int _ret;
public:
int Sum_Solution(int n) {
// 变⻓数组
Sum arr[n];
return _ret;
}
};
int Solution::_i = 1;
int Solution::_ret = 0;
匿名对象
这是相当重要的知识,后续的学习还是非常常见的
• ⽤ 类型(实参) 定义出来的对象叫做匿名对象,相⽐之前我们定义的 类型 对象名(实参) 定义出来的 叫有名对象。
来看示例:
//有名对象,生命周期在main函数结束以后
A aa1(1);
A aa2;
//匿名对象
A(1);
A();//无参的时候必须要加括号,否则无法区分,而且匿名对象的生命周期只有一行
• 匿名对象⽣命周期只在当前⼀⾏,⼀般临时定义⼀个对象当前⽤⼀下即可,就可以定义匿名对象。
匿名对象的应用场景
1)调用构造函数的时候,减少代码的书写量:
Solution s1;
cout << s1.Sum_Solution(10) << endl;
//优化后的代码
cout << Solution().Sum_Solution(10) << endl;
2)匿名对象能否被引用?
答案是可以。匿名对象和临时对象一样也具有常性
正确的书写形式如下:
const A & r=A();
和临时对象不一样的是,匿名对象的生命周期被延长了。
试想一下:
如果用完了,就销毁,那么引用的意义何在?引用不就成了野引用,这里的匿名对象的生命周期取决于引用的生命周期,也就是main函数结束之后再析构
打个比方来说:
匿名对象特别像一次性筷子,但是要去露营了,三天的时间外面没有换筷子的地方,所以也就将就着用了,它的生命周期也取决于编译器
对象拷⻉时的编译器优化
•现代编译器会为了尽可能提⾼程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传返 回值的过程中可以省略的拷⻉。
代码如下:
A f2()
{
A aa;
return aa;
}
int main()
{A aa2=f2();cout<<endl;return 0;
}
逻辑图如下:
进程地址图如下:
这里编译器会觉得有些浪费,两次拷贝构造会浪费空间
在VS2019会有第一次优化:
直接让aa拷贝构造给aa2
如果关闭优化呢?
情况如下:
代码:
A f2()
{
A aa;
return aa;
}
int main()
{
// 传值传参
A aa1;
f1(aa1);
cout << endl;
// 隐式类型,连续构造+拷⻉构造->优化为直接构造
f1(1);
// ⼀个表达式中,连续构造+拷⻉构造->优化为⼀个构造
f1(A(2));
cout << endl;
// 传值返回
// 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019 debug)
// ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。(vs2022 debug)
f2();
cout << endl;
// 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019 debug)
// ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。(vs2022 debug)
A aa2 = f2();
cout << endl;
// ⼀个表达式中,连续拷⻉构造+赋值重载->⽆法优化
aa1 = f2();
cout << endl;
return 0;
}
22相比较19,传值返回有着很大的不同,22把拷贝构造都拿掉了,22没有创建aa,aa是aa2的别名,aa相当于一个指针,全程没有拷贝。
22中也可以理解为把临时对象拷贝给了aa1,函数调用的时候