参数(计算机编程)

计算机编程中,参数正式参数子例程中使用的一种特殊类型的变量,用于参考作为子例程输入提供的数据之一。这些数据是参数的值(通常称为实际参数实际参数),该参数将被调用/调用。参数的有序列表通常包含在子例程的定义中,因此,每次调用子例程时,对该调用的参数进行评估,并且可以将结果值分配给相应的参数。

与通常的数学用法中的参数不同,计算机科学中的参数是调用/呼叫语句中传递/提供给函数,过程或例程的实际输入表达式,而参数是子例程实现的变量。例如,如果定义add子例程为def add(x, y): return x + y, 然后x, y是参数,而如果称为add(2, 3), 然后2, 3是参数。来自调用上下文的变量(及其表达式)可以是参数:如果子例程称为a = 2; b = 3; add(a, b)然后变量a, b是参数,而不是价值观2, 3。有关更多信息,请参见参数和参数部分。

关于如何声明参数的语义以及参数的(值)如何传递给子例程的参数,由语言的评估策略定义该系统的约定。在最常见的情况下,按值调用,一个参数在子例程中作为一个新的局部变量在参数的值(如果参数为变量)中初始化为参数的值(本地(隔离)副本),但在其他情况下,例如,通过引用调用,呼叫者提供的参数变量可能会受到所谓子例程中的操作的影响。

例子

C编程语言中的以下程序定义了一个名为“ Salestax”的函数,并具有一个名为“ Price”的参数。价格类型为“双重”(即双精度浮点数)。该函数的返回类型也是双重的。

double SalesTax(double price)
{
  return 0.05 * price;
}

定义函数后,可以将其调用如下:

SalesTax(10.00);

在此示例中,该函数已与参数10.00调用。发生这种情况时,将分配10.00,并开始计算其结果。在下面指定了生成结果的步骤,并包含在{}中。0.05 * price表明第一件事要做的是将0.05乘以价格的价值,给出0.50。return意味着该功能将产生0.05 * price。因此,最终结果(忽略可能的圆形误差,一个用代表小数部分作为二进制组分的遇到的结果)为0.50。

参数和参数

术语参数参数可能在不同的编程语言中具有不同的含义。有时它们可​​以互换使用,并且上下文被用来区分含义。术语参数(有时称为正式参数)通常用于指函数定义中的变量,而参数(有时称为实际参数)是指在函数呼叫中提供的实际输入。例如,如果将函数定义为def f(x): ..., 然后x是参数,如果是a = ...; f(a)然后a是论点。参数是(未结合的)变量,而参数可以是涉及文字和变量的文字或变量或更复杂的表达式。如果按值呼叫,则传递给该函数的内容是参数的值,例如,f(2)a = 2; f(a)是等效的调用 - 在通过引用的呼叫中,带有变量为参数,传递的内容是对该变量的引用 - 即使函数调用的语法可以保持不变。在函数声明和/或定义中,将对通过参考逐个价值的传递或通过的规范进行规范。

参数出现在过程定义中;参数出现在过程调用中。在功能定义中f(x) = x*x变量x是一个参数;在功能调用中f(2)值2是函数的参数。宽松地,参数是一种类型,而参数是一个实例。

参数是其定义中包含的过程的内在属性。例如,在许多语言中,一个可以将两个提供的整数添加在一起并计算总和的过程将需要两个参数,一个用于每个整数。通常,可以使用任意数量的参数定义过程,或者根本没有参数。如果一个过程具有参数,则指定参数的定义部分称为其参数列表

相比之下,参数是称为过程时提供给该过程时提供的表达式,通常是一个与参数之一相匹配的表达式。与构成过程定义不变的一部分的参数不同,参数可能因呼叫而异。每次调用一个过程时,指定参数的过程调用的一部分称为参数列表

尽管参数通常也称为参数,但当在运行时调用子例程时,参数有时被视为分配给参数变量的实际值或引用。当讨论呼叫到子例程的代码时,传递到子例程中的任何值或引用是参数,而在代码中给出这些值或引用的位置是参数列表。在讨论子例程定义内的代码时,子例程参数列表中的变量是参数,而运行时参数的值是参数。例如,在C中,在处理线程时,通常会传递一个类型的参数*并将其投入到预期类型:

