如何定义一个类的特殊成员?

摘要:const 成员 const 是C语言延续的关键字,核心语义为只读不可修改,在类中用于限定成员(数据方法)的只读属性,帮助编译器优化,提升代码安全性与效率。 const 类数据(成员属性) 核心用途 修饰一旦初始化就不可修改的类属性(如身
const 成员 const 是C语言延续的关键字,核心语义为只读不可修改,在类中用于限定成员(数据/方法)的只读属性,帮助编译器优化,提升代码安全性与效率。 const 类数据(成员属性) 核心用途 修饰一旦初始化就不可修改的类属性(如身份证号、学号、固定名称等)。 初始化方式(两种) 初始化方式 语法示例 特点与适用场景 构造函数初始化列表 cpp class Person { const unsigned int ID; public: Person(unsigned int id) : ID(id) {} // 初始化列表赋值 }; 1. 支持对象创建时传入不同初始值(如不同学生的学号) 2. 兼容性强,支持所有C++标准 类内直接初始化 cpp class Person { const unsigned int ID = 1234; // 类内定死值 }; 1. 所有对象共享同一固定值,不可修改 2. 仅支持C++11及以上标准,老旧编译器可能报错 3. 适用场景:类的公共固定属性(如学校名称) 典型示例 class Student { private: // 所有学生共享的固定属性(类内初始化) const string schoolName = "XXX学院"; // 每个学生独有的只读属性(初始化列表赋值) const int ID; public: // 构造函数初始化列表为ID赋值 Student(int id) : ID(id) {} }; int main() { Student Jack(001); // ID=001,schoolName=XXX学院 Student Lucy(002); // ID=002,schoolName=XXX学院 } 注意事项 const 类数据不可在构造函数体内部赋值(仅能通过初始化列表或类内初始化); 类内初始化的 const 成员,本质是编译期常量(若值为字面量),内存占用更高效。 const 类方法 核心用途 声明该方法不会修改类的任何数据成员,也不会调用非 const 方法,是“只读操作”的明确标识。 语法规则 const 关键字必须放在函数参数列表之后; 声明与定义中必须同时包含 const(const 是方法重载的依据之一)。 示例代码 // 头文件 Person.h #ifndef _PERSON_H #define _PERSON_H #include <string> using namespace std; class Person { int age; string name; public: // 声明时加const void showInfo() const; void setName(string newName); // 非const方法 }; #endif // 实现文件 Person.cpp #include "Person.h" // 定义时必须加const,与声明一致 void Person::showInfo() const { // 错误:const方法不能修改成员数据 // age = 100; // 错误:const方法不能调用非const方法 // setName("刘德华"); cout << "姓名:" << name << ",年龄:" << age << endl; } 注意事项 const 对象只能调用 const 类方法(非 const 对象可调用所有方法); 逻辑上不修改成员数据的方法,务必声明为 const(提升编译优化效率,减少误操作)。 拓展:const 与 static 的组合 static const 类数据(C++11前常用) class Student { // 类内声明(必须类外定义) static const int MAX_SCORE = 100; }; // 类外定义(C++11前必须,C++11后可省略,但建议保留兼容性) const int Student::MAX_SCORE; 特性:所有对象共享、只读、编译期常量(值需为字面量); 适用场景:类的公共常量(如满分、最大容量)。 constexpr 替代方案(C++11+) class Student { // 编译期常量,兼具static和const特性,无需类外定义 constexpr static int MAX_SCORE = 100; }; 优势:语法更简洁,支持更多编译期计算场景。 静态成员 被 static 修饰的类成员,属于类本身而非单个对象,用于表达类的公共属性/行为(如总人数、公共工具方法)。 静态成员数据 语法规则 类内声明(加 static),类外定义(分配内存,不加 static); 定义时需指定类作用域(类名::成员名)。 示例代码(学生总人数统计) #include <iostream> #include <string> using namespace std; class Student { private: // 单个对象属性 unsigned int ID; string name; // 类的公共属性(声明) static int total; public: // 构造函数:创建对象时总人数+1 Student(unsigned int id, string n) : ID(id), name(n) { total++; } // 静态方法:获取总人数 static void showTotal() { cout << "学生总人数:" << total << endl; } }; // 静态成员数据定义(类外,分配内存,初始值默认0) int Student::total; int main() { Student Jack(001, "Jack"); Student Lucy(002, "Lucy"); // 两种访问方式:类名::方法 或 对象.方法 Student::showTotal(); // 输出:学生总人数:2 Jack.showTotal(); // 输出:学生总人数:2 return 0; } 核心特性 特性 说明 存储位置 静态数据区(独立于对象,不占用对象内存) 共享性 所有对象共享一份,修改后影响所有对象 访问方式 类名::成员名(推荐) 或 对象.成员名(需权限允许) 权限约束 受 public/private/protected 限制(如private静态成员仅类内访问) 不可修饰 不能用 const 修饰(静态成员本身是类级别的,const是对象级别的只读) 静态成员方法 语法规则 声明时加 static,定义时不加 static; 无隐含 this 指针(不依赖具体对象)。 注意事项 只能调用其他静态成员(静态方法/静态数据),不能访问非静态成员(依赖对象的 this 指针); 不能被 const 修饰(无 this 指针,无法保证对象只读)。 拓展 静态成员的内存布局 普通成员:存储在对象内部(栈/堆),每个对象一份; 静态成员:存储在静态数据区,整个程序生命周期内唯一,仅一份。 静态成员的线程安全问题 多线程环境下,静态成员的修改可能导致数据竞争; 解决方案:使用互斥锁(std::mutex)保护静态成员的读写操作。 静态成员的初始化顺序 同一类内:按声明顺序初始化; 不同类间:初始化顺序不确定(避免在静态成员中依赖其他类的静态成员)。 类对象成员 类的成员可以是另一个类的对象,用于表达事物间的has-a(包含)关系(如汽车包含引擎、房子包含厨房)。 语法基础 // 被包含的类(引擎) class Engine { public: string brand; // 品牌 float displacement; // 排量 // 引擎启动方法 void start() { cout << brand << "引擎启动,排量:" << displacement << endl; } }; // 包含类(汽车) class Car { private: unsigned int VIN; // 车架号(自身属性) Engine engine; // 类对象成员(包含的属性) public: // 构造函数:通过初始化列表初始化对象成员 Car(unsigned int vin, string b, float d) : VIN(vin), engine{b, d} {} // 汽车漂移方法(依赖引擎) void drift() { engine.start(); cout << "车架号" << VIN << "的汽车正在漂移!" << endl; } }; int main() { Car myCar(123456, "丰田", 2.0); myCar.drift(); return 0; } 事物间的三大核心关系(OOP基础) 关系类型 含义 实现方式 示例 is-a(从属) 子类是父类的一种 类的继承 猫 → 哺乳动物、学生 → 人类 has-a(包含) 一个事物包含另一个事物 类对象成员 汽车 → 引擎、房子 → 厨房 use-a(使用) 一个事物使用另一个事物 友元/函数参数 遥控器 → 电视、司机 → 汽车 关键注意事项 初始化次序 类对象成员的初始化次序,取决于其在类中的声明顺序,与初始化列表中的顺序无关; 若成员间有依赖关系(如B依赖A初始化),需确保声明顺序为A在前、B在后。 初始化要求 类对象成员若无默认构造函数(无参/全默认参数),必须在初始化列表中显式初始化; 若有默认构造函数,可省略初始化(系统自动调用默认构造)。 拓展:has-a 与 is-a 的选择原则 当需要复用另一个类的功能(而非属性)时,优先用 has-a(组合); 当需要复用另一个类的属性+功能,且存在明确的“从属关系”时,用 is-a(继承); 示例:“鸟会飞” → 鸟(is-a)动物,鸟(has-a)翅膀(翅膀是属性,飞是依赖翅膀的功能)。 初始化列表 构造函数的特殊语法,专门用于成员数据的初始化,效率高于构造函数体内部赋值。 语法格式 class 类名 { 成员1; 成员2; public: 类名(参数列表) : 成员1(值1), 成员2(值2), ... { // 构造函数体(无需再初始化const/对象成员) } }; 核心特点与适用场景 适用场景 说明 const 成员 必须通过初始化列表初始化(无法在构造函数体赋值) 类对象成员 无默认构造时必须显式初始化,有默认构造时推荐用(效率更高) 普通成员 初始化效率高于构造函数体赋值(直接初始化,而非先默认构造再赋值) 引用成员 必须通过初始化列表初始化(引用一旦绑定不可修改) 示例代码(综合场景) class Score { // 成绩类(类对象成员) public: int math; int english; // 无默认构造函数(必须显式初始化) Score(int m, int e) : math(m), english(e) {} }; class Student { private: const int ID; // const成员 string name; // 普通成员 Score score; // 类对象成员(无默认构造) int& ageRef; // 引用成员 public: // 初始化列表:初始化所有成员 Student(int id, string n, int m, int e, int& age) : ID(id), name(n), score(m, e), ageRef(age) { // 错误:const成员不能在体内赋值 // this->ID = id; } }; int main() { int age = 18; Student Jack(001, "Jack", 90, 85, age); return 0; } 拓展:初始化列表的效率原理 构造函数体赋值:成员先默认初始化(如string默认是空串),再赋值(覆盖默认值),两次操作; 初始化列表:直接调用成员的构造函数初始化,一次操作,无冗余步骤。 构造与析构次序 类(含对象成员)的构造和析构遵循固定规则,是内存管理的核心知识点。 构造次序 先构造所有类对象成员(按声明顺序,与初始化列表无关); 再构造当前类对象(执行构造函数体)。 示例验证 #include <iostream> using namespace std; class A { public: A() { cout << "构造 A" << endl; } }; class B { public: B() { cout << "构造 B" << endl; } }; class Node { A a; // 声明顺序:A在前 B b; // 声明顺序:B在后 public: // 初始化列表顺序与声明顺序相反,但构造次序仍按声明 Node() : b(), a() { cout << "构造 Node" << endl; } }; int main() { Node node; // 输出结果: // 构造 A // 构造 B // 构造 Node return 0; } 析构次序 与构造次序完全相反: 先析构当前类对象(执行析构函数体); 再析构所有类对象成员(按声明顺序的逆序)。 示例验证 #include <iostream> using namespace std; class A { public: ~A() { cout << "析构 A" << endl; } }; class B { public: ~B() { cout << "析构 B" << endl; } }; class Node { A a; B b; public: ~Node() { cout << "析构 Node" << endl; } }; int main() { Node node; // 输出结果(程序结束时析构): // 析构 Node // 析构 B // 析构 A return 0; } 拓展:析构函数的核心作用 释放类对象占用的资源(如动态内存、文件句柄、网络连接); 类对象成员的析构由系统自动调用,无需手动处理。