函数传参的三种方式(详细)

最近又碰到了函数传参的问题,每次碰到都会让作者疑惑一段时间,这次干脆总结整理一下,顺便让自己更加透彻的了解传参的问题。咱们开始吧。

c语言中,函数调用时传送给形参表的实参必须与形参有三个一致:类型、个数、顺序,
函数传参有三种方式:值传递,地址传递,引用传递

下面我们首先来介绍值传递:

  • 值传递为最简单的一种,因为它是把实参的值传送给函数局部工作区相应的副本中,函数使用这个副本执行必要的功能。函数修改的是副本的值,实参的值不变。

我们来看代码:

#include<stdio.h>
void swap(int m, int n){
    int m;
    m = n;
    n = temp;
}
int main(){
    int a, b;
    a = 2;
    b = 3;
    swap(a, b);
    printf("a=%d,b=%d",a,b);
}

打印出结果
在这里插入图片描述
我们可以看到,a和b在进行交换之前,他们的值分别为2和3,而进行交换操作之后,它们的值仍为2和3,但是在函数swap内部我们可以看到m和n的值确实发生过了交换。
怎么解释这个问题呢,我们可以利用以下代码:

a = 2;
m = a;
m = m+3;
a = ?;	m = ?;

这时我们可以看出来,a赋值之后又将a的值赋给了m,这样无论m进行什么操作,都不会影响到a的值。
说到这里我们就可以理解上面的代码了
调用参数的时候,swap(a,b)代表把a和b作为参数传入,在函数swap接收的时候,它们会自动的将a和b的值copy一份给与自己的参数,也就是形参。
换成代码可以如下理解:

swap(a,b);
swap(int m = a,int n = b);

所以函数内部进行的所有操作都不会影响到外部。


那么如何才能使得调用函数后实参也发生转换呢?
说到这里,我们就可以引出第二种传参方式:地址传递。

  • 地址传递与值传递的不同在于,它把实参的存储地址传送给形参,使得形参指针和实参指针指向同一块地址。因此,被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。

上代码-.-

#include<stdio.h>
void swap(int *m, int *n){
    int temp;
    temp = *m;
    *m = *n;
    *n = temp;
    printf("m=%d,n=%d\n",m,n);
    printf("m=%d,n=%d\n",*m,*n);
}
int main(){
    int a, b;
    a = 2;
    b = 3;
    printf("交换前:a=%d,b=%d\n",&a,&b);
    swap(&a, &b);
    printf("交换后:a=%d,b=%d\n",a,b);
    printf("交换后:a=%d,b=%d\n",&a,&b);
}

结果如下:
在这里插入图片描述
如图所示,a和b的值确实发生了交换,原理是什么呢?为了便于大家更好的理解,我将交换前后ab的地址也一并打印了出来。
我们可以看到,执行swap函数时,m和n都拿到了a和b的地址。其实它们的操作和值传递是一样的,只不过不使用指针时,m和n会将a和b的值放到属于自己的地址中。使用指针后m和n也指向了a与b的地址,再进行交换,这样交换过后就不会局限于之在函数内部。

值得一提的是,如果你在函数中使用了指针变量来交换值,那么最终结果仍会是失败的。
如下:

#include<stdio.h>
void swap(int *m, int *n){
    int *temp;
    printf("m=%d,n=%d\n",m,n);
    printf("m=%d,n=%d\n",*m,*n);
    temp = m;
    m = n;
    n = temp;
    printf("m=%d,n=%d\n",m,n);
    printf("m=%d,n=%d\n",*m,*n);
}
int main(){
    int a, b;
    a = 2;
    b = 3;
    printf("交换前:a=%d,b=%d\n",a,b);
    printf("交换前:a=%d,b=%d\n",&a,&b);
    swap(&a, &b);
    printf("交换后:a=%d,b=%d\n",a,b);
    printf("交换后:a=%d,b=%d\n",&a,&b);
}

