关于C语言的相关问题_第1页
关于C语言的相关问题_第2页
关于C语言的相关问题_第3页
关于C语言的相关问题_第4页
关于C语言的相关问题_第5页
已阅读5页,还剩92页未读 继续免费阅读

下载本文档

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

文档简介

关于C语言的相关问题

说而明:本内容涉及到单片机的内容都是关于8051和keilC51编译器。

C程序开发步骤:

编辑一一编译一一连接一一运行。

编辑就是程序代码的录入,生成源程序*.c一编译就是语法分析查错,翻译生

成目标程序.obj,由编译器完成一连接就是与其它目标程序或库链接装配,生成可

执行程序*.exe

源程序目标程序可执行程序

内容程序设计语言机器语言机器语言

格式ASCII二进制二进制

可执行不可以不可以(因不含库文件和其它目标文件)可以

文件名后缀.c或.cpp.obj.exe

说明:C语言编制的单片机程序在编译时,C51要求先产生一个汇编语言源文

件,再编译此汇编文件。用C语言和汇编混合编制的程序,位于#pragmaasm和

#pragmaendasm之间的汇编段或汇编语言编写的函数是用A51编译,C程序

是用C51编译,然后用L51连接成.obj。*.asm是汇编语言程序

若在window中使用写字板编写程序,也是ASCH格式,但要存为*.c文件,不

能存为*.txt格式。

一、运算优先级:

1、运算符的优先级:C语言中,运算符的运算优先级共分为15级。1级最

高,15级最低。在表达式中,优先级较高的先于优先级较低的进行运算。而在

一个运算量两侧的运算符优先级相同时一,则按运算符的结合性所规定的结合方向

处理。

2、运算符的结合性:C语言中各运算符的结合性分为两种,即左结合性(自

左至右)和右结合性(自右至左)。例如算术运算符的结合性是自左至右,即先左后

右。如有表达式x-y+z则y应先与号结合,执行x-y运算,然后再执行+z

的运算。这种自左至右的结合方向就称为“左结合性”。而自右至左的结合方向

称为“右结合性”。最典型的右结合性运算符是赋值运算符。如x=y=z,由于“=”

的右结合性,应先执行y=z再执行x=(y=z)运算。C语言运算符中有不少为右结

合性,应注意区别,以避免理解错误。

C语言中的算术表达式只能由运算符、圆括号、系统函数和运算对象构成,

运算的顺序不但受优先级控制,还受结合性的影响,如上例中的:x-y+z;

C语言中有15个优先级,在表达式中,优先级高的先于优先级低的运算,

在一个运算量两侧的运算符优先级相同,则按运算符的结合性规定的结合方向处

理。如:a+b*(a+2)/c*(b+3).

运算符:单目,双目、三目

关系运算符都是双目运算符,其结合性均为左结合。关系运算符的优先级低

于算术运算符,高于赋值运算符。在六个关系运算符中,<,<=,>,>=的优先级相

同,高于==和!=,==和!=的优先级相同。

!(非)->算术运算符一关系运算符一&&和II-*赋值运算符

“&&”和低于关系运算符,“!”高于算术运算符。

逻辑运算符也有优先级别,!(逻辑非)f&&(逻辑与)一||(逻辑或),

逻辑非的优先值最高。C语言中的以上三个逻辑运算的结果是波尔值“真”或

“假二

位运算符也有优先级,从高到低依次是:“〜”(按位取反)一“<<"(左移)一

“>>"(右移)一(按位与)一“人”(按位异或)一T(按位或)

以上的位运算符运算按位运算后的结果是具体的数,8051汇编语言中的

ANL、ORL、XRL等按位运算后运算的结果也是具体的数值。

优先级运算符结合性

(最高)()□.自左向右1级

!~++—+-*(取值)、&(取地址)sizeof自右向左2级

*/%自左向右3级

+-自左向右4级

<<>>自左向右5级

<<=>>=自左向右6级

==!=自左向右7级

&自左向右

「自左向右

I自左向右

&&自左向右11级

II自左向右12级

?:自右向左13级唯一的三目运算符,格式:逻辑表达式?表达式1:

表达式2如:max=(a>b)?a:b;将条件运算符的结果赋给max.

条件运算符可嵌套,如:嵌套:x>0?l:(x〈0?T:0)

赋值和复合赋值运算符:=,+=,-=,*二,/=,%二,&=「二,仁,〈〈=,>>=自

右向左

赋值运算符左边必须是变量。连续赋值如:a=b=c=l不能在定义时使用。

格式:变量名复合赋值运算符表达式,其含义就是变量与表达式先进行

运算符所要求的运算,再把运算结果赋值给参与运算的

变量。其实这是C语言中一种简化程序的一种方法,凡是二目运算都能用

复合赋值运算符去简化表达。例如:

a+=56等价于a=a+56

派y/=x+9等价于y=y/(x+9)——把赋值运算符右边看为一个整体。

,运算符:自左向右(15级最低)

逗号在C语言中的作用作两种:1、一种就是起隔离作用;2、在C语言中

逗号还是一种特殊的运算符,也就是逗号运算符,能用它将两个或多个表达式连

接起来,形成逗号表达式。逗号表达式的一般形式为:

表达式],表达式2,表达式3...表达式n

这样用逗号运算符组成的表达式在程序运行时,是从左到右计算出各个表达

式的值,而整个用逗号运算符组成的表达式的值等于最右边表达式的值,就是

