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-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-12 char型指针des_str和to的内存结构
通过图5-12来演示上面的运行结果,在main函数中没有为目标字符指针变量申请内存空间,在调用函数之前发现编译器对指针进行初始化,该指针指向一个不可用的内存;在copy_string()函数中,在对字符指针变量to分配内存前后它的指向发生了变化,从之前的cccccccc,变为指向一片可用的内存单元,所以接下来成功地实现了字符串的复制,但是在调用copy_string()函数之后,指针变量des_str的值并没有发生变化,还是指向地址为cccccccc的存储单元,因此在执行printf语句的时候导致程序崩溃。
由上面的分析可知,实参的地址并没有改变,导致dec_str指针还是指向一个不可用的内存地址,从而在使用printf语句打印时出错。如果通过函数返回分配后的起始地址,那么在main()函数中同样可以打印出通过函数处理后的字符串。