一、结构体
1.1 结构体介绍
① 结构体:将多个数据类型(包含基本数据类型和构造数据类型)组合在一起,描述一个相对复杂的事物(个体)。
② 结构体是一种自定义数据类型。
③ 结构体在声明时不开辟内存空间,在定义时才开辟内存空间。
④ 用法:
1)首先声明结构体类型
2)然后定义结构体变量,并进行数据访问
1.2 定义结构体的语法
① 声明结构体格式: // 只是声明, 不开辟内存空间。
struct 结构体名
{
具体的属性值1;
具体的属性值2;
具体的属性值3;
……
}; // 有个分号,不可以省略
struct Date // 声明日期 结构体 不开辟开辟内存空间
{
int year;
int mon;
int day;
};
struct Person // 声明人员信息 结构体 不开辟开辟内存空间
{
char name[50];
int age;
int heigh;
char phone[20];
struct Date date; // 构造类型(嵌套结构体)
};
1.3 定义结构体的变量及初始化
① 格式:struct 结构体名 变量名;
② 定义结构体变量会开辟内存空间。
③ struct Person per2;
- 定义结构体变量,编译器会为 per2 分配内存空间,空间大小 = 结构体所有成员大小之和(考虑内存对齐)。
- Person是结构体名,per2是变量名。
④ struct Person *point= &per2;
- 定义结构体指针并指向 per2,point 本身是一个指针变量,占 8 字节(64位系统)或 4 字节(32位系统)。
- struct Person* 是类型:指向 Person 结构体的指针,&per2 是取地址操作,获取 per2 的首地址,将 per2 的地址赋值给 point。
//定义结构体变量 ,会开辟内存空间
struct Person per;
struct Person per1={0}; // 全部清0
// 全部初始化
struct Person per2 = {"张三", 20, 1.80, "110"};//按照声明里面顺序填写
// gcc 支持的语法
struct Person per3 = {
.name = "李四",
.phone = "120",
};
1.4 结构体成员的访问
① 成员访问运算符
. 双目运算符 ,从左到右 1级优先级
-> 双目运算符 ,从左到右 1级优先级
② . 或 ->运算符的使用场景:取决于运算符的的左操作数
- 当左操作数为变量时,使用 . 运算符
- 当左操作数为指针时,使用 -> 运算符
即:
- 通过变量访问成员时使用 .运算符
- 通过指针访问成员时使用 ->运算符
③ per2 是变量的情况:
per2.name ; 表达式的类型是 name 的数据类型 。 当使用访问运算符访问后,表达式的类型 由右操作数决定
④ per2 是指针的情况:
per2->name ; 表达式的类型是 name 的数据类型 。 当使用访问运算符访问后,表达式的类型 由右操作数决定
#include <stdio.h>
#include <string.h>
// 声明日期 结构体 不开辟开辟内存空间
struct Date
{
int year;
int mon;
int day;
};
// 声明人员信息 结构体 不开辟开辟内存空间
struct Person
{
char name[50];
int age;
float heigh;
char phone[20];
// struct Date date;
};
int main(int argc, char **argv)
{
//定义结构体变量 ,会开辟内存空间
struct Person per;
struct Person per1={0}; // 全部清0
// 全部初始化
struct Person per2 = {"张三", 20, 1.80, "110"};
// gcc 支持的语法
struct Person per3 = {
.name = "李四",
.phone = "120",
};
//访问
printf("name:%s heigh:%.2f phone:%s\\n", per2.name, per2.heigh, per2.phone);
// per3 -> struct Person 自定义类型
strcpy(per3.name, "王麻子"); // per3.name -> char [50]
per3.age = 20; // per3.age – > int
per3.heigh = 1.34; // per3.heigh -> float
strcpy(per3.phone, "130"); // per3.phone-> char [20]
printf("name:%s heigh:%.2f phone:%s\\n", per3.name, per3.heigh, per3.phone);
struct Person *point = NULL; //定义了一个名为 point 的结构体指针变量,初始化为NULL
point = &per2; //让指针指向 per2
printf("name:%s heigh:%.2f phone:%s\\n", point->name, point->heigh,
point->phone);
struct Person per = {0};
printf("input name:");
scanf("%s", per.name);
printf("input age:");
scanf("%d", &(per.age));
printf("input heigh:");
scanf("%f", &per.heigh);
printf("input phone:");
scanf("%s",per.phone);
struct Person *point = &per;
printf("name:%s heigh:%.2f phone:%s\\n", point->name, point->heigh,
point->phone);
printf("sizeof per is %lu\\n",sizeof(struct Person ));
return 0;
}
1.5 结构体的大小
① 结构体的大小不是简单地将所有成员大小相加,而是要考虑内存字节对齐
② 内存对齐规则
- 每个成员变量的起始地址必须是其类型大小的整数倍
- 结构体的总大小必须是其最大成员对齐要求的整数倍。
struct Person
{
char name[50]; // 50
int age; //4
float heigh; //4
char phone[20]; //20
};
printf("sizeof per is %lu\\n",sizeof(struct Person )); // 80byte,要考虑内存对齐


