指针作为参数的常见错误

本文章相关知识点:
1.指向指针的指针
2.指针用作函数参数
通过例题来说明:已知姓名用“名字#姓氏”的方式存储在字符串中,
例如“Jordan#Michael”.
编写一个find函数,获取姓名中名字的长度并获取姓氏。例如:Jordan#Michael中名字的长度是6,姓氏为 Michael。
首先给出一段错误代码:

#include <stdio.h>

int find(char *s, char ch, char *sub)
{
	for(int i = 0; *(s+i) != '\0'; i++)
		if(*(s+i) == ch)
		{
			sub = s+i+1;
			return i;
		}

	return 0;
}

void main()
{
	char fullName[] = "Jordan#Michael";
	char *givenName;

	int cnt = find(fullName, '#', givenName);
	printf("%d\t%s\n", cnt, givenName);
}

在find函数中,通过一个循环扫描字符串中的字符, 当发现#分隔符时,将分隔符后面首地址赋值给参数sub并返回分隔符前面字符的个数。这样看来程序似乎没什么问题,但是程序编译后,编译器却显示givenName是未定义的。那么问题出在哪里呢?
这里通过如下分析来解释原因:
指针作为参数的常见错误
在调用find函数前,指针givenName没有初始化,随机指向一个地址空间;调用find函数时,将givenName作为参数传递给find函数的形参sub,sub也指向了这个随机的地址;在find函数中,将分隔符后面首字母的地址赋值给sub,sub的值发生了变化,指向字符串中的字符M,此时givenName并没有变化,任然指向原来的空间;函数调用结束后,givenName仍旧没有被初始化和赋值。
因此find函数中修改指针find并不会影响主调函数中的指针givenName。如果想通过指针在指针在被调函数中修改主调函数的变量,必须将主调函数变量的地址作为参数,在被调函数中修改指针指向的内容

注意(指针传递的误区):通过指针传递参数时,最大的忌讳就是以为
只要参数是指针就万事大吉了。实际上,应该首先确定要修改的变量的类型,然后在将其地址作为参数。如果要修改的变量本身就是指针,就应该将该指针的地址作为参数,此时的形参类型是指向指针的指针。

要想在find函数中修改givenName的值,就得将givenName的地址&givenName作为参数传递给find函数。由于givenName本身就是指针,因此在find函数的参数中应该使用指向指针的指针char **sub。修改后find函数的声明和调用如下:
int find(char s, char ch, char **psub);
int cnt = find(fullName, ‘#’, &givenName);
将需要修改的变量的地址作为参数,也就是将givenName的地址&givenName作为参数;
在被调函数中修改指针指向的内容,被调函数中的指针是psub,指针指向的内容是 psub, 在被调函数中对psub进行赋值,就相当于修改了主调函数的变量givenName。
下图描述了修改后指针的变化:
指针作为参数的常见错误
在调用find函数之前,givenName指向一个随机的地址空间;调用find函数时,将givenName的地址作为参数传递给find函数的形参psub,使psub指向givenName;在find函数中,修改指针psub指向的内容
psub,就相当于修改了主调函数中的givenName,使得givenName指向字符M;函数调用结束后,givenName指向字符M。

修改后的程序:


#include <stdio.h>
int find(char *s, char ch, char **psub)
{
	for(int i = 0; *(s+i) != '\0'; i++)
	{
		if(*(s+i) == ch)
		{
			*psub = s+i+1;
			return i;
		}
	}
	return 0;
}
void main()
{
	char fullName[] = "Jordan#Michael";
	char *givenName = NULL;

	int cnt = find(fullName, '#', &givenName);
	printf("%d\t%s\n", cnt, givenName);
}