二、类与对象(四)

22 内部类

22.1 内部类的概念

如果一个类定义在另一个类的内部,这个类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员,外部类对内部类没有任何优越的访问权限,也就是说它和定义在一个全局的类没有什么区别,只是受外部类的类域限制。

例:

c++ 复制代码
#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 内部类的特性

  1. 内部类天生就是外部类的友元,即内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

例:

c++ 复制代码
#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;//OK
            cout << a.h << endl;//OK
        }
    };
};
int A::k = 1;
int main()
{
    A::B b;
    b.foo(A());
    return 0;
}

运行结果:

  1. 内部类可以定义在外部类的publicprotected、和private也就是任何位置。

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

例:

c++ 复制代码
#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();这样的方式来定义对象,因为编译器无法识别这是一个函数声明,还是对象定义。

定义匿名对象的方式和上面的方式类似,但是它不用取名字,而且匿名对象的生命周期只在它出现的那一行

例:

c++ 复制代码
#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 连续构造和拷贝构造时的优化

c++ 复制代码
#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 参数类型为引用时的优化

c++ 复制代码
#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 传值返回时的优化

c++ 复制代码
#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行代码直接返回一个匿名对象时,本来应该涉及一个构造和两个拷贝构造,但被编译器优化后,只涉及一个构造。

需要注意的是,下面这种情况无法优化:

c++ 复制代码
A aa1;
aa1 = Fun4();

原因在于,和A aa3 = Fun4();相比,aa1 = Fun4();还涉及到赋值运算符的重载,这将干扰优化的进行。

结论:

  1. 接收返回值对象,尽量以拷贝构造的方式接收,不要赋值接收。
  2. 函数中返回对象时,尽量返回匿名对象。
相关推荐
加成BUFF9 分钟前
C++入门讲解3:数组与指针全面详解
开发语言·c++·算法·指针·数组
老王熬夜敲代码29 分钟前
linux系统IO
linux·笔记
stars-he30 分钟前
FPGA学习笔记(6)逻辑设计小结与以太网发送前置
笔记·学习·fpga开发
天若有情67338 分钟前
我发明的PROTO_V4协议:一个让数据“穿上迷彩服”的发明(整数传输协议)
网络·c++·后端·安全·密码学·密码·数据
加油=^_^=39 分钟前
【C++11】特殊类设计 | 类型转换
c++·单例模式·类型转换
锦瑟弦音41 分钟前
跑酷游戏开发笔记3 && 游戏开始场景 cocos 3.8.7
javascript·笔记·游戏
加成BUFF42 分钟前
C++入门详解2:数据类型、运算符与表达式
c语言·c++·计算机
受之以蒙43 分钟前
智能目标检测:用 Rust + dora-rs + yolo 构建“机器之眼”
人工智能·笔记·rust
徐行code1 小时前
std::bind()和lambda的区别
c++
EniacCheng1 小时前
【RUST】学习笔记-环境搭建
笔记·学习·rust