程序设计:用户输入任意个成绩,计算平均值
#include
int main(void)
{
int people[100]={0};
int count=0;
int total=0;
int grade;
int i,j;
do
{
printf("Input grade : ");
scanf("%d",&grade);
if(grade>=0)
{
people[count++]=grade;
}
}while(grade>=0&&count<100);
for(i=0;i<count;i++)
{
total+=people[i];
}
printf("average: %d\n",total/count);
}
那么问题来了,必须预先定义一个很大的内存空间,可是我实际使用到的内存空
间可能没有那么大
1.1存储类别
从硬件方面来看,被存储的每个值都占用一定的物理内存,C语言把这样的一块
内存称为对象。一个对象可能并未存储实际的值,但是它在存储适当的值时
一定具有相应的大小。
从软件方面来看,程序需要一种方法访问对象(个人理解:内存)。这可以通过声明变量来完成:
int entity=3;该声明创建了一个名为entity的标识符,标识符是一个名称,在这种
情况下,标识符可以用来指定特定对象(个人理解:内存)的内容。在该例中,
标识符entity是软件指定对象的一种方式。
变量名不是指定对象的唯一途径,考虑下面的声明:
int *pt=&entity ;
int ranks[10];
pt是一个标识符,它指定了一个存储地址的对象。但是,表达式*pt不是标识符,
因为它不是一个名称。然而,它确实指定了一个对象,在这种情况下,它与entity
指定的对象相同。一般而言,那些指定对象的表达式被称为左值。所以,entity既
是标识符也是左值;*pt既是表达式也是左值。按照这个思路,ranks+2 * entity既
不是标识符(不是名称),也不是左值(它不指定内存位置上的内容)。但是表
达式* (ranks+2 * entity)是一个左值,因为它的确指定了特定内存位置的值,
即ranks数组的第7个元素。顺带一提,ranks的声明创建了一个可容纳10个int类
型元素的对象,该数组的每个元素也是一个对象。所有这些示例中,如果可以使
用左值改变对象中的值,该左值就是一个可修改的左值。
现在,考虑下面的声明:
const char* pc=“Behold a string literal!”;
程序根据该声明把相应的字符串字面量储存在内存中,内含这些字符值的数组就
是一个对象。由于数组中的每个字符都能被单独访问,所以每个字符也是一个对
象。该声明还创建了一个标识符为pc的对象,储存着字符串的地址。const只能保
证被pc指向的字符串内容不被修改,但是无法保证pc不指向别的字符("Behold a
string literal!"不能变,但是指针保存的那个地址可以变)。可以用存储期描述
对象,所谓存储期是指对象在内存中保留了多长时间。标识符用于访问对象,可
以用作用域和链接描述标识符,标识符的作用域和链接表明了程序的哪些
部分可以使用它。不同的存储类别具有不同的存储期、作用域和链接。
1.1.1作用域
块是用一对花括号括起来的代码区域。定义在块中的变量具有块作用域,块作用
域变量的可见范围是从定义处到包含该定义的块的末尾。
以前,具有块作用域的变量都必须声明在块的开头。C99标准放宽了这一限制,
允许在块的任意位置声明变量。因此,对于for的循环头,现在可以写成:
for(int i=0;i<10;i++)
printf(“A C99 feature:i=%d”,i);
为适应这个新特性,C99把块的概念扩展到包括for循环、while循环、do while循
环和if语句所控制的代码,即使这些代码没有用花括号括起来,也算是块的一部
分。所以,上面for循环中的变量i被视为for循环块的一部分,它的作用域仅限于
for循环。一旦程序离开for循环,就不能再访问i。
我们认为的多个文件在编译器中可能以一个文件出现。例如,通常在源代码(.c
扩展名)中包含一个或多个头文件(.h扩展名)。头文件会依次包含其它头文
件,所以会包含多个单独的物理文件。但是,C预处理实际上是用包含的头文件
内容替换#include指令。所以,编译器源代码文件和所有的头文件都看成是一个
包含信息的单独文件,这个文件被称为翻译单元。描述一个全局变量时,它的实
际可见范围是整个翻译单元。如果程序由多个源代码文件组成,那么该程序也将
由多个翻译单元组成。每个翻译单元均对应一个源代码文件和它所包含的文件。
1.1.2 链接
C变量有3种链接属性:外部链接、内部链接或无链接。具有块作用域、函数作用域或函数原型作用域的变量都是无链接变量。这意味着这些变量属于定义它们的块、函数或原型所有。具有文件作用域的变量(全局变量)可以是外部链接或内部链接。外部链接变量可以在多文件程序中使用,内部链接变量只能在一个翻译单元中使用。
C标准用“内部链接的文件作用域”描述仅限于一个翻译单元(即一个源代码文件和它所包含的头文件)的作用域,用"外部链接的文件作用域"描述可延伸至其它翻译单元的作用域。但是,对程序员而言,这些翻译太长了。一些程序员把“内部链接的文件作用域”简称为“文件作用域”,把”外部链接的文件作用域“简称为“全局作用域”或“程序作用域”。
1.1.3 存储期
作用域和链接描述了标识符的可见性。存储期描述了通过这些标识符访问的对象的生存期。C对象有4种存储期:静态存储期、线程存储期、自动存储期、动态分配存储期。
如果对象具有静态存储期,那么它在程序的执行期间一直存在。文件作用域变量具有静态存储期。注意,对于文件作用域变量,关键字static表明了其链接属性,而非存储期。以static声明的文件作用域变量具有内部链接。但是无论是内部链接还是外部链接,所有的文件作用域变量都具有静态存储期。
线程存储期用于并发程序设计,程序执行可被分为多个线程。具有线程存储期的对象,从被声明时到线程结束一直存在。以关键字_Thread_local声明一个对象时,每个线程都获得该变量的私有备份。
块作用域的变量通常都具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存;当退出这个块时,释放刚才为变量分配的内存。这种做法相当于把自动变量占用的内存视为一个可重复使用的工作区或暂存区。例如,一个函数调用结束后,其变量占用的内存可用于储存下一个被调用函数的变量。
变长数组稍有不同,它的存储期从声明处到块的末尾,而不是从块的开始到块的末尾。
然而,块作用域变量也能具有静态存储期。为了创建这样的变量,要把变量声明在块中,且在声明前加上关键字static。
void more(int num)
{
int index;
static int ct=0;
return 0;
}
这里,变量ct储存在静态内存中,它从程序被载入到程序结束期间都存在。但是,它的作用域定义在more()中。只有在执行该函数时,程序才能使用ct访问ct所指定的对象(但是,该函数可以给其他函数提供该存储区的地址以便间接访问该对象,例如通过指针形参或返回值)。
C使用作用域、链接和存储期为变量定义了多种存储方案。5种存储类别如表所示
###############################################################
1.1.4自动变量
属于自动存储类别的变量具有自动存储期、块作用域且无链接。默认情况下,声明在块或函数头中的变量都属于自动存储类别。为了更清楚地表达你的意图(例如,为了表明有意覆盖一个外部变量定义,或者强调不要把该变量改为其它存储类别),可以显式使用关键字auto,如下所示
int main(void)
{
auto int plox;
}
关键字auto是存储类别说明符。auto关键字在C++中的用法完全不同,如果编写C/C++兼容的程序,最好不要使用auto作为存储类别说明符。
块作用域和无链接意味着只有在变量定义所在的块中才能通过变量名访问变量(当然,参数用于传递变量的值和地址给另一个函数,但是这是间接的方法)。另一个函数可以使用同名变量,但是该变量是存储在不同内存位置上的另一个变量。变量具有自动存储意味着,程序在进入该变量声明所在的块时变量存在,程序在退出该块时变量消失。原来改变量占用的内存位置现在可做他用。
接下来分析一下嵌套块的情况。块中声明的变量仅限于该块机器包含的块使用。
int loop(int n)
{
int m;//m的作用域
scanf("%d",&m);
{
int i;//m和i的作用域
for(i=m;i<n;i++)
puts("i isn local to a sub-block\n");
}
return m;//m的作用域,i已经消失
}
变量n和m分别定义在函数头和外层块中,它们的作用域是整个函数,而且在调用函数到函数结束期间都一直存在。
如果内层块中声明的变量与外层块中的变量同名会怎样?内层块会隐藏外层块的定义。但是离开内层块后,外层块变量的作用域又回到原来的作用域。
#include
int main(void)
{
int x=30;//原始的x
printf("x in outer block:%d at %p\n",x,&x);
{
int x=77;//新的x,隐藏了原始的x
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)//原始的x
{
int x=100;//新的x,隐藏了原始的x
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;
}
1.1.5寄存器变量
变量通常储存在计算机内存中。如果幸运的话,寄存器变量储存在CPU的寄存器中,或者概括地说,储存在最快的可用内存中。与普通变量相比,访问和处理这些变量的速度更快。由于寄存器变量储存在寄存器而非内存中,所以无法获取寄存器变量的地址。绝大多数方面,寄存器变量和自动变量都一样。也就是说,它们都是块作用域、无链接和自动存储期。使用存储类别说明符register便可声明寄存器变量:
`int main(void)
{
register int quick;
}`
我们刚才说“如果幸运的话”,是因为声明变量为register类别与直接命令相比更像是一种请求。编译器必须根据寄存器或最快可用内存的数量衡量你的请求,或者直接忽略你的请求,所以可能不会如你所愿。在这种情况下,寄存器变量就变成普通的自动变量。即使是这样,仍然不能对改变量使用地址运算符。
在函数头中使用关键字register,便可请求形参是寄存器变量:
void macho(register int n)
可声明为register的数据类型有限。例如,处理器中的寄存器可能没有足够大的空间来储存double类型的值。
1.1.6 块作用域的静态变量
静态变量听起来自相矛盾,像是一个不可变的变量。实际上,静态的意思是该变量在内存中原地不动,并不是说它的数值不变。具有文件作用域的变量自动具有静态存储期。前面提到过,可以创建具有静态存储期、块作用域的局部变量。这些变量和自动变量一样,具有相同的作用域,但是程序离开它们所在的函数后,这些变量不会消失。也就是说,这种变量具有块作用域、无链接,但是具有静态存储期。计算机在多次函数调用之间会记录它们的值。
#include
void trystat(void)
{
int fade=1;
static int stay=1;
printf("fade=%d and stay=%d \n",fade++,stay++);
}
int main(void)
{
int count;
for(count=1;count<=3;count++)
{
printf("Here comes iteration %d:\n",count);
trystat();
}
return 0;
}
如果未显式初始化静态变量,它们会别初始化为0。
这两个变量声明很相似:
int fade=1;
static int stay=1;
第1条声明确实是trystat()函数的一部分,每次调用该函数时都会执行这条声明。第2条声明实际上并不是trystat()函数的一部分。如果逐步调试该程序会发现,程序似乎跳过了这条声明。这是因为静态变量和外部变量在程序被载入内存时已执行完毕。把这条声明放在trystat()函数中是为了告诉编译器只有trystat()函数才能看到该变量。这条声明并未在运行时执行。
不能在函数的形参中使用static
int wontwork(static int flu);//不允许
“局部静态变量”是描述具有块作用域的静态变量的另一个术语。阅读一些老的C文献时会发现,这种存储类别被称为内部静态存储类别。这里的内部指的时函数内部,而非内部链接。
1.1.7外部链接静态变量
外部链接的静态变量具有文件作用域、外部链接和静态存储期。该存储类别有时称为外部存储类别,属于该类别的变量称为外部变量。把变量的定义性声明放在在所有函数的外面便创建了外部变量。当然,为了指出该函数使用了外部变量,可以在函数中用关键字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数组时(这是可选的声明)不用指明数组大小,因为第一次声明已经提供了数组大小信息。main()中的两条extern声明完全可以省略,因为外部变量具有文件作用域,所以Errupt和Up从声明处到文件结尾都可见。它们出现在那里,仅仅是为了说明main()函数要使用这两个变量。如果省略掉函数中的extern关键字,相当于创建了一个自动变量。去掉下面声明中的extern:
extern int Errupt;
便成为
int Errupt;
这使得编译器在main()中创建了一个名为Errupt的自动变量。它是一个独立的局部变量,与原来的外部变量Errupt不同。该局部变量仅main()中可见,但是外部变量Errupt对于该文件的其它函数(如next())也可见。简而言之,在执行块中的语句时,块作用域中的变量将“隐藏”文件作用域中的同名变量。
下面3个示例演示了外部和自动变量的一些使用情况。
示例1中有一个外部变量Hocus,该变量对main()和magic()均可见
//示例1
int Hocus;
int magic();
int main(void)
{
extern int Hocus;//Hocus之前已声明为外部变量
}
int magic()
{
extern int Hocus;//与上面的Hocus是同一个变量
}
示例2中有一个外部变量Hocus,对两个函数均可见。这次,在默认情况下对magic()可见。
//示例2
int Hocus;
int magic();
int main(void)
{
extern int Hocus;//Hocus之前已声明为外部变量
}
int magic()
{
//并未在该函数中声明Hocus,但是仍可使用该变量
}
在示例3中,创建了4个独立变量。main()中的Hocus变量默认是自动变量,属于main()私有。magic()中的Hocus变量被显式声明为自动,只有magic可用。外部变量Hocus对main()和magic()均不可见,但是对该文件中未创建局部Hocus变量的其它函数可见。最后,Pocus是外部变量,magic可见,但是main()不可见,因为Pocus被声明在main()后面。
//示例3
int Hocus;
int magic();
int main(void)
{
int Hocus;//声明Hocus,默认是自动变量
}
int Pocus;
int magic()
{
auto int Hocus;//把局部变量显式声明为自动变量
}
初始化外部变量
外部变量和自动变量类似,也可以被显示初始化。与自动变量不同的是,如果未初始化外部变量,它们会被自动初始化为0。这一原则也适用于外部定义的数组元素。与自动变量 的情况不同,只能使用常量表达式初始化文件作用域变量:
int x=10;//没问题,10是常量
int y=3+10;//没问题,用于初始化的是常量表达式
int z=sizeof(int);//没问题,用于初始化的是常量表达式
int w=2*x;//不行,x是变量
//只要不是变长数组,sizeof表达式可被视为常量表达式