结构体类型的声明
1.结构体概念
结构体是一些值的集合,这些值称为成员变量,成员变量可以是不同类型的。
2.结构体类型的声明
如果我们要定义一本书,我们该如何定义呢?
例如:
struct book
{
char name[20];
int page;
char auth[20];
};
struct
是结构体关键字,book
是结构体标签名。
需要注意的是,结构体后面的;
不能少。
结构体变量的创建和初始化
1.结构体变量的创建
例如:
struct book
{
char name[20];
int page;
char auth[20];
}n;
struct book s1;
int main()
{
struct book s2;
return 0;
}
n,s1,s2
都是结构体变量,但是前两个是全局变量,在主函数内部的是局部变量,struct book
是结构体类型名。
2.结构体变量的初始化
例如:
struct book
{
char name[20];
int page;
char auth[20];
}n;
struct book s1;
int main()
{
struct book s2 = { "zhangsna",150,"lishi" };
return 0;
}
3。结构体的特殊声明
在声明结构的时候可以不完全的声明,省略结构体的标签名.
例如:
//匿名结构体
struct
{
int i;
char b;
int ret;
};
在使用的时候,需要有以下几点需要注意:
(1)匿名结构体类型如果没有对类型重命名的话,基本上只能使用一次。
4.结构体的自引用
例如:
struct n
{
int i;
struct n *s1;
};
结构体成员访问操作符
结构体成员访问操作符有两个,.
操作符和->
,第二个适用于指针。
第一个的使用方式是:变量名.成员名
第二个的使用方式是:结构体指针.成员名
例如:
struct book
{
char name[20];
int page;
char auth[20];
};
int main()
{
struct book s2 = { "zhangsna",150,"lishi" };
struct book* ptr = &s2;
printf("%s %d %s", s2.name, s2.page, s2.auth);
printf("%s %d %s", ptr->name, ptr->page, ptr->auth);
return 0;
}
结构体内存对齐
1.结构体内存对齐的规则
(1)结构体的第一个成员要对齐到结构体起始位置偏移量为0的地址处。
(2)其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的对齐数与变量大小的较小值。
(3)结构体的总大小为最大对齐数的整数倍。
(4)如果结构体中嵌套了结构体的话,嵌套的结构体成员要对齐到自己成员中最大对齐数的整数倍。结构体的整体大小就是最大对齐数(包括了嵌套的结构体成员)的整数倍的地址处。
例如:
struct S1
{
char c1;
int i;
char c2;
};
首先,结构体的第一个成员要对齐到结构体起始位置偏移量为0的地址处。
其次,int类型的变量的对齐数是4,小于8,因此,对齐数就是4,要在偏移量为4的整数倍的地址处存放。因此放在偏移量为8的地址处。
最后,结构体的总大小是成员当中最大对齐数的整数倍,结构团体的最后一个成员的地址在偏移量为8的地址处,共9个字节,不是最大对齐数的整数倍,因此,结构体的总大小为12个字节。
2.修改默认对齐数
#pragma这个预处理命令,可以可以修改编译器默认的对齐数。
例如:
#pragma pack(1)//设置默认对齐数为1
struct s
{
char c1;
int i;
char c2;
};
#pragma pack()//恢复默认对齐数
int main()
{
printf("%zd\n", sizeof(struct s));//结果是6
return 0;
}
结构体传参
struct s
{
char c1;
int i;
char c2;
};
void print1(struct s n)//第一种传参方式,值传递
{
printf("%c %d %c\n", n.c1, n.i, n.c2);
}
void print2(struct s* ptr)//第二种传参方式,址传递
{
printf("%c %d %c\n", ptr->c1, ptr->i, ptr->c2);
}
int main()
{
struct s s = { 'w',12,'h' };
print1(s);
print2(&s);
return 0;
}
采用哪一种方式更好呢?
如果采用第一种方式的话,会进入print1
函数时,会再次开辟空间,浪费空间和时间。
因此,结构体传参,我们一般会选择传指针。
结构体实现位段
1.什么是位段
位段的声明和结构体是类似的,不过有一些不同。
1.位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以
选择其他类型。
2. 位段的成员名后边有⼀个冒号和⼀个数字。
例如:
struct s
{
int a : 3;
int b : 4;
int c : 5;
int d : 4;
};
s就是一个位段类型。
printf("%zd\n", sizeof(struct s));//结果是多少呢?
2.位段的内存分配
(1)位段的成员可以是int,unsigned int ,char
等类型的
(2)位段的空间需要按照四个字节或一个字节来开辟。
(3)位段有许多不确定因素,位段时不跨平台的,位段的可移植性较查。
例如:
struct s
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
a占三个比特位,b占4个比特位,还剩一个比特位,不够放c,因此,再申请一个字节的空间放c,还剩下三个比特位,不够放d,因此再申请一个字节。共三个字节。
注意:用的编译器是vs
3.位段的跨平台问题
(1)int是unsigned还是signed
(2)是从左往右放,还是从右往左放
(3)剩下的空间是继续使用还是丢弃
(4)位段的最大数目不能确定(16位机器最大是16,32位机器最大是32,写成27,在16位机器上就会出问题)
4.位段使用的注意事项
不能对位段成员使用取地址操作符
几个成员可能会使用同一个字节,就会导致一些成员没有地址,一个字节才有一个地址,如果只一个比特位的话,是没有地址的。
例如:
struct s
{
int a : 3;
int b : 4;
int c : 10;
int d : 30;
};
int main()
{
struct s n;
//scanf("%d", &(n.a));//err
int ret = 0;
scanf("%d", &ret);
n.a = ret;
return 0;
}