结构体类型
结构体数组
案例:
/*
结构体数组案例:对候选人得票的统计程序。
设有3个候选人,每次输入一个得票的候选人的名字,要求最后输出各人得票结果
*/
#include <stdio.h>
#include <string.h>
// 定义一个候选人结构体(对象)
struct Person
{
char *name; // 名字
int count; // 票数
};
// 定义候选人数组,并初始化
struct Person persons[3] =
{
{"张悦", 0},
{"李想", 0},
{"牛逼", 0}};
void main()
{
int i, j;
char *leader_name; // 用来接收被投票的候选人姓名
// 使用一个循环,完成10次投票
for (i = 0; i < 10; i++)
{
printf("请输入您要投票的候选人姓名:\n");
scanf("%s", leader_name);
// 给被投票的候选人+1票
for (j = 0; j < 3; j++)
{
// 如何判断两个字符串
if (strcmp(leader_name, persons[j].name) == 0)
{
persons[j].count++;
}
}
}
printf("\n投票结果:\n");
for (i = 0; i < 3; i++)
{
printf("%5s: %d\n", persons[i].name, persons[i].count);
}
}
结构体指针
- 定义:结构体类型的指针变量指向结构体变量或者数组的起始地址。
- 语法:struct 结构体名 *指针变量列表;
- 举例:
struct Dog { char name[20]; int age; }; struct Dog dog; struct Dog *p = &dog;
结构体成员访问
- 结构体数组名访问结构体成员
- 格式:结构体数组名 --> 成员名;
- 结构体成员访问符
“ . ” :左侧是结构体变量(结构体对象 / 实例);也可以叫做结构体对象访问成员符;右侧是结构体成员。
-> :左侧是一个指针,也可以叫结构体指针访问成员符;右侧是结构体成员。
- 访问结构体成员有两种类型,三种方式:
- 类型1:通过结构体对象访问成员
struct stu { int id; char name[20]; } stu; //访问成员 stu.name;
- 类型2:通过结构体指针访问成员
1.指针引用访问成员struct stu { int id; char name[20]; } stu; struct Stu *p = &stu; //指针引用访问成员 p -> name;
2.指针解引用间接访问成员
3.结构体数组中元素的访问struct stu { int id; char name[20]; } stu; struct Stu *p = &stu; //指针解引用间接访问成员 (*p) -> name;
struct Stu { int id; char name[20]; int score; } stus[2] = { {1,"张三",{86,88,56}}, {2,"张四",{75,66,78}}, {3,"王五",{70,99,90}} }; //取数据 -- 下标法 printf("%s,%2f\n",stus[1].name,stus[1].scores[2]); //结构体成员引用符号: -> 指针法 printf("%s,%2f\n",stus -> name,stus -> scores[2]); //张三,56 printf("%s,%2f\n",(stus + 1) -> name,(stus + 1) -> scores[2]); //张四,78 printf("%s,%2f\n",(*(stus + 1)) -> name,(*(stus + 1)) -> .scores[2]); //王五 90
小贴士:
结构体是自定义数据类型,它是数据类型,用法类似于基本类型int;
结构体数组是存放结构体对象的数组,类似于int数组存放int数据;
基本类型数组怎么用,结构体数组就怎么用--->可以遍历,可以作为形式参数,也可以做指针等;
- 类型1:通过结构体对象访问成员
- 结构体类型的使用案例
代码:#include <stdio.h> //定义结构体 struct Cat { char *name; //姓名 int age; //年龄 char color[20]; //颜色 }; //1.结构体类型作为形式参数 void test1(struct Cat c); //2.结构体类型作为形式参数,结构体类型作为返回值类型 struct Cat test2(struct Cat c); //3.结构体数组作为形式参数 void test3(struct Cat cats[], int len); //4.结构体数组作为形式参数,结构体指针作为返回值数据类型 struct Cat *test4(struct Cat cats[], int len);
测试:
int main() { // 定义结构体对象 struct Cat cat = {"小黑", 8, "baise"}; // 结构体对象作为实际参数 test1(cat); // 定义结构体类型对象 struct Cat cat1 = {"小白", 8, "heise"}; // 调用函数并接收返回值 struct Cat res_cat1 = test2(cat1); // 通过返回值访问结构体对象的成员 printf("%s==%d==%s\n", c1.name, c1.age, c1.color); // 定义结构体数组 struct Cat cats[3] = { {"汤姆", 16, "蓝色"}, {"杰瑞", 18, "褐色"}, {"唐老鸭", 19, "白色"}}; // 结构体数组名作为实际参数 test3(cats, 3); // 定义结构体数组并初始化 struct Cat cats1[3] = { {"汤姆", 16, "蓝色"}, {"杰瑞", 18, "褐色"}, {"唐老鸭", 19, "白色"}}; // 调用函数 struct Cat *p = test4(cats1, 3); struct Cat *w; // 通过指针运算遍历数组 for (w = p; w < p + 3; w++) { // p[i][j] = *(p[i] + j) = *((p + i) + j) 三者等价 // 通过结构体指针访问符访问结构体的成员 printf("%s----%d----%s\n",w -> name,w -> age,w -> color); } }
结构体类型求大小
- 规则:字节对齐(数据在内存中存储在其类型大小的整数倍上)
1. 首先保证结构体中的成员存储在自身的对齐边界(类型大小的整数倍);
2. 在满足1的条件下,最终大小要满足 最大成员 所占存储单元的整数倍;
- 为什么要使用字节对齐:
节省内存,提高访问效率
- 在GNU标准中,可以再定义结构体时,
- 柔性数组:
struct st
{
...
char a[0];
}
柔性数组不占有结构体的大小。
案例:
/**
* 求结构体数据类型的大小
*/
#include <stdio.h>
// 定义测试结构体
struct TEST1
{
char a; // 1
int b; // 4
};
struct TEST1_1
{
char a; // 1
int b; // 4
} __attribute__((packed)); // 取消字节对齐,取消之后,结构体数据类型大小就等于其所有成员的数据类型之和
struct TEST1_2
{
char a __attribute__((aligned(2)));
int b;
};
struct TEST2
{
char a; // 1
short c; // 2
int b; // 4
};
struct TEST3
{
int num; // 4
char name[10]; // 10
char sex; // 1
int age; // 4
double score; // 8
};
struct TEST4
{
int num; // 4
short name[5]; // 10
char sex; // 1
int age; // 4
int scores[2]; // 8
};
int main()
{
// 创建结构体变量
struct TEST1 test1;
struct TEST2 test2;
struct TEST3 test3;
struct TEST4 test4;
struct TEST1_1 test1_1;
struct TEST1_2 test1_2;
// 计算大小
printf("%lu\n", sizeof(test1));
printf("%lu\n", sizeof(test2));
printf("%lu\n", sizeof(test3));
printf("%lu\n", sizeof(test4));
printf("%lu\n", sizeof(test1_1));
printf("%lu\n", sizeof(test1_2));
}
推导过程:快速计算结构体大小:
https://blog.csdn.net/weixin_72357342/article/details/131135555https://blog.csdn.net/x2528238270/article/details/120798606
共用体 / 联合体
- 定义:使几个不同的变量占用同一段内存的结构。共用体按定义中需要存储空间最大的成员来分配存储单元,其他成员也是用该空间,他们的首地址是相同的。
- 定义格式
union 共用体名称 { 数据类型 变量名; 数据类型 变量名; ... };
- 共用体的定义和结构体类型相似:
1. 可以有名字,也可以匿名;
2. 共用体在定义时也可以定义共用体变量;
3. 共用体在定义时也可以初始化成员;
4. 共用体也可以作为形参和返回值类型使用:
5. 共用体也可以定义共用体数组
...
也就是说,结构体,共用体都支持。
- 注意:
- 共用体弊大于利,尽量少用,一般很少用;
- 共用体变量在某一时刻只能存一个数据,并且也只能取出一个数。
- 共用体和结构体都是自定义数据类型,用法类似于基本数据类型
- 共用体可以是共用体的成员,也可以是结构体的成员。
- 结构体可以使共用体的成员,也可以是共用体的成员。
- 案例:
/** * 共用体 */ #include <stdio.h> // 定义共用体 union S { char a; float b; int c; }; // 共用体作为共用体的成员 union F { char a; union S s; }; // 共用体作为结构体的成员 struct G { int a; union S s; }; // 定义一个结构体 struct H { int a; char b; }; // 结构体作为结构体成员 struct I { int a; int b; struct H h; }; // 共用体作为结构体成员 struct J { int a; char b; union S s; }; void test1() { // 定义共用体类型 union Stu { int num; char sex; double score; }; // 定义匿名共用体:匿名共用体一般作为结构体成员或者其他共用体成员 union { int a; char c; } c; printf("%lu,%lu\n", sizeof(union Stu), sizeof(c)); } void test2() { union C { int a; char b; }; // 定义变量 union C c; // 存数据 c.a = 10; c.b = 'A'; printf("%d---%d\n", c.a, c.b); // 取数据 c.a += 5; printf("%d---%d\n", c.a, c.b); // 取数据 union E { char *f; long a; int b; } e = {"hello world!"}; printf("%s,%p---%ld,%p---%d\n", e.f, &(e.f), e.a, &(e.a), e.b); } int main() { test1(); test2(); }
- 共用体弊大于利,尽量少用,一般很少用;
枚举
- 定义:
我们一般情况下,定义常量使用宏定义(#define宏名称 值),宏定义非常适合没有关联关系的常量;但是有时候我们可能需要对一组拥有关联关系的量进行定义,比如 周一~周日、1月~12月 等,那么使用宏定义,就不是很清晰,在这个时候就需要使用到枚举。
枚举的存在就是将多个拥有关联关系的常量组合到一起,提高代码的可读性。
- 说明:
枚举类型定义了一组常量,我们在卡法中直接使用这些常量(常用)
当然枚举类型也可以类似于结构体一样定义变量等操作。(不常用)
枚举常量有默认值,从0开始依次加1;我们可以在定义时指定它的值,如果个别没有赋值,可以根据赋值依次加1推导。
- 特点:
定义了一组常量,类似于定义了多个自定义常量(宏定义)
提供了代码的可读性(避免了魔术数字)
- 定义语法:
定义枚举类型名以后就可以定义该枚举类型的变量enum 枚举类型名 变量列表;
在定义枚举类型的同时定义该枚举类型的变量。
enum 枚举类型名{ 枚举元素列表 }变量表;
直接定义枚举类型变量。
enum { 枚举元素列表 }变量表;
- 案例:
/* 枚举类型 */ #include <stdio.h> // 常量之宏定义 // 常量的命名:大写英文字母+下划线,举例:MAX_VALUE #define PI 3.1415926 void test1() { // 定义枚举类型 enum Week { SUN = 10, MON, TUE, WED, THU, FRI, SAT }; printf("%d,%d,%d\n", SUN, WED, SAT); // 定义枚举类型的变量(先定义变量,后赋值) enum Week w; // 初始化 w = MON; printf("%d\n", w); // 定义枚举类型的变量同时赋值(定义变量的同时赋值) enum Week w1 = THU; printf("%d\n", w1); enum H { A, B, C } x, y; x = B; y = C; printf("x=%d,y=%d\n", x, y); } void test2() { // 定义枚举 enum CaiQuan { SHI_TOU, JIAN_DAO, BU }; printf("请输入0~2之间的整数: [0-石头,1-剪刀,2-布]\n"); int number; scanf("%d", &number); switch (number) // switch 和 enum 是天生的搭档 { case SHI_TOU: printf("石头\n"); break; case JIAN_DAO: printf("剪刀\n"); break; case BU: printf("布\n"); break; } } int main() { test1(); test2(); }
typedef
- 说明:给类型重命名,不会影响到类型本身
- 作用:给已有的类型起别名
- 格式:
typedef 已有类型名 新别名;
使用:
//定义结构体
struct Stu
{
int a;
char *name;
char sex;
int age;
};
//类型重命名
typedef struct Student Stu;
//定义变量
struct Stu stu = {1,"张甜",'M',21};
//定义结构体的同时类型重命名
typedef struct PersonInfo
{
int a;
double b;
} Per;
//定义变量
struct Per per = {2,5};
- 应用场景
1.数据类型复杂(结构体,共用体,枚举,结构体指针)时使用
2.为了跨平台兼容性,例如:
1.size_t : 类型重命名后的数据类型,typedef unsigned long size_t;
2.unit_16 : 类型重命名后数据类型
- 案例
// 类型重命名 #include <stdio.h> struct Student { int age; char *name; double score; int arr[3]; }; typedef struct Student Stu_t; typedef Stu_t *pStu_t; void test1() { Stu_t s1 = {23, "zhangsan", 23.33, {11, 22, 33}}; printf("%d, %s, %f, %d\n", s1.age, s1.name, s1.score, s1.arr[0]); // Stu_t *p = &s1; Stu_t *p; p = &s1; pStu_t p2; p2 = p; printf("%d, %s, %f, %d\n", p2->age, p2->name, p2->score, p2->arr[0]); } int main() { test1(); return 0; }