第1节 标准C语言的基本语法
推荐给好友
打印
加入收藏
更新于2008-07-25 09:46:34

C语言是在70年代初问世的。1978年美国电话电报公司(AT&T)贝尔实验室正式发表了C语言。同时由B.W.Kernighan和D.M.Ritchit合著了著名的《THE C PROGRAMMING LANGUAGE》,简称为《K&R》也有人称之为K&R标准。但是,在《K&R》中并没有定义一个完整的标准C语言,后来由美国国家标准学在此基础上制定了一个C语言标准,于1983年发表,通常称之为ANSI C或标准C。

本节简要介绍C语言的基本知识,特别是一些和单片机编程密切相关的基本知识,未学过标准C语言的读者可以通过本节了解C语言,对于C语言很熟悉的读者,可以跳过本节。

5.1.1 数据类型

C语言的数据类型有基本类型和构造类型两大类。基本数据类型如表5-1所示


注:08C语言的double类型长度为4字节。

构造类型有数组、结构、联合、枚举、指针和空类型。结构和联合是基本数据类型的组合。枚举是一个被命名为整型常量的集合。空类型字节长度为0,主要有两个用途:一是明确地表示一个函数不返回任何值;二是产生一个同一类型指针(可根据需要动态地分配给其内存)。

5.1.2 运算符

C语言的运算符与大多数计算机语言基本相同,分为算术、逻辑、关系和位运算及一些特殊的操作符。表5-2列出了C语言的运算符及使用方法举例。

注:增量运算符和减量运算符存在运算和取数先后次序,例如,A++是先取变量A的值再对A加1,而++A是先对变量A加1再取A的值。

5.1.3 流程控制

在程序设计中主要有三种基本控制结构:顺序结构、选择结构和循环结构。

(1) 顺序结构
顺序结构就是从前向后依次执行语句。从整体上看,所有程序的基本结构都是顺序结构,中间的某个过程可以是选择结构或循环结构。

(2) 选择结构
在大多数程序中都会包含选择结构。它的作用是,根据所指定的条件是否满足,决定从给定的两组操作选择其一。在C语言中选择结构可以用两种语句来实现:if语句和switch语句。下面分别介绍这两种C语句的具体用法。

① if结构:
if (表达式) 语句项; 或 if (表达式) 语句项;
else 语句项;

如果表达式取值真(除0以外的任何值),则执行if的语句项;否则,如果else存在的话,就执行else的语句项。每次只会执行if或else中的某一个分支。语句项可以是单独的一条语句、也可以是多条语句组成的语句块,是语句块时要用大括号括起来:

格式: if(条件)
{ 语句块 }
else
{ 语句块 }


例: if (var = = TRUE)
printf("it's ok");
else
printf("not ready");

if语句可以嵌套,有多个if语句时else与最近的一个配对。对于多分支语句,可以使用if ... else if ...
else if ... else ...的多重判断结构,也可以使用下面讲到的switch()开关语句。

② switch结构
switch是C语言内部多分支选择语句,它根据某些整型和字符常量对一个表达式进行连续测试,当一常量值与其匹配时,它就执行与该变量有关的一个或多个语句。switch语句的一般形式如下:

