Skip to content

Commit 4cc527d

Browse files
committed
Update 06-scope-and-storage.md
1 parent f62fce1 commit 4cc527d

1 file changed

Lines changed: 171 additions & 16 deletions

File tree

documents/vol1-fundamentals/c_tutorials/06-scope-and-storage.md

Lines changed: 171 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
---
22
chapter: 1
33
cpp_standard:
4-
- 11
4+
- 11
55
description: 深入理解 C 语言的作用域规则、存储类别和链接性,掌握 static 的三种用法
66
difficulty: beginner
77
order: 8
88
platform: host
99
prerequisites:
10-
- 控制流:让程序学会选择和重复
10+
- 控制流:让程序学会选择和重复
1111
reading_time_minutes: 20
1212
tags:
13-
- host
14-
- cpp-modern
15-
- beginner
16-
- 入门
17-
- 基础
13+
- host
14+
- cpp-modern
15+
- beginner
16+
- 入门
17+
- 基础
1818
title: 作用域与存储类别
1919
---
20+
2021
# 作用域与存储类别
2122

2223
如果你写过超过两个源文件的项目,大概率已经踩过这样的坑:两个文件里都定义了一个叫 `count` 的全局变量,编译的时候链接器一脸懵逼地告诉你 `multiple definition`。或者更隐蔽的情况——你在某个 `.c` 文件里定义了一个辅助函数,结果别的文件不小心也调用了它,后来你改了那个函数的实现,调用方毫无预警地崩了。
@@ -261,13 +262,13 @@ extern int kValue = 42; // 千万别这么干!
261262

262263
这三者的关系可以用一个表格来总结:
263264

264-
| 声明位置 | 关键字 | 链接性 | 作用域 | 生命周期 |
265-
| --- | --- | --- | --- | --- |
266-
| 函数内 | (无) ||| 自动 |
267-
| 函数内 | `static` ||| 静态 |
268-
| 函数外 | (无) | 外部 | 文件 | 静态 |
269-
| 函数外 | `static` | 内部 | 文件 | 静态 |
270-
| 函数外 | `extern` | (取决于首次声明) | 文件 | 静态 |
265+
| 声明位置 | 关键字 | 链接性 | 作用域 | 生命周期 |
266+
| -------- | -------- | ------------------ | ------ | -------- |
267+
| 函数内 | (无) | | | 自动 |
268+
| 函数内 | `static` | | | 静态 |
269+
| 函数外 | (无) | 外部 | 文件 | 静态 |
270+
| 函数外 | `static` | 内部 | 文件 | 静态 |
271+
| 函数外 | `extern` | (取决于首次声明) | 文件 | 静态 |
271272

272273
这个表格值得多看几眼——注意函数外的 `static` 改变的是链接性(从外部变成内部),而不是作用域或生命周期。
273274

@@ -455,6 +456,58 @@ void counter_reset(void);
455456

456457
请自行实现 `counter.c`
457458

