文章目录
- 5. 内部类
- 6. 匿名对象
- 7. 对象拷贝时的编译器优化
5. 内部类
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
内部类默认是外部类的友元类。
内部类本质也是一种封装,当
A
类跟B
类紧密关联,A
类实现出来主要就是给B
类使用,那么可以考虑把A
类设计为B
的内部类,如果放到private/protected
位置,那么A
类就是B
类的专属内部类,其他地方都用不了。
class A
{
private:
static int _k;//静态成员变量存储在静态区里面,所以计算大小的时候不会被计算
int _h = 1;//4字节
public:
class B // B默认就是A的友元
{
public:
void foo(const A& a)
{
cout << _k << endl; //OK
cout << a._h << endl; //OK
}
private:
int _b;
};
};
int A::_k = 1;
int main()
{
cout << sizeof(A) << endl;
A::B b;
return 0;
}
打印:
4
打印4
说明只需要计算int _h = 1;是4字节
,不需要计算B
。
这说明A
对象里面没有B
对象。
A
和B
的关系其实就像下面一样,A
是A
,B
是B
。
B
定义到A
里面是说B
受到A
这个类域的限制。
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
}
private:
int _b;
};
int A::_k = 1;
int main()
{
cout << sizeof(A) << endl;
A::B b;
return 0;
}
当然,如果把B
类放到A
类的private
里面,那么就无法通过A::B b;
来访问了。
class A
{
private:
static int _k;//静态成员变量存储在静态区里面,所以计算大小的时候不会被计算
int _h = 1;//4字节
class B // B默认就是A的友元
{
public:
void foo(const A& a)
{
cout << _k << endl; //OK
cout << a._h << endl; //OK
}
private:
int _b;
};
};
int A::_k = 1;
int main()
{
cout << sizeof(A) << endl;
A::B b;//会报错
return 0;
}
学完内部类后我们也可以这么写之前的那题:
代码:
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;
6. 匿名对象
用类型(实参) 定义出来的对象叫做匿名对象,相比之前我们定义的 类型 对象名(实参) 定义出来的叫有名对象
匿名对象生命周期只在当前一行,一般临时定义一个对象当前用一下即可,就可以定义匿名对象。
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
//...
return n;
}
};
int main()
{
A aa1;//有名对象(有名字的对象)
//A aa1();// 有名对象不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
A();
A(1);
A aa2(2);
//原本我们这样调用对象的
Solution s1;
cout << s1.Sum_Solution(10) << endl;
// 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
cout << Solution().Sum_Solution(10) << endl;
return 0;
}
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
void func(A aa = A(1))//缺省参数一般是给常量和自定义变量,现在可以给个匿名对象了
{}
int main()
{
func();
const A& r = A();//匿名对象可以引用,但是匿名对象和临时对象一样有常性。不过const引用会延长他的生命周期。
return 0;
}
7. 对象拷贝时的编译器优化
现代编译器会为了尽可能提高程序的效率,在不影响正确性的情况下会尽可能减少一些传参和传返回值的过程中可以省略的拷贝。
如何优化
C++
标准并没有严格规定,各个编译器会根据情况自行处理。当前主流的相对新一点的编译器对于连续一个表达式步骤中的连续拷贝会进行合并优化,有些更新更"激进"的编译器还会进行跨行跨表达式的合并优化。
#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a1(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a1(aa._a1)
{
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a1 = aa._a1;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a1 = 1;
};
void f1(A aa)
{}
A f2()
{
A aa;
return aa;
}
int main()
{
// 优化
A aa0 = 1;
cout << endl;
// 传值传参
A aa1;
f1(aa1);
cout << endl;
// 隐式类型,连续构造+拷贝构造->优化为直接构造
f1(1);
// 一个表达式中,连续构造+拷贝构造->优化为一个构造
f1(A(2));
cout << endl;
cout << "***********************************************" << endl;
// 传值返回
// 返回时一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造 (vs2019 debug)
// 一些编译器会优化得更厉害,进行跨行合并优化,直接变为构造。(vs2022 debug)
f2();
cout << endl;
// 返回时一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造 (vs2019 debug)
// 一些编译器会优化得更厉害,进行跨行合并优化,直接变为构造。(vs2022 debug)
A aa2 = f2();
cout << endl;
// 一个表达式中,连续拷贝构造+赋值重载->无法优化
aa1 = f2();
cout << endl;
return 0;
}