结构和其他数据形式

声明结构的过程定义结构变量的过程可以被合并成一步,如下所示,将声明和变量定义合并在一起,是不需要使用标记的一种情况。结构设计告诉编译器如何表示数据,但是它没有让计算机为数据分配空间。

struct {
    char title[MAXTITL];
    char author[MAXAUTL];
    float value;
}library;
初始化结构体变量可以使用与初始化数组相似的语法:
struct book library={
    "The Pirate and the Devious Damsel",
    "Renee Vivotte",
    1.95
    };
简而言之,使用一个用花括号括起来的、逗号分隔的初始化项目列表进行初始化。每个初始化项目必须要和要初始化的结构成员类型相匹配。

访问结构成员

结构成员运算符点(.)访问结构中的各个成员。“.”的优先级比“&”高,因此&library.value和&(library.value)是一样的。
结构的指定初始化项目使用点运算符和成员名(而不是方括号和索引值)来标识具体的元素。

结构数组

声明结构数组

声明一个结构数组和声明其他任何类型的数组一样。
struct book library[MAXBKS];
这条语句声明library为一个具有MAXBKS个元素的数组,数组的每个元素都是book类型的结构。library本身不是结构名,它是元素类型为struct book结构的数组名。

声明和初始化结构指针

struct guy *him;
声明很简单,首先是关键字struct,其次是结构标记guy,然后是一个*号,紧跟着是指针名。
和数组不同,一个结构的名字不是该结构的地址,必须使用&运算符。
“.”运算符比“*”的优先级高。

结构和函数

向函数传递结构信息

函数的参数可以向函数传递值。每个值是个数字,可能是int型、float型、ASCII字符编码,或者是个地址。ANSI C允许把结构作为参数,因此,现在的C实现允许把结构作为参数传递,或把指向结构的指针作为参数传递。如果只关心结构一部分,还可以将结构成员作为参数传递给函数。

传递结构成员

只要结构成员是具有单个值得数据类型(即:int及其相关类型、char、float、double或指针),就可以把它作为参数传递给一个接受这个特定类型的函数。

使用结构地址

必须使用&运算符才能得到结构的地址。和数组名不一样,单独的结构名不是该结构地址的同义词。

把结构作为参数传递

其他结构特性

现在的C允许把一个结构赋值给另一个结构,不能对数组这样做。也就是说,如果n_data和o_data是同一类型的结构,可以像下面这样做:
o_data=n_data;  //把一个结构赋值给另一个结构
这就使o_data的每个成员都被赋成n_data相应成员的值,即使有一个成员是数组也照样完成赋值。
在现在的C(包括ANSI C)中,结构不仅可以作为参数传递给函数,也可以作为函数返回值返回。把结构作为函数参数可以将结构信息传递给一个函数,使用函数返回结构可以将结构信息从被调用函数传递给调用函数。同样,结构指针也允许双向通信,因此可以使用任一种方法解决编程问题。
通常,程序员为了追求效率而使用结构指针作为函数参数;当需要保护数据、防止意外改变数据时对指针使用const限定词。传递结构值是处理小型结构最常用的方法。

在结构中使用字符数组还是字符指针

是否可以用指向字符的指针代替字符数组?
struct names{
    char first[LEN];
    char last[LED]
};
可以改写成下面代码吗?
struct pnames{
    char *first;
    char *last;
};
答案是可以的。对于struct nams变量veep来说,字符串存储在结构内部;这个结构总共分配了40个字节来存放两个字符串。然而,对于struct pnames变量treas来说,字符串存储在编译器存储字符串常量的任何地方。这个结构中存放的只是两个地址而已,在我们的系统中它总共占用8个字节。struct pnames结构不为字符串分配任何存储空间。它只适用于在另外的地方已经为字符串分配了空间(例如字符串常量或数组中的字符串)。简单地说,pnames结构中的指针应该只用来管理那些已创建的而且在程序其他地方已经分配过空间的字符串。
因此,如果需要一个结构来存储字符串,请使用字符数组成员。存储字符指针有它的用处,但也有被严重误用的可能。

复合文字和结构(C99)

C99新增的的复合文字特性不仅适用于数组,也适用于结构。可以使用复合文字创建一个被用来作为函数参数或被赋值给另一个结构的结构。语法是把类型名写在圆括号中,后跟一个用花括号括起来的初始化项目列表。例如,下面是一个struct book类型的复合文字:
(struct book){"The Idiot","Fyodor Dostoyevsky",6.99}
出现在所有函数外面的复合文字具有静态存储时期,而出现在一个代码块内部的复合文字具有自动存储时期。适用于常规初始化项目列表的语法规则同样也适用于复合文字。这意味着,例如,可以在复合文字中使用指定初始化项目。

