一、。结构体基础知识
结构体属于聚合数据类型,C语言提供了两种聚合数据类型,数组和结构体。
数组里面保存的是同类型的元素的集合,它的每个元素是通过下标引用或者指针间接访问来选择的。
结构也是一些值的集合,这些值称为它的成员,但一个结构的各个成员可能具有不同的类型,他们需要通过名字去访问,那个成员都有自己的名字。
1.结构声明
举个例子:
struct SIMPLE
{
int a;
char b;
flaot c;
}; //注意这里必须有一个分号
这个结构体包含三个成员变量,分别为int型的a,char型的b,float型的c,SIMPLE为一个标签。这个声明把标签SIMPLE和这个成员列表联系在一起。注意这只是一个声明,并没有提供变量列表,所以它并未创建任何变量。已经声明之后的结构体struct SIMPLE 类似于int,double,它相当于一个结构体类型的标识符,可以用它来创建结构体变量。比如:
struct SIMPLE x;
struct SIMPLE y[20],*z;
这里的x,y和z都是同一类型的结构体,可以互相赋值,比如z=&x。
需要注意的是,如果不使用标签去声明结构体,必须在结构体后面直接创建结构体变量。
struct{
int a;
char b;
float c;
}x;
struct{
int a;
char b;
float c;
}y[20],*z;
这里虽然声明了两个结构体类型,而且他们的成员变量一摸一样,但是它们并不是同一类型的结构体变量,没有标签系统会认为这是两个不同类型的结构体,因此z=&x是非法的。而且这种结构体只能创建一次,无法创建第二个变量。
2.结构成员
结构体成员可以是标量、数组、指针甚至是其他结构体。
这里有一个更为复杂的例子:
struct COMPLEX{
float f;
int a[20];
long *lp;
struct SIMPLE s;
struct SIMPLE sa[10];
struct SIMPLE *sp;
};
一个结构体的成员的名字可以和其他结构的成员名字相同,所以这个结构的成员a并不会与struct SIMPLE s 的成员a冲突。
3.结构成员的直接访问和间接访问
结构变量的成员是通过点操作符(.)访问的。点操作符接受两个操作数,左操作数解释结构变量的名字,有操作数就是需要访问的成员的名字。这个表达式的结果就是指定的成员。例如:
struct COMPLEX comp;
//假设使用上面的结构体
comp.a; //访问comp结构体的a成员变量,a是一个数组,所以comp.a代表它的数组名
(comp.s).a; //访问s结构体的a成员,因为(.)的结合性是从左向右的所以括号可以取消comp.s.a
((comp.sa)[4]).c; //
当你有一个指向结构体地址的指针p时,要取出结构体成员变量,首先想到的是对指针进行解引用操作,拿出结构体名,然后用点操作符找到结构体成员变量。因为*操作的优先级低于(.)操作,所以必须加括号(*p).a。但是为了方便起见有一个更方便的操作符就有了,称为箭头操作符:
p->a;
p->b;
p->c;
结构体指针可以这样访问结构体成员变量。
4.结构的自引用
在一个结构内部包含一个类型为该结构本身的成员:
struct SELF_REF1{
int a;
struct SELF_REF1 b;
int c;
};
这种类型的自引用是非法的,因为成员b里面又包含着一个同类型的结构体,这样下去就永无止境,更不知道这个结构体有多大。但如果用指针的方法声明却是合法的:
strcut SELF_REF2{
int a;
struct SELF_REF2 *b;
int c;
};
这个声明和前面哪个声明的区别在于成员b是一个指针而不是一个结构,编译器在结构的长度确定之前就已经知道指针的长度。
5.不完整声明
看下面这个例子:
struct A {
struct B *partner;
};
struct B {
struct A *partner;
};
这是错误的,因为在声明A的时候发现它里面有结构体B的指针,但是因为结构体B没有声明,所以系统找不到B。当你把B放在前面那么B里面有结构体A的指针,会产生同样的错误。处理的办法是创建一个不完整声明:
struct B;
struct A {
struct B *partner;
};
struct B {
struct A *partner;
};
在A的上面加上B的不完整声明,这样告诉计算机有一个B类型的结构的,就不会出错了。
6.结构的初始化
结构的初始化方式和数组的初始化很相似,一个位于一堆花括号内部、由逗号分隔的初始值列表可用于结构各个成员的初始化,这些值根据结构成员的顺序写出。如果初始列表的值不够,剩余的结构成员将使用缺省值进行初始化。
这里有一个例子:
struct INIT_EX{
int a;
short b[10];
Simple c;
}x = {
10,
{1,2,3,4,5},
{25,'x',1.9}
};
二、位段
位段的成员必须是int、unsigned int和signed int的类型,其次,在成员的后面是一个冒号和一个整数,这个整数指定该位段所占用的位的数目。
下面是一个位段声明的例子:
struct CHAR{
unsigned ch : 7;
unsigned font: 6;
unsigned size: 19;
};
struct CHAR ch1;
这个位段所占的空间大小为8个字节,位段的空间上需要按照以四个字节或者一个字节(字符)的方式来开辟的,6+7=13,13+19>32,所以得再开辟4个字节。
位段涉及很多不确定因素,所以位段是不跨平台的。
位段不能跨平台的因素:
1.系统的存储方式不一样(大小端),从左到右还是从右到左。
2.int被系统当作正号还是负号不确定。
3.32位机器定义的27位,位段,无法在16位机器中运行。
4.当第二个位段成员过大,第一个位段成员的剩余空间无法满足它时,是紧贴着第一个储存,还是重新开辟空间,浪费剩余空间。
总结
位段虽然可以很好的节省空间,但是它有跨平台的限制。
三、枚举
枚举就是一一列举,把可能的值一一列举出来。例如:
enum Color
{
RED,
GREEN,
BLUE
};
以上定义的enum Color是枚举类型,{}内的是枚举类型的可能取值,也叫枚举常量,它们用 “,”隔开,默认值是0、1、2。。。也可以在定义的时候赋初值:
enum Color
{
RED=1,
GREEN=3,
BLUE=7
};
enum Color s = RED; //枚举变量初始赋值只能使用枚举常量
s = 10; //之后的赋值没有上面这个要求
我们可以用#define来定义常量,为什么还要使用枚举?
1.枚举的可读性和可维护性更高。
2.枚举具有类型检查,更严谨。
3.防止命名污染。
4.便于调试。
5.使用方便,一次可以定义多个。
四、联合
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一些成员,特点就是这些成员使用同一块空间(所以联合也叫共用体)。
例如:
union S
{
int a;
char b;
};
struct S c; //定义联合变量
printf("%d",sizeof(c)); //计算联合变量的大小
上面计算的大小是4,因为变量公用一块空间,大小至少是最大的成员的大小,并且要是最大对齐数的整数倍。
面试题大小端问题可以用联合很好的解决。
联合和结构体的巧妙使用:
//将long类型的IP地址,转化为点分十进制的表示形式
union ip_addr
{
unsigned long addr,
struct
{
unsigned char c1;
unsigned char c2;
unsigned char c3;
unsigned char c4;
}ip;
};
union ip_addr my_ip;
my_ip.addr = 176238749;
printf("%d.%d.%d.%d\n",my_ip.ip.c1,my_ip.ip.c2,my_ip.ip.c3,my_ip.ip.c4);