现在的位置: 首页 > 综合 > 正文

C语言中的结构(struct)

2017年08月07日 ⁄ 综合 ⁄ 共 2808字 ⁄ 字号 评论关闭

一、结构的声明与定义

C语言中对结构的声明与定义有很多值得注意的细节。下面列举几个方面:

1、下面是两个结构声明:

struct			//声明1  
{  
    int a;  
    char b;  
    float c;  
}x;  

struct			//声明2  
{  
    int a;  
    char b;  
    float c;  
}y[20], *z;  

以上两个声明被编译器当作两种不同的类型,即使它们的成员列表完全相同。因此,变量y、z的类型与x的类型不同,z = &x也是非法的。

2、标签允许多个声明使用同一个成员列表,并创建同一种类型的结构。例如:

struct SIMPLE  
{  
    int a;  
    char b;  
    float c;  
};  

以上声明把标签SIMPLE和成员列表联系在一起。该声明并没有提供变量列表,所以它并未创建任何变量。使用标签SIMPLE创建变量如下:

struct SIMPLE x;  
struct SIMPLE y[20], *z;  

此时y、z和x都是同一种类型的结构变量。

3、声明结构时经常使用typedef创建一种新的类型。例如:

typedef struct    
{  
    int a;  
    char b;  
    float c;  
}simple;  
simple x;  
simple y[20], *z;  

此时simple是类型名而不是结构标签。当然,y、z和x也都是同一种类型的结构变量。

4、结构的自包含和自引用。

(1)结构不能自包含,无论是直接的还是间接的。例如以下声明是非法的:

struct SELF_PRE1  
{  
    int a;  
    struct SELF_PRE1 b;  
    float c;  
};  

因为编译器无法计算sizeof值,也不知道该给这样的对象分配多少存储空间。

(2)结构的自引用是合法的。例如以下声明是合法的:

struct SELF_PRE2  
{  
    int a;  
    struct SELF_PRE2 *b;  
    float c;  
};  

因为任何类型的指针大小都一样,给指针分配存储空间的时候不需要知道它指向对象的类型细节。

5、不完整声明。

不完整声明指的是:声明一个作为结构标签的标示符,把这个标签用在不需要知道这个结构的长度的声明中。例如声明指向这个结构的指针。

struct B;   
struct A  
{  
    struct B *partner;  
};  
struct B  
{  
    struct A *partner;  
};  

以上声明中,A的成员列表需要标签B的不完整声明。一旦A被声明之后,B的成员列表也可以被声明。

注意以下声明的陷阱:

typedef struct    
{  
    int a;  
    SELF_PRE3 *b;  
    float c;  
}SELF_PRE3;  

类型名SELF_PRE3直到声明的末尾才定义,所以在结构声明的内部它尚未定义。解决方案是定义一个结构标签来声明b。如下:

typedef struct SELF_PRE3_TAG  
{  
    int a;  
    struct SELF_PRE3_TAG *b;  
    float c;  
}SELF_PRE3; 

二、位域

多数情况,以字节为基本单位的存储模式会浪费大量内存空间。有些设备提供的存储空间有限,例如嵌入式系统,因此必须对存储开销“精打细算”。可使用位域和位运算来解决。

1、位域的声明

位域以单个的位(bit)为单位来设计一个结构所需要的存储空间。位域的声明是在结构的成员名后面加一个冒号和一个整数,这个整数指定该位域所占用的位的数目。声明位域时注意:

(1)C语言位域成员必须声明为int、signed int或unsigned int类型,C++还允许使用char、long等类型。

(2)signed int类型数据的正负符号要占用1位,因此该类型的位域成员长度应该至少为2。

(3)最好用signed或unsigned显式声明位域,因为如果把位域声明为int类型,它究竟被解释为有符号数还是无符号数由编译器决定。

struct CHAR
{
	unsigned ch : 7;
	unsigned font : 6;
	unsigned size :19;
};
struct CHAR ch;

以上是一个位域声明的例子。位域能够利用存储ch和font所剩余的位来增加size的位数,避免了声明一个32位的整数来存储size位域。在32位机器上,这个声明将根据下面两种可能的方法创建ch。

2、位域的使用

使用位域的理由是:能够把长度为奇数的数据包装在一起,节省存储空间;能够很方便地访问一个整型值的部分内容。使用位域时有以下几点需要注意:

(1)结构中不同长度的字段实际上存储于一个或多个整型变量中。

(2)不要定义超越类型最大位数的位域成员。(这与平台相关)例如上面声明的位域,许多16位整数机器的编译器会把这个声明标志为非法的,因为最后一个位段的长度超过了整型的长度。

(3)可定义非具名的位域成员,其作用相当于占位符,可用来隔离两个相邻的位域成员。例如:

struct DATETIME
{
	unsigned int day : 5;
	unsigned int : 2;
	unsigned int hour : 5;
};

由于第二个位域成员没有名字,因此不能直接访问它所在的位。

(4)可定义长度为0的位域成员,其作用是迫使下一个成员从下一个完整的机器字开始分配空间。例如:

struct DATETIME
{
	unsigned int day : 5;
	unsigned int : 0;
	unsigned int hour : 5;
};

VC6.0环境下sizeof(DATETIME) = 8。

(5)不能取一个位域对象的数据成员的地址,即使该成员完全与字节边界对齐,因为字节是编址的最小单位而不是位。但可以取位域对象的地址,即使位域所有成员的位数总和达不到整字节的倍数,位域对象也会对齐到机器字长。

(6)不能把位域成员当作位的数组,因此不能使用访问数组元素的方法来访问位域成员的单个位。

(7)使用位域节省存储空间会导致程序运行速度的下降,因为计算机无法直接寻址到单个字节的某些位,必须通过额外的代码来实现。这种矛盾是由计算机的基本原理决定的,在“内存空间”和“运行速度”无法同时优化的情况下,由应用需求来决定优化哪一个。

(8)在设计位域的时候,最好不要让一个位域成员跨越一个不完整的字节来存放,因为这样会增加计算机运算的开销。

(9)注重可移植性的程序应避免使用位域。由于下面几点与实现有关的依赖性,位域在不同的系统中可能有所不同。

1) int位域被当作有符号数还是无符号数。

2)位域中位的最大数目。许多编译器把位域成员的长度限制在一个整型值的长度之内,所以一个能够运行于32位整数的机器上的位域声明可能在16位整数的机器上无法运行。

3)位域中的成员在内存中是从左向右分配的还是从右向左分配。

4)当一个声明指定了两个位域,第2个位域比较大,无法容纳于第一个位域剩余的位时,编译器有可能把第2个位域放在内存的下一个字,也可能直接放在第1个位域后面,从而在两个内存位置的边界上形成重叠。

三、结构的初始化

结构的初始化有两种方法:

1、使用memset函数或其他类似的内存初始化函数。

2、定义的时候指定初始值。可以仅指定第一个成员的初值来初始化对象,后面的成员将自动初始化为0,就像数组的初始化一样。

抱歉!评论已关闭.