链表中的应用

通常的做法:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
class Solution { public: ListNode* removeElements(ListNode* head, int val) { ListNode *prev = NULL, *p = head; while(p) { if(p->val == val) { if(prev) prev->next = p->next; else head = p->next; } else { prev = p; } p = p->next; } return head; } };

使用二级指针的做法:

1 2 3 4 5 6 7 8 9 10 11 12
class Solution { public: ListNode* removeElements(ListNode* head, int val) { for(ListNode **curr = &head; *curr; ) { if((*curr)->val == val) *curr = (*curr)->next; // *curr里相当于是prev->next else curr = &((*curr)->next); // 将 *curr 设置为当前节点的next,使得在处理下一个节点时,*curr相当于prev->next } return head; } };

一个指向链表节点的指针,与改节点的前一个节点的next值相同。

我看过好几遍,还是觉得不太好懂。不确定是否真的get到了要点。

在函数参数中的应用

strtod

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// double strtod(const char *str, char **endptr); // 从str开始,忽略尽可能多的空白字符后,读取一个double类型,直到第一个无效字符 // 出现,如果endptr不为NULL,则将*endptr指向该字符 char s[] = "3.14 -2.17828 6.022e23"; // 方法一: // 如果s不能被解析为double,则将&str作为endptr会出现死循环 char *p; double d; for(p = s, d = strtod(p, &p); ; d = strtod(p, &p)) { cout<<d<<endl; if(*p == 0) break; } // 方法二: // 总是能返回需要的结果 char **pEnd; for(p = s, d = strtod(p, &pEnd); p != pEnd; d = strtod(p, &pEnd)) { printf("'%.*s' %lf\n", (pEnd - p), p, d); // 被解析的字符串和解析结果 p = pEnd; // 从上次结束的位置开始解析 }

为何第二个参数是一个二级指针呢?从上面的使用方法中可以看出,该参数的作用是,将 解析出一个double后,剩下字符串的第一个位置的信息传给外部程序。

位置可以用原始字符串s和一个整数pos来表示,即s+pos,因此也可以用一个指针来表示 ,由于不能返回多个参数,更多的返回值一般通过指针来传递。

例如要返回一个整数,那么可以添加一个 int * 类型的参数,然后在函数内部更改这个 指针指向的内容,就可以实现将内部运行结果直接传给函数外部变量的目的。如果不用指 针,而是直接传 int 类型,由于C语言是传值调用,因此虽然把函数外部的一个值(实 参)传递进去,在函数内部还是会生成一个相同类型的值(形参),当函数运行结束,该 参数生命周期便结束了,即使形参携带了结果,也不能传递给实参。

strtod 函数中,除了要返回 double 的解析结果,还要返回一个剩余字符串起始位 置的 char * ,因此要添加一个 &(char *) 参数,来传递这个 char * ,仅此而 已。

strtol

1 2
// long strtol(const char *str, char **str_end, int base); // 与strtod使用方法类似,只是多了一个base参数,来传递进制数。

strtok

strtok 是一个很特殊的函数,初次使用回感觉比较违反直觉。看下 glibc 中的实现: strtok.c

代码很短,因为主要工作交给了 strspn, strpbrk 函数。特殊之处在于,使用了一个 static char *olds 来记录每次解析之后,余下字符串的起始位置,而不是使用二级指针参数来 将该位置传出去。

  • 会改变str内容:每段的第一个delim字符变为'\000'

  • 由于使用了static变量,注意线程安全性。

  • 如果字符串是存在临时堆栈区,函数退出后,栈区内存释放,但strtok里的static变量还可能存着字符串某个位置的地址,成为野指针。

  • strspn,strcspn,实现方法类似,用delim初始化一个长度为256的数组,然后检查str中的每个字符对应的数组中的值是否为1

  • strpbrk 是 strcspn 的包装函数

1 2 3 4 5 6
// char *strtok(char *str, const char *delimiters) char s1[] = "test -- big'' eas' king''"; char *p; for(p = strtok(s1, " -'"); p; p = strtok(NULL, " -'")) { printf("%s\n", p); }

按说 strtok 也需要传递指针,但strtok内部有一个静态变量,存着这个指针,当str为 NULL时,就使用这个静态变量作为str,因此不用传位置指针。当str不为NULL时,该静态 变量会更新。

其它应用

动态数组

  • 动态一维数组(通过指针访问数组)与普通的数组的工作原理类似,多出一个指针。而动态二维数组则多出 M + 1 个指针(一个二级指针和M个一级指针),其中M是数组的行数。

  • 对于数组,编译器在编译阶段就能计算出任意元素的位置,一般不做边界检查(数组维数信息只在编译时用到,而不是存到某些隐藏变量中),例如对于数组 A[3][4], A[7] 与 A[1][3] 是相同的。而动态二维数组则不能如此替换。