宜昌市网站建设_网站建设公司_轮播图_seo优化
2025/12/20 14:43:31 网站建设 项目流程

大家好!之前我们聊了类和对象的基础,比如构造函数、析构函数这些 “标配”。今天咱们再往深走一步,看看 C++ 里几个超实用的进阶特性 ——初始化列表静态成员友元内部类。这些知识点能帮你写出更灵活、更高效的代码,但也容易踩坑,咱们一个个掰扯清楚,每个点都附上手写代码和配图建议,保证新手也能看懂~

一、构造函数的 “优化方案”:初始化列表

之前咱们写构造函数时,习惯在函数体里给成员变量赋值,比如这样:

cpp

class Date { public: // 传统赋值方式:函数体里赋值 Date(int year, int month, int day) { _year = year; // 此时对象已经分配空间,只是“再赋值” _month = month; _day = day; } private: int _year; int _month; int _day; };

但这里有个小问题:在进入构造函数体之前,对象的成员变量其实已经分配好空间了。函数体里的操作本质是 “二次赋值”,不够高效。

这时候就该「初始化列表」登场了 —— 它能在成员变量分配空间的同时完成初始化,一步到位!

1. 初始化列表怎么写?

格式很简单:构造函数声明后加冒号,后面跟 “成员变量 (初始值)” 的列表,用逗号分隔。咱们把上面的 Date 类改成初始化列表版本:

cpp

class Date { public: // 初始化列表:冒号开始,成员变量(初始值)用逗号分隔 Date(int& xx, int year, int month, int day) : _year(year) // 用形参year初始化成员_year , _month(month) // 用形参month初始化成员_month , _day(day) // 用形参day初始化成员_day , _n(1) // const成员必须在这里初始化 , _ref(xx) // 引用成员必须在这里初始化 , _t(1) // 自定义类型Time没有默认构造,必须这里初始化 , _ptr((int*)malloc(12)) // 指针成员初始化(分配12字节空间) { // 函数体里可以做额外操作(比如检查指针是否为空) if (_ptr == nullptr) { perror("malloc fail"); // 打印内存分配失败信息 } else { memset(_ptr, 0, 12); // 把分配的空间清0 } } void Print() const { cout << _year << "-" << _month << "-" << _day << endl; } private: // 成员变量声明(注意顺序!初始化顺序和声明顺序一致) int _year; int _month; int _day; const int _n; // const成员:只能初始化,不能赋值 int& _ref; // 引用成员:必须绑定一个变量,不能单独存在 class Time { // 自定义类型Time:没有默认构造函数(必须传参) public: Time(int hour) : _hour(hour) {} // 只有带参构造 private: int _hour; } _t; // 成员变量_t:类型是Time int* _ptr; // 指针成员 };

2. 初始化列表的 3 个 “铁规则”

这些规则记不住,编译时必报错,一定要划重点!

  1. 特殊成员必须用初始化列表以下 3 种成员,只能在初始化列表里初始化,函数体里赋值会报错:

    • 引用成员(如int& _ref):引用必须在定义时绑定变量,没法 “先定义再赋值”;
    • const 成员(如const int _n):const 变量一旦初始化就不能改,函数体里赋值是 “修改”,不允许;
    • 没有默认构造的自定义类型(如Time _t):如果自定义类型没有 “不用传参的构造函数”,必须在初始化列表里传参构造。
  2. 每个成员只能初始化一次初始化列表里,一个成员变量只能出现一次(比如不能同时写_year(year), _year(2024)),因为 “初始化” 只能做一次。

