类和对象
第 14 课用 struct 把几样东西放在一起。它很方便,可也有个问题:谁都能直接改里面的成员。一个银行账户,余额随手就能改成负数:
1#include <iostream>2#include <string>3 4struct Account {5 std::string owner;6 int balance;7};8 9int main() {10 Account acc{"小林", 100};11 acc.balance = -5000;12 std::cout << acc.owner << " 的余额:" << acc.balance << '\n';13 return 0;14}
小林 的余额:-5000
编译器不拦着,程序也照常跑完。这一课讲 class:把数据藏起来,只让外面通过几个函数用它,规矩由这个类型自己守着。
成员函数
struct 和 class 里不光能放变量,还能放函数。写在里面的函数叫成员函数,它直接用得到同一个对象的成员变量:
1#include <iostream>2 3struct Counter {4 int count = 0;5 6 void add() {7 count++;8 }9 10 void show() {11 std::cout << "现在是 " << count << '\n';12 }13};14 15int main() {16 Counter people;17 Counter cars;18 people.add();19 people.add();20 cars.add();21 people.show();22 cars.show();23 return 0;24}
现在是 2 现在是 1
int count = 0; 给成员一个起始值。Counter 是类型,people 和 cars 是这个类型的两个对象,各有各的 count。调用成员函数也用 .:people.add() 加的是 people 的 count,和 cars 没关系。点单步,看画面上两个对象各自的字段怎么变。
class 和 private
把 struct 换成 class,再用 public: 和 private: 分开:public: 下面的谁都能用,private: 下面的只有这个类自己的成员函数能用。
1#include <iostream>2 3class Account {4public:5 void deposit(int amount) {6 if (amount > 0) {7 balance_ += amount;8 }9 }10 11 int balance() {12 return balance_;13 }14 15private:16 int balance_ = 0;17};18 19int main() {20 Account acc;21 acc.deposit(300);22 acc.deposit(-50);23 std::cout << "余额:" << acc.balance() << '\n';24 return 0;25}
余额:300
余额 balance_ 是私有的,外面只能通过 deposit 存钱,deposit 又不收负数,所以 -50 没存进去。这门课给私有的成员变量名后面加一个下划线(balance_),一眼就能看出哪些是成员;读余额的函数就可以叫 balance(),不会撞名。
外面硬要直接改,就是编译错误:
1#include <iostream>2 3class Account {4public:5 int balance() {6 return balance_;7 }8 9private:10 int balance_ = 0;11};12 13int main() {14 Account acc;15 acc.balance_ = -5000;16 std::cout << acc.balance() << '\n';17 return 0;18}
main.cpp: In function 'int main()':
main.cpp:15:9: error: 'int Account::balance_' is private within this context
15 | acc.balance_ = -5000;
| ^~~~~~~~
main.cpp:10:9: note: declared private here
10 | int balance_ = 0;
| ^~~~~~~~先看第一个 error::'int Account::balance_' is private within this context,balance_ 是私有的,在这里用不了。g++ 下面还会用 note: 指出它是在哪一行声明的。
为什么要 private
把数据藏起来,不是为了让人用着麻烦,而是让规矩只写在一个地方。账户的规矩是「余额不能变成负数」,那么取钱的检查就写在 withdraw 里,外面谁也绕不过去:
1#include <iostream>2 3class Account {4public:5 void deposit(int amount) {6 if (amount > 0) {7 balance_ += amount;8 }9 }10 11 bool withdraw(int amount) {12 if (amount <= 0 || amount > balance_) {13 return false;14 }15 balance_ -= amount;16 return true;17 }18 19 int balance() {20 return balance_;21 }22 23private:24 int balance_ = 0;25};26 27int main() {28 Account acc;29 acc.deposit(100);30 std::cout << acc.withdraw(30) << '\n';31 std::cout << acc.withdraw(500) << '\n';32 std::cout << "余额:" << acc.balance() << '\n';33 return 0;34}
1 0 余额:70
取 30 成功(显示 1),取 500 不够(显示 0),余额还是 70。以后哪里出了负数,只要查这个类就行。
还有一个好处:外面只认得 deposit、withdraw、balance 这几个函数。哪天想把余额改成按「分」存,或者加一条流水记录,只改类里面,用它的代码一行都不用动。
这段程序显示什么?
1#include <iostream>2 3class Light {4public:5 void toggle() {6 on_ = !on_;7 if (on_) {8 times_++;9 }10 }11 12 int times() {13 return times_;14 }15 16private:17 bool on_ = false;18 int times_ = 0;19};20 21int main() {22 Light lamp;23 lamp.toggle();24 lamp.toggle();25 lamp.toggle();26 std::cout << lamp.times() << '\n';27 return 0;28}
times_ 只在打开时加 1。灯从关开始:第一次打开(1),第二次关上,第三次又打开(2)。
const 成员函数
第 10 课说过,大的东西只看不改时传 const 引用。可是把 Account 用 const Account& 传进函数,调用 balance() 却编译不过:
1#include <iostream>2 3class Account {4public:5 int balance() {6 return balance_;7 }8 9private:10 int balance_ = 70;11};12 13void report(const Account& acc) {14 std::cout << "余额:" << acc.balance() << '\n';15}16 17int main() {18 Account acc;19 report(acc);20 return 0;21}
main.cpp: In function 'void report(const Account&)':
main.cpp:14:41: error: passing 'const Account' as 'this' argument discards qualifiers [-fpermissive]
14 | std::cout << "余额:" << acc.balance() << '\n';
| ~~~~~~~~~~~^~报错说 passing 'const Account' as 'this' argument discards qualifiers:acc 是 const 的,可 balance() 没有保证自己不改对象,编译器不敢调用它。(this 是指向对象自己的指针,第 19 课讲指针时再说。)
办法是在只看不改的成员函数的括号后面加 const:
1#include <iostream>2 3class Account {4public:5 int balance() const {6 return balance_;7 }8 9private:10 int balance_ = 70;11};12 13void report(const Account& acc) {14 std::cout << "余额:" << acc.balance() << '\n';15}16 17int main() {18 Account acc;19 report(acc);20 return 0;21}
余额:70
int balance() const 说的是:这个函数不改对象。要是在里面改了 balance_,就是编译错误。只看不改的成员函数(读数、显示、计算)都加上 const,养成习惯。
一个温度类
再看一个完整的例子。温度不能低于绝对零度 −273.15 °C,set_celsius 守着这条规矩;华氏度不存,要的时候现算:
1#include <iostream>2 3class Temperature {4public:5 bool set_celsius(double c) {6 if (c < -273.15) {7 return false;8 }9 celsius_ = c;10 return true;11 }12 13 double celsius() const {14 return celsius_;15 }16 17 double fahrenheit() const {18 return celsius_ * 9 / 5 + 32;19 }20 21private:22 double celsius_ = 0;23};24 25int main() {26 Temperature t;27 t.set_celsius(25);28 t.set_celsius(-300);29 std::cout << t.celsius() << " °C = " << t.fahrenheit() << " °F\n";30 return 0;31}
25 °C = 77 °F
-300 被拒绝了,温度还是 25 度。现在每个对象一开始都是 0 度,要另外调用 set_celsius 才有值;下一课的构造函数让对象一出生就带上该有的值。
struct 和 class 的区别
在 C++ 里,struct 和 class 几乎一样,struct 也能有成员函数、private:。只差一点:**没写 public: 或 private: 时**,struct 的成员默认是公开的,class 的默认是私有的。
这段程序会怎样?
1#include <iostream>2 3class Point {4 int x = 3;5};6 7int main() {8 Point p;9 std::cout << p.x << '\n';10 return 0;11}
class 里没写 public: 的成员默认是私有的,main 里用 p.x 就是编译错误:'int Point::x' is private within this context。换成 struct 就能显示 3。
习惯上:只是把几样数据放在一起、没什么规矩要守的,用 struct(像第 14 课的 Item);有规矩要守、数据要藏起来的,用 class,先写 public: 部分,再写 private: 部分。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
修好它:只看不改要加 const
✓程序编译不过。只改 Account 类,让它显示 余额:250。
余额:250
1#include <iostream>2 3class Account {4public:5 void deposit(int amount) {6 balance_ += amount;7 }8 9 int balance() {10 return balance_;11 }12 13private:14 int balance_ = 0;15};16 17void report(const Account& acc) {18 std::cout << "余额:" << acc.balance() << '\n';19}20 21int main() {22 Account acc;23 acc.deposit(250);24 report(acc);25 return 0;26}
计步器
✓写完 StepCounter:add(steps) 把步数加上去,但负数不算;total() 返回一共走了多少步。程序要显示 今天走了 9000 步。
今天走了 9000 步
1#include <iostream>2 3class StepCounter {4public:5 6private:7 int total_ = 0;8};9 10int main() {11 StepCounter today;12 today.add(3500);13 today.add(-200);14 today.add(5500);15 std::cout << "今天走了 " << today.total() << " 步\n";16 return 0;17}
歌单有多长
✓Playlist 用 vector 存每首歌的秒数。给它加一个 const 成员函数 total_minutes(),返回一共多少分钟(整数除法,不足一分钟的不算)。程序要显示 3 首,11 分钟。
3 首,11 分钟
1#include <iostream>2#include <vector>3 4class Playlist {5public:6 void add(int seconds) {7 songs_.push_back(seconds);8 }9 10 int count() const {11 return songs_.size();12 }13 14private:15 std::vector<int> songs_;16};17 18int main() {19 Playlist p;20 p.add(215);21 p.add(250);22 p.add(240);23 std::cout << p.count() << " 首," << p.total_minutes() << " 分钟\n";24 return 0;25}
这一课学会了
- 写在
struct/class里的函数是成员函数,用对象.函数()调用,直接用得到这个对象的成员变量;每个对象有自己的一份成员变量。 public:下面的谁都能用,private:下面的只有类自己的成员函数能用;外面用了私有成员是编译错误(is private within this context)。- 把数据设成
private,改它只能通过成员函数,规矩(余额不为负、温度不低于绝对零度)就只写在一个地方。 - 只看不改的成员函数在括号后面加
const;const对象和const引用只能调用这样的函数。 struct和class只差默认的访问权限:struct默认公开,class默认私有。- 这门课给私有成员变量名加下划线:
balance_。