C语言的值传递

首先对于C语言来说参数的传递只有两种方式:

  • 值传递
    将实参的值赋给形参,形参不能改变实参。传递的过程相当于在函数内部重新定义与实参同类型的变量,再把实参的值赋给该变量。
  • 地址传递
    将实参的地址传递给形参,形参不能改变实参。传递的过程相当于在函数内部定义几个指针变量,然后把实参的地址赋给这些指针,指针指向的内容就是实参。

刚好今天无意中看到一道有关C语言值传递的面试题,感觉非常具有代表性,背后涉及的知识也非常多,所以这里就拿出来分析一下,顺便讲讲C语言的值传递。(高手请直接略过本文)

题目如下:
void swap(int *x, int *y)
{
    int *tmp;
    tmp = x;
    x = y;
    y = tmp;
}
int main()
{
    int a = 1, b = 2;
    int *p1 = &a;
    int *p2=  &b;
    swap(p1, p2);
    return 0;
}

问题是:main函数执行完毕后,a和b的值有没有被交换?
答案:没有

一、接下来就详细分析下这个问题:
首先我们需要知道C语言中参数传递的几条规则:
  1. C语言中参数传递本质都是值传递(引用传递是C++中的概念)
  2. 形参只在调用时分配内存,调用结束时释放内存,形参被销毁。
  3. 参数传递是单向的,只能由实参传给形参。
  4. 形参只是实参的一份拷贝,形参的改变对实参本身没有影响。

注意:如果指针或者地址作为实参传入函数,指针指向的内容或者地址存放的数据是可以改变的,但是指针的值和地址本身是不会改变的。
根据以上规则,我们可以得出两个结论:1.swap函数传进去的是a和b的地址。2.swap函数的形参int *x和int *y是p1和p2的一份拷贝。下面是这6个变量在内存中(地址是假设的)的示意图:
在这里插入图片描述
上图左边是内存示意图,表明该块内存中存放的数据,右边是内存的地址,其中p1指向a,p2指向b。

我们还可以在程序中添加如下打印(对上述6个变量全部取地址),看下运行结果:

void swap(int *x, int *y)
{
    int* tmp;
    printf("x地址=%p  y地址=%p\n", &x, &y);
    tmp = x;
    x = y;
    y = tmp;
    //printf("x=%d  y=%d\n", *x, *y);
}

int main()
{
    int a = 1, b = 2;
    int *p1 = &a;
    int *p2=  &b;
    printf("a地址=%p  b地址=%p\n", &a, &b);
    printf("p1地址=%p  p2地址=%p\n", &p1, &p2);
    swap(&a, &b);
    printf("ab交换后:  ");
    printf("a=%d  b=%d\n", *p1, *p2);
    return 0;
}

运行结果如下图所示,这6个变量地址均不相同,且a,b没有交换。
在这里插入图片描述

传参过程相当于执行int *x=p1;int *y=p2;形参x,y是实参p1和p2的一份拷贝,它们在内存中也有自己的地址。

接着在swap函数中加上打印,看下swap函数到底交换了什么。

void swap(int *x, int *y)
{
    int* tmp;
    printf("交换前:\r\n");
    printf("\r\n");
    printf("x地址=%p  y地址=%p\n", &x, &y);
    printf("x=%p  y=%p\n", x, y);
    printf("\r\n");
    tmp = x;
    x = y;
    y = tmp;
    printf("交换后:\r\n");
    printf("\r\n");
    printf("x地址=%p  y地址=%p\n", &x, &y);
    printf("x=%p  y=%p\n", x, y);
    printf("\r\n");
}

在这里插入图片描述
swap执行时,x,y本身的地址没有改变,只是x和y进行了一次交换,且a,b本身也没有受到任何影响(p1,p2也不受影响)。swap函数执行结束时,形参x,y内存被自动释放,对实参依旧没有产生任何影响。

