各位C++战士们,欢迎来到面向对象的“觉醒时刻”!
Part1面向过程和面向对象
1.1、面向过程和面向对象的初步认识
编程思想的核心差异,从 C 语言和 C++ 的对比中可见一斑:
- C 语言是面向过程的:关注 “步骤”,分析解决问题的流程,通过函数调用逐步执行步骤(例如,实现一个栈时,会分步骤写初始化、入栈、出栈等函数,调用时按顺序执行)。
- C++ 是基于面向对象的:关注 “对象”,将问题拆分成不同的对象,通过对象间的交互完成任务(例如,实现栈时,将栈的属性(栈顶、容量、数据)和行为(初始化、入栈、出栈)封装成一个 “栈对象”,直接通过对象调用行为)。
面向对象编程(OOP)的核心思想是 “万物皆为对象”,将现实世界的事物抽象为程序中的对象,每个对象包含两部分:
- 数据状态(属性,如栈的容量、当前元素个数);
- 可执行行为(方法,如栈的入栈、出栈操作)。
面向对象的三大特性是其强大的核心:
- 封装:将数据和行为捆绑,隐藏内部细节,只暴露必要接口(如栈的内部扩容逻辑对外隐藏,用户只需调用Push);
- 继承:允许一个对象继承另一个对象的特性,快速构建相似对象(如 “学生” 继承 “人” 的基本属性,再添加 “学号” 等特有属性);
- 多态:同一行为在不同对象上有不同表现(如 “动物叫”,猫会 “喵喵”,狗会 “汪汪”)。
简言之,面向对象通过 “对象” 作为基本单元,实现数据和行为的有机结合,让程序结构更清晰、易维护、可扩展。
1.2、类的引入
C 语言的结构体只能定义变量,而 C++ 对结构体进行了扩展:结构体中不仅可以定义变量,还可以定义函数。例如,用 C++ 实现栈:
typedef int DataType;
struct Stack {
// 成员变量(属性)
int top;
int capacity;
DataType* array;
// 成员函数(行为)
void Init() {
top = 0;
array = (DataType*)malloc(sizeof(DataType) * 4);
if (array == nullptr) {
perror("malloc failed");
return;
}
capacity = 4;
}
void Push(DataType x) {
if (top == capacity) { // 扩容
DataType* tmp = (DataType*)realloc(array, sizeof(DataType) * (capacity * 2));
if (tmp == nullptr) {
perror("realloc failed");
return;
}
array = tmp;
capacity *= 2;
}
array[top++] = x;
}
DataType Top() {
if (top == 0) {
cout << "stack is empty" << endl;
return -1;
}
return array[top - 1];
}
};
int main() {
Stack ss; // C++中可省略struct关键字
ss.Init(); // 直接通过对象调用成员函数,无需传参
ss.Push(1);
ss.Push(2);
cout << ss.Top() << endl; // 输出2
return 0;
}
上述代码中,Stack结构体同时包含了变量(top、capacity等)和函数(Init、Push等),且调用函数时无需像 C 语言那样传递结构体指针 —— 这是因为函数内部可以直接访问结构体的成员变量。
不过,C++ 中更推荐用class关键字定义类(而非struct),两者功能类似,但存在访问权限的默认差异(后续会讲)。
1.3、类的定义
类的基本语法如下:
class ClassName {
// 类体:成员变量(属性)和成员函数(方法)
}; // 注意末尾的分号
- class是定义类的关键字;
- ClassName是类名;
- 类体中的变量称为成员变量(或属性);
- 类体中的函数称为成员函数(或方法)。
类的两种定义方式
1.声明和定义全部放在类体中
成员函数在类内定义时,编译器可能将其视为内联函数(inline),适合简单函数。
class TestClass {
public:
void Print() { // 类内定义,可能被视为内联
cout << _a << " " << _b << endl;
}
private:
int _a; // 成员变量命名建议:加下划线区分
int _b;
};
2.声明放在.h文件,定义放在.cpp文件
类声明和定义分离,适合复杂函数,避免重复编译。类外定义成员函数时,需用类名::指定作用域。
// test.h(声明)
class TestClass {
public:
void Print(); // 声明
private:
int _a;
int _b;
};
// test.cpp(定义)
#include "test.h"
void TestClass::Print() { // 类外定义,需加TestClass::
cout << _a << " " << _b << endl;
}
建议成员变量名前 / 后加下划线(如_name、age_),避免与函数参数或局部变量重名:
class Person {
public:
void SetName(string name) {
_name = name; // _name是成员变量,name是参数,清晰区分
}
private:
string _name; // 成员变量加下划线
};
1.4、类的访问限定符及封装
1.4.1、访问限定符
C++ 通过访问限定符控制成员的访问权限,实现 “封装” 的核心功能。常用的访问限定符有三种:
- public(公有):类外可直接访问(如对象.公有成员);
- private(私有):类外不可直接访问,仅类内成员函数可访问;
- protected(保护):类外不可直接访问,主要用于继承(后续讲解)。
访问限定符的作用域:
从限定符出现的位置开始,到下一个限定符出现为止;若没有下一个限定符,则到类体结束(})为止。
class 与 struct 的区别:
- class定义的类,默认访问权限为 private;
- struct定义的类,默认访问权限为 public(为兼容 C 语言的结构体)。
示例:
class A {
int _a; // 默认private,类外不可访问
public:
int _b; // public,类外可访问
private:
int _c; // private,类外不可访问
};
struct B {
int _x; // 默认public,类外可访问
private:
int _y; // private,类外不可访问
};
int main() {
A a;
a._b = 10; // 正确(public)
// a._a = 20; // 错误(private,类外不可访问)
B b;
b._x = 30; // 正确(public)
// b._y = 40; // 错误(private,类外不可访问)
return 0;
}
1.4.2、封装
封装的本质:将数据(成员变量)和操作数据的方法(成员函数)有机结合,隐藏内部实现细节,仅通过公开接口与外部交互。
例如,手机的封装:用户无需知道内部芯片、电路的工作原理,只需通过屏幕、按键(接口)操作即可。
C++ 中,封装通过访问限定符实现:
- 隐藏细节:将内部数据(如栈的array指针)设为 private,避免外部直接修改导致错误;
- 暴露接口:将必要操作(如Push、Pop)设为 public,供外部安全调用。
1.5、类的作用域
类定义了一个独立的作用域(类域),所有成员都属于这个作用域。在类外定义成员时,需用::(作用域操作符)指明所属类域。
示例:
class Person {
public:
void PrintInfo(); // 声明:属于Person类域
private:
char _name[20];
int _age;
};
// 类外定义:需用Person::指定作用域
void Person::PrintInfo() {
cout << _name << " " << _age << endl; // 可直接访问类内成员
}
1.6、类的实例化
类的实例化:用类类型创建对象的过程。
- 类是 “模型”:描述对象的属性和行为,不占用实际内存(如 “学生表” 是模型,记录学生的属性,但本身不是具体学生);
- 对象是 “实例”:类实例化出的具体实体,占用内存(如 “张三” 是 “学生表” 实例化出的具体学生,有具体的姓名、年龄)。
示例:
class Person {
public:
char _name[20];
int _age;
};
int main() {
Person p1; // 实例化对象p1
Person p2; // 实例化对象p2
p1._age = 18; // 对象p1的年龄
p2._age = 20; // 对象p2的年龄(不同对象的成员变量独立)
return 0;
}
上述代码中,Person类本身不占内存,p1和p2是实例化的对象,各自占用内存存储_name和_age。
1.7、类的空间大小
类的空间大小仅取决于成员变量,成员函数存储在公共代码区(所有对象共享同一份函数代码)。
示例分析
#include <iostream>
using namespace std;
class A {}; // 空类
class B {
private:
int _a; // 成员变量
};
class C {
public:
void Print() { cout << _a << endl; } // 成员函数(不占对象空间)
private:
int _a; // 成员变量
};
int main() {
cout << sizeof(A) << endl; // 输出1
cout << sizeof(B) << endl; // 输出4
cout << sizeof(C) << endl; // 输出4
return 0;
}
- 空类(A):编译器会分配 1 字节(占位符),用于唯一标识类的实例(否则无法区分不同对象);
- 类 B:仅含 1 个int成员变量,大小为 4 字节;
- 类 C:成员函数不占对象空间,仅int _a占 4 字节,故大小为 4 字节。
内存对齐规则
类的成员变量存储遵循结构体对齐规则(因为类的底层存储可视为 “带函数的结构体”):
- 第一个成员永远放在偏移量为 0 的位置;
- 从第二个成员开始,每个成员需对齐到 “对齐数” 的整数倍处(对齐数 = 成员自身大小与默认对齐数的较小值;VS 默认对齐数为 8,gcc 无默认对齐数,对齐数 = 成员自身大小);
- 类的总大小为 “最大对齐数” 的整数倍(最大对齐数 = 所有成员对齐数的最大值);
- 若嵌套类 / 结构体,嵌套的类 / 结构体需对齐到自身最大对齐数的整数倍处。
示例:
class D {
private:
char _c; // 对齐数1(自身大小)
int _i; // 对齐数4(min(4, 8))
double _d; // 对齐数8(min(8, 8))
};
// 内存布局:
// _c:偏移0(1字节)
// 填充3字节(偏移1-3),使_i对齐到4
// _i:偏移4(4字节)
// _d:偏移8(8字节,对齐到8)
// 总大小:16(最大对齐数8,16是8的整数倍)
cout << sizeof(D) << endl; // 输出16
1.8、this 指针
在类的成员函数中,隐含着一个this指针,它指向当前调用函数的对象。this指针的核心作用是区分成员变量和参数(或局部变量)。
示例:this 指针的使用
class Student {
private:
int _age; // 成员变量
public:
void SetAge(int age) { // 参数age与成员变量_age重名
this->_age = age; // this指向当前对象,this->_age即成员变量
}
int GetAge() {
return this->_age; // 可省略this(编译器自动补充)
}
};
int main() {
Student s;
s.SetAge(18); // 调用时,this自动指向s
cout << s.GetAge() << endl; // 输出18
return 0;
}
上述代码中,SetAge函数的参数age与成员变量_age重名,通过this->_age明确访问成员变量。
this 指针的特性
- 类型:类类型* const(例如Student* const),即指针本身不可修改(不能给this赋值);
- 作用域:仅在成员函数内部可用;
- 本质:成员函数的隐含形参,当对象调用函数时,编译器自动将对象地址作为实参传递给this(无需用户手动传递);
- 存储:不存储在对象中,通常由编译器通过寄存器(如 ecx)传递。
Part2对象的构造和析构函数
在 C++ 中,对象的创建和销毁需要特定的函数来管理初始化和清理工作,这就是构造函数和析构函数。它们是面向对象编程中确保对象正确初始化和资源释放的核心机制。
2.1、构造函数:对象的 “出生证明”
构造函数是一种特殊的成员函数,用于在创建对象时自动完成初始化工作(如分配内存、初始化成员变量等)。
核心特性:
- 函数名与类名相同:例如class Array的构造函数名为Array。
- 无返回值:无需声明返回类型,也不能有return语句。
- 自动调用:在对象创建时由编译器自动调用,用户无需(也不能)手动调用。
示例:基本构造函数
#include <iostream>
#include <cstdlib>
using namespace std;
class Array {
private:
int* m_data; // 数组数据地址
int m_size; // 数组大小
public:
// 无参构造函数(函数名与类名相同,无返回值)
Array() {
cout << "Array的无参构造函数" << endl;
m_size = 5; // 初始化大小为5
m_data = (int*)malloc(sizeof(int) * m_size); // 分配内存
}
// 其他成员函数(省略)
~Array(); // 析构函数(后续讲解)
};
// 析构函数定义(释放资源)
Array::~Array() {
cout << "Array的析构函数" << endl;
if (m_data != NULL) {
free(m_data); // 释放动态分配的内存
m_data = NULL;
}
}
int main() {
Array a1; // 创建对象时,自动调用无参构造函数
return 0;
}
运行结果:
Array的无参构造函数
Array的析构函数 // 程序结束时自动调用析构函数
2.2、构造函数的重载与调用
和普通函数一样,构造函数支持重载(多个构造函数参数列表不同)。创建对象时,编译器会根据传递的实参匹配对应的构造函数。
调用方式:
- 括号法:Array a(10);(最常用)。
- 等号法:Array a = 10;(仅适用于单参数构造函数)。
- 逗号表达式:Array a = (1, 2);(实际传递最后一个参数,此处等价于Array a(2))。
示例:重载构造函数
class Array {
private:
int* m_data;
int m_size;
public:
Array(); // 无参构造函数
Array(int s); // 单参数构造函数(指定大小)
Array(int s, int init); // 双参数构造函数(指定大小和初始值)
~Array();
};
// 无参构造函数
Array::Array() {
cout << "无参构造函数" << endl;
m_size = 5;
m_data = (int*)malloc(sizeof(int) * m_size);
}
// 单参数构造函数
Array::Array(int s) {
cout << "单参数构造函数" << endl;
m_size = s;
m_data = (int*)malloc(sizeof(int) * m_size);
}
// 双参数构造函数
Array::Array(int s, int init) {
cout << "双参数构造函数" << endl;
m_size = s;
m_data = (int*)malloc(sizeof(int) * m_size);
for (int i = 0; i < m_size; i++) {
m_data[i] = init; // 初始化数组元素
}
}
Array::~Array() {
free(m_data);
m_data = NULL;
}
int main() {
Array a1; // 调用无参构造函数
Array a2(10); // 调用单参数构造函数(括号法)
Array a3 = 8; // 调用单参数构造函数(等号法)
Array a4(5, 0); // 调用双参数构造函数
Array a5 = (3, 6); // 逗号表达式,实际传递6,调用单参数构造函数
return 0;
}
2.3、拷贝构造函数:对象的 “复制机”
拷贝构造函数是一种特殊的构造函数,用于用已存在的对象初始化新对象。
声明格式:
类名(const 类名& 源对象); // 参数为源对象的常量引用
调用时机:
- 用一个对象初始化另一个对象:Array a2(a1); 或 Array a2 = a1;。
- 函数参数为对象(值传递):当函数形参是对象时,会用实参拷贝初始化形参。
- 函数返回值为对象(值返回):返回时会拷贝一个临时对象作为返回值。
示例:拷贝构造函数
class Array {
private:
int* m_data;
int m_size;
public:
Array(); // 无参构造函数
Array(const Array& a); // 拷贝构造函数
~Array();
};
Array::Array() {
cout << "无参构造函数" << endl;
m_size = 5;
m_data = (int*)malloc(sizeof(int) * m_size);
}
// 拷贝构造函数(此处为简化示例,实际需实现深拷贝)
Array::Array(const Array& a) {
cout << "拷贝构造函数" << endl;
m_size = a.m_size; // 复制大小
m_data = (int*)malloc(sizeof(int) * m_size); // 分配新内存
// 后续需复制数据(深拷贝)
}
Array::~Array() {
free(m_data);
m_data = NULL;
}
// 场景2:函数参数为对象(值传递)
void print(Array a) { // 调用拷贝构造函数:用实参初始化a
// ...
}
// 场景3:函数返回值为对象
Array createArray() {
Array temp;
return temp; // 调用拷贝构造函数:拷贝temp到返回值
}
int main() {
Array a1;
Array a2(a1); // 场景1:用a1初始化a2
print(a1); // 场景2:触发拷贝构造函数
Array a3 = createArray(); // 场景3:触发拷贝构造函数
return 0;
}
2.4、深度拷贝:避免 “同归于尽” 的复制
默认情况下,拷贝构造函数会执行浅拷贝(仅复制指针地址,不复制指针指向的内容),这会导致两个对象共享同一块内存,释放时出现 double free 错误。深拷贝则会复制指针指向的内容,确保每个对象拥有独立的资源。
示例:深拷贝的实现
Array::Array(const Array& a) { // 深拷贝
cout << "深拷贝构造函数" << endl;
m_size = a.m_size;
// 1. 为新对象分配独立内存
m_data = (int*)malloc(sizeof(int) * m_size);
// 2. 复制源对象的数据到新内存
for (int i = 0; i < m_size; i++) {
m_data[i] = a.m_data[i];
}
}
深拷贝 vs 浅拷贝:
- 浅拷贝:m_data = a.m_data;(共享内存,危险)。
- 深拷贝:重新分配内存并复制数据(独立内存,安全)。
2.5、默认构造函数:编译器的 “默认礼物”
当用户未定义任何构造函数时,编译器会自动生成默认无参构造函数(什么也不做)。但一旦用户定义了构造函数,编译器将不再提供默认版本。
规则:
- 若用户定义了无参构造函数,编译器不再生成默认无参构造函数。
- 若用户定义了有参构造函数,编译器不再生成默认无参构造函数(此时创建无参对象会报错)。
示例:默认构造函数的行为
class Demo {
public:
// 若注释掉以下构造函数,编译器会生成默认无参构造函数
Demo(int a) { // 用户定义了有参构造函数
cout << "有参构造函数" << endl;
}
};
int main() {
// Demo d; // 报错:无合适的无参构造函数(编译器未生成)
Demo d(10); // 正确:调用用户定义的有参构造函数
return 0;
}
2.6、析构函数:对象的 “死亡证明”
析构函数用于在对象销毁时自动完成清理工作(如释放动态内存、关闭文件等)。
核心特性:
- 函数名:~类名(波浪线 + 类名)。
- 无返回值:无需声明返回类型。
- 无参数:不能重载,一个类只能有一个析构函数。
- 自动调用:对象生命周期结束时(如离开作用域)由编译器自动调用。
示例:析构函数的作用
class Array {
private:
int* m_data;
int m_size;
public:
Array(int size) {
m_data = (int*)malloc(sizeof(int) * size); // 分配内存
}
~Array() { // 析构函数:释放资源
cout << "释放内存" << endl;
if (m_data != NULL) {
free(m_data); // 释放动态分配的内存
m_data = NULL;
}
}
};
int main() {
{
Array a(10); // 创建对象,分配内存
} // a离开作用域,自动调用析构函数释放内存
return 0;
}
2.7、匿名对象:“来也匆匆,去也匆匆”
匿名对象是指未命名的临时对象,格式为类名(参数)。其生命周期仅在当前行,执行完后立即销毁(调用析构函数)。
示例:匿名对象的特性
class Test {
public:
Test() {
cout << "构造函数" << endl;
}
~Test() {
cout << "析构函数" << endl;
}
};
int main() {
Test(); // 匿名对象:构造后立即销毁
cout << "----------------" << endl;
return 0;
}
运行结果:
构造函数
析构函数
----------------
2.8、对象的动态创建和释放:new 与 delete
- new:动态创建对象,会自动调用构造函数。
- delete:释放动态创建的对象,会自动调用析构函数。
注意:malloc/free仅分配 / 释放内存,不会调用构造函数 / 析构函数,因此在 C++ 中应优先使用new/delete。
示例:new 与 delete 的使用
class Test {
public:
Test() {
cout << "无参构造函数" << endl;
}
Test(int a) {
cout << "有参构造函数" << endl;
}
~Test() {
cout << "析构函数" << endl;
}
};
int main() {
// 动态创建对象(调用有参构造函数)
Test* pt = new Test(10);
// ... 使用对象
delete pt; // 释放对象(调用析构函数)
// 动态创建数组(调用无参构造函数)
Test* arr = new Test[3];
delete[] arr; // 释放数组(注意加[])
return 0;
}
2.9、构造函数的形参初始化列表:初始化的 “快车道”
初始化列表用于在构造函数体执行前初始化成员变量,适用于以下场景:
- 成员变量为const修饰(必须初始化,不能赋值)。
- 成员变量是类对象,且该类没有无参构造函数(必须显式初始化)。
格式:
类名(参数) : 成员1(值1), 成员2(值2), ... {
// 构造函数体
}
示例:初始化列表的使用
class Date {
private:
int m_year, m_month, m_day;
public:
// Date没有无参构造函数,必须通过有参构造函数初始化
Date(int y, int m, int d) : m_year(y), m_month(m), m_day(d) {
cout << "Date构造函数" << endl;
}
};
class Student {
private:
const int m_id; // const成员必须初始化
Date birth; // Date对象,无无参构造函数
public:
// 初始化列表:初始化m_id和birth
Student(int id, int y, int m, int d) : m_id(id), birth(y, m, d) {
cout << "Student构造函数" << endl;
}
};
int main() {
Student s(1001, 2000, 1, 1); // 正确初始化
return 0;
}
运行结果:
Date构造函数
Student构造函数
Part3静态成员变量和静态成员函数
在 C++ 中,静态成员(包括静态成员变量和静态成员函数)属于类本身,而非类的某个具体对象。它们是实现多个对象共享数据或功能的重要机制。
3.1、静态成员变量:类的 “共享仓库”
静态成员变量被所有类对象共享,不属于某个特定对象,其内存空间在程序启动时分配,生命周期贯穿整个程序。
核心特性:
- 属于类,而非对象:所有对象访问的是同一份静态成员变量。
- 必须在类外初始化:类内仅声明,初始化需在类外(通常在.cpp 文件中)。
- 可通过类名直接访问:无需创建对象,格式为类名::静态变量名。
- 普通成员函数可访问:静态成员变量对类的所有成员函数可见。
示例:静态成员变量的使用
#include <iostream>
using namespace std;
class Student {
public:
static int count; // 静态成员变量:记录学生总数(类内声明)
int id; // 普通成员变量:学生编号
public:
Student() {
count++; // 每创建一个对象,总数+1
id = count; // 用总数作为编号
}
// 普通成员函数访问静态成员变量
int GetCount() {
return count;
}
};
// 静态成员变量必须在类外初始化(定义)
int Student::count = 0;
int main() {
Student s1;
Student s2;
// 访问静态成员变量的三种方式
cout << s1.count << endl; // 通过对象访问(不推荐)
cout << s2.count << endl; // 通过对象访问(不推荐)
cout << Student::count << endl; // 通过类名访问(推荐)
// 通过普通成员函数访问
cout << s1.GetCount() << endl; // 输出2
return 0;
}
运行结果:
2
2
2
2
3.2、静态成员函数:类的 “工具函数”
静态成员函数属于类本身,用于处理与类相关的操作,不依赖具体对象。
核心特性:
- 无 this 指针:不能访问普通成员变量(需依赖对象),只能访问静态成员变量。
- 可通过类名直接调用:无需创建对象,格式为类名::静态函数名()。
- 普通成员函数不可通过类名调用:普通函数依赖对象(有 this 指针)。
示例:静态成员函数的使用
class Student {
public:
static int count; // 静态成员变量
int id; // 普通成员变量
public:
Student() {
count++;
id = count;
}
// 静态成员函数:只能访问静态成员变量
static int GetCount1() {
// return id; // 错误:静态函数不能访问普通成员变量
return count; // 正确:访问静态成员变量
}
// 普通成员函数:可访问静态和普通成员变量
int GetId() {
return id;
}
};
int Student::count = 0;
int main() {
Student s1;
Student s2;
// 通过类名调用静态成员函数(推荐)
cout << Student::GetCount1() << endl; // 输出2
// 通过对象调用静态成员函数(不推荐)
cout << s1.GetCount1() << endl; // 输出2
// 普通成员函数必须通过对象调用
cout << s1.GetId() << endl; // 输出1
return 0;
}
静态成员的典型用途:
- 统计类的对象总数(如示例中的count)。
- 实现单例模式(全局唯一实例)。
- 定义工具函数(如数学计算、数据转换等与类相关的通用操作)。
Part4友元函数与友元类
友元(friend)机制允许类外部的函数或其他类访问其私有成员,打破了类的封装性,用于解决特定场景下的访问需求(需谨慎使用,避免过度破坏封装)。
4.1、友元函数:类的 “特殊访客”
友元函数是在类中声明的外部函数(非成员函数),可以访问类的所有成员(包括私有成员)。
声明方式:
在类内用friend关键字声明函数:
class 类名 {
friend 返回类型 函数名(参数); // 声明友元函数
};
特性:
- 友元函数不属于类,没有 this 指针。
- 必须通过参数传递对象,才能访问该对象的成员。
- 可访问类的 public、protected、private 成员。
示例:友元函数的使用
#include <iostream>
using namespace std;
class Test {
// 声明show为友元函数,允许其访问Test的所有成员
friend void show(Test& t);
private:
int m_a; // 私有成员
public:
void set(int a) {
m_a = a; // 类内函数可访问私有成员
}
};
// 友元函数定义(类外)
void show(Test& t) {
// 友元函数可直接访问Test的私有成员m_a
cout << "Test的私有成员m_a = " << t.m_a << endl;
}
int main() {
Test t1;
t1.set(100);
show(t1); // 调用友元函数,输出:Test的私有成员m_a = 100
return 0;
}
4.2、友元类:类的 “信任伙伴”
若类 B 被声明为类 A 的友元类,则类 B 的所有成员函数都能访问类 A 的所有成员(包括私有成员)。
声明方式:
在类 A 中用friend class声明类 B:
class A {
friend class B; // 声明B为A的友元类
};
特性:
- 单向性:B 是 A 的友元,但 A 不是 B 的友元(除非 B 也声明 A 为友元)。
- 不传递性:若 B 是 A 的友元,C 是 B 的友元,C 不一定是 A 的友元。
- 类 B 的所有成员函数都自动成为类 A 的友元函数。
示例:友元类的使用
#include <iostream>
using namespace std;
class A {
friend class B; // 声明B为A的友元类
private:
int m_a; // A的私有成员
public:
void set(int a) {
m_a = a;
}
};
class B {
private:
int m_b;
public:
// B的成员函数可访问A的私有成员
void print(A& a) {
cout << "A的私有成员m_a = " << a.m_a << endl;
}
};
int main() {
A a1;
a1.set(200);
B b1;
b1.print(a1); // 输出:A的私有成员m_a = 200
return 0;
}
友元的注意事项:
- 友元机制破坏了类的封装性,应尽量少用。
- 友元关系无法继承(子类不会继承父类的友元关系)。
- 友元关系是固定的,不能在运行时动态改变。
往期推荐
小米C++校招二面:epoll和poll还有select区别,底层方式?
顺时针螺旋移动法 | 彻底弄懂复杂C/C++嵌套声明、const常量声明!!!