前言
当我们设计程序时,其中有一个非常重要的步骤是选择表示数据的方式。
之前我们用数组存储数据,但这远远不够,比如存储一个人的数据:姓名,年龄,电话。因此c语言提供了结构体变量,让你自己创造新的形式,来表示不同数据。接下来,让我们开始结构体的学习。
一、声明结构体
结构体声明举例:创建一个结构体用来描述 书的相关信息
对于结构体的名称:命名标准和其他标识符一样。但有一个特别的是结构体可以没有名称。而没有名称的结构体称为匿名结构体。
对于结构体成员:可以是变量,数组,指针,甚至是结构体。
其中结构体成员是结构体的情况,在下文结构体变量中有解释。
——————————————————————————————————————————
二、结构体的运用
2.1、结构体变量与结构体指针
注:结构体变量的初始化
仿造上面的s2的初始化形式:要求一一对应。
当 结构体中包含结构体,如下
struct B
{
char a;
short b;
int c;
};
struct Student
{
struct B sb;
char name[20];
int age;
char id[20];
};
struct Student s = { {'w',3,5},"sfw",20,"20256161" };
注:结构体的自引用
上面结构体中含有结构体中情况描述的是含有其他结构体,而结构体的自引用则是自己含有自己。
如下:
struct book
{
int price;
struct book* p;
};
注意结构体的自引用中的结构体成员必需是结构体指针。
如果是结构体变量:struct book p
那将出错。因为此时的结构体大小无法确定。
结构体自引用的作用:在数据结构中用来建立链表
——————————————————————————————————————————
2.2、访问结构成员
当我们创建了结构体变量或结构体指针之后如何去使用它们呢?这时就要用到结构体访问操作符。
结构体变量:使用 操作符 - (.)
结构体指针:使用 操作符 - (->)
如下
struct book
{
//结构体的成员,用.来提取
char name[20];
char id[20];
int price;
};
int main(void)
{
int num = 10;
struct book b = { "c语言", "c20220826", 55 };
printf("书名:%s\n书号:%s\n定价:%d\n", b.name, b.id, b.price);
struct book* pb = &b;
printf("书名:%s\n书号:%s\n定价:%d\n", pb->name, pb->id, pb->price);//也可以(*pb).name
return 0;
}
注:结构的初始化器
C99和C11为结构提供了指定初始化器,使得结构体变量初始化时不必要按顺序来赋值
struct book s = {.price = 123, .name="Asdbha", .id="c54542525"};
——————————————————————————————————————————
三、结构体内存对齐
struct s
{
char c1;
int i;
char c2;
}s1;
问:sizeof(s1)的结果是多少?(int 为 4字节)
你可能认为结果为 1 + 4 + 1 = 6 即结构体成员的大小全部加起来。
但它的结果为 12
一个结构体变量的大小,并不是成员变量的简单相加,而是有一定规则。
结构体内存对齐的规则
C语言结构体对齐规则:
结构体(struct)的数据成员, 第一个数据成员存放的地址为结构体变量偏移量为0的地址处.
其他结构体成员自身对齐时, 存放的地址为min{ 有效对齐值为自身对齐值, 指定对齐值 } 的最小整数倍的地址处.
注 : 自身对齐值 : 结构体变量里每个成员的自身大小
注 : 指定对齐值:有宏 #pragma pack(N)指定的值, 这里面的 N一定是2的幂次方.如1, 2, 4, 8, 16等.
如果没有通过宏
- 在32位Linux主机上默认指定对齐值为4, 64位的默认对齐值为8,
- AMR CPU默认指定对齐值为8;
- vs-8
注:有效对齐值:结构体成员自身对齐时有效对齐值为自身对齐值与指定对齐值中 较小的一个.
总体对齐时, 字节大小是min{ 所有成员中自身对齐值最大的, 指定对齐值 } 的整数倍.
看上去并不好理解,下面我们举实际例子。
上面规则说到可以用宏改变默认对齐数
内存对齐的意义:
许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,
它们会要求这些数据的起始地址的值是某个数k的倍数,这就是所谓的内存对齐,
注:k被称为该数据类型的对齐模数(alignment modulus)。
这种强制的要求
- 简化了处理器与内存之间传输系统的设计
- 可以提升读取数据的速度。
总结:结构体内存对齐是以 空间 换 时间
怎样即节省空间又节省时间呢? - 调整变量顺序/修改默认对齐数
——————————————————————————————————————————
四、结构体传参
结构体传参一般传地址
因为结构体一般比较大,传值时消耗大,而传地址可以很好解决这个问题
struct s
{
int date[1000];
int num;
};
struct s s = { {1,2,3,4}, 1000 };
void print1(struct s s)
{
printf("%d", s.num);
}
void print2(struct s* ps)
{
printf("%d", ps->num);
}
int main(void)
{
struct s a = { 0 };
print1(s);//传结构体 - 数据太大,临时拷贝数据也太大
print2(&s);//传地址 - 指针大小,更节省空间
//因此一般用地址
return 0;
}
——————————————————————————————————————————
五、位段
5.1、位段的声明
位段的声明和结构是类似的,有两个不同
- 位段的成员必须可以是 int , unsigned int, signed int, char
- 位段的成员名后边有一个冒号和一个数字
声明如下:
struct A
{
int a : 2;// 表示 a 占2bit位
int b : 5;// 表示 b 占5bit位
int c : 10;
int d : 30;
由上可以看出,它操作的是比特位,比字节更小的单位。
};
那么它的作用是什么呢?
假如我们要表示性别:男,女,不确定。用2个比特位就够了。00 - 男,01 - 女, 10 - 不确定。能节省空间。
5.2、位段的内存问题
位段的内存分配
- 位段的成员可以是int , unsigned int, signed int, char(属于整型家族)
- 位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的
- 位段涉及很多不确定因素,位段是不跨平台的,注意可移植程序一个避免使用位段。
位段的跨平台问题
- int位段被看作有符号还是无符号是不确定的
- 位段中最大位的数码不确定(16位 - int - 2字节,32位-int-32,如int b:17,16位机器超了)
- 位段中成员在内存从左向右还是从右向左,不确定
- 当一个结构体包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位是,是舍弃剩余的位还是利用,不确定
总结:与结构相比,位段可以达到同样效果,但有跨平台问题