③ 结构体声明后,系统会对其内存空间布局进行优化调整,以提高CPU读写内存的效率。
-
系统会根据变量的地址值及其类型大小进行求余运算。若余数为0,则该变量可存储在当前地址;否则地址递增1后重新尝试。
-
找出结构体中占用空间最大的基本数据类型成员(4字节或8字节)。最终结构体的大小将被调整为4或8的整数倍。
1.6 结构体传参
1.6.1 值传递
void fun(struct Person per)
{
//对结构体的数据成员操作, 只能读取操作。
}
① 值传递(传整个结构体)使用频率较低,
② 存在劣势:由于采用值传递方式,被调函数中需要对实参进行完整复制。函数调用结束后形参空间将被释放。若结构体占用较大内存空间,则会导致效率显著降低。
1.6.2 地址传递
void func(struct Person *p)
{
p->age = 30; // 修改的是原变量
}
① 直接传递地址可提高内存使用效率,因为所有指针均为8字节大小。
② 在被调函数中,如果只需要读取数据的话,加const 修饰指针就可以了。 如果需要读写的话,去掉const修饰。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Date
{
int year;
int mon;
int day;
};
struct Person
{
char name[50]; // 这个地方是结构体的声明,不能给变量初值
int id; // 原因是没有内存空间
struct Date date; //嵌套结构体,date 是结构体变量,不是指针!
};
void fill_per(struct Person* p) // 对结构体进行修改
{
printf("input name:");
scanf("%s", p->name); // 字符串不需要 &
printf("input id:");
scanf("%d", &p->id); // int 需要 &
printf("input birth year:");
scanf("%d", &p->date.year); // 嵌套逐层访问
printf("input birth mon:");
scanf("%d", &p->date.mon);
printf("input birth day:");
scanf("%d", &p->date.day);
}
void show_per(const struct Person* p) // 对结构体进行读取操作,
{
printf("name:%s id:%d y:%d m:%d d:%d\\n", p->name, p->id, p->date.year,
p->date.mon, p->date.day);
}
int main(int argc, char** argv)
{
struct Person per = {0}; // 定义时初始化,用0填充结构体 ,清0
// memset(&per, 0, sizeof(per)); //用 memset 清零,(已存在的变量重新清零)
fill_per(&per);
show_per(&per);
return 0;
}
二、结构体数组
2.1 结构体数组定义
先定义结构体类型,再定义数组
struct 结构体名 数组名[整形常量];
定义: strcut Person array[10]; // 定义包含10个Person元素的数组
2.2 结构体数组初始化及访问
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Date
{
int year;
int mon;
int day;
};
struct Person
{
char name[50];
int id;
struct Date date;
};
void show_per(const struct Person* p)
{ printf("name:%s id:%d y:%d m:%d d:%d\\n", p->name, p->id, p->date.year,
p->date.mon, p->date.day);
}
//void show_pers(const struct Person *array, int size) 数组退化为指针
void show_pers(const struct Person array[], int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
show_per(&array[i]); //下标方式访问 array[i] 等价于 *(array + i)
}
}
int main(int argc, char** argv)
{
struct Person array[5] = {0}; //全部初始化为0,所有元素所有成员都为0
// 全部初始化
struct Person array2[3] = {
{"zhangsan", 10, {2026, 05, 10}},
{"lisi", 11, {2025, 03, 12}},
{"wangmaizi", 12, {2024, 01, 22}},
};
// gcc 支持的初始化方式
struct Person array3[3] = {
[1] = {"zhangsan", 20, {2021, 10, 1}},
}; //其他元素(下标0和2)自动初始化为0
printf("array3 name:%s y:%d m:%d d:%d\\n", array3[1].name,
array3[1].date.year, array3[1].date.mon, array3[1].date.day);
show_pers(array2, 3);
return 0;
}
三、typedef 和struct
#include <stdio.h>
#include <stdlib.h>
struct Person //定义一个名为 Person 的结构体类型
{
char name[50];
int id;
};
typedef unsigned char u8; //将 unsigned char 重命名为 u8
//定义了一个函数指针类型 PFUN,指向的函数:接受两个 int 参数,返回 int* 类型
typedef int* (*PFUN)(int, int);
//定义结构体 Person1 并同时创建别名 PER,
typedef struct Person1
{
char name[50];
int id;
} PER;
// PER == struct Person1
int main(int argc, char** argv)
{
struct Person per = {0}; // 初始化per的所有成员为0
PER per2 = {"zhangsan", 20}; // 使用PER别名创建结构体变量
struct Person1 per3 = {"lisi", 10}; // 使用原始结构体名创建变量
printf("name:%s id:%d\\n", per2.name, per2.id);
return 0;
}
四、共用体(联合体)
4.1 共用体介绍
① 共用体(Union) 是一种特殊的数据类型,允许在同一内存位置存储不同的数据类型,
但同一时刻只能存储一个成员的值。
② 其定义、初始化和成员访问方式与结构体完全相同,但在内存分配上存在关键差异:
1)内存分配差异:
- 结构体:为每个成员独立分配内存空间,涉及字节对齐问题
- 共用体:仅分配最大成员所需的内存空间,所有成员共享同一块内存
2)主要应用场景:
- 需要节省内存空间时(在结构体和共用体均可用的场合)
- 作为函数参数传递时,当参数存在多个互斥选项的情况(多选一)
- 可用于判断系统的字节序(大小端模式)
③ 结构体 VS 共用体
// 结构体:每个成员独立存储
struct Student {
int id; // 4字节
float score; // 4字节
char grade; // 1字节
}; // 总大小 ≈ 12字节(考虑对齐)
// 共用体:所有成员共享内存
union Data {
int id; // 4字节
float score; // 4字节
char grade; // 1字节
}; // 总大小 = 4字节(最大成员的大小)