  3. 初始化顺序 = 成员声明顺序(和列表顺序无关)这是最容易踩的坑!比如你在列表里先写_month(month), _year(year),但成员声明是_year在前、_month在后,那么实际初始化顺序还是先_year_month。✅ 建议:让 “初始化列表顺序” 和 “成员声明顺序” 保持一致,避免逻辑错误。

3. 小补充:C++11 的成员缺省值

如果某个成员变量大部分情况下都是同一个初始值,可以在声明时直接给缺省值。如果初始化列表没处理这个成员,就会用缺省值:

cpp

class Date { private: int _year = 2024; // 缺省值:没在初始化列表写,就用2024 int _month = 1; // 缺省值:没写就用1 int _day; // 没缺省值:没在列表写,内置类型是随机值 };

📌 小贴士:尽量用初始化列表!哪怕是普通成员,初始化列表也比函数体赋值更高效~(图 1:初始化列表执行流程示意图 —— 可手绘 “对象内存分配 → 初始化列表执行 → 构造函数体执行” 的步骤)

二、被所有对象 “共享” 的静态成员

有时候我们需要一个 “全局变量”,但又想让它只属于某个类(比如统计一个类创建了多少个对象)。这时候「静态成员」就派上用场了 —— 用static修饰的成员,所有对象共享,不占单个对象的内存。

1. 静态成员变量:所有对象的 “公共财产”

核心性质:
  • 存放在静态区(不是栈 / 堆),程序启动时分配,结束时释放;
  • 所有对象共享同一个静态成员变量(改一个对象的静态成员,其他对象看到的也会变);
  • 必须在类外初始化(类内只能声明,不能赋值,除非是const static整型)。
代码示例:统计对象创建个数

cpp

#include <iostream> using namespace std; class Test { public: Test() { _count++; // 每次创建对象,静态成员_count加1 } // 静态成员函数:用来访问私有静态成员(后面讲) static int GetCount() { return _count; } private: // 静态成员变量:声明(类内只能声明) static int _count; }; // 静态成员变量:类外初始化(必须写!格式:类名::成员名 = 初始值) int Test::count = 0; // 这里的_count和类里的是同一个 int main() { Test t1, t2, t3; // 创建3个对象 // 访问静态成员:类名::成员名(因为是public静态函数) cout << "创建的对象个数:" << Test::GetCount() << endl; // 输出3 return 0; }

2. 静态成员函数:没有 this 指针的 “特殊函数”

静态成员函数用static修饰,和普通成员函数最大的区别是:没有 this 指针(因为它不属于某个具体对象)。

核心规则:
  • 只能访问静态成员(变量 / 函数),不能访问非静态成员(因为没有 this 指针,找不到具体对象的成员);
  • 非静态成员函数可以访问静态成员(非静态有 this 指针,但静态成员是共享的,不用 this 找);
  • 访问方式:类名::函数名()对象.函数名()(推荐用类名访问,更清晰)。

3. 访问权限:看访问限定符!

很多人以为 “静态成员只能用类名访问”,其实不对 —— 核心看public/private/protected

  • 如果静态成员是public:类外可以直接用类名::成员名访问(比如上面的Test::GetCount());
  • 如果静态成员是private:类外不能直接访问,必须通过public静态成员函数间接访问(比如上面的_count是 private,只能通过GetCount()访问)。

三、突破封装的 “特殊通道”:友元

C++ 的类封装得很严,私有成员只能在类内访问。但有时候需要 “开个后门”(比如运算符重载、IO 流操作),这时候「友元」就来了 —— 它能让外部函数 / 类直接访问类的私有成员。

1. 友元函数:单个函数的 “后门”

特点:
  • 不是类的成员函数,但能访问类的private/protected成员;
  • 声明时加friend,放在类内任意位置(不受访问限定符限制);
  • 一个函数可以是多个类的友元。
代码示例:让 func 访问 A 和 B 的私有成员

cpp

#include <iostream> using namespace std; // 前置声明:告诉编译器B是个类(否则A里的友元声明不认识B) class B; class A { // 友元声明:func是A的友元,可以访问A的私有成员 friend void func(const A& aa, const B& bb); private: int _a1 = 1; // 私有成员 int _a2 = 2; }; class B { // 友元声明:func也是B的友元,可以访问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 << "A的私有成员:" << aa._a1 << endl; // 正确:访问A::_a1 cout << "B的私有成员:" << bb._b1 << endl; // 正确:访问B::_b1 } int main() { A aa; B bb; func(aa, bb); // 输出:1 和 3 return 0; }

2. 友元类:整个类的 “后门”

如果一个类 A 是类 B 的友元,那么A 的所有成员函数都能访问 B 的私有成员

核心规则:
  • 单向性:A 是 B 的友元 ≠ B 是 A 的友元(B 不能访问 A 的私有成员);
  • 不可传递:A 是 B 的友元,B 是 C 的友元 ≠ A 是 C 的友元;
  • 声明方式:在 B 类里写friend class A;
代码示例:B 是 A 的友元

cpp

class A { // 友元类声明:B是A的友元,B的所有成员函数能访问A的私有成员 friend class B; private: int _a1 = 1; }; class B { public: // B的成员函数:能直接访问A的私有成员 void PrintA(const A& aa) { cout << "A的私有成员:" << aa._a1 << endl; // 正确 } private: int _b1 = 3; }; int main() { A aa; B bb; bb.PrintA(aa); // 输出:1 // aa访问B的私有成员?错误!A不是B的友元 // cout << aa._b1; // 编译报错 return 0; }

⚠️ 注意:友元会破坏封装!能不用就不用,除非确实需要(比如实现operator<<重载)。(图 3:友元函数访问权限测试截图 —— 展示 func 运行后输出 “A 的私有成员:1” 和 “B 的私有成员:3”)

但是如果我们解封下面那段注释,代码一定会报错,因为A不是B的友元。

四、专属 “小助手”:内部类

如果一个类 A 和类 B 关系特别紧密(比如 A 就是为 B 服务的),可以把 A 定义在 B 的内部,这就是「内部类」。

1. 内部类的核心性质

