前言
查缺补漏
遇到的问题
内存
<1>
/* 功能:判断value值和线性表中的数据是否相同, 若相同则返回下标,并且返回相同的个数,用数组存起来 */ int_arrary list_locate(sqlink L,int value) { int_arrary value_locate; int i=0; int j=0; //value_locate->same=0; // int cnt=0;//用于存放相同元素的总个数 value_locate=(int_arrary)malloc(sizeof(Int_ARRay));//为Int_ARRay申请内存 value_locate->same=0; for(i=0;i<=L->last;++i) { if(L->data[i]==value) { value_locate->locate[j++]=i; (value_locate->same)++; } } return value_locate; }原本在未申请内存的时候我就value_locate->same=0;
- 这可能导致未定义行为,因为 value_locate 指向的内存还没有被分配
然后改成了这样:(更改顺序)
value_locate=(int_arrary)malloc(sizeof(Int_ARRay));//为Int_ARRay申请内存 value_locate->same=0;<2>
/* 功能:把L线性表中相同的元素就只保留一个 返回值:-1操作失败 0操作成功 */ int list_delete_same(sqlink L) { int_arrary value_locate; int i=0,j=0; //如果为空表或者原本表就没申请成功 返回-1 if(L==NULL||L->last==-1) return -1; for(i=0;i<=N;i++) { //返回在L线性表中L->data[i]的下标 value_locate=list_locate(L,L->data[i]); if(value_locate->same>1)//如果相同元素大于1,则存在相同项 { //从相同元素的第二个下标开始,到相同元素总个数same结束 for(j=value_locate->locate[1];j<=value_locate->same;j++) //从第二个下标循环依次执行 删除单个元素操作 list_delete(L,j); } else; //printf("sam value:%d\r\n",value_locate->same); //释放创建Int_ARRary时的内存 因为是个循环里面,如果不释放则容易造成内存泄漏的问题 Int_ARRary_free(value_locate); } return 0; // printf("sam value:%d\r\n",value_locate->same); }问题出在 Int_ARRary_free(value_locate);这里,刚开始未释放相关的内存(即未写Int_ARRary_free(value_locate);函数释放内存),这就会导致:
- value_locate 在循环外部定义,并在循环内部使用,这会导致内存泄漏,因为每次循环都会分配新的内存给 value_locate,但没有释放之前分配的内存。
经过改良如上面代码所示(即添加了Int_ARRary_free(value_locate);)
<3>
这个问题是我在编写单链表全删函数时遇到的,函数功能是删除原本链表所有的有数据的节点,并且返回头结点。
刚开始我写的代码是这样的:(铁铁可以先自己看一下问题所在)
/* 功能:把链表全删除,就剩头指针 返回:-1 失败,0 成功 */ linklist linklist_clear(linklist h) { int i; linklist p; linklist q; if(h==NULL) { printf("linklist is NULL\r\n"); return NULL; } p=h; p=p->next; while(p->next!=NULL) { //保存下一个节点指针 q=p->next; //删除节点 并且释放内存 if(p!=NULL) { free(p); p=NULL; } else break; //p切换到下一个结点 p=q; free(q); q=NULL; } return p; }代码整体逻辑是:
- 第一步:先让p指向h的第一个数据的结点(也就是第二个结点),因为要从这个结点开始删除结点,并且释放内存
- 第二步是进入循环了,当p的下一个节点不为NULL时执行循环-删除节点。循环里面的逻辑是这样的:
- 1.先用q保存h的第三个结点,
- 2.毕竟然后要销毁第二个结点嘛-free(p),p=NULL这两句,销毁了第二个结点就操作不了p.next了(段错误)也就是指不到第三个结点了。
- 3.然后就是更新p了,把刚才存入的结点q赋值给p,这样第三个结点就可以操作了。然后释放q点内存
- 4.等到了下一个循环的时候执行了q=p->next;q又指向了第四个结点。然后就构成了循环了,一直删除到尾结点。p==NULL就停止循环了
- 第三步:返回头结点p,因为此时p被删除的就剩下头结点了
好了,我在main函数里面编写了测试代码,大概功能就是先插入数据,然后调用linklist_clear删除结点。开始执行发现了输出乱码的问题:
代码问题
1:不用
free(q); q=NULL;这两句因为其是在子函数里面的,也就是在栈里面,是静态变量,函数结束就自动释放了。
2: 第二个就是 while(p->next!=NULL)这个逻辑问题了,应该是 while(p!=NULL)因为逻辑上是循环流动的p不为NULL时执行删除节点,而不是p的下一个节点。
3:根本原因是第三点就是我发现很有可能是我没有指定h头结点的next定义为NULL所导致,因为如果说我没有定义为NULL,删除到最后就会发现最后的p的next就会变成野指针。
下面图片是下面改正后代码截屏。
改正后代码:
/* 功能:把链表全删除,就剩头指针 返回:-1 失败,0 成功 */ linklist linklist_clear(linklist h) { linklist q; linklist a; //保存头结点 linklist p; if(h==NULL) { printf("linklist is NULL\r\n"); return NULL; } //从头开始 p=h; //保存头结点 a=h; // a->next=NULL; //h指向下一个节点,开始释放内存 p=h->next; //头结点的next指向NULL 代码解决的地方 h->next=NULL; while(p!=NULL) { //保存下一个节点指针 q=p->next; //删除节点 并且释放内存 if(p!=NULL) { free(p); p=NULL; } else break; //p切换到下一个结点 p=q; } //a->next=NULL; return a; }main函数:
#include"linklist.h" #include "stdio.h" void test_clear(); int main() { test_clear(); return 0; } void test_clear() { linklist h; linklist p; int value;//输入的数据 h=linklist_creat(); if(h==NULL) printf("h is NULL\r\n"); printf("input:"); while(value!=-1) { scanf("%d",&value); if(value==-1) break; linklist_tail_insert(h,value); printf("input:"); } //linklist_show(h); //puts(""); //把结点删除就剩下头结点 linklist_clear(h); linklist_show(h); //重新插入 puts("The following is reinsertion"); //尝试重新尾部插入数据 printf("input:"); while(value!=-2) { scanf("%d",&value); if(value==-2) break; linklist_tail_insert(h,value); printf("input:"); } //重新插入 linklist_show(h); }结果:
- 可以看到输入123后经过linklist_clear后删除的就剩下头结点了(什么也没显示),
- 然后在The following is reinsertion(翻译为:以下为重新插入)之后又输入了2 3 -2(这时候-2表示结束输入数据,上面-1表示结束输入数据),
- 最后又显示出来了2 3 。表示函数功能正常。
然后我又去看了一下地址,也是对的
可以看到是地址就剩下了头结点,代码BUG解决。
以下是查缺补漏的C语言知识
函数指针
这是我在学习FReeRTOS时遇到的一个:函数指针:Type void (*TaskFunction_t)(void*) 表示定义一个void类型的TaskFunction_t函数指针--本质是指针,用来传入其他函数的地址,其中传入的参数是void *类型的指针参数
因此定义完TaskFunction_t之后,可以通过声明一个TaskFunction_t类型的变量来指向一个函数,并调用这个函数
列如:
//首先,我们定义这个函数指针----本质是指针: typedef void (*TaskFunction_t)(void*); //然后造一个 void类型的函数 void testFunc(void *arg) { printf("hello world\n"); } //声明一个TaskFunction_t类型的指针,指向testFunc TaskFunction_t func = testFunc;//函数地址传递 //最后调用func函数,因为其指针已经指向testFunc的函数地址了 func(NULL); // 输出“hello world”的函数在FReeRTOS中就是
//首先定义一个函数指针---本质是指针 typedef void (* TaskFunction_t)( void * ); //然后将其封装在任务创建的函数中 BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,//定义一个TaskFunction_t类型的函数指针 const char * const pcName, const configSTACK_DEPTH_TYPE usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask ); //然后码农辛勤的自己创建一个任务 void Task1Function(void * param) { while (1) { printf("1"); } } //最后码农再调用xTaskCreate()--使得pxTaskCode指向Task1Function的函数地址,第一个参数相当于pxTaskCode=Task1Function xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1); //这样即可完成对任务的执行 打印1由此我们可以看出函数指针的目的是在函数中,以参数的形式调用函数
指针函数
说完函数指针就得说指针函数了。那指针函数的本质就是一个函数
指针函数是返回一个指针的函数。简单来说,它是一个以指针形式返回数据的函数,即函数的返回类型是某种指针类型。
列如:
// 定义一个返回指针的函数 int* getArray() { static int arr[] = {1, 2, 3, 4, 5}; return arr; } // 使用指针函数 int* p = getArray(); for (int i = 0; i < 5; ++i) { printf("%d ", p[i]); }它的作用就是:
- 返回动态分配的数组或结构体
- 提供对局部静态变量的访问
- 实现链表等数据结构的操作
关于数据结构可以看这篇博客--数据结构中的线性表
常用函数作用
sprintf();
static char pcBuffer[100]; sprintf(pcBuffer, "Msg %d\r\n", cnt++);当使用这行代码时,假设初始时 cnt 的值为 1,那么 pcBuffer 中的内容将会被格式化为 "Msg 1\r\n"。每次调用这行代码时,cnt 的值会自增,比如下一次调用时 cnt 的值为 2,则 pcBuffer 中的内容将会是 "Msg 2\r\n",依此类推。这样就可以动态地将 cnt 的值插入到消息中,方便地生成不同的消息内容。
enum:枚举
/* 定义2种数据来源(ID) */ typedef enum { eMotorSpeed, eSpeedSetPoint } ID_t; /* 定义在队列中传输的数据的格式 */ typedef struct { ID_t eDataID; int32_t lDataValue; }Data_t; /* 定义2个结构体 */ static const Data_t xStructsToSend[ 2 ] = { { eMotorSpeed, 10 }, /* CAN任务发送的数据 */ { eSpeedSetPoint, 5 } /* HMI任务发送的数据 */ };在这段代码中,enum 是一个关键字,用于定义一个枚举类型。枚举类型是一种用户自定义的数据类型,它允许我们定义一组命名的常量值。
在这个例子中,enum 定义了一个名为ID_t的枚举类型。该枚举类型有两个成员:eMotorSpeed 和 eSpeedSetPoint。每个成员都被赋予一个整数值,默认情况下,第一个成员的值为 0,后续成员的值依次递增。在这个例子中,eMotorSpeed的值为 0,eSpeedSetPoint 的值为 1。
通过定义枚举类型,可以更加直观地表示不同的标识符,并在代码中使用这些标识符来表示相关的值。例如,在使用该枚举类型时,可以将 eMotorSpeed 作为标识符来表示电机速度,而 eSpeedSetPoint 表示速度设定点。
sizeof和strlen函数的区别
sizeof运算符:
sizeof是C语言中的一个运算符,用于获取数据类型或变量在内存中所占的字节数。它返回的是编译时确定的数据类型或变量的大小,通常是在编译阶段计算的,而不是在运行时。- 当用于数组时,
sizeof返回整个数组占用的内存空间,包括所有元素和可能的填充字节。例如,sizeof(char)返回1,sizeof(int)返回整数类型的字节数,而sizeof(array)返回整个数组的字节数。
strlen函数:
strlen是C语言中的一个字符串处理函数,用于计算以空字符'\0'结尾的字符串的实际长度,即不包括空字符在内的字符数量。strlen函数在运行时遍历字符串,直到遇到空字符为止,并返回字符串中的字符数量。
sizeof是一个运算符,用于获取数据类型或变量的大小,它是在编译时计算的,返回的是字节数。strlen是一个函数,用于计算字符串的长度,它是在运行时计算的,返回的是字符串中的字符数量(不包括空字符)。
结构体
结构体函数
在结构体里面初始化函数
类似这样:
tp_dev.init();下面代码一层层剖开:
可以看出上面tp_dev.init();中的init是下面uint8_t (*init)(void); 中的函数指针init
typedef struct { uint8_t (*init)(void); /* 初始化触摸屏控制器 */ uint8_t (*scan)(uint8_t); /* 扫描触摸屏.0,屏幕扫描;1,物理坐标; */ void (*adjust)(void); /* 触摸屏校准 */ uint16_t x[CT_MAX_TOUCH]; /* 当前坐标 */ uint16_t y[CT_MAX_TOUCH]; uint16_t sta; /* 笔的状态 float xfac; /* 5点校准法x方向比例因子 */ float yfac; /* 5点校准法y方向比例因子 */ short xc; /* 中心X坐标物理值(AD值) */ short yc; /* 中心Y坐标物理值(AD值) */ uint8_t touchtype; } _m_tp_dev;_m_tp_dev tp_dev = { tp_init, tp_scan, tp_adjust, 0, 0, 0, 0, 0, 0, 0, 0, };uint8_t tp_init(void) {...... ...... ..... } static uint8_t tp_scan(uint8_t mode) {..... ...... } void tp_adjust(void) { ......... ........... }节点指针
在单链表的节点中使用结构体
typedef int data_t; typedef struct node { data_t data; struct node *next; }linknode,*linlist;这里的struct node *next中next是struct node *类型的指针。就好比int *p,p是int *类型指针一样。只不过是这个指针是指向这个结构体的。
在后面单链表的实现中还会存在这么几句代码:
while(q->next!=NULL) { //q=下一个节点 q=q->next; }就是当最后一个节点不是NULL时,q不断指向下一个节点
内存
memset();
//添加string.h //参数:起始地址,要填充的值,所占字节大小 //功能:把从_Dst地址开始的占_Size字节的内存用_Val填充 //返回:内存起始地址 void * __cdecl memset(void *_Dst,int _Val,size_t _Size);NULL问题
free(p); p=NULL;那么为什么要加p=NULL呢?
- 在C语言中,手动释放内存是一种良好的编程实践,特别是当不再需要一个动态分配的内存块时。但是为了避免悬挂指针(野指针)的问题,即指针仍然指向已经释放的内存地址,一般会将指针设置为NULL,这样可以防止在之后的代码中错误地引用已经释放的内存。当然,这并不是必须的,但是是一种很好的习惯,可以帮助避免一些潜在的问题,特别是在长时间或大型项目中。
Union(联合)
联合(Union)是一种数据结构,它允许在相同的内存位置存储不同的数据类型,与结构(Struct)类似,联合(Union)也可以包含多个成员,但是联合(Union)中的所有成员共享相同的内存空间。这意味着在任何给定时间,只能存储联合(Union)中的一个成员。
代码:
#include <stdio.h> union Data { int i; float f; char str[20]; }; int main() { union Data data; data.i = 10; printf("data.i : %d\n", data.i); data.f = 220.5; printf("data.f : %f\n", data.f); strcpy(data.str, "C Programming"); printf("data.str : %s\n", data.str); return 0; }在这个示例中,联合(Union) Data包含了一个整数 i、一个浮点数 f 和一个字符数组 str。每次给联合(Union)赋值会覆盖之前的值,并且只有最后一个赋值有效。因此,输出结果会显示最后一个赋值的结果。