在这里插入图片描述
a与b没有交换成功。
怎么理解?
我又将m和n交换途中的地址进行打印,很明显,我把mn的地址互换了,但是并没有对两个地址对应的值进行操作。你交换成功了 ,但又没有完全成功(不是)。


所以接下来就可以隆重介绍我们的第三位选手:引用传递

  • 引用传递是以引用为参数,则既可以使得对形参的任何操作都能改变相应数据,又使函数调用方便。引用传递是在形参调用前加入引用运算符“&”。引用为实参的别名,和实参是同一个变量,则他们的值也相同,该引用改变则它的实参也改变。

它的代码就方便多了

#include<stdio.h>
void swap(int &m, int &n){
    int temp;
    temp = m;
    m = n;
    n = temp;
    printf("m=%d,n=%d\n",m,n);
    printf("m=%d,n=%d\n",&m,&n);
}
int main(){
    int a, b;
    a = 2;
    b = 3;
    printf("交换前:a=%d,b=%d\n",a,b);
    printf("交换前:a=%d,b=%d\n",&a,&b);
    swap(a, b);
    printf("交换后:a=%d,b=%d\n",a,b);
    printf("交换后:a=%d,b=%d\n",&a,&b);
}

在这里插入图片描述
轻松完成任务。

对比于三种传参方式来说,引用传参直接操作实参,比前两种都简单。

引用类型作形参,在内存中并没有产生实参的副本,它直接对实参操作;而一般变量作参数,形参与实参就占用不同的存储单元,所以形参变量的值是实参变量的副本。因此,当参数传递的数据量较大时,用引用比用一般变量传递参数的时间和空间效率都好。

指针参数虽然也能达到与使用引用的效果,但在被调函数中需要重复使用“*指针变量名”的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。

那么,本次分享到这里就告一段落了,喜欢的朋友可以给个免费的赞,我们下次再见。

