🔷 第十章 类和对象
📄 一句话概括

访问类成员有三种"视角":类外面(对象用 .、指针用 ->)、类里面(成员函数直接写成员名,不用带对象名)、类名的视角(静态成员用 类名::)。能不能访问,由 private / protected / public 决定(10.1.1)。本篇还补一个实用规矩:const 成员函数——它是"我不会改装你"的承诺,也是访问 const 对象的唯一途径。

📝 本节和前面几节的关系

10.4 铺开了类成员的地图,本节先解决最基础的问题:"用什么符号、在哪儿碰得到"。用到的旧知识:7.6.1 的 ->(变量点、指针箭头)、6.4 的作用域、8.3.2 的". 与 -> 的由来"。本节还会第一次正式解释const 成员函数——10.1、10.2.2 的 Demo 里已经用过它(void print() const),当时没解释,现在补上。

一、三种访问视角

视角 1:类外面(最常用)

Point p;
p.set(3, 4);           // 对象用 点
p.show();

Point *ptr = &p;
ptr->set(5, 6);        // 指针用 箭头
ptr->show();           // 口诀(8.3.2):变量点、指针箭头

能碰到哪些成员? 只能碰 public 的。碰 private 会报:

error: 'int Point::x' is private within this context

视角 2:类里面(成员函数内部)

class Point {
private:
    int x, y;
public:
    void set(int a, int b) {
        x = a;          // 直接写成员名,不用写"哪个对象的 x"
        y = b;
    }
    void move(int dx, int dy) {
        set(x + dx, y + dy);   // 类内可以直接调自己的其他成员函数
    }
};

两条规律:

规律 说明
成员名直接用 不用带对象名,这个对象就是"当前对象"(10.4.4 揭晓原理)
可以访问自己的一切 private、protected、public 都能碰——自己家的抽屉当然随便开
成员之间可以互相调用 不用写 this-> 或对象名,直接写函数名(见下"顺序问题")

一个细节:成员函数之间调用,不受"书写先后"限制:

class A {
public:
    void f() { g(); }        // f 写在前面,调用了后面的 g
    void g() { cout << "g" << endl; }
};

能编译过——因为在类里,整个类可以看作"先整体读一遍再处理"(不像普通函数必须"先声明后使用",6.1.2)。这是类的一个便利。

视角 3:类名的视角(静态成员)

cout << Company::getTotal() << endl;     // 10.4 用过:用类名直接调

只有静态成员能这么访问(10.4.3 细讲)。

二、一张表总结三种视角

视角 写法 能访问什么 例子
类外 · 对象 对象名.成员 只有 public p.set(3, 4);
类外 · 指针 指针->成员 只有 public ptr->show();
类内 直接写成员名 全部(含 private) x = a;
静态成员 类名::成员 看该成员自己的权限 Company::getTotal()

三、const 成员函数:一句"我不会改"的承诺

写法:把 const 写在参数表后面、函数体前面。

class Point {
private:
    int x, y;
public:
    void set(int a, int b) { x = a; y = b; }

    void show() const {          // ★ const 成员函数
        cout << "(" << x << ", " << y << ")" << endl;
    }
    //  int  getX() const { return x; }     // 返回值的也常写成 const
};

它带来两个效果:

效果 说明
① 里面不能改数据 在 const 成员函数里写 x = 10; 会报 assignment of read-only member——承诺被编译器检查
② 能被 const 对象调用 这是关键:const 对象只能调用 const 成员函数

第 ② 条的实际价值:

const Point origin;          // 一个 const 对象
origin.show();               // ✅ 可以:show() 是 const 的
origin.set(1, 2);            // ❌ 编译错误:set() 不是 const 的

为什么这么设计? 因为 const 对象的承诺是"我不会变"。如果允许调用一个可能会改的函数,这个承诺就破了。编译器替你把关。

这也是本篇最实用的一条实践建议:

只要是"只读"的成员函数(只打印、只计算、只返回数据),就顺手加上 const。

好处有三条:

  1. 能被 const 对象和 const 引用调用(8.3 讲的 const T & 传参,函数里就只能调 const 成员函数!)
  2. 把意图写进代码——看的人一眼知道"这个函数不改数据"
  3. 编译器帮你守规矩——万一手滑在里面改了数据,会立刻报错