void ThreadFunction(void* pThreadArgument)
{
  // Naming the first parameter 'pThreadArgument' is correct, rather than
  // 'pThreadParameter'. At run time the value we use is an argument. As
  // mentioned above, reserve the term parameter for when discussing
  // subroutine definitions.
}

为了更好地理解差异,请考虑在C中写的以下功能:

int Sum(int addend1, int addend2)
{
  return addend1 + addend2;
}

该函数具有两个参数,分别为Addend1Addend2 。它将传递的值添加到参数中,并将结果返回到子例程的呼叫者(使用C编译器自动提供的技术)。

调用总和函数的代码可能看起来像这样:

int value1 = 40;
int value2 = 2;
int sum_value = Sum(value1, value2);

变量值1value2用值初始化。 Value1Value2都是在此上下文中对总和函数的参数。

在运行时,分配给这些变量的值将作为参数传递给函数总和。在总和函数中,评估参数附加1Addend2 ,分别得出参数40和2。添加了参数的值,并将结果返回到呼叫者,并将其分配给变量sum_value

由于参数和参数之间的区别,因此可以向程序提供不适当的论点。呼叫可能会提供太多或太少的论点;一个或多个参数可能是错误的类型。或可能以错误的顺序提供论点。这些情况中的任何一个都会导致参数和参数列表之间的不匹配,并且该过程通常会返回意外的答案或生成运行时错误

埃菲尔的替代惯例

Eiffel软件开发方法和语言中,术语参数参数具有公约确定的不同用途。该术语参数专门用于例程的输入,该术语参数仅用于通用类的类型参数化中。

考虑以下例程定义:

    sum (addend1: INTEGER; addend2: INTEGER): INTEGER
        do
            Result := addend1 + addend2
        end

例程sum需要两个参数addend1addend2,这被称为例行的正式论点。打电话sum指定实际参数,如下所示value1value2.

    sum_value: INTEGER
    value1: INTEGER = 40
    value2: INTEGER = 2
                
            sum_value := sum (value1, value2)

参数也被认为是正式的实际的。通用类的定义中使用了形式的通用参数。在下面的示例中HASH_TABLE被声明为具有两个正式通用参数的通用类,G代表感兴趣的数据和K表示数据的哈希密钥:

class HASH_TABLE [G, K -> HASHABLE] 
            

当班级成为客户时HASH_TABLE,正式的通用参数在通用推导中用实际通用参数代替。在以下属性声明中,my_dictionary被用作基于字符串的字典。因此,数据和关键形式的通用参数均用类型的实际通用参数代替STRING.

    my_dictionary: HASH_TABLE [STRING, STRING]

数据类型

强烈键入的编程语言中,必须在过程声明中指定每个参数的类型。使用类型推理的语言尝试从函数的身体和用法中自动发现类型。动态键入的编程语言将类型分辨率推迟到运行时。弱打字的语言几乎没有类型的分辨率,而是依靠程序员来正确。

某些语言使用特殊关键字(例如void )表示子例程没有参数。在形式类型理论中,这样的函数采用一个空的参数列表(其类型不是无效的,而是单位)。

参数通过

将参数分配给参数(称为参数传递)的确切机制取决于该参数使用的评估策略(通常按值调用),该策略可以使用关键字指定。

默认参数

一些编程语言,例如ADAC ++ClojureCommon LispFortran 90PythonRubyTCLWindows PowerShell允许在子例子的声明中明确或隐式地给出默认的参数。这允许呼叫者在调用子例程时省略该参数。如果明确给出了默认参数,则使用该值,如果呼叫者未提供该值。如果默认参数是隐式的(有时通过使用关键字(例如可选)),则该语言提供了一个众所周知的值(例如null,零,一个空字符串等),如果呼叫者未提供值。

PowerShell示例:

function doc($g = 1.21) {
    "$g gigawatts? $g gigawatts? Great Scott!"
}
PS  > doc
1.21 gigawatts? 1.21 gigawatts? Great Scott!
PS  > doc 88
88 gigawatts? 88 gigawatts? Great Scott!

默认参数可以看作是可变长度参数列表的特殊情况。

可变长度参数列表

某些语言允许定义子例程以接受可变数量的参数。对于此类语言,子例程必须通过参数列表迭代。

