文章目录
- 前言
- 一、友元函数
- 二、内部类
- 三、匿名对象
- 四、对象拷贝时的编译器优化
- 总结
前言
上篇博客我们学习了类和对象后期的一些内容——初始化列表、类型转换、static成员
今天我们来对类和对象进行最后的收尾
开始发车啦
一、友元函数
• 友元提供了一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或类声明的前面加friend,并且把友元声明放到一个类的里面。
• 外部友元函数可访问类的私有和保护成员,友元函数仅仅是一种声明,他不是类的成员函数。
• 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
• 一个函数可以是多个类的友元函数。
• 友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一个类中的私有和保护成员。
• 友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
• 友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是B的友元。
• 有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
代码和文字更配嗷
在我的理解就是,友元函数方便了类里面的一些成员的访问,可以耦合两个类的成员的关系
#include<iostream>
using namespace std;
class B;
class A
{// 友元声明friend void func(const A& aa, const B& bb);
private:int _a1 = 1;int _a2 = 2;
};
class B
{// 友元声明friend void func(const A& aa, const B& bb);
private:int _b1 = 3;int _b2 = 4;
};
void func(const A& aa, const B& bb)
{cout << aa._a1 << endl;cout << bb._b1 << endl;
}
int main()
{A aa;B bb;func(aa, bb);return 0;
}
#include<iostream>
using namespace std;
class A
{// 友元声明friend class B;
private:int _a1 = 1;int _a2 = 2;
};
class B
{
public:void func1(const A& aa){cout << aa._a1 << endl;cout << _b1 << endl;}void func2(const A& aa){cout << aa._a2 << endl;cout << _b2 << endl;}
private:int _b1 = 3;int _b2 = 4;
};
int main()
{A aa;B bb;bb.func1(aa);bb.func1(aa);return 0;
}
二、内部类
• 如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
• 内部类默认是外部类的友元类。
• 内部类本质也是一种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使用,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。
其实就跟命名空间域的套娃差不多
#include<iostream>
using namespace std;
class A
{
private:static int _k;int _h = 1;
public:class B // B默认就是A的友元{public:void foo(const A& a){cout << _k << endl; //OKcout << a._h << endl; //OK}};
};
int A::_k = 1;
int main()
{cout << sizeof(A) << endl;A::B b;A aa;b.foo(aa);return 0;
}
这里有一个有意思的题目:求1+2+3+…+n
摆明了只能用类去处理,用到类和对象里面的构造函数
没有内部类之前,我们是这样,就是在构造函数这里做文章
class Sum
{
public:Sum(){_ret += _i;++_i;}static int GetRet(){return _ret;}
private:static int _i;static int _ret;
};
int Sum::_i = 1;
int Sum::_ret = 0;
class Solution
{
public:int Sum_Solution(int n) {// 变长数组Sum arr[n];return Sum::GetRet();}
};
有了内部类之后,还可以这么玩,好像有点意思哈
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;
三、匿名对象
• 用 类型(实参) 定义出来的对象叫做匿名对象,相比之前我们定义的 类型 对象名(实参) 定义出来的叫有名对象
• 匿名对象生命周期只在当前一行,一般临时定义一个对象当前用一下即可,就可以定义匿名对象。
使用场景比较少,这里看看代码理解 叭
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().Sum_Solution(10);return 0;
}
四、对象拷贝时的编译器优化
• 现代编译器会为了尽可能提高程序的效率,在不影响正确性的情况下会尽可能减少一些传参和传参过程中可以省略的拷贝。
• 如何优化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 aa1;f1(aa1);cout << endl;// 隐式类型,连续构造+拷贝构造->优化为直接构造f1(1);// 一个表达式中,连续构造+拷贝构造->优化为一个构造f1(A(2));cout << endl;// 传值返回// 返回时一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造 (vs2019)// 一些编译器会优化得更厉害,进行跨行合并优化,直接变为构造。(vs2022)f2();cout << endl;// 返回时一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造 (vs2019)// 一些编译器会优化得更厉害,进行跨行合并优化,直接变为构造。(vs2022)A aa2 = f2();cout << endl;// 一个表达式中,连续拷贝构造+赋值重载->无法优化aa1 = f2();cout << endl;return 0;
}
最原始的流程就是这样的,连续拷贝然后再赋值
后面慢慢进化是这样的,省略了中间的连续拷贝,优化成为一个构造
对象拷贝就到这里啦~~~
总结
今天就是把类和对象的收尾工作结束啦
友元函数的访问内部成员,内部类的套娃,拷贝对象的深究
基础的类和对象就到这里啦,小编下一篇将为大家带来新的内容
不要走开,小编持续更新中~~~~~