二、类与对象(四)

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. 函数中返回对象时,尽量返回匿名对象。
相关推荐
煤泥做不到的!5 分钟前
挑战一个月基本掌握C++(第十一天)进阶文件,异常处理,动态内存
开发语言·c++
F-2H8 分钟前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++
axxy200036 分钟前
leetcode之hot100---24两两交换链表中的节点(C++)
c++·leetcode·链表
若亦_Royi2 小时前
C++ 的大括号的用法合集
开发语言·c++
Aileen_0v03 小时前
【AI驱动的数据结构:包装类的艺术与科学】
linux·数据结构·人工智能·笔记·网络协议·tcp/ip·whisper
Rinai_R5 小时前
计算机组成原理的学习笔记(7)-- 存储器·其二 容量扩展/多模块存储系统/外存/Cache/虚拟存储器
笔记·物联网·学习
吃着火锅x唱着歌5 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
ragnwang5 小时前
C++ Eigen常见的高级用法 [学习笔记]
c++·笔记·学习
胡西风_foxww6 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
lqqjuly8 小时前
特殊的“Undefined Reference xxx“编译错误
c语言·c++