知识点回顾
- 局部变量
在开始讲解函数之前,我们所理解的变量就是在内存中开辟一个存储数据的位置,并给它起个名字。因为那时候只有一个 main 函数,所以那时我们对它的作用范围一无所知,觉得定义了变量就随时随地可以使用它……直到我们学习函数才发现,不同函数之间定义的变量,它们是无法相互进行访问的。
- C99 新标准
C99 标准允许在 for 语句的第一个表达式部分声明变量,它的作用范围仅限于复合语句的内部。
值得一提的是,这里 for 语句因为定义了同名的i变量,所以它屏蔽了第一个定义的i变量。换句话说,在 for 语句的循环体中,无法直接访问到外边的i变量。
- C 语言允许在程序的任意位置声明变量
允许变量在需要时才声明是一件非常棒的事情,因为如果当你的函数体非常庞大的时候,你总不会愿意往前翻好几个屏幕去看某个变量 i、j、k 是什么意思吧。
有些朋友可能会有所担心:“要是到处定义变量,那一不小心重复定义了怎么办?”,其实这是编译器应该关心的问题(编译器会找出重复定义的变量并报错),你放心使用就可以了。
- 全局变量
在函数里边定义的,我们叫局部变量;在函数外边定义的,我们叫外部变量,也叫全局变量。有时候,我们可能需要在多个函数中使用共同的一个变量,那么就会用到全局变量。因为全局变量可以被本程序中其他函数所共用的。
- 全局变量初始化
与局部变量不同,如果不对全局变量进行初始化,那么它会自动初始化为 0。
- 当局部变量和全局变量同名时
如果在函数的内部存在一个与全局变量同名的局部变量,编译器并不会报错,而是再函数中屏蔽全局变量(也就是说在这个函数中,全局变量不起作用)。
- 如果一个全局变量在使用之后才被定义
那么你可以在对该全局变量进行访问之前,使用 extern 关键字对该变量名进行修饰。这样就相当于告诉编译器:这个变量我在后边定义了,你先别急着报错。
测试题
0. 为什么不建议大量使用全局变量?
答:因为大量使用全局变量会对程序结构产生不良的影响,而且可能导致程序中各个函数之间具有太多的数据联系(在模块化程序设计的指导下,我们应该尽量设计内聚性强,耦合性弱的模块。也就是要求你函数的功能要尽量单一,与其他函数的相互影响尽可能地少,而大量使用全局变量恰好背道而驰)。
1.请问在什么情况下变量 a 和变量 b 可以存放在同一个地址而里面的数据却允许不同?
答:当变量 a 和变量 b 分别为两个函数的局部变量时,就可能出现上述情况。
#include <stdio.h>
int func1(void);
int func2(void);
int func1(void)
{
int a = 520;
printf("a is %d, add of a is %p\n", a, &a);
}
int func2(void)
{
int b = 880;
printf("b is %d, add of b is %p\n", b, &b);
}
int main(void)
{
func1();
func2();
return 0;
}
2.“C 语言是变量在需要的时候才进行定义的”。那如果一个全局变量在被访问后才定义,便会报错……请问如何阻止悲剧的发生?
第一,将所有的全局变量都写在文件的起始位置(这招仅适用于单源文件程序);
第二,用 extern 关键字对后边定义的全局变量进行修饰。这样就相当于告诉编译器:这个变量我在后边定义了,你先别急着报错。
3. 请问下面代码依次会打印什么内容?
#include <stdio.h>
int i = 520;
int func(void);
int func(void)
{
printf("%d, ", i);
int i = 880;
for (int i = 1; i < 3; i++)
{
printf("%d, ", i);
}
printf("%d\n", i);
}
int main(void)
{
func();
return 0;
}
答:依次会输出 520, 1, 2, 880。
解析:不同地方声明的变量拥有不同的作用域,通常来说内层的变量作用域会覆盖外层的同名变量。
4. 请问下面代码有什么毛病?
#include <stdio.h>
int main(void)
{
char *name;
printf("What's your name:");
scanf("%s", name);
printf("Your name is %s\n", name);
return 0;
}
答:虽然程序声明了一个字符指针变量(指望它来存放字符串),但是并没有对其(name)进行初始化,这样也就意味着 name 可能是指向任何地方的,那么它就是一个悬空指针。前面我们强调过,使用悬空指针是极其危险的,上面代码就是一个很好的例子:虽然程序可以运行,但当你输入名字之后,程序立即崩溃(段错误)。改成char *name[1024]即可。
5.请列举全局变量的优点
第一,全局变量的使用,使得多个函数之间的“通信”变得简单。比如课堂中小郭被“抱”的故事,如果不使用全局变量,那么要统计小郭被“抱”了多少次就要麻烦许多。
第二,全局变量一旦声明,内存地址就固定了,程序的读写效率高。如果使用局部变量,每次函数调用都要经历开辟栈空间,和释放栈空间的过程。
动手(不求甚解)
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void shuffle(int array[], int length);
void deal(int array[], int player[3][14]);
void show(char names[3][40], int player[3][14]);
void shuffle(int array[], int length)
{
int index, temp, i;
srand(time(NULL));
for (i = 0; i < length; i++)
{
index = rand() % (length - i) + i;
if (index != i)
{
temp = array[i];
array[i] = array[index];
array[index] = temp;
}
}
}
void deal(int array[], int player[3][14])
{
int i, j, k = 0;
// 模拟发牌:拿起一副无序的扑克每人轮流发一张牌
for (i = 0; i < 14; i++)
{
for (j = 0; j < 3; j++)
{
player[j][i] = array[k++];
}
}
}
void show(char names[3][40], int player[3][14])
{
int i, j, poker;
printf("\n方=方角,梅=梅花,红=红桃,黑=黑桃\n\n");
for (i = 0; i < 3; i++)
{
printf("%s手上的牌是:", names[i]);
for (j = 0; j < 14; j++)
{
poker = player[i][j];
if (poker < 11 && 0 < poker)
{
printf("方%d ", poker);
}
else if (poker < 21 && 10 < poker)
{
printf("梅%d ", poker-10);
}
else if (poker < 31 && 20 < poker)
{
printf("红%d ", poker-20);
}
else if (poker < 41 && 30 < poker)
{
printf("黑%d ", poker-30);
}
else
{
switch (poker)
{
case 41: printf("方J "); break;
case 42: printf("方Q "); break;
case 43: printf("方K "); break;
case 44: printf("梅J "); break;
case 45: printf("梅Q "); break;
case 46: printf("梅K "); break;
case 47: printf("红J "); break;
case 48: printf("红Q "); break;
case 49: printf("红K "); break;
case 50: printf("黑J "); break;
case 51: printf("黑Q "); break;
case 52: printf("黑K "); break;
case 53: printf("小王 "); break;
case 54: printf("大王 "); break;
}
}
}
printf("\n\n");
}
}
int main(void)
{
int array[54];
int player[3][14];
int i, ch;
char names[3][40];
// 初始化扑克牌
// 1~10代表方(角)1~10, 41、42、43代表方(角)J、Q、K
// 11~20代表梅(花)1~10, 44、45、46代表梅(花)J、Q、K
// 21~30代表红(桃)1~10, 47、48、49代表红(桃)J、Q、K
// 31~40代表黑(桃)1~10, 50、51、52代表黑(桃)J、Q、K
// 53、54当然就代表小王大王啦~
for (i = 0; i < 54; i++)
{
array[i] = i + 1;
}
for (i = 0; i < 3; i++)
{
printf("\n请输入%d号玩家的名字:", i+1);
scanf("%s", names[i]);
}
do
{
shuffle(array, 54);
deal(array, player);
show(names, player);
printf("重新洗牌(Y/N)?");
do
{
ch = getchar(); // 过滤输入缓冲区的其他字符
} while (ch != 'Y' && ch != 'N');
} while (ch == 'Y');
return 0;
}