结构体
一个学生有名字,也有分数。到现在为止,我们只能用两个变量分开存:name 一个,score 一个。学生一多,就要两个数组,还得小心让它们一一对上。结构体(struct)把几样相关的数据装进一个变量里:
1#include <stdio.h>2 3struct student {4 char name[20];5 int score;6};7 8int main(void) {9 struct student s = { "小林", 95 };10 printf("%s:%d 分\n", s.name, s.score);11 return 0;12}
小林:95 分
点单步看看:s 在内存里画成一个大框,里面是 name 和 score 两个小格。它们是一个变量的两个部分,总是在一起。
定义一个结构体
struct student { ... }; 定义了一种新的类型,说明这种变量里有什么。里面的每一项叫成员:
| 部分 | 在上面的例子里 | 是什么 |
|---|---|---|
| 关键字 | struct | 说明下面是一个结构体 |
| 名字 | student | 这种结构体叫什么,用的时候写 struct student |
| 成员 | char name[20]; int score; | 每一项都写类型和名字,和声明变量一样 |
| 结尾 | }; | 花括号后面要有分号,很容易忘 |
定义只是说明「长什么样」,不占内存;写 struct student s; 才真的有了一个变量。用点号 . 拿到它的成员,读写都行。成员可以一项一项地赋值,字符串成员用 strcpy(第 14 课):
1#include <stdio.h>2#include <string.h>3 4struct product {5 char name[20];6 double price;7 int stock;8};9 10int main(void) {11 struct product p;12 strcpy(p.name, "笔记本");13 p.price = 12.5;14 p.stock = 40;15 p.stock = p.stock - 3;16 printf("%s %.2f 元,还剩 %d 本\n", p.name, p.price, p.stock);17 return 0;18}
笔记本 12.50 元,还剩 37 本
p.stock 用起来就是一个普通的 int:可以赋值、可以参加运算。
初始化
声明时可以用花括号一次给全:值按成员定义的顺序一个对一个。给的值不够,剩下的成员是 0(字符串就是空的):
1#include <stdio.h>2 3struct product {4 char name[20];5 double price;6 int stock;7};8 9int main(void) {10 struct product a = { "铅笔", 1.5, 120 };11 struct product b = { "橡皮", 2.0 };12 printf("%s %.2f %d\n", a.name, a.price, a.stock);13 printf("%s %.2f %d\n", b.name, b.price, b.stock);14 return 0;15}
铅笔 1.50 120 橡皮 2.00 0
顺序很要紧:写成 { 1.5, "铅笔", 120 } 就对不上了,编译器会报错或者给警告。
赋值是复制
数组不能用 = 整个赋值,结构体可以。b = a; 把 a 的每个成员都复制给 b,连里面的字符数组也一起复制。复制完它们就是两个互不相干的变量:
这段程序显示什么?
1#include <stdio.h>2 3struct student {4 char name[20];5 int score;6};7 8int main(void) {9 struct student a = { "小林", 80 };10 struct student b = a;11 b.score = 100;12 printf("%d %d\n", a.score, b.score);13 return 0;14}
b = a 复制了一份:b 有自己的 name 和 score。改 b.score 不会碰到 a,所以 a.score 还是 80。点单步能看到内存里是两个分开的框。
可是两个结构体不能用 == 比较,编译器不认:
1#include <stdio.h>2 3struct point {4 int x;5 int y;6};7 8int main(void) {9 struct point a = { 1, 2 };10 struct point b = { 1, 2 };11 if (a == b) {12 printf("一样\n");13 }14 return 0;15}
main.c: In function 'main':
main.c:11:11: error: invalid operands to binary == (have 'struct point' and 'struct point')
11 | if (a == b) {
| ^~要比,就一个成员一个成员地比:a.x == b.x && a.y == b.y。
结构体数组
结构体也能放进数组。songs[i] 是一首歌,songs[i].seconds 是这首歌的秒数。一个歌单:
1#include <stdio.h>2 3struct song {4 char title[30];5 int seconds;6};7 8int main(void) {9 struct song songs[3] = {10 { "清晨", 269 },11 { "海边", 226 },12 { "回家的路", 223 }13 };14 int total = 0;15 for (int i = 0; i < 3; i++) {16 printf("%d. %s(%d:%02d)\n", i + 1, songs[i].title, songs[i].seconds / 60, songs[i].seconds % 60);17 total += songs[i].seconds;18 }19 printf("一共 %d 分 %d 秒\n", total / 60, total % 60);20 return 0;21}
1. 清晨(4:29) 2. 海边(3:46) 3. 回家的路(3:43) 一共 11 分 58 秒
每首歌的名字和长度放在一起,不会对错行。%02d 让秒数不够两位时前面补 0,4:05 不会显示成 4:5。
传给函数
结构体可以当参数。和 int 一样,传的是值:参数是一份完整的副本(第 8 课)。所以函数里改它,外面看不到:
1#include <stdio.h>2 3struct account {4 char owner[20];5 int balance;6};7 8void deposit(struct account a, int amount) {9 a.balance += amount;10 printf("函数里:%d\n", a.balance);11}12 13int main(void) {14 struct account acc = { "小林", 100 };15 deposit(acc, 50);16 printf("main 里:%d\n", acc.balance);17 return 0;18}
函数里:150 main 里:100
点单步,走进 deposit 时,它的栈帧里多出一个完整的 a:owner 和 balance 都复制过去了。变的是这个副本,函数结束它就没了。
一个办法是让函数把改好的结构体返回来,由调用的地方存回去:
1#include <stdio.h>2 3struct account {4 char owner[20];5 int balance;6};7 8struct account deposit(struct account a, int amount) {9 a.balance += amount;10 return a;11}12 13int main(void) {14 struct account acc = { "小林", 100 };15 acc = deposit(acc, 50);16 printf("%s的余额:%d\n", acc.owner, acc.balance);17 return 0;18}
小林的余额:150
小技巧: 结构体大了,每次调用都复制一遍很浪费。更常见的办法是传它的地址,函数通过指针直接改,下一课就讲。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
一本书
✓定义 struct book,成员是书名 title(char 数组,20 个字节)和页数 pages(int)。声明一本书 { "三体", 302 },显示 三体:302 页。
三体:302 页
1#include <stdio.h>2 3// 在这里定义 struct book4 5int main(void) {6 7 return 0;8}
最便宜的
✓在 items 里找出价格最低的商品,显示 最便宜:牙刷 3.50。
最便宜:牙刷 3.50
1#include <stdio.h>2 3struct product {4 char name[20];5 double price;6};7 8int main(void) {9 struct product items[4] = {10 { "牙膏", 8.90 },11 { "毛巾", 12.00 },12 { "牙刷", 3.50 },13 { "香皂", 4.20 }14 };15 16 return 0;17}
没打上折
✓half_price 想把价格减半,可显示的还是原价。改好它:让函数返回改好的商品,main 把它存回 p。程序要显示 毛巾 6.00。
毛巾 6.00
1#include <stdio.h>2 3struct product {4 char name[20];5 double price;6};7 8void half_price(struct product p) {9 p.price = p.price / 2;10}11 12int main(void) {13 struct product p = { "毛巾", 12.00 };14 half_price(p);15 printf("%s %.2f\n", p.name, p.price);16 return 0;17}
这一课学会了
struct 名字 { 成员; ... };定义一种新类型,把几样相关的数据放在一起;结尾要分号。- 声明时用
{ ... }按顺序初始化,没给的成员是0;也可以一项一项赋值,字符串成员用strcpy。 - 用点号
.读写成员,s.score用起来就是一个普通变量。 b = a;复制整个结构体,之后两个互不相干;结构体不能用==比较。- 结构体数组把一组记录放在一起:
songs[i].title。 - 结构体传给函数传的是副本;要改外面的,就把新的结构体返回来(或者传指针,下一课讲)。