




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
C语言嵌入式系统编程修炼之零:基础知识
本节专门对第二节曾讲述过的指针作一详述。并介绍C语言新的数据类型:结构、联合和枚举。枚举是一个被命名为整型常数的集合。最后对类型说明(typedef)和预处理指令作一阐述。指针(point)
学习C语言,如果你不能用指针编写有效、正确和灵活的程序,
可以认为你没有学好C语言。指针、地址、数组及其相互关系是C语言中最有特色的部分。规范地使用指针,可以使程序达到简单明了,因此,我们不但要学会如何正确地使用指针,而且要学会在各种情况下正确地使用指针变量。1.指针和地址
1.1指针基本概念及其指针变量的定义
1.1.1指针变量的定义
我们知道变量在计算机内是占有一块存贮区域的,变量的值就存放在这块区域之中,在计算机内部,通过访问或修改这块区域的内容来访问或修改相应的变量。TurboC语言中,对于变量的访问形式之一,就是先求出变量的地址,
然后再通过地址对它进行访问,这就是这里所要论述的指针及其指针变量。所谓变量的指针,实际上指变量的地址。变量的地址虽然在形式上好象类似于整数,但在概念上不同于以前介绍过的整数,它属于一种新的数据类型,即指针类型。TurboC中,一般用"指针"来指明这样一个表达式&x的类型,
而用"地址"作为它的值,也就是说,若x为一整型变量,则表达式&x的类型是指向整数的指针,而它的值是变量x的地址。同样,若doubled;则&d的类型是指向以精度数d的指针,而&d的值是双精度变量d的地址。所以,指针和地址是用来叙述一个对象的两个方面。虽然&x、&d的值分别是整型变量x和双精度变量d的地址,但&x、&d的类型是不同的,一个是指向整型变量x的指针,而另一个则是指向双精度变量d的指针。在习惯上,
很多情况下指针和地址这两个术语混用了。
我们可以用下述方法来定义一个指针类型的变量。
int*ip;
首先说明了它是一指针类型的变量,注意在定义中不要漏写符号"*",
否则它为一般的整型变量了。另外,在定义中的int表示该指针变量为指向整型数的指针类型的变量,有时也可称ip为指向整数的指针。ip是一个变量,它专门存放整型变量的地址。
指针变量的一般定义为:
类型标识符
*标识符;
其中标识符是指针变量的名字,标识符前加了"*"号,
表示该变量是指针变量,而最前面的"类型标识符"表示该指针变量所指向的变量的类型。一个指针变量只能指向同一种类型的变量,也就是讲,我们不能定义一个指针变量,既能指向一整型变量又能指向双精度变量。
指针变量在定义中允许带初始化项。如:
inti,*ip=&i;
注意,这里是用&i对ip初始化,而不是对*ip初始化。和一般变量一样,
对于外部或静态指针变量在定义中若不带初始化项,指针变量被初始化为NULL,它的值为0。TurboC中规定,当指针值为零时,指针不指向任何有效数据,有时也称指针为空指针。因此,当调用一个要返回指针的函数(第五节中介绍)时,常使用返回值为NULL来指示函数调用中某些错误情况的发生。
1.1.2指针变量的引用
既然在指针变量中只能存放地址,因此,在使用中不要将一个整数赋给一指针变量。下面的赋值是不合法的:
int*ip;
ip=100;
假设
inti=200,x;
int*ip;
我们定义了两个整型变量i,x,还定义了一个指向整型数的指针变量ip。i,x中可存放整数,而ip中只能存放整型变量的地址。我们可以把i的地址赋给ip:
ip=&i;
此时指针变量ip指向整型变量i,假设变量i的地址为1800,这个赋值可形象理解为下图所示的联系。
ip
i
┏━━━┓
┏━━━┓
┃1800─——→┃200┃
┗━━━┛
┗━━━┛
图1.给指针变量赋值
以后我们便可以通过指针变量ip间接访问变量i,例如:
x=*ip;
运算符*访问以ip为地址的存贮区域,而ip中存放的是变量i的地址,因此,*ip访问的是地址为1800的存贮区域(因为是整数,实际上是从1800开始的两个字节),它就是i所占用的存贮区域,所以上面的赋值表达式等价于
x=i;
另外,指针变量和一般变量一样,存放在它们之中的值是可以改变的,也就是说可以改变它们的指向,假设
inti,j,*p1,*p2;
i='a';
j='b';
p1=&i;
p2=&j;
则建立如下图所示的联系:
p1
i
┏━━━┓
┏━━━┓
┃
╂──→┃'a'
┃
┗━━━┛
┗━━━┛
p2
i
┏━━━┓
┏━━━┓
┃
╂──→┃'b'
┃
┗━━━┛
┗━━━┛
图2.赋值运算结果
这时赋值表达式:
p2=p1
就使p2与p1指向同一对象i,此时*p2就等价于i,而不是j,图2.就变成图3.所示:
p1
i
┏━━━┓
┏━━━┓
┃
╂──→┃'a'
┃
┗━━━┛
┌→┗━━━┛
p2
│
j
┏━━━┓
│
┏━━━┓
┃
╂─┘
┃'b'
┃
┗━━━┛
┗━━━┛
图3.p2=p1时的情形
如果执行如下表达式:
*p2=*p1;
则表示把p1指向的内容赋给p2所指的区域,此时图2.就变成图4.所示
p1
i
┏━━━┓
┏━━━┓
┃
╂──→┃'a'
┃
┗━━━┛
┗━━━┛
p2
j
┏━━━┓
┏━━━┓
┃
╂──→┃'a'
┃
┗━━━┛
┗━━━┛
图4.*p2=*p1时的情形
通过指针访问它所指向的一个变量是以间接访问的形式进行的,所以比直接访问一个变量要费时间,而且不直观,因为通过指针要访问哪一个变量,取决于指针的值(即指向),例如"*p2=*p1;"实际上就是"j=i;",前者不仅速度慢而且目的不明。但由于指针是变量,我们可以通过改变它们的指向,以间接访问不同的变量,这给程序员带来灵活性,也使程序代码编写得更为简洁和有效。指针变量可出现在表达式中,设
intx,y*px=&x;
指针变量px指向整数x,则*px可出现在x能出现的任何地方。例如:
y=*px+5;
/*表示把x的内容加5并赋给y*/
y=++*px;
/*px的内容加上1之后赋给y
[++*px相当于++(px)]*/
y=*px++;
/*相当于y=*px;px++*/1.2.地址运算
指针允许的运算方式有:
(1).指针在一定条件下,可进行比较,这里所说的一定条件,
是指两个指针指向同一个对象才有意义,例如两个指针变量p,q指向同一数组,则<,>,>=,<=,==等关系运算符都能正常进行。若p==q为真,则表示p,q指向数组的同一元素;若p<q为真,则表示p所指向的数组元素在q所指向的数组元素之前(对于指向数组元素的指针在下面将作详细讨论)。
(2).指针和整数可进行加、减运算。设p是指向某一数组元素的指针,开始时指向数组的第0号元素,设n为一整数,则
p+n
就表示指向数组的第n号元素(下标为n的元素)。
不论指针变量指向何种数据类型,指针和整数进行加、减运算时,编译程序
总根据所指对象的数据长度对n放大,在一般微机上,char放大因子为1,int、
short放大因子为2,long和float放大因子为4,double放大因子为8。对于下面讲述到的结构或联合,也仍然遵守这一原则。
(3).两个指针变量在一定条件下,可进行减法运算。设p,q指向同一数组,
则p-q的绝对值表示p所指对象与q所指对象之间的元素个数。其相减的结果遵守对象类型的字节长度进行缩小的规则。2.指针和数组
指针和数组有着密切的关系,任何能由数组下标完成的操作也都可用指针来实现,但程序中使用指针可使代码更紧凑、更灵活。2.1.指向数组元素的指针
我们定义一个整型数组和一个指向整型的指针变量:
inta[10],*p;
和前面介绍过的方法相同,可以使整型指针p指向数组中任何一个元素,
假定给出赋值运算
p=&a[0];
此时,p指向数组中的第0号元素,即a[0],指针变量p中包含了数组元素a[0]的地址,由于数组元素在内存中是连续存放的,因此,
我们就可以通过指针变量p及其有关运算间接访问数组中的任何一个元素。
C语言中,数组名是数组的第0号元素的地址,因此下面两个语句是等价的
p=&a[0];
p=a;
根据地址运算规则,a+1为a[1]的地址,a+i就为a[i]的地址。
下面我们用指针给出数组元素的地址和内容的几种表示形式。
(1).p+i和a+i均表示a[i]的地址,或者讲,它们均指向数组第i号元素,即指向a[i]。
(2).*(p+i)和*(a+i)都表示p+i和a+i所指对象的内容,即为a[i]。
(3).指向数组元素的指针,也可以表示成数组的形式,也就是说,
它允许指针变量带下标,如p[i]与*(p+i)等价。
假若:
p=a+5;
则p[2]就相当于*(p+2),由于p指向a[5],所以p[2]就相当于a[7]。而p[-3]就相当于*(p-3),它表示a[2]。2.2.指向二维数组的指针
2.2.1.二维数组元素的地址
为了说明问题,我们定义以下二维数组:
inta[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}};
a为二维数组名,此数组有3行4列,共12个元素。但也可这样来理解,数组a由三个元素组成:a[0],a[1],a[2]。而它匀中每个元素又是一个一维数组,且都含有4个元素(相当于4列),
例如,a[0]所代表的一维数组所包含的4个元素为a[0][0],a[0][1],a[0][2],a[0][3]。如图5.所示:
┏━━━━┓
┏━┳━┳━┳━┓
a─→┃
a[0]
┃─→┃0┃1┃2┃3┃
┣━━━━┫
┣━╋━╋━╋━┫
┃
a[1]
┃─→┃4┃5┃6┃7┃
┣━━━━┫
┣━╋━╋━╋━┫
┃
a[2]
┃─→┃8┃9┃10┃11┃
┗━━━━┛
┗━┻━┻━┻━┛
图5.
但从二维数组的角度来看,a代表二维数组的首地址,
当然也可看成是二维数组第0行的首地址。a+1就代表第1行的首地址,a+2就代表第2行的首地址。如果此二维数组的首地址为1000,由于第0行有4个整型元素,所以a+1为1008,a+2也就为1016。如图6.所示
a[3][4]
a
┏━┳━┳━┳━┓
(1000)─→┃0┃1┃2┃3┃
a+1
┣━╋━╋━╋━┫
(1008)─→┃4┃5┃6┃7┃
a+2
┣━╋━╋━╋━┫
(1016)─→┃8┃9┃10┃11┃
┗━┻━┻━┻━┛
图6.
既然我们把a[0],a[1],a[2]看成是一维数组名,可以认为它们分别代表它们所对应的数组的首地址,也就是讲,
a[0]代表第0行中第0列元素的地址,即&a[0][0],a[1]是第1行中第0列元素的地址,即&a[1][0],根据地址运算规则,a[0]+1即代表第0行第1列元素的地址,即&a[0][1],一般而言,a[i]+j即代表第i行第j列元素的地址,即&a[i][j]。
另外,在二维数组中,我们还可用指针的形式来表示各元素的地址。如前所
述,a[0]与*(a+0)等价,a[1]与*(a+1)等价,因此a[i]+j就与*(a+i)+j等价,它表示数组元素a[i][j]的地址。
因此,二维数组元素a[i][j]可表示成*(a[i]+j)或*(*(a+i)+j),
它们都与a[i][j]等价,或者还可写成(*(a+i))[j]。
另外,要补充说明一下,如果你编写一个程序输出打印a和*a,
你可发现它们的值是相同的,这是为什么呢?我们可这样来理解:首先,为了说明问题,我们把二维数组人为地看成由三个数组元素a[0],a[1],a[2]组成,将a[0],a[1],a[2]看成是数组名它们又分别是由4个元素组成的一维数组。因此,a表示数组第0行的地址,而*a即为a[0],它是数组名,当然还是地址,它就是数组第0行第0列元素的地址。2.2.2指向一个由n个元素所组成的数组指针
在TurboC中,可定义如下的指针变量:
int(*p)[3];
指针p为指向一个由3个元素所组成的整型数组指针。在定义中,圆括号是不能少的,否则它是指针数组,这将在后面介绍。这种数组的指针不同于前面介绍的整型指针,当整型指针指向一个整型数组的元素时,进行指针(地址)加1运算,表示指向数组的下一个元素,此时地址值增加了2(因为放大因子为2),而如上所定义的指向一个由3个元素组成的数组指针,进行地址加1运算时,其地址值增加了6(放大因子为2x3=6),这种数组指针在TurboC中用得较少,
但在处理二维数组时,还是很方便的。例如:
inta[3][4],(*p)[4];
p=a;
开始时p指向二维数组第0行,当进行p+1运算时,根据地址运算规则,
此时放大因子为4x2=8,所以此时正好指向二维数组的第1行。和二维数组元素地址计算的规则一样,*p+1指向a[0][1],*(p+i)+j则指向数组元素a[i][j]。
例1
inta[3][4]={
{1,3,5,7},
{9,11,13,15},
{17,19,21,23}
};
main()
{
inti,(*b)[4];
b=a+1;/*b指向二维数组的第1行,此时*b[0]或**b是a[1][0]*/
for(i=1;i<=4;b=b[0]+2,i++)/*修改b的指向,每次增加2*/
printf("%d\t",*b[0]);
printf("\n");
for(i=0;i<2;i++){
b=a+i;
/*修改b的指向,
每次跳过二维数组的一行*/
printf("%d\t",*(b[i]+1));
}
printf("\n");
}
程序运行结果如下:
9
13
17
21
3
11
193.字符指针
我们已经知道,字符串常量是由双引号括起来的字符序列,例如:
"astring"
就是一个字符串常量,该字符串中因为字符a后面还有一个空格字符,所以它由8个字符序列组成。在程序中如出现字符串常量C编译程序就给字符串常量按排一存贮区域,这个区域是静态的,在整个程序运行的过程中始终占用,平时所讲的字符串常量的长度是指该字符串的字符个数,但在按排存贮区域时,C编译程序还自动给该字符串序列的末尾加上一个空字符'\0',用来标志字符串的结束,因此一个字符串常量所占的存贮区域的字节数总比它的字符个数多一个字节。
C语言中操作一个字符串常量的方法有:
(1).把字符串常量存放在一个字符数组之中,例如:
chars[]="astring";
数组s共有9个元素所组成,其中s[8]中的内容是'\0'。实际上,在字符数组定义的过程中,编译程序直接把字符串复写到数组中,即对数组s初始化。
(2).用字符指针指向字符串,然后通过字符指针来访问字符串存贮区域。
当字符串常量在表达式中出现时,根据数组的类型转换规则,它被转换成字符指针。因此,若我们定义了一字符指针cp:
char*cp;
于是可用:
cp="astring";
使cp指向字符串常量中的第0号字符a,如图7.所示。
cp
┏━━━┓
┏━┳━┳━┳━┳━┳━┳━┳━┳━┓
┃
─╂─→┃a┃
┃s┃t┃r┃i┃n┃g┃\0┃
┗━━━┛
┗━┻━┻━┻━┻━┻━┻━┻━┻━┛
图7.
以后我们可通过cp来访问这一存贮区域,如*cp或cp[0]就是字符a,
而cp[i]或*(cp+i)就相当于字符串的第i号字符,但企图通过指针来修改字符串常量的行为是没有意义的。4.指针数组
因为指针是变量,因此可设想用指向同一数据类型的指针来构成一个数组,
这就是指针数组。数组中的每个元素都是指针变量,根据数组的定义,指针数组
中每个元素都为指向同一数据类型的指针。指针数组的定义格式为:
类型标识*数组名[整型常量表达式];
例如:
int*a[10];
定义了一个指针数组,数组中的每个元素都是指向整型量的指针,该数组由10个元素组成,即a[0],a[1],a[2],...,a[9],它们均为指针变量。a为该指针数组名,和数组一样,a是常量,不能对它进行增量运算。a为指针数组元素a[0]的地址,a+i为a[i]的地址,*a就是a[0],*(a+i)就是a[i]。
为什么要定义和使用指针数组呢?主要是由于指针数组对处理字符串提供了更大的方便和灵活,使用二维数组对处理长度不等的正文效率低,而指针数组由于其中每个元素都为指针变量,因此通过地址运算来操作正文行是十分方便的。指针数组和一般数组一样,允许指针数组在定义时初始化,但由于指针数组的每个元素是指针变量,它只能存放地址,所以对指向字符串的指针数组在说明赋初值时,是把存放字符串的首地址赋给指针数组的对应元素,例如下面是一个书写函数month_name(n),此函数返回一个指向包含第n月名字的字符指针(关于函数,第6节将专门介绍)。
例2:打印1月至12月的月名:
char*month_name(intn)
{
staticchar*name[]={
"Illegalmonth",
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
};
return((n<1||n>12)?name[0]:name[n]);
}
main()
{
inti;
for(i=0;i<13;i++)
printf("%s\n",month_name(i));}结构(struct)
结构是由基本数据类型构成的、并用一个标识符来命名的各种变量的组合。
结构中可以使用不同的数据类型。1.结构说明和结构变量定义
在C语言中,结构也是一种数据类型,可以使用结构变量,因此,
象其它
类型的变量一样,在使用结构变量时要先对其定义。
定义结构变量的一般格式为:
struct结构名
{
类型
变量名;
类型
变量名;
...
}结构变量;
结构名是结构的标识符不是变量名。
类型为第二节中所讲述的五种数据类型(整型、浮点型、字符型、指针型和
无值型)。
构成结构的每一个类型变量称为结构成员,它象数组的元素一样,但数组中
元素是以下标来访问的,而结构是按变量名字来访问成员的。
下面举一个例子来说明怎样定义结构变量。
structstring
{
charname[8];
intage;
charsex[2];
chardepart[20];
floatwage1,wage2,wage3,wage4,wage5;
}person;
这个例子定义了一个结构名为string的结构变量person,
如果省略变量名person,则变成对结构的说明。用已说明的结构名也可定义结构变量。这样定义时上例变成:
structstring
{
charname[8];
intage;
charsex[2];
chardepart[20];
floatwage1,wage2,wage3,wage4,wage5;
};
structstringperson;
如果需要定义多个具有相同形式的结构变量时用这种方法比较方便,它先作结构说明,再用结构名来定义变量。
例如:
structstringTianyr,Liuqi,...;
如果省略结构名,则称之为无名结构,这种情况常常出现在函数内部,用这种结构时前面的例子变成:
struct
{
charname[8];
intage;
charsex[2];
chardepart[20];
floatwage1,wage2,wage3,wage4,wage5;
}Tianyr,Liuqi;2.结构变量的使用
结构是一个新的数据类型,因此结构变量也可以象其它类型的变量一样赋值、运算,不同的是结构变量以成员作为基本变量。
结构成员的表示方式为:
结构变量.成员名
如果将"结构变量.成员名"看成一个整体,
则这个整体的数据类型与结构中该成员的数据类型相同,这样就可象前面所讲的变量那样使用。
下面这个例子定义了一个结构变量,其中每个成员都从键盘接收数据,然后对结构中的浮点数求和,并显示运算结果,同时将数据以文本方式存入一个名为wage.dat的磁盘文件中。请注意这个例子中不同结构成员的访问。
例3:
#include<stdio.h>
main()
{
struct{
/*定义一个结构变量*/
charname[8];
intage;
charsex[2];
chardepart[20];
floatwage1,wage2,wage3,wage4,wage5;
}a;
FILE*fp;
floatwage;
charc='Y';
fp=fopen("wage.dat","w");
/*创建一个文件只写*/
while(c=='Y'||c=='y')
/*判断是否继续循环*/
{
printf("\nName:");
scanf("%s",);
/*输入姓名*/
printf("Age:");
scanf("%d",&a.wage);
/*输入年龄*/
printf("Sex:");
scanf("%d",a.sex);
printf("Dept:");
scanf("%s",a.depart);
printf("Wage1:");
scanf("%f",&a.wage1);
/*输入工资*/
printf("Wage2:");
scanf("%f",&a.wage2);
printf("Wage3:");
scanf("%f",&a.wage3);
printf("Wage4:");
scanf("%f",&a.wage4);
printf("Wage5:");
scanf("%f",&a.wage5);
wage=a.wage1+a.wage2+a.wage3+a.wage4+a.wage5;
printf("Thesumofwageis%6.2f\n",wage);/*显示结果*/
fprintf(fp,"%10s%4d%4s%30s%10.2f\n",,a.age,a.sex,a.depart,wage);/*结果写入文件*/
while(1)
{
printf("Continue?<Y/N>");
c=getche();
if(c=='Y'||c=='y'||c=='N'||c=='n')
break;
}
}
fclose(fp);
}3.结构数组和结构指针
结构是一种新的数据类型,同样可以有结构数组和结构指针。
一、结构数组
结构数组就是具有相同结构类型的变量集合。假如要定义一个班级40个同学的姓名、性别、年龄和住址,可以定义成一个结构数组。如下所示:
struct{
charname[8];
charsex[2];
intage;
charaddr[40];
}student[40];
也可定义为:
structstring{
charname[8];
charsex[2];
intage;
charaddr[40];
};
structstringstudent[40];
需要指出的是结构数组成员的访问是以数组元素为结构变量的,其形式为:
结构数组元素.成员名
例如:
student[0].name
student[30].age
实际上结构数组相当于一个二维构造,第一维是结构数组元素,每个元素是一个结构变量,第二维是结构成员。
注意:
结构数组的成员也可以是数组变量。
例如:
structa
{
intm[3][5];
floatf;
chars[20];
}y[4];
为了访问结构a中结构变量y[2]的这个变量,可写成
y[2].m[1][4]二、结构指针
结构指针是指向结构的指针。它由一个加在结构变量名前的"*"操作符来定
义,例如用前面已说明的结构定义一个结构指针如下:
structstring{
charname[8];
charsex[2];
intage;
charaddr[40];
}*student;
也可省略结构指针名只作结构说明,然后再用下面的语句定义结构指针。
structstring*student;
使用结构指针对结构成员的访问,与结构变量对结构成员的访问在表达方式上有所不同。结构指针对结构成员的访问表示为:
结构指针名->结构成员
其中"->"是两个符号"-"和">"的组合,好象一个箭头指向结构成员。例如要
给上面定义的结构中name和age赋值,可以用下面语句:
strcpy(student->name,"LuG.C");
student->age=18;
实际上,student->name就是(*student).name的缩写形式。
需要指出的是结构指针是指向结构的一个指针,即结构中第一个成员的首地址,因此在使用之前应该对结构指针初始化,即分配整个结构长度的字节空间,这可用下面函数完成,仍以上例来说明如下:
student=(structstring*)malloc(sizeof(structstring));
sizeof(structstring)自动求取string结构的字节长度,malloc()函数定义了一个大小为结构长度的内存区域,然后将其诈地址作为结构指针返回。注意:
1.结构作为一种数据类型,
因此定义的结构变量或结构指针变量同样有局部变量和全程变量,视定义的位置而定。
2.结构变量名不是指向该结构的地址,这与数组名的含义不同,
因此若需要求结构中第一个成员的首地址应该是&[结构变量名]。
4.结构的复杂形式
一、嵌套结构
嵌套结构是指在一个结构成员中可以包括其它一个结构,TurboC允许这种
嵌套。
例如:下面是一个有嵌套的结构
structstring{
charname[8];
intage;
structaddraddress;
}student;
其中:addr为另一个结构的结构名,必须要先进行,说明,即
structaddr{
charcity[20];
unsignedlonzipcode;
chartel[14];
}
如果要给student结构中成员address结构中的zipcode赋值,则可写成:
student.address.zipcode=200001;
每个结构成员名从最外层直到最内层逐个被列出,即嵌套式结构成员的表达方式是:
结构变量名.嵌套结构变量名.结构成员名
其中:嵌套结构可以有很多,结构成员名为最内层结构中不是结构的成员名。
二、位结构
位结构是一种特殊的结构,在需按位访问一个字节或字的多个位时,位结构比按位运算符更加方便。
位结构定义的一般形式为:
struct位结构名{
数据类型变量名:整型常数;
数据类型变量名:整型常数;
}位结构变量;
其中:数据类型必须是int(unsigned或signed)。整型常数必须是非负的整数,范围是0~15,表示二进制位的个数,即表示有多少位。
变量名是选择项,可以不命名,这样规定是为了排列需要。
例如:下面定义了一个位结构。
struct{
unsignedincon:8;
/*incon占用低字节的0~7共8位*/
unsignedtxcolor:4;/*txcolor占用高字节的0~3位共4位*/
unsignedbgcolor:3;/*bgcolor占用高字节的4~6位共3位*/
unsignedblink:1;
/*blink占用高字节的第7位*/
}ch;
位结构成员的访问与结构成员的访问相同。
例如:访问上例位结构中的bgcolor成员可写成:
ch.bgcolor
注意:
1.位结构中的成员可以定义为unsigned,也可定义为signed,
但当成员长度为1时,会被认为是unsigned类型。因为单个位不可能具有符号。
2.位结构中的成员不能使用数组和指针,但位结构变量可以是数组和指针,
如果是指针,其成员访问方式同结构指针。
3.位结构总长度(位数),是各个位成员定义的位数之和,
可以超过两个字节。
4.位结构成员可以与其它结构成员一起使用。
例如:
structinfo{
charname[8];
intage;
structaddraddress;
floatpay;
unsignedstate:1;
unsignedpay:1;
}workers;'
上例的结构定义了关于一个工从的信息。其中有两个位结构成员,每个位结
构成员只有一位,因此只占一个字节但保存了两个信息,该字节中第一位表示工人的状态,第二位表示工资是否已发放。由此可见使用位结构可以节省存贮空间。联合(union)1.联合说明和联合变量定义
联合也是一种新的数据类型,它是一种特殊形式的变量。
联合说明和联合变量定义与结构十分相似。其形式为:
union联合名{
数据类型成员名;
数据类型成员名;
...
}联合变量名;
联合表示几个变量公用一个内存位置,在不同的时间保存不同的数据类型
和不同长度的变量。
下例表示说明一个联合a_bc:
uniona_bc{
inti;
charmm;
};
再用已说明的联合可定义联合变量。
例如用上面说明的联合定义一个名为lgc的联合变量,可写成:
uniona_bclgc;
在联合变量lgc中,整型量i和字符mm公用同一内存位置。
当一个联合被说明时,编译程序自动地产生一个变量,其长度为联合中最大的变量长度。
联合访问其成员的方法与结构相同。同样联合变量也可以定义成数组或指针,
但定义为指针时,也要用"->"符号,此时联合访问成员可表示成:
联合名->成员名
另外,联合既可以出现在结构内,它的成员也可以是结构。
例如:
struct{
intage;
char*addr;
union{
inti;
char*ch;
}x;
}y[10];
若要访问结构变量y[1]中联合x的成员i,可以写成:
y[1].x.i;
若要访问结构变量y[2]中联合x的字符串指针ch的第一个字符可写成:
*y[2].x.ch;
若写成"y[2].x.*ch;"是错误的。
2.结构和联合的区别
结构和联合有下列区别:
1.结构和联合都是由多个不同的数据类型成员组成,但在任何同一时刻,
联合中只存放了一个被选中的成员,而结构的所有成员都存在。
2.对于联合的不同成员赋值,将会对其它成员重写,
原来成员的值就不存在了,而对于结构的不同成员赋值是互不影响的。
下面举一个例了来加对深联合的理解。
例4:
main()
{
union{
/*定义一个联合*/
inti;
struct{
/*在联合中定义一个结构*/
charfirst;
charsecond;
}half;
}number;
number.i=0x4241;
/*联合成员赋值*/
printf("%c%c\n",number.half.first,mumber.half.second);
number.half.first='a';
/*联合中结构成员赋值*/
number.half.second='b';
printf("%x\n",number.i);
getch();
}
输出结果为:
AB
6261
从上例结果可以看出:当给i赋值后,其低八位也就是first和second的值;当给first和second赋字符后,这两个字符的ASCII码也将作为i的低八位和高八位。枚举(enum)枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见。
例如表示星期的SUNDAY,MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,
SATURDAY,就是一个枚举。
枚举的说明与结构和联合相似,其形式为:
enum枚举名{
标识符[=整型常数],
标识符[=整型常数],
...
标识符[=整型常数],
}枚举变量;
如果枚举没有初始化,即省掉"=整型常数"时,则从第一个标识符开始,顺次赋给标识符0,1,2,...。但当枚举中的某个成员赋值后,其后的成员按依次加1的规则确定其值。
例如下列枚举说明后,x1,x2,x3,x4的值分别为0,1,2,3。
enumstring{x1,x2,x3,x4}x;
当定义改变成:
enumstring
{
x1,
x2=0,
x3=50,
x4,
}x;
则x1=0,x2=0,x3=50,x4=51
注意:
1.枚举中每个成员(标识符)结束符是",",
不是";",最后一个成员可省略","。
2.初始化时可以赋负数,以后的标识符仍依次加1。
3.枚举变量只能取枚举说明结构中的某个标识符常量。
例如:
enumstring
{
x1=5,
x2,
x3,
x4,
};
enumstrigx=x3;
此时,枚举变量x实际上是7。类型说明类型说明的格式为:
typedef类型定义名;
类型说明只定义了一个数据类型的新名字而不是定义一种新的数据类型。这
里类型是TurboC许可的任何一种数据类型。定义名表示这个类型的新名字。
例如:用下面语句定义整型数的新名字:
typedefintSIGNED_INT;
使用说明后,SIGNED_INT就成为int的同义词了,此时可以用SIGNED_INT定义整型变量。
例如:
SIGNED_INTi,j;(与inti,j等效)。
但longSIGNED_INTi,j;是非法的。
typedef同样可用来说明结构、联合以及枚举。
说明一个结构的格式为:
typedefstruct{
数据类型
成员名;
数据类型
成员名;
...
}结构名;
此时可直接用结构名定义结构变量了。例如:
typedefstruct{
charname[8];
intclass;
charsubclass[6];
floatmath,phys,chem,engl,biol;
}student;
studentLiuqi;
则Liuqi被定义为结构数组和结构指针。
在第二节讲过的文件操作中,用到的FILE就是一个已被说明的结构,其说明如下:
typedefstruct
{
shortlevel;
unsignedflags;
charfd;
unsignedcharhold;
shortbsize;
unsignedchar*buffer;
unsignedchar*curp;
unsignedistemp;
shorttoken;
}FILE
这个结构说明已包含在stdio.h中,用户只要直接用FILE定义文件指针变量就可以。事实上,引入类型说明的目的并非为了方便,而是为了便于程序的移植。预处理指令
由ANSI的标准规定,预处理指令主要包括:
#define
#error
#if
#else
#elif
#endif
#ifdef
#ifndef
#undef
#line
#pragma
由上述指令可以看出,每个预处理指令均带有符号"#"。下面只介绍一些常
用指令。
1.#define指令
#define指令是一个宏定义指令,定义的一般形式是:
#define宏替换名字符串(或数值)
由#define指令定义后,
在程序中每次遇到该宏替换名时就用所定义的字符串(或数值)代替它。
例如:可用下面语句定义TRUE表示数值1,FALSE表示0。
#defineTRUE1
#defineFALSE0
一旦在源程序中使用了TRUE和FALSE,编译时会自动的用1和0代替。
注意:
1.在宏定义语名后没有";"
2.在TurboC程序中习惯上用大写字符作为宏替换名,而且常放在程序开头。
3.宏定义还有一个特点,就是宏替换名可以带有形式参数,
在程序中用到时,实际参数会代替这些形式参数。
例如:
#defineMAX(x,y)(x>y)?x:y
main()
{
inti=10,j=15;
printf("TheMaxmumis%d",MAX(i,j);
}
上例宏定义语句的含义是用宏替换名MAX(x,y)代替x,y中较大者,
同样也可定义:
#defineMIN(x,y)(x<y)?x:y
表示用宏替换名MIN(x,y)代替x,y中较小者。
2.#error指令
该指令用于程序的调试,当编译中遇到#error指令就停止编译。其一般形式为:
#error出错信息
出错信息不加引号,当编译器遇到这个指令时,显示下列信息并停止编译。
Fatal:filenamelinenameerrordirective3.#include指令
#include指令的作用是指示编译器将该指令所指出的另一个源文件嵌入#include指令所在的程序中,文件应使用双引号或尖括号括起来。TurboC库函数的头文件一般用#include指令在程序开关说明。
例如:
#include<stdio.h>
程序也允许嵌入其它文件,例如:
main()
{
#include<help.c>
}
其中help.c为另一个文件,内容可为
printf("Gladtomeetyouhere!");
上例编译时将按集成开发环境的Options/Directories/Includedirectories中指定的包含文件路径查找被嵌入文件。4.#if、#else、#endif指令
#if、#els和#endif指令为条件编择指令,它的一般形式为:
#if常数表达式
语句段;
#else
语句段;
#endif
上述结构的含义是:若#if指令后的常数表达式为真,则编译#if到#else之间的程序段;否则编译#else到#endif之间的程序段。
例如:
#defineMAX200
main()
{
#ifMAX>999
printf("compiledforbigger\n");
#else
printf("compiledforsmall\n");
#endif
}
5.#undef指令
#undef指令用来删除事先定义的宏定义,其一般形式为:
#undef宏替换名
例如:
#defineTRUE1
...
#undefTURE
#undef主要用来使宏替换名只限定在需要使用它们的程序段中。函数
C程序是由一组或是变量或是函数的外部对象组成的。函数是一个自我包含的完成一定相关功能的执行代码段。我们可以把函数看成一个"黑盒子",你只要将数据送进去就能得到结果,而函数内部究竟是如何工作的的,外部程序是不知道的。外部程序所知道的仅限于输入给函数什么以及函数输出什么。函数提供了编制程序的手段,使之容易读、写、理解、排除错误、修改和维护。
C程序中函数的数目实际上是不限的,如果说有什么限制的话,那就是,一个C程序中必须至少有一个函数,而且其中必须有一个并且仅有一个以main为名,这个函数称为主函数,整个程序从这个主函数开始执行。
1.函数的说明与定义
C语言中所有函数与变量一样在使用之前必须说明。所谓说明是指说明函数是什么类型的函数,一般库函数的说明都包含在相应的头文件<*.h>中,例如标准输入输出函数包含在stdio.h中,非标准输入输出函数包含在io.h中,
以后在使用库函数时必须先知道该函数包含在什么样的头文件中,在程序的开头用#include<*.h>或#include"*.h"说明。只有这样程序在编译,连接时TurboC才知道它是提供的库函数,否则,将认为是用户自己编写的函数而不能装配。1.1函数说明1.经典方式
其形式为:
函数类型
函数名();
2.ANSI规定方式
其形式为:
函数类型
函数名(数据类型
形式参数,
数据类型
形式参数,......);
其中:函数类型是该函数返回值的数据类型,可以是以前介绍的整型(int),
长整型(long),字符型(char),单浮点型(float),双浮点型(double)以及无值型(void),也可以是指针,包括结构指针。无值型表示函数没有返回值。
函数名为C语言的标识符,小括号中的内容为该函数的形式参数说明。可以只有数据类型而没有形式参数,也可以两者都有。对于经典的函数说明没有参数信息。如:
intputlll(intx,inty,intz,intcolor,char*p)/*说明一个整型函数*/
char*name(void);
/*说明一个字符串指什函数*/
voidstudent(intn,char*str);/*说明一个不返回值的函数*/
floatcalculate();
/*说明一个浮点型函数*/
注意:如果一个函数没有说明就被调用,编译程序并不认为出错,而将此函数默认为整型(int)函数。因此当一个函数返回其它类型,又没有事先说明,
编译时将会出错。1.2函数定义
函数定义就是确定该函数完成什么功能以及怎么运行,相当于其它语言的一个子程序。C语言对函数的定义采用ANSI规定的方式。即:
函数类型
函数名(数据类型形式参数;数据类型形式参数...)
{
函数体;
}
其中函数类型和形式参数的数据类型为C语言的基本数据类型。函数体为C语言提供的库函数和语句以及其它用户自定义函数调用语句的组合,并包括在一对花括号"{"和"}"中。
需要指出的是一个程序必须有一个主函数,其它用户定义的子函数可以是任意多个,这些函数的位置也没有什么限制,可以在main()函数前,
也可以在其后。C语言将所有函数都被认为是全局性的。而且是外部的,即可以被另一个文件中的任何一个函数调用。2函数的调用2.1
函数的简单调用
C语言调用函数时直接使用函数名和实参的方法,也就是将要赋给被调用
函数的参量,按该函数说明的参数形式传递过去,然后进入子函数运行,运行结束后再按子函数规定的数据类型返回一个值给调用函数。使用TurboC2.0的库函数就是函数简单调用的方法。举例说明如下:
例1:
#include<stdio.h>
intmaxmum(intx,inty,intz);
/*说明一个用户自定义函数*/
intmain()
{
inti,j,k;
printf("i,j,k=?\n");
scanf("%4d%4d%4d",&i,&j,&k);
maxmum(i,j,k);
getch();
return0;
}
maxmum(intx,inty,intz)
{
intmax;
max=x>y?x:y;
max=max>z?max:z;
printf("Themaxmumvalueofthe3datais%d\n",max);
}
2.2函数参数传递
一、调用函数向被调用函数以形式参数传递
用户编写的函数一般在对其说明和定义时就规定了形式参数类型,因此调用这些函数时参量必须与子函数中形式参数的数据类型、顺序和数量完全相同,否则在调用中将会出错,得到意想不到的结果。
注意:
当数组作为形式参数向被调用函数传递时,只传递数组的地址,而不是将整个数组元素都复制到函数中去,即用数组名作为实参调用子函数,调用时指向该数组第一个元素的指针就被传递给子函数。因为在TurboC2.0中,没有下标的数组名就是一个指向该数组第一个元素的指针。当然数组变量的类型在两个函数中必须相同。
用下述方法传递数组形参。
例2:
#include<stdio.h>
voiddisp(int*n);
intmain()
{
intm[10],i;
for(i=0;i<10;i++)
m[i]=i;
disp(m);
/*按指针方式传递数组*/
getch();
return0;
}
voiddisp(int*n)
{
intj;
for(j=0;j<10;j++)
printf("%3d",*(n++));
printf("\n");
}
另外,当传递数组的某个元素时,数组元素作为实参,此时按使用其它简单变量的方法使用数组元素。例2按传递数组元素的方法传递时变为:
#include<stdio.h>
voiddisp(intn);
intmain()
{
intm[10],i;
for(i=0;i<10;i++){
m[i]=i;
disp(m[i]);
/*逐个传递数组元素*/
}
getch();
return0;
}
voiddisp(intn)
{
printf("%3d\t");
}
这时一次只传递了数组的一个元素。二、被调用函数向调用函数返回值
一般使用return语句由被调用函数向调用函数返回值,该语句有下列用途:
1.它能立即从所在的函数中退出,返回到调用它的程序中去。
2.返回一个值给调用它的函数。
有两种方法可以终止子函数运行并返回到调用它的函数中:一是执行到函数的最后一条语句后返回;一是执行到语句return时返回。前者当子函数执行完后仅返回给调用函数一个0。若要返回一个值,就必须用return语句。只需在return语句中指定返回的值即可。例1返回最大值时变为:
例3:
#include<stdio.h>
intmaxmum(intx,inty,intz);
/*说明一个用户自定义函数*/
intmain()
{
inti,j,k,max;
printf("i,j,k=?\n");
scanf("%4d%4d%4d",&i,&j,&k);
max=maxmum(i,j,k);
/*调用子函数,并将返回值赋给max*/
printf("Themaxmumvalueis%d\n",max);
getch();
return0;
}
maxmum(intx,inty,intz)
{
intmax;
max=x>y?x:y;
/*求最大值*/
max=max>z?max:z;
return(max);
/*返回最大值*/
}
return语句可以向调用函数返回值,但这种方法只能返回一个参数,在许多情况下要返回多个参数,这是用return语句就不能满足要求。TurobC2.0提供了另一种参数传递的方法,就是调用函数向被调用函数传递的形式参数不是传递变量本身,而是传递变量的地址,当子函数中向相应的地址写入不同的数值之后,也就改变了调用函数中相应变量的值,从而达到了返回多个变量的目的。
例4:
#include<stdio.h>
voidsubfun(int*m,int*n);
/*说明子函数*/
intmain()
{
inti,j;
printf("i,j=?\n");
scanf("%d,%d",&i,&j);/*从键盘输入2个整数*/
printf("Inmainbeforecalling\n"/*输出此2数及其乘积*/
"i=%-4dj=%-4di*j=%-4d\n",i,j,i*j);
subfun(&i,&j);
/*以传送地址的方式调用子函数*/
printf("Inmainaftercalling\n"/*调用子函数后输出变量值*/
"i=%-4dj=%-4di*j=%-4d\n",i,j,i*j);
getch();
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年病例审核专家考试试题及答案
- 2026年四川科技职业学院单招职业适应性测试必刷测试卷含答案
- 2025年汽车租赁押金合同样本
- 2026年上海商学院单招职业倾向性测试题库完美版
- 怀孕期间协议书
- 合作收购股权协议书
- 工程介绍费协议书
- 现代农业产业示范区基础设施建设项目投资计划书
- 私底下签的买卖协议书
- 2026年台州学院单招综合素质考试必刷测试卷附答案
- 中国人民政协课件
- 腹痛科普课件
- 除尘布袋更换应急救援预案(3篇)
- 2025年广西桂林生态资源开发集团有限公司公开招聘2人笔试参考题库附答案解析
- 小学生中草药课件
- DB64∕T 1561-2022 养老机构安宁服务规范
- 图书馆建设项目数字化方案
- 台球助教培训课件
- 麻醉疼痛护理科普知识精讲
- 2025年乡村振兴战略技能知识考试题与答案
- 2024年天津自然博物馆招聘制社会化工作人员考试真题
评论
0/150
提交评论