结构体
结构声明
在声明结构时,必须列出它的所有成员,这个列表包括成员的类型和名字。
struct (tag){
int a;
short b;
float c;
}x;
struct (tag){
int a;
short b;
float c;
}y[20],*z;
z=&x是合法的吗?
非法的。这两个声明被编译器当做两种不同的类型,即使他们的成员列表完全相同,所以z和x的类型完全不同。
想要用同一个结构体创建不同变量,可采用下列方法:
struct simple{
int a;
char b;
float c;
};
struct simple x;
struct simple y[20];
struct simple *z;
声明结构时可以用另一种良好技巧,例:
typedef simple{
int a;
short b;
float c;
};
这种方法和声明一个结构体的效果基本相同,typedef 是创建一种新的类型,区别在于,simple是个类型名,而不是结构体标签,所以后续的声明可以写成这个样子:
simple x;
simple y[20],*z;
如果想在多个源文件中使用同一种类型的结构,应该把结构体声明或typedef声明放在同一个文件下使用。
结构体成员的直接访问
结构体成员是通过(.)直接访问的,点操作符接受两个操作数,左操作数为结构变量的名字,右操作数则是需要访问的成员的名字,例:
struct complex{
float t;
int a[20];
long *lp;
struct simple s;
struct simple sa[10];
struct simple *sp;
}
struct complex com;
com.sa[4].c;//最终指向的是有四个结构成员的结构体名为sa的成员c
com.s.a;
下标引用和点操作符具有相同的优先级,他们的结合性都是从左往右,所以我们可以省略所有括号。
结构成员的间接访问
如果你拥有一个指向结构体指针,那该如何访问这个结构的成员?
应先对指针进行间接访问操作,访问这个结构体,再用点操作符访问这个成员。
例:
(*cp).s;
(*cp).sa[4].c;
为了方便使用,也可以用箭头的来访问:
cp->s;
cp->sa[4]->c;
结构体的自引用
在一个结构体内包含一个类型为该结构体本身的成员是否合法呢?
例:
struct simple{
int a;
struct simple b;
int c;
};
这种自引用是非法的,因为成员b是另一个完整的结构,其内部又包含一个完整的相同类型的结构,好像一个无止境的递归程序。
正确写法如下:
struct simple{
int a;
struct simple *b;
int c;
};
这个声明和上面的声明区别在于b现在是一个指针而非结构体,编译器在结构体长度确定之前就知道指针的长度,所以这种自引用是合法的。
typedef struct{
int a;
simple *b;
short c;
}simple;
上面这种写法是非法的,因为类型名知道末尾才定义,所以在结构内部并不知道的simple *b是什么。
结构体初始化
struct point{
int x;
int y;
}p1; //声明类型的同时定义变量p1;
struct point p2; //定义结构体变量p2;
P1={1,2}; //初始化P1;
struct point{
int x;
int y;
}p1={1,2};
结构、指针和成员
typedef struct{
int a;
short b[2];
}Ex2;
typedef struct Ex{
int a;
char b[3];
EX2 c;
struct Ex *d;
};
Ex x={10,"hi",{5,{-1,25}},0};
Ex *px = &x;
访问结构
Ex *px = &x;
*px表示整个结构:
访问结构体成员
px->a;
创建一个指向整型的指针:
int* pi;
如何让pi也指向整型成员a?
直接让pi=px是错误的,因为他们类型不同不能直接赋值,
pi=(int*)px;
正确表达方式为:
pi = &px->a;
->的优先级高于&,所以不需要使用括号
访问嵌套的结构
px->c
px-c.a
访问指针成员
*px->d;
d是一个指向结构体的指针,此时->作用于成员d所储存的指针值,而此时d包含了一个NULL指针,所以它不指向任何东西,对于一个NULL指针进行解引用操作是个错误,若有些环节不会捕捉到这个错误,在这些机器上,将不会发现这个错误,在这里,我们创建另一个结构:
Ex y;
x.d=&y;
相当于,此时的d指向了一个未初始化的结构体,
内存对齐
计算下面结构体长度:
struct allage{
char a;
int b;
char c;
};
每一个平台上都有一个默认对齐数,假设默认对齐数是4,
则该结构体存储结构为:
位段
位段(或称“位域”,Bit field)为一种数据结构,可以把数据以位的形式紧凑的储存,并允许程序员对此结构的位进行操作。
位段成员必须声明为int、signed int、unsigned int等类型
struct simple{
int a: 2;
int b: 5;
int c: 10;
int d: 30;
};
位段的内存分配
位段中不存在内存对齐的说法,相反,位段是为了更节省空间,用多少字节开辟多少空间,不够用就再开辟空间,如上面的位段结构内存分配:
int a :4; // int为整型,开辟4个字节的空间,32bit,a占4bit,剩余28bit,
int b : 5; /// b占5bit,剩余空间为23bit,
int c : 10; // c占10bit,剩余空间为13bit,
int d : 30; //d占30bit,剩余空间不够存下d,则再开辟4个字节(int为整型,占4个字节)
所以上面这个位段结构一共开辟了2个字节
struct S{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
struct S s={0};
s.a=10;
s.b=12;
s.c=3;
s.d=4;
char a : 3; //char为整型,占一个字节,为8bit,a占3bit,剩余5-bit,
char b : 4; //b占4bit,剩余1bit,
char c : 5; //c占5bit,剩余的1bit不够存c,则再开辟一个字节(char c类型占一个字节),剩余3bit,
char d : 4; //d占4bit,剩余空间不够出存d,则在开辟一个字节
故上面这个位段一共开辟了3个字节的空间。
在内存地址空间中,位段是从右往左存储数据的,上面例子在内存中存储的方式如下图:
这种结构的好处是:
可以使数据单元节省储存空间,当程序需要成千上万个数据单元时,这种方法就显得尤为重要。
位段可以很方便的访问一个整数值的部分内容从而可以简化程序源代码,可以更好的节省空间但在不同的平台上时不可移植的。
联合
联合的所有成员引用的是内存中相同的位置,
union un{
int d;
char i;
double c;
};
联合的大小至少是最大成员的大小。
int main()
{
union un u;
printf("%d",&(u));
printf("%d",&(u.i));
printf("%d",&(u.d));
}
以上打印的地址都是相同的地址,成员i,d,c在使用时都是从地址的起始位置开始使用。
联合还可以用来判断大小端
union Un{
char i;
int c;
};
int check()
{
union Un u;
u.c=1;
return u.i;
}
int main()
{
if(check()==1)
{
printf("小端");
}
else
{
printf("大端");
}
return 0;
}