C语言之重修班

前言 

本文包括但不限于以下内容,只要一直学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函数的区别

  1. sizeof 运算符:

    • sizeof 是C语言中的一个运算符,用于获取数据类型或变量在内存中所占的字节数。它返回的是编译时确定的数据类型或变量的大小,通常是在编译阶段计算的,而不是在运行时。
    • 当用于数组时,sizeof 返回整个数组占用的内存空间,包括所有元素和可能的填充字节。例如,sizeof(char) 返回 1sizeof(int) 返回整数类型的字节数,而 sizeof(array) 返回整个数组的字节数。
  2. 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)赋值会覆盖之前的值,并且只有最后一个赋值有效。因此,输出结果会显示最后一个赋值的结果。

  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值