格式:switch(表达式)
{
case 常数1:
语句项1
break;
case常数2:
语句项2
break;
…………
default:
语句项


例:
switch(number)
{
case 1:
printf(“First”);
break;
case 2:
printf(“Second”);
break;
default:
printf(“input a number”);
}


根据case语句中所给出的常量值,按顺序对表达式的值进行测试,当常量与表达式值相等时,就执行这个常量所在的case后的语句块,直到碰到break或switch语句执行完成为止。若没有一个常量与表达式值相符,则执行default后的语句块。Default是可选的,如果它不存在,并且所有的常量与表达式值都不相符,那就不做任何处理。

switch语句与if语句的不同之处在于switch只能对等式进行测试,而if可以对关系表达式或逻辑表达式进行测试。

break语句还可用于循环语句,break语句在switch语句中是可选的,如果不用break,就继续在下一个case语句中执行,一直到碰到break或switch的末尾为止,这样的程序效率比较低。

(3) 循环结构
C语言中的循环结构常用for循环,while循环与do...while循环。
① for循环
格式为:
for(表达式1;表达式2;表达式3){语句}
执行过程为:
Ⅰ 先求解表达式1;
Ⅱ 再判断表达式2,如果其值为真(非0),则执行for中指定的内嵌语句,然后执行下面的第Ⅲ步。若为假(0)
,则结束循环,转到循环下面的语句;
Ⅲ 求解表达式3;
Ⅳ 转到上面第Ⅱ步骤继续执行。
For语句的执行流程如图5-1所示。
例:
a=0;

for(I=0;I<100;I++)

图5-1 for循环流程图
a=a+2;//循环结束,a=200
② while循环
格式为:
while(表达式) {语句}
当表达式的值为真(非0)时执行while语句中的内嵌语句。其流程如图5-2所示。其特点是:先判断表达式,后
执行语句。
例:
while(bOverFlow)
{
if(n<100)
n++;
else
bOverFlow = FALSE;



图5-2 while循环流程图}
③ do....while循环
格式为:
do {循环体语句}
while(表达式);
其特点是:先执行语句,后判断表达式。它的执行过程与while循环不同的是先执行循环体语句,再判断循环
条件。如图5-3所示。
例:
do
{
if(n<100)

图5-3 do while循环流程图
n++;
else
bOverFlow = FALSE;
}while(bOverFlow);
(4) break语句和continue语句
在循环中常常使用break语句和continue语句,这两个语句都会改变循环的执行情况。break语句用来从循环体中强行跳出循环,终止整个循环的执行;continue语句只用来结束本次循环,而不是整个循环。如以下两个循环结构

① while (表达式1)
{
……
if(表达式2) break;
……
}


② while (表达式1)
{
……
if(表达式2) continue;
……
}

程序①的流程图如图5-4所示,而程序②的流程图如图5-5所示。请注意两个图中当“表达式2”条件为真时流程的转向

5.1.4 函数

所谓函数,即子程序,也就是“语句的集合”,就是说把经常使用的语句群定义成函数,供其他程序调用,这样就可以避免重复编写程序的麻烦,也可以缩短程序的长度。当一个程序太大时,建议将其中的一部分程序改成用函数的方式调用较好,因为大程序过于繁杂容易出错,而小程序容易调试,也易于阅读和修改。函数定义的一般形式如下所示:

类型标识符 函数名(类型 参数1,类型 参数2,类型 参数3,……)
{说明部分语句}

(1) 使用函数的注意事项
①函数定义时要同时声明其类型。
②调用函数前要先声明该函数。
③传给函数的参数值,其类型要与函数原定义一致。
④接收函数返回值的变量,其类型也要与函数类型一致。

(2) 函数的声明
void function1(void)
此函数无返回值,也不传参数。
void function2(unsigned char i, int j)
此函数无返回值,但需要unsigned char类型的参数i和int类型的参数j。
unsigned char function3(unsigned char i)
此函数有返回值,其类型为unsigned char。

(3) 函数的返回值
return 表达式;
return语句用来立即结束函数,并返回一确定值给调用程序。如果函数的类型和return语句中表达式的值不 一致,则以函数类型为准。对数值型数据,可以自动进行类型转换。即函数类型决定返回值的类型。

5.1.5 指针

指针是C语言中广泛使用的一种数据类型,运用指针是C语言最主要的风格之一。利用指针变量可以表示各种数据结构,很方便地使用数组和字符串,并能像汇编语言一样处理内存地址,从而编出精练而高效的程序。

指针是一种特殊的数据类型,在其它语言中一般没有。指针是指向变量的地址,实质上指针就是存储单元的地址。根据所指的变量类型不同,可以是整型指针(int *)、浮点型指针(float *)、字符型指针(char *)、
结构指针(struct *)和联合指针(union *)。

(1) 指针变量的定义
其一般形式为:类型说明符 * 变量名;
其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。

 例如:
int *point_1;
表示point_1是一个指向整型的指针变量,它的值是一个整型变量的地址。

(2) 指针变量的赋值
指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值。未经赋值的指针变量不能使用,否则将造成系统混乱,甚至死机。指针变量的赋值只能赋予地址。
① 指针变量初始化的方法
int a;
int *point =&a;
② 给指针赋值的方法
int a;
int *point;
point =&a;
将数值赋给指针将导致错误,例如:int *point; point =1000;是错误的。
被赋值的指针变量前不能再加“*”说明符,如写为*point =&a也是错误的。

(3) 指针的运算
① 取地址运算符 &
取地址运算符&是单目运算符,其结合性为自右至左,其功能是取变量的地址。
② 取内容运算符 *
取内容运算符*是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在*运算符之后跟的变量必须是指针变量。
注意:指针运算符*和指针变量说明中的指针说明符*并非相同。在指针变量说明中,“*”是类型说明符,表示其后的变量是指针类型。而表达式中出现的“*”则是一个运算符用以表示指针变量所指的变量。
main(){
int a=5,*point=&a;
printf("%d",*point);
}
表示指针变量point取得了整型变量a的地址。本语句表示输出变量a的值。
③ 加减算术运算
对于指向数组的指针变量,可以加上或减去一个整数n。设pa是指向数组a的指针变量,则pa+n,pa-
n,pa++,++pa,pa--,--pa运算都是合法的。指针变量加或减一个整数n的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动n个位置。
注意:数组指针变量向前或向后移动一个位置和地址加1或减1在概念上是不同的。因为数组可以有不同的类型,各种类型的数组元素所占的字节长度是不同的。如指针变量加1,即向后移动1个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1。

例如:
int a[5],*pa;
pa=a; /*pa指向数组a,也是指向a[0]*/
pa=pa+2; /*pa指向a[2],即pa的值为&pa[2]*/
指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减运算是毫无意义的。

(4) void指针类型
顾名思义,void *为“无类型指针”,即用来定义一个指针变量,不指定它是指向哪一种类型数据,但可以 把它强制转化成任何一种类型的指针。对于void *类型的指针变量不能进行取内容运算和加减算术运算,因 为编译器不知道它指向的具体类型。

众所周知,如果指针p1和p2的类型相同,那么我们可以直接在p1和p2间互相赋值;如果p1和p2指向不同的数据类型,则必须使用强制类型转换运算符把赋值运算符右边的指针类型转换为左边指针的类型。

例如:
float *p1;
int *p2;
p1 = (float *)p2;
而void *则不同,任何类型的指针都可以直接赋值给它,无需进行强制类型转换:
void *p1;
int *p2;
p1 = p2;
但这并不意味着,void *也可以无需强制类型转换地赋给其它类型的指针,也就是说p2 = p1这条语句编译就会出错,而必须将p1强制类型转换成“int *”类型的。因为“无类型”可以包容“有类型”,而“有类型”则不能包容“无类型”。

5.1.6 结构体

结构体是由基本数据类型构成的,并用一个标识符来命名的各种变量的组合。结构体中可以使用不同的数据类型。

(1) 结构体的说明和结构体变量的定义
定义一个结构体的一般形式为:

①struct 结构体名
{成员列表};


②struct 结构体名
{成员列表}结构体变量;

在①方式中,仅定义了一个结构体。如果要使用这种类型的结构体还需要使用变量定义语句。②方式在定义结构体的同时,定义了结构体变量。例如下面左右两边的代码是等价的。

struct student
/*定义一个student结构体*/
{ char name[8];
int age;
char sex[2];
};
struct student student1;
/*定义一个student结构变量


struct student
{
char name[8];
int age;
char sex[2];
}student1;
/*定义一个student结构体,同时定义一个student结构变量student1*/


(2) 结构体变量的使用
结构体是一个新的数据类型,因此结构体变量也可以象其它类型的变量一样赋值运算,不同的是结构体变量以成员作为基本变量。

结构体成员的表示方式为:
结构体变量.成员名
如果将“结构体变量.成员名”看成一个整体,则这个整体的数据类型与结构体中该成员的数据类型相同,这样就像前面所讲的变量那样使用。例如:
student1.age=18;

(3) 结构体指针
结构体指针是指向结构体的指针。它由一个加在结构体变量名前的“*”操作符来定义。例如用上面已说明的结构体定义一个结构体指针如下:

struct student *Pstudent;
使用结构体指针对结构体成员的访问,与结构体变量对结构体成员的访问在表达方式上有所不同。结构体指针对结构体成员的访问表示为:

结构体指针名->结构体成员
其中"->"是两个符号"-"和">"的组合,好象一个箭头指向结构体成员。例如要给上面定义的结构体中name和
age赋值, 可以用下面语句:
strcpy(Pstudent->name,"LiuYuZhang");
Pstudent->age=18;
实际上,Pstudent->name就是(*Pstudent).name的缩写形式。

需要指出的是结构体指针是指向结构体的一个指针,即结构体中第一个成员的首地址,因此在使用之前应该对结构体指针初始化,即分配整个结构体长度的字节空间。这可用下面函数完成,例如
:Pstudent=(struct sudent*)malloc(size of (struct student));
size of (struct student)自动求取student结构体的字节长度, malloc() 函数定义了一个大小为结构体长
度的内存区域,然后将其地址作为结构体指针返回。


5.1.7 共用体

有时需要将几种不同类型的变量存放到同一段内存单元中。例如,可把一个整型变量、一个字符型变量放在同一个地址开始的内存单元(如图5-6所示)。以上3个变量在内存中占的字节数不同,但都从同一地址开始(图中设地址为$0080)存放。这种几个不同的变量共同占用同一段的结构,称为“共用体”类型结构。

1) 共用体的定义
定义一个联合类型的一般形式为

