嵌入式 - C语言:构造数据类型、位运算与内存管理

发布于:2025-08-04 ⋅ 阅读:(11) ⋅ 点赞:(0)

 在 C 语言中,除了基础的数据类型,构造数据类型为我们处理复杂数据提供了强大工具,而位运算和内存管理则是优化程序性能、避免内存问题的关键。本文将从结构体数组出发,深入讲解共用体、枚举、位运算符及内存管理的核心知识与实践技巧。

一、结构体数组:批量处理复杂对象

结构体数组是由相同结构体类型元素组成的集合,非常适合批量管理具有多个属性的对象(如学生、员工等)。

1. 结构体数组的定义与初始化

  • 定义形式结构体类型 数组名[元素个数];,其中元素个数必须是常量。

    例如,定义一个包含 3 名学生的数组:

    struct student {
        char name[32];  // 姓名
        char sex;       // 性别('m'/'f')
        int age;        // 年龄
        int score;      // 成绩
    };
    struct student stu_array[3];  // 包含3个学生的数组
    
  • 初始化方式

    • 全部初始化:按顺序为每个元素的成员赋值。
      struct student stu_array[3] = {
          {"zhangsan", 'm', 19, 100},
          {"lisi", 'f', 18, 90},
          {"wanger", 'm', 19, 60}
      };
      
    • 指定元素初始化:通过[索引]指定特定元素的成员,未指定的元素默认初始化为 0。
      struct student stu_array[3] = {
          [1] = {.name = "zhangsan", .score = 90}  // 仅初始化第2个元素
      };
      

2. 结构体数组的访问与传参

  • 元素访问:通过数组名[索引].成员名访问单个成员,例如 stu_array[0].age = 20;
  • 传参技巧:结构体数组传参时,推荐传递地址(指针),避免大结构体的拷贝开销。函数定义形式为:

  • 示例:遍历打印学生信息
    void print_students(struct student *pstu, int len) {
        for (int i = 0; i < len; i++) {
            printf("姓名:%s,年龄:%d\n", pstu[i].name, pstu[i].age);
        }
    }

3.实战代码 

#include<stdio.h>
struct student {
	char name[32];
	char sex;
	int age;
	int score;
};