PowerShell示例:

function marty {
    $args | foreach { "back to the year $_" }
}
PS  > marty 1985
back to the year 1985
PS  > marty 2015 1985 1955
back to the year 2015
back to the year 1985
back to the year 1955

命名参数

某些编程语言(例如ADAWindows PowerShell )允许子例程命名参数。这允许调用代码更加自我记录。它还为呼叫者提供了更大的灵活性,通常允许更改参数的顺序,或者根据需要省略参数。

PowerShell示例:

function jennifer($adjectiveYoung, $adjectiveOld) {
    "Young Jennifer: I'm $adjectiveYoung!"
    "Old Jennifer: I'm $adjectiveOld!"
}
PS  > jennifer 'fresh' 'experienced'
Young Jennifer: I'm fresh!
Old Jennifer: I'm experienced!
PS  > jennifer -adjectiveOld 'experienced' -adjectiveYoung 'fresh'
Young Jennifer: I'm fresh!
Old Jennifer: I'm experienced!

功能语言的多个参数

lambda演算中,每个函数完全具有一个参数。通常在lambda conculus中表示具有多个参数的函数作为一个函数,该函数是第一个参数,并返回一个获取其余参数的函数。这是一种称为咖哩的转换。一些编程语言,例如MLHaskell ,遵循此方案。在这些语言中,每个函数都完全具有一个参数,并且看起来像多个参数函数的定义实际上是句法糖,用于返回函数等函数的定义等。功能应用程序在这些函数应用程序中左求解语言以及在lambda conculus中,因此,将函数应用于多个参数的应用被正确评估为应用于第一个参数的函数,然后将结果函数应用于第二个参数,等等。

输出参数

输出参数(也称为OUT参数返回参数)是用于输出的参数,而不是更常见的输入用途。使用参考参数来调用,或按值为参考的值参数来调用,因为输出参数是某些语言中的成语,尤其是C和C ++,而其他语言对输出参数具有内置支持。对输出参数的内置支持的语言包括ADA (请参阅ADA子程序), Fortran (自Fortran 90 ;请参见Fortran“ Intent”),对SQL的各种程序扩展,例如PL/SQL (请参阅PL/SQL函数)和Transact -SQLC#.NET框架Swift和脚本语言TScript (请参阅TScript函数声明)。

更确切地说,可以区分三种类型的参数或参数模式输入参数,输出参数和输入/输出参数;这些通常表示in,out, 和in out或者inout。输入参数(对输入参数的参数)必须是一个值,例如初始化变量或文字,并且不得重新定义或分配给;输出参数必须是可分配的变量,但不必初始化,任何现有值都无法访问,并且必须为值分配;并且输入/输出参数必须是初始化的,可分配的变量,并且可以选择分配一个值。在语言之间的确切要求和执行差异 - 例如,在ADA 83中,即使在分配后也只能将输出参数分配给而不是读取(在ADA 95中删除了这一点,以消除对辅助累加器变量的需求)。这些类似于表达式中的概念是r值(具有值),l值(可以分配)或r-value/l-value(具有值,可以分配),尽管这些术语在C中具有专门含义。

在某些情况下,仅区分输入和输入/输出,而输出被认为是输入/输出的特定用途,在其他情况下,仅支持输入和输出(但不支持输入/输出)。默认模式在语言之间有所不同:在Fortran 90输入/输出中为默认值,而在C#和SQL扩展输入中,输入为默认值,在TScript中,每个参数都被明确指定为输入或输出。

句法,参数模式通常用函数声明中的关键字表示,例如void f(out int x)在C#中。常规的输出参数通常放在参数列表的末尾,以明确区分它们,尽管并非总是遵循。 tscript使用一种不同的方法,其中列出了函数声明输入参数,然后输出参数,由结肠隔开(:),并且该函数本身没有返回类型,如此功能,该功能计算文本的大小分段:

TextExtent(WString text, Font font : Integer width, Integer height)