union 共用名
{
成员表
}共用变量名;


成员表中含有若干成员,成员的一般形式为:
类型说明符 成员名
例如:
union abc{
int a;
char b;
long c;
}u1;

(2) 共用体的使用
在共用体变量u1被分配的内存单元数量等于长整型变量c的长度(4字节)中。如果整型变量c的赋值为:
u1.c=0x12345678;
则u1.a和u1.b的值也被修改。本例中,它们的值为:
u1.a=0x1234;
u1.b=0x12;

5.1.8 位域

有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1两种状态,用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。

(1) 位域的定义
位域定义与结构定义相仿,其形式为:
struct 位域结构名{
位域列表
};
位域列表格式为:
类型说明符位域名:位域长度
例如:
struct bs
{
int a:8;
int b:2;
int c:6;
}b1;

说明b1为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。对于位域的定义尚有以下几点说明:

①一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:
struct bs
{
int a:4
int :0 //空域
int b:4 //从下一单元开始存放
int c:4
};

在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。
②由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。
③位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:
struct k
{
int a:1
int :2 //无域名,2位不能使用
int b:3
int c:2
}b1;
从以上分析可以看出,位域在本质上就是一种结构类型,不过其成员是按二进位分配的。

(2) 位域的使用
位域的使用和结构成员的使用相同,其一般形式为:
位域变量名•位域名位
例如在上面定义的位域b1可以这样调用:
b1.a=1; //将b1的第0位置1
b1.b=7; //将b1的第3~5位置111
通过位域定义位变量,是实现单个位操作的重要途径和方法,采用位域定义位变量,产生的代码紧凑、高效。