“表达式n”的值。

sizeof运算符:奇怪的运算符,有点像函数,却又不是。sizeof是用来求

数据类型、变量或是表达式的字节数的一个运算符,但它并不像“=”之类运算

符那样在程序执行后才能计算出结果,它是直接在编译时产生结果的。它的语

法如卜:sizeof(数据类型或变量或是表达式).

二、数据类型及其转换:

C语言的数据分为常量和变量。

★标识符定义和命名规则

定义:标识变量名、符号常量名、函数名、数组名、文件名的字符串序列——

名字。

命名规则:

只能由字母、数字、下划线组成,且第一个字符必须是字母或下划线大小写

字母含义不同,一般用小写,不能使用关键字。

C语言大小是敏感的,同一个字母大和小写被认为是两个不同的数据。关键

字一般都是小写。

TC允许最长32个字符,建议长度不超过8个字符使用:先定义、后使用。

1、数值数据有两种:整数和浮点小数

整数(包含整型常数)的三种表示方法:

・十进制整数:由数字0〜9和正负号表示.如123,-456,0

•八进制整数:由数字0开头,后跟数字0〜7表示.如0123,011

・十六进制整数:由Ox开头,后跟。〜9,a~f,A〜F表示.如0xl23,0xff

★在C语言中计算超出数据类型的范围就溢出,但是系统不报错,编译时要

注意。如:整型变量最大值32767,加1后是-32768的补码形式。

浮点数(float、double)不能用“==0”进行关系判断,用<=10-6;

单精度float有效数字6〜7位,双精度double型有效数字15〜16位.

•浮点型常量一般按双精度64位处理,数后加F或f按单精度;

•浮点型常量不分float和double

•\ddd:3位8进制数代表的字是ASCII表中该数值所对应的字符。

•\xhh:2位16进制数代表的字是ASCII表中该数值所对应的字符。

•字符数据在内存中的存储形式及其使用方法

・单个字符常量是用单引号括起来的,以二进制存放字符的ASCII码值(0〜

255整数)如:'a'存的就是65,"\"反斜杠加字符是转义字符,单个转义常

量也是用单引号括起来,如:<\n,表示换行,'\\'表示单斜杠,''表示单

引号,''表示双引号。

・字符串是用双引号括起来的单个或多个字符,从左到右以二进制存放字符

的ASCII码值,字符串常量或自后边自动加一个空字符“\0”,实际占有空间是

实际字符数(空格也算)+lo

•与整数的存储形式类似

字符数据可以字符或整数形式输出;字符型与整型间互相赋值。

如:inti;chara;i=A;i的值是A在ASCH表中对应的65;

a=65,a的值是65在ASCH表中对应的A;a=,9';把9当作字符赋给了

a,a在存储器中存的是字母9就对应的57的值。

表达式返回的结果值是有类型的。表达式隐含的数据类型取决于组成表达式

的变量和常量的类型。类型转化的原则是从低级向高级自动转化(除非人为的加

以控制)。计算的转换顺序基本是这样的:

字符型一>整型一>长整型一>浮点型一>单精度型一》双精度型

就是当字符型和整型在一起运算时,结果为整型,如果整型和浮点型在一起

运算,所得的结果就是浮点型,如果有双精度型参与运算,那么答案就是双精度

型了。

・将一个浮点小数赋给一个整型变量,则小数部分被舍去,如:inta=6.5;

实际存的a的值是6。

・将一个整数数据赋给一个float型,整数部分不变,后边加小数点和0,

如:floata=12;a=12.000000

・字符变量可以给它赋字符常量、-128〜127之间的整数或转义字符。

强制转换是这样的,在类型说明符的两边加上括号,就把后面的变量转换成

所要的类型了。如:(int)a;(float)b;

第一个式子是把a转换成整型,如果原先原小数部分,则舍去。

第二个式子是把b转换成浮点型,如果原先是整数,则在后面补0。

每一个表达式的返回值都具有逻辑特性。如果返回值为非0,则该表达式返

回值为真,否则为假。

2、C语言有四种对数据类型进行修饰的符号

long长型、short短型、signed有符号型、unsigned无负号型。

Long——般修饰int、double:int2个字节,16位;Longint4个字节,

32位;double8个字节,64位,Longdouble16个字节,128位。

short一般修饰int,shorint2个字节,16位。

signed和unsigned一般修饰(long、short)int、char,表示是否是带符号

数,最高位是否负号位。

3、关于补码存储问题

计算机中数据是以补码形式存储的,正数的补码是本身,负数的补码要转换,

但是机器不能区分正负,CPU它只识别‘0'、T',由于计算机是用高级语言编

写程序,所以把负数转换为补码存储是编译系统完成的,但汇编语言是直接面对

机器编成,使用的是助记符和16进制操作数,没有数据类型,没有明确的正负

数标记,机器本身不能区分正数和负数,数据的正负都在程序员的心里,程序员

认为是正数就按正数规则处理,认为是负数就按负数规则处理,单片机的加减程

序运算各位都参与计算,机器是按无符号数处理,加减判断溢出是看0V,0V溢

出是按负数以补码参与运算的情况下进行判断的,要进行有符号数运算,必须把

负数转换为补码进行,所以用户先要将负数变成补码存入,再进行加减,然后根

据0V的情况进行修正(用户软件)。0V溢出是建立在补码基础上判断的,所以

一旦0V溢出后,说明有效位不够,要增加1位有效位,然后根据Cy判断数的正

