访问类成员有三种"视角":类外面(对象用 .、指针用 ->)、类里面(成员函数直接写成员名,不用带对象名)、类名的视角(静态成员用 类名::)。能不能访问,由 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。
好处有三条:
- 能被
const对象和const引用调用(8.3 讲的const T &传参,函数里就只能调const成员函数!) - 把意图写进代码——看的人一眼知道"这个函数不改数据"
- 编译器帮你守规矩——万一手滑在里面改了数据,会立刻报错
特别提醒:用 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
读结果三句话:
p->set(...)改了b1——指针和对象指的是同一个东西(7.6.1)。const Book cb = b1;之后还能cb.show()——因为show()有const(这是它存在的意义)。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 就好。 这个错以后一定会遇到——记住它说的是"你给的是一个只读对象,但函数没承诺只读"。
五、常见误区
- "
const成员函数和const成员变量是一回事"——不是。const成员变量=这个数据不能改;const成员函数=这个函数不修改对象的数据。前者管数据,后者管行为。 - "
const加在函数前面"——错位置。const成员函数写在参数表后面:void show() const;。写在前面(const void show();)指的是返回值是const,完全是另一回事。 - "给所有成员函数都加
const就对了"——不行。会改数据的函数(set、move、push)绝对不能加,加了就编译不过。判据只有一条:这个函数改不改对象的数据? - "类里面也不能访问别的对象的私有成员"——能访问。同一个类的不同对象之间,可以互相访问私有成员(因为它们是"自己人")。比如
void copyFrom(const Point &other) { x = other.x; }是合法的——other.x虽然私有,但你正站在Point的内部。 - "类外访问静态成员必须用对象"——不必,用类名更规范:
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`。- ☐ 10.4.2 内联成员函数(下一节)
相关笔记
- 类成员 —— 上一节:类成员地图
- 内联成员函数 —— 下一节:写在类里的函数默认内联
- 使用结构体指针变量做函数参数 —— "变量点、指针箭头"的由来
- 使用引用传递参数 ——
const T &:为什么只读函数必须加const - 变量作用域 —— 类也是一种作用域边界
- 🔷 C++基础 · 索引 —— 全课路线图