特别提醒:用 const T & 传对象时,只有 const 成员函数能调。如果忘了给 show() 加 const,会看到这个熟悉的报错:

error: passing 'const Point' as 'this' argument discards qualifiers

看到 discards qualifiers,就是"传进来的是只读对象,但你的函数没标 const"。(qualifiers=限定符,这里指 const。)

四、动手 Demo:三种视角 + const 的三种考验

#include <iostream>
using namespace std;

class Book {
private:
    char  title[30];
    double price;

public:
    Book() : price(0) { title[0] = '\0'; }

    void set(const char *t, double p) {           // 会改数据:不能加 const
        strcpy(title, t);
        price = p;
    }

    void show() const {                            // 只读:加 const(好习惯)
        cout << "《" << title << "》 " << price << " 元" << endl;
    }

    double getPrice() const { return price; }      // 只读:加 const
};

// ① 传 const 引用:只能调 const 成员函数
void printBook(const Book &b) {
    b.show();                    // ✅ 因为 show() 是 const 的
    // b.set("X", 1);            // ❌ 去掉注释就报错:set 不是 const
    cout << "  价格读到 " << b.getPrice() << endl;
}

int main() {
    // 视角 1:对象用点
    Book b1;
    b1.set("C++入门", 59.8);
    b1.show();

    // 视角 1:指针用箭头
    Book *p = &b1;
    p->set("C++进阶", 88.0);
    p->show();

    // ② const 对象:只能调 const 成员函数
    const Book cb = b1;                 // 用 b1 造一个 const 对象(复制构造,10.2.2)
    cb.show();                          // ✅

    // ③ 传 const 引用进函数
    printBook(b1);
    return 0;
}

看到什么算成功:

《C++入门》 59.8 元
《C++进阶》 88 元
《C++进阶》 88 元
《C++进阶》 88 元
  价格读到 88

读结果三句话:

  1. p->set(...) 改了 b1——指针和对象指的是同一个东西(7.6.1)。
  2. const Book cb = b1; 之后还能 cb.show()——因为 show() 有 const(这是它存在的意义)。
  3. printBook(b1) 里只调了 show() 和 getPrice(),两个都是 const 成员函数——所以 const Book &b 这个窄门能走通。

30 秒实验:把 void show() const 里的 const 删掉,然后编译。你会看到 passing 'const Book' as 'this' argument discards qualifiers,报错会指向 cb.show(); 和 b.show(); 两行。加回 const 就好。 这个错以后一定会遇到——记住它说的是"你给的是一个只读对象,但函数没承诺只读"。

五、常见误区

  1. "const 成员函数和 const 成员变量是一回事"——不是。const 成员变量=这个数据不能改;const 成员函数=这个函数不修改对象的数据。前者管数据,后者管行为。
  2. "const 加在函数前面"——错位置。const 成员函数写在参数表后面:void show() const;。写在前面(const void show();)指的是返回值是 const,完全是另一回事。
  3. "给所有成员函数都加 const 就对了"——不行。会改数据的函数(set、move、push)绝对不能加,加了就编译不过。判据只有一条:这个函数改不改对象的数据?
  4. "类里面也不能访问别的对象的私有成员"——能访问。同一个类的不同对象之间,可以互相访问私有成员(因为它们是"自己人")。比如 void copyFrom(const Point &other) { x = other.x; } 是合法的——other.x 虽然私有,但你正站在 Point 的内部。
  5. "类外访问静态成员必须用对象"——不必,用类名更规范:Company::getTotal()。用对象调也能过(10.4 的 Demo 试过),但说明不了"它和对象无关"这个事实。
自测三问(点开对答案) 1. 对象和指针分别用什么符号访问成员?类里呢?——**对象用 `.`**(`p.set()`),**指针用 `->`**(`p->set()`);**类里的成员函数直接写成员名**,不用带对象名,也能访问全部权限的成员。 2. `const` 成员函数的两个作用是什么?——① **里面不能修改对象的数据**(承诺由编译器检查,写了就报 `assignment of read-only member`);② **能被 `const` 对象和 `const` 引用调用**——这也是为什么要给只读函数加 `const`:**不给的话,`const T &` 传进来的对象就用不了它。** 3. 报错 `discards qualifiers` 通常是什么原因?——你在**用只读(`const`)对象调用一个没有标 `const` 的成员函数**。解决办法:如果是只读函数,就给它补上 `const`。

相关笔记