1 结构体的意义
在实际应用过程中,需要实现对某个事物或者对象的多个属性的存储,需要定义多个数据类型变量存储,此时的多个变量之间是相互独立,不能很好的表示整个事物或者对象。需要将对象的多个属性作为整体存储。在C语言中可以使用结构体来存储。实质就是将对象的多个属性抽象的数据类型进行封装为整体。
2 结构体类型的声明和使用
结构体是一种构造数据类型,作为使用者按照需求自定义数据类型,是将一个或者多个数据类型成员进行封装。
所谓结构体,是由多个抽象数据类型成员构成的集合,抽象数据类型是根据存储的事物或者对象的属性进行抽象。并使用关键字struct进行封装得到的抽象数据类型。
2.1 结构体类型的声明
2.1.1 结构体抽象语法:
struct 结构体类型名称 {
抽象数据类型成员列表
};
1. struct是结构体数据类型封装的关键字。表示所抽象的数据类型为结构体数据类型;
2. 结构体类型名称:表示整个抽象数据类型的名称,可以省略。用于对结构体类型的访问(需要包含struct关键字和结构类型名称为整体访问)。
为了访问的方便,可以使用typedef给结构体类型取别名。
3. 抽象数据类型成员:
1) 对需要存储事物属性的抽象,成员可以是1个或者多个抽象数据类型的变量,每一个成员之间需要使用分号(;)分隔。
2) 每一个成员构成包含:数据类型 成员变量名
3) 成员可以是基本数据类型变量、构造数据类型变量、还可以是指针(数据类型指针和函数指针)。
2.1.2 结构体类型的声明和使用
1.声明和使用分离
#include <stdio.h>
/* 结构体类型的声明 */
struct Stu {
int num;
char name[32];
int score;
};
int main()
{
struct Stu stu;
/* 使用结构体类型定义结构体变量 */
}
注意:
1) 声明和使用可以在一个文件中完成。
2) 声明和使用可以不在同一个文件中:
头文件中作声明,在源程序文件中包含头文件,在使用;
其它源程序文件中声明并且定义变量,在使用源程序文件中可以通过extern对变量进行声明引用。
2.在结构体声明的时候,定义结构体变量
可以省略结构体类型名称,直接定义,此时的结构体类型只能在类型声明的时候使用或者使用typedef给结构体类型取别名,后续通过别名使用结构体类型。
也可以不省略结构体类型名称,同时定义结构体变量。此时结构体类型在后续依然可以使用。
#include <stdio.h>
int main()
{
/* 由于省略了结构体类型名称,在声明结构体类型的时候可以定义变量,在后续不可以在使用 */
struct {
int *data;
int size;
} arr;
/* 在声明的时候没有省略结构体类型名称,在声明的时候定义变量,此时的结构体在后续依然可以使用 */
struct Stu {
int num;
char name[32];
int age;
} stu;
/* 使用已有的结构体类型定义变量 */
struct Stu stu1;
}
3.结构体类型声明位置
对于结构体类型的声明位置:
1) 可以是全局做类型声明,可以整个程序域中访问结构体类型。
2) 可以是局部做类型声明,只能在局部模块内使用结构体类型。
2.2 结构体变量的定义和成员的访问
所谓的结构体变量,指的是变量的数据类型为结构体类型,满足变量的所有规则及其语法。
变量的定义:
存储类型 数据类型 变量名称;
对于结构体变量数据类型为结构体类型:
1.由struct关键字和结构体类型名称构成的整体;
2.也可以是结构体类型的别名表示;
3.在声明结构体类型的时候,来定义结构体变量。
2.2.1 定义已有结构体类型的变量和成员访问
1.结构体变量定义
直接使用结构类型定义结构体变量:
结构体类型:是由struct关键字和结构体类型名称构成,也可以是typedef取别名的数据类型。
2.结构体的访问
对于结构体的访问,不能整体访问,只能逐一成员访问。
具体访问形式:结构体变量名称.成员变量
3.实例
#include <stdio.h>
/* 声明结构体类型struct Stu,同时使用typedef取别名为STU */
typedef struct Stu {
int num;
char name[32];
int age;
} STU;
int main()
{
struct Stu stu = {12, "ikun", 18};
printf("%d:%s-%d\n", stu.num, stu.name, stu.age); /* 结构体变量.成员变量逐一访问 */
STU stu1 = {26,"dasao", 28};
printf("%d:%s-%d\n", stu1.num, stu1.name, stu1.age);
}
2.2.2 声明结构体类型同时定义结构体变量
#include <stdio.h>
int main()
{
struct Demo {
int a;
int b;
} obj; /* 在声明结构体类型的时候定义结构体变量,由于结构类型名称没有省略,在后续可以使用struct Demo来定义新的结构体变量 */
printf("obj: %d - %d\n", obj.a, obj.b);
struct Demo obj1;
printf("obj1: %d - %d\n", obj1.a, obj1.b);
struct {
int c;
int d;
} obj2; /* 在声明结构体类型的时候定义结构体变量,由于结构体类型名称省略,也没有使用typedef给类型取别名,所以该结构体类型不能再次使用 */
printf("obj2: %d - %d\n", obj2.c, obj2.d);
}
/* 在所有的情况下:对于结构体变量的访问只能通过结构体变量名称.成员变量逐一访问。不能整体访问 */
2.3 结构体变量初始化
2.3.1 先定义结构体变量,在设置初始值
在定义结构体变量的时候,如果未设置初始值;结构体变量的成员初始值由结构体变量的存储空间决定:
- 如果结构体变量为静态变量(全局变量和static修饰的局部变量),所有成员的初始值为0值。
- 如果结构体变量为auto类型变量,所有成员的初始值为随机值。
在结构体变量访问的时候,可以逐一成员设置值,也可以整体设置值(实质是将相同类型结构体变量的成员逐一赋值)。
#include <stdio.h>
#include <string.h>
struct Stu {
int num;
char name[32];
int age;
};
struct Stu stu1;
int main()
{
struct Stu stu2;
/* 访问结果:stu1成员变量的值为0值,stu2成员变量的值为随机值 */
printf("stu1: %d-%s-%d\n", stu1.num, stu1.name, stu1.age);
printf("stu2: %d-%s-%d\n", stu2.num, stu2.name, stu2.age);
/* 结构体成员变量逐一设置值 */
stu1.num = 1;
strcpy(stu1.name, "kunkun");
stu1.age = 18;
printf("stu1: %d-%s-%d\n", stu1.num, stu1.name, stu1.age);
printf("stu2: %d-%s-%d\n", stu2.num, stu2.name, stu2.age);
/* 使用相同类型的结构体变量赋值 */
stu2 = stu1;
printf("stu1: %d-%s-%d\n", stu1.num, stu1.name, stu1.age);
printf("stu2: %d-%s-%d\n", stu2.num, stu2.name, stu2.age);
}
2.3.2 在定义结构体变量同时初始化
1.在定义结构体变量,整体初始化设置
使用相同数据类型结构体变量设置值
2.在定义结构体变量,逐一成员初始化
- 可以对结构体变量所有成员全部初始化值
- 可以对结构体变量的部分成员初始化值
#include <stdio.h>
#include <string.h>
struct Stu {
int num;
char name[32];
int age;
};
int main()
{
struct Stu stu = {1, "xiaoqiang", 18}; /* 在定义结构体变量的时候,逐一成员全部设置初始值 */
printf("stu: %d-%s-%d\n", stu.num, stu.name, stu.age);
struct Stu stu1 = stu; /* 在定义结构体变量的时候,整体初始设置 */
printf("stu1: %d-%s-%d\n", stu1.num, stu1.name, stu1.age);
struct Stu stu2 = {2, "xiaoming"}; /* 在定义结构体变量的时候,逐一成员顺序部分初始化,未初始化成员值为0值 */
printf("stu2: %d-%s-%d\n", stu2.num, stu2.name, stu2.age);
struct Stu stu3 = {
.num = 3,
.age = 18,
}; /* 在定义结构体变量的时候,逐一给指定成员初始化。 */
printf("stu3: %d-%s-%d\n", stu3.num, stu3.name, stu3.age);
}
3 结构体数据类型空间大小
由于结构体是由多个数据类型成员变量构成的集合,对于集合中所有成员需要在内存中存储。存储空间的大小由结构体中所有的成员所占空间的大小以及字节对齐方式共同决定。
3.1 按照基本数据类型默认字节对齐方式
所有的基本数据类型,存在默认字节对齐方式。在32位系统中默认情况
char 所占空间是1字节 默认按照1字节对齐 起始地址最后1位为任意值
short 所占空间是2字节 默认按照2字节对齐 起始地址最后1位为0值
int 所占空间4字节 默认按照4字节对齐 起始地址最后2位为00值
long 所占空间4字节 默认按照4字节对齐 起始地址最后2位为00值
在系统中,会存在最大字节对齐方式,32位系统默认最大是4字节对齐,64位系统默认最大是8字节对齐
#include <stdio.h>
struct Demo1 {
char c; /* 默认是按照1字节对齐 */
short s; /* 2字节对齐 */
int i; /* 4字节对齐 */
};
struct Demo2 {
char c;
int i;
short s;
};
struct Demo3 {
char c;
int i;
short s;
char ch;
};
struct Demo4 {
char buf[15];
int i;
short s;
};
int main()
{
printf("%lu\n", sizeof(struct Demo1)); //8
printf("%lu\n", sizeof(struct Demo2)); //12
printf("%lu\n", sizeof(struct Demo3)); //12
printf("%lu\n", sizeof(struct Demo4)); //24
return 0;
}
3.2 修改系统最大字节对齐方式
#pragma pack(n) # 修改系统默认最大字节对齐方式,n=1,2,4,8;
在修改最大字节对齐方式的时候,不能超过默认的最大字节对齐方式,如果超过,会按照默认最大字节对齐方式对齐。
注意:对齐方式,在下次声明会做出改变。
实例:
#include <stdio.h>
struct Demo1 {
char c; /* 默认是按照1字节对齐 */
short s; /* 2字节对齐 */
int i; /* 4字节对齐 */
};
struct Demo2 {
char c;
int i;
short s;
};
#pragma pack(2) /* 修改最大字节对齐方式为2字节 */
struct Demo3 {
char c;
int i;
short s;
char ch;
};
#pragma pack(4)
struct Demo4 {
char buf[11];
int i;
short s;
};
#pragma pack() /* 修改为默认字节对齐方式 */
int main()
{
printf("%lu\n", sizeof(struct Demo1)); //8
printf("%lu\n", sizeof(struct Demo2)); //12
printf("%lu\n", sizeof(struct Demo3)); //10
printf("%lu\n", sizeof(struct Demo4)); //24
return 0;
}
3.3 结构体字节对齐的意义
- 数据存储的时候,采用字节对齐,按照对齐方式(数据存储空间大小的整数倍)存储,在数据读写效率比较高。
- 在数据通信时候,随着运行版本和系统的改变,不会因为某些数据类型的改变导致其它数据存储位置的改变。
4 结构体位段
所谓的结构体位段,指的是将结构体成员不在是对整个成员空间的运用,是对成员的某些位的应用,构成结构体位段数据。
#include <stdio.h>
struct Demo {
int a:5; /* 使用变量a的低5位的存储空间 */
char c:3; /* 使用变量c的低3位的存储空间 */
};
int main()
{
printf("%lu\n", sizeof(struct Demo)); /* 有效数据位一共有8位,整个结构体是按照int类型4字节对齐,空间大小是4字节*/
struct Demo obj;
obj.a = 0x23; //0010 0011 只能取数据的低5位的编码: 0 0011
obj.c = 0x34; //0011 0100 只能取数据的低3位的编码:100
//有效数据位的编码:100 0 0011 0x83
char *p = (char *)&obj;
printf("%x\n", *p);
printf("%x,%x\n", obj.a, obj.c);
return 0;
}
5 结构体综合应用
5.1 结构体数组
所谓的数组,指的是具有相同类型的多个元素在连续存储空间顺序存储的集合。
所谓的结构体数组,实质是数组,满足数组的所有特征,数据元素的数据类型是结构体类型。
结构体数组定义的语法规则:
存储类型 数据类型(结构体数据类型) 数组名称[常量表达式];
数组元素的访问:
不能整体访问,只能逐一访问数组元素的逐一成员,数组名称[下标].成员。 下标范围:[0, 数组元素个数-1]
#include <stdio.h>
struct Stu {
int num;
char name[32];
int score;
};
int main()
{
int i;
/* 全部初始化 */
//struct Stu stu[5] = {1, "frist", 11, 2, "two", 22, 3, "three", 33, 4, "four", 44, 5, "five", 55};
//struct Stu stu[5] = {{1, "frist", 11}, {2, "two", 22}, {3, "three", 33}, {4, "four", 44}, {5, "five", 55}};
/* 部分初始化 */
struct Stu stu[5] = { /* 可以给指定的数组元素设置初始值 */
[1] = {1, "frist", 11},
[3] = {3, "three", 33}
};
for (i = 0; i < sizeof(stu)/sizeof(stu[0]); i++) {
printf("%d: %s %d\n", stu[i].num, stu[i].name, stu[i].score);
}
return 0;
}
5.2 结构体指针
所谓结构体指针,其实质是指针,所指向空间的数据类型是结构体类型。其值为整个结构体类型数据存储空间的起始地址。
结构体指针的定义语法:
存储类型 数据类型(结构体数据类型) *指针变量名称;
存储类型:修饰指针变量本身存储空间的属性;
结构体指针初始化:和指针的初始化是一致的。
结构体指针的访问:
对于结构体指针的访问:
1) (*指针).成员 # 指针引用访问到整个结构体,需要通过结构体.成员逐一访问
2) 指针->成员 # 指针指向空间中成员直接访问。
#include <stdio.h>
struct Stu {
int num;
char name[32];
int score;
};
int main()
{
struct Stu stu = {1, "ikun", 88};
struct Stu *p = &stu;
printf("%d : %s %d\n", (*p).num, (*p).name, (*p).score); /* (*指针).成员 */
printf("%d : %s %d\n", p->num, p->name, p->score); /* 指针->成员 */
(*p).num = 13;
printf("%d : %s %d\n", (*p).num, (*p).name, (*p).score);
printf("%d : %s %d\n", p->num, p->name, p->score);
p->num = 21;
printf("%d : %s %d\n", (*p).num, (*p).name, (*p).score);
printf("%d : %s %d\n", p->num, p->name, p->score);
return 0;
}
5.3 结构体指针数组
对于结构体指针数组,实质是数组,数组元素的类型是结构体指针类型,也就数组元素是指针,指向的空间是结构空间。
定义语法:
存储类型 数据类型(结构体数据类型) * 数组名[常量表达式];
访问形式:
- (*数组名[下标]).成员;
- 数组名[下标]->成员
#include <stdio.h>
struct Stu {
int num;
char name[32];
int score;
int age;
};
struct Stu stu2 = {2, "xiaoming", 80, 16};
int main()
{
int i;
struct Stu stu1 = {1, "xiaoli", 90, 30};
struct Stu *arr[5] = {&stu1, &stu2};
for (i = 0; i < 2; i++) {
printf("%d : %s %d %d\n", arr[i]->num, (*arr[i]).name, (*(arr+i))->score, (**(arr+i)).age);
}
}
5.4 函数指针作为结构体成员
在声明结构体的时候,成员可以是数据类型成员,也可以是指针类型成员,在指针类型成员的时候,可以是数据类型指针,也可以是函数指针。在函数指针作为结构体成员的时候,相同类型的结构体变量可以访问不同函数指针所指向的函数实现不同的功能访问。
最终实现,相同类型结构体变量访问不同的函数。实质是回调实现。
#include <stdio.h>
struct Demo {
void (*func_p)(void); /* 函数指针作为结构体的成员变量,最终可以实现不同结构体变量,通过相同的成员访问到不同的函数接口 */
};
void test1(void)
{
printf("%s\n", __func__); /* 函数调用的标识常量,表示函数的名称 */
}
void test2(void)
{
printf("%s\n", __func__);
}
int main()
{
struct Demo obj1 = {test1};
struct Demo obj2 = {test2};
/* 相同类型的不同结构体变量,调用同一个成员函数指针访问到不同的函数接口, */
obj1.func_p();
obj2.func_p();
return 0;
}
在Linux内核底层代码中应用很多,实现设备驱动链表,不同的设备在调用相同函数指针访问到不同的函数接口功能。
6 共用体
所谓的共用体,指的是多个数据类型变量共享同一内存空间,其中多个数据变量不能同时访问;当其中的一个成员在写内存的时候,其它成员空间的值也会被修改。
6.1 共用体类型的声明和定义
union 共用体名称 {
多个成员变量列表; /* 成员变量可以是一个或者多个成员构成,其中每一个成员包含(数据类型 变量名称),成员可以是指针和数据 */
}
在共用体中,多个成员共享内存空间。
共用体成员的访问:共用体变量.成员变量
#include <stdio.h>
/* 共用体类型的声明 */
union Demo{
int a;
char buf[4];
};
int main()
{
int i;
union Demo obj; /* 定义共用体变量 */
obj.a = 0x12345678;
for (i = 0; i < 4; i++) {
printf("%x ", obj.buf[i]);
}
printf("%p - %p - %p\n", &obj, &(obj.a), obj.buf); /* 由于成员共享内存空间,此时地址值相等 */
}
6.2 共用体空间的大小
共用体空间大小由共用体中最大成员空间和最大字节对齐方式决定。
#include <stdio.h>
union Data {
char buf[13]; /* 最大内存空间成员:所占空间大小为13字节 */
int a; /* 最大字节对齐成员:按照4字节对齐,则共用体空间的大小是4的整数倍 */
};
union Data1 {
char buf[13]; /* 最大内存空间成员:所占空间大小为13字节 */
short a; /* 最大字节对齐成员:按照2字节对齐,则共用体空间的大小是2的整数倍 */
};
int main()
{
printf("%d\n", sizeof(union Data)); //16
printf("%d\n", sizeof(union Data1)); //14
}
6.3 共用体成员访问
如果是共用体变量,成员的访问:
共用体变量.成员
如果是共用体指针,成员访问
(*共用体指针).成员;
共用体指针->成员
#include <stdio.h>
union Data {
int a;
char buf[4];
};
int main()
{
union Data data = {
.a = 0x12345678,
};
union Data *p = &data;
printf("data.a = 0x%x\n", data.a);
printf("(*p).a = 0x%x\n", (*p).a);
printf("p->a = 0x%x\n", p->a);
}
7 枚举类型
所谓的枚举类型,指的是对常量的列举,表示的是多个常量的集合。用来表示在某一数据上的多个具体值,其中多个具体在具体的某个时刻只能出现一种情况,例如表示今天星期几(1,2,3,4,5,6,7)。
7.1 枚举类型的定义
enum 枚举数据类型名称 {
枚举常量值列表, /* 常量值可以是一个或者多个,多个常量值之间使用逗号分隔,其中每一个常量值都有一个特殊的符号表示,符号可以设置常量初始值,也可以采用默认的常量初始值,在使用默认常量值的时候满足:第一个常量符号默认初始值为0,其它常量符号的初始值上一个常量符号值+1。 */
};
对于枚举类型空间大小:4字节
#include <stdio.h>
/* 声明枚举类型 */
enum TEST {
ONE=1, /* 枚举第一个成员,如果未初始化设置,其初始值为0 */
TOW, /* 常量符号没有设置值,则为前一个常量符号值+1 */
THREE,
FOUR,
FIVE,
SIX=66,
SEVEN
};
int main()
{
enum TEST test = TOW; /* 可以是枚举类型定义变量,并设置初始值 */
printf("%d\n", ONE); /* 常量符号可以不做定义直接访问 */
printf("%d\n", FIVE);
printf("%d\n", test); /* 对变量的访问 */
}