假如我把C我的世界粘液块弹跳器堆到70以上会怎么样

我的图书馆
编译环境设置好以后,就可以正式学习C语言了。一、变量(一)、变量类型和表示方法1.什么是变量?一句话,变量是存储数据的值的空间。由于数值的类型有多种,有整数、小数(浮点数)、字符等等,那么对应的变量就有整型变量、浮点型变量、字符型变量。变量还有其他的具体分类。整型变量还可具体分为无符号型、长整型和短整型。浮点型也可分为单精度型、双精度型和长双精度型。此外还可以分为静态变量、外部变量、寄存器变量和自动存储变量。这些数据类型我们在本节和后面的章节中都会陆陆续续介绍。那么变量我们总要给它取个名字吧,这个名字我们叫做标识符。标识符的命名有一定的规则:(1).标识符只能由字母、数字和下划线三类字符组成(2).第一个字符必须是字母(第一个字符也可以是下划线,但被视作系统自定义的标识符)(3).大写字母和小写字母被认为是两个不同的字符,如A和a是两个不同的标识符 (4).标识符可以任意长,但只有前32位有效。有些旧的C版本对外部标识符的限制为6位。这是由于链接程序的限制所总成的,而不是C语言本身的局限性(5).标识符不能是C的关键字2.从上面的规则中,有个关键字的概念。那么什么叫关键字呢?从表面字意上也可以看出,关键字是C语言本身某些特性的一个表示,是唯一的代表某一个意思的。下面列出ANSI标准定义的32个C语言的关键字,这些关键字在以后的学习中基本上都会用到,到时再说它们的各自用法。auto break case char const continue defaultdo double else enum extern float forgoto if int long register return shortsigned sizeof static struct switch typedefunion unsigned void volatile whileC语言还包括一些不能用做标识符的扩展关键字。asm cdecl _cs _ds _es far huge interrupt near pascal _ss所以在以后的学习中,在给变量命名时要避开这些关键字。3. Turbo C2.0规定所有变量在使用前都必须加以说明。一条变量说明语句由数据类型和其后的一个或多个变量名组成。变量说明的形式如下: 类型 &变量表&; 这里类型是指Turbo C2.0的有效数据类型。变量表是一个或多个标识符名,每个标识符之间用,分隔。(二)、整型变量整型变量是用来存储整数的。整型变量又可具体分为好几种,最基本的整型变量是用类型说明符int声明的符号整型,形式如下:int C这里int是类型说明符,Counter是变量的名字。整型变量可以是有符号型、无符号型、长型、短型或象上面定义的普通符号整型。整型是16位的,长整型是32位,短整型等价于整型。以下是几种整型变量的声明示例:long int A /*长整型*/long A /*长整型,等价于上面*/signed int T /*有符号整型*/signed T /*有符号整型,等价于上面*/unsigned int O /*无符号整型*/unsigned O /*无符号整型,等价于上面*/short int SmallA /*短整型*/short SmallA /*短整型,等价于上面*/unsigned short int M /*无符号短整型*/unsigned short M /*无符号短整型,等价于上面*/从上面的示例可以看出,当定义长整型、短整型、符号整型或无符号整型时,可以省略关键字int。注明:1.用signed对整型变量进行有符号指定是多余的,因为除非用unsigned指定为无符号型,否则整型都是有符号的。2.当一个变量有几重特性时,声明关键字的顺序可以任意。以下几种声明是等价的:unsigned long T1;long unsigned T2;unsigned long int T3;unsigned int long T4;long unsigned int T5;long int unsigned T6;int unsigned long T7;int long unsigned T8;(三)、浮点类型变量顾名思义,浮点类型变量是用来存储带有小数的实数的。C语言中有三种不同的浮点类型,以下是对这三种不同类型的声明示例:float A /*单精度型*/double BigA /*双精度型*/long double ReallyBigA /*长双精度型*/这里Amount,BigAmount,ReallyBigAmount都是变量名。浮点型都是有符号的。(四)、字符型变量字符型变量中所存放的字符是计算机字符集中的字符。对于PC机上运行的C系统,字符型数据用8位单字节的ASCII码表示。程序用类型说明符char来声明字符型变量:这条声明语句声明了一个字符型变量,标识符为ch。当以这种形式声明变量之后,程序可以在表达式中引用这个变量,关于语句和表达式的知识在后面将会介绍。字符数据类型事实上是8位的整型数据类型,可以用于数值表达式中,与其他的整型数据同样使用。在这种情况下,字符型变量可以是有符号的,也可以是无符号的。对于无符号的字符型变量可以声明为:除非声明为无符号型,否则在算术运算和比较运算中,字符型变量一般作为8位有符号整型变量处理。还有其他的如指针型变量,void型变量,以后再介绍。
二、常量常量的意思就是不可改变的量,是一个常数。同变量一样,常量也分为整型常量、浮点型常量、字符型常量,还有字符串常量、转义字符常量和地址常量。(一)、整型常量整型常量可以是长整型、短整型、有符号型、无符号型。在Tubbo C 2.0里有符号整型常量的范围从-3,无符号整型的为0到65535;有符号长整型的范围为-到。无符号长整型的范围为0到。短整型同字符型。可以指定一个整型常量为二进制、八进制或十六进制,如以下语句:-129, 0x12fe, 0177常量的前面有符号0x,这个符号表示该常量是十六进制表示。如果前面的符号只有一个字母0,那么表示该常量是八进制。有时我们在常量的后面加上符号L或者U,来表示该常量是长整型或者无符号整型:22388L, 0x4efb2L, 40000U后缀可以是大写,也可以是小写。(二)、浮点型常量一个浮点型常量由整数和小数两部分构成,中间用十进制的小数点隔开。有些浮点树非常大或者非常小,用普通方法不容易表示,可以用科学计数法或者指数方法表示。下面是一个实例:3.E-30, 2.47E201注意在C语言中,数的大小也有一定的限制。对于float型浮点数,数的表示范围为-3.到3.,其中-1.到1.不可见。double型浮点型常数的表示范围为-1.79E308到1.79E308,其中-4.94E-324到4.94E-324不可见。在浮点型常量里我们也可以加上后缀。FloatNumber=1.6E10F; /*有符号浮点型*/LongDoubleNumber=3.45L; /*长双精度型*/后缀可大写也可小写。说明: 1. 浮点常数只有一种进制(十进制)。 2. 所有浮点常数都被默认为double。 3. 绝对值小于1的浮点数, 其小数点前面的零可以省略。如:0.22可写为.22, -0.0015E-3可写为-.0015E-3。 4. Turbo C默认格式输出浮点数时, 最多只保留小数点后六位(三)、字符型常量 字符型常量所表示的值是字符型变量所能包含的值。我们可以用ASCII表达式来表示一个字符型常量,或者用单引号内加反斜杠表示转义字符。'A', '\x2f', '\013';其中:\x表示后面的字符是十六进制数,\0表示后面的字符是八进制数。注意:在Turbo C 2.0中,字符型常量表示数的范围是-128到127,除非你把它声明为unsigned,这样就是0到255。(四)、字符串常量字符串常量就是一串字符,用双引号括起来表示。Hello,World!\nEnter selection:\aError!!!(五)、转义字符上面我们见到的\x,\n,\a等等都是叫转义字符,它告诉编译器需要用特殊的方式进行处理。下面给出所有的转义字符和所对应的意义:转义字符 描述\' 单引号\ 双引号\\ 反斜杠\0 空字符\0nnn 八进制数\a 声音符\b 退格符\f 换页符\n 换行符\r 回车符\t 水平制表符\v 垂直制表符\x 十六进制符它们的具体用法我们到讲输出语句时再介绍。(六)、地址常量我们前面说的变量是存储数据的空间,它们在内存里都有对应的地址。在C语言里可以用地址常量来引用这些地址,如下:&Counter, &S&是取地址符,作用是取出变量(或者函数)的地址。在后面的输入语句和指针里还会说明。这一节所讲到的变量和常量知识可以说是在一切程序中都要用到,特别是变量的声明和命名规则。无论是加减乘除还是大于小于,都需要用到运算符,在C语言中的运算符和我们平时用的基本上都差不多。运算符包括赋值运算符、算术运算符、逻辑运算符、位逻辑运算符、位移运算符、关系运算符、自增自减运算符。大多数运算符都是二目运算符,即运算符位于两个表达式之间。单目运算符的意思是运算符作用于单个表达式。(具体什么是表达式下一节再说)
一、赋值运算符赋值语句的作用是把某个常量或变量或表达式的值赋值给另一个变量。符号为‘=’。这里并不是等于的意思,只是赋值,等于用‘==’表示。注意:赋值语句左边的变量在程序的其他地方必须要声明。得已赋值的变量我们称为左值,因为它们出现在赋值语句的左边;产生值的表达式我们称为右值,因为她它们出现在赋值语句的右边。常数只能作为右值。例如:count=5;total1=total2=0;第一个赋值语句大家都能理解。 第二个赋值语句的意思是把0同时赋值给两个变量。这是因为赋值语句是从右向左运算的,也就是说从右端开始计算。这样它先total2=0;然后total1=total2;那么我们这样行不行呢?(total1=total2)=0;这样是不可以的,因为先要算括号里面的,这时total1=total2是一个表达式,而赋值语句的左边是不允许表达式存在的。
二、算术运算符在C语言中有两个单目和五个双目运算符。符号 功能+ 单目正- 单目负* 乘法/ 除法% 取模+ 加法- 减法下面是一些赋值语句的例子, 在赋值运算符右侧的表达式中就使用了上面的算术运算符:Area=Height*Wnum=num1+num2/num3-num4;运算符也有个运算顺序问题,先算乘除再算加减。单目正和单目负最先运算。取模运算符(%)用于计算两个整数相除所得的余数。例如:a=7%4;最终a的结果是3,因为7%4的余数是3。那么有人要问了,我要想求它们的商怎么办呢?b=7/4;这样b就是它们的商了,应该是1。也许有人就不明白了,7/4应该是1.75,怎么会是1呢?这里需要说明的是,当两个整数相除时,所得到的结果仍然是整数,没有小数部分。要想也得到小数部分,可以这样写7.0/4或者7/4.0,也即把其中一个数变为非整数。那么怎样由一个实数得到它的整数部分呢?这就需要用强制类型转换了。例如:a=(int) (7.0/4);因为7.0/4的值为1.75,如果在前面加上(int)就表示把结果强制转换成整型,这就得到了1。那么思考一下a=(float) (7/4);最终a的结果是多少?单目减运算符相当于取相反值,若是正值就变为负值,若是负数就变为正值。单目加运算符没有意义,纯粹是和单目减构成一对用的。
三、逻辑运算符逻辑运算符是根据表达式的值来返回真值或是假值。其实在C语言中没有所谓的真值和假值,只是认为非0为真值,0为假值。符号 功能&& 逻辑与|| 逻辑或! 逻辑非例如:5!3;0||-2&&5;!4;
当表达式进行&&运算时,只要有一个为假,总的表达式就为假,只有当所有都为真时,总的式子才为真。当表达式进行||运算时,只要有一个为真,总的值就为真,只有当所有的都为假时,总的式子才为假。逻辑非(!)运算是把相应的变量数据转换为相应的真/假值。若原先为假,则逻辑非以后为真,若原先为真,则逻辑非以后为假。还有一点很重要,当一个逻辑表达式的后一部分的取值不会影响整个表达式的值时,后一部分就不会进行运算了。例如:a=2,b=1;a||b-1;因为a=2,为真值,所以不管b-1是不是真值,总的表达式一定为真值,这时后面的表达式就不会再计算了。
四、关系运算符关系运算符是对两个表达式进行比较,返回一个真/假值。符号 功能& 大于& 小于&= 大于等于&= 小于等于== 等于!= 不等于这些运算符大家都能明白,主要问题就是等于==和赋值=的区别了。一些刚开始学习C语言的人总是对这两个运算符弄不明白,经常在一些简单问题上出错,自己检查时还找不出来。看下面的代码:if(Amount=123) ……很多新人都理解为如果Amount等于123,就怎么样。其实这行代码的意思是先赋值Amount=123,然后判断这个表达式是不是真值,因为结果为123,是真值,那么就做后面的。如果想让当Amount等于123才运行时,应该if(Amount==123) ……
五、自增自减运算符这是一类特殊的运算符,自增运算符++和自减运算符--对变量的操作结果是增加1和减少1。例如:--CCouter--;++AAmount++;
看这些例子里,运算符在前面还是在后面对本身的影响都是一样的,都是加1或者减1,但是当把他们作为其他表达式的一部分,两者就有区别了。运算符放在变量前面,那么在运算之前,变量先完成自增或自减运算;如果运算符放在后面,那么自增自减运算是在变量参加表达式的运算后再运算。这样讲可能不太清楚,看下面的例子:num1=4;num2=8;a=++num1;b=num2++;
a=++num1;这总的来看是一个赋值,把++num1的值赋给a,因为自增运算符在变量的前面,所以num1先自增加1变为5,然后赋值给a,最终a也为5。b=num2++;这是把num2++的值赋给b,因为自增运算符在变量的后面,所以先把num2赋值给b,b应该为8,然后num2自增加1变为9。那么如果出现这样的情况我们怎么处理呢?c=num1+++num2;到底是c=(num1++)+num2;还是c=num1+(++num2);这要根据编译器来决定,不同的编译器可能有不同的结果。所以我们在以后的编程当中,应该尽量避免出现上面复杂的情况。
六、复合赋值运算符在赋值运算符当中,还有一类C/C++独有的复合赋值运算符。它们实际上是一种缩写形式,使得对变量的改变更为简洁。Total=Total+3;乍一看这行代码,似乎有问题,这是不可能成立的。其实还是老样子,'='是赋值不是等于。它的意思是本身的值加3,然后在赋值给本身。为了简化,上面的代码也可以写成:Total+=3;复合赋值运算符有下列这些:符号 功能+= 加法赋值-= 减法赋值*= 乘法赋值/= 除法赋值%= 模运算赋值&&= 左移赋值&&= 右移赋值&= 位逻辑与赋值|= 位逻辑或赋值^= 位逻辑异或赋值上面的十个复合赋值运算符中,后面五个我们到以后位运算时再说明。 那么看了上面的复合赋值运算符,有人就会问,到底Total=Total+3;与Total+=3;有没有区别?答案是有的,对于A=A+1,表达式A被计算了两次,对于复合运算符A+=1,表达式A仅计算了一次。一般的来说,这种区别对于程序的运行没有多大影响,但是当表达式作为函数的返回值时,函数就被调用了两次(以后再说明),而且如果使用普通的赋值运算符,也会加大程序的开销,使效率降低。
七、条件运算符条件运算符(?:)是C语言中唯一的一个三目运算符,它是对第一个表达式作真/假检测,然后根据结果返回两外两个表达式中的一个。&表达式1&?&表达式2&:&表达式3&在运算中,首先对第一个表达式进行检验,如果为真,则返回表达式2的值;如果为假,则返回表达式3的值。例如:a=(b&0)?b:-b;当b&0时,a=b;当b不大于0时,a=-b;这就是条件表达式。其实上面的意思就是把b的绝对值赋值给a。
八、逗号运算符在C语言中,多个表达式可以用逗号分开,其中用逗号分开的表达式的值分别结算,但整个表达式的值是最后一个表达式的值。假设b=2,c=7,d=5,a1=(++b,c--,d+3);a2=++b,c--,d+3;对于第一行代码,有三个表达式,用逗号分开,所以最终的值应该是最后一个表达式的值,也就是d+3,为8,所以a=8。对于第二行代码,那么也是有三个表达式,这时的三个表达式为a2=++b、c--、d+3,(这是因为赋值运算符比逗号运算符优先级高)所以最终表达式的值虽然也为8,但a2=3。
还有其他的如位逻辑运算符,位移运算符等等,我们等到讲位运算时再说明。
九、优先级和结合性 从上面的逗号运算符那个例子可以看出,这些运算符计算时都有一定的顺序,就好象先要算乘除后算加减一样。优先级和结合性是运算符两个重要的特性,结合性又称为计算顺序,它决定组成表达式的各个部分是否参与计算以及什么时候计算。下面是C语言中所使用的运算符的优先级和结合性:优先级 运算符 结合性(最高) () [] -& . 自左向右! ~ ++ -- + - * & sizeof 自右向左* / % 自左向右+ - 自左向右&& && 自左向右& &= & &= 自左向右== != 自左向右& 自左向右^ 自左向右| 自左向右&& 自左向右|| 自左向右: 自右向左= += -= *= /= %= &= ^= |= &&= &&= 自右向左(最低) , 自左向右在该表中,还有一些运算符我们没有介绍,如指针运算符、sizeof运算符、数组运算符[]等等,这些在以后的学习中会陆续说明的。
前面几节介绍了常量和变量、运算符、表达式和语句的概念,对它们的使用有了一个大概的了解。也许刚学程序的人会觉得有些枯燥,下面我们就来编写第一个C语言程序。
#define PI 3.1416main(){float Radius,A scanf(%f,&Radius); /*输入半径的值*/Area=PI*Radius*R printf(%f\n,Area); /*输出圆的面积*/}
1.一个C语言程序,通常由带有#号的编译预处理语句开始。关于预处理我们在以后介绍,这里的#define PI 3.1415926相当于PI代表3.1416,下面在程序中遇到PI,我们就用3.1416替代一下。在以后的程序中,在学习预处理之前,我们都将不使用预处理语句。2.main() 任何一个完整的程序都需要main(),这是一个函数,具体什么是函数,以后再讲,这儿你就要记住就行。后面有一对{}把所有的语句都括在里面,表明那些语句都属于main()里面。程序运行时从这个左大括号开始。3.{}里面的4行语句大家应该都能明白,先定义两个变量,一个代表半径,一个代表面积,然后输入半径的值,然后求面积,最后在屏幕上输出面积。程序到main()的那对{}的右大括号结束。求面积的语句Area=PI*Radius*R相当于Area=3.1416*Radius*R(完全用3.1416替代PI)。
具体程序从编写到运行得到结果的步骤为:1.双击tc.exe,进入Turbo C 2.0编译界面2.ALT+E 进入编辑模式3.书写程序4.F2 存储程序(也可进入File菜单,选择save),第一次存储需要写上程序名称(*.C),回车5.ALT+F9 编译,如果有错误和警告,光标停留在错误行,回车进行修改,修改后,回到4;没有错,下一步6.CTRL+F9 连接和运行程序7.用ALT+F5查看程序运行结果,任意键返回程序
如何打开一个已有的C文件:1.双击tc.exe,进入Turbo C 2.0编译界面2.F3 进入load状态,找到所要打开文件的目录,找到文件,回车;后面都一样。
具体的有哪些快捷键及其它们的作用,请查看第一节概述。
说明:1.必须在程序的最开始部分定义所有用到的变量,例如这里的Area,Radius。2.变量的命名要尽量有意义,如用代表该意思的英文单词、或者是汉语拼音,例如这里的Radius,Area,绝对禁止用毫无干系的字母,如a,b,c。例如下面的程序,虽然意思和上面的一样,但是看上去意思不明朗,时间长了,很可能忘记程序本身的意思。对于仅仅是控制程序运行,不代表实际意思时,可以用一些简单字母。main(){float a,b;scanf(%f,&a);b=3.1416*a*a;printf(%f\n,b);}3.采用层次书写程序的格式,要有合理的缩进,必要的时候要有空行,一行只书写一个语句。所有语句尽量不分行,除非太长(分行时变量、运算符,格式字符等等不能拆开),例如下面两个程序看起来就不好看了,虽然它们的功能和前面是一样的。main(){float Radius,Ascanf(%f,&Radius);Area=3.1416*Radius*Rprintf(%f\n,Area);}
main(){float Radius,Ascanf(%f,%Radius);Area=3.1416*Radius*Rprintf(%f\n,Area);}4.程序在适当的地方要用/*……*/注释,它的意思表示在/* */里面的所有字符都不参加编译。因为一个较大的程序,经过一段时间,有些地方可能连编程者都忘记了,增加注释可以帮助恢复记忆,调试程序时,也容易找出错误。注释也可以分行写。5.在书写{}时要对齐。虽然不对应也不影响程序运行,但对齐后方便以后检查程序,也是为了美观,特别是后面学到流程控制时,{}一定要对齐。
程序设计方法:1.从问题的全局出发,写出一个概括性的抽象的描述。2.定义变量,选取函数,确定算法。算法这个东西不好说,遇到的问题多了,自然就会形成自己一整套的算法。3.按照解决问题的顺序把语句和函数在main()里面堆砌起来。
一个好的C程序员应该做到:1.在运行程序之前存盘2.所有在程序中用到的常量都用预处理语句在程序开头定义3.所有在程序中用到的函数都在程序开头声明4.头文件的#ifndef5.变量名和函数名使用有意思的英文单词或汉语拼音6.尽量少用全局变量或不用全局变量7.采用层次的书写程序格式,对for,while,if_else,do_while,switch_case等控制语句或他们的多重嵌套,采用缩格结构8.所有对应的{}都对齐9.尽量用for,而不用while做记数循环10.尽量不用goto语句11.一个函数不宜处理太多的功能,保持函数的小型化,功能单一化12.一个函数要保持自己的独立性,如同黑匣子一样,单进单出13.函数的返回类型不要省略14.用malloc()分配内存空间时,以后一定要用free()释放15.打开文件后,记住在退出程序前要关闭16.出错情况的处理17.写上必要的注释这里说的是一些基本的,经常遇到的情况,还有其他很多要注意的地方,在实际编程中都会遇到.
一个表达式的返回值都可以用来判断真假,除非没有任何返回值的void型和返回无法判断真假的结构。当表达式的值不等于0时,它就是“真”,否则就是假。一样个表达式可以包含其他表达式和运算符,并且基于整个表达式的运算结果可以得到一个真/假的条件值。因此,当一个表达式在程序中被用于检验其真/假的值时,就称为一个条件。
一、if语句if(表达式) 语句1;如果表达式的值为非0,则执行语句1,否则跳过语句继续执行下面的语句。如果语句1有多于一条语句要执行时, 必须使用{和} 把这些语句包括在其中, 此时条件语句形式为: if(表达式){语句体1;}例如:if(x&=0) y=x;if(a||b&&c) {z=a+b;c+=z;}
二、if--else语句除了可以指定在条件为真时执行某些语句外,还可以在条件为假时执行另外一段代码。在C语句中利用else语句来达到这个木的。if(表达式) 语句1;else 语句2;同样,当语句1或语句2是多于一个语句时,需要用{}把语句括起来。例如:if(x&=0) y=x;else y=-x;
三、if--else if--else结构。if(表达式1) 语句1; else if(表达式2) 语句2; else if(表达式3) 语句3; . . . else 语句n; 这种结构是从上到下逐个对条件进行判断,一旦发现条件满点足就执行与它有关的语句, 并跳过其它剩余阶梯;若没有一个条件满足,则执行最后一个else 语句n。最后这个else常起着缺省条件的作用。同样,如果每一个条件中有多于一条语句要执行时,必须使用{和}把这些语句包括在其中。条件语句可以嵌套,这种情况经常碰到,但条件嵌套语句容易出错,其原因主要是不知道哪个if对应哪个else。例如: if(x&20||x&-10) if(y&=100&&y&x) printf(Good); else printf(Bad); 对于上述情况, Turbo C2.0规定: else语句与最近的一个if语句匹配, 上例中的else与if(y&=100&&y&x)相匹配。为了使else与if(x&20||x&-10)相匹配, 必须用花括号。如下所示: if(x&20||x&-10){ if(y&=100&&y&x) printf(Good); } else printf(Bad);
下面举几个例子:1.输入一个数,如果大于0,输出如果是负数,输出如果正好是0,则输出zero。main(){scanf(%f,&f);if(num&0)printf(plus\n);else if(num&0)printf(negative\n);elseprintf(zero\n); }先定义两个变量,然后输入一个数,然后判断这个数的范围,输出对应的字符串。
2.输入一个数x,输出y。其中y是x的绝对值。main(){float x,y;scanf(%f,&x);if(x&=0) y=x;else y=-x;printf(%f\n,y);}程序比较简单,这儿就不分析了。其实Trubo C 2.0把一些常用的功能都写好了,我们只需要使用就可。例如求绝对值的功能在C的库里面就有。看下面的:#include math.hmain(){float x,y;scanf(%f,&x);y=fabs(x); /*求x的绝对值,然后赋值给y*/printf(%f\n,y);}
这个程序和上面的程序完成的功能是一模一样的,都是求绝对值。可以看出,用下面这个方法比上面就要好一些。由于fabs()是一个函数,系统自带的,所以在使用它的时候,我们必须把它所在的库文件math.h包含都程序中,即程序最前面一行。类似的还有求开方sqrt(),求指数幂exp()等等,这些与数学方面有关的函数都在math.h里面。具体哪些有哪些没有,在什么库里面,可以查看一些手册。
3.输入x,输出y,x和y满足关系:x&-5 y=x;-5&=x&1 y=2*x+5;1&=x&4 y=x+6;x&=4 y=3*x-2;程序如下:main(){float x,y;scanf(%f,&x);if(x&-5)y=x;else if(-5&=x&&x&1)y=2*x+5;else if(1&=x&&x&4)y=x+6;elsey=3*x-2;printf(%f\n,y);}
这里要说明两点:(1).-5&=x&&x&1不能写成-5&=x&1;1&=x&&x&4也不能写成1&=x&4;在C语言中,不能认识连续不等式。(2).y=2*x+5不能写成y=2x+5;y=3*x-2也不能写成y=3x-2;这与我们平时所写的方法不一样。
4.输入三个数x,y,z,然后按从大到小输出。main(){float x,y,z;scanf(%f%f%f,&x,&y,&z);if(x&=y&&x&=z){printf(%f\t,x);if(y&=z) printf(%f\t%f\n,y,z);else printf(%f\t%f\n,z,y);}else if(y&=x&&y&=z){printf(%f\t,y);if(x&=z) printf(%f\t%f\n,x,z);else printf(%f\t%f\n,z,x);}else{printf(%f\t,z);if(x&=y) printf(%f\t%f\n,x,y);else printf(%f\t%f\n,y,x);}}说明:这是一个典型的if语句嵌套结构,如果不使用括号,那么if和else的对应关系就乱了。
四、switch--case语句在编写程序时, 经常会碰到按不同情况分转的多路问题, 这时可用嵌套if -else-if语句来实现, 但if-else-if语句使用不方便, 并且容易出错。对这种情况, Turbo C2.0提供了一个开关语句。开关语句格式为: switch(变量) { case 常量1: 语句1或空; case 常量2: 语句2或空; . . . case 常量n: 语句n或空; default: 语句n+1或空; }
执行switch开关语句时,将变量逐个与case后的常量进行比较,若与其中一个相等,则执行该常量下的语句,若不与任何一个常量相等,则执行default后面的语句。注意: 1.switch中变量可以是数值,也可以是字符,但必须是整数。2.可以省略一些case和default。3.每个case或default后的语句可以是语句体,但不需要使用{和}括起来。例如:main(){int x,y;scanf(%d,&x);witch(x){case 1:y=x+1; /*退出开关语句,遇到break才退出*/case 4:y=2*x+1;default:y=x--;}printf(%d\n,y);}
从上面的例子可以看出,用开关语句编的程序一定可以用if语句做。那么在什么情况下需要用switch语句呢?一般在出现比较整的情况下或者能转化成比较整数的情况下使用。看下面的例子:
例子:一个学生的成绩分成五等,超过90分的为'A',80-89的为'B',70-79为'C',60-69为'D',60分以下为'E'。现在输入一个学生的成绩,输出他的等级。
(1).用if语句main(){scanf(%d,&num);if(num&=90) grade='A';else if(num&=80&&num&89) grade='B';else if(num&=70&&num&79) grade='C';else if(num&=60&&num&69) grade='D';else grade='E';printf(%c,grade);}
(2).用switch语句main(){scanf(%d,&num);num/=10;switch(num){case 10:case 9:grade='A';case 8:grade='B';case 7:grade='C';case 6:grade='D';default:grade='E';}printf(%c,grade);}
说明一点,并不是每个case里面有都语句,有时侯里面是空的,就好象这一题。switch语句执行的顺序是从第一case判断,如果正确就往下执行,直到break;如果不正确,就执行下一个case。所以在这里,当成绩是100分时,执行case 10:然后往下执行,grade='A';退出。想想看,这里为什么要用num/=10;?假设当程序中有浮点数时怎么办呢?Turbo C 2.0提供三种基本的循环语句: for语句、while语句和do-while语句。
一、循环语句(一)、for循环 它的一般形式为:for(&初始化&;&条件表过式&;&增量&)语句;初始化总是一个赋值语句,它用来给循环控制变量赋初值;条件表达式是一个关系表达式,它决定什么时候退出循环;增量定义循环控制变量每循环一次后按什么方式变化。这三个部分之间用;分开。例如:for(i=1;i&=10;i++)语句;上例中先给i赋初值1,判断i是否小于等于10,若是则执行语句,之后值增加1。再重新判断,直到条件为假,即i&10时,结束循环。注意: (1).for循环中语句可以为语句体,但要用{和}将参加循环的语句括起来。(2).for循环中的初始化、条件表达式和增量都是选择项,即可以缺省,但;不能缺省。省略了初始化,表示不对循环控制变量赋初值。省略了条件表达式,则不做其它处理时便成为死循环。省略了增量,则不对循环控制变量进行操作,这时可在语句体中加入修改循环控制变量的语句。(3).for循环可以有多层嵌套。例如:for(;;) 语句;for(i=1;;i+=2) 语句;for(j=5;;) 语句;这些for循环语句都是正确的。main() {int i,j;printf(i j\n);for(i=0;i&2;i++)for(j=0;j&3;j++)printf(%d %d\n,i,j);} 输出结果为: i j0 00 10 21 01 11 2
用for循环求1+2+…&#的和:main(){int sn=0,i;for(i=1;i&=100;i++)sn+=i; /*1+2+…&#*/printf(%d\n,sn);}从程序可以看出,使用循环语句可以大大简化代码。(二)、while循环 它的一般形式为: while(条件)语句;while循环表示当条件为真时,便执行语句。直到条件为假才结束循环。并继续执行循环程序外的后续语句。例如:#include stdio.hmain() {
c='\0'; /*初始化c*/while(c!='\n') /*回车结束循环*/c=getche(); /*带回显的从键盘接收字符*/}
上例中,while循环是以检查c是否为回车符开始,因其事先被初始化为空,所以条件为真,进入循环等待键盘输入字符;一旦输入回车,则c='\n',条件为假,循环便告结束。与for循环一样,while循环总是在循环的头部检验条件,这就意味着循环可能什么也不执行就退出。 注意: (1).在while循环体内也允许空语句。 例如: while((c=getche())!='\n'); 这个循环直到键入回车为止。 (2).可以有多层循环嵌套。 (3).语句可以是语句体, 此时必须用{和}括起来。用while循环求1+2+…&#的和:main(){int sn=0,i=0;while(++i&=100)sn+=i; /*求1+2+…&#*/printf(%d\n,sn);}(三)、do--while循环 它的一般格式为:do{语句块;}while(条件);这个循环与while循环的不同在于:它先执行循环中的语句,然后再判断条件是否为真,如果为真则继续循环;如果为假,则终止循环。因此,do-while循环至少要执行一次循环语句。同样当有许多语句参加循环时,要用{和}把它们括起来。
用do--while循环求1+2+…&#的和:main(){int sn=0,i=1;dosn+=i; /*求1+2+…&#*/while(++i&=100);printf(%d\n,sn);}从上面三个程序看出,使用for,while和do--while求解同样的问题,基本思路都差不多,只是在第一次计算时,注意初值。
二、循环控制(一)、break语句break语句通常用在循环语句和开关语句中。当break用于开关语句switch中时,可使程序跳出switch而执行switch以后的语句;如果没有break语句,则将成为一个死循环而无法退出。break在switch中的用法已在前面介绍开关语句时的例子中碰到,这里不再举例。 当break语句用于do-while、for、while循环语句中时,可使程序终止循环而执行循环后面的语句,通常break语句总是与if语句联在一起。即满足条件时便跳出循环。例如:main(){int sn=0,i;for(i=1;i&=100;i++){if(i==51) /*如果i等于51,则跳出循环*/sn+=i; /*1+2+……+50*/}printf(%d\n,sn);}
可以看出,最终的结果是1+2+……+50。因为在i等于51的时候,就跳出循环了。自己写写怎样在while和do--while循环中增加break语句。注意: 1. break语句对if-else的条件语句不起作用。2. 在多层循环中,一个break语句只向外跳一层。例如:main() {int i,j;printf(i j\n);for(i=0;i&2;i++)for(j=0;j&3;j++){if(j==2)printf(%d %d\n,i,j);}} 输出结果为: i j0 00 11 01 1当i==0,j==2时,执行break语句,跳出到外层的循环,i变为1。(二)、continue语句continue语句的作用是跳过循环本中剩余的语句而强行执行下一次循环。continue语句只用在for、while、do-while等循环体中, 常与if条件语句一起使用,用来加速循环。例如:main(){int sn=0,i;for(i=1;i&=100;i++){if(i==51) /*如果i等于51,则结束本次循环*/sn+=i; /*1+2+…&#+…&#*/}printf(%d\n,sn);}从程序中可以看出,continue语句只是当前的值没有执行,也就是说当前的值跳过去了,接着执行下次循环。main() {int i,j;printf(i j\n);for(i=0;i&2;i++)for(j=0;j&3;j++){if(j==1)printf(%d %d\n,i,j);}} 输出结果为: i j0 00 21 01 2(三)、goto语句goto语句是一种无条件转移语句,与BASIC中的goto语句相似。goto语句的使用格式为: goto 标号; 其中标号是Turbo C 2.0中一个有效的标识符,这个标识符加上一个:一起出现在函数内某处,执行goto语句后,程序将跳转到该标号处并执行其后的语句。标号既然是一个标识符,也就要满足标识符的命名规则。另外标号必须与goto语句同处于一个函数中,但可以不在一个循环层中。通常goto语句与if条件语句连用,当满足某一条件时,程序跳到标号处运行。goto语句通常不用,主要因为它将使程序层次不清,且不易读,但在多层嵌套退出时,用goto语句则比较合理。main(){ int sn=0,i;for(i=1;i&=100;i++){if(i==51) /*如果i等于51,则跳出循环*/sn+=i; /*1+2+……+50*/}loop: ;printf(%d\n,sn);}可以看出,这儿的goto语句和break作用很类似。这儿的loop: ;printf(%d\n,sn);也可以写成loop: printf(%d\n,sn);main(){int sn=0,i;for(i=1;i&=100;i++){if(i==51) /*如果i等于51,则跳出本次循环*/sn+=i; /*1+2+…&#+…&#*/loop: ;}printf(%d\n,sn);}可以看出这儿的loop语句和continue的作用类似。但是某些情况下又必须使用goto语句,否则会让程序大大臃肿。如:main() {int i,j,k;printf(i j k\n);for(i=0;i&2;i++)for(j=0;j&3;j++)for(k=0;k&3;k++){if(k==2)printf(%d %d %d\n,i,j,k);}loop: ;} 输出结果为: i j k0 0 00 0 1如果不使用goto语句,而使用break,continue语句,应该这样main() {int i,j,k;printf(i j\n);for(i=0;i&2;i++){for(j=0;j&3;j++){for(k=0;k&3;k++){if(k==2)printf(%d %d %d\n,i,j,k);}if(k==2)}if(k==2)}}输出结果为: i j k0 0 00 0 1所以在同时跳出多层循环时,应该使用goto语句。记住,所有的goto语句其实都是可以用break,continue代替的。
下面举几个例子:1.求两个整数的最大公约数。例如10和15的最大公约数是5。分析:最大公约数一定小于等于最小的那个数一半,同时能被两数整除。main(){int num1,num2,i,scanf(%d%d,&num1,&num2);min=num1 for(i=min/2;i&0;i--)if(num1%i==0&&num2%i==0)printf(最大公约数为%d\n,i);}
2.求1!+2!+……+n!(n&10)main(){int n,i;long temp=1,sn=0; /*从9!以后,所得的值就超过了int范围*/scanf(%d,&n);for(i=1;i&=n;i++){temp*=i;sn+= /*如果没有这一步,求的就是n!*/}printf(%ld\n,sn);}那么想想,如果求1!+3!+5!+……+n!应该怎么办?
3.判断一个整数是不是素数(素数就是只能被本身和1整除的数)。#include math.hmain(){int num,i,flag=0;scanf(%d,&num);for(i=2;i {flag=0; /*标志变量复位*/if(num%i==0){flag=1;}}if(flag==0) printf(是素数\n);else printf(不是素数\n);} 可以说,在所有的C语言书上,都有判断素数的例题。它的编程思想是:把一个变量作为标志变量,用来标志是不是素数;循环体是从2到sqrt(num),因为如果一个数不是素数的话,一定能分解成num=num1*num2,它们中的最小值一定小于sqrt(num),所以循环的时候只要到sqrt(num)就可以了。同时要注意变量复位的问题数组,顾名思义就是一组同类型的数。
一、数组的声明声明数组的语法为在数组名后加上用方括号括起来的维数说明。本接仅介绍一维数组。下面是一个整型数组的例子:int array[10];这条语句定义了一个具有10个整型元素的名为array的数组。这些整数在内存中是连续存储的。数组的大小等于每个元素的大小乘上数组元素的个数。方括号中的维数表达式可以包含运算符,但其计算结果必须是一个长整型值。这个数组是一维的。下面这些声明是合法的:int offset[5+3];float count[5*2+3];下面是不合法的:int n=10;int offset[n]; /*在声明时,变量不能作为数组的维数*/
二、用下标访问数组元素int offset[10];表明该数组是一维数组,里面有10个数,它们分别为offset[0],offset[1],……offset[9];千万注意,数组的第一个元素下标从0开始。一些刚学编程的人员经常在这儿犯一些错误。offset[3]=25;上面的例子是把25赋值给整型数组offset的第四个元素。在赋值的时候,可以使用变量作为数组下标。main(){int i,offset[10];for(i=0;i&10;i++) scanf(%d,&offset[i]);for(i=9;i&=0;i--) printf(%d ,offset[i]);printf(\n);}题目的意思是先输入10个整数,存入到数组中,然后反序输出。
三、数组的初始化前面说了,变量可以在定义的时候初始化,数组也可以。int array[5]={1,2,3,4,5};在定义数组时,可以用放在一对大括号中的初始化表对其进行初始化。初始化值的个数可以和数组元素个数一样多。如果初始化的个数多于元素个数,将产生编译错误;如果少于元素个数,其余的元素被初始化为0。如果维数表达式为空时,那么将用初始化值的个数来隐式地指定数组元素的个数,如下所式:int array[]={1,2,3,4,5};这也表明数组array元素个数为5。main(){int i,array[]={1,3,5,7,9,11};for(i=0;i&5;i++) printf(%d ,array[i]);printf(\n);}最终结果为1 3 5 7 9
四、字符数组整数和浮点数数组很好理解,在一维数组中,还有一类字符型数组。char array[5]={'H','E','L','L','O'};对于单个字符,必须要用单引号括起来。又由于字符和整型是等价的,所以上面的字符型数组也可以这样表示:char array[5]={72,69,76,76,79}; /*用对应的ASCII码*/举一个例子:main(){char array[5]={'H','E','L','L','O'};for(i=0;i&5;i++) printf(%d ,array[i]);printf(\n);}最终的输出结果为72 69 76 76 79但是字符型数组和整型数组也有不同的地方,看下面的:char array[]=HELLO;如果我们能看到内部的话,实际上编译器是这样处理的:char array[]={'H','E','L','L','O','\0'};看上面最后一个字符'\0',它是一个字符常量,Turbo C编译器总是给字符型数组的最后自动加上一个\0,这是字符的结束标志。所以虽然HELLO只有5个字符,但存入到数组的个数却是6个。但是,数组的长度仍然是5。i=strlen(array); /*求字符串的长度,在string.h里面*/可以看出i仍然是5,表明最后的'\0'没有算。#include string.hmain(){ int i,j;char array[]=094387j=strlen(array);for(i=0;i printf(\n);}其实我们可以根据判断'\0'来输出字符串,看下面的:main(){char array[]=094387for(i=0;array[i]!='\0';i++) printf(%c,array[i]);printf(\n);}
举几个例子:1.输入10个整数存入数组中,然后把它们从小到大排列并放在同一数组中。(思路:先找出最小的,放在第一个位置,为了防止把原先的数覆盖掉,可以把原先的第一个数和最小数的位置互换)。main(){int array[10];int i,j,min,for(i=0;i&10;i++) scanf(%d,&array[i]);for(i=0;i&9;i++){min=array[i];for(j=i+1;j&10;j++)if(min&array[j]) /*里面的4行语句很重要*/{min=array[j];stmp=array[i];array[i]=array[j];array[j]=}}for(i=0;i&10;i++) printf(%d ,array[i]);printf(\n);}
分析:先让第一个值作为基准,如果后面有比它小的,那么就把这两个数互换一下,同时把基准换成小的值。两个数互换应该这样(stmp=a;a=b;b=),而不是(a=b;b=a;),想想这是为什么?必须要用一个变量作为桥梁。这种一个一个的把最小的放在前面的排序方法,我们形象的叫做冒泡法。
2.输入一行字符存入数组,然后把他们反序存入到同一数组中。#include stdio.hmain(){char c,stmp,array[80];int i=0,j;while((c=getchar())!='\n') /*注意这儿的用法*/array[i++]=c;array[i]='\0'; /*为什么要加'\0'?是否可以不加?*/for(j=i-1;j&=i/2;j--){stmp=array[j];array[j]=array[i-1-j];array[i-1-j]=}for(i=0;array[i]!='\0';i++) printf(%c,array[i]);printf(\n);}
3.一个已经排好序的数组,输入一个数,利用二分法把这个数从原数组中删除,数组顺序保持不变。如原数组为1,3,5,7,9,11,13,15,17,19,待删除的数为13,则输出为1,3,5,7,9,11,15,17,19。二分法:每次都是判断中间的数是否满足要求,若满足则删除,若不满足,则把该数当作边界,然后再找中点。例如这一题,第一次的是10个数的中点,为11,发现11&13,则找11-19的中点15,发现15&13,再找11-15的中点13,正好,则删除。main(){ int array[10]={1,2,3,5,8,15,20,30,100,200};int first=0,end=9,middle=(first+end)/2,num,i;scanf(%d,&num);while(array[middle]!=num) /*注意这里面的三行代码*/{if(array[middle]&num) end= else first=middle=(first+end)/2;}for(i=0;i&9;i++){if(i&=middle) array[i]=array[i+1];printf(%d ,array[i]);}printf(\n);}程序没有考虑当输入的数在原先数组中没有时怎么处理。如果要考虑这个问题,程序该怎么改动呢?一、高维数组
有时,数组的维数并不止一维,例如一个记录消费中心在第一季度里各个月的收入数据就可以用二维数组来表示。定义二维数组的方法是在一维数组定义的后面再加上一个用方括号括起来的维数说明。例如:float array[3][8];实际上,这个数组可以看成3个连续的一维数组,每个一维数组具有8个元素。该数组在内存中的存储格式为最左边的维数相同的元素连续存储,也即按行存储的。首先存储第一行8个元素,其次是第二行,最后是第三行。main(){int array[3][3]={1,2,3,4,5,6,7,8,9};int i,j;for(i=0;i&3;i++){for(j=0;j&3;j++) printf(%3d);printf(\n);}}它的输出结果为:1 2 34 5 67 8 9可以看出,二维数组元素是按行存储的。
我们也可以对数组进行赋值,而不是初始化。main(){int array[3][3];int i,j;for(j=0;j&3;j++)for(i=0;i&3;i++) scanf(%d,&array[i][j]);for(i=0;i&3;i++){for(j=0;j&3;j++) printf(%3d);printf(\n);}}当输入1 2 3 4 5 6 7 8 9&回车&输出为:1 4 72 5 83 6 9
数组可以是二维、三维甚至是更高维数的,虽然C语言对维数的处理没有上限,但是处理高维数组是很头疼的事。一般尽量避免处理四维和四维以上的数组。下面看一个三维数组的例子:main(){ int array[2][3][4];int i,j,k;for(i=0;i&2;i++)for(j=0;j&3;j++)for(k=0;k&4;k++) array[i][j][k]=i*12+j*4+k;}这个三维数组可以看成2个二维数组,每个二维数组又可以看成3个一维数组。可以在头脑里想象成两个平行平面,每个平面内有3*4个点。所以共有24个元素。
二、字符串数组上面讲的都是存放数值的,有一类数组,用来处理字符串的,我们叫字符串数组。其实字符串数组也是二维数组,只是它的特殊性,才单独拿出来说的。main(){char s[10][10];for(i=0;i&10;i++) scanf(%s,s[i]);}
先看它的输入特性,前面在说输入语句的时候说过,遇到字符串输入,可以不加'&',现在只要记住这个特性就可以,以后说指针的时候再讲为什么。但是这儿为什么用s[i],可能很多人不太明白。我们定义的是二维数组,而输入的时候,却使用一维数组的形式。这是因为字符串在内存里地址可以用它的名字表示,就好象这种形式:main(){char s[10];scanf(%s,s);}定义的是一维数组,输入语句用变量形式表示一样。通过前面的'%s'形式可以看出,s[i]是一个数组,所以s就是二维数组了。这里要注意一点,scanf()函数在输入字符串时候不能支持空格,看下面的例子:main(){char s[3][10];for(i=0;i&10;i++)scanf(%s,s[i]);for(i=0;i&3;i++)printf(%s\n,s[i]);}我们输入:11114444我们是想把1111赋值给s[0],赋值给s[1],4444赋值给s[2]。可实际上编译器是这样做的,把1111赋值给s[0],把2222赋值给[1],把3333赋值给s[2]。实际输出:111122223333在输入字符串的时候,如果使用scanf(),就把空格当作下一个输入了。那么我们怎么解决这个问题呢?毕竟很多情况下,一行字符串肯定有空格出现的。我们使用新的函数gets()。这个函数是专门接受字符串输入的,它跳过了空格的影响。把上面的输入语言修改为gets(s[i])即可。我们定义了char s[3][10],超过10个字符肯定不行,如果少于10个字符,电脑怎么处理呢?电脑是在每个字符串的后面自动补上'\0',作为字符串的结束标志。我们经常在填写一些可选择的内容时经常发现,待选的字符串都是按字母排列好的,我们怎么用C语言实现这个功能?在C语言里,字符串的排序是按照字符的ASCII码来的,如果第一个字符一样,则比较第二个,依次类推。main(){char s1[6]=addfgh,s2[5]=for(i=0;s1[i]!='\0'&&s2[i]!='\0';i++){if(s1[i] {printf(s1 exit(1);}else if(s1[i]&s2[i]){printf(s1&s2\n);exit(1);}}if(s1[i]=='\0' && s2[i]!='\0') printf(s1 else if(s2[i]=='\0' && s1[i]!='\0') printf(s1&s2\n);else printf(s1==s2\n);}
上面的例子就是比较两个字符串大小的,先比较第一个,如果相同,接着比较第二个,如果不相同,则分出大小。一直往后比较,直到其中某一个到'\0',你也可以先用strlen()函数找出最小的长度。exit()函数的作用是退出程序,具体它的用法可以看看相关资料。
其实C语言把我们经常需要的字符串处理函数都做好了,我们只需要调用它即可。如strcmp()用来比较、strcpy()用来拷贝等等。看看它们的用法:#include string.hmain(){char s1[10],s2[10],s2[10];gets(s1);gets(s2);k=strcmp(s1,s2); /*比较s1和s2大小*/if(k==0) printf(s1==s2\n);else if(k&0) printf(s1&s2\n);else printf(s1 strcpy(s3,s1); /*把s1拷贝到s3*/printf(%s\n,s3); }
可以看出,比较大小时,如果k&0,则s10,则s1&s2;如果k=0,则s1=s2。实际上这是一个函数,具体什么是函数,以及为什么写成那种形式,我们下节再说。这些函数都包含在string.h头文件中,所以在程序的开头,都要写上#include string.h。字符串处理有很多函数,你们可以看看相关的书,也可以看看Turbo C的帮助。本节介绍C程序的基本单元--函数。函数中包含了程序的可执行代码。每个C程序的入口和出口都位于函数main()之中。main()函数可以调用其他函数,这些函数执行完毕后程序的控制又返回到main()函数中,main()函数不能被别的函数所调用。通常我们把这些被调用的函数称为下层(lower-level)函数。函数调用发生时,立即执行被调用的函数,而调用者则进入等待状态,直到被调用函数执行完毕。函数可以有参数和返回值。
程序员一般把函数当作“黑箱”处理,并不关心它内部的实现细节。当然程序员也可以自己开发函数库。说明一点,函数这一节很重要,可以说一个程序的优劣集中体现在函数上。如果函数使用的恰当,可以让程序看起来有条理,容易看懂。如果函数使用的乱七八糟,或者是没有使用函数,程序就会显得很乱,不仅让别人无法查看,就连自己也容易晕头转向。可以这样说,如果超过100行的程序中没有使用函数,那么这个程序一定很罗嗦(有些绝对,但也是事实)。
一、函数的定义一个函数包括函数头和语句体两部分。函数头由下列三不分组成:函数返回值类型函数名参数表一个完整的函数应该是这样的:函数返回值类型 函数名(参数表){语句体;}函数返回值类型可以是前面说到的某个数据类型、或者是某个数据类型的指针、指向结构的指针、指向数组的指针。指针概念到以后再介绍。函数名在程序中必须是唯一的,它也遵循标识符命名规则。参数表可以没有也可以有多个,在函数调用的时候,实际参数将被拷贝到这些变量中。语句体包括局部变量的声明和可执行代码。我们在前面其实已经接触过函数了,如abs(),sqrt(),我们并不知道它的内部是什么,我们只要会使用它即可。这一节主要讲解无参数无返回值的函数调用。
二、函数的声明和调用为了调用一个函数,必须事先声明该函数的返回值类型和参数类型,这和使用变量的道理是一样的(有一种可以例外,就是函数的定义在调用之前,下面再讲述)。看一个简单的例子:void a(); /*函数声明*/
main(){a(); /*函数调用*/}
void a() /*函数定义*/{scanf(%d,&num);printf(%d\n,num);}
在main()的前面声明了一个函数,函数类型是void型,函数名为a,无参数。然后在main()函数里面调用这个函数,该函数的作用很简单,就是输入一个整数然后再显示它。在调用函数之前声明了该函数其实它和下面这个程序的功能是一样的:main(){scanf(%d,&num);printf(%d\n,num);}可以看出,实际上就是把a()函数里面的所有内容直接搬到main()函数里面(注意,这句话不是绝对的。)我们前面已经说了,当定义在调用之前时,可以不声明函数。所以上面的程序和下面这个也是等价的:void a(){scanf(%d,&num);printf(%d\n,num);}
main(){a();}因为定义在调用之前,所以可以不声明函数,这是因为编译器在编译的时候,已经发现a是一个函数名,是无返回值类型无参数的函数了。
那么很多人也许就会想,那我们何必还要声明这一步呢?我们只要把所有的函数的定义都放在前面不就可以了吗?这种想法是不可取的,一个好的程序员总是在程序的开头声明所有用到的函数和变量,这是为了以后好检查。前面说了,在调用之前,必须先声明函数,所以下面的做法也是正确的(但在这里我个人并不提倡)。main(){void a();a();}
void a(){scanf(%d,&num);printf(%d\n,num);}
一般来说,比较好的程序书写顺序是,先声明函数,然后写主函数,然后再写那些自定义的函数。既然main()函数可以调用别的函数,那么我们自己定义的函数能不能再调用其他函数呢?答案是可以的。看下面的例子:
void a();void b();
main(){a();}
void a(){b();}
void b(){scanf(%d,&num);printf(%d\n,num);}
main()函数先调用a()函数,而a()函数又调用b()函数。在C语言里,对调用函数的层数没有严格的限制,我们可以往下调用100层、1000层,但是在这里我们并不提倡调用的层数太多(除非是递归),因为层数太多,对以后的检查有一些干扰,函数调过来调过去,容易让自己都晕头转向。某些人可能就不明白了,看上面的例子,好象使用函数后,程序变的更长了,更不让人理解。当然,我举的这个例子的确没有必要用函数来实现,但是对于某些实际问题,如果不使用函数,会让程序变的很乱,这涉及到参数问题,我们下一节再说。前面我们说的都是无参数无返回值的函数,实际程序中,我们经常使用到带参数有返回值的函数。
一、函数参数传递1.形式参数和实际参数函数的调用值把一些表达式作为参数传递给函数。函数定义中的参数是形式参数,函数的调用者提供给函数的参数叫实际参数。在函数调用之前,实际参数的值将被拷贝到这些形式参数中。2.参数传递先看一个例子:void a(int); /*注意函数声明的形式*/
main(){scanf(%d,&num);a(num); /*注意调用形式*/}
void a(int num_back) /*注意定义形式*/{printf(%d\n,num_back);}
在主函数中,先定义一个变量,然后输入一个值,在a()这个函数中输出。当程序运行a(num);这一步时,把num的值赋值给num_back,在运行程序过程中,把实际参数的值传给形式参数,这就是函数参数的传递。形参和实参可能不只一个,如果多于一个时,函数声明、调用、定义的形式都要一一对应,不仅个数要对应,参数的数据类型也要对应。
void a(int,float);
main(){int num1;float num2;scanf(%d,&num1);scanf(%f,&num2);a(num1,num2);}
void a(int num1_back,float num2_back){printf(%d,%f\n,num1_back,num2_back);}
上面的例子中,函数有两个参数,一个是整型,一个是浮点型,那么在声明、调用、定义的时候,不仅个数要一样,类型也要对应。如果不对应,有可能使的编译错误,即使没错误,也有可能让数据传递过程中出现错误。再看一个例子:
void a(int);
main(){scanf(%d,&num);a(num);}
void a(int num){printf(%d\n,num);}
看上面的例子,形式参数和实际参数的标识符都是num,程序把实际参数num的值传递给形式参数num。有些人可能就不明白了,既然两个都是num,为什么还要传递呢?干脆这样不就行了吗:
main(){scanf(%d,&num);a();}
void a(){printf(%d\n,num);}
其实不然,这就要涉及到标识符作用域的问题。作用域的意思就是说,哪些变量在哪些范围内有效。一个标识符在一个语句块中声明,那么这个标识符仅在当前和更低的语句块中可见,在函数外部的其实地方不可见,其他地方同名的标识符不受影响,后面我们会系统讲解作用域的问题。在这儿你就要知道两个同名的变量在不同的函数中是互不干扰的。前面将的都是变量与变量之间的值传递,其实函数也可以传递数组之间的值。看下面的例子:
void a(int []);
main(){int array[5],i;for(i=0;i&5;i++) scanf(%d,&array[i]);a(array);}
void a(int array[]){for(i=0;i&5;i++) printf(%d\t,array[i]);printf(\n);}
这就是数组之间的值传递。注意他们的声明和定义形式,和变量参数传递有什么区别?有了后面的[]就表明传递的是一个数组。其中在定义的时候,也可以写成void a(int array[5]);想想,如果我们写成了int array[4]会有什么情况发生?目前我们只学了数组和变量,以后还会知道指针、结构,到那是,函数也可以传递它们之间的值。
二、函数值的返回其实我们也可以把函数当作一个变量来看,既然是变量,那一定也可以有类型。还举最前面的例子,现在要求在main()函数里输入一个整数作为正方形的边长,在子函数里求正方形的面积,然后再在主函数里输出这个面积。我们前面的程序都是在子函数里输出的,现在要求在主函数里输出,这就需要把算好的值返回回来。先看例子:
int a(int); /*声明函数*/
main(){int num,scanf(%d,&num);area=a(num); /*调用时的形式*/printf(%d,area);}
int a(int num){int area_area_back=num*return area_ /*返回一个值*/}和前面的程序有几点不同:(1).声明函数类型时,不是void,而是int。这是由于最后要求的面积是整型的,所以声明函数的返回值类型是整型。(2).return语句 它的意思就是返回一个值。在C语言中,return一定是在函数的最后一行。(3).调用函数的时候,由于函数有一个返回值,所以必须要用变量接受这个返回值(不是绝对的),如果我们不用一个变量接受这个值,函数还照样返回,但是返回的这个值没有使用。
上面的例子运行过程是这样的,先把实参的值传递给形参,然后在子函数里计算面积得到area_back,然后返回这个面积到主函数,也就是把area_back赋值给area,最后输出。前面说了,返回值有时不一定非要用一个变量来接受,我们可以把上面的程序简化为:
int a(int);
main(){scanf(%d,&num);printf(%d,a(num)); /*函数调用放在这儿*/}
int a(int num){int area_area_back=num*return area_}这样函数返回的值就可以直接放到输出缓冲区直接输出了。还可以再简化为:
int a(int);
main(){scanf(%d,&num);printf(%d,a(num));}
int a(int num){return num* /*直接在这儿返回*/}
对于函数而言,一个函数只能返回一个值,如果想返回一组数值,就要使用数组或者结构或者指针。其实对于这些,还是返回一个值,只是这个值是一个地址而已。但是对于数组的返回有和变量不同,因为数组和地址是联系在一起的。看一个例子:
void a(int []);
main(){int array[5]={1,2,3,4,5},i;a(array);for(i=0;i&5;i++) printf(%d,array[i]);}
void a(int array[]){for(i=0;i&5;i++) array[i]++;}
看看这个程序,好象函数没有返回值,但是函数的功能的确实现了,在主函数当中输出的值的确都各加了1上来。这就是因为数组和变量不同的缘故,在后面讲指针的时候再详细说明。下面看一个实际例子,加深对函数的理解:
用函数实现,判断一个整数是不是素数?在主函数里输入输出,子函数里判断。
#include math.hint judge(int);
main(){int num,scanf(%d,&num);result=judge(num);if(result==1) printf(yes\n);else printf(no\n);}
judge(int num){int i,flag=1;for(i=2;i&=sqrt(num);i++)if(num%i==0){flag=0;}}
可以看出,函数的功能就是为了让程序看起来有条理,一个函数实现一个特定的功能。如果我们还和以前那样,把所有代码都放在main()函数,好象程序就显的臃肿了。而且函数有一个显著的好处就是很方便的使用。这里面的judge()函数判断一个数是不是素数,如果我们以后还有判断某个数是不是素数,就可以直接使用这个函数了。我们这样,把下面的代码:
judge(int num){int i,flag=1;for(i=2;i&=sqrt(num);i++)if(num%i==0){flag=0;}}保存为judge.h文件,放到include目录里面。
以后就可以直接使用这个函数了,就好象直接使用abs(),sqrt()这些函数一样方便。
#include math.h /*必须要有它*/#include judge.h
main(){int num,scanf(%d,&num);result=judge(num);if(result==1) printf(yes\n);else printf(no\n);}
看上面的例子,我们在程序中直接使用了函数judge(),这就是我们自己编写的第一个所谓的库函数。但是程序的第一行要包含math.h文件,这是因为在judge.h里面使用了sqrt()函数,所以为了方便,我们可以把math.h放到judge.h里面,也就是在judge.h文件的第一行加上include math.h,这样,我们的主程序中就不需要包含它了,但是这样做也有副作用,具体有什么副作用,我们以后接触到时再介绍。我们实际用到的一些程序,也许代码有很长,上千行,甚至上万行,这些代码不可能放在一个*.c文件中,所以我们经常把一些功能做成*.h,*c的文件形式,然后在主程序中包含这些文件,这样就把一个大程序分割成几个小块,不仅浏览方便,对以后的修改也有很多好处。我们在平时就应该有这样的好习惯,把一些经常使用的功能做成库函数的形式保存下来,也许刚开始你会觉得很烦琐,可到了后来,也许几年过去了,你会发现,一个好几千行上万行的程序,有一大半的功能你都有,直接调用就可,这会大大缩短你的程序开发周期的。就好象这里的判断素数一样,如果以后还需要判断一个数是不是素数,就没必要再写那些代码了,直接调用judge()函数就可。
一、作用域和生存期C程序的标识符作用域有三种:局部、全局、文件。标识符的作用域决定了程序中的哪些语句可以使用它,换句话说,就是标识符在程序其他部分的可见性。通常,标识符的作用域都是通过它在程序中的位置隐式说明的。1.局部作用域前面各个例子中的变量都是局部作用域,他们都是声明在函数内部,无法被其他函数的代码所访问。函数的形式参数的作用域也是局部的,它们的作用范围仅限于函数内部所用的语句块。
void add(int);
main(){int num=5;add(num);printf(%d\n,num); /*输出5*/}
void add(int num){num++;printf(%d\n,num); /*输出6*/}
上面例子里的两个num变量都是局部变量,只在本身函数里可见。前面我们说了,在两个函数出现同名的变量不会互相干扰,就是这个道理。所以上面的两个输出,在主函数里仍然是5,在add()函数里输出是6。
2.全局作用域对于具有全局作用域的变量,我们可以在程序的任何位置访问它们。当一个变量是在所有函数的外部声明,也就是在程序的开头声明,那么这个变量就是全局变量。
void add(int);
main(){int n=5;add(n);printf(%d\n,num); /*输出6*/}
void add(num) /*形式参数没有指定类型*/{num++;printf(%d\n,num); /*输出6*/}
上面的main()和add()里面,并没有声明num,但是在最后输出的时候却要求输出num,这是由于在程序的开始声明了num是全局变量,也就是在所有函数里都可以使用这个变量。这时候一个函数里改变了变量的值,其他函数里的值也会出现影响。上面的例子输出都是6,因为在add()函数里改变了num的值,由于num是全局变量,就好象它们两个函数共用一个变量,所以在main()函数里的num也随之改变了。
3.文件作用域在很多C语言书上,都没有说明文件作用域,或者只是略微的提到,其实文件作用域在较大程序中很有作用(在多文件系统中)。文件作用域是指外部标识符仅在声明它的同一个转换单元内的函数汇总可见。所谓转换单元是指定义这些变量和函数的源代码文件(包括任何通过#include指令包含的源代码文件)。static存储类型修饰符指定了变量具有文件作用域。
static void add(int);
main(){scanf(%d,&num);add(num)printf(%d\n,num);}
void add(num){num++;}
上面的程序中变量num和函数add()在声明是采用了static存储类型修饰符,这使得它们具有文件作用域,仅爱定义它们的文件内可见。由于我们提到的大多数程序都只有一个编译文件组成,所以这种写法没有实际意义。但是实际工程上的文件有很多,它们不是由一个人写成的,由很多人共同完成,这些文件都是各自编译的,这难免使得某些人使用了一样的全局变量名,那么为了以后程序中各自的变量和函数不互相干扰,就可以使用static修饰符,这样在连接到同一个程序的其他代码文件而言就是不可见的。
二、变量存储类型前面我们说了,声明变量时用如下类似的形式:
它们都没有存储类型修饰符,我们在声明时也可以通过存储类型修饰符来告诉编译器将要处理什么类型的变量。存储类型有以下四种:自动(auto)、静态(static)、外部(extern)、寄存器(regiser)。1.自动存储类型自动存储类型修饰符指定了一个局部变量为自动的,这意味着,每次执行到定义该变量的语句块时,都将会为该变量在内存中产生一个新的拷贝,并对其进行初始化。实际上,如果不特别指明,局部变量的存储类型就默认为自动的,因此,加不加auto都可以。main(){auto int num=5;printf(%d\n,num);}在这个例子中,不论变量num的声明是否包含关键字auto,代码的执行效果都是一样的。函数的形式参数存储类型默认也是自动的。
2.静态存储变量前面已经使用了static关键字,但是对于局部变量,静态存储类型的意义是不一样的,这时,它是和自动存储类型相对而言的。静态局部变量的作用域仍然近局限于声明它的语句块中,但是在语句块执行期间,变量将始终保持它的值。而且,初始化值只在语句块第一次执行是起作用。在随后的运行过程中,变量将保持语句块上一次执行时的值。看下面两个对应的程序:
/*1.C*/ /*2.C*/int add(); int add();
main() main(){ {result=add() result=add();printf(%d ,result); printf(%d ,result);result=add(); result=add();printf(%d ,result); printf(%d ,result);result=add(); result=add();printf(%d,result); printf(%d,result);} }
int add() int add(){ {int num=50; static int num=50;num++; num++;} }
上面两个源文件,只有函数add()里的变量声明有所不同,一个是自动存储类型,一个是静态存储类型。对于1.C文件,输出结果为51 51 51;这很好理解,每次初始值都是50,然后加1上来。对于2.C文件,输出结果为51 52 53;这是由于变量是静态的,只在第一次初始化了50,以后都是使用上次的结果值。当第一次调用add()时,初始化为50,然后加1,输出为51;当第二次调用时,就不初始化了,这时num的值为上次的51,然后加1,输出52;当第三次调用时,num为52,加1就是53了。比较就会发现它们的不同之处了。静态变量在下一节要说的递归函数中经常使用到。当第一次不指明静态变量的初始值时,默认为0。
下面举一个例子,把我们说到的静态变量理解一下。求1+2+…&#的值
void add();
main(){result=0;for(i=0;i&100;i++) add();printf(%d\n,result);}
void add(){static int num=0;num++;result+=}
add()函数被调用了100次,num的值从1一直变到100,这样就可以求出它们的和了。如果写成int num=0;那就是求1+1+…&#0个1的值了。实际上类似的这类问题我们可以通过递归函数来解决,什么是递归,我们下一节介绍。3.外部存储类型外部存储类型声明了程序将要用到的、但尚未定义的外部变量。通常,外部存储类型都是用于声明在另一个转换单元中定义的变量。下面举一个例子,这个例子包括两个文件。
/*1.C*/void a();
main(){a();printf(%d\n,num);}
void a(){num=5;}
这两个程序是分别编译的,然后连接成一个执行文件。具体如何操作,可以查看一些手册,这儿我简单说了一下。把上面两个文件都编译好后,再制作一个.prj文件,里面的内容是:1.c2.c只有这两行,这可在编辑状态下写成,存盘,取名为1.prj。然后选择project选项,选择project name,填入1.prj文件名,按F9后,即可生成1.exe文件。
main()函数中变量num是在另一个文件中定义的。因此,当编译器编译1.c时,无法确定该变量的地址。这时,外部存储类型声明告诉编译器,把所有对num的引用当作暂且无法确定的引用,等到所有便宜好的目标代码连接成一个可执行程序模块时,再来处理对变量num的引用。外部变量的声明既可以在引用它的函数的内部,也可以在外部。如果变量声明在函数外部,那么同一转换单元内的所有函数都可以使用这个外部变量。反之,如果在函数内部,那么只有这一个函数可以使用该变量。
前面说了文件作用域的问题,如果在声明全局变量时,加上static修饰符,那么该变量只在当前文件内可见,而extern又可以引用其它文件里的变量。所以在一个大型程序中,每个程序员只是完成其中的一小块,为了让自己的变量不让其他程序员使用,保持一定的独立性,经常在全局变量前加static。我们可以这样来说明一下:还是上面的两个文件,现在再增加一个文件3.c,内容为:
void a(){num=6;}
把1.prj文件后面加上3.c 这样,我们生成的1.exe文件,执行时输出是5,而不是6。因为3.c文件的num变量增加了文件作用域,在其他文件中是无法使用它的。4.寄存器存储类型被声明为寄存器存储类型的变量,除了程序无法得到其地址外,其余都和自动变量一样。至于什么是变量地址,以后说指针时会详细介绍。
main(){num=100;printf(%d,num);}
使用寄存器存储类型的目的是让程序员指定某个局部变量存放在计算机的某个硬件寄存器里而不是内存中,以提高程序的运行速度。不过,这只是反映了程序员的主观意愿,编译器可以忽略寄存器存储类型修饰符。寄存器变量的地址是无法取得的,因为绝大多数计算机的硬件寄存器都不占用内存地址。而且,即使编译器忽略寄存器类型修饰符把变量放在可设定地址的内存中,我们也无法取地址的限制仍然存在。要想有效的利用寄存器存储类型,必须象汇编语言程序员那样了解处理器的内部构造,知道可用于存放变量的寄存器的数量和种类,以及他们是如何工作的。但是,不同计算机在这些细节上未必是一样的,因此对于一个可移植的程序来说,寄存器存储类型的作用不大。特别是现在很多编译器都能提供很好的优化效果,远比程序员来选择有效的多。不过,寄存器存储类型还是可以为优化器提供重要的参考。一、栈在说函数递归的时候,顺便说一下栈的概念。栈是一个后进先出的压入(push)和弹出(pop)式数据结构。在程序运行时,系统每次向栈中压入一个对象,然后栈指针向下移动一个位置。当系统从栈中弹出一个对象时,最近进栈的对象将被弹出。然后栈指针向上移动一个位置。程序员经常利用栈这种数据结构来处理那些最适合用后进先出逻辑来描述的编程问题。这里讨论的程序中的栈在每个程序中都是存在的,它不需要程序员编写代码去维护,而是由运行是系统自动处理。所谓的系统自动维护,实际上就是编译器所产生的程序代码。尽管在源代码中看不到它们,但程序员应该对此有所了解。再来看看程序中的栈是如何工作的。当一个函数(调用者)调用另一个函数(被调用者)时,运行时系统将把调用者的所有实参和返回地址压入到栈中,栈指针将移到合适的位置来容纳这些数据。最后进栈的是调用者的返回地址。当被调用者开始执行时,系统把被调用者的自变量压入到栈中,并把栈指针再向下移,以保证有足够的空间存储被调用者声明的所有自变量。当调用者把实参压入栈后,被调用者就在栈中以自变量的形式建立了形参。被调用者内部的其他自变量也是存放在栈中的。由于这些进栈操作,栈指针已经移动所有这些局部变量之下。但是被调用者记录了它刚开始执行时的初始栈指针,以他为参考,用正或负的偏移值来访问栈中的变量。当被调用者准备返回时,系统弹出栈中所有的自变量,这时栈指针移动了被调用者刚开始执行时的位置。接着被调用者返回,系统从栈中弹出返回地址,调用者就可以继续执行了。当调用者继续执行时,系统还将从栈中弹出调用者的实参,于是栈指针回到了调用发生前的位置。可能刚开始学的人看不太懂上面的讲解,栈涉及到指针问题,具体可以看看一些数据结构的书。要想学好编程语言,数据结构是一定要学的。
二、递归递归,是函数实现的一个很重要的环节,很多程序中都或多或少的使用了递归函数。递归的意思就是函数自己调用自己本身,或者在自己函数调用的下级函数中调用自己。递归之所以能实现,是因为函数的每个执行过程都在栈中有自己的形参和局部变量的拷贝,这些拷贝和函数的其他执行过程毫不相干。这种机制是当代大多数程序设计语言实现子程序结构的基础,是使得递归成为可能。假定某个调用函数调用了一个被调用函数,再假定被调用函数又反过来调用了调用函数。这第二个调用就被称为调用函数的递归,因为它发生在调用函数的当前执行过程运行完毕之前。而且,因为这个原先的调用函数、现在的被调用函数在栈中较低的位置有它独立的一组参数和自变量,原先的参数和变量将不受影响,所以递归能正常工作。程序遍历执行这些函数的过程就被称为递归下降。程序员需保证递归函数不会随意改变静态变量和全局变量的值,以避免在递归下降过程中的上层函数出错。程序员还必须确保有一个终止条件来结束递归下降过程,并且返回到顶层。例如这样的程序就是递归:
void a(int);
main(){int num=5;a(num);}
void a(int num){if(num==0)printf(%d,num);a(--num);}
在函数a()里面又调用了自己,也就是自己调用本身,这样就是递归。那么有些人可能要想,这不是死循环吗?所以在递归函数中,一定要有return语句,没有return语句的递归函数是死循环。我们分析上面的例子,先调用a(5),然后输出5,再在函数中调用本身a(4),接着回到函数起点,输出4,……,一直到调用a(0),这时发现已经满足if条件,不在调用而是返回了,所以这个递归一共进行了5次。如果没有这个return,肯定是死循环的。虽然递归不难理解,但是很多在在使用递归函数的时候,问题多多。这里面一般有两个原因:一是如何往下递归,也就是不知道怎么取一个变量递归下去;二是不知道怎么终止递归,经常弄个死循环出来。下面看几个例子:1.求1+2+…&#的和先分析一下。第一递归变量的问题,从题目上看应该取1,2,…&#这些变量的值作为递归的条件;第二就是如何终止的问题,从题目上看应该是当数为100的时候就不能往下加了。那么我们试着写一下程序。
int add(int);
main(){int num=1,sn=add(num);printf(%d\n,sn);getch();}
int add(int num){sn+=if(num==100)add(++num);}
分析一下程序:前调用add(1),然后在子函数中把这个1加到sn上面。接着调用add(2),再把sn加2上来。这样一直到100,到了100的时候,先加上来,然后发现满足了if条件,这时返回sn的值,也就是1+2+…&#的值了。这里有一个问题一定要注意,就是 有些人就不明白,为什么要使用static类型修饰符,为什么不使用int sn=0;?如果使用int sn=0;这样的语句,在每次调用函数add()的时候,sn的值都是赋值为0,也就是第一步虽然加了1上来,可是第二次调用的时候,sn又回到了0。我们前面说了,static能保证本次初始化的值是上次执行后的值,这样也就保证了前面想加的结果不会丢失。如果你修改为int sn=0,最后结果一定是最后的100这个值而不是5050。
2.求数列s(n)=s(n-1)+s(n-2)的第n项。其中s(1)=s(2)=1。可以看出,终止条件一定是s(1)=s(2)=1。递归下降的参数一定是n。
int a(int);
main(){int n,s;scanf(%d,&n);s=a(n);printf(%d\n,s);getch();}
int a(int n){if(n&3) return 1;return a(n-1)+a(n-2);}
这个题目主要说明的是,在函数中,不一定只有一个return语句,可以有很多,但是每次对归的时候只有一个起作用。题目不难理解,这儿不分析了。说了这些递归,其实它和函数的调用没有大的区别,主要就是一个终止条件要选好。递归函数很多时候都能用循环来处理。
main(){int n=20,array[20];for(i=0;i {if(i&2) array[i]=1;else array[i]=array[i-1]+array[i-2];}printf(%d\n,array[19]);getch();}
上面的程序就是实现一模一样的功能的。但是它有一个缺陷,就是n的值不是通过键盘输入来得到。如果想通过键盘来得到n,可以这样:
main(){int n,i;int s1=1,s2=1,tempscanf(%d,&n);for(i=3;i&=n;i++){temp=s2;s2+=s1;s1=}printf(%d\n,s2);getch();}
但是在某些场合,使用递归比使用循环要简单的多。而且有些题目,一看就知道应该使用递归而不是循环来处理。预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。可见预处理过程先于编译器对源代码进行处理。在C语言中,并没有任何内在的机制来完成如下一些功能:在编译时包含其他源文件、定义宏、根据条件决定编译时是否包含某些代码。要完成这些工作,就需要使用预处理程序。尽管在目前绝大多数编译器都包含了预处理程序,但通常认为它们是独立于编译器的。预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符。预处理指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。下面是部分预处理指令:
指令 用途# 空指令,无任何效果#include 包含一个源代码文件#define 定义宏#undef 取消已定义的宏#if 如果给定条件为真,则编译下面代码#ifdef 如果宏已经定义,则编译下面代码#ifndef 如果宏没有定义,则编译下面代码#elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码#endif 结束一个#if……#else条件编译块#error 停止编译并显示错误信息
一、文件包含#include预处理指令的作用是在指令处展开被包含的文件。包含可以是多重的,也就是说一个被包含的文件中还可以包含其他文件。标准C编译器至少支持八重嵌套包含。预处理过程不检查在转换单元中是否已经包含了某个文件并阻止对它的多次包含。这样就可以在多次包含同一个头文件时,通过给定编译时的条件来达到不同的效果。例如:
#define AAA#include t.c#undef AAA#include t.c
为了避免那些只能包含一次的头文件被多次包含,可以在头文件中用编译时条件来进行控制。例如:/*my.h*/#ifndef MY_H#define MY_H……#endif
在程序中包含头文件有两种格式:#include #include my.h第一种方法是用尖括号把头文件括起来。这种格式告诉预处理程序在编译器自带的或外部库的头文件中搜索被包含的头文件。第二种方法是用双引号把头文件括起来。这种格式告诉预处理程序在当前被编译的应用程序的源代码文件中搜索被包含的头文件,如果找不到,再搜索编译器自带的头文件。采用两种不同包含格式的理由在于,编译器是安装在公共子目录下的,而被编译的应用程序是在它们自己的私有子目录下的。一个应用程序既包含编译器提供的公共头文件,也包含自定义的私有头文件。采用两种不同的包含格式使得编译器能够在很多头文件中区别出一组公共的头文件。
二、宏宏定义了一个代表特定内容的标识符。预处理过程会把源代码中出现的宏标识符替换成宏定义时的值。宏最常见的用法是定义代表某个值的全局符号。宏的第二种用法是定义带参数的宏,这样的宏可以象函数一样被调用,但它是在调用语句处展开宏,并用调用时的实际参数来代替定义中的形式参数。1.#define指令#define预处理指令是用来定义宏的。该指令最简单的格式是:首先神明一个标识符,然后给出这个标识符代表的代码。在后面的源代码中,就用这些代码来替代该标识符。这种宏把程序中要用到的一些全局值提取出来,赋给一些记忆标识符。#define MAX_NUM 10int array[MAX_NUM];for(i=0;i 在这个例子中,对于阅读该程序的人来说,符号MAX_NUM就有特定的含义,它代表的值给出了数组所能容纳的最大元素数目。程序中可以多次使用这个值。作为一种约定,习惯上总是全部用大写字母来定义宏,这样易于把程序红的宏标识符和一般变量标识符区别开来。如果想要改变数组的大小,只需要更改宏定义并重新编译程序即可。宏表示的值可以是一个常量表达式,其中允许包括前面已经定义的宏标识符。例如:#define ONE 1#define TWO 2#define THREE (ONE+TWO)注意上面的宏定义使用了括号。尽管它们并不是必须的。但出于谨慎考虑,还是应该加上括号的。例如:six=THREE*TWO;预处理过程把上面的一行代码转换成:six=(ONE+TWO)*TWO;如果没有那个括号,就转换成six=ONE+TWO*TWO;了。宏还可以代表一个字符串常量,例如:#define VERSION Version 1.0 Copyright(c) 20032.带参数的#define指令带参数的宏和函数调用看起来有些相似。看一个例子:#define Cube(x) (x)*(x)*(x)可以时任何数字表达式甚至函数调用来代替参数x。这里再次提醒大家注意括号的使用。宏展开后完全包含在一对括号中,而且参数也包含在括号中,这样就保证了宏和参数的完整性。看一个用法:int num=8+2;volume=Cube(num);展开后为(8+2)*(8+2)*(8+2);如果没有那些括号就变为8+2*8+2*8+2了。下面的用法是不安全的:volume=Cube(num++);如果Cube是一个函数,上面的写法是可以理解的。但是,因为Cube是一个宏,所以会产生副作用。这里的擦书不是简单的表达式,它们将产生意想不到的结果。它们展开后是这样的:volume=(num++)*(num++)*(num++);很显然,结果是10*11*12,而不是10*10*10;那么怎样安全的使用Cube宏呢?必须把可能产生副作用的操作移到宏调用的外面进行:int num=8+2;volume=Cube(num);num++;3.#运算符出现在宏定义中的#运算符把跟在其后的参数转换成一个字符串。有时把这种用法的#称为字符串化运算符。例如:
#define PASTE(n) adhfkj#n
main(){printf(%s\n,PASTE(15));}宏定义中的#运算符告诉预处理程序,把源代码中任何传递给该宏的参数转换成一个字符串。所以输出应该是adhfkj15。4.##运算符##运算符用于把参数连接到一起。预处理程序把出现在##两侧的参数合并成一个符号。看下面的例子:
#define NUM(a,b,c) a##b##c#define STR(a,b,c) a##b##c
main(){printf(%d\n,NUM(1,2,3));printf(%s\n,STR(aa,bb,cc));}
最后程序的输出为:123aabbcc千万别担心,除非需要或者宏的用法恰好和手头的工作相关,否则很少有程序员会知道##运算符。绝大多数程序员从来没用过它。
三、条件编译指令条件编译指令将决定那些代码被编译,而哪些是不被编译的。可以根据表达式的值或者某个特定的宏是否被定义来确定编译条件。1.#if指令#if指令检测跟在制造另关键字后的常量表达式。如果表达式为真,则编译后面的代码,知道出现#else、#elif或#endif为止;否则就不编译。2.#endif指令#endif用于终止#if预处理指令。
#define DEBUG 0main(){#if DEBUGprintf(Debugging\n);#endifprintf(Running\n);}
由于程序定义DEBUG宏代表0,所以#if条件为假,不编译后面的代码直到#endif,所以程序直接输出Running。如果去掉#define语句,效果是一样的。3.#ifdef和#ifndef#define DEBUG
main(){#ifdef DEBUGprintf(yes\n);#endif#ifndef DEBUGprintf(no\n);#endif}#if defined等价于# #if !defined等价于#ifndef4.#else指令#else指令用于某个#if指令之后,当前面的#if指令的条件不为真时,就编译#else后面的代码。#endif指令将中指上面的条件块。
#define DEBUG
main(){#ifdef DEBUGprintf(Debugging\n);#elseprintf(Not debugging\n);#endifprintf(Running\n);}
5.#elif指令#elif预处理指令综合了#else和#if指令的作用。
#define TWO
main(){#ifdef ONEprintf(1\n);#elif defined TWOprintf(2\n);#elseprintf(3\n);#endif}程序很好理解,最后输出结果是2。
6.其他一些标准指令#error指令将使编译器显示一条错误信息,然后停止编译。#line指令可以改变编译器用来指出警告和错误信息的文件号和行号。#pragma指令没有正式的定义。编译器可以自定义其用途。典型的用法是禁止或允许某些烦人的警告信息。
学习Turbo C语言,如果你不能用指针编写有效、正确和灵活的程序,可以认为你没有学好C语言。指针、地址、数组及其相互关系是C语言中最有特色的部分。规范地使用指针,可以使程序达到简单明了,因此,我们不但要学会如何正确地使用指针,而且要学会在各种情况下正确地使用指针变量。
一、指针基本概念及其指针变量的定义我们知道变量在计算机内是占有一块存贮区域的,变量的值就存放在这块区域之中, 在计算机内部, 通过访问或修改这块区域的内容来访问或修改相应的变量。Turbo C语言中, 对于变量的访问形式之一,就是先求出变量的地址,然后再通过地址对它进行访问,这就是这里所要论述的指针及其指针变量。所谓变量的指针, 实际上指变量的地址。变量的地址虽然在形式上好象类似于整数, 但在概念上不同于以前介绍过的整数, 它属于一种新的数据类型, 即指针类型。Turbo C中, 一般用指针来指明这样一个表达式&x的类型,而用地址作为它的值,也就是说, 若x为一整型变量, 则表达式&x的类型是指向整数的指针,而它的值是变量x的地址。同样, 若则&d的类型是指向以精度数d的指针,而&d的值是双精度变量d的地址。所以, 指针和地址是用来叙述一个对象的两个方面。虽然&x、&d的值分别是整型变量x和双精度变量d的地址, 但&x、&d的类型是不同的, 一个是指向整型变量x的指针, 而另一个则是指向双精度变量d的指针。在习惯上,很多情况下指针和地址这两个术语混用了。 我们可以用下述方法来定义一个指针类型的变量。int *首先说明了它是一指针类型的变量,注意在定义中不要漏写符号*,否则它为一般的整型变量了。另外,在定义中的int 表示该指针变量为指向整型数的指针类型的变量, 有时也可称ip为指向整数的指针。ip是一个变量, 它专门存放整型变量的地址。 指针变量的一般定义为: 类型标识符 *标识符; 其中标识符是指针变量的名字, 标识符前加了*号,表示该变量是指针变量, 而最前面的类型标识符表示该指针变量所指向的变量的类型。一个指针变量只能指向同一种类型的变量, 也就是讲, 我们不能定义一个指针变量, 既能指向一整型变量又能指向双精度变量。 指针变量在定义中允许带初始化项。如: int i, *ip=&i; 注意, 这里是用&i对ip初始化, 而不是对*ip初始化。和一般变量一样,对于外部或静态指针变量在定义中若不带初始化项, 指针变量被初始化为NULL, 它的值为0。Turbo C中规定, 当指针值为零时, 指针不指向任何有效数据, 有时也称指针为空指针。因此, 当调用一个要返回指针的函数时(以后会讲到), 常使用返回值为NULL来指示函数调用中某些错误情况的发生。既然在指针变量中只能存放地址,因此,在使用中不要将一个整数赋给一指针变量。下面的赋值是不合法的: int * ip=100; 假设 int i=200, int * 我们定义了两个整型变量i,x,还定义了一个指向整型}

我要回帖

更多关于 我的世界粘液块弹跳器 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信