伸缩型数组成员(C99)

C99具有一个称为伸缩型数组成员(flexible array member)的新特性。利用这一新特性可以声明最后一个成员是一个具有特殊属性的数组结构。该数组成员的特殊属性之一是它不存在,至少不立即存在。第二个特殊属性是您可以编写适当的代码使用这个伸缩型数组成员,就像它确实存在并且拥有您需要的任何数目元素一样。
声明一个伸缩型数组成员的规则:
伸缩型数组成员必须是最后一个数组成员。
结构中必须至少有一个其他成员。
伸缩型数组就像普通数组一样被声明,除了它的方括号内是空的。
例:
struct flex
{
    int count;
    double average;
    double scores[];
};
如果声明了一个struct flex类型的变量,您不能使用scores做任何事情,因为没有为它分配任何内存空间。实际上,C99的意图并不是让你声明struct flex类型的变量,而是希望您声明一个指向struct flex类型的指针,然后用malloc()来分配足够的空间,以存放struct flex结构的常规内容和伸缩型数组成员需要的任何额外空间。
例如,假设想要用scores表示含有5个double型数值的数组,那么就要这样做:
<pre name="code" class="objc">struct flex *pf;    //声明一个指针
pf=malloc(sizeof(struct flex)+5*sizeof(double));        //请求一个结构和一个数组的空间
 
   

把结构内容保存到文件中

由于结构可以保存多种多样的信息,所以它是建立数据库的重要工具。
一个结构中保存的整套信息用术语来称就是一个记录(record),单个的项目成为字段(field)。
或许最显而易见的也是最没效率的保存记录的方法就是fprintf()。
一个更好的解决方法是使用fread()和fwrite()函数以结构大小为单元进行读写。这些函数在读写时使用了与程序所使用的相同的二进制表示法。例如:
fwrite(&primer,sizeof(struct book),1,pbooks);
这个语句定位到结构primer的开始地址,将该结构的所有字节复制到pbooks相关联的文件中。sizeof(struct book)告诉函数要复制的每一块有多大,1表示只需要复制一块。具有同样参数的fread()函数将把一个结构大小的一块数据从文件复制到&primer指向的位置。简单地说,这些函数一次性读写整个记录,而不是一个字段。

联合简介

联合(union)是一个能在同一个存储空间里(但不同时)存储不同类型数据的数据类型。
使用联合类型的数组,可以创建相同大小单元的数组,每个单元都能存储多种类型的数据。
联合是以与结构同样的方式建立的,也是需要有一个联合模板和一个联合变量。可以在一步中定义它们,也可以使用联合标记在两步中定义。下面是一个带有标记的联合模板的例子:
union hold{
    int digit;
    double bigfl;
    char letter;
};
具有类似声明的结构可以含有一个int型数值 一个double型数值以及一个char型数值,而这个联合可以含有一个int型数值 一个double型数值 一个char型数值。
下面是定义3个hold类型联合变量的例子:
union hold fit;         //hold类型的联合变量
union hold save[10];    //10个联合变量的数组
union hold * pu;        //指向hold类型变量的指针
第一个声明创建一个变量fit。编译器分配足够的空间以保存所描述的可能性的最大需求。在这种情况下,列出的最大可能性是double型数据。在我们的系统里,它需要64位,即8个字节。第二个声明创建了一个save数组,它含有10个元素,每个元素大小为8个字节。第三个声明创建了一个指针,可以存放一个hold联合的地址。
可以初始化一个联合。因为联合只存储一个值,所以初始化的规则与结构的初始化不同。具体地,有3种选择:
1、可以把一个联合初始化为同样类型的另一个联合;
2、可以初始化联合的第一个元素;
3、按照C99标准,可以使用一个指定初始化项目。
union hold valA;
valA.letter='R';
union hold valB=valA;           //把一个联合初始化为另一个联合
union hold valC={88};           //初始化联合的digit成员
union hold valD={.bigfl=118.2}; //指定初始化项目
下面示例了怎样使用联合:
fit.digit=23;       //把23存储在fit中;使用2个字节
fit.bigfl=2.0;      //清除23,存储2.0;使用8个字节
fit.letter='h';     //清除2.0,存储‘h';使用1个字节
点运算符表示正在使用哪种数据类型。在同一个事件只能存储一个值。即使有足够的空间,也不能同时保存一个char类型和一个int类型的值。

