类的特殊成员

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 方法,是“只读操作”的明确标识。

语法规则

  1. const 关键字必须放在函数参数列表之后
  2. 声明与定义中必须同时包含 constconst 是方法重载的依据之一)。

示例代码

// 头文件 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; 
};
  • 优势:语法更简洁,支持更多编译期计算场景。
const成员权限与访问规则示意图

静态成员

static 修饰的类成员,属于类本身而非单个对象,用于表达类的公共属性/行为(如总人数、公共工具方法)。

静态成员数据

语法规则

  1. 类内声明(加 static),类外定义(分配内存,不加 static);
  2. 定义时需指定类作用域(类名::成员名)。

示例代码(学生总人数统计)

#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(使用) 一个事物使用另一个事物 友元/函数参数 遥控器 → 电视、司机 → 汽车
OOP三大对象关系示意图

关键注意事项

初始化次序

  • 类对象成员的初始化次序,取决于其在类中的声明顺序,与初始化列表中的顺序无关;
  • 若成员间有依赖关系(如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默认是空串),再赋值(覆盖默认值),两次操作;
  • 初始化列表:直接调用成员的构造函数初始化,一次操作,无冗余步骤。

构造与析构次序

类(含对象成员)的构造和析构遵循固定规则,是内存管理的核心知识点。

构造次序

  1. 先构造所有类对象成员(按声明顺序,与初始化列表无关);
  2. 再构造当前类对象(执行构造函数体)。

示例验证

#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;
}

析构次序

与构造次序完全相反

  1. 先析构当前类对象(执行析构函数体);
  2. 再析构所有类对象成员(按声明顺序的逆序)。

示例验证

#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;
}

拓展:析构函数的核心作用

  • 释放类对象占用的资源(如动态内存、文件句柄、网络连接);
  • 类对象成员的析构由系统自动调用,无需手动处理。

posted @ 2025-12-19 10:28  Jaklin  阅读(17)  评论(0)    收藏  举报