前言
本文包括但不限于以下内容,只要一直学C这部分就一直更新下去
遇到的问题
内存
<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)赋值会覆盖之前的值,并且只有最后一个赋值有效。因此,输出结果会显示最后一个赋值的结果。