李柱明博客:https://i.cnblogs.com/posts/edit-done;postId=15487326
指针和引用都是一个意思,都是存储所指对象的内存地址。
理解指针非常重要,对于后期使用指针、函数传参等等都发挥着理论指导作用。
指针:
指针丢失&内存泄漏例子:
如单向链表,插入节点时,注意指针的更新:
// 错误示范 p->next = x; // 将 p 的 next 指针指向 x 结点; x->next = p->next; // 将 x 的结点的 next 指针指向 b 结点;
上述代码中一个错误点就是:p->netx 已经由原来的 b 更新为 x 了,到第二行代码时原意为 x 的下一个想指向 b 的,但是指针丢失了,导致 x->next 指向了 x 本身,链表后面的节点全部丢失。后面丢失的节点就是内存泄漏了。
注意:
注意野指针:
哨兵为了处理边界问题。如首节点。
哨兵不参与逻辑业务。
编程建议:链表尽量弄个链表句柄,方便管理。首节点的指针可以当做链表的句柄使用。
在链表中,第一个节点都需要特殊处理,如删除单向链表的第一个节点,就得把指向该链表的指针更新为第二个节点。
这样每次操作链表都需要检查是否是第一个节点,如果是还要特殊处理,显得繁琐。
所以我们固定一个首节点,实际数据的节点从第二个节点开始。这样就不用管链表是否为空。
首节点还可以记录链表的一些数据。
首节点的指针可以当做链表的句柄使用。
含首节点的链表交带头链表,相反叫不带头链表。
参考以下两段代码:
代码一:
// 在数组 a 中,查找 key,返回 key 所在的位置 // 其中,n 表示数组 a 的长度 int find(char* a, int n, char key) { // 边界条件处理,如果 a 为空,或者 n<=0,说明数组中没有数据,就不用 while 循环比较了 if(a == null || n <= 0) { return -1; } int i = 0; // 这里有两个比较操作:i<n 和 a[i]==key. while (i < n) { if (a[i] == key) { return i; } ++i; } return -1; }
代码二:
// 在数组 a 中,查找 key,返回 key 所在的位置 // 其中,n 表示数组 a 的长度 // 我举 2 个例子,你可以拿例子走一下代码 // a = {4, 2, 3, 5, 9, 6} n=6 key = 7 // a = {4, 2, 3, 5, 9, 6} n=6 key = 6 int find(char* a, int n, char key) { if(a == null || n <= 0) { return -1; } // 这里因为要将 a[n-1] 的值替换成 key,所以要特殊处理这个值 if (a[n-1] == key) { return n-1; } // 把 a[n-1] 的值临时保存在变量 tmp 中,以便之后恢复。tmp=6。 // 之所以这样做的目的是:希望 find() 代码不要改变 a 数组中的内容 char tmp = a[n-1]; // 把 key 的值放到 a[n-1] 中,此时 a = {4, 2, 3, 5, 9, 7} a[n-1] = key; int i = 0; // while 循环比起代码一,少了 i<n 这个比较操作 while (a[i] != key) { ++i; } // 恢复 a[n-1] 原来的值, 此时 a= {4, 2, 3, 5, 9, 6} a[n-1] = tmp; if (i == n-1) { // 如果 i == n-1 说明,在 0...n-2 之间都没有 key,所以返回 -1 return -1; } else { // 否则,返回 i,就是等于 key 值的元素的下标 return i; } }
在字符串 a 很长的时候,执行效率更高的是代码二。
因为代码二的字符串检索是比代码一少了一个判断。
代码一:O(2n)
代码二:O(n)
注意:上面代码只是给个例子,实际开发中,若不追求极致效率,为了代码可读性,不建议上面这个代码的实现。
在处理链表时,先用大脑走一遍,主要是边界问题,如: