1 Star 0 Fork 0

Tl的Twistzz本人/C语言_laptop

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
存储类别,链接,内存管理.txt 28.70 KB
一键复制 编辑 原始数据 按行查看 历史
Tl的Twistzz本人 提交于 2021-06-26 21:16 . 好玩的
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
存储类别(storage class)
从硬件方面来看,被存储的每个值都占用一定的物理内存,c语言把这样的一块内存成为对象(object).对象可以存储一个或多个值。
一个对象可能并未存储实际的值,
但是它在存储适当的值时一定具有相应的大小。
程序需要一种方法访问对象。这可以通过声明变量来完成:
int entity=3;
该声明创建了一个名为entity的标识符(identifier)。标识符是一个名称,可以用来指定特定对象的内容。
变量名不是指定对象的唯一途径:
int * pt=&entity;
int ranks[10];
第一行声明中,pt是一个标识符,它指定了一个存储地址的对象。但是,表达式*pt不是标识符,因为它不是一个名称。然而,
它确实指定了一个对象,它与entity指定的对象相同。
一般而言,指定对象的表达式被称为左值。所以entity既是标识符也是左值;*pt既是表达式也是左值。表达式*(ranks+2*entity)
是一个左值,因为它的确指定了特定内存位置的值,即ranks数组的第7个元素。
如果可以使用左值改变对象中的值,该左值就是一个可修改的左值(modifiable lvalue).
const char * pc="ABCD";
程序根据声明把相应的字符串字面量存储在内存中,内含这些字符值的字符串字面量就是一个对象,字符串字面量中的每个字符都能被单独访问,
所以每个字符也是一个对象。该变量还创建了一个标识符为pc的对象,存储着字符串的地址。可以设置pc重新指向其他字符串,所以pc是一个可修改
的左值。const只能保证被pc指向的字符串内容不被修改,但是无法保证pc不指向别的字符串。由于*pc制定了存储'B'字符的数据对象,所以*pc是
一个左值,但不是一个可修改的左值。
*可以用存储期(storage duration)描述对象,存储期是指对象在内存中保留了多长时间。
*标识符用于访问对象,可以用作用域(scope)和链接(linkage)描述标识符,标识符的作用域和链接表明了程序的哪些部分可以使用它。不同的存储类别
具有不同的存储、作用域和链接。标识符可以在源代码的多文件中共享、可用于特定文件的任意函数中、可仅限于特定函数中使用,甚至只在函数中
的某部分使用。对象可存在于程序的执行期,也可以仅存在于它所在函数的执行期。对于并发编程,对象可以在特定线程的执行期存在。
作用域
作用域描述程序中可访问标识符的区域。一个c变量的作用域可以是块作用域、函数作用域、函数原型作用域或文件作用域。
*块使用一对花括号括起来的代码区域。例如,整个函数体是一个块。函数中的任意复合语句也是一个块。定义在块中的变量具有块作用域(block scope)
块作用域变量的可见范围是从定义处到包含该定义块的末尾。另外,虽然函数的形式参数声明在函数的左花括号之前,但是它们也具有块作用域,属于
函数体这个块。所以到目前为止,我们使用的局部变量(包括函数的形式参数)都具有块作用域。因此,下面代码中的变量cleo和patrict都具有块作用域:
double blocky(double cleo)
{
double patrick = 0.0;
...
return patrick;
}
声明在内存块中的变量,其作用域仅限于该声明所在的块:
double blocky(double cleo)
{
double patrick = 0.0;
int i;
for (i = 0; i < 10; i++)
{
double q = cleo * i;
...
patrick *= q;
}
return patrick;
}
该例中,q的作用域仅限于内存块。
*具有块作用域的变量允许在块中的任意位置声明变量。对于for的循环头,现在可以这样写:
for(int i=0; i<10;i++)
*C99把块的概念拓展到包括for循环、while循环、do while循环和if语句所控制的代码,即使这些代码没有用花括号括起来,也算是块的一部分。
*函数作用域(function scope)仅用于goto语句的标签。这意味着即使一个标签首次出现在函数的内层块中,它的作用域也延申至整个函数。如果在
两个块中使用相同的标签会很乱。标签的函数作用域防止了这样的事情发生。
函数原型作用域(function prototype scope)用于函数圆形中的形参名(变量名):
int mighty(int mouse,double large);
函数原型作用域的范围是从形参定义处到原型声明结束。这意味着,编译器在处理函数原型中的形参时只关心它的类型,而形参名通常无关紧要。
而且,即使有形参名,也不必与函数定义中的形参名相匹配。只有在变长数组中,形参名才有用;
方括号中必须使用函数原型中已经声明的名称。
*变量的定义在函数的外面,具有文件作用域(file scope).具有文件作用域的变量,从它的定义处到该定义所在的文件的末尾均可见:
#include<stdio.h>
int units = 0;
void critic(void);
int main(void)
{
...
}
void critic(void)
{
...
}
这里,变量units具有文件作用域,main()和critic()函数都可以使用它(units具有外部链接文件作用域)。由于这样的变量可用于多个函数,所以这样
的变量也被称为全局变量。
*链接
C变量有三种链接属性:外部链接、内部链接或无链接。具有块作用域、函数作用域和或函数原型作用域的变量都是无链接变量
*具有文件作用域的变量可以是外部链接或内部链接。外部链接变量可以在多文件程序中使用,内部链接变量只能在一个翻译单元中使用。
*内部链接作用域-文件作用域 外部链接的做文件作用域-全局作用域/
如何知道文件作用域变量是内部链接还是外部链接?可以查看外部定义中是否使用了存储类别说明符static:
int giant = 5; //文件作用域,外部链接
static int godgers = 3; //文件作用域,内部链接
int main(void)
{
...
}
*存储期
作用域和链接描述了标识符的可见性。存储期描述了通过这些标识符访问的对象的生存期。C对象有4种存储期:静态存储期、线程存储期、自动存储期、动态分配存储期。
如果对象具有静态存储期,那么它在执行程序期间一直存在。文件作用域变量具有静态存储期。注意,对于文件作用域变量,关键字static表明了表明了其链接属性,而非存储期。以static声明的的
文件作用域变量具有内部链接。但是无论是内部链接还是外部链接,所有的 文件作用域变量都具有静态存储期。
*线程存储期用于并发程序设计,程序执行可被分为多个线程。具有线程存储期的对象,从被声明时到线程结束一直存在。以关键字_Thread_local声明一个对象时,每个线程都获得该变量的私有备份。
*块作用域的变量通常都具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存:当退出这个块时,释放刚才为变量分配的内存。这种做法相当于把自动变量占用的内存视为一个可
重复使用的工作区或暂存区。例如,一个函数调用结束后,其变量占用的内存可用于存储下一个被调用函数的变量。
变长数组稍有不同,它们的存储期从声明处到块的末尾,而不是从块的开始处到块的末尾。
void bore(int number)
{
int index;
for (index = 0; index < number; index++)
{
puts("They dont make them the way they used to.\n");
}
return 0;
}
变量number和index在每次调用bore()函数时被创建,在离开函数时被销毁
块作用域变量也能具有静态存储区,把变量声明在块中,且在声明前加上static:
void bore(int number)
{
int index;
static int ct =0;
for (index = 0; index < number; index++)
puts("They dont make them the way they used to.\n");
return 0;
}
变量ct存储在静态内存种,它从程序被载入到程序结束期间都存在。但是,它的作用域定义在bore函数块中。只有在执行该函数时,程序才能使用ct访问它所指定的对象。(函数可以给其他函数提供该存储区的地
址以便间接访问该对象,例如通过指针形参或返回值)
*自动变量
属于自动存储类别的变量具有自动存储期、块作用域且无链接。默认情况下,声明在块或函数头中的任何变量都属于自动存储类别:
int main(void)
{
auto int index;
关键字auto是存储类别说明符(storage-class specifier)。auto关键字在C++中的用法完全不同。
块作用域和无链接意味着只有在变量定义所在的块中才能通过变量名访问该变量。其他函数可以使用同名变量,但是该变量是存储在不同内存位置上的另一个变量。
int loop(int n)
{
int m;//m的作用域
scanf("%d", &m);
{
int i;//m和i的作用域
for (i = m; i < n; i++)
{
puts("i is local to a sub-block");
}
}
return m;//m的作用域i已经消失
}
在上面的代码中,i仅在内层块中可见。如果内层块中声明的变量与外层块中的变量同名。内层块会隐藏外层块的定义。但是离开内层块后,外层块变量的作用域又回到了原来的作用域。
#include<stdio.h>
int main(void)
{
int x = 30;
printf("x in outer block:%d at %p\n", x, &x);
{
int x = 77;
printf("x in inner block:%d at %p\n", x, &x);
}
printf("x in outer block:%d at %p\n", x, &x);
while (x++ < 33)
{
int x = 100;
x++;
printf("x in while loop:%d at %p\n", x, &x);
}
printf("x in outer block:%d at %p\n", x, &x);
return 0;
}
x in outer block:30 at 010FFC34
x in inner block:77 at 010FFC28
x in outer block:30 at 010FFC34
x in while loop:101 at 010FFC1C
x in while loop:101 at 010FFC1C
x in while loop:101 at 010FFC1C
x in outer block:34 at 010FFC34
程序首先创建了变量x并初始化为30.然后定义了一个心得变量x,并设置为77。新变量 隐藏了原始的x。第3条printf位于第1个内层块后面,现实的是原始x的值,原始的x既没有消失也不曾改变。while循环
的测试条件中使用的是原始的x:
while(x++<33)
在该循环中,程序创建了第3个x变量,该变量只定义在while循环中。每当执行到循环体中的x++时,递增为101的是新的x,然后printf显示了该值。每轮迭代结束,新的x变量就消失。然后循环的测试条件
使用并递增原始的x,再次进入循环体,再次创建新的x。这个x被创建和销毁了3次。该循环必须在测试条件中递增x,因为如果在循环体中递增x,那么递增的是循环体中创建的x,而非测试条件中使用的原始x。
*没有花括号的块
c99特性:作为循环或if语句的一部分,即使不使用花括号,也是一个块。更完整的说,整个循环是它所在的子块(sub-block),循环体是整个循环块的子块。于此类似,if语句是一个块,与其相关联的子语句是if语句的
子块。这些规则会影响到声明的变量和这些变量的作用域。
#include<stdio.h>
int main(void)
{
int n = 8;
printf(" Initially,n = %d at %p\n", n, &n);
for (int n = 1; n < 3; n++)
printf(" loop 1: n = %d at %p\n", n, &n);
printf("After loop 1,n = %d at %p\n", n, &n);
for (int n = 1; n < 3; n++)
{
printf(" loop 2 index n = %d at %p\n", n, &n);
int n = 6;
printf(" loop 2: n = %d at %p\n", n, &n);
n++;
}
printf("After loop 2,n = %d at %p\n", n, &n);
return 0;
}
Initially,n = 8 at 012FF808
loop 1: n = 1 at 012FF7FC
loop 1: n = 2 at 012FF7FC
After loop 1,n = 8 at 012FF808
loop 2 index n = 1 at 012FF7F0
loop 2: n = 6 at 012FF7E4
loop 2 index n = 2 at 012FF7F0
loop 2: n = 6 at 012FF7E4
After loop 2,n = 8 at 012FF808
第1个for循环头中声明的n,其作用域作用至循环末尾,而且隐藏了原始的n。但是,离开循环后,原始的n又其作用了。
第2个for循环头中声明的n作为循环的索引,隐藏了原始的n。然后,在循环体中又声明了一个n,隐藏了索引n。结束一轮迭代后,声明在循环体中的n消失,循环头使用索引n进行测试。当整个循环结束时,原始的n
又起作用了。
*自动变量的初始化
自动变量不会初始化,除非显式初始化它。
int main(void)
{
int repid;
int tents = 5;
}
tents变量被初始化为5;但是repid变量的值是之前占用分配给repid空间中的任意值。
*寄存器变量、
变量通常存储在计算机内存中。如果幸运的话,寄存器变量存储在CPU的寄存器中(存储在最快的可用内存中)。与普通变量相比,访问和处理这些变量的速度更快。由于寄存器变量存储在寄存器而非内存中,所以
无法获取寄存器变量的地址。绝大多数方面,寄存器变量和内存变量都一样。它们都是块作用域,无链接和自动存储期。使用存储类别说明符register便可声明寄存器变量:
int main(void)
{
register int quick;
声明变量为register类别与直接命令相比更像是一种请求。编译器必须根据寄存器或最快可用内存的数量衡量你的请求,或者直接忽略你的请求,所以可能不会如你所愿。在这种情况下,寄存器变量就变成普通的
自动变量。即使这样,仍然不能对该变量使用地址运算符。
在函数头中使用关键字register,便可请求形参是寄存器变量:
void macho(register int n)
可声明为register的数据类型有限。例如,处理器中的寄存器可能没有足够大的空间来存储double类型的值。
*块作用域的静态变量
静态变量(static variable。具有文件作用域的变量自动具有(也必须是)静态存储期。前面提到过,可以创建具有静态存储期、块作用域的局部变量。这些变量和自动变量一样,具有相同的作用域,但是程序离开
它们所在的函数后,这些变量不会消失。也就是说,这些变量具有块作用域、无链接,但是具有静态存储期。计算机在多次函数调用之间会记录它们的值。在块中以存储类别说明符static声明这种变量。
#include<stdio.h>
void trystat(void);
int main(void)
{
int count;
for (count = 1; count <= 3; count++)
{
printf("Here comes iteration %d:\n", count);
trystat();
}
return 0;
}
void trystat(void)
{
int fade = 1;
static int stay = 1;
printf("fade = %d and stay = %d\n", fade++, stay++);
}
Here comes iteration 1:
fade = 1 and stay = 1
Here comes iteration 2:
fade = 1 and stay = 2
Here comes iteration 3:
fade = 1 and stay = 3
静态变量stay保存了它被递增1后的值,但是fade每次都是1.这表明了初始化的不同:每次调用trystat()都会初始化fade,但是stay只在编译trystat()时被初始化一次。如果未显示初始化静态变量,他们会被初始化
为0.
int fade = 1;
static int stay = 1;
第1条声明确实是trystat()函数的一部分,每次调用函数时都会执行这条声明。这是运行时行为。
第2条声明实际上并不是trystat()函数的一部分。如果逐步调试该程序会发现,程序似乎跳过了这条声明。这是因为静态变量和外部变量在程序被载入内存时已执行完毕。把这条声明放在trystat()函数中是
为了告诉编译器只有trystat()函数才能看到该变量。这条声明并未在运行时执行。
不能再函数的形参中使用static:
Int wontwork(static int flu); //不允许
*外部链接的静态变量
外部链接的静态变量具有文件作用域、外部链接和静态存储期。该类别也可以称为外部存储类别(external storage class),属于该类别的变量称为外部变量(external variable)。把变量的定义性声明(defining
declaration)放在所有函数的外面便创建了外部变量。当然,为了指出该函数使用了外部变量,可以在函数中用关键字extern再次声明。如果一个源代码文件使用的外部变量定义在另一个源代码文件中,则必须用
extern在该文件中声明该变量:
int Errupt; //外部定义的变量
double Up[100]; //外部定义的数组
extern char Coal;//如果Coal被定义在另一个文件,
void next(void);
int main(void)
{
extern int Errupt; //可选的声明
extern double Up[]; // 可选的声明
}
void next(void)
{
}
在main()中声明Up数组时(这是可选的声明)不用指明数组大小,因为第1次声明已经提供了数组大小信息。main()中的两条extern声明完全可以省略,因为外部变量具有文件作用域,所以Errupt和Up从声明
处到文件结尾都可见。它们出现在那里,仅为了说明main()函数要使用这两个变量。
如果省略掉函数中的extern关键字,相当于创建了一个自动变量。去掉下面声明中的extern:
int Errupt;
这是的编译器在main()中创建了一个名为Errupt的自动变量。它是一个独立的局部变量,与原来在外部的Errupt不同。该局部变量仅main()中可见,但是外部变量Errupt对于该文件的其他函数如next()也可
见。
外部变量具有静态存储区。因此,无论程序执行到main()、next()还是其他函数,数组Up及其值一直存在。
*初始化外部变量
外部变量和自动变量一样,可以被显示初始化。与自动变量不同的是,如果未初始化外部变量,它会被自动初始化为0.这以原则也适用于外部定义的数组元素。与自动变量不同的,只能使用常量表达式初始化文件作
用域变量:
int x=10;//10是常量
int y=3+20;//没问题,用于初始化的是常量表达式
int x2 = 2 * x; //不行,x是变量
*使用外部变量
#include<stdio.h>
int units = 0;
void critic(void);
int main(void)
{
extern int units;
printf("How many pounds to a firkin of butter?\n");
scanf("%d", &units);
while (units != 56)
critic();
printf("You must have looked it up!\n");
return 0;
}
void critic(void)
{
printf("No luck,my friend. Try again.\n");
scanf("%d", &units);
}
当while循环结束时,main()也知道units的新值。所以main()和critic()都可以通过标识符units访问相同的变量。用C的属于来描述是,units具有文件作用域、外部链接和静态存储期。
把units定义在所有函数定义外面,units便是一个外部变量,对units定义下面的所有函数均可见。因此,critics()可以直接使用units变量。
main()也可以直接访问units。但是main()中确实有如下声明:
extern int units;
存储类别说明符extern告诉编译器,该函数中任何使用units的地方都引用同一个定义在函数外部的变量。再次强调,main()和critic()使用的都是外部定义的units。
*定义和声明
int tern = 1; //tern被定义
main()
{
extern int tern; //使用定义在别处的tern
这里tern被声明了两次。第1次声明为变量预留了存储空间,该声明构成了变量的定义。第2次声明只告诉编译器使用之前已经创建的tern变量,所以这不是定义。第1次声明被称为定义式声明(defining declaration),
第2次声明构被称为引用式声明(referencing declaration).关键字extern表示该声明不是定义,它指示编译器去别处查询其定义。
假设这样写:
extern int tern;
int main(void)
{
编译器会假设tern实际的定义在该程序的别处,也许在别的文件中。该声明不会引起分配存储空间。因此,不要用关键字extern创建外部定义,只用它来引用现有的外部定义。
// file_one.c
char a=5;
//file_two.c
extern char a=10; //错误
*内部链接的静态变量
该存储类别的变量具有静态存储期、文件作用域和内部链接。在所有函数外部(这点与外部变量相同),用存储类别说明符static定义的变量具有这种存储类别:
static int a= 1;//静态变量,内部链接
int main(void)
{
这种变量称为内部链接的静态变量(static variable with internal linkage).普通的外部变量可用于同一程序中任意文件的函数,但是内部链接的静态变量只能用于同一文件中的函数。
*多文件
只有当程序由多个翻译单元组成时,才体现区别内部链接和外部链接的重要性。
复杂的C程序通常由多个单独的源代码文件组成。有时,这些文件可能要共享一个外部变量。C通过在一个文件中进行定义式声明,然后在其他文件中进行引用式声明来实现共享。也就是说除了一个定义式声明,
其他声明都要使用extern。而且只有定义式声明才能初始化变量。
注意,如果外部变量定义在一个文件中,那么其他文件在使用该变量之前都必须先声明它。
*存储类别说明符
关键字static和extern的含义取决与上下文。C语言有6个关键字作为存储类别说明符:auto\register\static\extern\_Thread_local\typedef。typedef关键字与任何存储内存无法。在绝大多数情况下,不能在声明中
使用多个存储类别说明符,所以不能使用多个存储类别说明符作为typedef的一部分。唯一例外的是_Thread_local,它可以和static或extern一起使用。
auto说明符表明变量是自动存储期,只能用于块作用域的变量声明中。由于在块中声明的变量本身就具有自动存储期,所以使用auto主要是为了明确表达要使用与外部变量同名的局部变量的意图。
register说明符也只作用于块作用域的变量,它把变量归为寄存器存储类别,请求最快速度访问该变量。同时,还保护了该变量的地址不被获取。
static说明符创建的对象具有静态存储期,载入程序时创建对象,当程序结束时对象消失。如果static用于文件作用域声明,作用域受限于该文件。如果static用于块作用域声明,作用域则受限于该块。因此,程序只
要在运行,对象就存在并保留其值,但是只有在执行块内代码时,才能通过标识符访问。块作用域的静态变量无链接。文件作用域的静态变量具有内部链接。
extern说明符表明声明的变量定义在别处。如果包含extern的声明具有文件作用域,则引用的变量必须具有外部链接。如果包含extern的声明具有块作用域,则引用的变量可能具有外部链接或内部链接,这取决于该
变量的定义式声明。
*小结
自动变量具有块作用域、无链接、自动存储期。它们是局部变量,属于其定义所在块。寄存器变量的属性和自动变量相同,但是编译器会使用更快的内存或寄存器存储它们,不能获取寄存器变量的地址。
具有静态存储期的变量可以具有外部链接、内部链接或者无链接。在同一个文件所有函数的外部声明的变量是外部变量,具有文件作用域、外部链接和静态存储期。如果这种声明前面加上关键字static,那么其声明
的变量具有文件作用域、内部链接和静态存储期。如果在函数中声明一个变量,则改变了具有块作用域、无链接、静态存储期。
具有自动存储期的变量,程序在进入该变量 的声明所在的块时才为其分配内存,在退出该块时释放之前分配的内存。如果未初始化,自动变量中时垃圾值。程序在编译时为具有静态存储期的变量分配内存,并在程序
的运行过程中一直保留这块内存,如果未初始化,这样的变量会被设置为0.
具有块作用域的变量时局部的,属于包含该声明的块私有。具有文件作用域的变量对文件(或翻译单元)中位于其声明后面的所有函数可见。具有外部链接的文件作用域变量,可用于该程序的其他翻译单元。具有内
部链接的文件作用域变量,只能用于其声明所在的文件内。
//parta.c ---不同的存储类别
//与partb.c一起编译
#include<stdio.h>
void report_count();
void accumulate(int k);
int count = 0;
int main(void)
{
int value; //自动变量
register int i; //寄存器变量
printf("Enter a positive integer(0 to quit): ");
while (scanf_s("%d", &value) == 1 && value > 0)
{
++count;
for (i = value; i >= 0; i--)
accumulate(i);
printf("Enter a positive integer (0 to quit): ");
}
report_count();
return 0;
}
void report_count()
{
printf("Loop executed %d times\n", count);
}
partb.c:
#include<stdio.h>
extern int count; //引用式声明,外部链接
static int total = 0; //静态定义,内部链接
void accumulate(int k); //函数原型
void accumulate(int k)
{
static int subtotal = 0; //静态,无链接
if (k <= 0)
{
printf("loop cycle: %d\n", count);
printf("subtotal: %d;total: %d\n", subtotal, total);
subtotal = 0;
}
else
{
subtotal += k;
total += k;
}
}
在该程序中,块作用域的静态变量subtotal统计每次while循环传入accummulate()函数的总数,具有文件作用域、内部链接的变量total统计所有传入accummulate()函数的总数。当传入负值时,accummulate()
函数报告total和subtotal的值,并在报告后重置subtotal为0.由于parta.c调用了accummulate()函数,所以必须包含accummulate()函数尔的原型。而partb.c只包含了accummulate()函数的定义,并未在
文件中调用该函数,所以其原型为可选(即省略原型也不影响程序使用)。该函数使用了外部变量count统计main()中的while函数迭代的次数。在parta.c中,在main()和report_count()共享count。
Enter a positive integer(0 to quit): 5
loop cycle: 1
subtotal: 15;total: 15
Enter a positive integer (0 to quit): 10
loop cycle: 2
subtotal: 55;total: 70
Enter a positive integer (0 to quit): 2
loop cycle: 3
subtotal: 3;total: 73
Enter a positive integer (0 to quit): 0
Loop executed 3 times
*存储类别和函数
函数也有存储类别,可以是外部函数(默认)或静态函数。C99新增了第3种类别--内联函数。外部函数可以被其他文件的函数访问,但是静态函数之能用于其定义所在的文件。假设一个文件中包含了一下函数原型“
double gamma(double); //默认为外部函数
static double beta(int, int);
extern double belta(double, int);
在同一个程序中,其他文件中的函数可以调用gamma()和belta(),但是不能调用beta(),static创建的函数属于特定模块私有。这样做避免了名称冲突的问题。
通常的做法是,用extern关键字声明定义在其他文件中的函数。这样做是为了表明当前文件中使用的函数被定义在别处。除非是用static关键字,否则一般函数声明都默认为extern。
*随机函数和静态变量
//rand0.c
static unsigned long int next = 1; //种子
unsigned int rand0(void)
{
next = next * 1103515245 + 12345;
return (unsigned int)(next / 65536) % 32768;
}
该函数使用该种子生成新的数,这个新数又成为新的种子。然后,新种子可用于生成更新的种子。因此使用静态变量next。
该函数是用于返回一个0·32767之间的值。
//drive0.c
#include<stdio.h>
extern unsigned int rand0(void);
int main(void)
{
int count;
for (count = 0; count < 5; count++)
{
printf("%d\n", rand0());
}
return 0;
}
238
25553
18733
13146
24533
程序输出的数字不论运行几次都是相同的。两次输出完全相同,体现了“伪随机”的一个方面。每次主程序运行,都开始于相同的种子1.可以引入另一个函数srand1()重置种子来解决这个问题。关键是要让next成为
只供rand1()和srand1()访问的内部链接静态变量( srand1()相当于C库中的srand()函数 )。把srand1()加入rand1()所在的文件中。
srand.c
static unsigned long int next = 1; //种子
int rand1(void)
{
next = next * 15612845 + 12345;
return (unsigned int)(next / 65536) % 32768;
}
void srand1(unsigned int seed)
{
next = seed;
}
drive.c
#include<stdio.h>
#include<stdlib.h>
extern void srand1(unsigned int x);
extern int rand1(void);
int main(void)
{
int count;
unsigned int seed;
printf("Please enter your choice for seed.\n");
while (scanf("%u", &seed) == 1)
{
srand1(seed); //重置种子
for (count = 0; count < 5; count++)
printf("%d\n", rand1());
printf("Please enter next seed (q to quit):\n");
}
printf("Done\n");
}
Please enter your choice for seed.
1
238
25553
18733
13146
24533
Please enter next seed (q to quit):
513
23909
3638
9057
26378
434
Please enter next seed (q to quit):
l
Done
设置seed的值为1,输出的结果与前面程序相同。但是设置seed的值为513后就得到了新结果。
*自动重置种子
如果C实现允许访问一些可变的量(如时钟系统),可以用这些值初始化种子值。例如,ANSI C 有一个time()函数返回系统时间。time()返回值的类型名是time_t,具体类型与系统有关。这没关系,我们可以
强制类型转换:
#include<time.h> //提供time()原型
srand1((unsigned int)time(NULL)); //初始化seed
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/twistzz_of_tl/c-language.git
git@gitee.com:twistzz_of_tl/c-language.git
twistzz_of_tl
c-language
C语言_laptop
master

搜索帮助