无论是51、arduino还是stm32,在大学期间常用的单片机都是以C语言为基础的,这里列出C语言基础的一些语句函数,希望可以帮助初学者。
目录
一、语言基础
1.1、C语言概述
1.2、C语言语法基础
1.3、 C语言的数据类型
1.4 C语言运算符与表达式
二、数据的输入和输出
2.1 字符输入输出函数
2.2 格式输入输出函数
三、选择结构程序设计语句
3.1 if语句
3.2 switch语句
四、循环结构程序设计
4.1 while语句
4.2 do...while语句
4.3 for语句
4.4 循环体结构的嵌套
4.5 break语句和continue语句在循环结构中的应用
4.6 综合实例:
五、函数与编译预处理
5.1 函数概述
5.2 函数的定义和说明
5.3 函数的调用
5.4 函数的参数
5.5 函数的嵌套调用和递归调用
5.6 变量的存储类型
5.7 内部函数和外部函数
5.8 编译预处理命令
#include
#include
int main()
{
int num1.num2;
num1=1;num2=2;
printf(“num1+num2=%d”,num1+num2)
}
(1)头文件: #include语句是预处理指令,并不是执行语句,制定了程序引用的头文件(调用库功能)。
(2)主函数:包括一个main()函数。int表示main()返回一个整数,void表示main不接受任何参数。
(3)函数结构:任何函数,包括主函数main(),都由函数说明和函数体,两部分组成
函数类型 函数名(参数表)
{
函数体(执行语句)
}
符号//之后是程序的注释信息,仅在一行内有效
/*和*/可跨越多行进行注释
(1)字符集
C语言具有4类字符集:
英文字母:大小写各26,共52个;
阿拉伯数字:0-10,共10个;
下划线:_ ;
特俗符号:由1-2个字符组成,如:+、-、*、/、%、++、--、<、>、=、>=、<=、==、!=、&&、||、&、|、~、^、>>、<<、()、[]、{}、?:、.、,、:
除字符串和注释外,C语言程序只能用字符集中的字符来书写。
(2)标识符:标识变量名、符号常量名、函数名、类型名、文件名等的有效字符序列,
C语言标识符只能有=由字母、数字、和下划线组成,且第一个字符必须为字母或者下划线。
(3)关键字:具有特定含义的标识符,C语言中的关键字都用小写字母表示,共有32个;
auto、break、case、char、const、continue、default、do、double、else、enum、extern、float、for、goto、if、int、long、register、return、short、signed、sizeof、static、struct、switch、typedef、union、unsigned、void、volatile、while
(4)常量:程序运行过程中,其值不能改变的量
(5)变量:程序运行过程中其值可以改变的量
1.3.1、 C语言的数据类型
1、整型变量:不含小数部分的数值型数据;由机器中数据的长度分为基本整型(int)、短整型(short)和长整型(long);同样整型长度的数据又分为无符号(unsigned)和有符号(signed)
其中long int、short int、unsigned int中的关键字int可以省略。
关键字 |
字节数 |
取值范围 |
char |
1 |
-128~127 即-2^7~(2^7-1) |
unsigned char |
1 |
0-255 即0~(2^8-1) |
short[int] |
2 |
-32768~32767 即-2^15~(2^15-1) |
unsigned short[int] |
2 |
0~65535 即0~(2^16-1) |
int |
4 |
-2147483648~2147483647 即-2^31~(2^31-1) |
unsigned [int] |
4 |
0~4294967295 即0~(2^32-1) |
long [int] |
4 |
-2147483648~2147483647 即-2^31~(2^31-1) |
unsigned [int] |
4 |
0~4294967295 即0~(2^32-1) |
2、整形常量:十进制数值表示方法与数学上相同;八进制数值在数码前面加数字0;十六进制在数码前面加0x
1.3.2 实型数据
1、实型变量:带有小数点的数,也称为浮点数。实行数据有:单精度实数(float)、双精度实数(double)、长双精度实数(long double)
关键字 |
字节数 |
精度 |
float |
4 |
6~7 即(-3.4x10^-38)~3.4x10^38 |
double |
8 |
15~16 即(-1.7x10^-308)~1.7x10^308 |
long double |
16 |
18~19 即(-1.2x10^-4932)~1.2x10^4932 |
double型变量的值的最大有效位数通常是float型的两倍。
2、实型常量:一般不分float和double,任何一个实型数据既可以赋给float变量,又可以赋给double变量
(1)十进制小数形式:由数字和小数点组成,如3.1415、-6.5、.3
(2)指数形式:3.12E-6表示3.12x10^-6(十进制小数+e或E+十进制整数三部分组成);e或E前必须有数字,e或E后必须是整数;精度又称为有效位由于float的精度为7,前7位有效,0.1234567E+8
1.3.3 字符型数据
1、字符型变量:字符型变量用来存放一个字符,占用一个字节,
如:char c1,c2;
2、字符型常量:一种是一对单引号括起来的一个字符如’A’表示大写字母A,’ ‘表空格;另一种是单引号括起来的由反斜杠(\)引导的一个字符或一个数字序列
字符形式 |
功能 |
\n |
换行 |
\t |
制表字符 |
\b |
退格 |
\r |
回车 |
\\ |
反斜杠字符 |
\’ |
单引号字符 |
\” |
双引号字符 |
\ddd |
1~3位八进制数表示的字符 |
\xhh |
1~2位16进制表示的字符 |
3、字符串常量:用双引号括起来的字符序列,“guoqing”C语言中每个字符串都是以’\0’为结束标记的;不能将字符串常量赋给一个字符变量。每一个字符均以ASCII码存放,最后一个为空字符(二进制00000000,纪委null或\0,”a”为两个字节)
类型 |
示例 |
算术运算符 |
+、-、*、/、% |
关系运算符 |
>、<、==、>=、<=、!= |
逻辑运算符 |
!、&&、|| |
位运算符 |
<<、>>、~、|、^、& |
赋值运算符 |
= |
条件运算符 |
?、: |
逗号运算符 |
, |
指针运算符 |
*、& |
求字节数运算符 |
sizeof |
强制类型转换运算符 |
(类型) |
分量运算符 |
.(点)、-> |
下标运算符 |
[ ] |
其它 |
函数调用运算符() |
1.4.1 算术运算符和算术表达式
1、算术运算符:+、-、*、/、%(其中+、*、/为双目符即要求两个操作数,-为单目符可仅需一个操作数取负值)
(1)两个操作数都是整型运算结果为整型,只要有一个是实型结果为实型。如果被除数或除数有一个是负值舍入方向不确定,多数机器结果向零取整,如-5/3=-1(余-2),少数结果为-2(余1)
(2)求余运算要求两个操作数都是整数
(3)*、/、%优先级高于+、-运算,算术运算符的结合顺序为从左至右
2、自增运算符与自减运算符:++、--(自增与自减操作数只能是变量,且只能是整型或字符型也可用于指针变量)
(1)将自增自减运算符放在变量前面,其作用先给变量加一或减一,然后再使用该变量值:x=++a为a=a+1,;x=a;
(2)将自增或自减放在变量的后面,其作用是先使用该变量的值,然后再给变量加一或减一:x=a++为 x=a; a=a+1;
(3)自增或自减的结合方向为从右向左,这与运算符的运算方向相反,如-i++意为-(i++)
1.4.2 赋值运算
1、赋值运算符:”=”,将一个数据或一个表达式的值赋给一个变量;
2、赋值类型转换:左右两边变量类型不一致,但都是数值型或字符型时,全将赋值运算符右边的值类型自动转换成与左边相同的类型;
3、必须把复合赋值符右边的表达式看成一个整体,先求右边表达式值,再和左边的变量做相关运算:a+=x+y*3为a=a+(x+y*3);
1.4.3 关系运算符
1、关系运算符(六个):<(小于) <=(小于等于) >(大于) >=(大于等于) ==(等于) !=(不等于);
2、优先级:==和=!同级,前四个优秀级高于后两个,算术运算符的优先级高于关系运算符的优先级,关系运算符高于赋值运算符:a=b
C语言本身没有提供输入和输出语句,C语言编译系统中的stdio.h头文件包含了标准输入和输出有关变量的定义及相应的宏定义
字符输入函数是getchar,函数原型:int getchar(void); 函数功能:从设备输入一个字符,函数的返回值是该字符的ASCII码值
字符输出函数putchar 函数原型:int puchar(int);
示例程序:
#include
main()
{
int i=97,j;
char ch=’a’;
j=getchar();
putchar(i);
putchar(j);
putchar(‘\n’);
putchar(ch);
}
输入b,结果为
ab
a
字符串输入函数gets(s); 字符串输出函数:puts(s);
#include
int main(void)
{
char s[5];
gets(s);
puts(s);
}
输入abcde 输出abcde
scanf是具有格式控制输入的函数,函数格式:scanf(“格式控制字符串”,地址表);
格式控制字符串必须用英文状态的双引号括起来,主要由%和格式字符组成,作用是将输入的数据转换成指定的格式后存入地址表所指向的变量中。
d 用来输入十进制整数、c用来输入单个字符、s用来输入字符串、f用来输入实数
需注意:
(1)格式控制字符串中除格式字符外没有其他字符,两个数据之间允许以一个或多个空格隔开,也可以按回车键、跳格键隔开;
如:scanf(“%d%d%d”,&a,&b,&c);
下面输入数据的方式都是正确的
13 1 23
13
1
23
对于:scanf(“x=%d,y=%d,z=%d”,&x,&y,&z);
输入为:x=123,y=456,z=789
(2)可以指定scanf函数输入数据所占的宽度,系统将自动按指定宽度来截取数据。
scanf(“%3d%4d%3d”,&x,&y,&z) 输入1234567890,则x为123,y为4567,z为890
(3)格式字符’%’后面使用‘*’时,表示该对应的数据被禁止使用
scanf(“%3d%*4d%3d”,&x,&y,&z); 输入1234567890 x为123,4567呗跳过,890赋给y
(4)用scanf()输入实数,格式说明符为%f,但不能规定精度
(5)格式字符串必须在地址表中有一个变量与之对应,并且格式格式字符必须与相应变量一致,如果输入不一致,scanf()将停止处理,其返回值为0,若为%c格式,空格字符和转义字符都作为有效字符输入
格式输出函数printf可用来输出任何类型数据,而且可以同时输出多个不同类型的数据,一般调用形式为: printf(“格式控制字符串”,输出表);
1、单分支if语句 格式:
if(表达式)
语句;
表达式为真,执行语句,表达式为假,不执行语句。
2、双分支if else语句:一般格式:
if(表达式) 语句1;
else(表达式) 语句2;
如果表达式的值为真(非0)执行表达式1,如果表达式值为假(0),执行语句2;
#include
main()
{
float score;
printf(“input score\n”);
scanf(“%f”,&score);
if(score>=60)
printf(“pass\n”);
else
printf(“fall!\n”);
}
3、多分支选择语句
一般格式:
if(表达式) 语句1;
else if(表达式2) 语句2;
else if
....
else 语句n+1;
首先求表达式1的值,如果值为真(非0),则执行语句1,后面的语句不再执行,if语句结束,否则再求表达式2的值,如果为真,则执行语句2
#include
main()
{
float socre;
printf(“input score:”);
scanf(“%f”,&score);
if(score>=90)
printf(“A\n”);
else if(score>=80)
printf(“B\n”);
else if(score>=70)
printf(“C\n”);
else if(score>=60)
printf(“D\n”);
}
4、if语句嵌套:如果if(表达式)或else后面的语句又包含一个或多个if语句,就称为if语句的嵌套。
if语句两层嵌套结构如下:
if(表达式1)
if(表达式1_1) 语句1_1;
else 语句1_2;
else
if(表达式2_1) 语句2_1;
else 语句2_2;
#include
main()
{
int x,y;
printf(“input x:”);
scanf(“%d”,&x);
if(x<=0)
if(x<=-10)
y=x+2;
else
y=x-2;
else
if(x<=10)
y=x*2;
else
y=x/2;
printf(“y=%d”,y);
}
注意:
(1)if后面的表达式,一般为逻辑表达式或关系表达式,可以为任何数据类型(整型、实型、字符型、指针数据型)
(2)对于双分支if else语句,else语句不能单独使用,它必须和if子句配合使用
(3)如果if或else后面包括多条语句,需要将这多条语句用{ }括起来构成复合语句
(4)if与else配对原则:从最内层开始,else总是与它上面相距最近且尚未配对的if配对。
3.2.1、switch一般形式:
switch(表达式)
{
case 常量表达式1: 语句1;
case 常量表达式2: 语句2;
......
case 常量表达式3: 语句3
default : 语句n+1;
}
计算表达式的值,并逐个与其后的常量表达式值做比较,当值与某个常量表达式的值相等时,即执行其后的语句,然后不再进行判断,继续执行后面所有case后的语句。如果表达式的值与所有case后的常量表达式均不相同,则执行default后的语句。
#include
main()
{
int x;
scanf(“%d”,&x);
switch(x)
{ case 1:printf(“Spring\n”);
case 2:printf(“Summer\n”);
case 3:printf(“Autumn\n”);
case 4:printf(“Winter\n”);
}
}
在程序中输入1,则输出结果为
Spring
Summer
Autumn
Winter
3.2.2、break语句
为了实现在执行满足条件的语句后就使流程跳出switch结构,break语句可以达到这个目的。
switch(表达式)
{ case 常量表达式1: 语句1; break;
case 常量表达式2: 语句2; break;
.......
case 常量表达式n: 语句n; break;
default ; 语句n+1;
}
#include
main()
{
int x;
switch(x)
{ case 1:printf(“Spring\n”); break;
case 2:printf(“Summer\n”); break;
case 3:printf(“Autumn\n”); break;
default:printf(“Winter\n”); break;
}
}
例程:输入两个整数和运算符,要求计算结果。
#include
void main()
{
int num1,num2;
char sign;
printf(“input expression:\n”);
scanf(“%d%c%d”,&&num1,&sign,&num2);
switch(sign)
{
case ‘+’:printf(“%d\n”,num1+num2); break;
case ‘-’:printf(“%d\n”,num1-num2); break;
case ‘*’:printf(“%d\n”,num1*num2); break;
case ‘/’:printf(“%d\n”,num1/num2); break;
default:print(“input error\n”);
}
}
注意:
(1)case后的各常量必须各不相同,且必须是整数或者字符型;
(2)case后允许有多个语句,可以不用{ }括起来;
(3)case子句出现的顺序不会影响运行结果;
(4)如果多种情况需公用一组执行语句,可用case的常量表达式将多种情况列出,在最后一种情况之后安排需要执行的语句。
switch(grade)
{ case 9:
case 8: printf(“Good\n”);
case 7:
case 6: printf(“pass\n”);
default: printf(“Fail\n”);
}
grade的值为9或8时,输出good!;grade的值为7或6时,输出Pass!;grade的值不是9/8/7/6中的任何一个时,输出Fail!
一般表达式:
while(表达式)
循环体结构;
如果表达式的值非0,就执行循环体语句,然后再计算表达式的值,由表达式的值决定是否再次执行循环体语句,直到表达式的值为0时推出循环。
例程:
#include
main()
{
float score,sum=0,ave=0;
int count=0;
scanf(“%f”,&score);
while(score>=0)
{ sum=sum+score;
count++;
scanf(“%f”,score);
}
if(count!=0) ave=sum/count;
printf(“ave=%.2f\n”,ave);
}
编写程序,判断一个数是否为素数
#include
main()
{
int m,i=2;
scanf(“%d”,&m);
while(m%i!=0&&i<=m-1)
i++;
if(i==m)
printf(“%d is prime\n”,m);
else
printf(“%d isn’t prime\n”,m);
}
一般格式:
do
循环体结构;
while (表达式);
先执行一次循环体语句,然后计算表达式,如果表达式的值非0,则重复执行一次循环体语句,直到表达式的值为0时推出循环,语句结束。do...while的特点是先执行再判断。
例程:
#include
main()
{
float score,sum=0,ave=0;
int count=0;
scanf(“%f”,&score);
if(score>=0)
do
{ sum=sum+score;
count++;
scanf(“%f”,score);
} while(score>=0);
if(count!=0) ave=sum/count;
printf(“ave=%.2f\n”,ave);
}
注意:
(1)while中的表达式通常是关系表达式或逻辑表达式,表达式的值可以为任意值;
(2)循环体中必须要有能使循环体结束的语句,否则形成无限循环;
(3)while语句先判断表达式的值,若表达式的值一开始就为0,循环体语句一次都不执行;do...whilw语句的循环体至少执行一次。
例程:根据公式s=1=1/(1+2)+1/(1+2+3)+1/(1+2+3+4)+...+1/(1+2+3+4+..+n)
#include
main()
{
int n,k,t=0;
double s=0.0;
printf(“\n please enter n:”);
scanf(“%d”,&n);
k=1;
do
{ t=t+k;
s=s+1.0/t;
k++;
} while(K<=n);
printf(“\n The result is %lf\n”,s);
}
for语句不仅适用于循环次数确定的情况,还适用于循环次数不确定而只给出循环结束条件的情况。
一般格式:
for(表达式1;表达式2;表达式3;)
循环体语句;
首先求表达式1的值,然后求表达式2的值,如果表达式2的值为真(非0),则执行循环体语句,接着求表达式3的值,然后再求表达式2的值,如此反复,直达表达式2的值为0;
例程:
#include
main()
{
int i,n;
long fac;
scanf(“%d”,&n);
fac=1;
for(i=1;i<=n;i++)
fac=fac*i;
printf(“\n%d!=%ld\n”,n,fac);
}
说明:
(1)如果在for语句之前给循环体变量赋了初值,则表达式1可以省略,但其后的分号不能省略,如果3个表达式都省略,循环体中的语句就会无限次循环,即死循环;
(2)逗号表达式在for语句中的运用。for语句中的表达式1和表达式3都可以使用逗号表达式,特别是在两个循环变量控制控制循环的情况下,例如:
int i,j,sum=0;
for(i=1,j=100;i<=j;i++,j--)
sum=sum+i+j;
例程:输入一个整数n,计算n(包括n)以内能被5或7整除的所有自然数的倒数之后并输出。
#include
main()
{
int i,n;
double sum=0.0;
printf(“\nInput n: “);
scanf(“%d”,&n);
for(i=1;i<=5;i++)
if(i%5==0||i%7==0)
sum=sum+1.0/i;
printf(“sum=%lf\n”,sum);
}
在循环体语句中又包含一个完整的循环体结构,称为循环体结构的嵌套。
例程:编写程序输出如下图形:
*
**
***
****
*****
#include
main()
{
int i,j;
for(i=1;i<=5;i++)
{
for(j=1;j<=1;j++)
printf(“*”);
printf(“\n”);
}
}
例程:编写程序求100~1000之间素数的个数。
#include
main()
{
int m,i,k,count=0;
for(m=101;m<1000;m+=2)
{
i=2;
while(m%i!=0&&i<=m-1)
i++;
if(i==m)
count++
}
printf(“The prime number is %d\n”);
}
一般格式:、break;
break语句是限定转向语句。break语句常在switch语句和循环结构中出现。在循环结构中通常与if语句在一起使用,一旦瞒住条件就使程序立即退出循环体结构,转而执行循环结构后面的语句。
例程:从键盘上连续输入数据,如果是正数,则累加;如果是负数,则程序结束。
#include
main()
{
int x;
long sum=0;
for( ; ; )
{ scanf(“%d”,&x);
if(x>=0) sum+=x;
else break;
}
printf(“sum=%ld\n”,sum);
}
2、continue语句
一般格式:
continue;
continue语句被称为继续语句。continue一般也与if语句一起使用,一旦条件成立,就跳过循环体中continue语句之后的语句段,提前结束本次循环体的执行,接着进行下一次循环条件的判断。
例程:编写程序输出100~200之间能被7整除的数。
#include
main()
{ int n;
for(n=100;n<=200;n++)
{ if(n%7!=0;
continue;
printf(“%5d”,n);
}
printf(“\n”);
}
break语句和continue语句的区别是,一旦条件满足,break语句则结束整个循环,不再进行循环条件的判断;而continue语句只是结束本次循环,接着进行下一次循环条件的判断。
break语句:
for(n=1;n<=10;n++)
{
if(n==5)
break;
printf(“%3d”,n);
}
运行结果:1 2 3 4
continue语句:
for(n=1;n<=10;n++)
{
if(n==5)
continue;
printf(“%3d”,n);
}
运行结果:1 2 3 4 6 7 8 9 10
编写程序输出所有的水仙花数:水仙花数是指3位数的各位数字的立方和等于这个数本身
#include
main()
{ int unit,ten,hundred,n;
for(n=100;n<1000;n++)
{
hundred=n/100;
ten=n/10-hundred*10;
unit=n%10;
if(n==unit*unit*unit+ten**ten*ten+hundred*hundred*hundred)
printf(“%6d”,n);
}
}
编写程序求解百钱买百鸡问题:鸡翁一值钱五,鸡母一值钱三,鸡雏三值钱一。
设变量a、b分别代表鸡翁、母鸡的个数,鸡雏的个数为100-a-b。a在0~19,b在0~33。
#inlcude
main()
{
int a,b,c;
for(a=0;a<=19;a++)
for(b=0;b<=33;b++)
{ c=100-a-b;
if(5.0*a+3.0*b+c/3.0==100)
printf(“a=%d,b=%d,c=%d\n”,a,b,c);
}
}
C语言源程序是由函数组成的。前面的程序大都只有一个主函数main(),在C语言程序设计中,通常将一个较大程序分成几个功能单一的子程序模块,用函数来实现每个子程序的1功能。C语言程序由一个或多个函数构成,其中有且只有一个名为main()的主函数,C语言总是从main()函数开始执行,最后在main()函数结束整个程序的运行。
(1)一个C源程序必须有且只有一个主函数main()。C程序总是从main()函数开始执行,调用其他函数后总是回到main()函数,最后在main()函数中结束整个程序;
(2)一个C程序由一个或多个源文件组成——可分别编写、编译和调试;
(3) 一个源文件由一个或多个函数组成,可为多个C程序公用;
(4)C语言是以源文件为单位而不是以函数为单位编译的;
(5) 在C语言中,所有函数(包括主函数)。一个函数的定义,可以放在程序中的任意位置,主函数main()之前或之后。但在一个函数的函数体内,只能调用其他函数,不能再定义另一个函数,即不能嵌套定义;
(6)主函数名main()是系统定义的,是运行时首先调用的函数,它可以调用其他函数,但不能被其他函数调用了其他函数间可以相互调用,也允许嵌套调用。习惯上把调用者称为主调函数。函数可以自己调用自己,称为递归调用;
(7) 函数定义上看可分为库函数和用户定义函数两种:
1 库函数:由C系统提供,用户无需定义,只需在程序前写出该函数原型的头文件即可;
2 用户定义函数:用户按需编写的函数,不仅要在程序中定义函数本身,而且还必须在主函数模块中对该被调函数进行类型说明,才能使用;
(8)从主调函数与被调函数之间数据传送角度来看,函数又分为无参函数和有参函数两种。
对库函数一般调用形式
函数名(参数表)
例程:
#include
#include
main()
{
double a,b;
scanf(“%lf”,&a);
b=sin(a);
printf(“6.4lf”,b);
}
include命令必须以#开头,系统提供的文件以.h为后缀,文件名用一对双引号或者一对尖括号<>括起来,二者的区别是:
注意:include是命令,不是语句,结尾没有分号。
5.2.1 函数的定义格式】
[函数返回值的类型名] 函数名([类型名 形式参数1,类型名 形式参数2,...])
/*函数首部*/
{
[说明部分;] /*函数体*/
[语句部分;]
}
其中[]内为可选项。
注意:函数名、一对圆括号和花括号不能省略。
1、无参数的一般形式
无参函数的一般形式为:
函数返回值的类型名 函数名(void)
{ [说明语句部分;]
[可执行语句部分;]
}
新标准中,函数不可以默认参数表,如果不需要参数,则用void表示,主函数main()除外。
例程:构造一个输出一行“*”的函数
void printstar()
{ printf(“***********\n”);
}
2、有参函数的一般形式
有参函数的一般形式为:
函数返回值的类型名 函数名(数据类型 参数[数据类型 参数2...])
{ [说明语句部分;]
[可执行语句部分;]
}
有参比无参多了一个参数表。将函数定义中的参数表称为形式参数表
例程:构造一个求两个双精度数之和的函数。
double add(double x,double y)
{ double s;
s=x+y;
return s;
}
注意:
(1) 函数名和形式参数都是用户命名的标识符。同一程序中,函数名必须唯一;形式参数只要在同一函数中唯一即可,可以与其他函数的变量同名;
(2)C语言规定,不能在一个函数的内部再定义函数
对函数类型名的说明,必须与return语句中返回值表达式的类型一致。如果不一致,则以函数类型为准,由系统自动进行转换,如果省略函数标准类型,系统一律按照int类型处理。
例程:求两个数中的大数(例中函数调用时使用了类型转换)
#include
max(float x,float y)
{
float z;
z=x>y?x:y;
return z;
}
main()
{
float a=1.5,b=0.5;
float c;
c=max(a,b);
printf(“max is %f\n”,c);
}
运行结果: max is 1.000000
C语言中函数补充内容:
(1) 空函数:无参数且函数体为空的函数,一般形式为:
[函数类型] 函数名(void) {}
例如:
dump() {}
(2) 带参数的形式参数表中类型和变量必须成对出现,如下定义是错误的:
double add(double x,y)
5.2.2 函数的返回值
函数的返回值就是return返回语句中的表达式的值。
return语句格式:
return(表达式); 或 return 表达式; 或 return;
功能:
(1) 把return后面的表达式的值返回给调用函数
(2)把控制转向调用函数
例如:return(x); return x+y; return; 都是合法的
注意:void型的函数中不能包括带值得return语句;主体函数体内不能出现return语句。
(1) 当函数没有return语句时,以结束函数的大括号作为返回点。这时的返回值是系统给的不确定值,】。假如为了明确表示不返回值,可以用void定义成无(空)类型;
(2)在同一函数中可根据需要在多处出现return语句,但函数第一次遇到return时就立即停止执行,并返回到主调函数。
例程:编写程序使两个字符s1和s2相等时,返回1,否则返回-1
int find_char(char s1,char s2)
{
if (s1==s2)
return 1;
return -1;
}
以下程序是合法的
#include
double add(double x,double y)
{
double s;
s=x+y;
return s;
}
main()
{
double a,b,c;
a=10;b=20;
c=add(a,b) /*1*/
printf(“%lf”,add(a,b)); /*2*/
add(a,b); /*3*/
}
在/*1*/中,add的返回值被赋予c,在/*2*/行中,返回值实际上没有赋给任何变量,单被printf所使用。在/*3*/行中,返回值被丢弃不用
5.2.3 函数的说明
1、函数说明也称为函数声明,是函数调用前的准备。在首次调用某函数前,使用函数说明语句使C语言编程程序了解函数返回值类型,这个信息对于程序能否正确运行影响极大,因为不同类型的数据有不同的长度和内部表示方法,在ANSI新标准中,采用函数原型方式,对被调函数进行说明,格式如下:
函数类型 函数名(数据类型[参数名1][,数据类型[参数名2]...]);
其中[]内为可选项。若没有参数,可用void明确说明。
函数说明语句就是函数定义中的函数首部加上分号,这些内容称为函数原型,例如:
float max(float x,float y);
等价于
float max(float,float);
函数说明的例子如下:
void srand(unsigned int seed);
函数srand 无返回值,仅有一个unsigned int类型的参数
说明:
(1) 函数说明可以是一条独立的语句,也可以与普通变量一起出现在同一个说明语句中,如:
float x,max(float,float);
(2)在函数名前没有说明函数类型时,默认为int类型,如int dump(int); 等价于dump(int);
2、函数说明的位置
(1)C语言规定,被调用函数的说明位置处在被调用前并且在所有函数的外部时,则后面所有外置上都可以对该函数进行调用。
(2)函数说明放在调用函数内的说明部分,这时其作用只在本调用函数内部。
例程:
#include
main()
{
float a=1.5,b=0.5;
float max(float,float);
float c;
c=max(a,b);
printf(“max is %f”,c);
}
float max(float x,float y)
{
float z;
z=x>y?x:y;
return z;
}
运行结果 max is 1.500000
一般形式为:
函数名([实际参数表])
[]内为可选项。当实际参数多于一个时,各实际参数间以逗号隔开。实际参数可以是常量、变量或表达式,如max(float x,float y)的调用可为max(2,3)、max(3+4.2,7*a)、max(a,b)等
调用时,首先计算实参表达式的值,然后把这些值按顺序一一传给对应的形参。当函数无参数时,实参列表就位空。
说明:
(1)调用函数时,函数名必须与具有该功能的自定义函数名完全一致;
(2) 实参的个数必须与所调用函数的形参个数相等;
(3)实参表中包括多个参数,对参数的求值顺序因系统而异,有的系统自左向右,有的系统则相反,VC6.0、Turbo C 和MS C是按自右向左的顺序进行
#include
main()
{
int a=1,b,f(int,int);
b=f(a,++a);
printf(“%d”,b);
}
int f(int x,int y)
{
int z;
if(x>y) z=1;
else
if(x==y) z=0;
else z=-1;
return(z);
}
运行结果0
在一个函数中调用另一个函数需要具备的条件
(1)被调函数已经存在(库函数或用户自定义)
(2)使用库函数或其他文件中的函数,应在文件开头用#include命令将有关编译处理信息包含到文本中。
(3)对被调用的函数进行说明
函数中的参数分为形参和实参
(1)形参与实参:调用函数时形参才分配内存单元,同时实参将数据传给实参,调用结束后形参所占内存单元被释放,实参单元保持原值;
(2)实参可以是变量、常量、表达式
(3)实参和形参应个数相等、类型系统
(4)调用函数时,主调函数和被调函数之间有数据传递关系,实参对形参的数据传递是单向的,只能由实参传给形参,反之则不行,即形参的改变不会影响实参
例程:编写程序实现从两整数中求较大数
#include
float max(float x,float y);
main()
{
float a,b;
float c;
scanf(“%f”,&a,&b);
c=max(a,b);
printf(“a=%f,b=%f,c=%f\n”,a,b,c);
}
float max(float x,float y)
{
float z;
z=x>y?x:y;
printf(“x=%f,y=%f\nz=%f\n”,x,y,x);
return(z);
}
输入:2,3
运行结果:
x=2.000000 y=3.000000
z=3.000000
a=2.000000 b=3.000000
max=3.000000
(1)在上面的max函数中,假若把z=x>y?x:y;改为z=++x>++y?x:y;若执行时输入2,3,执行后结果为:
x=3.000000,y=4.000000
z=4.000000
a=2.000000,b=3.000000
max=4.000000
(2)多次调用时,形参分配到的内存单元已经被收回,故形参分配到的内存单元不一定系统
(3)形参和实参分配存储单元的时刻与作用范围不同,分配到的存储单元也不同,故形参和实参可以同名
例程:
#include
main()
{
int x=10,y=30;
printf(“Before swap:x=%d y=%d\n”,x,y);
swap(x,y);
printf(“After swap :x=%d y=%d\n”,x,y);
}
int swap(int x,int y)
{
int t;
t=x;x=y;y=t;
printf(“In swap function:x=%d y=%d\n”,x,y);
}
运行结果:
Before swap:x=10 y=30
In swap function:x=30 y=10
After swap:x=10 y=30
程序执行到语句swap(x,y)时,进行参数传递,形参变量x,y进行了交换,执行完毕收回形参x,y的存储单元,返回到主函数,主函数中实参x,y没有变化
函数的嵌套调用是指,在执行被调用函数时,被调用函数又调用了其他函数。
例程:
#include
int dif(int x,int z);
int max(int x,int y,int z);
int min(int x,int y,int z);
void main()
{
int a,b,c,d;
scanf(“%d%d%d”,&a,&b,&c);
d=dif(a,b,c);
printf(“Max-Min=%d\n”,d);
}
int dif(int x,int y,int z);
{
return max(x,y,z)-min(x,y,z);
}
int max(int x,int y,int z)
{
int r;
r=x>y?x:y
return(r>z?r:z);
}
int min(int x,int y,int z)
{
int r;
r=x
运行结果:输入 5 9 7后
Max-Min=4
5.5.2 函数的递归调用
1、递归的概念:一个函数在它的函数体内,直接或间接地调用自己,满足以下三个条件,可使用递归法:
(1) 可以把求解问题转化成一个新问题,这个新问题与原来问题的解法相同,只是处理的对象的值有规律地递增或递减;
(2)可以应用这个转化过程使问题得到解决;
(3)必定有一个明确的结束递归的条件(一定要有递归出口)
例程:用递归法求n!
#include
main()
{
int n;
float s;
float f(int);
printf(“Input n=”);
scanf(“%d”,&n);
s=f(n);
printf(“%d!=%.0f”,n,s);
}
float f(int x)
{
int t;
if(x==0) t=1;
else t=f(x-1)*x;
return t;
}
运行结果:
Input n=3
3!=6
在求n!中,既可以使用递推法也可以使用递归法:
递推方法:0!=1->1!=0!X1->2!=1!x2.....n!=(n-1)!xn
递归方法:首先回推:欲求n! 先求(n-1)! .... 0!;
再递推:直到1!->2!...n!
递归和迭代之间的选择,一般情况,当一个问题既可以选择递推又可以使用递归应该避免使用递归,因为迭代避免了一些列函数调用和返回中所涉及的参数传递的时间耗费和返回值的额外开销,故比递归方法快,占用空间小。但有些问题很难建立一个迭代方法,用递归方法却很容易,如汉诺塔问题:
汉诺塔问题:有三个塔,每个都放若干个盘子。开始所有盘子都在A上,盘子从上到下,按直径增大,设计一个盘子移动号顺序使得A上的所有盘子借助于塔B移动到C上
(若只有一个盘子直接从A到C,若有一个以上:1、把n-1个盘子依照题目中的规则从A借助于塔C移动到塔B,2、将剩下一只盘直接从A到C,3、再次将B塔上的n-1个盘子借助于塔A搬到塔C)
#include
void hanoi(int m,int a,int b,int c)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{
hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}
main()
{
int n;
printf(“input n:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}
例程:反向输出一个整数
反向输出一个正整数的算法归纳为:
if(n为一位整数)
输出n;
else
{ 输出n的个位上的数字;
对剩余数字组成的新整数重复”反向输出操作”;
}
#include
void main()
{
void printn(int x);
int n;
printf(“Input n=”);
scanf(“%d”,&n);
if(n<0)
{
n=-n;putchar(‘-’);
}
printn(n);
}
void printn(int n)
{
if(x>=0&&x<=9)
printd(“%d”,x);
else
{
printf(“%d”,x%10);
printfn(x/10);
}
}
5.6.1 变量的存储类型
C语言中的变量必须先定义后使用,变量的类型决定了计算机为变量预留多少存储空间。C语言中变量分为全局有效、局部有效和在复合语句内有效3中。
一个完整的变量说明格式如下:
[存储类型] 数据类型 变量表; 或 数据类型 [存储类型] 变量表;
C语言中变量有4中存储类型:自动变量(auto)、寄存器变量(register)、静态变量(static)、外部变量(extern)。
根据它们在内存中的位置,又分为自动和静态两类。其中auto、register描述的时自动,存储在动态存储区;static、extern描述的时静态类,存储在静态存储区。
用户程序的存储一般分为三个区,如下表:
动态存储区(堆栈) |
静态存储区 |
程序代码区 |
静态存储区存放的变量在编译时分配存储单元,程序结束才收回存储单元。动态存储区的变量会在程序运行期间根据需要随时动态分配存储空间。
C语言中所有的变量都有自己的作用域。变量说明的位置不同,其作用域也不同据此将C语言中的变量分为局部变量(内部变量)和全局变量(外部变量)
5.6.2 局部变量
在函数内部或复合语句内部定义的变量称为局部变量。函数的形参也属于局部变量。局部变量的作用域时本函数内部或复合语句内部。所以局部变量也称“内部变量”。
局部变量分为:自动变量、寄存器变量、静态局部变量。
主函数定义的局部变量只能在主函数中使用,其他函数不能使用。同时,主函数也不能使用其他函数定义的局部变量,这一点是与其他语言不同的。
1. 自动变量
在函数内部或复合语句内部定义的变量,如果没有写明存储类,或使用了auto说明符,系统就认为所定义的变量属于自动变量类别,有时也称为(动态)局部变量、
形参是被调用函数的局部变量。形参默认的关键字是auto,但不能将auto直接加在形参前面。
自动变量生存期为:函数调用时,分配存储单元,函数返回时,收回存储单元;复合语句执行前分配存储单元,复合语句执行后收回存储单元。自动变量的初始化是:每一次调用,形参都以实参为初值。所以未赋初值的自动变量“无定义”,其值不定。
注意:复合语句内部,所在函数的同名局部变量被屏蔽掉,只有复合语句内部的同名变量有效,如下程序:
#include
main()
{
int x=1;
{ int x=2;
{ int x=3;
printf(“%d”,x);
}
printf(“%d”,x);
}
printf(“%d”,x);
}
运行结果:
3
2
1
3个x各有自己的存储空间和作用域,互不冲突,其中内层x屏蔽外层x的访问。
2 寄存器变量
register类型变量存放在CPU的寄存器中,它们属于自动变量。运行程序中,访问位于寄存器中的值比访问内层中的值快很多。
例程:计算的值。i=0,1,2...9
#include
double power(int x,register int n)
{
register int i;
int p=1;
for(i=0;i
程序说明:
a、只有自动变量和形参可以被定义为register变量,寄存器变量只能是int、char和指针类型的变量;
b、 register变量没有地址,不能对它进行求址运算,因为它放在寄存器中而不是内存中;
c、 register只是对编译程序的一种建议,当没有足够的寄存器空间编译程序将自动按auto处理
3 静态局部变量
在函数体(复合语句)内部,用以下定义格式定义的变量称为静态局部变量:
static 数据类型 变量表
例如:
static int a=8;
关键字static使得变量存储在内存的静态存储区,编译程序为之生成永久存储单元,其生存期为整个程序的一次运行。
静态局部变量的初始化只在编译时进行一次,每次调用它所在的函数时,不再重新赋初值,只是保留上次调用结束时的值。若定义但不初始化,则自动赋以0(数值型)或‘\0’(字符型)
例程:
#include
main()
{
int f(void);
int j;
for(j=0;j<3;j++)
printf(“%d\n”,f());
}
int f(void)
{
static int x=1;
x++;
return x;
}
运行结果:
2
3
4
函数f被调用3次,由于x是静态局部变量,编译时分配存储空间,故每次调用函数f时,变量x不再重新初始化,保留加1的值,得到上面的输出结果。
5.6.3 全局变量和静态全局变量
当变量定义在函数体外时,该变量就称为全局变量,全局变量也称为外部变量。
对于全局变量,可用extern和static两种说明符,用static修饰的全局变量称为静态全局变量(静态外部变量)。全局变量和静态全局变量都属于静态存储类。生存期都是程序的一次执行,定义和初始化都是在程序编译时进行的,初始化只有一次。若没有初始化,则自动赋以’0’或’\0’。
全局变量不属于任何一个函数,其作用域:从全局变量的定义位置开始,到本文件结束为止。全局变量可为各函数所共享,所以函数之间数据交流可以使用全局变量,但并不提倡使用。
一个C语言程序可以由一个或多个源程序文件组成,每个源程序文件作为一个编译单位单独进行编译,然后连接成一个可执行文件exe。假如每个文件都定义了一个同名全局变量,则分别编译时将分别给每个变量分配一个存储空间,连接时就会出现同一个变量名“重复定义”的错误。此时可通过外部变量说明实现在其他文件中无需再次定义而直接使用。
外部变量说明的一般形式:
extern 数据类型 全局变量1[全局变量2...]
其作用是声明这些变量是在别处已经定义的全局变量,通知编译程序无需再给它分配存储单元,这时该变量的作用域进行了延伸。若外部变量出现引用它的函数中,则该变量的作用域从extern说明处起,延伸到了该函数末尾。函数外的extern变量延伸到整个文件结束。
例程:
#include
void try_1(void)
{
extern int i; /*先用extern进行说明,说明i是在后面定义的外部变量*/
/*i的作用域延伸为从此处到整个文件的结束*/
i=i+5;
}
int i;
main()
{
try_1();
printf(“i=%d\n”,i);
}
运行结果:i=5
如果将全局变量的作用范围扩展至整个源文件,可以将变量说明放在文件头部。
例程:
/*FILE1*/
#include
int i;
void func();
main()
{
i=5;
printf(“FILE1:%d\n”,i);
func();
}
/*FILE2*/
extern int i; /*外部变量说明,说明文件FILE2中的变量i是引用File1中定义的外部变量i*/
void func()
{
printf(“FILE2:%d\n”,i);
}
运行结果:
FILE1:5
FILE2:5
说明:
a当程序中多个函数使用同一数据时,全局变量很有效,可加强函数模块之间的联系,但又使这些函数依赖全局变量,使得独立性降低。在编制大型程序时变量值可能在程序其他地点被改变;
b在同一源文件中,容许全局变量和局部变量同名。此时,在局部变量的作用域内,全局变量将被屏蔽而不起作用。
例程:
#include
void num()
{
extern int x,y; /*外部变量说明,说明num函数中的x,y是后面定义的全局变量
int a=15,b=10; /*变量a,b是局部变量,其作用域只在num函数内部*/
x=a-b;
y=a+b;
}
int x,y; /*全局变量定义*/
main()
{
int a=7,b=5; /*变量a,b是局部变量,其作用域只在main函数的内部*/
x=a+b;
y=a-b;
num();
printf(“%d,%d\n”,x,y);
}
运行结果: 5,25
注意:
a、如果extern x,y;不加上extern,则函数内定义了局部变量x,y,其作用范围为函数num内部,与main函数中用的全局变量x,y无关,输出结果为12,2
b、如果程序内extern int x,y;语句不加上extern,并且全局变量定义int x,y;位于程序文件顶部,输出结果仍是12,2,因为此时虽然全局变量x,y的作用域是整个文件,但局部变量的作用域内,同名全局变量将被屏蔽而不起作用。
例如,把上方两个例子中FILE1的外部变量定义int i;改为static int i;,则分别编译两个文件时一切正常,但是把这两个文件连接在一起时将产生FILE2中符号’i’无定义的错误。
当一个源程序由多个源文件组成时,C语言根据函数能否被其他源文件中的函数调用,将函数分为内部函数和外部函数。
5.7.1 内部函数
如果一个函数只能被本文件中的函数调用,不能被同一源程序其他文件调用,这种函数称为内部函数(又称为静态函数)。
定义一个内部函数,只需在函数类型前再加一个static关键字即可,定义格式如下:
static 函数类型 函数名(函数参数表)
{......}
例如:
static int fun(int a,int b,int c) {......}
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数是否会与其他文件中的函数同名。
5.7.2 外部函数
在定义函数时如果没有加关键字static或冠以extern,则表示此函数是外部函数,其定义格式为:
[extern] 函数类型 函数名(函数参数表)
{......}
其中[ ]内为可选项。如:
int fun(int a,int b,int c) {......} 或 extern int fun(int a,int b,int c) {......}
若函数定义在其他文件内,或定义在调用点之前,在调用外部函数时,必须对其进行说明:
[extern] 函数类型 函数名(参数类型表[函数名1[,参数类型表2,...])
例程
a文件mainf.c中定义外部函数示例。
main()
{ extern void input(...),process(...),output(...); /*说明本文件要用到其他文件中定义的函数*/
input(...);process(...);output(...); /*函数调用*/
}
b文件subf1.c中定义外部函数示例
...
extern void input(...) /*定义外部函数input*/
{...}
c文件subf2.c中定义外部函数示例
...
extern void process(...) /*定义外部函数process*/
...
d文件subf3.c中定义外部函数示例
...
extern void output(...) /*定义外部函数output*/
{...}
在C语言中,凡是以字符”#”开头的行都称为:编译预处理”命令行。他们实际上不是C语言的一部分,却扩展了C语言程序设计环境。
所谓”编译预处理”就是在对C源程序进行编译前,由编译预处理程序对这些编译处理行进行处理的过程。
C语言预处理命令有:#define、#undef、#include、#if、#else、#elif、#endif、#ifdef、#indef、#line、#pragma、#error。这些命令均以”#”开头,一行只写一个命令,并且末尾不能加分号,以区别于其他C语句。他们可根据需要出现在程序中的任何一行的开始位置,但通常都写在程序的开始位置。
5.8.1 宏替换
不带参数的宏定义形式为:
#define 宏名 替换文本
或
#define 宏名
后一种形式不包含”替换文本”,仅说明标识符”宏名”被定义。
#define PI 3.14
标识符PI称为”宏名”,是用户定义的标识符,通常大写,不得与程序中的其他名字相同;”3.14”是替换文本,也称”宏体”,编译时,在此命令行之后,预处理程序对源程序中的所有名为PI的标识符用字符串”3.14”来替换,这个替换过程称为”宏替换”。
替换文本可以包含已定义的宏名
#define PI 3.14
#define JAPI (PI+1)
#define JAJAPI (2*JAPI)
说明:
a、宏名一般习惯用大写字母表示,也可以用小写字母
b、使用宏名替换一个字符串,可增加可读性,也便于修改,当发生改变时,可以只改#define命令行,从而做到一改全改;
c、宏定义是用宏名代替一个字符串,简单的置换,不做正确性检查
d、宏定义不是C语句,结尾不能有分号,加了分号,会连分号一起进行置换
#define PI 3.14;
则对程序中的语句
printf(“%f”,PI*10*10);
预处理后悔被替换为:
printf(“%f”,3,14;*10*10);
e对于程序中双括号括起来的字符串内的字符,即使宏名相同,也不进行置换。
#define R 3.0
#define PI 3.14
#define L 2*PI*R
则对程序中的语句:
printf(“L=%f\n”,L);
预处理后只会替换后面的L,前面双括号的L不会替换
f宏定义是一个专门预处理命令的专用名词,只做字符替换,不分配空间;
g宏替换不占运行时间,只占编译时间,宏替换是在编译时进行的,而函数调用是在程序运行时处理的
1、一维数组:当数组中每一个元素只有一个下标时,称为一维数组。C语言中,数组必须显示地说明。定义一个一维数组一般形式为:
类型名 数组名[常量表达式];
例如:
int a[5]; //int时类型名,a[5]就是一维数组说明符
(1)方括号中的5规定a[5]含有5个元素,由于C语言固定每个数组第一个月元素下标总为0,所以5个数组元素表示为a[0]、a[1]、a[2]、a[3]、a[4]。此处不能使用a[5]。
(2)类型名int规定了a数组中的每一个元素都是整型,即每个元素只能存放整数。
(3)定义数组类型时可以是int、char、float、double、unsigned等;
(4)数组名后是用方括号括起来的常量表达式,不能用圆括号。常量表达式是数组的长度,即数组中所包含元素的个数。
2、一维数组的引用:C语言规定只能逐个引用数组元素,不能一次引用一个数组。一维数组元素的引用形式如下:
数组名
编程将数字0~4放入一个整数组并输出
#include
#include
main()
{
int i, x[5];
for (i = 0; i < 5; i++)
x[i] = i;
for (i = 0; i < 5; i++)
printf("%d\n", x[i]);
system("pause");
}
(1)一个数组元素实质上是一个变量名,代表内存中一个存储单元;
(2)C语言中,一个数组不能整体引用,如数组x[4]不能用x代表x[0]到x[4]这5个元素。数组名中存放的是一个地址常量,它代表整个数组的首地址;
(3)C语言不检查数组元素下标是否越界。因此数组的两端都有可能越界进而破坏其它存储单元的数据。
3、一维数组的初始化
数组的初始化:定义数组时对数组各元素指定初值。
(1)在定义数组时对数组元素赋初值
int a[5]={1,2,3,4,5};
(2)给部分元素赋初值
int a[5]={5,4};
表明只给前两个元素赋初值,即a[0]=5,a[1]=4,其它元素自动赋0值。
(3)对全部元素赋初值,可以不指定数组长度,系统自动根据数值个数来决定数组长度
int a[]={1,2,3,4,5};
系统a定义为有5个元素的数组。