传智播客C语言入门6_第1页
传智播客C语言入门6_第2页
传智播客C语言入门6_第3页
传智播客C语言入门6_第4页
传智播客C语言入门6_第5页
已阅读5页,还剩135页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

传智播客C语言入门教程(6),讲师:尹成QQ:77025077博客:,C语言,C+语言,传智播客,高薪就业,2.不仅仅局限于内存文件,3.灵活却难以理解指针进阶,4.更深入的理解函数进阶,5.生存期、作用域与可见域.,传智播客C语言入门教程(6)大纲,1.穷人时代如何节约内存位运算,C语言课程概述,6.初学者的疑难解答,6.1穷人时代如何节约内存位运算,伟大的比尔盖茨在DOS时代曾经有过这样的说法“640K的内存对一般人来说已经足够”,可现在的电脑内存动辄就是1G、2G,且不评判上述那句话的正误,但从日新月异的硬件发展来看,我们就能猜出二三十年前的程序员是如何在内存使用上精打细算的,与之相比,现代程序员好像少了很多烦恼,内存似乎永远用不完,但内存再大,也要精打细算,从位运算的角度看一下如何合理节约内存的使用。,6.1.1什么是位运算,从开始到现在,经常出现的一个词是内存单元,即1B,我们说char型占1个内存单元(1B),而short型占2个内存单元(2B)。1B被当成整体来看,但不要忘记有下面的等式成立:1B=8bits1个字节有8个位,每个位有0、1两个取值,从这个角度上说,1个字节所能包含的意义似乎比预想的要大的多。,6.1.2开灯关灯,举例来说,房间里有8盏灯,为了控制每盏灯的亮灭,可以声明8个Byte变量,变量为0代表灯灭,变量非0代表灯亮,这完全行得通,而且看起来很有效率。能否换种角度思考,能不能只有1个字节的8个位来控制8盏灯?该位为0代表灯灭,该位为1代表灯亮,答案是“可以”,比较两种方法,发现使用位操作的方式有效节省了内存,如所示:,6.1.3改变状态,假设某个时刻,灯1和灯2亮,而其他灯都灭,此时控制字为11000000,想变换下状态,让灯1和灯3亮,其他灯灭,目标控制字为10100000,只要赋值给该单元即可。问题又来了,如果原来不知道哪几盏灯亮哪几盏灯灭,现在还是想让第3盏灯亮起来,赋值操作看来是行不通了,要如何做呢?在这个背景下,位运算的概念被提出,即能否只改变控制第3盏灯的那一位的状态?本章的剩余章节将结合开灯关灯这一场景讲述位运算的内容。总体来说,C语言中的位运算符有以下两类:位逻辑运算符:/*声明操作数czs1,初始化*/shortczs2=123;/*声明操作数czs2,初始化*/shortResAnd,ResOr,ResNot,Res;/*声明几个变量用以保存结果*/ResAnd=czs1/*等待,按任意键继续*/,6.1.10移位运算,顾名思义,移位运算就是将某个量中的bits向左或向右移动,该量的值也会相应发生变化,在开灯关灯这个场景中,移位运算的一个重要应用是实现流水灯效果,即按从1到8或从8到1的顺序每次只亮一个灯。,6.1.11移位运算基本形式,移位运算表达式的基本形式为:An;/*右移*/A称为操作数,其必须为数字型变量或数字型常量,此处的数字型包括整型、浮点型和char型,A中存储的0、1序列向左或右移动n位,移动后的值作为整个表达式的输出,执行移位运算并不改变操作数A的值。,6.1.12移位运算演示案例,#include/*使用printf要包含的头文件*/#includevoidmain(void)/*主函数*/unsignedshortczs=1889;/*声明操作数czs,初始化*/unsignedshortls=0,rs=0;/*声明两个结果变量,保存左移右移的结果*/rs=czs4;/*左移操作*/ls=czs关闭文件系统自动打开和关闭三个标准文件:标准输入-键盘stdin标准输出-显示器stdout标准出错输出-显示器stderr打开文件fopen函数原型:FILE*fopen(char*name,char*mode),功能:按指定方式打开文件返值:正常打开,为指向文件结构体的指针;打开失败,为NULL,要打开的文件名,使用文件方式,例文件打开与测试FILE*fp;fp=fopen(“aa.c”,“w”);if(fp=NULL)printf(“Fileopenerror!n”);exit(0);,例FILE*fp;fp=fopen(“c:fengyibkctest.dat”,”r”);,例FILE*fp;char*filename=“c:fengyibkctest.dat”fp=fopen(filename,”r”);,6.2.12关闭文件fclose,作用:使文件指针变量与文件“脱钩”,释放文件结构体和文件指针函数原型:intfclose(FILE*fp),功能:关闭fp指向的文件返值:正常关闭为0;出错时,非0,文件打开时返回的文件类型指针,不关闭文件可能会丢失数据,6.2.13字符读写函数fgetc和fputc,fgetc(fp)fp为文件句柄,函数值为得到的字符。fputc(ch,fp)ch为字符变量,fp为句柄。成功函数返回相应字符;失败返回EOF。按照文本的方式读取字符,以及写入字符。feof函数原型:intfeof(FILE*fp)功能:判断文件是否结束返值:文件结束,返回真(非0);文件未结束,返回0,6.2.14字符串I/O:fgets与fputs,函数原型:,char*fgets(char*s,intn,FILE*fp)intfputs(char*s,FILE*fp),功能:从fp指向的文件读/写一个字符串返值:fgets正常时返回读取字符串的首地址;出错或文件尾,返回NULLfputs正常时返回写入的最后一个字符;出错为EOF,fgets从fp所指文件读n-1个字符送入s指向的内存区,并在最后加一个0(若读入n-1个字符前遇换行符或文件尾(EOF)即结束),fputs把s指向的字符串写入fp指向的文件,6.2.14数据块I/O:fread与fwrite,数据块I/O:fread与fwrite函数原型:,size_tfread(void*buffer,size_tsize,size_tcount,FILE*fp)size_tfwrite(void*buffer,size_tsize,size_tcount,FILE*fp),功能:读/写数据块返值:成功,返回读/写的块数;出错或文件尾,返回0说明:typedefunsignedsize_t;buffer:指向要输入/输出数据块的首地址的指针size:每个要读/写的数据块的大小(字节数)count:要读/写的数据块的个数fp:要读/写的文件指针fread与fwrite一般用于二进制文件的输入/输出,6.2.15格式化I/O:fprintf与fscanf,格式化I/O:fprintf与fscanf函数原型:,intfprintf(FILE*fp,constchar*format,argument,)intfscanf(FILE*fp,constchar*format,address,),功能:按格式对文件进行I/O操作返值:成功,返回I/O的个数;出错或文件尾,返回EOF,例fprintf(fp,“%d,%6.2f”,i,t);/将i和t按%d,%6.2f格式输出到fp文件fscanf(fp,“%d,%f”,/若文件中有3,4.5,则将3送入i,4.5送入t,6.2.16如何检测错误,出错的检测ferror函数函数原型:intferror(FILE*fp)功能:测试文件是否出现错误返值:未出错,0;出错,非0说明每次调用文件输入输出函数,均产生一个新的ferror函数值,所以应及时测试fopen打开文件时,ferror函数初值自动置为0,6.2.17文件定位,前面介绍的文件读写是针对顺序读写的情况,实际上,文件的读写方式有两种,一是顺序读写,位置指针按字节顺序从头到尾移动,另一种是随机读写,位置指针按需要移动到任意位置,随机形式多用于二进制文件的读写。如果要对文件进行随机读写,就需要控制文件位置指针的值,这就是文件定位,与文件定位有关的函数是rewind函数,fseek函数和ftell函数,本节来看一下这些函数的用法。,6.2.18文件定位移动到开头rewind,rewind函数没有返回值,其调用形式为;rewind(FILE*fp);该函数使得文件位置指针返回文件开头。,6.2.19得到当前位置ftell,随机形式允许文件位置指针跳来跳去,为得到文件指针的当前位置,C语言标准库提供了ftell函数,其原型为:longftell(FILE*);执行成功时,返回当前文件指针到文件头有多少个字节,否则,返回-1。,6.2.20移动指针fseek,文件定位中最重要的一个函数是fseek,用以控制、调整文件指针的值,从而改变下一次读写操作的位置,其函数原型为;intfseek(FILE*fp,longoffset,intstartPos);其中,fp是文件指针,startPos是起始点,offset是目标位置相对起始点的偏移量,可以为负数,如果函数操作执行成功,文件位置指针将被设定为“起始点+offset”,起始点并不是任意设定的,C语言给出了3中起始点方式,如所示:,6.2.21文件小结,6.2.22文件习题,编程实现统计英文文档中大小写字母数字的个数。学习下汉字标准,并实现统计中文文档。,1,实现文件的增删查改。,2,实现文件的批量修改,在文件末位添加一段话。,3,实现根据密码加密解密一个文件。,4,6.3指针高级进阶,6.3.1指针与数组名,数组名是一种常指针(不能修改),其值等于数组占据内存单元的首地址,但其类型取决于数组的维数。二维数组aij大家认为int*p4;,6.3.5指针数组练习,有一个指针数组,其元素分别指向一个整型数组的元素,用指向指针数据的指针变量,输出整型数组各元素的值。然后从小到大显示。,6.3.6练习分析,#includeintmain()inta5=1,3,5,7,9;int*num5=,p,6.3.7指针数组,将若干字符串按字母顺序(由小到大)输出。解题思路:定义一个指针数组,用各字符串对它进行初始化,然后排序,但不是移动字符串,而是改变指针数组的各元素的指向。,charname59=“gain”,“much”,“stronger”,“point”,“bye”;,char*name5=“gain”,“much”,“stronger”,“point”,“bye”;,6.3.8二维数组与指针数组区别,二维数组存储空间固定字符指针数组相当于可变列长的二维数组,指针数组元素的作用相当于二维数组的行名但指针数组中元素是指针变量二维数组的行名是地址常量,6.3.9指向指针数据的指针,在了解了指针数组的基础上,需要了解指向指针数据的指针变量,简称为指向指针的指针。,name,p,6.3.10使用指向指针数据的指针变量,char*name=“Follow”,“Great”,“FORTRAN”,“Computer”;char*p;inti;for(i=0;i1)+argv;printf(“%sn”,*argv);-argc;return0;,6.3.14-调试命令行,6.3.15指向数组结构的指针,普通数组名可以看成是“指向数组元素首地址的常指针”,结构体数组名同样可以看成是指向结构体数组元素首地址的常指针,也可以声明一个结构指针变量,使其指向数组元素首地址,这两种方式都能实现通过指针访问数组元素,我们来亲自动手实践一下。,6.3.16函数指针的内存原理,函数被载入内存,函数必然有一个地址是函数的入口,我们用这个地址来调用,函数名也是指向函数入口点的指针,我们可以通过函数名找到函数的执行入口。同时C语言的编译器(无论VC或者GCC)都有这样的规则。针对函数voidrun(),函数名run解析为函数的地址,run,”即声明了一个指针数组A,大小为3,其中每个元素都是int型指针。如果数组元素都是指向同型函数(返回值类型相同,参数类型相同)的指针,该数组称为函数指针数组,来看一个例子:double(*f5)();f是一个数组,有5个元素,元素都是函数指针,指向的函数类型是没有参数且返回double类型的函数。函数指针数组的使用方式和普通数组完全一致,我们来亲自演练一下。,6.3.18指向函数指针的指针,再来看下述语句:double(*f5)();已经知道,数组名可作为指向数组首元素起始地址的常指针,那函数指针数组的数组名是什么呢?类推得出,函数指针数组名,对应上面语句中的f,是指向函数指针的常指针,下述代码声明了一个指向函数指针的指针变量p,并用f为其初始化:double(*p)()=f;,6.3.19指针小练习,做个小测试,用变量名p给出下列有关指针的定义:(1)一个指向整型数的指针。(2)一个指向整型数指针的指针(3)一个有10个整型指针的数组(4)一个指向有10个整型数数组的指针。(5)一个指向函数的指针,该函数有一个整型参数,并返回一个整型数。(6)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数,并返回一个整型数。(7)一个指向函数指针的指针,所指向的函数有一个整型参数,并返回一个整型数。,6.3.20对比define与typedef,C语言中,内置类型,如int型,其指针(int*)可以看成种新的类型,那有没有函数指针类型呢?借助前面介绍的typedef,能容易地将函数指针类型化。在结构体和共用体一节中介绍了typedef和#define的基本用法,对比了以下两个语句:typedefdouble*DP;DPpDouble1,pDouble2;与#defineDPdouble*DPpDouble1,pDouble2;不知大家是否还记得两者的不同,通俗地说,#define是种字面替换,而typedef却是引入一个新的助记符号,这么说稍显枯燥,下面给出一个简单的理解方式:试着将上面语句中的typedef和#define去掉试试看。对typedef语句“typedefdouble*DP;”来说,去掉typedef后,其仍然是条完整的C语句“double*DP;”,该语句用以声明一个double类型的指针变量DP。由此可以理解:typedef的作用是将变量名作为(或说定义为)该变量所属类型的别名(或说助记符)。#define不具备这种特点,去掉#define后,“DPdouble*”并不是一条合法的C语句。如何用define与typedef定义函数指针?,6.4更深入的理解函数进阶,本节从更深层次帮助大家理解函数。主要是函数的参数的传递两种形式,传值与传地址。函数的输入-参数,函数的输出-返回值不仅可以是int,double等等也可以是数组,结构体等等。,6.4.1参数传递的副本机制,如果将函数比作剧本,那形参和实参的关系相当于角色和演员的关系,函数的参数传递有传值和传地址两种方式。传值调用时,在函数内对形参的改变都不会影响实参,要想在函数内对实参进行操作,必须采用传地址调用的方式。这是形象化的理解,从本质上说,这是由参数传递的副本机制决定的。所谓副本机制,是指copy(拷贝)的思想,不论是传值调用还是传址调用,编译器都要为每个参数制作临时副本,或称拷贝,函数体中对参数的修改都是对副本的修改,下面具体分析之。,6.4.2传址调用的副本机制,相比传值调用,传址调用似乎要复杂一点,但只要知道,传址调用也是通过副本机制,便能很好地理解传址调用的机理.,6.4.3return局部变量为什么合法,函数返回的副本机制很好地解释了为什么return一个局部变量是合法的,来看一段简单的求和函数代码:intsum(inta,intb)/*函数定义*/intc=a+b;/*局部变量c*/returnc;/*返回*/intd=sum(1,2);/*函数调用*/来看语句“intd=sum(1,2);”,该语句先执行函数sum,sum函数执行完毕后将结果赋值给int型变量d,如果从字面上理解,是将c赋值给d,但实际上,在执行赋值操作时,由于函数sum已经执行完毕返回,函数中的局部变量c已被撤销,不存在了。实际上,在c被撤销前,函数已经为返回值c创建了副本,保存在特定的位置上,赋值操作是由该位置处的副本完成的,形象的示意如所示。,6.4.4函数返回值的副本机制,如果要细分,函数返回也可以认为存在传值和传址两种方式。函数返回同样也是根据副本机制来处理的,首先来回顾下函数返回的流程:当执行到return语句时,return的值被复制到某个内存单元或寄存器中,其地址是由编译器来维护的,程序员无法直接访问该地址,也就是说,在函数执行完毕,相关现场被撤销前,返回的值被复制保存到了某个地方,编译器访问该位置即可知道函数的返回值。该位置即可看成是函数中返回值的副本。对函数返回取地址是不合法的,即假设存在如下函数:intA(intb,intc);不允许使用如下形式的语句:,6.4.5动态分配内存可以跨函数使用,下面来看一下如何通过返回指针在函数中动态申请内存,感受下#include/*使用printf要包含的头文件*/char*GetMemory(intnum)/*定义函数GetMemory*/char*p=(char*)malloc(sizeof(char)*num);/*动态申请内存*/returnp;voidmain(void)/*主函数*/char*str=NULL;/*声明一char型指针str并初始化为NULL*/str=GetMemory(10);/*调用函数GetMemory*/if(str!=NULL)/*判断动态内存是否申请成功*/strcpy(str,Hello,C);/*将一个C风格字符串复制给str*/printf(%s,str);free(str);/*释放动态申请的内存*/getchar();/*等待,按任意键结束*/,6.4.6如何分配内存跨函数使用,动态申请内存是在堆中完成的,而函数返回不会释放堆内存,但不要忘记,函数返回时,栈内存中的内容会被自动清除,因此,不要返回指向栈内存的指针。请试着分析下述的问题所在:,6.4.7返回指向只读存储区的指针,如果将中的GetMemory函数修改如下,会怎样?char*GetMemory(void)/*定义函数GetMemory*/char*p=Hello,C;returnp;/*返回局部指针*/Hello,C作为常量字符串,位于程序的只读存储区(.rodata),此时,返回指向只读存储区的指针p并没有问题,但该指针只能用于输出,而不能用于输入改写。,6.4.8函数与结构体,结构体可以看成一种数据组织方式,将很多不同类型的相关数据打包,构成一种新的类型,从这种意义上说,结构体变量完全可以当成是一种普通类型的变量来使用。结构体变量作函数参数时,也有传值和传址两种方式,函数返回亦是如此,既可以返回结构体变量,也可以返回指向非局部结构体变量的指针。,6.4.9结构体变量的传值和传址调用,采用值传递时,在函数内将生成实参的“复制品”,如果参数多是像int,char之类的简单变量,这些变量占用的内存并不多,复制也快。但结构或共用体变量往往由多个成员变量组成,占用内存大,如果复制一份,会造成时间和空间双重浪费。采用地址传递不会造成时空浪费,因为不管是多么复杂的结构类型,指针参数只占4个内存字节。,6.4.10结构体变量的成员作为函数参数,结构体变量的数据成员作函数实参时,结构体变量的数据成员可以当成是普通变量来使用,同样存在传值和传址两种函数调用方式.,6.4.11返回结构体的函数,如果函数的返回值是某个结构体变量,常称该函数为结构体型函数,结构体型函数定义的基本格式为:struct结构体名函数名(形参列表)函数体;声明一个结构体型函数时也不要遗漏struct,如下:struct结构体名函数名(形参列表);,6.4.12返回结构体指针的函数,结构体指针同样可以作为函数的返回类型,前提是该指针不是指向栈内存的,换句话说,该指针不是指向局部结构体变量的。与返回结构体变量相比,返回结构体指针大大节省了函数返回时创建副本的时空开销,有较高的效率。,6.4.13函数与数组,数组是种使用广泛的数据结构,数组名和数组元素都可以作为函数的参数,实现函数间的数据传递和共享。此外,由于数组名和指针的对应关系,在一些需要指针型参数的场合,可以用数组名(即常指针)作函数参数。,(1)把数组的元素作实参inta1=1,2,3fun1(a2)(2)把数组名作实参inta=1,2,3fun2(a),6.4.14数组名作为函数的参数,77,voidmain()floats5=80,82,70,90,68;floata1=f1(s0,s1,s2,s3,s4);/数组元素作实参floata2=f2(s,5);/数组名(数组首地址)作实参,floata1(floata,floatb,floatc,floatd,floate)return(a+b+c+d+e)/5;,floata2(floatscore,intn)floatresult=0;inti;for(i=0;in;i+)result+=scorei;returnresult/n;,在调用函数时需要传递多个实参,效率低、通用性差,在调用函数时只需传递两个实参,效率高、通用性强,6.4.15数组名作为函数的参数,数组名既可以作为函数的形参,也可以作为函数的实参,数组名实际上是数组元素的首地址,所以,数组名作函数参数属于传址调用,当用数组名作为函数实参时,函数形参会接收到此数组的首地址。这就意味着函数形参数组首地址等于实参数组首地址。在函数中对形参数组的操作直接作用于实参数组。,voidmain()inta3=4,5,6;f(a);/数组名作实参,voidf(intb3)b0=8;b1=9;b2=10;,6.4.16数组名作函数参数,例将数组a中的n个整数按相反顺序存放,实参与形参均用数组,voidinv(intx,intn)intt,i,j,m=(n-1)/2;for(i=0;i=m;i+)j=n-1-i;t=xi;xi=xj;xj=t;main()inti,a10=3,7,9,11,0,6,7,5,4,2;inv(a,10);printf(Thearrayhasbeenreverted:n);for(i=0;i10;i+)printf(%d,ai);printf(n);,m=4,6.4.17数组名作函数参数,例将数组a中的n个整数按相反顺序存放,voidinv(int*x,intn)intt,*p,*i,*j,m=(n-1)/2;i=x;j=x+n-1;p=x+m;for(;i=p;i+,j-)t=*i;*i=*j;*j=t;main()inti,a10=3,7,9,11,0,6,7,5,4,2;inv(a,10);printf(Thearrayhasbeenreverted:n);for(i=0;i10;i+)printf(%d,ai);printf(n);,实参用数组,形参用指针变量,6.4.18数组名作函数参数,例将数组a中的n个整数按相反顺序存放,voidinv(int*x,intn)intt,*i,*j,*p,m=(n-1)/2;i=x;j=x+n-1;p=x+m;for(;i=p;i+,j-)t=*i;*i=*j;*j=t;main()inti,a10,*p=a;for(i=0;i10;i+,p+)scanf(%d,p);p=a;inv(p,10);printf(Thearrayhasbeenreverted:n);for(p=a;pa+10;p+)printf(%d,*p);,实参与形参均用指针变量,6.4.19数组名作函数参数,例将数组a中的n个整数按相反顺序存放,voidinv(intx,intn)intt,i,j,m=(n-1)/2;for(i=0;i=m;i+)j=n-1-i;t=xi;xi=xj;xj=t;main()inti,a10,*p=a;for(i=0;i10;i+,p+)scanf(%d,p);p=a;inv(p,10);printf(Thearrayhasbeenreverted:n);for(p=arr;p0)intx;,成对的大括号及内部的语句组成了程序块。函数体、选择和循环结构中都要用大括号表示边界。大括号内定义的变量,必须紧跟在左大括号后面,其作用范围是从定义处直到配对的右大括号之前。若多层大括号中出现重名变量,外层同名变量在内层中被屏蔽不起作用。,z作用域,x作用域,y作用域,6.5.11作用域与可见域,变量可以在程序中三个地方说明:函数的参数定义部分形式参数函数内部局部变量函数外部全局变量,6.5.12auto变量,函数的形参及代码块中定义的变量都属于auto变量,这是C语言中应用最广的一种变量,这类变量是栈分配的,是动态分配存储空间的。举函数形参为例,当调用该函数时,为形参分配存储空间,当函数调用结束时,就自动释放这些存储空间。对代码块中定义的变量(包含函数中定义的变量),当执行到变量声明语句时,系统为这些auto变量分配空间,当程序流程离开代码块时,这些变量被自动撤销,其占用的内存空间被释放。,6.5.13定义格式,自动变量的定义格式为auto数据类型变量1=初始化表达式,变量2=初始化表达式;其中,方括号表示可以省略,此处变量不仅指普通内置类型的变量,还包括数组、结构体和指针等复合结构。C语言默认所定义的变量是auto变量,在以前所举例子中,函数和代码块中的局部变量并没有使用关键字auto,这实际上是遵循了C语言的默认规定,举个例子来说,在一个函数中,如下定义:inta;floatb:自动被C编译器解释为:autointa;autofloatb;,6.5.14作用域和生存期,auto变量的作用域和生存期都局限在定义它的代码块中,所谓代码块,是指用两个花括号包裹起来的代码行,函数只是代码块的一种,常见的代码块还有if结构、for结构等等,哪怕只是两个成对花括号,也能构成一个独立代码块。此外,结合“先声明,后使用”的原则,可知,auto变量的作用域和生存期对应着从定义到所在代码块结束这块时空区域。来看下面的函数:intfunc(intm,intn)intx;/*等价于autointx;*/inta,b,c;/*等价于autointa,b,c;*/inty,z;/*等价于autointy,z;*/return0;,6.5.15屏蔽,代码块可以嵌套应用形成一定的层次结构,那内外层代码块中可否定义同名变量呢?如果可以,这些同名变量有什么关系呢?,/*此处记为A行,声明3个自动变量,作用域和生存期为从A行到main函数执行结束*/intx=1,y=2,z=3;printf(xis%d,yis%d,zis%dn,x,y,z);/*#1*/*此处记为B行,声明的y,作用域和生存期为从B行到#4,屏蔽了A行定义的y*/inty=6;printf(xis%d,yis%d,zis%dn,x,y,z);/*#2*/*此处记为C行,声明的y和z,作用域和生存期为从C行到#3*/*屏蔽了B行定义的y和A行定义的z*/inty=8,z=9;printf(xis%d,yis%d,zis%dn,x,y,z);/*#3*/printf(xis%d,yis%d,zis%dn,x,y,z);,6.5.16重复定义,auto变量不能重复定义,所谓重复,是指在同一代码块中,出现两个同名变量。此处所指的同一代码块,不包括屏蔽的情况。下面的代码就犯了重复定义的错误:if()intx,y;doublex,y;并列层次的代码块中可以出现同名变量而不会引起混淆,最普遍的一个例子就是函数,由于所有的函数都是在外部定义的,包括main函数在内的所有函数都是并列的,因此,函数A内定义的auto变量在函数B内是完全不可见的,即使两个函数中定义了同名变量,编译器也能很好地将其区分开,这大大方便了函数的编写。,6.5.17初始化,某些编译器并不会自动为auto变量初始化,这项工作必须在变量定义时由程序员显式完成,否则,变量的值是随机不确定的。不论是指针还是普通变量,时刻提醒自己注意初始化,能有效防止一些稀奇古怪错误的发生。,6.5.18register变量,一般来说,CPU访问内部寄存器的速度大大高于访问内存的速度,因此,有人提议,能否将一些应用频繁的变量放在CPU的通用寄存器中,这样,在使用该变量时便不必再访问内存,直接从寄存器中取,将大大提高程序运行的效率,因此,C语言引入了register变量,称为寄存器变量。,6.5.20extern变量,extern变量又称全局变量,放在静态存储区,所谓全局,是说该变量可以在程序的任意位置使用,其作用域是整个程序代码范围内,和auto变量不同的是,extern变量有定义和声明之分。,6.5.19寄存器变量的定义格式,寄存器变量的定义格式为:register数据类型变量1=初始化表达式,变量2=初始化表达式;和auto变量一样,register变量也属于局部变量。只能在函数体内定义register变量,CPU使用寄存器中数据的速度要远远快于使用内存中的数据速度,因此,应用好CPU内的寄存器可以大大提高程序的运行效率和速度。但是,CPU中寄存器的数量有限,所以,通常是把使用频繁的变量定义为寄存器变量。,VC编译器会自动优化设置程序运行时变量的存放位置,用户不必做此声明,6.5.21全局变量定义,全局变量定义的基本格式为:extern类型变量名=初始化表达式;此时,初始化表达式不可省略,此指令通知编译器在静态存储区中开辟一块指定类型大小的内存区域,用于存储该变量。下列语句创建了一个初始值为100的int型全局变量m:externintm=100;C语言规定,只要是在外部,即不是在任何一个函数内定义的变量,编译器就将其当作全局变量,无论变量定义前是否有extern说明符。也就是说,只要在函数外部书写下述形式即可:intm=100;当全局变量定义时,当且仅当省略了extern时,初始化表达式才可省略,系统默认将其初始化为0,对于定义的全局数组或结构,编译器将其中的每个元素或成员的所有位都初始化为0。,6.5.22变量定义与声明,一般为了叙述方便,把建立存储空间的变量声明称定义,而把不需要建立存储空间的声明称为声明在函数中出现的对变量的声明(除了用extern声明的以外)都是定义,6.5.23extern变量,externintx;/提升全局变量x作用范围至此voidmain()x+;intx=8;voidf1()x-;,若在全局变量定义处之前想要访问这个变量,可以在相应的地方用“extern”关键字提前声明此变量,从而将在后面定义的全局变量作用范围扩展到前面来。,x原来的作用范围,x现在的作用范围,6.5.24extern变量案例,115,voidnum()externintx,y;/*将全局变量x,y作用域扩展到此处*/inta=15,b=10;x=a-b;y=a+b;intx,y;/*全局变量x,y*/intmain()inta=7,b=5;x=a+b;/*x=7+5*/y=a-b;/*y=7-5*/num();printf(“%d,%d”,x,y);return0;,程序的运行结果是:5,25,6.5.25同多个文件中声明的外部变量,全局变量的作用范围还可以扩展至其它C程序文件,只需要在其它C程序文件中使用extern关键字说明一下即可。,intA;voidprintValue();/声明函数intmain()A=0;printValue();return0;,externintA;/声明外部变量AvoidprintValue()printf(“%d”,A);A+;,prog1.c,prog2.c,6.5.26全局变量示例,全局变量是与程序共存亡的,因此,全局变量的生存期不是关心的重点,经常讨论的是其作用域与可见域。全局变量的作用域是整个程序,不论该程序由几个文件组成,理论上,可以在程序的任意位置使用定义的全局变量,但在特定位置处,全局变量是否可见取决于是否对其进行了合理声明。不进行任何声明时,全局变量的可见域为从定义到本文件结束,来看一段示例代码:voidmain(void)/*主函数*/externintx;/*声明全局变量x*/printf(xis%d,x);/*可以访问*/printf(xis%d,x);/*错误,找不到x*/intx=5;,6.5.27全局变量示例,给定b的值,输入a,求a*b的值。解题思路:分别编写两个文件模块,其中文件file1包含主函数,另一个文件file2包含求a*b的函数。在file1文件中定义外部变量A,在file2中用extern声明外部变量A,把A的作用域扩展到file2文件。,6.5.28全局变量屏蔽,在auto变量一节中提到,内层代码块中声明的变量将屏蔽外层代码块中声明的同名变量,屏蔽准则对全局变量同样适用,externintx=10;/*全局变量x定义,带初始表达式*/doubley=30;/*extern可省略,只要定义在外部即可*/voidmain()/*主函数*/externfloatz;/*声明全局变量z*/z=5;/*对全局变量的操作*/voidprint(void);/*print函数声明*/print();/*print函数调用*/printf(xis%d,yis%f,zis%fn,x,y,z);/*输出全局变量*/floatz;/*全局变量z定义,默认初始化为0*/voidprint()/*print函数*/intx=1;/*局部变量屏蔽全局变量*/doubley=3;printf(xis%d,yis%f,zis%fn,x,y,z);/*输出局部变量*/,6.5.29全局变量利弊,全局变量带来的好处是显而易见的:(1)为函数间数据传递提供了新的途径,函数返回值仅仅只能有1个,很多情况下,这不能满足要求,而全局变量可用于更多处理结果。(2)利用全局变量可以减少形参和实参的个数,省去函数调用时的时空开销,提高程序运行的效率。但是,全局变量在程序执行期间都有效,一直占据着存储单元,不像局部变量等在调用执行期间临时占用内存,退出函数时便将其释放。最大的问题是降低了函数的封装性和通用性,由于函数中存在全局变量,因此,如果想把函数复用在其他文件中,必须连所涉及的全局变量一块移植过去,容易引发各种问题,造成程序不可靠。全局变量使得函数间独立性下降,耦合度上升,可移植性和可靠性变差。为了代码的可移植,在可以不使用全局变量的情况下应尽量避免使用全局变量。,6.5.30static变量的定义格式,static变量的定义格式为:static数据类型变量1=初始化表达式,变量2=初始化表达式;与extern变量都是全局变量不同,static变量有静态全局变量和静态局部变量之分:静态局部变量,除了生存期是整个程序执行期间(与程序共存亡)外,其作用域与可见域与普通auto变量完全一样。Static局部变量只有定义,没有声明。静态全局变量和extern变量的不同体现在作用域上,extern作用域是本程序的所有源代码文件,只要在一个文件中定义,在其他文件中使用时只要对其进行声明即可。而静态全局变量其作用域仅限于从定义位置起到本文件结束的一段代码区域,不能被其他文件中的函数所使用。静态全局变量实际上是对extern变量破坏封装性和可靠性的一种改良。当省略初始化表达式时,编译器自动以0初始化静态变量。对于数组或结构,编译器将其中的每个元素或成员的所有二进制位都初始化为0。,6.5.31static声明一个变量的作用,用static声明一个变量的作用是:(1)对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。常使用静态局部变量在函数调用间歇保存某些变量的值。(2)对全局变量用static声明,则该变量的作用域只限于本文件模块(即被声明的文件中)。,6.5.32extern变量和static变量的初始化,如果要对变量进行赋值初始化,只能使用常量表达式来初始化extern变量和static变量,常量表达式包括直接常量、#define常量、枚举常量和sizeof()运算符,下面的初始化代码都是合法的:intnum;/*编译器自动将num初始化为0*/intnumA=sizeof(double);/*sizeof运算符*/,6.5.33函数的作用域与可见域,C语言中的函数都是独立的代码块,以二进制形式存储在程序代码区,函数名可以看成是指向其对应代码块入口点的常量指针。以前接触到的函数都是外部的,类似于extern变量的用法,只要在一个文件中定义一次,并通过声明使其可见,便可以被源程序中其他源文件中的其他函数调用,实际上,也可定义只能在本文件调用的内部函数。,6.5.34内部函数,所谓内部函数,是指一个源文件中定义的函数只能被本文件中的函数调用,而不能被其他源文件中的函数调用。定义一个内部函数的方法是在其函数前使用关键字static,static返回类型函数名(参数表)函数体函数可以定义成静态的或者外部的静态函数只能在其程序文件内部被识别staticfn_typefn_name(argumentlist);外部函数可以被程序的所有文件识别externfn_typefn_name(argumentlist);,6.5.35外部函数的可见域,如果一个函数可以被其他源文件中的函数调用,称为外部函数,用关键字extern修饰,定义格式为:extern返回类型函数名(参数表)函数体中括号表示可省略,即C语言默认所定义的函数是外部的,这就是前面所举例子函数定义都直接采用“返回类型函数名(参数表)”的原因。和外部变量一样,在源程序中,外部函数只能定义一次,其作用域为所有的源程序文件,但其默认可见域为从函数定义位置起到该源文件结束,如果要在其他源文件中调用外部函数时,需要对该函数进行声明以扩展其可见域。可见域扩展的程度同样取决于声明的位置,如果是在代码块中声明的,扩展的范围是从声明位置起到代码块结束,如果是外部声明的,扩展范围是从声明位置起到该文件结束。声明的格式为:extern返回类型函数名(参数表);,6.5.36结构体定义的作用域与可见域,结构体类型定义位置的不同给程序带来什么样的影响呢结构体变量定义位置给程序带来什么样的影响呢结构体类型既可以定义在代码块内部,也可以定义在外部。当定义在代码块内部时,其作用域与可见域为定义位置起到代码块结束,当定义在外部时,其作用域和可见域为定义位置起到其所在的源文件结束。结构体变量作为全局变量,有声明与定义,作用域为全局,可见域为定义位置起到其

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论