从赋值操作理解不同类型的函数传参

本文详细探讨了在编程中函数传参的原理,包括按值和按引用传递的区别,并通过C#示例代码展示了ref关键字如何实现按引用传参。

从赋值操作理解不同类型的函数传参

         我们都知道所谓的程序,就是对传入的数据进行操作,最终将处理的数据输出给用户。那么在我们的编程中的体现,就是通过函数(或者方法)参数传入数据,然后通过函数返回值输出处理后的数据。数据的传入涉及到函数的实参和形参的传递,数据的输出涉及到函数的返回值和返回值类型的参数。函数返回值没有什么特别之处,而其他的都是函数传参的问题,既然是传参,那么传参到底传过去了什么?

函数传参与赋值操作

         我们都知道函数传参涉及到形参和实参,那么实参和形参就是两个不同的变量,所以函数传参的过程,类似(或者说就是)使用实参给形参赋值的过程。那么我们先来分析按值传参的情况

         1是值类型变量的赋值操作图,我们大家都知道值类型变量直接保存变量的内容,所以赋值的过程就是将ValA保存的内容直接拷贝到ValB中,此时两个变量除了保存的值相同外,没有其他的任何关系,所以此时修改ValB的内容并不会引起ValA的变化。这是与值类型按值传参的效果是一样的。

1.值类型变量赋值

         2展示的是引用类型变量的赋值操作,我们都知道引用类型的变量保存的是一个地址,这个地址指向了变量内容所在的地址,也就是说引用变量保存的是一个地址。那么引用类型的赋值操作同样也是拷贝变量保存的内容,这样ValAValB就都指向了同样地址。如果我们修改ValB的成员,那么ValA中也会随之产生变化。我们可以看到值类型和引用类型的赋值操作是一样的,都是对变量直接保存的内容的拷贝过程。但是如果我们使用其他对象对ValB重新赋值,这时ValB就指向了其他对象,ValA还指向原来的对象并没有变化。

2.引用类型变量赋值

         我们知道C#中引入了ref关键字,使我们可以通过函数参数返回函数的处理结果,对于我们需要同时返回多项结果的时候比较有用。其实这也就是我们通常所说的按引用传参。图3展示了变量地址赋值操作,此时我们使用新的对象对ValB所指定的对象赋值,这是ValA也就指向了新的对象了。C#中的ref是否也是这样操作的呢?我们看以看一下下边的代码和相应的IL就知道了。

3.变量地址赋值操作

C#代码

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FunctionParameter
{
    class PassByValue
    {       
        public void PassValueParameterByReference(ref ReferencePara parameter)
        {
            parameter.a = 1;
            parameter = new ReferencePara();
        }

        public class ReferencePara
        {
            public int a;
            public int b;
        }
    }
}

 

对应的MSIL代码 

//传递的是变量的地址
.method public hidebysig instance void  PassValueParameterByReference(class FunctionParameter.PassByValue/ReferencePara& parameter) cil managed
{
  // 代码大小       17 (0x11)
  .maxstack  8
  IL_0000:  nop
  //加载参数
  IL_0001:  ldarg.1
  //加载对象的引用
  IL_0002:  ldind.ref
  IL_0003:  ldc.i4.1
  IL_0004:  stfld      int32 FunctionParameter.PassByValue/ReferencePara::a
  IL_0009:  ldarg.1
  IL_000a:  newobj     instance void FunctionParameter.PassByValue/ReferencePara::.ctor()
  //绑定对象的引用
  IL_000f:  stind.ref
  IL_0010:  ret
// end of method PassByValue::PassValueParameterByReference

总结

         从我们大学一开始接触计算机编程开始,我们就开始记忆两种函数传参的异同,其实传参的过程就是实参对形参赋值的过程,只不过有时拷贝的是变量的内容,有时拷贝的是变量的地址罢了。

Python函数传参是非常灵活和强大的特性之一。以下是关于如何设置Python函数参数的一些关键点: 1. **位置参数**:这是最基本的传递参数的方式,按照定义顺序依次传递给函数内部相应的变量。 ```python def greet(name, age): print(f"Hello {name}, you are {age} years old.") greet("Alice", 30) ``` 2. **默认值参数**:可以在定义函数为某些参数指定默认值,在调用不提供该参数将采用默认值。 ```python def greet_with_default(name="Guest"): print(f"Hello {name}") greet_with_default() # 使用默认值 "Guest" greet_with_default("Bob") # 指定参数值覆盖默认值 ``` 3. **关键字参数**:允许通过参数名称直接赋值,而无需关心它们的位置。 ```python def describe_pet(animal_type, pet_name): """显示宠物信息""" print(f"\nI have a {animal_type}.") print(f"My {animal_type}'s name is {pet_name.title()}.") describe_pet(pet_name='harry', animal_type='hamster') ``` 4. **不定长参数(*args 和 **kwargs)** - `*args`用于接收任意数量的位置参数并作为一个元组处理; - `**kwargs`则收集键值对形式的关键字参数,并将其作为字典访问。 ```python def make_pizza(*toppings): print("\nMaking pizza with the following toppings:") for topping in toppings: print(f"- {topping}") make_pizza('pepperoni') make_pizza('mushrooms', 'green peppers', 'extra cheese') # kwargs example def build_profile(first, last, **user_info): user_info['first_name'] = first user_info['last_name'] = last return user_info user_profile = build_profile('albert', 'einstein', location='princeton', field='physics') print(user_profile) ``` 5. **命名空间内的全局变量 vs 局部变量**: 当在函数体内修改列表、字典等可变数据结构可以直接改变原对象;但对于不可变的数据类型如整数、字符串,则需要明确声明其为global才能更改模块级作用域下的同名变量。 6. **强制使用关键字参数(/)** :从Python 3.8开始引入了新的语法糖"/"使得开发者能够更清晰地控制哪些参数只能按位置传递哪些又可以接受名字标签。 7. **仅限关键词参数(*)**: 当星号出现在形参列表里第一个非keyword-only 参数之前的所有都是position-or-keyword 形式之后的就是 keyword only 类型. 综上所述,掌握这些基础知识可以帮助我们更好地理解和编写高效简洁的Python程序!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值