本博客涉及的结构体知识有:
1.0:结构体的创建和使用
2.0: typedef 关键字与#define 关键字的区别
3.0: 结构体成员的访问【地址访问与成员访问】
4.0: 结构体嵌套调用
5.0 数组访问赋值结构体成员
......
1.0:结构体的创建和使用
结构体类型和枚举类型一样,是一种用户自定义的数据类型,它可以使用一个变量来描述事物的多种属性,便于数据的管理,数据类型声明的语法格式:或者说->【结构是一些值的集合,这些值被称为成员变量,结构体中的成员可以是不同的变量类型 】,和数组相比结构体的数据类型更为的灵活,数组指的是一组相同类型的值的集合,数组的下标是从0开始的。
格式1:
// 结构体的声明格式
typedef struct
{
成员列表
}structName_t;
注:成员列表中的成员并不是变量在什么的时候并不会开辟内存空间
内存空间的开辟要在创建结构体变量之后
***********************************
* 结构体: 创建结构体案例
* 参数 : 结构体成员 char name[20]; char tel[12];
* 返回值: 无
* 时间 : 2024/7/13
* 作者 : _沧浪之水_
************************************
**/
typedef struct
{
char name[20];
char tel[12];
char sex[5];
int high;
}People_t;
格式2:
结构体变量的声明
struct Person
{
// 成员列表
char name[20];
int age;
}struct_name_t;
上面的结构体是一个结构体数据类型,并不是一个变量,创建结构体类型的时候并不会开辟内存空间
struct Stu
{
// 成员列表
char name[20];
int age;
}s1,s2;
s1 与 s2 是 struct Stu 类型的变量(s1 s2 是结构体成员的全局变量)
格式3:
// 结构体成员变量声明
struct Point
{
int x;
int y;
}p1 = {2,3};
struct score
{
int n;
char ch;
};
struct Stu
{
char name[20];
int age;
struct score s;
};
成员变量的初始化
int main()
{
struct Stu s1 = {"8888",20,{100,'q'}};
printf(%s %d %d %c,s1.name,s1.age,s1.n,s1.s.ch);
}
结构体变量的定义
/**
***********************************
* 结构体: 结构体变量定义后同时进行赋值
* 参数 : 无参数
* 返回值: 无返回值
* 时间 : 2024/7/13
* 作者 : _沧浪之水_
************************************
**/
People_t peo = { "张三","15863310892","男",181 };
2.0 typedef 关键字的使用
typedef关键关键字:用于定义一个已有关键字的别名,具体创建格式如下所示
/**
***********************************
* 结构体: typedef 关键字的用法
* 参数 : 无参数
* 返回值: 无返回值
* 时间 : 2024/7/13
* 作者 : _沧浪之水_
************************************
**/
typedef 数据类 名字
typedef uint8_t uint;
typedef unsigned char uchar;
typedef 关键字和define关键字的区别,两者都是给对象取一个别名增强程序的可读性【尽量防止程序中魔鬼数字的出现】,两者有如下的区别
1: 使用场景不同
- typedef 关键字用于给数据类型定义别名
- #define关键字又称为(宏定义) 用于给数字,表达式(写表达式时注意带上括号防止异常问题的出现),代码语句定义别名。
2:执行时机不同
- typedef在编译阶段执行;
- #define在预编译阶段执行;
3:定义方法不同
- #define别名在替换对象的前面,并且定义后面不用加分号;
#define PI 3.14
#define MAX_NUM_LIST 9
typedef的别名在替换对象的后面,并且定义后面需要加分号;
typedef unsigned char uchar;
3.0: 结构体成员的访问
注:有指针访问和圆点运算符访问两种访问方式
使用圆点运算符并打印输出
/**
***********************************
* 结构体: 结构体成员变量访问
* 参数 : 结构体成员访问
* 返回值: 无
* 时间 : 2024/7/13
* 作者 : _沧浪之水_
************************************
**/
// peo 是结构体变量,结构体变量创建后才会开辟内存控制,结构体变量初始化
People_t peo = { "张三","15863310892","男",181 };
// 浮点数在内存中不能精确的存储,结构体嵌套初始化
recallStruct_t recal = { {"李四","15863310892","男",181},100, 88.8f };
printf("%s %s %s %d\n", peo.name, peo.tel, peo.sex, peo.high);
printf("%s %s %s %d %d %f\n",
recal.p.name,
recal.p.tel,
recal.p.sex,
recal.p.high,
recal.num,
recal.f);
使用指针访问【地址访问】
/**
***********************************
* 结构体: 结构体传递地址调用
* 参数 : *p 指针地址
* 返回值: 无
* 时间 : 2024/7/13
* 作者 : _沧浪之水_
************************************
**/
void Print(People_t *p)
{
// 左边的是结构体指针 “结构体指针->成员变量”
printf("%s %s %s %d\n", p->name, p->tel, p->sex, p->high);
}
注:以上的这种写法需要在主函数中进行调用
4.0: 结构体嵌套调用
#define _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
/**
***********************************
* 结构体: 创建结构体
* 参数 : 结构体成员 char name[20]; char tel[12];
* 返回值: 无
* 时间 : 2024/7/13
* 作者 : _沧浪之水_
************************************
**/
typedef struct
{
char name[20];
char tel[12];
char sex[5];
int high;
}People_t;
/**
***********************************
* 结构体: 创建结构体
* 参数 : 结构体成员,内部包含结构体【顺带变量初始化,结构体初始化】
* 返回值: 无
* 时间 : 2024/7/13
* 作者 : _沧浪之水_
************************************
**/
typedef struct
{
People_t p;
int num;
float f;
}recallStruct_t;
/**
***********************************
* 结构体: 结构体传递地址调用
* 参数 : *p 指针地址
* 返回值: 无
* 时间 : 2024/7/13
* 作者 : _沧浪之水_
************************************
**/
void Print(People_t *p)
{
// 左边的是结构体指针 “结构体指针->成员变量”
printf("%s %s %s %d\n", p->name, p->tel, p->sex, p->high);
}
/**
***********************************
* 结构体: 结构体成员变量访问
* 参数 : 结构体成员访问
* 返回值: 无
* 时间 : 2024/7/13
* 作者 : _沧浪之水_
************************************
**/
void PrintTwo(People_t input)
{
// 左边的是结构体变量 “结构体变量.成员变量”
printf("%s %s %s %d\n", input.name, input.tel, input.sex, input.high);
}
int main()
{
// peo 是结构体变量,结构体变量创建后才会开辟内存控制,结构体变量初始化
People_t peo = { "张三","15863310892","男",181 };
// 浮点数在内存中不能精确的存储,结构体嵌套初始化
recallStruct_t recal = { {"李四","15863310892","男",181},100, 88.8f };
printf("%s %s %s %d\n", peo.name, peo.tel, peo.sex, peo.high);
printf("%s %s %s %d %d %f\n", recal.p.name, recal.p.tel, recal.p.sex, recal.p.high, recal.num, recal.f);
Print(&peo);
PrintTwo(peo);
return 0;
}
注:以上的注释编写方式仅限于更好的理解知识,实际的开发过程会更为规范,推荐书籍《高质量 C C++ 编程指南 》。
注:上面的两种打印输出方式哪一种打印方式更好,结构体传递参数吧 “对象” 进行结构体传参的时PrintTwo() 这种参数传递方式方式时PrintTwo(peo),peo实际是一个对象已经在内存中开辟了一块内存空间,如果把对象作为实际参数传递给形参的时候“形参里面的内容实际上是实际参数的一份拷贝” 打印peo的时候打印输出的数据是一样的,空间和时间的浪费会降低程序的性能。【推荐使用地址传递的方式把参数的地址传递进去(地址的大小就是4-8字节)通过地址找到数据】参数传递的时候会压栈,结构体传递参数的时候尽量传递结构体的地址节省时间和空间。
5.0 数组方式给结构体成员赋值
#define _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
#define MAX_LIST_NUM (sizeof(arrList) / sizeof(arrList[0]))
/**
***********************************
* 结构体: 创建结构体
* 参数 : 结构体成员 char name[20]; char tel[12];
* 返回值: 无
* 时间 : 2024/7/13
* 作者 : _沧浪之水_
************************************
**/
typedef struct
{
char name[20];
char tel[12];
char sex[5];
int high;
}People_t;
static People_t arrList[] =
{
{"Keil","122345625","男",180},
{"Ling","122345625","女",185}
};
int main()
{
uint8_t i = 0;
for (i = 0; i < MAX_LIST_NUM; i++)
{
printf("%s %s %s %d\n",
arrList[i].name,
arrList[i].tel,
arrList[i].sex,
arrList[i].high);
}
return 0;
}
6.0 结构体内存对齐
现代处理器在读取或写入内存时,倾向于处理特定长度的数据,这些长度通常是2字节、4字节、8字节等。这是因为处理器内部的总线宽度、缓存行大小和寄存器大小通常是这些值的倍数。当数据对齐到这些边界时,处理器可以更高效地读取或写入数据,避免了多次内存访问或者额外的数据重排操作,从而提高了执行速度。
-
硬件限制
某些硬件平台不能在任意内存地址上访问任意数据类型。例如,一些处理器可能无法直接读取非对齐的长字节数据,尝试这样做可能会导致硬件异常,比如对齐错误(alignment fault)。
-
跨平台和移植性
不同的硬件架构可能有不同的内存对齐偏好。如果一个结构体在一个架构下没有正确对齐,而在另一个架构下被读取,可能会导致不可预测的行为或错误。通过遵循标准的对齐规则,可以增强代码的跨平台兼容性和可移植性。
-
内存访问模式
对齐也有助于优化缓存行为。缓存通常以特定大小的块存储数据,如果数据对齐得当,就更有可能在缓存命中时加载更多的相关数据,从而减少缓存缺失和提高性能。
结构体内存定义示意图:
offsetof结构体偏移量验证
#define _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
struct s1
{
char a;
int i;
char b;
};
struct s2
{
char b;
char a;
int i;
};
int main()
{
struct s1 sOne;
struct s2 sTwo;
printf("%d\n", sizeof(struct s1));
printf("%d\n", sizeof(struct s2));
printf("|+----------+|----------+|---------+|-----------|");
// 使用offsetof计算偏移量
printf("%d\n", offsetof(struct s1,a));
printf("%d\n", offsetof(struct s1,i));
printf("%d\n", offsetof(struct s1,b));
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
struct s1
{
char a;
int i;
char b;
};
struct s2
{
char b;
char a;
int i;
};
int main()
{
struct s1 sOne;
struct s2 sTwo;
printf("%d\n", sizeof(struct s1));
printf("%d\n", sizeof(struct s2));
printf("|+----------+|----------+|---------+|-----------|");
printf("\n");
// 使用offsetof计算偏移量
printf("%d\n", offsetof(struct s1,a));
printf("%d\n", offsetof(struct s1,i));
printf("%d\n", offsetof(struct s1,b));
printf("%d\n", offsetof(struct s2, b));
printf("%d\n", offsetof(struct s2, a));
printf("%d\n", offsetof(struct s2, i));
return 0;
}
计算结构体成员的大小
嵌套结构体大小计算
结构体修改默认对齐数
#define _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
// 修改默认对齐数
#pragma pack(4)
struct S
{
int i; // 4 ----- > 4 和 4 进行比较那就是 4
double d; // 8 -----> 8 和 4 进行比较那么还是 4
};
#pragma pack()
int main()
{
printf("%d\n", sizeof(struct S)); // 12
return 0;
}
结构体参数传递方式
注:结构体传递参数的方式一共有两种,一个是地址传递,一个是.操作符传递,函数传递参数的时候,需要压栈,会有时间和空间上的系统开销如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大会导致程序性能的下降,涉及结构体参数传递的时候尽量使用地址传递的方式。
结构体传递参数方式 1
#define _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
struct S
{
int data[100];
int num;
};
static void Print(struct S ss)
{
uint8_t i;
for (i = 0; i < 3; i++)
{
printf("%d ", ss.data[i]);
}
printf("%d ", ss.num);
}
int main()
{
struct S s = { {1,6,8},100 };
Print(s);
return 0;
}
结构体传递参数方式 2
#define _CRT_SECURE_NO_WARNINGS
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include <string.h>
#include "add.h"
#include <stdint.h>
struct S
{
int data[100];
int num;
};
static void Print(struct S ss)
{
uint8_t i;
for (i = 0; i < 3; i++)
{
printf("%d ", ss.data[i]);
}
printf("%d ", ss.num);
}
static void Print_p(struct S *ps)
{
uint8_t i;
for (i = 0; i < 3; i++)
{
printf("%d ", ps->data[i]);
}
printf("%d ", ps->num);
}
int main()
{
struct S s = { {1,6,8},100 };
Print(s);
printf("\n");
printf("|-------------+----------+------+------|\n");
Print_p(&s);
return 0;
}
......