前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >C语言的值传递

C语言的值传递

作者头像
知否知否应是绿肥红瘦
发布于 2025-02-19 13:31:28
发布于 2025-02-19 13:31:28
17000
代码可运行
举报
文章被收录于专栏:Linux知识Linux知识
运行总次数:0
代码可运行

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

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

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

题目如下:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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个变量全部取地址),看下运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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函数到底交换了什么。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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。程序修改如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void swap(int *x, int *y)
{

    int tmp;
    tmp = *x;
    *x = *y;
    *y = tmp;
}

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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),因此,形参并未改变实参的值。 下面再举个指针传参的面试题说明下这个问题:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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。 所以对于上述程序,如果要程序正常运行,可以使用二级指针,修改后程序如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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.数组作为形参的情况

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void test(char str[5])
{
   ....
}

下面进行举例说明:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#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个字节,由此可见:对于数组作为形参的这种情况,实参传递的是数组的首地址,形参数组接收这个首地址,实参和形参数组占用同一块内存。

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 题目如下:
  • 一、接下来就详细分析下这个问题:
    • 首先我们需要知道C语言中参数传递的几条规则:
  • 二、问题背后的问题:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档