int GetAllstu(struct student *pstu,int len)
{
	int i = 0;
	for(i = 0;i < len;i++)
	{
#if 0
//指针++ ->访问
		gets(pstu->name);
		scanf(" %c",&pstu->sex);
		scanf("%d",&pstu->age);
		scanf("%d",&pstu->score);
		pstu++;

//指针+i ->访问
		gets((pstu+i)->name);
		scanf(" %c",&(pstu+i)->sex;
		scanf("%d",&(pstu+i)->age);
		scanf("%d",&(pstu+i)->score);
#endif	

//直接用p[i] 访问
		gets(pstu[i].name);
//		scanf("%s",pstu[i].name);//不会接收 空格和'\0'
		                         //字符串接收不用加'&'
		scanf(" %c",&pstu[i].sex);
		scanf("%d",&pstu[i].age);
		scanf("%d",&pstu[i].score);
        
		getchar();//加入 getchar()函数,清除每一次的 \n
	}
	return 0;
}
int PutAllstu(struct student *pstu,int len)

{
	int i =0;
	for(i = 0;i < len;i++)
	{
		printf("姓名:%s\n",pstu->name);
		printf("性别:%c\n",pstu->sex);
		printf("年龄:%d\n",pstu->age);
		printf("成绩:%d\n",pstu->score);
		pstu++;
#if 0

		printf("姓名:%s\n",(*(pstu+i)).name);
		printf("性别:%c\n",(*(pstu+i)).sex);
		printf("年龄:%d\n",(*(pstu+i)).age);
		printf("成绩:%d\n",(*(pstu+i)).score);
	
		printf("姓名:%s\n",(pstu+i)->name);
		printf("性别:%c\n",(pstu+i)->sex);
		printf("年龄:%d\n",(pstu+i)->age);
		printf("成绩:%d\n",(pstu+i)->score);

		printf("姓名:%s\n",pstu[i].name);
		printf("性别:%c\n",pstu[i].sex);
		printf("年龄:%d\n",pstu[i].age);
		printf("成绩:%d\n",pstu[i].score);

#endif
		printf("===========================\n");
	}
		return 0;
}
int main(void)

{
	struct student st[3] = {
		{"zhangsan",'m',18,90},
		{"lisi",'f',16,70},
		{"wanger",'m',18,80},
	};

	struct student stu[3] = {
		[0] = {
				.name = "zhaowu",
				.score = 80,
		},
		[2] = {
				.name = "maliu",
				.score = 70,
		},
	};

	struct student s[3];

	GetAllstu(s,3);
	PutAllstu(s,3);


	return 0;
}

#include<stdio.h>

union s {
	char a;
	short b;
	int c;

};

int main(void)
{

	union s s1;

	s1.a = 'A';
	printf("a:%c\n",s1.a);

	s1.b = 100;
	printf("b:%d\n",s1.b);

	s1.c = 1000;
	printf("c:%d\n",s1.c);

	s1.a = 'A';
	printf("a:%c\n",s1.a);

	//上一个值会被下一个值覆盖
	printf("s1.a = %c\n",s1.a);
	printf("s1.b = %d\n",s1.b);
	printf("s1.c = %d\n",s1.c);


	return 0;
}

  1.  ​联合体内存共享
    联合体s的所有成员(char ashort bint c)共享同一块内存区域,内存大小由最大成员int c(4字节)决定。​任何成员被赋值都会覆盖其他成员的值

  2. 内存覆盖过程
    分步解析代码操作(假设系统为小端字节序):

    • s1.c = 1000;
      1000的十六进制为 0x000003E8,小端存储模式在内存中的布局:
      低地址 -> 高地址
      0xE8 0x03 0x00 0x00
    • s1.a = 'A';(ASCII值为65 = 0x41)
      仅覆盖内存的第一个字节(低地址):
      0x41 0x03 0x00 0x00  // 原0xE8被覆盖为0x41
      此时内存实际值为十六进制数 0x00000341
  3. 读取成员时的解释

    • s1.a:读取第1字节 → 0x41 → 字符 'A'(正确输出)。
    • s1.b:读取前2字节 → 0x0341(小端模式)
      0x0341 十进制 = ​3 × 256 + 65 = 833
    • s1.c:读取全部4字节 → 0x00000341(小端)
      0x341 十六进制 = ​3 × 256 + 65 = 833

二、共用体(联合体):共享内存的灵活类型

共用体(union)与结构体的最大区别是所有成员共享同一块内存空间,适合在不同场景下使用不同类型访问同一段数据。

1. 共用体的定义与特性

  • 定义形式
    union 共用体名 {
        数据类型1 成员1;
        数据类型2 成员2;
        ...
    }; {insert\_element\_4\_}
    
  • 核心特性
    • 所有成员共享同一块内存,空间大小等于最大成员的大小。
    • 修改一个成员会覆盖其他成员的值(因内存共享)。

2. 经典应用:判断内存大小端

内存大小端是指多字节数据在内存中的存储顺序:

  • 小端存储:低地址存放数据的低字节(如0x11223344在内存中为44 33 22 11)。
  • 大端存储:低地址存放数据的高字节(如0x11223344在内存中为11 22 33 44)。

利用共用体判断大小端的代码:

#include<stdio.h>

union s {
	char a;
	short b;
	int c;

};

int main(void)
{

	union s s1;

	s1.a = 'A';
	printf("a:%c\n",s1.a);

	s1.b = 100;
	printf("b:%d\n",s1.b);

	s1.c = 1000;
	printf("c:%d\n",s1.c);

	s1.a = 'A';
	printf("a:%c\n",s1.a);

	//上一个值会被下一个值覆盖
	printf("s1.a = %c\n",s1.a);
	printf("s1.b = %d\n",s1.b);
	printf("s1.c = %d\n",s1.c);


	return 0;
}

  1.  ​联合体内存共享
    联合体s的所有成员(char ashort bint c)共享同一块内存区域,内存大小由最大成员int c(4字节)决定。​任何成员被赋值都会覆盖其他成员的值

  2. 内存覆盖过程
    分步解析代码操作(假设系统为小端字节序):

    • s1.c = 1000;
      1000的十六进制为 0x000003E8,小端存储模式在内存中的布局:
      低地址 -> 高地址
      0xE8 0x03 0x00 0x00
    • s1.a = 'A';(ASCII值为65 = 0x41)
      仅覆盖内存的第一个字节(低地址):
      0x41 0x03 0x00 0x00  // 原0xE8被覆盖为0x41
      此时内存实际值为十六进制数 0x00000341
  3. 读取成员时的解释

    • s1.a:读取第1字节 → 0x41 → 字符 'A'(正确输出)。
    • s1.b:读取前2字节 → 0x0341(小端模式)
      0x0341 十进制 = ​3 × 256 + 65 = 833
    • s1.c:读取全部4字节 → 0x00000341(小端)
      0x341 十六进制 = ​3 × 256 + 65 = 833

三、枚举:增强代码可读性的常量集合

枚举(enum)用于定义一组命名常量,使代码更易读、维护。

1. 枚举的定义与特性

  • 定义形式

    enum 枚举名 {
        常量1,
        常量2,
        ...
    }; {insert\_element\_8\_}
    
  • 默认规则

    • 第一个常量默认值为 0,后续常量值为前一个 + 1。
    • 可手动指定常量值,未指定的则延续前一个值。
  • 示例

    enum week {
        MON,    // 0
        TUE,    // 1
        WED = 5,  // 手动指定为5
        THU     // 6(5+1)
    };
    

2. 应用场景

枚举常用于表示状态、选项等固定值集合,例如:

enum status {
    SUCCESS,  // 0:成功
    ERROR,    // 1:错误
    TIMEOUT   // 2:超时
};

// 使用枚举使代码更清晰
int connect() {
    if (/* 连接成功 */) return SUCCESS;
    else if (/* 超时 */) return TIMEOUT;
    else return ERROR;
}

 3.实战代码

#include<stdio.h>

enum weekday {
	//默认第一个数为0
	//按顺序递增
	MONDAY ,
	TUESDAY,
	WEDNESDAY = 9,
	THURDAY,
	FRIDAY,
	SATURSDAY,
	SUNDAY,
};

int main(void)
{
	enum weekday day;
	
	printf("请输入今天是周几:\n");
	scanf("%d", (int *)&day);
	//将枚举类型转换为int类型

	switch (day)
	{
		case MONDAY:printf("尾号1和6限行\n");break;
		case TUESDAY:printf("尾号2和7限行\n");break;
		case WEDNESDAY:printf("尾号3和8限行\n");break;
		case THURDAY:printf("尾号4和9限行\n");break;
		case FRIDAY:printf("尾号5和0限行\n");break;
		case SATURSDAY:
		case SUNDAY:
					printf("不限行\n");
	}

	return 0;
}




四、位运算符:直接操作内存的高效工具

位运算符用于对二进制位直接操作,在底层编程、性能优化中广泛应用。

1. 常用位运算符

运算符 含义 规则
& 按位与 对应位均为 1 则为 1,否则为 0(与 0 得 0)
| 按位或 对应位有 1 则为 1(或 1 置 1)
^ 按位异或 对应位不同则为 1,相同为 0
~ 按位取反 0 变 1,1 变 0
<< 左移 各二进制位左移 n 位,高位丢弃,低位补 0(等价于 ×2ⁿ)
>> 右移 各二进制位右移 n 位,低位丢弃,高位补符号位(等价于 ÷2ⁿ)

2. 实用技巧

  • 置位操作:将某一位设为 1(例如第 3 位):
    int num = 0;
    num |= (1 << 3);  // 1<<3 为0b1000,与num或运算后第3位变为1
  • 清位操作:将某一位设为 0(例如第 2 位):
    num &= ~(1 << 2);  // ~(1<<2) 为0b11111011,与num与运算后第2位变为
    
  • 不使用临时变量交换两数
    int a = 3, b = 5;
    a ^= b;  // a = 3^5
    b ^= a;  // b = 5^(3^5) = 3
    a ^= b;  // a = (3^5)^3 = 5 
    

 

#include<stdio.h>
int main(void)

{
	int a = 100;
	int b = 200;
//异或实现不使用中间变量交换
	a = a ^ b;
	printf("a = %d\n",a);
	b = a ^ b;//a b b =  a ^ 0
	printf("b = %d\n",b);
	a = a ^ b;//a b a b b = 0 ^ b
	printf("a = %d\n",a);

	printf("a = %d,b = %d\n",a,b);

	return 0;
}

五、内存管理:避免泄露与优化性能

C 语言中,内存管理由程序员手动控制,堆区空间的申请与释放是重点。

1. 内存区域划分

程序运行时的内存分为以下区域:

  • 栈区:存放局部变量,由操作系统自动管理(进入作用域时分配,离开时释放)。
  • 堆区:由程序员手动申请(malloc)和释放(free),空间较大,生命周期灵活。
  • 数据段:存放全局变量、静态变量,编译时分配,程序结束后释放。
  • 文本段:存放函数代码和指令,只读。

2. 堆区操作函数

  • malloc:申请堆区空间

    void *malloc(size_t size);  // size为字节数,成功返回首地址,失败返回NULL{insert\_element\_19\_}
    
     

    示例:申请一个能存放 5 个 int 的数组

    int *arr = (int*)malloc(5 * sizeof(int));
    if (arr == NULL) {  // 必须检查申请是否成功
        printf("内存申请失败\n");
        return -1;
    }
    
  • free:释放堆区空间

    void free(void *ptr);  // ptr为malloc返回的地址{insert\_element\_20\_}
    
     

    注意:

    • 释放后需将指针置为NULL,避免野指针(指向已释放空间的指针)。
    • 不能重复释放同一空间,否则会导致程序崩溃。

3. 内存泄露及其避免

  • 内存泄露:只申请堆区空间而不释放,导致可用内存逐渐减少,最终可能使程序崩溃。
  • 避免方法
    • 遵循 “谁申请,谁释放” 原则。
    • 使用完堆区空间后立即用free释放,并将指针置空。
    • 复杂场景下可使用内存泄露检测工具(如 Valgrind)。

4.实战代码

1==============

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(void)
{

	//指针初始化为NULL
	int *p = NULL;
	char *s = NULL;

	s = malloc(32);
	p = malloc(4);
	if(NULL == p || NULL == s)
	{
		printf("malloc failed\n");
		return -1;
	}

	*p = 100;
//	s = "hello world";//错误//字符串只读区
//str = hello world (虽然可以输出)
//free(): invalid pointer
//Aborted (core dumped)
	//free(s) 试图释放只读区的字符串常量触发段错误
	strcpy(s,"hello world");

	printf("str = %s\n",s);
	free(s);
	printf("*p = %d\n",*p);
//需要释放堆区空间
	free(p);
	return 0;

}


2================= 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//封装函数实现字符串的输入
char *fun(void)
{
	//char str[] = "hello world";//错误
	//子函数运行结束后会被回收
	char *str = NULL;

	str = malloc(32);
	if(NULL == str)
	{
		printf("malloc failed\n");
		return NULL;
	}

	strcpy(str,"hello world");

	return str;
}

int main(void)
{
	char *p = NULL;

	p = fun();
	printf("p = %s\n",p);

	free(p);//释放后再置为NULL
			//防止产生野指针
			//指针指向的内存已被释放,
			//但指针仍保留原地址值。
	p = NULL;

	return 0;
}

六、综合运用


//有一个班的4个学生,有5课程。
//	1、求第一门课的平均分;
//	2、找出有两门以上课程不及格的学生,
//	输出他们的学号和全部课程成绩及平均分
//	3、找出平均分在90分以上或全部课程成绩
//	在85分以上的学生。
//	   分别编写三个函数来实现以上三个要求
	
#include<stdio.h>
struct student{
	char name[32];
	int n;
	int score[5];
};

float avg(struct student *ps,int stu)
{
	int sum = 0;
	int i = 0;
	for(i = 0;i < 5;i++)
	{
		sum += ps[stu].score[i];
	}

	return (float)sum / 5.0;
}
int Averagescore(struct student *ps,int class)
{
	int i = 0;
	int sum = 0;
	for(i = 0;i < 4;i++)
	{
		sum += ps[i].score[class];
	}
	printf("第%d门课的平均分 = %.2f\n",class+1,(float)sum / 5.0);
	printf("==============================\n");
	return 0;
}

int fun1(struct student *ps)
{
	int i = 0;
	int j = 0;

	for(i = 0;i < 4;i++)
	{
		int un = 0;
		int stu = -1;
		for(j = 0;j < 5;j++)
		{
			if(ps[i].score[j] < 60)
				un++;
			if(un > 2)
			{	stu = i;
				break;
			}
		}
		if(-1 != stu)
		{	printf("%s有两门以上课程不及格\n",ps[i].name);
			printf("学号:%d\n",ps[i].n);
			printf("课程成绩:");
			for(j = 0;j < 5;j++)
				printf("%d ",ps[i].score[j]);
			printf("\n平均分:%.2f\n",avg(ps,i));
			printf("==============================\n");
		}
	}
	return 0;
}

int fun2(struct student *ps)
{
	int m = 0;
	int i = 0;
	int j = 0;

	for(i = 0;i < 4;i++)
	{
		for(j = 0;j < 5;j++)
		{
			if(ps[i].score[j] <= 85)
				break;
		}
		if(j == 5 || avg(ps,i) > 90)
		{	if(m == 0)
				printf("平均分在90以上或全部成绩在85以上的学生有:\n");
			printf("%s ",ps[i].name);
			m++;
		}
	}
		if(m == 0)
			printf("没有平均分在90以上或全部成绩在85以上的学生");
	
			printf("\n");
		printf("==============================\n");
	
	return 0;
}

int main(void)
{

	struct student s[4] ={
		{"zhangsan",2001,{50,55,70,36,90}},
		{"lisi",2002,{89,56,98,77,87}},
		{"wanger",2003,{99,97,83,96,79}},
		{"chenxi",2004,{85,33,24,94,34}},
	};

	Averagescore(s,0);
	fun1(s);
	fun2(s);

	return 0;
}


网站公告

今日签到

点亮在社区的每一天
去签到