C语言函数间参数的传递方式(二)没有返回,仅仅靠形参、实参传递参数的函数
这一篇我们来看看没有返回,只靠形参、实参传递参数的函数,先来学习最简单的一种:
1、传值调用(赋值传递)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 void swap(int a,int b) 6 { 7 int temp; 8 printf("交换前,a=%d b=%d \n",a,b); 9 temp=a; 10 a=b; 11 b=temp; 12 printf("交换后,a=%d b=%d \n",a,b); 13 } 14 int main() 15 { 16 int x=10,y=20; 17 printf("交换前,x=%d y=%d \n",x,y); 18 swap(x,y); 19 printf("交换后,x=%d y=%d \n",x,y); 20 return 0; 21 }
从运行程序结果可以看出:被调函数swap只对形参操作,实参无变化。显然,传值调用属于单向值传递,函数运行结果不影响、不改变调用函数的实参。
看过了最简单的,下面来看看复杂一点儿的————引用调用(指针传递、赋地址传递)。C语言中,这个词也被叫做“指针传递”、“赋地址传递”。在C++语言中,则有另外的含义了。用起来,也会方便许多,但是,C语言不支持。引用调用(指针传递、赋地址传递)分为同级指针传递,和二级指针传递2种方式。同时,根据主调函数是否申请内存,又分两种情况来分析。下面先来看:
2、主调函数申请内存条件下,同级指针传递:
2.1 主调函数申请内存,同级指针传递(传递变量地址)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 void swap(int * a,int *b) 6 { 7 int temp; 8 printf("a地址:%p b地址:%p \n",a,b); 9 printf("交换前,a=%d b=%d \n",*a,*b); 10 temp=*a; 11 *a=*b; 12 *b=temp; 13 printf("交换后,a=%d b=%d \n",*a,*b); 14 } 15 int main() 16 { 17 int x=10,y=20; 18 printf("x地址:%p y地址:%p\n",&x,&y); 19 printf("交换前,x=%d y=%d \n",x,y); 20 swap(&x,&y); 21 printf("交换后,x=%d y=%d \n",x,y); 22 return 0; 23 }
本例中,main主函数申请了2个变量——x和y,并把它们的地址——&x和&y传递给被调函数swap,被调函数的形参定义了2个指针变量int *a,int *b来接收。由于a,b,&x,&y都是地址,相当于a=&x,b=&y。
如果把&x和&y也看成指针,则有*a=*(&x)=x,*b=*(&y)=y。显然,对其解引用后,所得值为变量的值,而不是地址。所以称之为同级指针传递(因为a,b和&x,&y都是只需一次解引用操作即可求得变量的值,我认为称之为“一级指针传递”更为贴切)。
从程序运行结果来看,被调函数修改了实参的值。所以,我们能得出结论:主调函数申请内存,同级指针传递实参变量地址,被调函数能够修改实参的值。
下面我们来看看堆和栈上字符串地址的传递:
2.2 主调函数申请内存,同级指针传递(传递堆上、栈上字符串地址)
1 void func(char* p) 2 { 3 strcpy(p, "Hello,World!"); 4 } 5 void test01() 6 { 7 //分配到栈上 8 char buf[1024] ="I like C语言!"; 9 printf("栈上初始字符串:%s \n", buf); 10 func(buf); 11 printf("主调函数申请内存,同级指针传递,栈上字符串返回:%s \n", buf); 12 } 13 14 void printString(char* str) 15 { 16 strcpy(str, "Hello,World!"); 17 printf("主调函数申请内存,同级指针传递,堆上字符串返回:%s \n", str); 18 } 19 void test02() 20 { 21 //分配到堆区 22 char* p = malloc(sizeof(char) * 64); 23 memset(p, 0, 64); 24 strcpy(p, "I like C语言!"); 25 printf("堆上初始字符串:%s \n", p); 26 printString(p); 27 printf("字符串长度=%d \n", strlen(p)); 28 } 29 30 int main() 31 { 32 test01(); 33 test02(); 34 system("pause"); 35 return EXIT_SUCCESS;
}
这里,由于我们可以把字符串名“buf”和指针“p”看作地址,显然,这也是同级指针传递。并且,通过程序运行结果,我们可以得出结论:主调函数申请内存,同级指针传递字符串地址,被调函数能够对堆和栈上字符串进行赋值,修改操作。
下面再来看一段代码:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 void buildString(char* pp) 6 { 7 char* temp = "被调函数修改字符串"; 8 pp = temp; 9 printf("%s \n", *pp); 10 } 11 void test01() 12 { 13 char* p = "主调函数定义初始字符串"; 14 printf("%s \n", p); 15 buildString(p); 16 printf("%s \n", p); 17 } 18 19 int main() 20 { 21 test01(); 22 return 0; 23 }
上面这段代码无法正常运行,再次证明了一个事实:以指针形式声明的字符串,保存在程序数据区,不能被改动。
通过上面3个例程,我们可以得出结论:主调函数申请内存,同级指针传递地址(无论是变量地址还是堆或者栈的地址)条件下,被调函数能够修改实参的值。当然,程序数据区的字符串依然不能修改!
2.3 在这里,我们提出一个问题:如果,在主调函数申请内存,二级指针传递地址(无论是变量地址还是堆或者栈的地址,甚至是程序数据区变量、常量地址)条件下,会发生什么那?也许,这将是我们下一篇随笔的命题!
3、被调函数申请内存,同级指针传递地址
3.1 被调函数申请内存,同级指针传递变量地址,这种情况不存在。或者说我不知道如何用C语言来就这种情况编程。
3.2 被调函数申请内存,同级指针传递字符串地址
1 #define _CRT_SECURE_NO_WARNINGS 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 6 void getString01(char* pp) //传递数组地址 7 { 8 char sTring[] = "Hello,world!"; 9 printf("字符串数组地址是:%p \n", sTring); 10 pp = sTring; 11 } 12 void test01() 13 { 14 char* p = NULL; 15 getString01(p); 16 printf("返回数组地址得到: %p, p=%s \n\n", p, p); 17 } 18 19 void getString02(char* pp) //传递指针地址 20 { 21 char* pOint = "Hello,Kitty!"; 22 printf("字符串指针地址是:%p \n", pOint); 23 pp = pOint; 24 } 25 void test02() 26 { 27 char* p = NULL; 28 getString02(p); 29 printf("返回指针地址得到:%p, p= %s \n\n", p, p); 30 } 31 32 void getString03(char* pp) //传递堆地址 33 { 34 char* hEap = malloc(20 * sizeof(char)); 35 if (hEap == NULL) return; 36 memset(hEap, 0, 20 * sizeof(char)); 37 strcpy(hEap, "Hello,Miss!"); 38 pp = hEap; 39 printf("堆地址是: %p \n", pp); 40 } 41 void test03() 42 { 43 char* p = NULL; 44 getString03(p); 45 printf("返回堆地址得到: %p, p=%s \n", p, p); 46 } 47 int main(void) 48 { 49 test01(); //调用函数,得到输出:“传递同级指针得:(null)” 50 test02(); 51 test03(); 52 system("pause"); 53 return EXIT_SUCCESS; 54 }
运行上面例程,结论是: 被调函数无论是在堆上,还是在栈上,还是以指针形式在程序数据区申请内存,其对形参的定义都无法传递给实参。
3.3 被调函数申请内存,二级指针传递字符串地址
1 #define _CRT_SECURE_NO_WARNINGS 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 6 void getString01(char** pp) //传递数组地址 7 { 8 char sTring[] = "Hello,world!"; 9 printf("字符串数组地址是:%p \n", sTring); 10 *pp = sTring; 11 } 12 void test01() 13 { 14 char* p = NULL; 15 getString01(&p); 16 printf("返回数组地址得到: %p, p=%s \n\n", p, p); 17 } 18 19 void getString02(char** pp) //传递指针地址 20 { 21 char* pOint = "Hello,Kitty!"; 22 printf("字符串指针地址是:%p \n", pOint); 23 *pp = pOint; 24 } 25 void test02() 26 { 27 char* p = NULL; 28 getString02(&p); 29 printf("返回指针地址得到:%p, p= %s \n\n", p, p); 30 } 31 32 void getString03(char** pp) //传递堆地址 33 { 34 char* hEap = malloc(20 * sizeof(char)); 35 if (hEap == NULL) return; 36 memset(hEap, 0, 20 * sizeof(char)); 37 strcpy(hEap, "Hello,Miss!"); 38 *pp = hEap; 39 printf("堆地址是: %p \n", pp); 40 } 41 void test03() 42 { 43 char* p = NULL; 44 getString03(&p); 45 printf("返回堆地址得到: %p, p=%s \n", p, p); 46 } 47 int main(void) 48 { 49 test01(); //调用函数,得到输出:“传递同级指针得:(null)” 50 test02(); 51 test03(); 52 system("pause"); 53 return EXIT_SUCCESS; 54 }
运行上面例程,结论是: 被调函数在堆上,或者以指针形式在程序数据区申请内存,其对形参的定义都可以传递给实参。在栈上定义的字符串数组,由于栈上变量的生命周期的原因,无法正确传递给实参。