嵌入式入门Day17
Day17
- 指针
- 指向堆区的指针
- 练习
- 野指针
- 预处理指令
- 预处理指令的作用
- 预处理指令的分类
- 文件包含指令
- 宏定义
- 条件编译
指针
指向堆区的指针
- 堆区空间的申请和释放(
malloc
、free
)
#include <stdlib.h>void *malloc(size_t size)/** 功能:在堆区申请指定的size字节大小空间,并将该空间的地址返回给调用函数* 参数:要申请的空间大小,以字节为单位* 返回值:成功返回申请出来的空间的起始地址,是一个万能指针,使用是需要转变为具体的指针,失败时返回NULL*/void free(void *ptr)/** 功能:将堆区的手动申请的内存空间释放* 参数:堆区空间的指针* 返回值:无*/
- 可以完成单个空间的申请和释放也可以完成连续空间的申请和释放
- 单个空间
#include<stdio.h>
#include<stdlib.h> //标准的库函数头文件int main(int argc, const char *argv[])
{//申请一个字节的空间char *ptr1 = (char *)malloc(1); //malloc(sizeof(char)) //在堆区申请1字节的空间,并将地址赋值给ptr1if(ptr1 == NULL){printf("空间申请失败\n");return -1;}//程序执行至此,表示空间已经申请成功printf("*ptr1 = %d, *ptr1 = %c\n", *ptr1, *ptr1); //没有初始化,默认是随机值*ptr1 = 'H'; //给堆区空间进行赋值printf("*ptr1 = %d, *ptr1 = %c\n", *ptr1, *ptr1); //没有初始化,默认是随机值//单个申请4字节int *ptr2 = (int *)malloc(sizeof(int)); //malloc(4)if(NULL == ptr2){printf("空间申请失败\n");return -1;}printf("*ptr2 = %d\n", *ptr2); //随机值*ptr2 = 520; //使用堆区空间printf("*ptr2 = %d\n", *ptr2); //520//手动将其释放free(ptr1);free(ptr2);ptr1 = NULL;ptr2 = NULL;return 0;
}
- 连续空间
#include<stdio.h>
#include<stdlib.h>int main(int argc, const char *argv[])
{//连续申请5个字节的空间char *ptr1 = (char *)malloc(5); //malloc(sizeof(char)*5)if(NULL == ptr1){printf("空间申请失败\n");return -1;}for(int i=0; i<5; i++){printf("%c\t", ptr1[i]);}printf("\n");//对堆区空间的使用ptr1[0] = 'A';ptr1[1] = 'B';ptr1[2] = 'C';ptr1[3] = '\0';printf("ptr1 = %s\n", ptr1);//申请连续的整形空间int *ptr2 = (int *)malloc(sizeof(int)*5);if(NULL==ptr2){printf("空间申请失败\n");return -1;}for(int i=0; i<5; i++){printf("请输入第%d个元素:", i+1);scanf("%d", ptr2+i); //&ptr2[i]}printf("您输入的元素分别是:");for(int i=0; i<5; i++){printf("%d\t", ptr2[i]);}return 0;
}
练习
在堆区申请8个int类型的空间,存储8名学生的成绩,并完成相关操作
1> 申请空间
2> 释放空间
3> 录入学生成绩
4> 输出学生成绩
5> 进行从大到小排序
每个功能要求使用函数实现
#include <stdlib.h>
#include <string.h>//在堆区申请一个n字节大小的空间
int *shenqing(int n)
{//申请堆区变量int *score = (int *)malloc(sizeof(int)*n);//判断是否申请成功if (NULL == score){printf("申请空间失败\n");return NULL;}//下面两行是两种将堆区空间置零的方式memset(score, 0, sizeof(int)*n);bzero(score, sizeof(int)*n);return score;
}
//在堆区空间录入数据
void luru(int *score)
{//检测堆区指针是否有所指向if (NULL == score){printf("录入失败\n");return ;}else{//循环录入for (int i = 0; i < 8; i++){printf("请输入第%d个学生的成绩\n", i+1);scanf("%d", score+i);}printf("录入成功\n");}
}
//数据显示
void show(int *score)
{//检测指针是否有所指向if (NULL == score){printf("输出失败\n");}else{//遍历输出for (int i = 0; i < 8; i++){printf("%d\t", score[i]);}putchar(10);}
}
//冒泡排序
void sort(int *score)
{for (int i = 1; i < 8; i++){for (int j = 0; j < 8-i; j++){if (score[j] < score[j+1]){int temp = score[j];score[j] = score[j+1];score[j+1] = temp;}}}
}
//释放堆区空间
void myfree(int **score)
{ //调用free释放堆区空间free(*score);//将指针指向NULL防止出现野指针*score = NULL;
}int main(int argc, const char *argv[])
{int *score = shenqing(8);if (NULL == score){return -1;}luru(score);show(score);sort(score);show(score);myfree(&score);show(score);return 0;
}
野指针
- 野指针就是指向非法内存空间地址的指针
- 种类:
- 定义指针变量时,没有初始化
- 当指针指向数组时,数组下标越界后的指针
- 指针原本有指向,但是指向的内存过期了(生命周期结束、手动释放)—> 悬空指针
- 指针函数返回的局部变量的地址
1. int *ptr; //未初始化2. int arr[5] = {1, 2, 3, 4, 5};int *qtr = arr;qtr += 5; //野指针3. int *ptr = NULL;if(1){int num = 520;ptr = #}//if结束后num将会被释放,ptr将会成为野指针4. int *ptr = (int *)malloc(4);free(ptr);//释放后ptr成为野指针5. int *fun(){int num = 520;return #}int main(void){int *ptr = fun();//此处ptr也为野指针}
- 如何预防野指针:当内存过期后,将指针置空即可
预处理指令
预处理指令的作用
在程序执行之前做做一些简单的前置工作,例如条件编译、宏定义、文件包含等等
预处理指令的分类
- 文件包含:#include
- 宏定义:有参宏和无参宏,使用一个名字代替字符串
- 条件编译:根据给定的条件,判断某些程序是否进行编译
文件包含指令
- 使用格式:
#include <文件名.h>
或者#include "文件名.h"
- 区别:
- 使用尖括号包裹的文件,直接在库文件中寻找该文件 /usr/include/
- 使用双引号包裹的文件,会先从当前文件位置寻找目标文件
- 作用:就是将包含的文件内容预处理阶段展开
- 使用文件包含完成分文件编译
- 头文件(.h文件):主要用于函数的声明、结构体的声明、宏定义、全局变量的定义
- 源文件(.c文件):实现函数的定义
- 主程序文件(包含main函数的.c文件):用于执行程序
- 一般而言,同一个文件的头文件和源文件名字一致
头文件格式
#ifndef 头文件名_H
#define 头文件名_H //以上两行是用于防止重复包含的,当这个宏名没有被使用是才会编译下面的声明,使用过后就不会再编译了函数声明...#endif
源文件格式
#include <stdio.h>
#include "头文件名.h"函数实现...
主函数格式
#include <stdio.h>
#include "头文件名.h"int main(void)
{函数调用...return 0;
}
最终编译时进行联合编译即可
gcc head.c main.c
宏定义
- 在C语言中,可以使用一个名字表示一个字符串或一类字符串,该名字是一个常量
例如:生活中的圆周率π,表示的就是3.1415926… 圆周率表示就是一个宏 - 宏的作用仅仅是指代作用,只做直接替换,不进行任何操作,也不做任何检查
- 使用格式:
- 无参宏:
#define 宏名 字符串
- 有参泓:
#define 宏名(形参列表) 包含形参列表的字符串
- 注意:有参宏定义中,形参没有数据类型,直接给定变量名即可
- 无参宏:
- 取消宏定义:
#undef 宏名
#include <stdio.h>
#define PI 3.14
#define SUM(x,y) x+yint main(void)
{double num = PI;printf("num = %.2lf\n", num);int a = 4;int b = 5;printf("a + b = %d\n", SUM(a, b)); //此处输出为:a + b = 9printf("%d\n",5*SUM(a, b)); //注意!!!宏定义只进行简单的替换操作,不进行任何计算//替换结果为:5*4+5,将会先计算乘法,结果为25,而不是45//平时使用时请务必加上括号,以免出现意外情况
}
- 有参宏和函数的区别
- 展开时机:有参宏而言,在预处理阶段展开,而函数在调用时才展开
- 内存使用:有参宏而言,占用的是所在函数的空间,而函数在调用时会单独开辟空间
- 效率上:有参宏的效率高于函数
- 有参宏是宏定义,只替换不计算不做任何正确性检查,而函数调用要符合函数的调用标准,形参会进行计算
条件编译
- 根据给定的条件,对某些程序代码进行编译
- 判断宏是否存在
#ifdef 宏名
代码块1;
#else
语句块2;
#endif
如果宏名被定义过了,就编译代码块1,反之编译代码块2
#ifndef 宏名
代码块1;
#else
代码块2;
#endif
如何宏名没有被定义,就编译代码块1,反之编译代码块2
3. 判断表达式是否成立
#if 表达式1
代码块1;
#elif 表达式2
代码块2;
#else
代码块3;
#endif
先判断表达式1,成立编译代码块1,否则判断表达式2,表达式2成立就编译代码块2,否则编译代码块3