📌 相关专栏
-
【C++ 专栏】
📌 相关文章推荐
很高兴你点开这篇文章✨
这里会持续更新我喜欢的内容,关注我,一起慢慢变好呀
👍 点赞 ⭐ 收藏 💬 评论
文章目录
- 前言
- 一、友元函数
-
- [1.1 为什么需要友元?](#1.1 为什么需要友元?)
- [1.2 前置声明的作用](#1.2 前置声明的作用)
- [1.3 友元的特点](#1.3 友元的特点)
- 二、友元类
- 三、内部类
-
- [3.1 什么是内部类](#3.1 什么是内部类)
- [3.2 内部类的特性](#3.2 内部类的特性)
- [3.3 外部类的大小不包含内部类](#3.3 外部类的大小不包含内部类)
- 四、匿名对象
-
- [4.1 什么是有名对象 vs 匿名对象](#4.1 什么是有名对象 vs 匿名对象)
- [4.2 匿名对象的特点](#4.2 匿名对象的特点)
- [4.3 匿名对象的典型用法](#4.3 匿名对象的典型用法)
- 五、编译器优化(拷贝优化)
-
- [5.1 传值传参时的拷贝](#5.1 传值传参时的拷贝)
- [5.2 传值返回时的优化(NRVO)](#5.2 传值返回时的优化(NRVO))
- [5.3 现代编译器的优化结果](#5.3 现代编译器的优化结果)
- 六、知识点汇总
- 七、常见面试题
-
- [🐾 </h3>Q1:友元函数可以写在类的private区域吗?](#🐾 Q1:友元函数可以写在类的private区域吗?)
- [🐾 </h3>Q2:内部类可以访问外部类的私有成员吗?](#🐾 Q2:内部类可以访问外部类的私有成员吗?)
- [🐾 </h3>Q3:匿名对象和有名对象的区别?](#🐾 Q3:匿名对象和有名对象的区别?)
- [🐾 </h3>Q4:编译器一定会做NRVO优化吗?](#🐾 Q4:编译器一定会做NRVO优化吗?)
- 八、总结
- 九、本文所有代码
-
- [🐾 </h3>Test.cpp](#🐾 Test.cpp)
前言
💡 在前几篇文章中,我们学习了类的基础和const成员函数。这一篇我们来聊几个更"进阶"的特性:
- 友元:让外部函数或其他类访问你的私有成员
- 内部类:把一个类定义在另一个类内部
- 匿名对象:不取名字的对象,用完就销毁
- 编译器优化:编译器悄悄帮你减少拷贝,提高效率
这些特性在实际开发中经常用到,理解它们能让我们的代码更灵活、更高效。
🐶 🐾 ✨ 🐾 🐶
一、友元函数
1.1 为什么需要友元?
类的私有成员默认只能被类内部访问。但有时候,我们想让某个外部函数也能访问私有成员, 比如 重载<<和>>时。
- 解决办法:友元函数------在类内部用friend关键字声明一个外部函数。
cpp
class B; // 前置声明,因为func的参数中用到了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;
};
// 友元函数实现:可以访问A和B的私有成员
void func(const A& aa, const B& bb)
{
cout << aa._a1 << endl; // ✅ 可以访问A的私有成员
cout << bb._b1 << endl; // ✅ 可以访问B的私有成员
}
int main()
{
A aa;
B bb;
func(aa, bb); // 输出:1 3
return 0;
}
1.2 前置声明的作用
编译器是向上查找的。当A的友元声明中用到B类型时,必须在前面先声明class B;,否则编译器不认识B。
cpp
class B; // 前置声明:告诉编译器B是一个类,后面会定义
class A {
friend void func(const B& bb); // 现在编译器知道B是一个类型
};
1.3 友元的特点
| 特点 | 说明 |
|---|---|
| 单向性 | A是B的友元,不代表B是A的友元 |
| 不可传递 | 友元关系不能继承 |
| 不受访问限定符影响 | 声明放在public/private都可以 |
| 突破封装 | 谨慎使用,破坏了封装性 |
🐶 🐾 ✨ 🐾 🐶
二、友元类
一个类可以声明为另一个类的友元,此时友元类的所有成员函数都可以访问另一个类的私有成员。
cpp
class A
{
friend class B; // B是A的友元类,B的所有成员函数都可以访问A的私有成员
private:
int _a1 = 1;
int _a2 = 2;
};
class C
{
friend class B; // B也可以是C的友元类(一个类可以是多个类的友元)
private:
int _c1 = 0;
int _c2 = 0;
};
class B
{
public:
void func1(const A& aa, const C& cc)
{
cout << aa._a1 << endl; // ✅ 访问A的私有成员
cout << cc._c1 << endl; // ✅ 访问C的私有成员
cout << _b1 << endl; // ✅ 访问自己的私有成员
}
private:
int _b1 = 3;
int _b2 = 4;
};
int main()
{
A aa;
B bb;
C cc;
bb.func1(aa, cc); // 输出:1 0 3
return 0;
}
注意: 友元关系是单向的。B是A的友元,但A不是B的友元,A不能访问B的私有成员。
🐶 🐾 ✨ 🐾 🐶
三、内部类
3.1 什么是内部类
一个类定义在另一个类的内部,叫做内部类。
cpp
class A
{
public:
class B // 内部类
{
public:
void foo(const A& a)
{
cout << _k << endl; // ✅ 可以访问A的静态成员
cout << a._h << endl; // ✅ 可以访问A的私有成员(B是A的友元)
}
private:
int _b1;
};
private:
static int _k;
int _h = 1;
};
int A::_k = 1; // 静态成员类外初始化
int main()
{
cout << sizeof(A) << endl; // 4(只包含_h,不包含内部类B)
A aa;
A::B b; // 通过域作用符访问内部类
b.foo(aa); // 输出:1 1
return 0;
}
3.2 内部类的特性
| 特性 | 说明 |
|---|---|
| 独立类 | 内部类是一个独立的类,外部类对象不包含内部类 |
| 受外部类域限制 | 访问内部类需要外部类::内部类 |
| 自动友元 | 内部类自动成为外部类的友元(C++特性) |
| 反向不行 | 外部类不是内部类的友元 |
| 可访问静态成员 | 内部类可以直接访问外部类的静态成员 |
3.3 外部类的大小不包含内部类
cpp
class A
{
class B {
int _b1;
int _b2; // 8字节
};
int _h; // 4字节
};
sizeof(A); // 4,不是12
内部类只是在编译器的类型命名空间里多了一个类型,不会成为外部类的成员变量。
🐶 🐾 ✨ 🐾 🐶
四、匿名对象
4.1 什么是有名对象 vs 匿名对象
cpp
class A
{
public:
A(int a = 0) : _a(a) { cout << "A(int a)" << endl; }
~A() { cout << "~A()" << endl; }
private:
int _a;
};
int main()
{
A aa1; // 有名对象:生命周期贯穿整个main函数
A(); // 匿名对象:生命周期只有这一行
A(1); // 匿名对象:带参数的匿名对象
// A aa2(); // ❌ 错误!被编译器当成函数声明
return 0;
}
// 输出:
// A(int a) <- 构造aa1
// A(int a) <- 构造匿名对象A()
// ~A() <- 销毁匿名对象A()
// A(int a) <- 构造匿名对象A(1)
// ~A() <- 销毁匿名对象A(1)
// ~A() <- 销毁aa1
4.2 匿名对象的特点
| 特点 | 说明 |
|---|---|
| 不需要取名字 | 类型(参数) 直接定义 |
| 生命周期极短 | 只在当前语句有效,立即构造立即析构 |
| 典型使用场景 | 作为函数实参,用完即毁 |
| 注意歧义 | A aa2(); 会被编译器当成函数声明 |
4.3 匿名对象的典型用法
cpp
// 方案类调用单次函数
int result = Solution().Sum_Solution(10); // 匿名对象,用一次就销毁
// 传参时直接用匿名对象
void func(A aa);
func(A(10)); // 直接传匿名对象,不需要先定义有名对象
🐶 🐾 ✨ 🐾 🐶
五、编译器优化(拷贝优化)
5.1 传值传参时的拷贝
cpp
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() { cout << "~A()" << endl; }
A& operator=(const A& aa) { cout << "A& operator=" << endl; return *this; }
private:
int _a1 = 1;
};
void f1(A aa) // 传值传参
{
// aa是形参,通过拷贝构造得到
}
int main()
{
A aa1; // 构造
f1(aa1); // 传值传参 → 拷贝构造
return 0;
}
// 输出:
// A(int a) <- 构造aa1
// A(const A& aa) <- 拷贝构造形参
// ~A() <- 销毁形参
// ~A() <- 销毁aa1
优化建议: 用引用传参可以避免拷贝。
cpp
void f1(const A& aa) // const引用,不拷贝
5.2 传值返回时的优化(NRVO)
cpp
// 未优化时的理论流程:
// 构造局部aa → 拷贝构造临时对象 → 销毁aa → 返回临时对象
A f2()
{
A aa; // 构造局部对象
return aa; // 传值返回
}
int main()
{
f2(); // 场景1:不接收返回值
A aa2 = f2(); // 场景2:用返回值构造新对象
aa1 = f2(); // 场景3:赋值给已有对象
return 0;
}
5.3 现代编译器的优化结果
在很多编译器(如VS、GCC开启优化)下,输出结果是:
| 场景 | 构造次数 | 拷贝构造次数 | 赋值次数 |
|---|---|---|---|
f2() |
1 | 0 | 0 |
A aa2 = f2() |
1 | 0 | 0 |
aa1 = f2() |
1 | 0 | 1(赋值) |
这就是NRVO(Named Return Value Optimization,具名返回值优化):编译器直接在接收返回值的内存上构造aa,省略了中间的拷贝构造。
🐶 🐾 ✨ 🐾 🐶
六、知识点汇总
| 知识点 | 核心要点 |
|---|---|
| 友元函数 | friend声明,可访问私有成员,需前置声明 |
| 友元类 | 友元类的所有成员函数都是友元 |
| 友元特点 | 单向、不可传递、破坏封装 |
| 内部类 | 定义在类内部,自动成为外部类友元 |
| 内部类独立性 | 不占外部类空间,访问需外部类::内部类 |
| 匿名对象 | 类型(参数),生命周期仅一行 |
| 匿名对象用途 | 函数实参、单次调用的临时对象 |
| 传值传参优化 | 用const &避免拷贝 |
| 传值返回优化 | NRVO:编译器省略中间拷贝 |
🐶 🐾 ✨ 🐾 🐶
七、常见面试题
🐾 Q1:友元函数可以写在类的private区域吗?
可以。friend声明不受访问限定符影响,放在public/private/protected都可以。
🐾 Q2:内部类可以访问外部类的私有成员吗?
可以。内部类自动成为外部类的友元。
🐾 Q3:匿名对象和有名对象的区别?
匿名对象没有名字,生命周期只有当前语句;有名对象有名字,生命周期直到作用域结束。
🐾 Q4:编译器一定会做NRVO优化吗?
不一定。NRVO是编译器优化,不同编译器、不同优化级别行为不同。依赖优化结果写代码是不安全的。
🐶 🐾 ✨ 🐾 🐶
八、总结
这一篇我们学习了C++的五个进阶特性:
- 友元:突破封装,但需谨慎使用
- 内部类:组织代码的一种方式,自动友元
- 匿名对象:临时使用的对象,用完即毁
- 编译器优化:减少拷贝,提高效率
🐶 🐾 ✨ 🐾 🐶
九、本文所有代码
🐾 Test.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
//////////////////////////////////////////////////////////////////////////////
// //友元函数
// // 友元提供了一种突破类访问限定符封装的方式,友元分为友元函数和友元类
// //在函数声明前面或者类声明前面加friend,并且把友元声明放到一个类的里面
//
//class B;
//前置声明,因为当一个函数是多个类的友元时,
// 必须要用前置声明将后面的类先进行声明,否则A的友元函数声明编译器不认识B,
//编译器原则是当要用到的任何类型和变量都是向上查找
//class A
//{
// //友元函数声明,或者友元类声明:friend class B;
// 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;
//}
///////////////////////////////////////////////////////////////////////////////////
////友元类
////友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一类中的私有和保护成员
//class A
//{
// //友元类声明
// friend class B;
// //此时B类中的所有成员函数都是A类的友元函数
// //即B中所有的成员函数都能使用A中的所有东西
//
//private:
// int _a1 = 1;
// int _a2 = 2;
//};
//
//class C
//{
// //友元类的声明
// friend class B; //B类的成员也可以供C用
//private:
// int _c1 = 0;
// int _c2 = 0;
//};
//
//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;
// }*/
//
// void func1(const A& aa, const C& cc)
// {
// cout << aa._a1 << endl;
// cout << cc._c1 << endl;
// cout << _b1<< endl;
// }
// void func2(const A& aa, const C& cc)
// {
// cout << aa._a2 << endl;
// cout << cc._c2 << endl;
// cout << _b2 << endl;
// }//函数一样,一个类也可以是多个类的友元类
//
//private:
// int _b1 = 3;
// int _b2 = 4;
//};
//int main()
//{
// A aa;
// B bb;
// C cc;
// bb.func1(aa, cc);
// bb.func2(aa, cc);
// return 0;
//}
/////////////////////////////////////////////////////////////////////////////
//内部类
//一个类定义在另一个类的内部叫做内部类,但他也是一个独立的类,
//只受外部类类域限制和访问限定符限制,所以外部类定义的对象不包含内部类
//class A
//{
//private :
// static int _k;
// int _h = 1;
//
//public:
// class B
// {
// public:
// //foo是一个函数名,通常用作示例/测试的占位符名称,类似"test、func"
// void foo(const A& a) //foo函数:接收A的常量引用
// {
// cout << _k << endl; //输出为1:B默认是A的友元,能使用A的所有成员
//
// cout << a._h << endl; //1
// }
// private:
// int _b1;
// };
//
// //A(int aa = 0)
// //{
// // B::_b1++;//err:对非静态成会"A::B_b1"的非法引用
// //}
// //B默认是A的友元,能使用A的所有成员;但A不是B的友元,无法访问B的私有成员
//};
//
////类外成员初始化
//int A::_k = 1;
//
//int main()
//{
// //输出A类的大小:仅包含非静态成员_h(int 占4字节)
// cout <<sizeof(A)<<endl; //B类中也有类型int的成员变量,但A类的大小不为8而为4,
// //说明A类并不包含b类这个内部类
// //只是B类受到了A类类域的限制,仍然是一个独立的类
//
// A aa; //定义A的变量
// A::B b; //B作为内部类受到A类类域的限制和访问限定符限制
// //要用域作用限定符::才能访问B类
// b.foo(aa); //调用foo函数,传入aa对象
// return 0;
//}
/////////////////////////////////////////////////////////////////////////////
//匿名对象
// 用类型(形参)定义出来的对象,叫匿名对象
// 类型对象名(实参)定义出来的叫有名对象
//
//class A
//{
//public:
// //带默认参数的构造函数
// A(int a = 0)
// :_a(a) //使用初始化列表:_a(a),直接初始化成员变量_a,构造时输出"A(int a)"
// //等价于A(int a=0)
// // {
// // _a=a; //这是赋值,不是初始化
// // }
// //初始化列表:对象创建时,直接给成员变量赋初值
// //函数体内赋值:对象现被默认值初始化,再被覆盖
// {
// cout << "A(int a)" << endl;
// }
// ~A() //析构函数,对象生命周期结束时自动调用,析构时输出"~A()"
// {
// cout << "~A()" << endl;
// }
//private:
// int _a;
//
//};
//
////class Solution
////{
////public:
//// int Sum_Solution(int n)
//// {
//// return 0;
//// }
////};
//int main()
//{
// A aa1; //有名对象,生命周期贯穿整个main函数,直到main结束时才会析构
//
// //A aa1(); //err:"aa1":重定义;以前的定义是:"数据变量"
// //编译器无法识别是否为一个函数声明,还是对象定义
// //可以定义匿名对象---不用取名字
//
// A(); //匿名对象
// A(1);
// //匿名对象的生命周期只有这一行,后边自动调用析构函数
// return 0;
//
//
// //运行结果
// //A(int a) //构造aa1
//
// //A(int a) //构造匿名对象A()
// // ~A() //销毁匿名对象A()
//
// //A(int a) //构造匿名对象A(1)
// // ~A() //销毁匿名对象A(1)
//
// // ~A() //销毁aa1
//}
/////////////////////////////////////////////////////////////////////////////////////////////
//
////对象拷贝时的编译器优化
////传值传参
class A
{
public:
//带默认参数的构造函数
A(int a = 0)
:_a1(a) //初始化列表
{
cout << "A(int a)" << endl;
}
//拷贝构造函数
A(const A& aa) //拷贝构造:_a1=aa._a1
:_a1(aa._a1)
{
cout << "A(const A& aa)" << endl;
}
~ A() //析构函数
{
cout << "~A()" << endl;
}
//赋值运算符重载 operator =
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa) //防止自赋值
{
_a1 = aa._a1; //成员变量赋值,将aa的_a1赋值给当前对象的_a1
}
return *this; //返回自身引用,支持链式赋值
}
private:
int _a1=1;
};
void f1(A aa)
{
}
//传值返回函数
//理论构造流程:构造aa->拷贝构造临时对象->销毁aa
//- 编译器优化(NRVO:具名返回值优化):
//很多编译器会直接在接收返回值的内存上构造 aa ,省略中间的拷贝构造
A f2()
{
A aa; //构造局部对象 aa
return aa;//传值返回
}
int main()
{
{
//传值传参
//构造+拷贝构造
A aa1; //调用构造初始化对象aa1
f1(aa1); //传值传参会调用拷贝构造,进行引用传参就可以减少拷贝
cout << endl;
cout << "***************************************" << endl;
//传值返回
f2();
cout << endl;
A aa2 = f2();
cout << endl;
aa1 = f2();
cout << endl;
return 0;
}
}
🐶 🐾 ✨ 🐾 🐶
🐾 下一篇我们继续学习:
- 初始化列表
- 类型转换
- static成员

谢谢你看到这里呀
如果喜欢这篇内容,点个关注,下次更新不迷路✨
👍 点赞 ⭐ 收藏 💬 评论
