知识点回顾(版权属于fishc.com)
1.作用域
上一讲我们是从变量的作用域角度将变量划分为局部变量和全局变量,这是从空间的角度来分析的。我们发现当变量被定义在程序的不同位置时,它的作用范围是不一样的。这个范围就是我们说的作用域。
c语言编译器可以确认4种不同类型的作用域:代码块作用域、文件作用域、原型作用域、函数作用域。
1.1 代码块作用域(block scope)
最常见的就是代码块作用域。所谓代码块,就是位于一对花括号之间的所有语句。
1.2文件作用域(file scope)
任何在代码块之外声明的标识符都具有文件作用域,作用范围是从他们的声明位置开始的到文件的结尾处都是可以访问的。另外,函数名也具有文件作用域,因为函数名本身也是在代码块之外的。
1.3 原型作用域(prototype scope)
原型作用域只使用于那些在函数原型中声明的参数名。我们知道函数在声明的时候可以不写参数的名字(但是参数类型是必须要写上的),其实多尝试你还可以发现,函数原型的参数名还可以随便写一个名字,不必与形式参数相匹配(当然,这样毫无意义)。允许你这么做,只是因为原型作用域齐了作用。
1.4 函数作用域(function scope)
函数作用域只适用于goto语句的标签,作用将goto语句的标签限制在同一个函数内部,以及防止出现同名标签。
3. 连接属性
简单的来说,编译器将你的源文件变成可执行程序需要经过两个步骤:编译和链接。编译过程主要是将你写的源代码生成机器码格式的目标文件,而链接过程则是将相关的库文件添加进来(比如你在源文件中调用了 stdio 库的 printf 函数,那么在这个过程中,就把 printf 函数的代码添加进来),然后整合成一个可执行程序。
链接属性是个什么东西呢?
我们知道大型的程序都有好些个源文件构成,那么在不同文件中的同名标识符,编译器是如何处理的呢?这就要看链接属性了
在 C 语言中,链接属性一共有三种:
external(外部的)-- 多个文件中声明的同名标识符表示同一个实体
internal(内部的)-- 单个文件中声明的同名标识符表示同一个实体
none(无)-- 声明的同名标识符被当作独立不同的实体(比如函数的局部变量,因为它们被当作独立不同的实体,所以不同函数间同名的局部变量并不会发生冲突)
默认情况下,具备文件作用域的标识符拥有 external 属性。也就是说该标识符允许跨文件访问。对于 external 属性的标识符,无论在不同文件中声明多少次,表示的都是同一个实体
使用 static 关键字可以使得原先拥有 external 属性的标识符变为 internal 属性。这里有两点需要注意:
使用 static 关键字修改链接属性,只对具有文件作用域的标识符生效(对于拥有其他作用域的标识符是另一种功能)
链接属性只能修改一次,也就是说一旦将标识符的链接属性变为 internal,就无法变回 external 了
测试题
0. 是什么决定了两个同名变量是否会发生冲突?
答:看这两个同名变量是否处在同一作用域中。
1. goto 语句的作用域是?
答:goto 语句受函数作用域(function scope)所限制,因此 goto 语句仅能在函数体内部跳转,不能跨函数跳跃。
2. 全局变量和函数名所拥有的作用域是否完全相同?
答:局部变量的作用域一般位于代码块作用域,全局变量一般位于文件作用域。 看成局部变量了
答:是,因为任何在代码块之外声明的标识符都具有文件作用域(比如全局变量),作用范围是从它们的声明位置开始,到文件的结尾处都是可以访问的。另外,函数名也具有文件作用域,因为函数名本身也是在代码块之外。
3. 请用一句话解释声明和定义的区别?
答:声明属于文件作用域,定义属于 代码块作用域。
正解: 当一个变量被定义时候,编译器为变量申请内存空间并填充一些值。当一个变量被声明的时候,编译器就知道该变量被定义在其他地方。声明通知变化一起该变量名及相关的类型已存在,不需要在为此申请内存空间。(一言蔽之:声明和定义的主要区别就是是否为其分配内存空间)
4. 默认情况下,全局变量拥有哪一种链接属性?
答:全局变量具有external属性。
默认情况下,具备文件作用域的标识符拥有 external 属性。也就是说该标识符允许跨文件访问。对于 external 属性的标识符,无论在不同文件中声明多少次,表示的都是同一个实体。
5. 默认情况下,局部变量拥有哪一种链接属性?
答:局部变量拥有internal属性。
正解:局部变量拥有none链接属性。因此声明的同名标识符被当做独立不同的实体(比如函数的局部变量,因此他们被当做独立不同的实体,所以不同函数间同名的局部变量并不会发生冲突))
6. 请问下边代码会输出什么内容?
#include <stdio.h>
void func();
void func()
{
A:
printf("NO!\n");
B:
printf("YES!\n");
}
int main()
{
goto A;
func();
goto B;
return 0;
}
答:会报错,因为goto标签的作用域仅限于同一函数体内部
7. 对于不带参数的函数,定义时在参数列表的位置写上 void 有啥好处?
答:告诉编译器此函数没有返回任何类型的值,可以帮助人们检查错误。
.但写上 void 有一个好处,那就是当你试图对一个参数为 void 的函数传入参数时,编译器会毫不犹豫地给予你错误提示!
动动手
0. 实现猴子排序(Bogo Sort)算法。这一题要求你自己写一个 bogo_sort 函数,实现上述的算法内容。提供给你 main 函数:
int main(void)
{
int array[] = {73, 108, 111, 118, 101, 70, 105, 104, 67};
int i, length;
time_t begin, end;
begin = time(NULL);
length = sizeof(array) / sizeof(array[0]);
bogo_sort(array, length);
printf("排序后的结果是:");
for (i = 0; i < length; i++)
{
printf("%d ", array[i]);
}
putchar('\n');
end = time(NULL);
printf("总共耗时:%ld秒\n", end - begin);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int in_order(int array[], int length);
void shuffle(int array[], int length);
void bogo_sort(int array[], int length);
int in_order(int array[], int length)
{
int i = 0;
while (array[i] <= array[i+1] && ++i < length - 1)
;
if (i == length - 1)
{
return 1;
}
else
{
return 0;
}
}
void shuffle(int array[], int length)
{
int index, temp, i;
static int t1, t2;
srand(t1);
t1 = rand();
t2 = time(NULL);
srand(t1+t2);
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 bogo_sort(int array[], int length)
{
while (!in_order(array, length))
{
shuffle(array, length);
}
}
int main(void)
{
int array[] = {73, 108, 111, 118, 101, 70, 105, 104, 67};
int i, length;
time_t begin, end;
begin = time(NULL);
length = sizeof(array) / sizeof(array[0]);
bogo_sort(array, length);
printf("排序后的结果是:");
for (i = 0; i < length; i++)
{
printf("%d ", array[i]);
}
putchar('\n');
end = time(NULL);
printf("总共耗时:%ld秒\n", end - begin);
return 0;
}