参数模式是一种表示语义的形式,表明程序员的意图并允许编译器捕获错误并应用优化 - 它们不一定暗示操作语义(参数传递实际发生)。值得注意的是,虽然可以按值按值来实现输入参数,而输出和输入/输出参数则通过参考来实现- 这是一种无需内置支持的语言实现这些模式的简单方法- 这并不总是是如何实现的实施的。在ADA '83理由中详细讨论了这种区别,该理论强调参数模式是从中实际实现的参数传递机制(通过参考或通过复制)的。例如,在C#输入参数(默认,没有关键字)中,按值传递,输出和输入/输出参数(outref)通过引用,在PL/SQL输入参数中传递(IN)通过参考,输出和输入/输出参数传递(OUTIN OUT默认情况下按值传递,结果复制回,但可以通过参考通过NOCOPY编译器提示。

句法与输出参数相似的结构是将返回值分配给具有与函数相同名称的变量。这是在PascalFortran 66Fortran 77中发现的,如在Pascal的示例中:

function f(x, y: integer): integer;
begin
    f := x + y;
end;

这在语义上有所不同,因为在调用时,简单地评估了该函数 - 它没有从调用范围中传递一个变量来存储输出。

使用

输出参数的主要用途是从函数返回多个值,而输入/输出参数的使用是使用参数传递(而不是通过共享环境,如全局变量中的共享环境)修改状态。返回多个值的一个重要用途是解决返回值和错误状态的半序列问题- 请参阅半脑返回问题:多值返回

例如,要从C中的一个函数返回两个变量,一个人可能会写下:

int width
int height;
F(x, &width, &height);

在哪里x是输入参数,widthheight是输出参数。

C和相关语言中的一个常见用例用于例外处理,其中一个函数将返回值放在输出变量中,并返回与功能是否成功相对应的布尔值。原型的例子是TryParse.NET中的方法,尤其是C#,将字符串解析为整数,返回true关于成功和false失败。这具有以下签名:

public static bool TryParse(string s, out int result)

并且可以使用如下:

int result;
if (!Int32.TryParse(s, result)) {
    // exception handling
}

类似的考虑也适用于返回几种可能类型之一的值,其中返回值可以指定类型,然后将值存储在几个输出变量之一中。

缺点

在现代编程中,通常会劝阻输出参数,从本质上讲是尴尬,令人困惑和太低的水平 - 普遍的回报值要易于理解和使用。值得注意的是,输出参数涉及具有副作用(修改输出)的功能,并且在语义上与参考文献相似,这比纯函数和值更令人困惑,并且在输出参数和输入/输出参数之间的区别可能是微妙的。此外,由于大多数参数通常是输入参数,因此输出参数和输入/输出参数是不寻常的,因此易于误解。

输出和输入/输出参数可以防止功能组成,因为输出存储在变量中,而不是在表达式的值中存储。因此,最初必须声明一个变量,然后函数链的每个步骤都必须是单独的陈述。例如,在C ++中,以下功能组成:

Object obj = G(y, F(x));

当用输出和输入/输出参数写入时,F这是一个输出参数,因为G输入/输出参数):

Object obj;
F(x, &obj);
G(y, &obj);

在具有单个输出或输入/输出参数的函数的特殊情况下,没有返回值,如果输出或输入/输出参数(或在C/C ++中,其地址)也可以由函数返回在这种情况下,上述变为:

Object obj;
G(y, F(x, &obj));

备择方案

输出参数的用例有多种选择。

为了从函数返回多个值,替代方案是返回元组。句法上,如果可以使用自动序列解开和平行分配,例如GO或Python,例如:

def f():
    return 1, 2
a, b = f()

为了返回几种类型之一的值,可以使用标记的联合;最常见的情况是无效的类型选项类型),其中返回值可能为null表示故障。对于例外处理,可以返回无效类型或提出异常。例如,在Python中,也许有:

result = parse(s)
if result is None:
    # exception handling

或者,更愚蠢的是:

try:
    result = parse(s)
except ParseError:
    # exception handling

不需要局部变量并在使用输出变量时复制返回的微观优化也可以通过足够复杂的编译器应用于常规函数和返回值。

C和相关语言中输出参数的通常替代方法是返回包含所有返回值的单个数据结构。例如,给定一个结构封装宽度和高度,可以写下:

WidthHeight width_and_height = F(x);

在面向对象的语言中,与其使用输入/输出参数,通常可以通过共享,传递对象然后突变对象来使用呼叫,尽管没有更改变量所指的对象。

也可以看看