-
C语言中的构造数据类型有:数组类型(自定义大小)、结构体类型、共用体类型、枚举类型
(一)结构体类型定义
-
在实际应用中,当一个结构体类型有多个文件需要使用时,可以将类型定义放在一个.h文件,需要使用的文件包含相应的头文件即可
struct 结构体数据类型名
{
<成员变量>; // 成员变量用分号隔开
};
1、结构体类型名
-
指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元,所以不能在定义结构体类型时给成员赋值
2、结构体变量名
-
实际分配空间,为了能在程序中使用结构类型的数据,应当定义结构体类型的变量,并在其中存放具体的数据
3、结构体成员变量
-
结构体中的成员拥有独立的空间(重要)
(二)结构体变量的定义及初始化
1、结构体变量定义方法
可以在声明的时候直接定义结构体变量,也可以先声明后定义结构体变量
成员也可以是一个结构体变量
结构体变量是局部变量,其成员内容不确定,需要在定义的时候进行初始化
2、初始化
-
初始化的顺序必须和结构体成员的顺序一致
struct people
{
int id; // 编号
char name[10]; // 姓名
char gender[4]; // 性别
int age; // 年龄
}P2, P3 = { 3, "小明", "男", 15 }; // 结构体类型声名时定义变量
struct people P1; // 先声明后定义,提倡这种方法
-
注意:定义一次性结构体。因为没有结构体类型名,所以不能定义别的结构体变量
3、清空结构体变量
-
结构体变量有多大清多大
-
在定义结构体变量时将其清空为0,然后进行结构体变量的数据输入
memset(&结构体变量名,0,sizeof(结构体变量名))
(三)结构体变量成员的引用
1、结构体成员引用符:' . '
2、结构体成员引用的基本格式
结构体变量名.成员名
除了初始化以外,不能直接改变字符数组的值。因为字符数组是常量,只能通过拷贝的方式赋值
void test1()
{
struct people P1; // 结构体类型声名后定义变量,定义了一个struct people结构体类型的变量P1
P1.id = 1;
strcpy(P1.name, "张三"); // strcpy 字符串拷贝函数:将字符串放到数组中去
strcpy(P1.gender, "男");
P1.age = 18;
printf("%d\t%s\t%s\t%d\n", P1.id, P1.name, P1.gender, P1.age)
struct people P4 = {4,"小花","女"}; // 结构体类型声名后定义变量P4,并初始化赋值
struct people P5;
P5 = P2; // 一个结构体变量可以用于给另一个结构体变量赋值
}
3、结构体变量成员获取键盘输入
struct stu
{
int num; // 4B
char name[32]; // 32B
int age; // 4B
};
struct stu lucy;
memset(&lucy, 0,sizeof(lucy)); // 清空
printf("请输入num name age:");
// &lucy.num 取的是num成员地址
scanf("%d %s %d",&lucy.num, lucy.name , &lucy.age);
printf("num=%d, name=%s, age=%d\n",lucy.num,lucy.name,lucy.age);
4、结构变量之间的相互赋值
-
相同类型的结构体变量可以直接赋值(推荐)
struct stu lucy={100,"小法", 18};
struct stu bob;
// 需求 将lucy的值 赋值 bob
// 方式一:逐个成员赋值
bob.name = lucy.name; // 字符数组不能直接赋值
// 方法二:相同类型的结构体变量可以直接赋值(推荐),直接把空间内容复制过来
bob = lucy;
// 方法三:方法二的底层实现,根据lucy的大小创建bob
memcpy(&bob,&lucy,sizeof(struct stu));
printf("num=%d, name=%s, age=%d\n",bob.num,bob.name,bob.age);
(四)给结构体类型取别名(建议给结构体取别名来进行使用)
-
在给结构体类型取别名后,就不能在结构体类型声名时定义变量
typedef struct student
{
int num;
char name[20];
char sex;
}STU; // STU是别名,不是结构体变量
STU Lucy,Bob; // 使用STU去定义相应的对象类型与变量分开定义
-
对于上述的定义一次性结构体。因为已经给其取了别名,可以用来定义变量
typedef struct
{
int num;
char name[32];
int age;
}STU; // 此时的STU不是结构体变量 而是结构体类型
(五)结构体数组
一个结构体变量中可以存放一组数据:如一个学生的学号、姓名、成绩等数据
如果有10个学生的数据,定义10个结构体变量很不方便,这时候我们可以使用结构体数组
结构体数组与以前介绍过的数值型数组不同之处:每个数组元素都是一个结构体类型的数据,它们都分别包括各个成员
1、定义
-
数组里面每个元素是整个结构体的信息
struct student S[3]; // 结构体数组:用于保存3个人的相关信息
2、结构体数组元素的使用
-
结构体数组名[数组下标].成员变量名
S[0].id=1; // 这里引用了struct student类型的结构体数组S的第1个元素S[0]的成员id,使其值等于1
printf(“%s\n”,S[2].name);
3、练习:定义一个结构体数组,求学生平均成绩
typedef struct student
{
int num;
char name[20];
float score;
}STU;
STU edu[3]={
{101,”Lucy”,78},
{102,”Bob”,59.5},
{103,”Tom”,85},
};
int n = sizeof(edu)/sizeof(edu[0]);
int i = 0;
int sum = 0;
for(i=0;i<3;i++)
{
sum += deu[i].score;
}
printf("平均成绩为%d\n",sum/n);
(六)结构体的嵌套
-
一种结构体类型中可以有其他结构体类型的成员
struct A
{
int id;
char name[10];
};
struct B // 在B结构体里面存放了A,调用相关成员
{
struct A a1; // 其他结构体类型的成员
float score;
};
int main()
{
struct B b1 = {{2021, "小明"}, 100}; // 定义结构变量b1
b1.score = 99.5;
b1.a1.id = 1; // 结构体嵌套成员的引用
strcpy(b1.a1.name, "小强");
printf("%d\t%s\t%f\n", b1.a1.id, b1.a1.name, b1.score);
}
(七)结构体指针
指向结构体变量首地址的指针,通过结构体指针即可访问该结构体变量
结构体指针定义:struct 结构体数据类型名 *结构体指针变量名;
在定义结构体指针时需要给定指向的对象或指向空(NULL),避免出现野指针
结构体指针主要用于结构体变量传参以及顺序表、链表中
1、结构体指针的使用
int main()
{
typedef struct Student
{
int num; // 学号
string name; // 姓名
char sex; // 性别
float score; // 成绩
}STU,*STU_P;
STU stu = {1998,"wangchao",'m',100};
STU *p = &stu; // *p = stu;
STU_P p = &stu';
cout << p->name << " " << p->num++ << " " << p->score << " " << p->sex++ << endl; // wangchao 1998 100 m
cout << p->num << p->sex; // 1999 n m的ASCII值+1得到n
cout << ++p->num << ++p->sex; // 2000 o
}
- 注意
STU是结构体类型
STU_P是结构体指针类型
指针p占4字节,在32位平台指针占4字节
通过结构体指针访问成员变量使用 ->,结构体变量访问用 .
2、案例: 从堆区给结构体申请一个空间
STU *p = NULL;
// 从堆区申请结构体空间
p = (STU *)calloc(1,sizeof(STU));
if(NULL == p)
{
perror("calloc");
return;
}
// 获取键盘输入
printf("请输入一个学生的信息num name age\n");
scanf("%d %s %d", &p‐>num, p‐>name, &p‐>age);
// 遍历
printf("num = %d, name=%s, age=%d\n",p‐>num, p‐>name, p‐>age);
// 释放空间,避免重复释放
if(p != NULL)
{
free(p);
p=NULL;
}
3、结构体指针作为函数参数
void mySetSTUData(STU *p) // p=&lucy
{
printf("请输入一个学生的信息num name age\n");
scanf("%d %s %d", &p‐>num, p‐>name, &p‐>age);
}
void myPrintSTUData(const STU *p) // p =&lucy *p只读,p的指向可以改变
{
printf("sizeof(p)=%d\n", sizeof(p));
printf("num = %d, name=%s, age=%d\n",p‐>num, p‐>name, p‐>age);
}
void test03()
{
STU lucy;
memset(&lucy,0,sizeof(lucy));
mySetSTUData(&lucy); // 定义一个函数 给lucy的成员获取键盘输入
myPrintSTUData(&lucy); // 定义一个函数 打印lucy的成员信息
}
4、案例:从堆区申请申请一个结构体数组,分函数实现
STU * get_array_addr(int n)
{
return (STU *)calloc(n,sizeof(STU));
}
// arr代表的是空间首元素地址
void my_input_stu_array(STU *arr, int n)
{
int i=0;
for(i=0;i<n;i++)
{
printf("请输入第%d个学生的信息\n",i+1);
// scanf("%d %s %d", &arr[i].num, arr[i].name, &arr[i].age);
scanf("%d %s %d", &(arr+i)‐>num , (arr+i)‐>name, &(arr+i)‐>age);
}
}
void my_print_stu_array(const STU *arr, int n)
{
int i=0;
for(i=0;i<n;i++)
{
printf("num=%d, name=%s, age=%d\n", (arr+i)‐>num, (arr+i)‐>name,(arr+i)‐>age);
}
return;
}
void test04()
{
int n = 0;
STU *arr=NULL;
printf("请输入学生的个数:");
scanf("%d", &n);
// 根据学生的个数从堆区申请空间
arr = get_array_addr(n);
if(arr == NULL)
{
perror("get_array_addr");
return;
}
my_input_stu_array(arr, n); // 从键盘给结构体数组arr输入数据
my_print_stu_array(arr, n); // 遍历结构体数组
// 释放空间
if(arr != NULL)
{
free(arr);
arr=NULL;
}
}
(八)结构体内存空间分配
1、什么是内存对齐?
-
实际上系统在分配储存单元时,以字为单位,一个字包括四个字节(32位CPU,一次性读取4字节)
-
一般情况下结构体类型所占内存大小为所有数据成员的大小之和,但是结构体类型所占内存大小存在内存对齐(内存补齐)
-
内存对齐:如果一个结构体类型所占内存大小不够存储整数个最大成员,则会补齐结构体内存
2、默认对齐原则
确定分配单位:每一行应该分配的字节数。由结构体中最大的基本类型长度确定
确定成员的起始位置的偏移量:成员自身类型的整数(0~n)倍
收尾工作:结构体的总大小为分配单位的整数
-
案例1
![](https://i-blog.csdnimg.cn/blog_migrate/20936a01c6dd97ee13dca8a48bad5bdf.bmp)
-
案例2
![](https://i-blog.csdnimg.cn/blog_migrate/d190f7394c4da8eea2c658c8e6ff8674.bmp)
3、结构体嵌套结构体的内存大小
确定分配单位:每一行应该分配的字节数。由所有结构体中最大的基本类型长度决定
确定成员的偏移量:自身类型的整数(0~n)倍
结构体成员偏移量:被嵌套的结构体中最大的基本类型整数倍
结构体成员中的成员偏移量是相对于被嵌套的结构体的
收尾工作:结构体的总大小为分配单位的整数倍
结构体成员的总大小为被嵌套的结构体里最大基本类型的整数倍
-
案例1
typedef struct
{
short d;
char e;
}DATA2;
typedef struct
{
short a;
int b;
DATA2 c;
char f;
}DATA;
void test08()
{
DATA data;
printf("%d\n",sizeof(DATA)); // 16
printf("a:%u\n", &data.a); // a:1703628
printf("b:%u\n", &data.b); // b:1703632
printf("c中d:%u\n",&data.c.d); // c中d:1703636
printf("c中e:%u\n",&data.c.e); // c中e:1703638
printf("f:%u\n",&data.f); // f:1703640
}
![](https://i-blog.csdnimg.cn/blog_migrate/143b10f4ce1bb96d755a97b93c575e00.bmp)
-
案例2
typedef struct
{
short d;
char e;
}B;
typedef struct
{
int a;
short b;
B c;
short f;
}A;
void test09()
{
DATA data;
printf("%d\n",sizeof(DATA)); // 12
printf("a:%u\n", &data.a); // a:1703632
printf("b:%u\n", &data.b); // b:1703636
printf("c中d:%u\n",&data.c.d); // c中d:1703638
printf("c中e:%u\n",&data.c.e); // c中e:1703640
printf("f:%u\n",&data.f); // f:1703642
}
![](https://i-blog.csdnimg.cn/blog_migrate/53b2c5012319e586b95101e1ffd2b0a4.bmp)
-
案例3
typedef struct
{
char a;
int b;
short c;
}DATA;
void test10()
{
DATA data={'a',100, 20};
char *p = &data;
printf("c = %hd\n", data.c);
// 需求:借助p访问20
printf("c = %hd\n", *(short *)(p+8));
}
-
指针是char *类型的,只能读取一个字节宽度。强转为short *类型,这样就可以读取20了
4、强制类型对齐
-
使用#pragma pack改变默认对其原则
-
格式:#pragma pack (value)时的指定对齐值value
-
注意
value只能是1、2、4、8等
指定对齐值与数据类型对齐值相比,取较小值
如果指定对齐值:
设为1:则short、int、float等均为1
设为2:则char仍为1,short为2,int 变为
-
步骤
确定分单位:每一行应该分配的字节数min(value,默认分配单位)
成员偏移量 = 成员自身类型的整数(0~n)倍
收尾工作 = 分配单位的整数(0~n)倍
-
案例1
#include<stdio.h>
#pragma pack(2) // 指定对齐规则
typedef struct
{
char a;
int b;
short c;
}DATA1;
void test01()
{
printf("%d\n", sizeof(DATA1)); // 8
return;
}
5、结构体数组所占内存大小
-
结构体的内存大小*数组元素个数
struct people { // 保存人的相关信息的结构体
int id; // 编号 4
char name[10]; // 姓名 10
char gender[4]; // 性别 4
int age; // 年龄 4
};
struct test {
char ch; // 1
int i; // 4
float f; // 4
double d; // 8
};
int main()
{
struct people P[10]; // 结构体数组:用于保存10个人的相关信息
P[0].id = 0; // 结构体数组元素成员的引用
P[2].name;
P[9].age = 30;
printf("%d\n", sizeof(struct people)); // 24 22/8除不尽 向上取24
printf("%d\n", sizeof(struct test)); // 24 17/8除不尽 向上取24
printf("%d\n", sizeof(P)); // 240
return 0;
}
(九)位段、位域
1、位段
信息在计算机的存取长度一般以字节为单位,有时存储一个信息不必用一个或多个字节
例如:"真"或"假":用0或1表示,只需一位即可
在计算机用于过程控制、参数检测或数据通信领域时,控制信息往往只占一个字节中的一个或几个二进制位
-
怎样向一个字节的一个或几个二进制位赋值和改变它的值?
-
位运算符:<< >> & | ~ ^
-
结构体中定义位段,利用位段可以减少存储空间并简化位操作(将其看作一个变量进行操作)
-
2、位段的使用
-
C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,以位为单位的成员称为“位段”或称“位域”
struct packed_data{
unsigned int a:2; // a只占一个字节中的两位二进制位
unsigned int b:6;
unsigned int c:4;
unsigned int d:4;
unsigned int i; // 不是位段
} data;
// 其中a,b,c,d分别占2位,6位,4位,4位,i为整型,占4个字节
-
相邻位域可以压缩(压缩的位数不能超过成员自身大小)
typedef struct
{
unsigned char a:2;
unsigned char b:2;
unsigned char c:5;
}DATA2;
void test02()
{
DATA2 data;
printf("%d\n", sizeof(DATA2)); // 2
// 位段 不能取地址
// printf("%p\n", &data.a);
// 位段的赋值不要超过位段的大小 a:2
data.a = 6; // 0110
printf("%u\n", data.a); // 2
}
3、注意事项
-
位段不能取地址
-
对于位段成员的引用如下:data.a = 2。赋值时,不要超出位段定义的范围;如段成员a定义为2位,最大值为3,即(11)2,所以data a = 6,就会取6的低两位进行赋值
-
位段成员的类型一般只考虑unsigned int类型也可以考虑unsigned char
-
一个位段必须存放在一个存储单元中,不能跨两个单元。第一个单元空间不能容纳下一个位段,则该空间不用,从下一个单元起存放该位段
-
位段的长度不能大于存储单元的长度
4、无意义位段的定义
typedef struct
{
unsigned char a:2; // 00
unsigned char :4; // 无意义的位段(占有4位)
unsigned char b:2; // 11
}DATA3;
void test03()
{
DATA3 data;
memset(&data, 0, 1);
data.a = 0; // 00
data.b = 3; // 11
printf("%d\n",sizeof(DATA3));
printf("%#x\n", *(unsigned char *)&data); // 1100 0000 0xc0
}
-
a是低位,b是高位
-
应用1
-
如一个段要从另一个存储单元开始,可以定义
unsigned a:1;
unsigned b:2;
unsigned :0;
unsigned c:3; // 另一个单元
// 由于用了长度为0的位段,其作用是使下一个位段从下一个存储单元开始存放
// 将a、b存储在一个存储单元中,c另存在下一个单元