C#vs Python参数传递

C# vs Python argument passing

python的参数传递规则与c的参数传递规则的主要区别是什么(如果有的话)?

我非常熟悉python,只开始学习c。我想知道我是否可以考虑规则集,比如当一个对象被引用传递时,或者按照C的值传递时,就像在python中一样,或者我需要记住一些关键的区别。


C按值传递参数,除非您指定要以不同的方式传递参数。如果参数类型是结构,则复制其值,否则复制对对象的引用。返回值也是如此。

可以使用refout修饰符修改此行为,修饰符必须在方法声明和方法调用中都指定。两者都将该参数的行为更改为按引用传递。这意味着您不能再传递更复杂的表达式。refout的区别在于,当将变量传递给ref参数时,它必须已经初始化,而传递给out参数的变量不必初始化。在该方法中,out参数被视为未初始化的变量,必须在返回之前为其赋值。


将python称为"传递值"或"传递引用"语言,并与C、C等进行比较的问题是,python对数据的引用方式有不同的概念。python不容易适应传统的按值或按引用二分法,导致混淆和"它是按值调用的!"不,这是参考电话,我可以证明!"不,你这栗色的,很明显这是按价值计算的!"无穷无尽的循环。

事实上,两者都不是。python使用按共享调用(也称为按对象调用)。有时,这似乎是一种按值策略(例如,当处理诸如intfloatstr等标量值时),有时也像一种参照策略(例如,当处理诸如listdictsetobject等结构化值时)。DavidGoodger的代码就像一个pythonista,完美地总结了这一点,"其他语言有变量;Python有名字。"作为一个额外的好处,他提供了清晰的图形来说明不同之处。

在封面下,共享调用的实现更像是引用调用(正如Noctis Skytower提到的一个突变的float示例所演示的那样),但是如果你把它看作引用调用,你会很快偏离轨道,因为引用是实现,它们不是公开的语义。

相比之下,C使用按值调用或参照调用——尽管有人可能会认为,out选项代表了一种超出C、pascal等中所示的纯参照调用的微调。

所以,python和c确实是非常不同的——在体系结构级别,无论如何。在实践中,按价值和参照的结合将允许您创建与共享调用非常相似的程序——尽管在细节和角落的案例中有一个棘手的小魔鬼。

如果您有兴趣了解不同语言在比较上下文中的参数传递策略,维基百科关于表达评估策略的页面值得一读。虽然这并不详尽(有很多方法可以剥下这只特别的猫的皮!)它巧妙地涵盖了一系列最重要的内容,以及一些有趣的、不常见的变化。


python总是使用pass-by引用值。也不例外。任何变量分配都意味着分配参考值。也不例外。任何变量都是绑定到引用值的名称。总是。

您可以将reference视为使用时自动取消引用的目标对象的地址。这样,您似乎可以直接处理目标对象。但是在两者之间总是有一个参照物,要跳到目标上还要多走一步。

更新——这里有一个需要的示例,证明通过引用传递:

Illustrated example of passing the argument

如果参数是按值传递的,则不能修改外部lst。绿色是目标对象(黑色是存储在其中的值,红色是对象类型),黄色是内存,其中的引用值——绘制为箭头。蓝色实心箭头是传递给函数的参考值(通过蓝色虚线箭头路径)。难看的深黄色是内部词典。(实际上它也可以画成绿色的椭圆。颜色和形状只表示它是内部的。)

更新——与fgb关于通过引用示例swap(a, b)的评论和delnan关于不可能编写swap的评论有关。

在编译语言中,变量是能够捕获类型值的内存空间。在python中,变量是绑定到保存目标对象引用值的引用变量的名称(在内部捕获为字符串)。变量的名称是内部字典中的键,该字典项的值部分存储对目标的引用值。

其他语言中的swap的目的是交换传递变量的内容,即交换内存空间的内容。这也可以在python中实现,但只适用于可以修改的变量——这意味着可以修改其内存空间的内容。这仅适用于可修改的容器类型。从这个意义上讲,一个简单的变量总是常量,即使它的名称可以被重用用于其他目的。

如果函数应该创建一些新的对象,那么获取它的唯一方法是通过容器类型参数,或者通过python return命令。但是,python return在语法上看起来好像可以传递多个参数。实际上,传递到外部的多个值形成了一个元组,但可以在语法上将该元组分配给更多的外部python变量。