459+
### 练习 1 参考答案
460+
461+
main.c
462+
463+
```c
464+
#include <stdio.h>
465+
#include "counter.h"
466+
467+
int main(void) {
468+
printf("%d\n",counter_get()); //输出应当是0
469+
counter_increment();
470+
printf("%d\n",counter_get()); //输出应当是1
471+
counter_increment();
472+
printf("%d\n",counter_get()); //输出应当是2
473+
counter_reset();
474+
printf("%d\n",counter_get()); //输出应当是0
475+
return 0;
476+
}
477+
```
478+
479+
counter.h
480+
481+
```c
482+
#ifndef MODERNCPP_PRE8_1_COUNTER_H
483+
#define MODERNCPP_PRE8_1_COUNTER_H
484+
485+
void counter_increment(void);
486+
int counter_get(void);
487+
void counter_reset(void);
488+
489+
#endif //MODERNCPP_PRE8_1_COUNTER_H
490+
```
491+
492+
counter.c
493+
494+
```c
495+
#include "counter.h"
496+
497+
static int counter = 0;
498+
void counter_increment(void) {
499+
counter++;
500+
}
501+
502+
void counter_reset(void) {
503+
counter = 0;
504+
}
505+
506+
int counter_get(void) {
507+
return counter;
508+
}
509+
```
510+
458511
### 练习 2:多文件符号可见性
459512
460513
创建三个文件 `a.c`、`b.c`、`main.c`。要求:
@@ -471,21 +524,123 @@ void counter_reset(void);
471524
// 各 .c 文件的实现留给你
472525
```
473526

527+
### 练习 2 参考答案
528+
529+
main.c
530+
531+
```c
532+
#include <stdio.h>
533+
#include "a.h"
534+
#include "b.h"
535+
536+
int main(void) {
537+
538+
printf("%d\n",kSharedValue); //输出应当是0
539+
set_kSharedValue(100);
540+
printf("%d\n",kSharedValue); //输出应当是100
541+
542+
return 0;
543+
}
544+
```
545+
546+
a.h
547+
548+
```c
549+
#ifndef MODERNCPP_PRE8_2_A_H
550+
#define MODERNCPP_PRE8_2_A_H
551+
552+
extern int kSharedValue;
553+
554+
#endif //MODERNCPP_PRE8_2_A_H
555+
```
556+
557+
b.h
558+
559+
```c
560+
#ifndef MODERNCPP_PRE8_2_B_H
561+
#define MODERNCPP_PRE8_2_B_H
562+
563+
void set_kSharedValue(int value);
564+
565+
#endif //MODERNCPP_PRE8_2_B_H
566+
```
567+
568+
a.c
569+
570+
```c
571+
#include <stdio.h>
572+
#include "a.h"
573+
574+
int kSharedValue = 0;
575+
static void helper_a(void) {
576+
printf("need help?");
577+
}
578+
```
579+
580+
b.c
581+
582+
```c
583+
#include <stdio.h>
584+
#include "b.h"
585+
#include "a.h"
586+
587+
588+
static void helper_a(void) {
589+
printf("need help?");
590+
}
591+
void set_kSharedValue(int value) {
592+
kSharedValue = value;
593+
}
594+
```
595+
474596
### 练习 3:延迟初始化
475597

476598
`static` 局部变量实现一个 `get_config` 函数:第一次调用时执行初始化(打印 "Initializing..." 并设置默认值),后续调用直接返回已初始化的值,不再重新初始化。
477599

478600
```c
601+
typedef struct {
602+
int max_connections; //建议设为5
603+
int timeout_ms; //建议设为500
604+
const char* server_name; //建议设为localhost
605+
} Config;
606+
607+
const Config* get_config(void);
608+
```
609+
610+
> 提示:`static` 局部变量只在第一次进入函数时被初始化——正好可以用来实现"只初始化一次"的语义。
611+
612+
### 练习 3 参考答案
613+
614+
```c
615+
#include <stdio.h>
616+
479617
typedef struct {
480618
int max_connections;
481619
int timeout_ms;
482620
const char* server_name;
483621
} Config;
484622
485623
const Config* get_config(void);
486-
```
487624
488-
> 提示:`static` 局部变量只在第一次进入函数时被初始化——正好可以用来实现"只初始化一次"的语义。
625+
int main(void) {
626+
get_config(); //应当输出"Initializing..."
627+
printf("%d %d %s\n",get_config()->max_connections, get_config()->timeout_ms, get_config()->server_name); //应当输出"5 500 localhost"
628+
return 0;
629+
}
630+
631+
const Config* get_config(void) {
632+
static Config config;
633+
static int initialized = 0;
634+
if (!initialized) {
635+
printf("Initializing...\n");
636+
config.max_connections = 5;
637+
config.timeout_ms = 500;
638+
config.server_name = "localhost";
639+
initialized = 1;
640+
}
641+
return &config;
642+
}
643+
```
489644

490645
## 参考资源
491646

0 commit comments

Comments
 (0)