负,最后再求该数的补码(CPL+1),才能得到实际的数。有效位的多少决定求补

的数值,如:101补码是:111(-3),1001补码是:1111(-7)

高级语言不用用户干涉,转换和修正是编译系统进行,因此在高级语言中要

输入负数就要在10进制数前加上负号“-"明确标记,若标记为负数则编译系统

将其变成2进制补码存入,如:chara=-1,存入的就是口1111111(char类型

一个字节),如:inta=-l,存入的就是类型两个字节),

若无负号(正数不加+)编译系统是按正数处理,直接转换为2进制存入,如:

inta=127,存入的就是冰)00000001111111;

在C语言中超出数据类型的范围就溢出,但是系统不报错,超出该类型总

长的部分丢失。

如:chara=129,超出-128〜127范围,但编译系统转换为2进制为

010000001,符号位“0”丢失,存入10000001,最高位是1,因a是char类型,

是有符号数据,若用〃%C〃格式输出,编译系统把最高位1看为负号,“10000001”

是-127的补码,则按-127对应的ASCII字符处理,若用10进制int格式输出,

先把“10000001”看作-127,再转换为int类型(16位)补码,是

1111111110000001,"%d”格式输出是T27,"%u〃格式输出是65409。

如:chara=-129,超出-128〜127范围,但编译系统转换为2进制补码为

lOmilll,符号位“1”丢失,存入01111111,最高位是0,因a是char类型,

是有符号数据,若用〃%C〃格式输出,把最高位。看为正号,“01111111”是127,

则按127对应的ASCII字符处理,若用10进制int格式输出,先把“01111111”

看作127,再转换为int类型(16位)补码,是0000000001111111,"%d”格式输

出是127,"%u”格式输出是127。

如:unsignedchara=129,在0〜255范围内,但编译系统转换为2进制为

10000001直接存入,无符号位,因a是unsignedchar类型,是无符号数据,

若用"%C”格式输出,“10000001”是129,则按129对应的ASCII字符处理,若用

10进制int格式输出,先把“10000001”看作129,再转换为int类型(16位)

补码,是0000000010000001,〃%『'格式输出是129,“%u〃格式输出是129。

在定义数据的时候,如果定义的是有符号数,编译系统就会把最高位当符号

位,定义的是无符号数,编译系统就不会把最高位当符号位。无论以何种格式输

出,有符号的就当作有符号处理,无符号就当作无符号处理。

★其实单片机编成很少用带符号数运算,一般都用无符号运算,加就用

ADD,ADDC,减就直接用SUBB,不要搞得太复杂。

三、printf()和scanf()函数的调用格式为:

1、printf(格式控制,输出列表);即〈格式化字符串〉〈参量表》

格式控制由输出文字和数据格式说明组成,一般用双引号括起来,格式说

明是以“%”开头,形式是:京数据输出宽度说明X格式符>,宽度说明可以省略.

格式控制中双引号内除格式说明外的其它字符原样输出,输出列表中可以是变

量、表达式、数值(常量)。

量表是需要输出的一系列参数,其个数必须与格式化字符串所说明的输出参

数个数一样多各参数之间用,分开,且顺序一一对应,否则将会出现意想不到的

错误。

注意:printf("%d”“%d",a,b)格式控制符可以分别用多个银号扩起来,

引号内的非格式字符原样打印。

(1).可以在%和字母之间插进数字表示最大场宽。(要求格式字符在外之后,所以

表示宽度的数字也要在%之后。)

例如:%3d表示输出3位整型数,不够3位右对齐。

%9.2f表示输出场宽为9的浮点数,其中小数位为2,整数位为6,小数点占一

位,不够9位右对齐。

%8s表示输出8个字符的字符串,不够8个字符右对齐。

如果字符串的长度、或整型数位数超过说明的场宽,将按其实际长度输出。

但对浮点数,若整数部分位数超过了说明的整数位宽度,将按实际整数位输出;若

小数部分位数超过了说明的小数位宽度,则按说明的宽度以四舍五入输出。

另外,若想在输出值前加一些0,就应在场宽项前加个0。

例如:%04d表示在输出一个小于4位的数值时,将在前面补0使其总宽度为

4位。

如果用浮点数表示字符或整型量的输出格式,小数点后的数字代表最大宽度,

小数点前的数字代表最小宽度。

例如:%6.9s表示显示一个长度不小于6且不大于9的字符串。若大于9,

则第9个字符以后的内容将被删除。

(2).可以在%和字母之间加小写字母1,表示输出的是长型数。

例如:%ld表示输出long整数

%lf表示输出double浮点数

(3).可以控制输出左对齐或右对齐,即在%和字母之间加入一个-号可说明

输出为左对齐,否则为右对齐。

例如:7d表示输出7位整数左对齐

格式符:输出十进制整数,有3种用法

★%d格式:以带符号的十进制形式输出整数,按数据实际长度输出,数据范

围-32768—32767

★%md格式:m指定输出字段的宽度,数据位数小于m,左端补空格,反之按

实际输出。

★%ld格式:输出长整型数据;可以用%mid格式指定输出列宽;

★0格式符:以不带符号的八进制输出整数,是将内存中的二进制位整个按

八进制输出,所以输出值没有符号。可以指定输出宽度%m。,长整型可以用%1。

格式输出。(该处是字母“。”,在输出格式中用字母。表示八进制输出,在立即

数前加数字0表示该数是八进制数。)

