Bootstrap

C 语言结构体

本博客涉及的结构体知识有:

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字节等。这是因为处理器内部的总线宽度、缓存行大小和寄存器大小通常是这些值的倍数。当数据对齐到这些边界时,处理器可以更高效地读取或写入数据,避免了多次内存访问或者额外的数据重排操作,从而提高了执行速度。

  1. 硬件限制

某些硬件平台不能在任意内存地址上访问任意数据类型。例如,一些处理器可能无法直接读取非对齐的长字节数据,尝试这样做可能会导致硬件异常,比如对齐错误(alignment fault)。

  1. 跨平台和移植性

不同的硬件架构可能有不同的内存对齐偏好。如果一个结构体在一个架构下没有正确对齐,而在另一个架构下被读取,可能会导致不可预测的行为或错误。通过遵循标准的对齐规则,可以增强代码的跨平台兼容性和可移植性。

  1. 内存访问模式

对齐也有助于优化缓存行为。缓存通常以特定大小的块存储数据,如果数据对齐得当,就更有可能在缓存命中时加载更多的相关数据,从而减少缓存缺失和提高性能。


结构体内存定义示意图:

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;
}


 ......

;