Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >C语言的值传递

C语言的值传递

作者头像
知否知否应是绿肥红瘦
发布于 2025-02-19 13:31:28
发布于 2025-02-19 13:31:28
16900
代码可运行
举报
文章被收录于专栏: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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
C语言之精华——指针详解(下)
2、指向数组元素的指针 支持 递增 递减 运算。(实质上所有指针都支持递增递减 运算 ,但只有在数组中使用才是有意义的)
C语言中文社区
2022/05/30
5990
C语言之精华——指针详解(下)
【C语言】⒉万字带你玩转高阶指针『0»1』
🚀write in front🚀 ---- 🔎大家好,我是謓泽,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎 🏅2021年度博客之星物联网与嵌入式开发TOP5~2021博客之星Top100~阿里云专家^星级博主~掘金 || InfoQ创作者~周榜34»总榜2815🏅 🆔本文由 謓泽 原创 CSDN首发🙉如需转载还请通知⚠ 📝个人主页:打打酱油desuCSDN博客💬 🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​ 📣系列专栏:【C】系列_打打酱油desu-CSDN博客[〇~①]🎓
謓泽
2022/12/12
6160
【C语言】⒉万字带你玩转高阶指针『0»1』
C语言---深入指针(4)
对于qsort函数来说,我们只需要额外构建一个比较函数就能利用qsort进行快速排列
Undoom
2024/09/23
1020
【期末复习】⚡考试月来临!C语言复习,这一篇带你逃离挂科区!(完结)
注意:函数就是功能。每一个函数用来实现一个特定的功能。函数的名字应反映出它代表的功能,这样代码的可读性会大大提升
小丞同学
2021/08/16
9440
C语言指针深度解剖
指针是C语言的灵魂,深入理解指针,是学好学会C语言的重要前提。因此,本文将重点讲解C语言指针的深度内容。
二肥是只大懒蓝猫
2023/03/30
4940
C语言指针深度解剖
C语言学习——指针精华(1)
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/170990.html原文链接:https://javaforall.cn
全栈程序员站长
2022/09/23
2460
C语言学习——指针精华(1)
再谈C语言——C指针详解
还有一点:C语言中的一切函数调用中,实参传递给形参的机理都是“按值传递(pass by value)”,如果我们要在函数中修改被传递过来的对象,就必须通过这个对象的指针来完成。
xxpcb
2024/07/12
1230
再谈C语言——C指针详解
【C语言篇】深入理解指针4(模拟实现qsort函数)
如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数时,被调⽤的函数就是回调函数。
半截诗
2024/10/09
900
【C语言篇】深入理解指针4(模拟实现qsort函数)
指针02
结果是10 匪夷所思 明明把a传进去了 为什么没有用??因为main函数的a和addnumber函数的a不是一个a
用户7272142
2023/10/11
1420
指针02
轻松拿捏C语言——【保姆级·指针讲解】期末C语言<指针>急救包,全是干货,诚意满满!
有一栋楼,里有200个房间,假如我们要去某个房间找某个人,然后他说他在C304,我们就能通过门牌号C304快速找到他所在房间。
用户11162265
2024/06/14
1310
轻松拿捏C语言——【保姆级·指针讲解】期末C语言<指针>急救包,全是干货,诚意满满!
C语言进阶(八) - 指针进阶
使用typedef对函数指针void (*)(int)类型进行重命名,简化上面的函数声明:
怠惰的未禾
2023/04/27
6520
C语言进阶(八) - 指针进阶
C语言学习系列-->看淡指针(1)
在大学的宿舍里,每个宿舍都有属于自己的编号(比如:222),每一栋楼也有属于自己名字或者编号(比如:慧苑,B05)。通过这些编号,我们在点外卖的时候,直接将宿舍楼和宿舍号写在地址上,外卖小哥就会将你所点的食物送到对应的宿舍。如果,没有这些编号,你该怎么直接描述地址呢?让小哥一个一个找吗?效率低。
南桥
2024/01/26
1230
C语言学习系列-->看淡指针(1)
C语言:深入理解指针(4)
      函数指针是将函数的地址取出来,再通过函数地址去调用,那为什么不直接用函数名调用呢??原因是因为函数指针可以用来实现回调函数,而回调函数有自己的应用场景。
小陈在拼命
2024/02/17
1390
C语言:深入理解指针(4)
C语言——指针(五)
在上一篇文章中,我们提到了函数指针,函数指针是用来存放函数地址的指针,这篇文章,我们还将继续探究函数与指针。
用户11029137
2024/03/19
980
C语言——指针(五)
结构体知识------址传递和值传递
相关知识 1. 普通变量(char a):a是变量名,对应内存空间的大小是sizeof(char),对应地址假设是0x001,也就是地址0x001存放的是变量a的值,存放的数据类型是字符型。 2. 指针变量(char *p):指针变量的本质还是一个变量,只不过存放的数据类型是地址。p是变量名,对应的内存空间的大小是sizeof(char *),对应的地址假设是0x002,也就是地址0x002中存放的是变量p的值,存放的数据类型是指针:int a = 1; a在内存中的地址假设是0x001。 3. 形参是函数定义的时候用的,实参是调用函数的时候用的。 函数的参数都是形参,只有在函数调用的时候系统才会为形参分配空间和地址,形参和实参不会是同一个内存地址。 例如:
跋扈洋
2021/02/02
6180
C语言指针详解(文末有福利)
假如我们定义了 char a=’A’ ,当需要使用 ‘A’ 时,除了直接调用变量 a ,还可以定义 char *p=&a ,调用 a 的地址,即指向 a 的指针 p ,变量 a( char 类型)只占了一个字节,指针本身的大小由可寻址的字长来决定,指针 p 占用 4 个字节。
C语言与CPP编程
2020/12/02
5450
C语言指针详解(文末有福利)
【C语言基础】:深入理解指针(二)
指针的基本运算有三种,分别是: 1. 指针 ± 整数 2. 指针 - 指针 3. 指针的关系运算
爱喝兽奶的熊孩子
2024/04/10
1550
【C语言基础】:深入理解指针(二)
熬夜整理的万字C/C++总结(二),值得收藏
假如我们定义了 char a=’A’ ,当需要使用 ‘A’ 时,除了直接调用变量 a ,还可以定义 char *p=&a ,调用 a 的地址,即指向 a 的指针 p ,变量 a( char 类型)只占了一个字节,指针本身的大小由可寻址的字长来决定,指针 p 占用 4 个字节。
C语言与CPP编程
2021/08/03
1.3K0
熬夜整理的万字C/C++总结(二),值得收藏
C语言——I /深入理解指针(一)
⽣活中我们把门牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语言中给地址起了新的名字叫:指针。 所以我们可以理解为:内存单元的编号 == 地址 == 指针
用户11015888
2024/03/11
1470
C语言——I /深入理解指针(一)
函数(二)(函数的调用与值传递)
程序执行到一个函数调用另一个函数的语句时,程序的执行流程从发生函数调用的位置离开主调函数,转移到被调函数开始执行。被调函数中执行到return语句或执行完最后一条语句时,程序执行流程重新回到主调函数的离开位置,继续执行主调函数后面的语句或表达式。
pigeon
2022/04/11
9320
函数(二)(函数的调用与值传递)
相关推荐
C语言之精华——指针详解(下)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验