与其他语言中的变量模拟相关的更新。内存空间是由单个元素列表模拟的——也就是说,还有一个间接级别。那么,swap(a, b)就可以像用其他语言一样编写。唯一奇怪的是,我们必须使用列表中的元素作为对模拟变量值的引用。这样模拟其他语言变量的必要性是因为只有容器(容器的子集)是Python中唯一可以修改的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> def swap(a, b):
...   x = a[0]
...   a[0] = b[0]
...   b[0] = x
...
>>> var1 = ['content1']
>>> var2 = ['content2']
>>> var1
['content1']
>>> var2
['content2']
>>> id(var1)
35956296L
>>> id(var2)
35957064L
>>> swap(var1, var2)
>>> var1
['content2']
>>> var2
['content1']
>>> id(var1)
35956296L
>>> id(var2)
35957064L

注意,现在的var1var2模拟了经典语言中"正常"变量的外观。swap更改了内容,但地址保持不变。

对于可修改对象(例如列表),您可以编写与其他语言完全相同的swap(a, b)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> def swap(a, b):
...   x = a[:]
...   a[:] = b[:]
...   b[:] = x[:]
...
>>> lst1 = ['a1', 'b1', 'c1']
>>> lst2 = ['a2', 'b2', 'c2']
>>> lst1
['a1', 'b1', 'c1']
>>> lst2
['a2', 'b2', 'c2']
>>> id(lst1)
35957320L
>>> id(lst2)
35873160L
>>> swap(lst1, lst2)
>>> lst1
['a2', 'b2', 'c2']
>>> lst2
['a1', 'b1', 'c1']
>>> id(lst1)
35957320L
>>> id(lst2)
35873160L

注意,像a[:] = b[:]这样的多重赋值必须用于表示列表内容的复制。


python总是传递值:

1
2
3
4
5
6
7
8
9
10
def is_python_pass_by_value(foo):
    foo[0] = 'More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.'
    foo = ['Python is not pass-by-reference.']

quux = ['Yes, of course, Python *is* pass-by-value!']

is_python_pass_by_value(quux)

print(quux[0])
# More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.

C默认为传递值,但如果在方法声明站点和调用站点都使用了ref关键字,则也支持传递引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
struct MutableCell
{
    public string value;
}

class Program
{
    static void IsCSharpPassByValue(string[] foo, MutableCell bar, ref string baz, ref MutableCell qux)
    {
        foo[0] ="More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.";
        foo = new string[] {"C# is not pass-by-reference." };

        bar.value ="For value types, it is *not* call-by-sharing.";
        bar = new MutableCell { value ="And also not pass-by-reference." };

        baz ="It also supports pass-by-reference if explicitly requested.";

        qux = new MutableCell { value ="Pass-by-reference is supported for value types as well." };
    }

    static void Main(string[] args)
    {
        var quux = new string[] {"Yes, of course, C# *is* pass-by-value!" };

        var corge = new MutableCell { value ="For value types it is pure pass-by-value." };

        var grault ="This string will vanish because of pass-by-reference.";

        var garply = new MutableCell { value ="This string will vanish because of pass-by-reference." };

        IsCSharpPassByValue(quux, corge, ref grault, ref garply);

        Console.WriteLine(quux[0]);
        // More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.

        Console.WriteLine(corge.value);
        // For value types it is pure pass-by-value.

        Console.WriteLine(grault);
        // It also supports pass-by-reference if explicitly requested.

        Console.WriteLine(garply.value);
        // Pass-by-reference is supported for value types as well.
    }
}

如您所见,如果没有使用ref关键字的显式注释,c的行为与python完全相同。值类型是传递值,其中传递的值是对象本身,引用类型是传递值,其中传递的值是指向对象的指针(也称为对象共享调用)。

python不支持可变的值类型(可能是件好事),因此不可能观察传递值逐值和传递指针逐值之间的区别,因此您可以将所有内容都视为传递指针逐值,并大大简化您的心理模型。

C还支持out参数。它们也是通过引用传递的,但可以保证被调用方永远不会从中读取,只会写入,因此调用方不需要预先初始化它们。当您在Python中使用元组时,它们用于模拟多个返回值。它们有点像单向引用。


没什么不同

1
2
3
def func(a,b):
    a[0]=5 #Python
    b=30
1
2
3
4
public int func( ref int a,out int b,int d)
{
  a++;b--;  //C#
}
1
2
3
4
x=[10]
y=20
func(20,30) #python
print x,y   #Outputs x=[5],y=20 Note:I have used mutable objects.Not possible with int.
1
2
3
int x=10,y=20;
func(ref x,out y,18); //C#
Console.Writeline("x={0} y={1}",x,y);//Outputs x=11,y=19