★x格式符:以不带符号的十六进制输出整数,同。格式符,无符号,即无

负十六进制数。可以指定输出宽度%mx,长整型可以用%lx格式输出。

★u格式符:以不带符号的号进制输出整型数据,unsigned(无符号);

int型可以用%u格式输出,unsigned型也可以用%d、%。和%x格式输出。

例4.3无符号数据的输出

#include<stdio.h>

voidmain()

{unsignedinta=65535;

intb=-2;

printf("a=%d,%o,%x,%u\n”,a,a,a,a);

printf("b=%d,%o,%x,%u\n”,b,b,b,b);

)

运行结果:

a=-1,177777,ffff,65535

b=-2,177776,fffe,65534

以上可以看出,%d是按有符号数输出整数,%口按无符号数输出整数,整数

在机器中是以二进制存的,且以补码形式存储,所以若把无符号数按照9团有符

号输出,会将最高位看成符号位进行处理,将数看作补码,进行处理后输出真值,

所以上例中a=T;若把有符号数按照%u无符号输出,会将最高的符号位按照有效

数值处理,处理后按正整数输出,所以上例中b=65534;所以输出格式要和数据

类型一致,防止出错。

%。和%x格式输出都是把最高位当作有效数据处理,直接转换为相应的进制

输出。

★c格式符:输出一个字符;值在。-255的整数,可以用%c形式输出为字符。

★s格式符:输出——个字符串,有%s,%ms,%—ms,%m.ns,%—m.ns五种用

法。printf(“%s”,“ABC”);结果:ABC

★f格式符:输出实数

%f格式:整数部分全部输出,小数6位。可以有非有效数字输出,因为单

精度有效位7位,双精度16位。%m.nf格式:占m歹!],其中n位小数,左补空

格。%-m.nf格式:右补空格。如:floata=567.789;printf(“%f”,a);

结果:567.789000

★e格式符:指数形式输出实数

%e格式:不指定m和n,小数6位,指数部分共5位,

其中e和指数符号各1位,指数值3位。%m.ne和%-m.ne格式:m、n、一的含义

同前面。没有n时,自动=6。如:floata=567.789;printf("%e”,a);

结果:5.677890e+002

★g格式符:输出实数

可以自动根据数值大小选择f或e格式(选列少的)不输出无意义的零。

2、scanf函数(格式输入函数函通过标准输入设备输入一组数据。

格式:scanf(,地址表列);

功能:按指定格式从键盘读入数据,存入地址表指定的存储单元中,并按回

车键结束。

格式控制中尽量简单,避免出错。

★输入分隔符的指定

一般以空格、TAB或“或作为分隔符;

输入数据时,遇非法输入则认为数据结束;

其它字符在格式控制中作分隔符:格式串中两个格式符间有其它字符,则输

入时对应位置也要有相同的字符。也就是说在格式控制中加入了格式说明之外的

其它字符,则输入数据时这些字符也同样输入。但只作为间隔。若格式控制中没

有间隔的符号,在输入时,用户可以用空格、“,”等分隔,不然机器无法区分。

如:scanf("x=%d,y=%d",&a,&b);输入时要以:x=12,y=10输入。

注意:scanf以字符串格式输入数组时,以空格和回车为结束符。get只以

回车为结束符。如:

gets(al);scanf("%s”,a2);

输入:chinabeijingJchinabeijingJ

输出:al=chinabeijinga2=china

四、书写规则

(1).一5〈二x&&x〈l不能写成一5<=x〈l;1〈二x&&x〈4也不能写成1〈二x<4;在C语言

中,不能认识连续不等式。

(2).y=2*x+5不能写成y=2x+5;y=3*x-2也不能写成y=3x-2;这与我们平时所

写的方法不一样。

五、goto语句

格式为:goto语句标号;/*不能跟数字等其它*/

如:loop:if(i<=100)

{sum+=i;

i++;

gotoloop;

}

goto语句是一种无条件转移语句,语句标识符加上一个“:”一起出现在函

数内某处,执行goto语句后,程序将跳转到该标号处并执行其后的语句。标号

既然是一个标识符,也就要满足标识符的命名规则。另外标号必须与goto语句

同处于一个函数中,但可以不在一个循环层中。通常got。语句与if条件语句连

用,当满足某一条件时,程序跳到标号处运行。goto语句通常不用,主要因为

它将使程序层次不清,且不易读,但在多层嵌套退出时,用goto语句则比较合

理。所以在同时跳出多层循环时,应该使用got。语句。记住,所有的got。语句

其实都是可以用break,continue代替的。

常见的goto语句使用方法是用它来跳出多重循环,不过它只能从内层循环

跳到外层循环,不能从外层循环跳到内层循环。(只能向外跳不能往里跳。)

六、函数

C语言的函数一般有三种:主函数(main函数)、用户自定义函数、库函数,

程序是开始于主函数,结束于主函数,在程序中,预处理语句必须以开头,

不能加“;”,在函数书中,C语言语句后边必须加。

1、概述

有参函数定义的一般形式:

类型标识符函数名(形式参数表列)〃函数返回值类型隐含为int型

{说明部分

语句

形参的数据类型一一声明,不能合并,

如:intmax(intx,inty)(对)

intmax(intx,y)(错)

对形参的声明的传统方式

类型标识符函数名(形参表)/*很少用*/

形参类型说明

{说明部分

语句

}

如:intmax(x,y):老版

intx,y;/*在打括号外*/

{intz;

z=x>y?x:y;

return(z);

形式参数和实际参数

形式参数:定义函数时函数名后面括号中的变量名

实际参数:调用函数时函数名后面括号中的表达式

几点说明:

1、实参可以是常量、变量或表达式。必须有确定的值。当函数调用时,将

实参的值传递给形参,若是数组名,则传送的是数组首地址。

2、形参必须指定类型,只能是简单变量或数组,不能是常量或表达式;

3、形参与实参类型一致,个数相同顺序相同。

4、若形参与实参类型不一致,自动按形参类型转换-----函数调用转换;

4、形参在函数被调用前不占内存;函数调用时为形参分配内存;调用结束,

内存释放;

5、实参对形参的数据传送是值传送,也是单向传送,当被调函数的形参发

生变化时,并不改变主调函数实参的值。形、实参占据的是不同的存储单元

函数调用时直接将实参写在()中,不能带数据类型,如:delay(5)

例:形、实参占据的是不同的存储单元

#include<stdio.h>

voidmain()

{inta=2,b=3;

printf("a=%d,b=%d\n”,a,b);

printf("&a=%x,&b=%x\n”,&a,&b);

add(a,b);

printf("a=%d,b=%d\n”,a,b);

printf("&a=%x,&b=%x\n”,&a,&b);

)

add(intx,inty)

{x=x+8;y=y+12;

printf(“x=%d,y=%d\nw,x,y);

printf("&x=%x,&y=%x\n”,&x,&y);

)

运行结果:

a=2,b=3

&a=ffd6,&b=ffd8

x=10,y=15

&x=ffd2,&y=ffd4

a=2,b=3

&a=ffd6,&b=ffd8

注意:如果以数组名或指针传过来的是地址,那么改变的就是同一地址单元

的值,该地址的存放的变量自然就改变。

★函数的返回值

返回语句形式:

return(表达式);或return表达式;

功能:使程序控制从被调用函数返回到调用函数中,同时把返值带给调用

函数。

★说明:

1、函数的返回值,必须用return语句带回。

2、return语句只能把一个返值传递给调用函数。

3、函数中可有多个return语句,执行哪一个由程序执行情况来定。

if(a>b)return(a);

elsereturn(b);

4、return后的值可以是一个表达式,如:return(x>y?x:y);

5、返回值的类型为定义的函数类型,不指定的按整型处理。

6、若return语句中表达式类型与函数类型不一致,则转换为函数类型。

7、若无return语句,遇}时,自动返回调用函数。可能返回一个不确定

或无用的值。

8、无返回值的函数,定义为void类型。

★函数调用的一般形式:函数名(实参表列)

函数调用的方式:

1、函数语句:以独立的语句去调用函数。不要求有返回值,仅完成一定

的操作。

2、函数表达式:

函数返回一个确定值,以参加表达式的运算。不可用于void

例:m=max(a,b)*2;

3、函数参数:函数调用作为另一个函数的参数。

例:printf("%d”,max(a,b));/*输出大数*/

第3种这种调用只能在实参中使用,不能在定义函数时在形参中写其它函

数。

☆数组名可作函数参数时类型应一致,采用数组名作为参数的传递,传递

是地址,就是主调函数和被调函数处理的是同一空间,形参的改变实参也会改变,

具有双向传递性,这是指针的性质。

数组名作形参时一维数组大小或多维数组第一维可不指定在定义数组时

在数组名后面跟一个空的方括弧,方括弧不能少,否则就不能说明是数组名,数

据类型也不能少。但多维数组形参的第二维不能省,而且实参和形参的数要相同。

每个程序有且只有一个main函数,一个程序可能由多个文件(乎c)构成,

但只能有,个main函数。每个C程序的入口和出口者M立于函数main()之中。main。

函数可以调用其他函数,这些函数执行完毕后程序的控制又返回到main。函数

中,也就是开始于main,也结束于main函数。main()函数不能被别的函数所调

用。通常我们把这些被调用的函数称为下层(lowerTevel)函数。函数调用发生

时,立即执行被调用的函数,而调用者则进入等待状态,直到被调用函数执行完

毕。函数可以有参数和返回值。

为了调用一个函数,必须事先声明该函数的返回值类型和参数类型,这和使

用变量的道理是一样的。当定义在调用之前时\可以不声明函数。也可以在主函

数内部,调用之前声明。

一般来说,比较好的程序书写顺序是,先声明函数,然后写主函数,然后再

写那些自定义的函数。

函数在声明、调用、定义的时候,形参和实参不仅个数要一样,类型也要对

应。

这里先讲讲KEILC编译器所支持的注释语句。一种是以“〃”符号开始的

语句,符号之后的语句都被视为注释,直到有回车换行。另一种是在“/*”和

“*/”符号之内的为注释。注释不会被C编译器所编译。一个C应用程序中

应有一个main主函数,main函数能调用别的功能函数,但其它功能函数不允

许调用main函数。不论main函数放在程序中的那个位置,总是先被执行。

★几点说明:

(1)一个源文件由一个或者多个函数组成。

(2)一个C程序由一个或者多个源文件组成。

(3)所有函数都是平行的,不能嵌套定义。

注意:定义多个子函数,若都放在所有函数之前声明(程序的开头),不分

先后顺序。多个外部变量也一样,若放在在所有函数之前声明(程序的开头),

不分先后顺序。因卷现处理声明,才处理函数(包含调用),所以不用分先后。

2、中断函数(C51)

Void函数名(void)interruptn[usingm]

{函数体}

Interrupt表示该函数是中断函数,n表示中断号,如INTO排第一为0,本

项不能省,它告诉中断函数的入口地址,在单片机中,中断的入口地址是规定死

的,汇编语言一般在每个中断的入口地址出写一个无条件跳转指令,使其跳到中

断子程序(不放调用指令,因为调用指令要压栈)。C语言就是用这个告诉中断入口地

址的,编译器会自动处理。

m表示工作寄存器组,8051有四个工作寄存器组。中括号项可省略。

注意:

①中断函数不用调用,中断相应后直接进入;

②中断函数没有返回值;

③中断函数不能传递参数;

④中断函数中调用其它函数,要保证使用同一个寄存器组(就是工作寄存器

区)。

※中断函数是一个特殊的函数,没有参数,也没有返回值;但是程序中允不

允许使用return呢?答案是允许的,不过只能用"return;〃,不能用

"return(z);";用在一些需要快速返回的地方,对应的汇编会有多个RET语句,

相对效率会高一些。

Xusing的用法,using可以修饰任何函数,不过个人建议只用来修饰中断

函数;简单的说,''using〃会指定工作寄存器组,由于中断函数一般都是比较紧

急的事情,有时一条语句都会斤斤计较,所以使用using切换寄存器组可以省去

一些压栈的动作,由于51只有两级中断,同级中断不能被打断,因此,我们可

以同级中断设成同样的寄存器组,从某种意义上来说,有一组寄存器是多余的。

同时个人建议中断函数应该使用using这个关键字。

※中断函数也可以调用其它函数,但是一般很少这样使用,中断中调用的函

数最好不要被中断外的其它函数调用,因为会出现“重复调用”的警告,有时这

种调用是很致命的。中断调用了函数,会出现一些莫名其妙的问题,一些数据不

对。其实一般是因为汇编中使用了绝对寄存器引起的,有人说中断函数使用那个

寄存器组,被中断调用的函数就使用哪个寄存器组,我认为这样不好:这样会

增加额外的消耗,使用using会增加一下语句:PUSHPSWMOVPSW,#XX(压

栈保护)。推荐的方法有两种:

①使用"ttpragmaNOAREGS"禁止使用绝对寄存器

②使用“和ragmaRB(x)”来指定本文件的工作寄存器组

★内部函数和外部函数

根据函数能否被其它源文件调用,将函数分为内部函数和外部函数。

内部函数一一静态函数,只能被本文件中其它函数所调用

定义形式:

static类型标识符函数名(形参表)

如:staticintfun(inta,intb)

内部函数,其作用域仅限于定义它的所在文件。此时,在其它的文件中可以

有相同的函数名,它们相互之间互不干扰。

外部函数

能被其它文件中的函数所调用

定义形式:

[extern]类型标识符函数名(形参表)※定羲畤一般不是用extern,

在调用的文件中用extern迤行馨明就可以.

如:[extern]intfun(inta,intb)

省略extern,隐含为外部函数,调用此函数的文件中也要用extern声明所用

函数是外部函数。

用航巧山(16把文件2.C包含在文件1.C中,在文件1.C中定义的函数书写

在包含之后,所以仍然要在2.c中用extern声明;

七、变量的作用域、存储类型和存储器类型

1、作用域和生存期

C程序的标识符作用域有三种:局部、全局、文件。

1.1局部作用域

前面各个例子中的变量都是局部作用域,他们都是声明在函数内部,无法被

其他函数的代码所访问。函数的形式参数的作用域也是局部的,它们的作用范围

仅限于函数内部所用的语句块。

在符合语句中定义的变量值在符合语句中起作用。

在内部定义的变量就是局部变量,不能用extem进行声明。

1.2.全局作用域

对于具有全局作用域的变量,我们可以在程序的任何位置访问它们。当一个

变量是在程序的开头(所有函数之前),且在所有函数的外部声明(定义),那么

这个变量就是全局变量。如果不是在程序的开头,而自其它位置,但也在所有函

数之外定义,这就是外部变量,其之后的函数能使用,前面的不能使用,前面

的要使用就的在前面声明,若在函数内声明,只有本函数能使用,定义之前的其

它函数不能使用,在外部声明,声明之前还是不能用。若是后边定义,程序开头

声明,所有的函数都能用(就是全部局变量)。因此外部变量和全局变量的含义

还是有区别的。

在外部定义的才是外部变量,可以用extern声明为全局变量,或扩展作用

范围。外部变量是以静态存储方式存储在静态存储区中,编译时只赋一次初值(定

义时的赋初值),不赋初值自动赋0

1.3.文件作用域

在很多C语言书上,都没有说明文件作用域,或者只是略微的提到,其实文

件作用域在较大程序中很有作用(在多文件系统中)。文件作用域是指外部标识符

仅在声明它的同一个转换单元内的函数汇总可见。所谓转换单元是指定义这些变

量和函数的源代码文件(包括任何通过#include指令包含的源代码文件)。如:

staticintnum;

staticvoidadd(int);

main()

(

scanf(%d,&num);

add(num)

printf(%d\n,num);

)

voidadd(num)

(

num++;

)

上面的程序中变量num和函数add()在声明是采用了static存储类型修饰

符,这使得它们具有文件作用域,仅在定义它们的文件内可见。

由于我们提到的大多数程序都只有一个编译文件组成,所以这种写法没有实

际意义。但是实际工程上的文件有很多,它们不是由一个人写成的,由很多人共

同完成,这些文件都是各自编译的,这难免使得某些人使用了一样的全局变量名,

那么为了以后程序中各自的变量和函数不互相干扰,就可以使用static修饰符,

这样在连接到同一个程序的其他代码文件而言就是不可见的。

外部变量的声明既可以在引用它的函数的内部,也可以在外部。如果变量声

明在函数外部,那么同一转换单元内的所有函数都可以使用这个外部变量。反之,

如果在函数内部,那么只有这一个函数可以使用该变量。

前面说了文件作用域的问题,如果在声明全局变量时,加上static修饰符,

那么该变量只在当前文件内可见,而extern又可以引用其它文件里的变量。所

以在一个大型程序中,每个程序员只是完成其中的一小块,为了让自己的变量不

让其他程序员使用,保持一定的独立性,经常在全局变量前加static。我们可

以这样来说明一下:

注意:static声明的变量一般认为是在编译时分配存贮存间,其实应该理解

为程序规定死了该变量的地址,在程序初始化时(包含复位)开辟出来,在整个

程序运行的过程能中都不释放。static声明的变量一般是在编译的时候赋初值

的,即只赋值一次。在程序初始时已经有初值,则以后每次调用的时候不再重新

赋值,如果定义时候不赋值,则编译的时候自动赋值为0,static变量只初始化

一次,无论是在循环语句中,还是在递归中,这里的初始化是指定义同时的初始

化,定义后,再赋初值,不认为是初始化。Auto变量在函数结束或循环语句结

束(循环语句内部变量)空间释放,在调用时重新初始化。

注解:就是说在工程文件中,一个程序有很多文件组成,各文件分开各自编

译,然后连接成一个执行文件(.exe文件),所以用static声明的变量和函数只在

各自的文件内起作用,不在分开编译的其它文件中起作用,但用include包含进

来的文件是统一编译的,所以是一个文件。

*用extern声明的外部变量只能只同一个工程文件内部分开编译的各文件之

间起作用,不同的工程文件毫不相关的文件是不起作用的。在同一工程文件中一

个文件要用另外一个文件中的全局变量,就的在引用文件中用extern声明.

在C语言中,是先定义后使用,有先后性和位置性。如:用二田山(16把文

件2.c包含在文件l.c中,在文件Lc中,^include“2.c"inta;因为

#include"2.c"在前,inta;在后,所以仍然要在2.c中声明externinta;

注意:在用extern声明变量只是扩大它的作用范围,变量声明时可省略数据类

型,但最好不要省。

■全局变量和局部变量同名冲突时,在局部变量的作用范围内全局变量被屏

蔽不起作用,局部变量起作用。

2.寄存器存储类型

使用寄存器存储类型的目的是让程序员指定某个局部变量存放在计算机的

某个硬件寄存器里而不是内存中,以提高程序的运行速度。不过,这只是反映了

程序员的主观意愿,编译器可以忽略寄存器存储类型修饰符。

寄存器变量的地址是无法取得的,因为绝为多数计算机的硬件寄存器都不占

用内存地址。而且,即使编译器忽略寄存器类型修饰符把变量放在可设定地址的

内存中,我们也无法取地址的限制仍然存在。

long,double,float不能设为register型,因为超过寄存器长度。

3、存储器类型(在C51中,其它系列单片机不一定适合)

C51中:[存储种类]数据类型[存储器类型]变量名表

在定义格式中除了数据类型和变量名表是必要的,其它都是可选项。存储种

类有四种:自动(auto),外部(extern),静态(static)和寄存器(register

缺省类型为自动(auto)。

而这里的数据类型则是和我们在第四课中学习到的名种数据类型的定义是

一样的。说明了一个变量的数据类型后,还可选择说明该变量的存储器类型。存

储器类型的说明就是指定该变量在单片机C语言硬件系统中所使用的存储区域,

并在编译时准确的定位。表6—1中是KEILuVision2所能认别的存储器类型。

注意的是在AT89c51芯片中RAM只有低128位,位于80H到FFH的高128位则在

52芯片中才有用,并和特殊寄存器地址重叠。特殊寄存器(SFR)的地址表请看

附录二AT89c51特殊功能寄存器列表

存储器类型说明

data直接访问内部数据存储器(128字节),访问速度最快

bdata可位寻址内部数据存储器(16字节),允许位与字节混合访问

idata间接访问内部数据存储器(256字节),允许访问全部内部地址

pdata分页访问外部数据存储器(256字节),用MOVX@Ri指令访问

xdata外部数据存储器(64KB),用MOVX@DPTR指令访问

code程序存储器(64KB),用MOVC@A+DPTR指令访问

上表是存储器类型的关键字。

关于存储器类型一般放在变量名前:如autointdatai;但是要定义指针

变量本身的存储器类型时一般放在最前面或放在*和变量名之间。如:dataint*p;

或int*datap;以上是定义指针本身存在data区,*和data之间没有空格;

下面:intdata*datap;本句的意思是指针和指针指向的数据都在data区。

一般C51编译器不允许在BDATA区域声明float和double。

对于C语言这样的高级语言一般编程不考虑存储器类型,就是不管变量存

在哪个区域,所以C语言中不涉及存储器类型,但是对于针对单片机的编程需要

知道变量存在什么地方,就是存储器类型。所以C51中有存储器类型。

4、存储模式(在C51中,其它系列单片机不一定适合)

存储模式决定了没有明确指定存储类型的变量、函数参数等的缺省存储区

域,(没有说明的存储器类型就由存储模式决定)共三种:

Small模式

所有缺省变量参数均装入内部RAM(若内部空间不够,才会存到外部RAM),

优点是访问速度快,缺点是空间有限,只适用于小程序。和使用data指定存储

器类型是一样。

Compact模式

所有跳省变量均位于夕卜部RAM区的一页(256Bytes),具体哪一页可由P2口

指定(通过程序指定),在STARTUP.A51文件中说明,和使用pdata指定存储器类型

是一样,优点是空间较Small为宽裕,速度较Small慢,较large要快,是一种

中间状态。

large模式

所有缺省变量可放在多达64KB的外部RAM区,优点是空间大,可存变量多,

缺点是速度较慢。和使用Xdata指定存储器类型是一样。

指定存储模式:由smallcompact及large说明。

每个编译器都有自己的默认存储模式,要说明本程序的存储模式使用

“#pragma模式(smallcompact及large)“如#pragmasmall。

例如:voidfunl(void)small{}

提示:small说明的函数内部变量全部使用内部RAM(若内部空间不够,才会存

到外部RAM)。关键的经常性的耗时

的地方可以这样声明,以提高运行速度。

在那个函数名后声明就只在本函数内有作用,之外无效,还是默认的存储

模式,若本函数内部变量又特别声明存储类型,那该变量遵循内部声明。

5、绝对地址访问

C51提供了三种访问绝对地址的方法:

5.1.绝对宏:在absacc.h文件中定义的,必须包含进来才能使用。

在程序中,用“#in序ude〈absacc.h>”即可使用其中定义的宏来访问绝

对地址,包括:CBYTE(程序区以字节形式)、XBYTE(外部XDATA区以字节形式)、PBYTE

(外部PDATA区以字节形式)、DBYTE(内部DATA区以字节形式)、PWORD(外部PDATA区以

字形式)、CWORD(程序区以字形式)、XWORD(外部XDATA区以字形式)、DWORD(内部DATA

区以字形式)。

具体使用可看一看absacc.h便知

例如:

rval=CBYTE[0x0002];指向程序存贮器的0002h地址

rval=XWORD[0x0002];指向外RAM的0004h地址

5.2._at_关键字

直接在数据定义后加上_at_const(常量地址)即可,但是注意:

(1)绝对变量不能被初使化;

(2)bit型函数及变量不能用_at_指定。

例如:

idatastructlinklist_at_0x40;指定list结构从40h开始。(idata区)

xdatachartext[25b]_at_0xE000;指定text数组从0E000H开始(xdata区)

提示:如果外部绝对变量是I/O端口等可自行变化数据,需要使用

volatile关键字进行描述,请参考absacc.h。

一般C语言只定义变量,不给具体的地址。以上都是给变量规定了绝对地

址。

5.3.连接定位控制

此法是利用连接控制指令codexdatapdatadatabdata对“段"地址

进行,如要指定某具体变量地址,则很有局限性,不作详细讨论。

八、函数递归

1.栈

栈是一个后进先出的压入(push)和弹出(pop)式数据结构。

程序员经常利用栈这种数据结构来处理那些最适合用进先后出逻辑来描述

的编程问题。这里讨论的程序中的栈在每个程序中都是存在的,它不需要程序员

编写代码去维护,而是由运行是系统自动处理。所谓的系统自动维护,实际上就

是编译器所产生的程序代码。尽管在源代码中看不到它们,但程序员应该对此有

所了解(转成汇编语言看的见)。

当一个函数(调用者)调用另一个函数(被调用者)时,运行时系统将把调用者

的所有实参和返回地址压入到栈中,栈指针将移到合适的位置来容纳这些数据。

最后进栈的是调用者的返回地址。当被调用者开始执行时,系统把被调用者的自

变量压入到栈中,并把栈指针再向下移,以保证有足够的空间存储被调用者声明

的所有自变量。当调用者把实参压入栈后,被调用者就在栈中以自变量的形式建

立了形参。被调用者内部的其他自变量也是存放在栈中的。由于这些进栈操作,

栈指针已经移动所有这些局部变量之下。但是被调用者记录了它刚开始执行时的

初始栈指针,以他为参考,用正或负的偏移值来访问栈中的变量。当被调用者准

备返回时,系统弹出栈中所有的自变量,这时栈指针移动了被调用者刚开始执行

时的位置。接着被调用者返回,系统从栈中弹出返回地址,调用者就可以继续执

行了。当调用者继续执行时,系统还将从栈中弹出调用者的实参,于是栈指针回

到了调用发生前的位置。

注释:就是说主调函数的实参都压入堆栈,其它的变量仍然保存在原内存中,

被调函数的变量和形参都进栈,在栈中进行实参和形参之间的传递。

2、递归

递归的意思就是函数自己调用自己本身,或者在自己函数调用的下级函数中

调用自己。

递归之所以能实现,是因为函数的每个执行过程都在栈中有自己的形参和局

部变量的拷贝,这些拷贝和函数的其他执行过程毫不相干。这种机制是当代大多

数程序设计语言实现子程序结构的基础,是使得递归成为可能。

程序员需保证递归函数不会随意改变静态变量和全局变量的值,以避免在递

归下降过程中的上层函数

温馨提示

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

评论

0/150

提交评论