本文继续总结《C和指针》第10章的内容,主要讲解结构体和联合体。
结构体
前面我们讲解过数组,我们知道数组是存储相同数据类型的元素的集合,数据的每个元素可以通过下标引用或指针间接访问。在实际的工程中,有非常多的情况下需要将不同数据类型的元素聚合在一起来统一表达,于是,结构体应运而生。
结构体也是一些值的集合,这些值在结构体的概念中称之为成员,每个成员可以是不同的数据类型,每个成员都有自己的名字,可以通过名字来访问成员。
结构体变量也属于标量类型,与数组名不同。
结构体声明
struct [name]
{
member-list
} [variable-list];
结构体使用关键词struct
来表示。
// 结构体定义 有多种方式
// 方式1
struct fruit
{
int a;
float b;
char c;
};
struct fruit fruit1; // 定义结构体变量
struct fruit fruit2={1, 3.14, 'a'}; // 定义结构体变量并初始化
// 方式2(工程中,通常使用这种方式)
typedef struct fruit_s
{
int a;
float b;
char c;
} fruit_t;
fruit_t fruit1;
fruit_t fruit2={1, 3.14, 'a'};
// 方式3
struct fruit_s
{
// 匿名结构体作为内部结构成员,通常在链表等高级数据结构中使用
struct
{
void* next;
int id;
} node;
int a;
float b;
char c;
};
// 方式4
// 在一个复杂的工程中,如果模块化做的好,在对外的API的函数参数中,有时会定义结构体指针,而调用者又不用关心该结构体的具体细节,但是为了使编译器能编译通过,有时我们需要声明一下结构体。
struct apple;
struct fruit_s
{
struct apple *mem;
int a;
总之,不管你如何声明结构,需要把握的总的原则就是要让编译器能"理解",使编译通过。编译器将 结构体指针看作是指针,而不关心结构体的内部成员;对于结构体变量,编译器一定得知道它的每一个成员的数据类型,以便知道这个变量的长度,分配合适的内存。
结构体成员
结构体的成员可以是 基础数据类型的标量,也可以是数组,也可以是指针,也可以是结构体。
typedef struct simple_s
{
int a;
float b;
char c;
}simple_t;
typedef struct complex_s
{
float f;
int a[20]; // int 数组
int *p; // 指向int的指针
simple_t s; // simple_t 结构体成员变量
simple_t *ps[10]; // 指向simple_t结构体的指针数组
simple_t *c; // 指向simple_t结构体的指针
}complex_t;
结构体成员的访问
// 直接访问,通过 点操作符
complex_t com1;
memset(&com1, '\0', sizeof(com1)); // 初始化com1变量
com1.f = 3.14;
com1.s.a = 1;
com1.s.b = 3.14;
com1.s.c = 'a';
printf("f=%f, a=%d, b=%f, c=%c\n", com1.f, com1.s.a, com1.s.b, com1.s.c);
// 间接访问,通知 指针的箭头操作符
complex_t *p_com1 = &com1;
printf("f=%f, a=%d, b=%f, c=%c\n", p_com1->f, p_com1->s.a, p_com1->s.b, p_com1->s.c);
// 在高级数据结构,例如,链表,树中,常常在一个结构体内部包含指向该结构体本身的指针
typedef struct _node_s
{
struct _node_s *next;
int id;
}node_t;
当然,还有更复杂的定义方式,但是我并不打算在本文中讲解,在后面的系列文章中讲到高级数据结构时,再提出来。
结构体对齐
作为函数参数的结构体
我们知道,C语言中函数函数的传递本质上就是传递一个"值"。(如果不清楚,请参考《C和指针》阅读笔记(6))。
如果直接将传递结构体变量,语法上是可行的,但是,试想一下,如果该结构体很大,那么,每次调用函数就需要拷贝一份很大字节的数据到函数的栈上,显然是影响效率的。这时候,传递结构体指针,效率就会急速提升(特别是对于大结构体),因为一个指针才占用8个字节。
结构体位段
在某些网络协议或者资源有限的嵌入式平台上,有时会将结构体的成员所占的存储空间,进一步压缩,以节省存储空间。
位段是结构的一种,但它的成员长度是以位为单位指定。
//IP头部,总长度20字节
typedef struct _ip_hdr
{
#if LITTLE_ENDIAN
unsigned char ihl:4; //首部长度,占4个bits
unsigned char version:4, //版本
#else
unsigned char version:4, //版本
unsigned char ihl:4; //首部长度
#endif
unsigned char tos; //服务类型
unsigned short tot_len; //总长度
unsigned short id; //标志
unsigned short frag_off; //分片偏移
unsigned char ttl; //生存时间
unsigned char protocol; //协议
unsigned short chk_sum; //检验和
struct in_addr srcaddr; //源IP地址
struct in_addr dstaddr; //目的IP地址
}ip_hdr;
联合体
联合体与结构体的声明形式上非常相似,但关键词不同。联合体使用union
来表示。另外,联合体的成员的存储方式也和结构体有差异,联合体的所有成员引用内存中相同的位置。当你想在不同时刻把不同的数据存储于同一位置时,就使用联合体。
#include <stdio.h>
#include <stdlib.h>
union a_u
{
int a;
float b;
};
int main(int argc, char** argv)
{
union a_u u;
printf("address of u.a = %p, addres of u.b = %p\n", &u.a, &u.b);
// address of u.a = 0x7ffecbd61a00, addres of u.b = 0x7ffecbd61a00
// u.a的地址和u.b的地址 相同,说明成员a和成员b存储在相同的位置
u.a = 1;
printf("int-u.a=%d, u.b=%f\n", u.a, u.b);
// int-u.a=1, u.b=0.000000
u.b = 3.14;
printf("float-u.a=%d, u.b=%f\n", u.a, u.b);
// float-u.a=1078523331, u.b=3.140000
// 说明每个成员的类型决定了这些成员被如何解释
return 0;
}
关注我
我的公众号二维码,欢迎关注
QQ讨论群:679603305