  • 独立性:内部类是一个独立的类,外部类的对象不包含内部类的成员(内存上没关系);
  • 域限制:内部类的名字只在外部类的域里有效(比如Solution::Sum, outside 用不了);
  • 默认友元:内部类默认是外部类的友元(内部类能访问外部类的私有成员);
  • 封装性:如果内部类放在外部类的private里,那么只有外部类能使用这个内部类(专属小助手)。

2. 代码示例:用内部类计算 1 到 n 的和

比如我们要实现一个Solution类,里面有个Sum_Solution函数计算 1+2+...+n。可以把求和的逻辑放在内部类Sum里,让Sum成为Solution的专属工具:

cpp

#include <iostream> using namespace std; class Solution { public: int Sum_Solution(int n) { // 创建n个Sum对象:每个对象构造时会累加1~n Sum* p = new Sum[n]; delete[] p; // 释放内存(避免内存泄漏) // 访问内部类的静态成员:外部类::内部类::成员名 return Sum::GetRet(); } private: // 内部类Sum:放在private里,只有Solution能使用 class Sum { public: Sum() { _ret += _i; // 构造时累加:_ret = 1+2+...+n _i++; } // 静态成员函数:返回累加结果 static int GetRet() { return _ret; } private: static int _i; // 静态成员:记录当前要加的数(初始1) static int _ret; // 静态成员:记录累加结果(初始0) }; }; // 内部类的静态成员:类外初始化(格式:外部类::内部类::成员名 = 初始值) int Solution::Sum::_i = 1; int Solution::Sum::_ret = 0; int main() { Solution sol; cout << "1到10的和:" << sol.Sum_Solution(10) << endl; // 输出55 return 0; }

(图 4:内部类使用场景示意图 —— 标注 “Solution 类(外部)” 包含 “Sum 类(内部,private)”,箭头指向 “Sum 只为 Solution 服务”)

总结:这 4 个特性怎么用?

特性核心作用注意事项
初始化列表高效初始化成员(尤其是特殊成员)初始化顺序 = 声明顺序;特殊成员必须用
静态成员所有对象共享数据 / 函数类外初始化;静态函数无 this 指针
友元突破封装(开后门)破坏封装,尽量少用;单向不可传递
内部类封装紧密关联的类独立存在;默认是外部类的友元;private 专属

其实这些特性不难,关键是多动手敲代码 —— 比如试着用静态成员统计对象个数,用内部类实现一个简单的计算器,踩几次坑就记住了~ 如果你在实践中遇到问题,欢迎在评论区留言讨论!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询