<think>好的,我现在需要帮用户理解函数传参三种方式:值传递、地址传递和引用传递的区别和联系。首先,我得回忆一下这些概念的基本定义,然后结合用户提供的三个引用内容来组织回答。 首先,用户提供的三个引用分别来自C++和JavaScript,所以可能需要区分不同语言中的实现差异。比如,JS中只有值传递,但如果是对象的话,传递的是引用的副本,这可能和C++的引用传递不同。不过用户主要问的是三种方式,所以重点可能在C++中的情况。 接下来,我需要明确每种传递方式的机制: 1. **值传递**:函数接收的是实参的拷贝。修改形参不会影响实参。适用于基本类型,但对于大对象可能效率低,且有浅拷贝问题。引用[2]提到值传递是单向的,形参是实参的拷贝,改变形参不影响实参。 2. **地址传递(指针传递)**:传递的是变量的地址,函数通过指针间接访问和修改实参。需要解引用操作,如引用[1]中的例子,用指针来交换变量。这种方式可以修改实参,但操作相对繁琐,需要注意指针的有效性。 3. **引用传递**:C++中的引用是别名,传递时直接操作原变量,语法更简洁。引用[2]提到引用传递不需要解引用,直接操作原变量,适合结构体和类,避免拷贝开销。 然后,需要比较它们的区别和联系: - **参数类型**:值传递是拷贝,地址传递是指针,引用传递是别名。 - **操作方式**:地址传递需要解引用,引用传递直接使用。 - **效率**:值传递对于大对象效率低,地址和引用传递更高效。 - **安全性**:指针可能为空或野指针,引用则必须初始化且不为空。 - **语言差异**:如JS中的对象传递是引用副本,属于共享传递,修改属性会影响原对象,但重新赋值不会,这点可能需要说明。 用户可能混淆不同语言中的表现,比如JS的“引用传递”其实和C++的不同。不过用户的问题可能更关注C++的情况,因为引用[1]和[2]都是C++相关的。但引用[3]提到JS的情况,可能需要简要对比。 然后,组织回答的结构: 1. 分点解释三种传递方式,每种包括定义、特点、示例代码(可能的话)、适用场景。 2. 对比三者的区别,比如参数类型、内存操作、效率、安全性。 3. 总结联系,比如地址和引用传递都能修改实参,值传递保护原数据。 4. 添加相关问题,帮助用户深入理解。 需要注意的是,用户提供的引用中有提到地址传递在C++中的例子,如交换函数使用指针,而引用传递则更简洁。同时,引用[2]指出复合类型避免值传递,推荐指针或引用。这些点都要整合到回答中。 可能需要检查示例代码的正确性,比如C++中的swap函数使用指针和引用的不同写法,确保语法正确。此外,是否需要区分指针传递和引用传递在函数定义和调用时的差异,比如调用时指针需要取地址,而引用直接传递变量。 最后,确保回答符合用户的要求:结构清晰,分步骤解释,引用标记正确,添加相关问题。同时使用中文,数学公式正确用$符号。</think>### 函数传参三种方式及区别 #### 一、值传递 **定义**:将实参的**值拷贝**一份传递给形参,函数内操作的是拷贝后的数据,不会影响原始实参。 **特点**: - 形参是实参的独立副本,修改形参不影响实参[^2]。 - 适用于基本数据类型(如`int`, `char`)或不需要修改实参的场景。 - 对复合类型(如结构体、类)可能引发性能问题(深拷贝开销)。 **示例**(C++): ```cpp void mySwapValue(int a, int b) { int temp = a; a = b; b = temp; // 交换失败,实参不变 } ``` --- #### 二、地址传递(指针传递) **定义**:将实参的**内存地址**传递给形参(指针),通过解引用操作直接修改内存中的原始数据。 **特点**: - 需使用取地址符`&`传递参数,函数内用`*`操作指针指向的值[^1]。 - 可修改实参,但需注意空指针或野指针问题。 - 适用于需要修改实参或传递大型数据的场景。 **示例**(C++): ```cpp void mySwapPointer(int* a, int* b) { int temp = *a; *a = *b; *b = temp; // 交换成功,实参被修改 } ``` --- #### 三、引用传递 **定义**:形参是实参的**别名**,直接操作原始数据,语法更简洁(无需解引用)。 **特点**: - 形参与实参共享内存地址,修改形参即修改实参。 - 语法安全(引用必须初始化且不可为空)。 - 适用于需要高效修改实参的场景,尤其是复合类型(如类、结构体)。 **示例**(C++): ```cpp void mySwapRef(int &a, int &b) { int temp = a; a = b; b = temp; // 交换成功,实参被修改 } ``` --- #### 四、三者的区别与联系 | **特性** | 值传递 | 地址传递 | 引用传递 | |----------------|----------------------|-----------------------|-----------------------| | **参数类型** | 拷贝值 | 指针 | 别名 | | **内存操作** | 独立副本 | 直接操作原数据 | 直接操作原数据 | | **效率** | 低(复合类型) | 高 | 高 | | **安全性** | 高(保护原数据) | 需防指针异常 | 高(不可为空) | | **语法复杂度** | 简单 | 需`*`和`&`操作符 | 简洁(无符号) | **联系**: - 地址传递和引用传递都能直接修改实参,但引用传递语法更安全、简洁。 - 值传递通过拷贝保护原始数据,适用于函数内局部修改。 --- #### 五、跨语言对比(JavaScript) 在JavaScript中: - **基本类型**(如`number`, `string`)是值传递,修改形参不影响实参。 - **引用类型**(如`Object`, `Array`)是“共享传递”:传递的是引用的副本,修改属性会影响原对象,但重新赋值形参不会影响实参[^3]。 **示例**: ```javascript function updateObj(obj) { obj.value = 2; // 修改属性,原对象被影响 obj = { value: 3 }; // 重新赋值,原对象不变 } ``` ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值