用C语言写数据结构与算法的一些注意事项

1.空指针,野指针,悬挂指针

空指针:定义:int *p=NULL;

未分配内存空间,用NULL表示。

野指针:定义:int *p;

没有被初始化,或者指向未知的内存位置,或者指向无效的内存地址。野指针可能包含任意的内存地址,可能指向堆、栈或任何其他地方。对野指针进行解引用操作会导致未定义的行为。

悬挂指针:指在程序中仍然存在,但指向的内存地址已经无效或不再可用的指针。

  有几种情况会导致悬挂指针的出现:

  • 释放内存后未置空指针
  • 超出作用域的指针
  • 使用已被释放的内存

所以通常在释放内存后,要将悬挂指针置空

2. 引用参数:形参写成&L,即为引用参数,为C++中的用法。

C语言中,通过传递指针来模拟引用参数的行为。

3.函数做形参(函数指针)

函数指针的声明形式为 int (*func)(int),其中 int 表示返回值的类型, func 是函数指针的名称, (int) 是参数列表的声明。

4.bool类型

C语言中没有bool类型,可借助宏定义来完成。函数返回宏时,返回值的类型就是宏展开后的类型。

5.malloc

int *p = (int*)malloc(sizeof(int));

蓝色的(int*)是强制类型转换,因为只能给指针分配内存,所以必须转换为指针类型

6.形参是指针类型,如:SqList *L,  则对应的实参应定义为:SqList L, 并将&L传入。

若实参定义为:SqList *L,则L为野指针,直接传入会导致不可预料的后果,将L初始化为NULL也不行。而且定义成指针没有意义,因为数据存储在结构体成员数组或指针内。若定义成指针,不仅要给L->elem分配内存,还要给L本身这个指针分配内存。

给L本身这个指针分配内存:

 L = (SqList *)malloc(sizeof(SqList));

7. LinkList L

L 确实是一个指针,但在 C 语言中,函数参数仍然是按值传递的。当你传递L作为参数时,函数得到的是L的拷贝,而不是原始指针本身。所以必须传入一个指向L的指针,此时指向L的指针本身被拷贝,但修改此拷贝时,修改的是此拷贝指向的内容,也就是L。

关键:修改指针,修改的是指针指向的内容

8. LinkList *L

L是个二重指针,访问结构体成员时,应:(*L)->data

9.单链表插入节点

当使用 LNode p; 来表示当前要插入的节点时,每次循环迭代都会创建一个新的 p,但由于 p 是局部变量,它的生命周期仅限于循环块内。当每次迭代结束时,p 将被销毁(p 实际上是在下一次迭代开始时被销毁的,而不是在当前迭代结束后被销毁)这意味着它的内存空间会被释放。然后,你将 (*L)->next 设置为 &p,这意味着 (*L)->next 现在指向了一个已被销毁的内存地址,这个地址实际上不再有效。

这种情况下,链表的每个节点都指向了同一个内存地址,即被销毁的 p 的地址。因为 p 在每次迭代结束时被销毁,所以链表中的所有节点的 next 指针实际上都指向相同的地址。这导致链表的每个节点都指向了相同的数据,最终链表中只包含最后一次迭代的输入。

要解决这个问题,你应该为每个新节点使用动态内存分配,确保每个节点都有独立的内存,而不是共享同一个局部变量 p 的内存。这样,你可以正确构建一个包含多个节点的链表,并确保数据的完整性和正确性。

10.尾插法建表

错误示范:通过移动指针 p 来进行链表的构建

LNode *p = (*L)->next;

for (i = 1; i <= n; i++) {

    p = (LNode*)malloc(sizeof(LNode));

    if (!p) exit(ERROR);

    scanf("%d",&(p->data));

    p = p->next;

}

标蓝的两句:在循环中,执行 p = (LNode*)malloc(sizeof(LNode));为 p 分配了一个新的节点。然而并没有将这个新节点连接到链表中,而是直接将 p 更新为指向新分配的节点。这将导致原始链表的第一个节点 (*L)->next 丢失,因为 p 不再指向它。

更正代码:给p->next分配内存,不会丢失与链表的关系。然后将 p 移动到新节点,以确保它指向链表的最后一个节点。

LNode *p = (*L); // 指向链表的头节点

    for (i = 1; i <= n; i++) {

        p->next = (LNode*)malloc(sizeof(LNode));

        if (!(p->next)) exit(ERROR);

        p = p->next;

        scanf("%d", &(p->data));

        p->next = NULL; // 设置新节点为链表的尾节点

    }

标蓝的三句:按逻辑应该是以下这个顺序,但上面的更简洁

        scanf("%d", &(p->next->data ));

        p->next->next = NULL;

        p = p->next;

10.  ListInsert(LinkList L, int i, ElemType e)

int ListInsert(LinkList L, int i, ElemType e) {

    // 修改 L 不会影响外部链表头指针,一般用于有头结点的链表}

LinkList *L: 传递的是链表头指针的地址。在函数内部对 *L 的修改将直接影响外部链表的头指针。通过修改 *L 可以改变外部链表的结构。

int ListInsert(LinkList *L, int i, ElemType e) {

    // 修改 *L 会影响外部链表头指针,更多用于无头结点的链表,栈就是个典型例子}

故用LinkList L和LinkList *L做形参均可,使用 LinkList L 作为形参在特定情况(Init和Create不行,Insert和Delete可以。因为前两个牵扯到分配内存)下是可以正常工作的,这是因为在 C 语言中,参数传递是通过值传递进行的,但对于指针类型的参数,传递的是指针的副本,仍然可以通过这个副本修改原始链表。       

不能修改L对空间的指向关系,即不能在函数内对L重新分配内存,但可以对L指向的内存进行修改。

11.内存特点

1、栈内存特点

数据一执行完毕,变量会立即释放,节约内存空间。

优势存取速度比堆要快,仅次于直接位于CPU中的寄存器。

缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

一般直接创建的变量

具体代表:

函数调用栈: 栈内存主要用于存储函数调用的局部变量、函数参数、返回地址等。每次函数调用时,系统都会在栈上分配一块内存用于存储当前函数的数据。

局部变量: 函数中声明的局部变量通常存储在栈上。这些变量的生命周期与函数的调用周期相对应。

函数参数: 被传递给函数的参数也存储在栈上。它们在函数调用时被推入栈,函数结束时从栈中弹出。

2、堆内存特点

堆内存中所有的实体都有内存地址值,内存释放靠垃圾回收机制不定时的收取。

堆的优势:可以动态地分配内存大小。

缺点:由于要在运行时动态分配内存,存取速度较慢。

具体代表:动态分配的数组: 使用 malloc、calloc 或 new 在堆上分配的动态数组是堆内存的具体代表。例如,int* arr = (int*)malloc(10 * sizeof(int)); 分配了包含 10 个整数的数组。

动态分配的对象: 对象的实例,特别是在面向对象编程中,通常通过 new 运算符在堆上分配内存。例如,SomeClass* obj = new SomeClass();。

链表和树节点: 动态数据结构,如链表、树等,通常在堆上分配内存。新的节点可以在运行时动态添加,而不需要预先知道其数量。

全局变量: 全局变量和静态变量的存储位置通常在程序启动时就分配在堆上。它们的生命周期贯穿整个程序的执行过程。

字符串: 动态分配的字符串,如使用 malloc 或 new 分配的字符数组,也是堆内存的典型例子。

3、栈内存的数据共享:

栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

int a = 3;

int b = 3;

编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。

这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sabrisimba

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值