二、类与对象(四)

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 内部类的特性

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

例:

#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(外部类)= 外部类,和内部类没有任何关系。

例:

#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();还涉及到赋值运算符的重载,这将干扰优化的进行。

结论:

  1. 接收返回值对象,尽量以拷贝构造的方式接收,不要赋值接收。
  2. 函数中返回对象时,尽量返回匿名对象。

相关推荐

  1. 北海 - Rust面向对象

    2024-01-09 20:16:03       32 阅读
  2. 第八章 对象、类面向对象编程 第节——类

    2024-01-09 20:16:03       23 阅读
  3. 面对对象编程(

    2024-01-09 20:16:03       17 阅读
  4. 西南科技大学C++程序设计实验(类对象三)

    2024-01-09 20:16:03       40 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-09 20:16:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-09 20:16:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-09 20:16:03       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-09 20:16:03       20 阅读

热门阅读

  1. Vue使用vue-img-cropper实现图片裁剪

    2024-01-09 20:16:03       38 阅读
  2. 用python实现提取word中的所有图片

    2024-01-09 20:16:03       40 阅读
  3. CentOS7安装Neo4j

    2024-01-09 20:16:03       45 阅读
  4. unity中 canvas下物体的朝向跟随

    2024-01-09 20:16:03       40 阅读
  5. C与C++队列实现

    2024-01-09 20:16:03       37 阅读
  6. Zookeeper集群+Kafka集群

    2024-01-09 20:16:03       36 阅读
  7. okhttpclient.setsslsocketfactory 报错解决

    2024-01-09 20:16:03       41 阅读
  8. 「 PyMuPDF专栏 」PyMuPDF为PDF文件添加注释

    2024-01-09 20:16:03       33 阅读
  9. 彻底卸载Microsoft Edge的几种方法

    2024-01-09 20:16:03       36 阅读
  10. Docker 的基本概念和优势

    2024-01-09 20:16:03       30 阅读
  11. PHP 完整表单实例

    2024-01-09 20:16:03       29 阅读