4.2 共用体定义
union 共用体类型名
{
数据成员1;
数据成员2;
数据成员3;
……
}; // 分号不可省略
4.3 共用体初始化及访问
#include <stdio.h>
union TestAAA //共用体直接定义
{
int a; // 4字节
char b; // 1字节
long d; // 8字节
};
intmain(int argc, char **argv)
{
union TestAAA test={0}; //初始化清零
test.a =300; // 设置整数值300(十进制) = 0x12C(十六进制
printf("a is %d\\n",test.a); // a is 300
printf("b is %d\\n",test.b); // b is 44
printf("d is %ld\\n",test.d); // b is 300
test.d =123132300; // 会覆盖原来 test.a 中值 ,内存只有一份。
return 0;
}
4.4 大小端判断
#include <stdio.h>
//大小端判断
union End
{
int ia; // 4字节
char cc; // 1字节
};
int main(int argc, char **argv)
{
union End end = {0}; // 初始化为0
end.ia = 1; // 设置int值为1
printf("union size is %lu\\n",sizeof(union End )); //union size is 4
if (1 == end.cc) // 检查低字节的值,读取char成员(只读1个字节)
{
printf("小端\\n"); //小端
}
else
{
printf("大端\\n");
}
return 0;
}
五、枚举类型(enum)
① 枚举:定义一个自定义的变量,把变量可以取到所有的值一一列举出来(或者说一个取值的范围)。
② 枚举值在C语言中被视为整型(int)常量数据。
③ 枚举类型的目的是为了提高程序的可读性和可维护性。
④ 声明枚举类型:
1)格式:enum 枚举类型名 {枚举值1,枚举值2,枚举值3,枚举值4…..}
2)枚举值 默认从0 开始,后面的值都是依次+1
enum WEEK {MON,TUE,WED,THU,FRI,SAT,SUN};
下标值分别为 0 1 2 3 4 5 6
3)枚举值 ,用户可以指定初值。
enum WEEK1 {MON1=3,TUE1,WED1,THU1,FRI1,SAT1,SUN1};
下标值分别为 3 4 5 6 7 8 9
⑤ 定义变量
#include <stdio.h>
//枚举的定义
// MON = 0 1 2 3 4 5 6
enum WEEK {MON,TUE,WED,THU,FRI,SAT,SUN}; //默认值从0开始
//3 4 5 6 7 8 9
enum WEEK1 {MON1=3,TUE1,WED1,THU1,FRI1,SAT1,SUN1}; //显式指定起始值,后续递增
intmain(int argc, char **argv)
{
enum WEEK week= THU; // week = 3
// week =1000; 枚举这个类型,c语言中 当整形变量对待。不建议这么写。
switch(week)
{
case MON: // 0
printf("go to school\\n");
break;
case FRI: // 4
printf("to play…\\n");
break;
case SUN: // 6
printf("go to sleep\\n");
break;
default:
printf("发呆\\n"); //发呆
}
int a=20;
a += FRI; // a = 20 + 4 = 24
printf("a is %d\\n",a); //a is 24
return 0;
}