5.1.9 编译预处理

C语言提供编译预处理的功能,“编译预处理”是C编译系统的一个重要组成部分。C语言允许在程序中使用几种特殊的命令(它们不是一般的C语句)。在C编译系统对程序进行通常的编译(包括语法分析,代码生成,优化等)之前,先对程序中的这些特殊的命令进行“预处理”,然后将预处理的结果和源程序一起再进行常规的编译处理,以得到目标代码。C提供的预处理功能主要有宏定义、条件编译和文件包含。

(1) 宏定义
#define 宏名 表达式
表达式可以是数字、字符、也可以是若干条语句。在编译时,所有引用该宏的地方,都将自动被替换成宏所
代表的表达式。例如:
#define PI 3.1415926
#define S(r) PI*r*r

(2) 条件编译
#if 表达式
#else 表达式
#endif
如果表达式成立,则编译#if下的程序,否则编译#else下的程序,#endif为条件编译的结束标志。
#ifdef 宏名 //如果宏名称被定义过,则编译以下程序。
#ifndef 宏名 //如果宏名称未被定义过,则编译以下程序。
条件编译通常用来调试、保留程序(但不编译),或者在需要对两种状况做不同处理时使用。

(3) “文件包含”处理
所谓“文件包含”是指一个源文件将另一个源文件的全部内容包含进来,其一般形式为:
#include “文件名”

5.1.10 用typedef定义类型

除了可以直接使用C提供的标准类型名(如int、char、float、double、long等)和自己定义的结构体、共用体 、指针、枚举类型外,还可以用typedef定义新的类型名来代替已有的类型名。例如:
typedef unsigned char INT8U;
指定用INT8U代表unsigned char类型。这样下面的两个语句是等价的:
unsigned char i; 等价于 INT8U i;

用法说明:
①用typedef可以定义各种类型名,但不能用来定义变量。
②用typedef只是对已经存在的类型增加一个类型名,而没有创造新的类型。
③typedef与#define有相似之处,如:
typedef unsigned int INT16U;
#define INT16U unsigned int;
这两句的作用都是用INT16U代表unsigned int。但事实上,它们二者是不同的,#define是在预编译时处理的 ,它只能作简单的字符串替代,而typedef是在编译时处理的。
④当不同源文件中用到各种类型数据(尤其是像数组、指针、结构体、共用体等较复杂数据类型)时,常用
typedef定义一些数据类型,并把它们单独存放在一个文件中,而后在需要用到它们的文件中用#include命令把它们包含进来。
⑤使用typedef有利于程序的通用与移植。

<<上一节 下一节>>

相关链接


 
关于我们 | 诚邀加盟 | 客户服务 | 相关法律 | 网站地图 | 友情链接 | 服务信箱:service@eefocus.com
© 2006 与非门科技信息咨询(北京)有限公司 All Rights Reserved.