二、问题背后的问题:
  • 1.如何修改程序实现上述交换?
    • a.直接修改源数据
      由于传进来的是a和b的地址,所以可以直接修改地址上的内容,实现交换ab。程序修改如下:

      void swap(int *x, int *y)
      {
      
          int tmp;
          tmp = *x;
          *x = *y;
          *y = tmp;
      }
      

      这种方式并没有修改传进来的参数,因为a,b的地址并没有改变,只是通过传进来的参数间接的修改了地址上的内容,所以仍然满足前面说的形参不能改变实参本身。

    • b.二级指针修改源数据
      这种方法和一级指针类似,都是通过地址间接修改ab的值,无非就是多一级指针。程序如下:

      void swap(int **x, int **y)
      {
          int tmp;
          printf("swap交换前:\r\n");
          printf("\r\n");
          printf("x地址=%p  y地址=%p\n", &x, &y);
          printf("x=%p  y=%p\n", x, y);
          printf("\r\n");
          tmp = **x;
          **x = **y;
          **y = tmp;
          printf("swap交换后:\r\n");
          printf("\r\n");
          printf("x地址=%p  y地址=%p\n", &x, &y);
          printf("x=%p  y=%p\n", x, y);
          printf("\r\n");
      }
      
      int main()
      {
          int a = 1, b = 2;
          int *p1 = &a;
          int *p2=  &b;
          printf("ab交换前:\r\n");
          printf("a地址=%p  b地址=%p\n", &a, &b);
          printf("p1地址=%p  p2地址=%p\n", &p1, &p2);
          printf("p1=%p  p2=%p\n", p1, p2);
          printf("========================\r\n");
          swap(&p1, &p2);
          printf("========================\r\n");
          printf("ab交换后:\r\n");
          printf("p1地址=%p  p2地址=%p\n", &p1, &p2);
          printf("p1=%p  p2=%p\n", p1, p2);
          printf("a=%d  b=%d\n", a, b);
          return 0;
      }
      

      程序执行结果如下,p1和p2的地址以及p1,p2的值都没有改变,但是ab的值已经交换。
      在这里插入图片描述

  • 2.如何修改传入指针的值?
    • 使用二级指针
      下面先用示意图说明下这个问题。假设内存中有如下变量指针p=0x300,变量x地址为0x300,变量y的地址为0x304。现将p作为参数传入某函数,要求在该函数內修改p的值,使p=0x304?
      在这里插入图片描述
      显然对于这个问题,如果函数形参是一级指针,也就是传入的值为0x300,那我们是无论如何也不能将0x300改为0x304的(因为形参是无法改变传入的值的),但是如果传入的是p的地址(0x0004),那么我们就可以修改0x0004这个地址上的内容,也就是修改了p的值。
      这里或许有同学会问,既然二级指针改变了p的值,那么形参不还是改变了实参?其实不是这样的,首先我们需要了解传入的是什么值,发生改变的又是什么值,这里传入的是p的地址(0x0004),发生改变的是p(由0x300变为0x400),因此,形参并未改变实参的值。
      下面再举个指针传参的面试题说明下这个问题:

      void GetMemory(char *p)
      {
          p = (char *)malloc(100);
      }
      
      int main()
      {
          char *str=NULL;
          GetMemory(str);
          strcpy_s(str,6,"Hello");
          printf(str);
          return 0;
      }
      

      问题是:上述程序运行会有什么问题?
      答案:程序崩溃,一级指针传参无法改变指针的值,所以str的值一直是NULL。
      所以对于上述程序,如果要程序正常运行,可以使用二级指针,修改后程序如下:

      void GetMemory(char **p)
      {
          *p = (char *)malloc(100);
      }
      
      int main()
      {
          char *str=NULL;
          printf("申请之前:str=%p\r\n",str);
          GetMemory(&str);
          printf("申请之后:str=%p\r\n",str);
          strcpy_s(str,6,"Hello");
          printf(str);
          free(str);//动态申请的内存一定要手动释放
          return 0;
      }
      

在这里插入图片描述

  • 3.数组作为形参的情况

我们或许会遇到下面这种数组作为形参的情况,这是一种特殊情况,按照之前的分析,数组作为形参传递时,函数内部会定义一个与之类型相同的数组接收传入的数组变量,但实际上不是这样。

void test(char str[5])
{
   ....
}

下面进行举例说明:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void test(char str[5])
{
    printf("str = %d\n",sizeof(str));
    printf("str addr = %x\n",str);
    printf("str:%d-%d-%d-%d-%d\r\n",str[0],str[1],str[2],str[3],str[4]);
    str[1] = 66;
}
int main(void)
{
    char buf[]={1,2,3,4,5};
    printf("buf addr = %x\n",buf);
    test(buf);
    printf("buf:%d-%d-%d-%d-%d\r\n",buf[0],buf[1],buf[2],buf[3],buf[4]);
    return 0;
}

运行结果如下:
在这里插入图片描述
通过打印str和buf的值,发现buf和str的值是一样的,而且str占用的空间是4个字节,由此可见:对于数组作为形参的这种情况,实参传递的是数组的首地址,形参数组接收这个首地址,实参和形参数组占用同一块内存。

void test(char str[5]);
//这个表达式中的5实际上没有任何意义,因为形参只接收数组首地址,
//并不清楚实参数组长度
//一般再加个额外的参数,指示数组长度,防止数组越界情况
void test(char str[5],int n);//n表示数组长度

简单总结下:C语言参数传递的本质都是值传递;值传递意味着只能由实参传给形参,形参是实参在内存中的一份拷贝,函数运行结束时被销毁,形参无法改变实参。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值