赞
踩
程序是计算机对数据进行操作的步骤,数据与操作构成了程序的两个要素。数据既是程序必要组成部分,也是程序处理的对象。而C语言为数据提供了九种基本数据类型,这些数据类型用来描述程序中不同数据的数据结构,数据取值范围以及数据在内存中的存储等性质。
不同的数据类型都分为两类讨论,常量以及变量。
我们可以将九种数据类型分为三类:
基本类型:整型(int),浮点型,(float,double),字符型(char),枚举型(enum),指针型(*);
聚合类型【也称构造类型】:数组型(【】),结构型(struct),联合型(union);
其他类型:空类型(void);
C语言字符集:组成C语言源程序代码的基本字符成为C语言字符集,它是构成C语言的基本元素。C语言允许使用的,并且能被编译器翻译转换成执行程序源代码的可执行运行的基本字符集。
C语言允许使用的基本字符(ASCII–字符常量)如下:
补充(Supplement):
补充(Supplement):
转义字符虽然包含两个或多个字符,但它只代表一个字符。编译系统在见到字符“\”时,会接着找它后面的字符,把它处理成一个字符,在内存中只占一个字节。
因此我们可以认为:转义字符是特殊的ASCII码,也就是特殊的字符常量,故字符常量中明确包含了转义字符(ASCII中的33个控制字符及通讯专用字符)和键盘上可以按出来的普通字符。
ASCII中的控制字符以及通讯专用字符:33个,范围:0~31和127,不是所有的控制字符都可以被转义字符来特殊输出,相反地,大多数ASCII控制字符单独都具有控制功能,因此它们不是转义字符。
字符常量是C语言所提供的最基本字符集合,所有的其他字符集(字符数组)都是通过不同的基本字符组合而成
另外(Addition):
enum(enumerate)枚举类型:在实际问题中,有些变量的取值被限定在一个有限的范围内。例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等。如果把这些量说明为整型,字符型或其它类型显然是不妥当的(比如说星期一如果要用数值代表那就是只能为1,因为1是最清楚能代表星期一的,而如果定义成整型int,则int型变量数值可以是任意能变化的,这样就失去了一个被限定取值的变量的意义)。为此,C语言提供了一种称为“枚举”的类型。在“枚举”类型的定义中列举出所有可能的取值,被说明为该“枚举”类型的变量取值不能超过定义的范围。应该说明的是,枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。
1. 枚举类型的定义和枚举变量的定义: 在定义枚举变量之前,先定义枚举类型。枚举类型定义格式如下: enum 枚举类型名 { 枚举元素表 }; 例如:enum Day {sum,mon,tue,wed,thu,fir,sat};
定义具有day枚举类型的枚举变量定义可以采用以下两种方法中的任意一种:
enum Day{sum,mon,tue,wed,thu,fir,sat} day1;
等价于:enum{sum,mon,tue,wed,thu,fir,sat} day1;
或者 :
2. 枚举类型变量的赋值和使用:
在使用枚举类型时,需要注意以下几点:
(1). 在C语言中,对枚举元素是按常量处理的,它们不是变量,不能被赋值,只能被初始化。
(2). 枚举元素作为常量,它们是有值的,C语言编译时按定义的顺序依次对它们从整型数据0开始赋值。例如以上元素若全无初始化,则sum=0,mon=1,……,sat=6。
若以上元素中进行了初始化(初始化可以是任意整型值):
1°:部分初始化:如mon=1,fir=5,则编译时未初始化的按照顺序依次赋值直到被初始化的元素的前一个。如这里按照顺序自动赋值只会给sum赋值,sum=0。mon已经初始化了,其值就是初始化的值,mon后面的元素依次以mon初始化值为基准,后一个元素依次以前一个元素的值为基准赋值前一个元素的值加整型数值1,如tue被自动赋值2((int)mon+1),同理wed被自动赋值(int)tue+1也就是3,……,一直到下一个被初始化的元素,若此后没有第二个被初始化的元素,则一直按照此方法赋值到结尾。因此这里一直到fir停止顺序赋值,fir被初始化赋值为5,下一个元素sat又没初始化了,则继续按照刚刚的规则sat=(int)fir+1。
(注意:如果这里我不按照正常逻辑,我给mon初始化66,fir初始化100,则该枚举类型元素的值依次为:sum=0,mon=66,tue=67,wed=68,thu=69,fir=100,sat=101,另外:数值顺序无所谓先后,比如我只给sum初始化100,fir=55依然是正确的初始化形式,这样的化mon=101,……,sat=56)
2°:全部初始化:也就是给每个元素初始化你想给定的值即可
(3). 一个整数值不能直接赋予一个枚举变量,因为数据类型不对应。 例如不能这样赋值:day1=2;
这样是错误的,因为左边是enum类型数据,而2是整型常量为int型
因此如果我们不用enum所定义好的类型里的元素给enum变量赋值,则需要用到强制转换,如:day1=(enum
Day)2;等价于day1=tue; 同理,虽然这些元素被赋予了整型值,但依然不能给int型变量直接赋值,如:int
a=mon;是错误的,也需要强制转换:int a=(int)mon;这样是对的。
(4). 编译器给枚举变量分配的存储单元大小与整型量相统,枚举变量在输出时,只能输入对应的枚举元素的值(序号)。
枚举变量可以通过printf()函数输出其枚举元素对应的值,即整型数值。而不能直接通过printf()函数输出其标识符。要想输出其标识符,可以通过数组或者switch语句将枚举值转换为相应的字符进行输出。
(5). 当enum与sizeof联用,无论enum声明的类型中的字面型常量集合的元素个数如何,sizeof返回的字节大小永远是4字节。 如 sizeof(enum)=4,sizeof(enum Day)=4,sizeof(enum Day day1)=4。至于为什么不管里面元素个数,返回的储存空间大小都为4 bety,可能是因为enum里的元素为一种狭义宏定义,enum是一种基本数据类型,其类型本身意义是一个字面整型常量的集合,无论定义多少个元素,都相当于是告诉编译器有多少个被宏定义的标识符,因此相当于没有元素,(enum)和(enum 某集合标识符)也因此被理解为只是一种声明某个常量集合的类型说明符,它们都只是一种标识符,一种枚举(集合)类型说明的标识符,其作用仅是告诉编译器程序中定义且引入这些字面型常量,又因sizeof是返回一个变量或者某数据类型所占内存的字节大小,而enum由此可见实质上是一种声明整型常量(枚举/集合的元素)的数据类型(如int这个数据类型描述的是一种占4字节大小空间的整型数值的数据结构,sizeof返回的即是int描述的该数据类型所占内存大小)所以sizeof一个未细分的enum或者sizeof一个细分后的enum,都是相当于返回其所描述对象的数据类型所占空间值。另外,这些标识符用于后续程序中就是被相应整型值替换的数值常量,这些都是被存到代码区的,也就是说enum的元素被存到常量区,相当于告诉了编译器这些自定义的字面值常量。。
如: enum a {mon=55,tue=66,wed=88};
printf("%d,%d,%d,b=%d\n",mon,tue,wed);被预编译(预处理)的时候替换等价为:printf("%d,%d,%d,b=%d\n",55,66,88);
打印的结果为:55,66,88;因此元素既然是被“宏定义”,所以一样可以直接格式转换来打印,因为会被数值替换。**
(6). 若某个枚举类型变量未被初始化或者被赋值,则实际上就相当于定义了一个拥有某enum数据类型的而值为整型值的变量,其值是随机的整型值。
如:
int a;未被初始化,后续也没赋值就直接输出a的值,a的值为随机整型值
同理:enum Day day1;
day1的值也为随机整型值
两者都是随机整型值是因为它们都被存储到栈区中。(这是栈区变量赋值的一种默认情况,默认在没有确定值的情况下,就赋予随机值)
以下是一个关于enum用法的实例
/*说明一个枚举类型enum month,它的枚举元素为Jan,Feb,…,Dec。编写能 显示上个月名称的函数last_month。例如,输入Jan时能显示Dec。再编写另一个函数 printmon,用于打印枚举变量的值。最后编写主函数调用上述函数生成一张12个月份 及其前一个月份的对照表。*/ #include<stdio.h> enum month{Jan=1,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec}; char *p[12]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; void last_month(enum month m) { if(m>=2&&m<=12) printf("%s\n",p[m-2]); if(m==1) printf("%s\n",p[11]); } void printmon(enum month m) { printf("%s\n",p[m-1]); } void main() { enum month m; printf("the last month list:\n"); for(m=Jan;m<=Dec;m=(enum month)(m+1)) /*注意:这里不能m++,因为m是数值常量,常量不能自增自减,因此这个expression的意思是(m+1)也就是m先从数值上加了1,再将这个(m+1)数值常量强制转换成(enum month)对应到该enum类型的元素,注意m=(m+1)也不对,因为赋值语句右侧的(m+1)是一个int型的数值常量,而左侧m是enum变量,其数据类型是enum main::m,只能接受相应的enum month中的字面值常量*/ last_month(m); printf("the month print:\n"); for(m=Jan;m<=Dec;m=(enum month)(m+1)) printmon(m); /*在除了赋值语句以外的情况下,m等价于元素里的每个标识符,而每个标识符又被数值替换,因此m就是一个整型数常量,也就是m本身虽然具有enum的数据类型属性,但其值就是一个数值常量,而赋值语句要考虑数据类型属性是因为涉及到不同数据类型是不同的数据结构*/ }
根据这个例子,总的来看似乎是实现了变量意义的可视化,定义一个显示月份的变量m,循环里的expression3即m等于下一个月份,enum使整个程序清晰明了。但需要注意的是:enum里的元素不能直接输出其标识符,这里是通过字符串指针数组实现的输出打印。enum变量值只用来判断是第几个月份,而这个判断的值通过指针来输出相应月份的标识。
3. enum和#define的区别:
- 宏是在预处理阶段直接进行替换并且不进行类型检查,而枚举则是在程序运行之后才起作用,编译后程序识别到enum类型标识符,才进行相应的enum元素声明。
- 宏定义的标识符可以被任意文本内容替换,而枚举只能定义被整型值替换的字面值常量。也就是说define没有类型,但enum有,enum的变量同样具有一个变量该具有的特点,若作用域,值等等,enum变量的值是被限定范围的,且用enum定义的字面型常量去赋予
- 宏定义不会主动赋值,而enum会自动赋值
- 宏定义一次只能定义一个,而enum可以一次定义大量的相关常量
- 宏定义不是实体,而枚举常量是一种实体
typedef 类型定义符:为了适应用户的习惯和便于程序的移植,除了可以直接使用C语言提供的标准类型和自定义的类型(数组,结构体,联合和枚举)外,C语言允许用户通过类型定义将已有的各种类型定义成新的类型标识符。经类型定义后,新的类型标识符与标准类型名一样,可以用来定义相应的变量。
typedef 原数据类型 新的类型名;
例如:typedef int Length;
typedef int Height;
则此时Length和Height两个类型名标识符都具有了int的属性,可以用这两个新的类型名来定义变量,如Length x;这样x就能很清楚地视为一个表示整型长度的变量。
1. 数组:
例如:typedef char Character[20]; 则char str1[20]; 等价于Character str1[20];
2. 指针:
例如:typedef float,* PFLOAT; 则float *p1;等价于PFLOAT p1;
3. 结构体:
例如:typedef struct student{参数列表}List; 则struct student a1;等价于 List a1;
4. 函数:
例如:typedef char DFCH();则char af();等价于DFCH af;
空格、制表符、换行符等统称为空白符(space character),它们只用来占位,并没有实际的内容,也显示不出具体的字符。
程序员要善于利用空白符:缩进(制表符)和换行可以让代码结构更加清晰,空格可以让代码看起来不那么拥挤。
空行用于分隔不同的逻辑代码段,它们是按照功能分段的。
c语言由于书写自由。因此我们更应该注意正确优雅的格式,在相邻的不同标识符(包括关键字,用户标识符,变量名,函数名,类型说明符等等)之间必须出现至少一个或者多个空白字符(或注释),不然它们会被编译器解释为单个标记。在绝大多数操作符的使用中,中间都隔以空格。
(1)逗号作为分隔符用来分隔多个变量(数组元素之间逗号隔开)和函数参数;
(2)空白符常用来作为多个单词间的分隔符,也可以作为输数据时自然输入项的缺省分隔符;
(3)分号常用于for循环语中for后面,圆括号内的三个表达式之间;
(4)冒号用于语句标号与语句之间。
C语言中有几种声明,它的类型名可以省略。
进行赋值运算时,需要赋值号右边表达式的值赋予左边变量,同时左边变量必须满足是左值表达式。但如果赋值号两边的数据类型不一致,则需要进行强制类型转换,转换的结果总是将右边表达式的类型转换成赋值号左边变量的类型,如果不用强制转换符,则为隐式强制转换。而隐式强制转换,是一种根据转换规则的自动转换,规则是低等级的自动向高等级类型转换。char->int->float->double
所以,在进行赋值运算时,赋值号两边的数据类型最好一致,不要交叉赋值,至少右边的数据类型要比左边的数据类型级数低,或者右边数据的值在左边数据类型的取值范围内,否则,将会导致运算精度降低(如:(int)2.8=2,也可用于小数的取整),甚至从高类型到低类型的转换会发生存储字节空间的减少,从而可能会引发因数据截断造成的数据损害。
另外涉及到数据截断的问题,可以知道在我们使用格式转换符的时候,一定要与打印值的数据类型对应起来,否则会造成数据丢失。如:printf("%f\n",3);
这里3是int型,而%f是转换打印输出浮点型数据,因此会造成最终结果丢失。
双引号直接扩起来的被c语言系统认定为字符串常量,被存放在代码区不可修改,如char *str=“字符串”;意义是把后面双引号的常量字符串的内存地址给常量指针(const char )初始化;而char a[]=“字符串”;是编译器自己根据双引号内的字符字节大小分配一块连续的内存空间,其中数组名是char const类型,是只读的。因此对一个常量进行修改可能殃及及程序中其他字符串常量。因此,许多ANSI编译器不允许对字符串常量修改,如果想随意修改你输入的字符串,请用数组去存储。
字符串通常存储在数组中,因此c语言才没有额外存在一种字符串类型的数据结构。而选择NUL字符(空字符)作为字符串的终止符,是因为它不是一个可打印的字符。
位于一对花括号之间的所有语句称为一个代码块。函数主体由一对花括号括起来,括起来的函数主体也就是个大的代码块,该大的代码块中又有如条件语句,循环语句等等括起来的小代码块。另外,语句的结束标志为分号;代码块的结束和函数的结束为反花括号;字符串结束为空字符。
c语言并不具备任何输入/输出语句(注意:是说的输入输出语句!并没有这种标识输入输出语句的关键字):I/O是通过调用函数库的输入输出函数实现的。另外,c语言也不具备任何异常处理语句,它们也是通过调用库函数来完成的。
if,while等语句后面的括号也是该语句完整的一部分,因此if语句实际上完整来说是if()语句。
如果在switch-case语句中,想要同一个语句的范围扩大,则可以将其前面需要扩大的范围内的case(i)语句都设为空语句,如:case 1: case 2: case 3: statement- list;break;case···default 如果扩的范围太大,可能换成一系列的if语句会更好,其中default为默认值的意思,如果case中的预设情况没有一种能与现实情况对应,则会自动进入默认情况default语句。
制表符\t是以绝对位置(8个空格)8 col(列)为单位。如printf(“字符串\t”);的作用效果是先打印字符串,再将光标移到该行的后八位空格的位置。因此如果反过来printf(“\t字符串”);则是从空了8列的位置开始打印字符串。
C语言最简单的语句就是空语句,它本身只包含一个分号。
在ANSI C(ANSI即American National Standards Institute,美国国家标准学会。ANSI C是 美国国家标准协会(ANSI)对 C语言发布的标准。)的任何一种实现中,存在两种不同的环境。第一种是翻译环境(translation environment),在这个环境里,源代码被转换为可执行的机器指令。第二种是执行环境(execution environment),它用于实际执行代码。标准明确说明,这两种环境不必位于同一台机器上。例如,交叉编译器(cross compiler)就是在一台集器上运行,但它所产生的可执行代码运行于不同类型的机器上。操作系统也是如此。标准同时讨论了独立环境(freestanding environment),就是不存在操作系统的环境。你可能在嵌入式系统种(如微波炉控制器)遇到这种类型的环境。
C语言代码由固定的词汇按照固定的格式组织起来,简单直观,程序员容易识别和理解,但是对于CPU,C语言代码就是天书,根本不认识,CPU只认识几百个二进制形式的指令。这就需要一个工具,将C语言代码转换成CPU能够识别的二进制指令,也就是将代码加工成
.exe 程序的格式;这个工具是一个特殊的软件,叫做编译器(Compiler)。
编译器能够识别代码中的词汇(关键字,标识符)、句子(条件结构,循环结构)以及各种特定的格式,并将他们转换成计算机能够识别的二进制形式,这个过程称为编译(Compile)。
(这里所说的词汇也就是上述文章里所谈到的C语言字符集,以及系统内部定义的关键字所表示语句等等)
编译分为两个阶段组成:
首先是预处理(preprocessor)处理。在这个阶段,预处理器在源代码上执行一些文本操作和带#的操作指令。例如,用目标文本代替由#define指令定义的符号以及读入由#include指令包含的文件的内容,复制到该源文件内。
其次是解析(parse)处理。在这个阶段,判断它的语句的意思,这个阶段是产生绝大多数错误和警告信息的地方。
随后便产生了目标代码(二进制代码,储存到了代码区)
C语言代码经过编译以后,并没有生成最终的可执行文件(.exe 文件),而是生成了一种叫做目标文件(Object File)的中间文件(或者说临时文件)。目标文件也是二进制形式的,它和可执行文件的格式是一样的。对于 Visual
C++,目标文件的后缀是.obj;对于 GCC,目标文件的后缀是.o。
链接(Link)其实就是一个“打包”的过程,它将所有二进制形式的目标文件和系统组件组合成一个可执行文件。完成链接的过程也需要一个特殊的软件,叫做链接器(Linker)。
链接器同时会引入标准C函数库种任何被该程序所用到的函数,而且它也可以搜索程序员个人的程序库,将其中需要使用的函数链接到程序中。
程序的执行过程也需要4个步骤:
首先,程序必须载入到内存中。 在宿主环境中(也就是具有操作系统的环境),这个任务由操作系统完成。那些不是存储在堆栈中的尚未初始化的变量将在这个时候得到初始值,如静态分配到静态存储区的变量(extern,static),它们被储存到普通内存中。在独立环境中,程序的载入必须由手工安排,也可能是通过把可执行代码置入只读内存(ROM)来完成。
然后,程序的执行便开始。在宿主环境中,==通常一个小型的启动程序与程序链接在一起。它负责处理一系列日常事务,如收集命令行参数以便程序能够访问它们。==接着,便开始调用main函数。
接着,开始执行程序代码。**在绝大多数机器里,程序将使用一个运行时堆栈(heap/stack),它用于存储函数的局部变量和返回地址。
这样的话,程序很好地将自己所需要长期使用的变量和临时调用的变量进行区分,它们的生命期不同,不浪费额外内存空间
最后,就是程序执行的终止。它可以由多种不同的原因引起。正常的终止就是main函数返回。除此之外,程序也可能是因为用户按下break键或者电话连接的挂起而终止,另外也可能是在执行过程中出现错误而自行中断。
数据结构是在整个计算机科学与技术领域中被广泛使用的术语,它用来反映一个数据的内部构成。 数据抽象是**把系统中需要处理的数据和这些数据上的操作结合在一起,根据其功能、性质、作用等因素抽象成不同的抽象数据类型(abstract data type,ADT)。**抽象数据类型是用户进行软件设计时从问题的数学模型中抽象出来的逻辑数据结构和逻辑上的一组操作,而不考虑计算机的具体存储结构和操作的具体实现。抽象数据类型的表示和实现都可以封装起来,便于移植和重用
数据结构反映数据的内部构成,即数据由哪些成分构成,以什么方式构成,以及数据元素之间呈现什么结构。数据结构就是数据存在的形式。 具有相同数据结构的数据归为一类,可以用数据类型来定义。
另外,数据结构有逻辑上的数据结构和物理上的数据结构之分。逻辑上的数据结构反映各数据之间的逻辑关系;物理上的数据结构反映各数据在计算机内的存储安排。
数据类型是一个值的集合和定义在此集合上的一组操作的总称。数据类型一般指数据元。数据元( Data Element),也称为数据元素,是用一组属性描述其定义、标识、表示和允许值的数据单元,在一定语境下,通常用于构建一个语义正确、独立且无歧义的特定概念语义的信息单元。 可以认为,数据类型是在程序设计语言中已经实现了的数据结构。
数据类型可分为简单类型和构造类型。简单类型中的每个数据是无法再分割的整体,如一个整数,浮点数,字符等都是无法再分割的整体,所以它们所属的类型均为简单类型。在构造类型中,允许各数据本身具有复杂的数据结构,允许复合嵌套,如由若干个整数构成的整数数组。
对于构造类型,其数据结构就是相应元素的集合和元素之间所含关系的集合。简单来说就是数据结构=数据类型(一种或多种)+成员之间的关联方式。
标准还定义了几个三字母词(trigrph),三字母词就是几个字符的序列,合起来表示另一个字符。三字母词使C环境可以在某些缺少一些必需字符的字符集上实现。 这里列出了一些三字母词以及它们所代表的字符。
转义字符’反斜杠+?'在书写连续多个问号时使用,防止连续的问号被解释为三字母词。
程序对数据进行操作。变量数据具有3个属性——作用域,链接属性以及存储类型。这3个属性决定了一个变量的“可视性”(也就是它可以在什么地方使用)和“生命期”(它的值将保持多久)。
当变量在程序的某个部分被声明时,它只有在程序的一定区域内才能被访问。这个区域由标识符的作用域(scope)决定。标识符的作用域就是程序中该标识符可以被使用的区域。
编译器可以确认4种不同类型的作用域——代码块作用域、文件作用域、原型作用域和函数作用域。
位于一对花括号之间的所有语句称为一个代码块。任何在代码块的开始位置声明的标识符都具有代码块作用域(block scope),表示它们可以被这个代码块中所有语句访问。当标识符不在开始位置声明时,作用域为从声明的位置开始,一直到代码块结束。当代码块处于嵌套状态时,声明于内层代码的标识符的作用域达到该代码块的尾部时便告终止。然而,如果内层代码块有一个标识符的名字与外层代码块的一个标识符同名,内层的那个标识符就将屏蔽外层的标识符——外层的那个同名标识符无法在内层代码块中通过名字访问。(如在主函数中有一段代码块内调用了子函数,而子函数中也存在一个与主函数代码块中同名的标识符,则在调用期间系统访问这个名字的标识符是子函数中的那个,而不是主函数中的,这就好比在我们调用函数通过值传递的时候,通常定义的形参列表是与实参同名的变量,它们不会产生纠缠,只会在各自的作用域内作用。)
另外:函数形参的作用域开始于形参的声明处,结束于该函数体原型的结尾。 如果在函数体内部声明了名字与形参相同的局部变量,则意味着该局部变量与形参作用域相同,因此形参的作用域设定是ANSI C很好地规避了形参与内部局部变量重叠的这种错误可能性,它把形参的作用域设定为整个子函数代码块,这样声明于函数内部的局部变量就无法与形参同名,因为它们的作用域相同,会出现报错。
如:
#include<stdio.h>
int f (int x) {
int x;
return x+1; }
void main(void) {
int x=2;
x=f(x);
printf("x=%d\n",x); } //该程序会出现报错,因为在f函数内部定义了一个与形参相同名字的局部变量:error C2082: redefinition of
formal parameter 'x' ```
任何在所有代码块之外声明的标识符都具有文件作用域(file scope),它表示这些标识符从它们声明之处开始一直作用到它所在的源文件结尾,在本源文件区域内访问。 在文件中定义的函数名也具有文件作用域,因为函数名本身并不属于任何代码块。应该指出的是,在头文件中编写并通过#include指令包含到其他文件中的声明就好像它们是直接写在那些文件中一样。它们的作用域并不局限于头文件的文件尾。
原型作用域(prototype scope)只适用于在函数原型中声明的参数名,也就是形参。其作用范围开始于形参的声明处,结束于该函数体原型的结尾。 在形参被实参赋予了相应的值后,形参可以被理解为成为了该函数的一个代码块作用域的同名同类型的局部变量。
函数作用域(function scope)只适用于语句标签,语句标签用于goto语句。 基本上,函数作用域可以简化为一条规则——一个函数中的所有语句标签必须唯一。
另外:goto语句的使用方法
当组成一个程序的各个源文件分别被编译之后,所有的目标文件以及那些从一个或多个函数库中引用的函数将链接在一起,形成可执行程序。然而,如果相同的标识符出现在几个不同的源文件中时,它们是像Pascal那样表示同一个实体,还是表示不同的实体?标识符的 链接属性(linkage) 决定如何处理在不同文件中出现的标识符。链接属性一共有3种——external(外部)、internal(内部)以及none(无链接属性)。
属于external链接属性的标识符不论在不同的文件中声明多少次,位于多少个不同的源文件,它们都是表示同一个实体。
另外:在这里补充一下对extern类型变量的作用域和external链接属性的变量的区别与联系
关键字extern和static用于在声明中修改标识符的链接属性。
static int i;
int func()
{
//statements;
extern int i; //这里的声明并不会修改首行对i变量的声明的原始的链接属性,i变量依然是internal链接属性
}
变量的存储类型(storage class)是指存储变量值的内存类型。变量的存储类型决定了变量何时创建,何时销毁以及它的值将保持多久。 有3个地方可以用于储存变量(注意,这里所说的是变量的储存区,像函数,标识符等代码都是储存于程序代码区的(存放程序的代码,即CPU执行的机器指令),以及数组名,常量或字符串常量等储存在文字常量区):普通内存(静态区),运行时堆栈,硬件寄存器。
变量的缺省存储类型取决于它的声明位置。凡是在任何代码块之外声明的缺省储存类型变量总是存储于静态内存中,这类变量称为静态内存分配变量。静态内存分配变量包括:全局变量(extern修饰)和静态变量(static修饰)。 对于这类变量,无法为它们指定其他存储类型。(如局部变量除了本身可以被自动存储于栈区以外,还可以手动为其分配堆区内存,即malloc函数的引用)静态内存分配变量在程序运行之前创建(在执行步骤中的第一步,见上文),在程序的整个执行期间始终存在。它始终保持原先的值,除非给它赋予一个不同的值或者程序结束由操作系统回收释放内存空间。
另外,静态内存分配的变量若未初始化赋值,则系统会自动为其赋值。若是数值型变量则赋值0,若是字符型则赋值’\0’。数组亦是一样,static
int a[5];则系统在其定义声明时自动赋予五个0,static char str[5];则系统在定义声明时自动赋五个’\0’。
程序运行时,由程序员调用malloc()函数来主动申请的,需使用free()函数来释放内存,若申请了堆区内存,之后忘记释放内存,很容易造成内存泄漏。
在程序执行到声明自动变量的代码块时,自动变量才被创建,当程序的执行流离开该代码块时,这些自动变量(栈区)便自动销毁。
另外,函数的形式参数不能声明为静态,因为实参总是在堆栈中传递给函数,用于支持递归。并且动态内存分配的变量若未初始化赋值,则系统会为其赋予随机值。若数值型变量就赋值随机数值,字符型变量就赋予随机字符,数组也同理。
最后,关键字register用于自动变量的声明,提示它们应该存储于机器的硬件寄存器而不是内存中,这类变量称为寄存器变量。通常,寄存器变量比存储于内存的变量访问起来效率更高。但是编译器并不一定要理睬register关键字,如果有太多的变量被声明为register,它只会选取前几个实际存储与寄存器中,其余的就按照普通自动变量处理。如果一个编译器字节具有一讨寄存器优化方案,它也可能忽略register关键字,其依据是由编译器来决定哪些变量存储于寄存器中要比人脑的决定更为合理一些。在经典情况下,我们希望把使用频率最高的那些变量声明为寄存器变量。
寄存器变量的创建,销毁时间和自动变量相同,但它需要一些额外的工作。当函数开始执行时,它把需要使用的所有寄存器的内容都保存到堆栈中,当函数返回时,这些值再复制回寄存器中。
#include<stdio.h>
void fun()
{
static int b;
printf("&b=%d,b=%d\n",&b,b); // 打印结果:&b=4357684,b=0
}
void main()
{
static int b;
b=1;
fun();
printf("&b=%d,b=%d\n",&b,b); // 打印结果:&b=4357688,b=1
}
----------------------------------------------------------------------------------------------------------------------------- Stay hungry,Stay foolish .
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。