5.6 指针与函数之间的关系

在第1章中简单介绍了指针与函数的关系,但是并没有进行深入的讲解。关于函数指针和指针函数的内容,将会在第7章进行深入讲解,在此重点介绍指针作为函数参数的使用情况。

指针作为函数参数,最典型的莫过于main()函数,所以先通过main()函数来看看指针作为参数的使用情况。


include<stdio.h>

void main(int argc,char*argv[])

{

while(argc>0)

{

printf("%s\t",*argv++);

argc—;

}

return;

}


运行结果:


C:\>fdsa.exe hello world

fdsa.exe hello world


先介绍main()函数中参数的含义,其中,argc表示命令行输入参数的个数,在此命令行参数有fdsa.exe、hello、world三个,所以argc的值为3;而argv表示一个指针数组,用来存放命令行输出的字符串,相当于“char*argv[]={"fdsa.exe","hello","world"};”。细心的读者可能发现了一个问题,前面说过,数组名不能够进行自加和自减等运算,但是上面的代码中却利用参数中的数组名进行自加运算,而且成功输出了。难道前面说错了吗?看下面这段代码。


include<stdio.h>

void main()

{

char*argv[]={"fdsa.exe","hello","world"};

int i=0;

while(i<3)

{

printf("%s\t",*argv++);

i++;

}

return;

}


以上程序编译的时候出错了,看来,数组名的确不可以进行自加和自减运算。其实,在前面的程序中,数组作为参数时成功地实现了输出,是因为数组作为参数时已经不再是数组了,而是一个指针变量,所以能够对其进行自加运算。我们可以通过下面几段代码来逐一分析。


include<stdio.h>

void print(char str[])

{

printf("print函数中的sizeof(str)=%d\n",sizeof(str));

printf("%s\n",str);

return;

}

void main()

{

char str[]="Hello Bigloomy!";

printf("main函数中的sizeof(str)=%d\n",sizeof(str));

print(str);

return;

}


运行结果:


main函数中的sizeof(str)=16

print函数中的sizeof(str)=4

Hello Bigloomy!


从前面的代码中可以发现,通过sizeof操作符从main()函数和print()函数中得到的str大小并不一样,在main()函数中是16字节,与前面讲解字符串数组时分析的一样,会在字符串的后面自动添加一个串结束符‘\0’,所以大小是16。而在print()函数中的str大小为4字节,由此可以看出,在参数str传递的过程中传递的并不是整个数组的大小,而是数组的首地址,这时print()函数中的参数不是一个数组,而是一个指针变量。如果读者觉得上面的解释不好理解,那么再来看下面这段代码。


include<stdio.h>

void print(char str[])

{

printf("print函数中的str=%d\n",str);

printf("print函数中的&str=%d\n",&str);

printf("%s\n",str);

return;

}

void main()

{

char str[]="Hello Bigloomy";

printf("main函数中的str=%d\n",str);

printf("main函数中的&str=%d\n\n",&str);

print(str);

return;

}


运行结果:


main函数中的str=1245040

main函数中的&str=1245040

print函数中的str=1245040

print函数中的&str=1244960

Hello Bigloomy!


在main()函数中打印的str和&str结果相同,都是数组的首地址;而在print()函数中打印的str和&str并不相同,str与在main()函数中打印的结果相同,表示数组的首地址,而&str却不一样,这也进一步说明这里的str是一个指针变量,而不再是一个数组。如图5-11所示,print()函数中的str是一个指针变量,所以它拥有自己的地址。这就是在两个函数中得到的结果不一致的原因,参数传递过去的仅仅是数组的首地址,所以在print()函数中的str指针变量中保存的是数组的首地址。为了便于理解,通常将参数传递中的“char str[]”改写为“char*str”。

5.6 指针与函数之间的关系 - 图1

图 5-11 main()函数和print()函数中str的内存结构

下面再通过一段代码来看指针作为参数的使用情况。


include<stdio.h>

void copy_string(char from[],char to[])

{

while(to++=from++);

return;

}

void main()

{

char str[]="this is a string!";

printf("%s\n",str);

char dec_str[20];

copy_string(str,dec_str);

printf("%s\n",dec_str);

return;

}


运行结果:


this is a string!

this is a string!


通过函数copy_string()成功地将str中的字符串复制到dec_str数组中,虽然copy_string()函数中的参数是数组形式的,但是它的实质是指针变量。再看copy_string()函数的实现方式,这里通过while循环来实现,由于str字符数组的后面有结束符‘\0’,因此我们巧妙地将这一条件作为复制是否结束的标志。需要注意的是,在mian()函数中传递的两个参数都是已经在内存中分配了的。如果没有在main()函数中分配目标变量的内存大小,会出现什么问题呢?看看下面的代码。


include<stdio.h>

include<stdlib.h>

void copy_string(charfrom,charto)

{

to=(char)malloc(sizeof(char)40);

char*to_start=to;

for(;to=from;from++,to++);

printf("%s\n",to_start);

return;

}

void main()

{

char*str="Hello World!";

printf("%s\n",str);

char*dec_str;

copy_string(str,dec_str);

//printf("%s\n",dec_str);

return;

}


运行结果:


Hello World!

Hello World!


在上面的代码中,在main()函数中没有为dec_str指针分配内存单元,但是在copy_string()函数中为参数to分配了内存单元,成功地在函数copy_string()中实现了字符串的复制。那么这个复制好的字符串能否像上面的字符串那样返回呢?取消注释最后一行printf语句来验证是否成功返回复制后的字符串,我们会发现,运行到这一行时直接崩溃掉了。这是什么原因所导致的呢?我们通过下面一段代码来加以分析。


include<stdio.h>

include<stdlib.h>

void copy_string(char from[],char to[])

{

printf("分配前to=%x\n",to);

to=(char)malloc(sizeof(char)20);

printf("分配后to=%x\n",to);

char*start_to=to;

while(to++=from++);

printf("copy_string函数:%s\n\n",start_to);

return;

}

void main()

{

char str[]="Hello World!";

printf("main函数:%s\n",str);

char*dec_str;

printf("调用函数copy_string前dec_str=%x\n\n",dec_str);

copy_string(str,dec_str);

printf("调用函数copy_string后dec_str=%x\n",dec_str);

return;

}


运行结果:


main函数:Hello World!

调用函数copy_string前dec_str=cccccccc

分配前to=cccccccc

分配后to=380fe0

copy_string函数:Hello World!

调用函数copy_string后dec_str=cccccccc


5.6 指针与函数之间的关系 - 图2

图 5-12 char型指针des_str和to的内存结构

通过图5-12来演示上面的运行结果,在main函数中没有为目标字符指针变量申请内存空间,在调用函数之前发现编译器对指针进行初始化,该指针指向一个不可用的内存;在copy_string()函数中,在对字符指针变量to分配内存前后它的指向发生了变化,从之前的cccccccc,变为指向一片可用的内存单元,所以接下来成功地实现了字符串的复制,但是在调用copy_string()函数之后,指针变量des_str的值并没有发生变化,还是指向地址为cccccccc的存储单元,因此在执行printf语句的时候导致程序崩溃。

由上面的分析可知,实参的地址并没有改变,导致dec_str指针还是指向一个不可用的内存地址,从而在使用printf语句打印时出错。如果通过函数返回分配后的起始地址,那么在main()函数中同样可以打印出通过函数处理后的字符串。