22 内部类
22.1 内部类的概念
如果一个类定义在另一个类的内部,这个类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员,外部类对内部类没有任何优越的访问权限,也就是说它和定义在一个全局的类没有什么区别,只是受外部类的类域限制。
例:
#include <iostream>
using namespace std;
class A
{
private:int h;
public:class B{private:int b;};
};
int main()
{//B bb; //错误代码A::B bb;//B受A的类域限制return 0;
}
22.2 内部类的特性
- 内部类天生就是外部类的友元,即内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
例:
#include <iostream>
using namespace std;
class A
{
private:static int k;int h;
public:class B // B天生就是A的友元{public:void foo(const A& a){//B可以访问A的私有成员cout << k << endl;//OKcout << a.h << endl;//OK}};
};
int A::k = 1;
int main()
{A::B b;b.foo(A());return 0;
}
运行结果:
-
内部类可以定义在外部类的
public
、protected
、和private
也就是任何位置。 -
sizeof(外部类)= 外部类
,和内部类没有任何关系。
例:
#include <iostream>
using namespace std;
class A
{
private:int h;
public:class B{private:int b;};
};
int main()
{A aa;cout << sizeof(aa) << endl;return 0;
}
运行结果:
23 匿名对象
23.1 匿名对象的引入及特性
以往我们调用成员函数的时候,通常的做法是会先定义一个对象,然后再通过这个对象去调用函数,但是有些时候比如我们在做题目,某个函数只需要被调用一次就可以了,如果定义一个对象才能调用就有些麻烦了,这个时候我们就可以通过匿名对象来对函数进行调用。
以往我们说不能用A aa1();
这样的方式来定义对象,因为编译器无法识别这是一个函数声明,还是对象定义。
定义匿名对象的方式和上面的方式类似,但是它不用取名字,而且匿名对象的生命周期只在它出现的那一行。
例:
#include <iostream>
using namespace std;
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();cout << "-----" << endl;cout << Solution().Sum_Solution(10) << endl;return 0;
}
运行结果:
从输出结果可以看到,27
行的时候定义了一个匿名对象,到了28
行后它就自动调用了析构函数。
除此之外,29
行还展示了匿名对象的应用场景,即对于那些需要通过对象来调用函数但对象本身并不重要的情况,就可以通过匿名对象进行调用。当然还有一些其他使用场景,这个等以后遇到再说。
23 编译器对拷贝对象时的一些优化
在传参和传返回值的过程中,为减少对象的拷贝,通常编译器会对以下场景做一些优化:
23.1 连续构造和拷贝构造时的优化
#include <iostream>
using namespace std;
class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a = aa._a;}return *this;}~A(){cout << "~A()" << endl;}
private:int _a;
};int main()
{A aa1 = 1;//构造+拷贝构造return 0;
}
运行结果:
从输出结果可以看到,35
行代码本来应该涉及一个构造和一个拷贝构造,但被编译器优化后只涉及一个构造。
23.2 参数类型为引用时的优化
#include <iostream>
using namespace std;
class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a = aa._a;}return *this;}~A(){cout << "~A()" << endl;}
private:int _a;
};
void Fun2(const A& aa)
{}
int main()
{A aa1 = 1;cout << "-----" << endl;Fun2(aa1);//aa只是aa1的别名,无优化cout << "-----" << endl;Fun2(2);//构造一个临时变量后给别名,无优化cout << "-----" << endl;Fun2(A(3));//return 0;
}
运行结果:
从输出结果可以看到, 当函数参数类型为引用时,因为形参只是实参的一个别名,所以不需要做任何优化。
结论:尽量使用const &
传参。
23.3 传值返回时的优化
#include <iostream>
using namespace std;
class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a = aa._a;}return *this;}~A(){cout << "~A()" << endl;}
private:int _a;
};
void Fun1(A aa)
{}
void Fun2(const A& aa)
{}
A Fun3()
{A aa;//构造return aa;//拷贝构造
}
A Fun4()
{return A();
}
int main()
{A aa1 = 1;cout << "-----" << endl;Fun3();//无优化cout << "-----" << endl;A aa2 = Fun3();//本来应该是一个构造,两个拷贝构造,但被编译器优化为一个构造和一个拷贝构造cout << "-----" << endl;Fun4();//构造+拷贝构造->优化为直接构造A aa3 = Fun4();//构造+拷贝构造+拷贝构造->优化为直接构造cout << "-----" << endl;return 0;
}
运行结果:
从输出结果可以看到,51
行代码本来应该涉及一个构造和两个拷贝构造,但被编译器优化为和49
行代码一样,只涉及一个构造和一个拷贝构造。除此之外,58
行代码直接返回一个匿名对象时,本来应该涉及一个构造和两个拷贝构造,但被编译器优化后,只涉及一个构造。
需要注意的是,下面这种情况无法优化:
A aa1;
aa1 = Fun4();
原因在于,和A aa3 = Fun4();
相比,aa1 = Fun4();
还涉及到赋值运算符的重载,这将干扰优化的进行。
结论:
- 接收返回值对象,尽量以拷贝构造的方式接收,不要赋值接收。
- 函数中返回对象时,尽量返回匿名对象。