枚举类型

枚举类型声明 代表整数常量的符号名称。通过关键字enum,可以创建一个新“类型”并指定它可以具有的值(实际上,enum常量是int类型的,因此在使用int类型的任何地方都可以使用它)。枚举类型的目的是提高程序的可读性。它的语法与结构的语法相同。例如,可以使用这样的声明:
enum spectrum{red,orange,yellow,green,blue,violet};
enum spectrum color;
第一个声明设置spectrum为标记名,从而允许您把enum spectrum作为一个类型名使用。第二个声明使得color成为该类型的一个变量。花括号中的标识符枚举了spectrum变量可能有的值。

默认值

默认时,枚举列表中的常量被指定为整数值0、1、2等等。因此,上面的orange具有值1。

指定值

可以选择常量具有的整数值,只须在声明中包含期望的值:
enum spectrum{red=0,orange=2,yellow=4,green=6,blue=8,violet=10};

enum的用法

注意:枚举类型是内部使用的。如果想输入color值orange,只能输入1,而不是单词orange。或者,可以读入字符串“orange”,并让程序将它转换成值orange。

typedef简介

typedef并不创建新的类型,只是创建了便于使用的标签。它和#define相似,但是它们具有3个不同之处:
1、与#define不同,typedef给出的符号名称仅限于对类型,而不是对值。
2、typedef的解释由编译器,而不是预处理器执行。
3、虽然它的范围有限,但在其受限范围内,typedef比#define更灵活。
例:假设要对1字节的数值使用术语BYTE,只须像定义一个char变量那样定义BYTE,然后在这个定义前面加上关键字typedef,如:
typedef unsigned char BYTE;
之后可以使用BYTE来定义变量:
BYTE x,y[10],*z;
通常,这些定义使用大写字母,以提醒用户这个类型名称实际上是一个符号缩写。
也可以对结构使用typedef:
typedef struct complex{
    float real;
    float imag;
}COMPLEX;
这样就可以使用COMPLEX代替struct complex来表示复数。使用typedef的原因之一是为经常出现的类型创建一个方便的、可识别的名称。

函数和指针

函数指针:假定一个指针指向一个int变量,它保存着这个int变量在内存中存储的地址。同样,函数也有地址,这是因为函数的机器语言实现是由载入到内存的代码组成。 指向函数的指针中保存着函数代码起始处的地址。
当声明一个数据指针时,必须声明它指向的数据的类型。当声明一个函数指针时,必须声明它指向的函数类型。要指定函数类型,就要指出函数的返回类型以及函数的参量类型。
例:有如下函数原型:
void ToUpper(char *);
函数ToUpper()的类型是“具有char *类型的参量,返回类型是void的函数”。要声明指向这种类型的函数的指针pf,可以这样做:
void (*pf)(char *);     //pf是一个指向函数的指针
从这个声明中可以看出,第一对圆括号将运算符*和pf结合在一起,这意味着pf是一个指向函数的指针。这就使得(* pf)是一个函数,并使(char *)作为该函数的参量列表,void作为其返回类型。
总结:声明一个指向特定函数类型的指针,首先声明一个该类型的函数,然后用(* pf)形式的表达式代替函数名称;pf就成为可指向那种类型函数的指针了。
有了函数指针之后,可以把适当类型的函数的地址赋值给它。在这种场合中,函数名可以用来表示函数的地址。

如果返回值是和原函数相同类型的函数指针,要定义成这样:
typedef func_ptr_t (*func_ptr_t) (int)
但是C语言中不允许这样定义一个函数指针。所以只能寻找变通的方法,这个方法就是强制类型转换。我们可以按照以下方式定义:
typedef void (*func_ptr_ret_t) (int)
typedef func_ptr_ret_t (*func_ptr_t) (int)
使用的时候进行强制类型转换,将返回值转换为func_ptr_t,这样就实现了一个能够返回与原函数相同类型函数指针的函数
func_ptr_t func;
func_ptr_t func2;
func=(func_ptr_t)func2(参数);
1、用函数指针表示状态(状态函数)。
2、状态函数接受一个参数(event),并根据event决定下一个状态。
3、状态函数返回一个函数指针,此函数指针的类型也是状态函数指针。即状态函数返回一个指向下一个状态函数的指针。










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值