数学およびコンピュータ プログラミングにおいて、可変個引数関数は、引数の数が不定の関数、つまり可変個の引数を受け入れる関数です。可変個引数関数のサポートは、プログラミング言語によって大きく異なります。
variadicという用語は1936年から1937年にさかのぼる新語です。 [1] この用語は1970年代まで広く使用されていませんでした。
概要
自然に可変長関数として現れる数学的および論理的演算は多数あります。たとえば、数値の合計や文字列またはその他のシーケンスの連結は、任意の数のオペランドに適用できると考えられる演算です (これらのケースでは形式的には結合法則が適用されますが)。
多くの言語で可変引数関数として実装されているもう 1 つの操作は、出力のフォーマットです。C関数printfとCommon Lisp関数はformat、このような 2 つの例です。どちらも、出力のフォーマットを指定する 1 つの引数と、フォーマットする値を提供する任意の
数の引数を取ります。
可変長関数は、一部の言語で型安全性の問題を引き起こす可能性があります。たとえば、C の は、不注意に使用すると、書式文字列攻撃printfと呼ばれるセキュリティ ホールを引き起こす可能性があります。この攻撃は、可変長関数の言語サポートが型安全ではないために可能になります。つまり、関数がスタックに配置された引数よりも多くの引数をポップしようとすることを許可し、スタックを破壊して予期しない動作を引き起こします。この結果、CERT コーディネーション センターは、 C の可変長関数を重大度の高いセキュリティ リスクと見なしています。[2]
関数型プログラミング言語では、可変引数は、関数とリスト/シーケンス/配列を引数として受け取り、そのリストで指定された引数を使用して関数を呼び出すことにより、可変数の引数を関数に渡すapply関数を補完するものと見なすことができます。 [要出典]関数型言語Haskellでは、可変引数関数は型クラス の値を返すことによって実装できます。 のインスタンスが最終的な戻り値と関数であるT場合、任意の数の追加引数が許可されます。[さらなる説明が必要]Tr(T t) => x -> tx
項書き換え研究の関連テーマは、ヘッジまたはヘッジ変数と呼ばれます。[3]引数を持つ関数である可変長引数とは異なり、ヘッジは引数のシーケンスそのものです。また、制約(「引数は 4 つまで」など)があり、可変長ではない(「引数は正確に 4 個だけ取る」など)場合もあります。そのため、可変長引数と呼ぶと誤解を招く可能性があります。ただし、これらは同じ現象を指しており、時には言い回しが混在して、可変長変数(ヘッジと同義)などの名前が付けられることがあります。変数という単語の二重の意味と、関数型プログラミングと項書き換えにおける引数と変数の違いに注意してください。たとえば、項(関数)は 3 つの変数を持つことができ、そのうちの 1 つがヘッジである場合、項は 3 つ以上の引数(ヘッジが空であることが許可されている場合は 2 つ以上)を取ることができます。
例
C言語
C言語で可変引数関数を移植可能に実装するには、標準stdarg.hヘッダーファイルが使用されます。古いヘッダーは廃止され、代わりに が採用さvarargs.hれました。C++では、ヘッダーファイルが使用されます。[4]stdarg.hcstdarg
#include <stdarg.h> #include <stdio.h>
ダブル平均( int count 、...) { va_list ap ; int j ;ダブル合計= 0 ;
va_start ( ap , count ); /* C23 より前: 最後の固定パラメータが必要 (アドレスを取得するため) */ for ( j = 0 ; j < count ; j ++ ) { sum += va_arg ( ap , int ); /* ap を次の引数に増分します。 */ } va_end ( ap );
合計/カウントを返します。
int main ( int argc , char const * argv []) { printf ( "%f \n " , average ( 3 , 1 , 2 , 3 )); 0 を返す; }
これは、任意の数の引数の平均を計算します。関数は引数の数やその型を認識しないことに注意してください。上記の関数は、型が 、でありint、引数の数が最初の引数で渡されることを想定しています (これは頻繁に使用されますが、言語やコンパイラによって強制されるものではありません)。printfなど、その他の場合には、引数の数と型は書式文字列から計算されます。どちらの場合も、正しい情報を提供するのはプログラマ次第です。 (または、や などのセンチネル値を使用して、パラメータ リストの終了を示すこともできます。) 関数が想定するよりも少ない引数が渡された場合、または引数の型が正しくない場合、無効なメモリ領域が読み取られ、書式文字列攻撃などの脆弱性につながる可能性があります。システムによっては、 をセンチネルとして使用してもこのような問題が発生することがあります。または、正しいターゲット型の専用の null ポインターを使用してそれらを回避できます。
NULLnullptrNULLnullptr
stdarg.hは型 を宣言し、、、、 のva_list4 つのマクロを定義します。 と の各呼び出しは、対応する の呼び出しと一致している必要があります。 可変引数を扱う場合、関数は通常、マクロによって操作される型
(この例では )の変数を宣言します。va_startva_argva_copyva_endva_startva_copyva_endva_listap
va_startva_listは、オブジェクトと関数の最後のパラメータへの参照(省略記号の前のパラメータ。マクロはこれを使用して位置を確認します)の2 つの引数を取ります。C23では、2 番目の引数は不要になり、可変長関数では省略記号の前の名前付きパラメータは不要になります。[注 1] [6]またはva_listで使用するためにオブジェクトを初期化します。参照が正しくない場合(最後のパラメータとは異なるパラメータへの参照、またはまったく異なるオブジェクトへの参照など)、コンパイラは通常警告を発しますが、コンパイルが正常に完了するのを妨げることはありません。va_argva_copyva_argは、オブジェクト (事前に初期化済み) と型記述子の 2 つの引数を取りますva_list。これは次の可変引数に展開され、指定された型を持ちます。 をva_arg連続して呼び出すと、各可変引数を順番に処理できます。型が正しくないか、次の可変引数がない場合は、未指定の動作が発生します。va_endは、1 つの引数、va_listオブジェクトを取ります。これはクリーンアップに役立ちます。たとえば、変数引数を複数回スキャンしたい場合、プログラマーはva_listを呼び出してオブジェクトを再初期化しva_end、もう一度を呼び出すことになりますva_start。va_copy引数を 2 つ取ります。どちらもva_listオブジェクトです。2 つ目の引数 (初期化されている必要があります) を 1 つ目の引数に複製します。「可変引数を複数回スキャンする」例に戻ると、これは、va_start最初の に対してを呼び出しva_list、次に を使用してva_copy2 つ目の引数に複製することで実現できますva_list。可変引数を と 1 つ目のva_arg引数で 1 回目にスキャンした後va_list( で破棄va_end)、プログラマーは可変引数を と 2 つ目の引数でva_arg2 回目にスキャンできますva_list。また、包含関数が戻る前に、va_end複製された に対して を呼び出す必要があります。va_list
C#の場合
C#では、キーワードを使用して可変引数関数を記述しますparams。引数には型を指定する必要がありますが、object[]すべてを包括するものとして使用できます。呼び出し側では、引数を 1 つずつリストするか、必要な要素型を持つ既存の配列を渡すかを選択できます。可変引数形式の使用は、後者の
構文糖です。
システムの使用;
class Program { static int Foo ( int a , int b , params int [] args ) { // a と b を無視して、 args の整数の合計を返します。int sum = 0 ; foreach ( int i in args ) sum += i ; return sum ; } static void Main ( string [] args ) { Console.WriteLine ( Foo ( 1 , 2 ) ); // 0 Console.WriteLine ( Foo ( 1 , 2 , 3 , 10 , 20 )); // 33 int [] manyValues = new int [] { 13 , 14 , 15 }; Console.WriteLine ( Foo ( 1 , 2 , manyValues ) ); // 42 } }
C++の場合
C++ の基本的な可変引数機能は、C のものとほぼ同じです。唯一の違いは構文で、省略記号の前のカンマを省略できます。C++ では、名前付きパラメータのない可変引数関数が許可されますが、関数の最後の固定引数の名前が必要なため、それらの引数にアクセスする方法は提供されていませんva_start。
#include <iostream> #include <cstdarg>
void simple_printf ( const char * fmt ...) // C スタイルの "const char* fmt, ..." も有効です{ va_list args ; va_start ( args , fmt ); while ( * fmt != '\0' ) { if ( * fmt == 'd' ) { int i = va_arg ( args , int ); std :: cout << i << '\n' ; } else if ( * fmt == 'c' ) { // 整数型への自動変換に注意int c = va_arg ( args , int ); std :: cout << static_cast < char > ( c ) << '\n' ; } else if ( * fmt == 'f' ) { double d = va_arg ( args , double ); std :: cout << d << '\n' ; } ++ fmt ; } va_end ( args ); }
int main () { simple_printf ( "dcff" , 3 , 'a' , 1.999 , 42.5 ); }
可変長テンプレート(パラメーター パック) は、言語に組み込まれたfold 式を使用して C++ でも使用できます。
#include <iostream>
テンプレート< typename ... Ts > void foo_print ( Ts ... args ) { (( std :: cout << args << ' ' ), ...); }
int main () { std :: cout << std :: boolalpha ; foo_print ( 1 , 3.14f ); // 1 3.14 foo_print ( "Foo" , 'b' , true , nullptr ); // Foo b true nullptr }
C++のCERTコーディング標準では、誤用リスクが低いため、Cスタイルの可変長関数よりもC++の可変長テンプレート(パラメータパック)の使用を強く推奨しています。 [7]
囲碁では
Goの可変引数関数は、任意の数の末尾引数で呼び出すことができます。[8] fmt.Printlnは一般的な可変引数関数であり、キャッチオール型として空のインターフェースを使用します。
パッケージメイン
「fmt」をインポートする
// この可変引数関数は、任意の数の int を引数として受け取ります。
func sum ( nums ... int ) { fmt . Print ( "The sum of " , nums ) // これも可変引数関数です。total := 0 for _ , num := range nums { total += num } fmt . Println ( " is" , total ) // これも可変引数関数です。}
func main () { // 可変長関数は、通常の方法で個別の引数で呼び出すことができます。sum ( 1 , 2 ) // "[1 2] の合計は 3 です" sum ( 1 , 2 , 3 ) // "[1 2 3] の合計は 6 です"
// スライスにすでに複数の引数がある場合は、
次のように func(slice...) を使用してそれらを可変長関数に適用します。nums := [] int { 1 , 2 , 3 , 4 } sum ( nums ... ) // "[1 2 3 4] の合計は 10 です" }
出力:
[1 2]の合計は3です [1 2 3]の合計は6です [1 2 3 4]の合計は10です
ジャワ島
C# と同様に、JavaObjectの型はキャッチオールとして使用できます。
public class Program { // 可変長引数メソッドは、受け取った追加の引数を配列に格納します。 // 結果として、`printArgs` は実際には 1 つのパラメーター(可変長の `String` 配列)を持つメソッドです。 private static void printArgs ( String ... strings ) { for ( String string : strings ) { System . out . println ( string ); } }
public static void main ( String [] args ) { printArgs ( "hello" ); // printArgs(["hello"]) の省略形printArgs ( "hello" , "world" ); // printArgs(["hello", "world"]) の省略形} }
JavaScriptの場合
JavaScript は可変引数の型を気にしません。
関数sum (... numbers ) {数値を返します。reduce (( a , b ) => a + b , 0 ); }
console.log ( sum ( 1,2,3 ) ) ; // 6 console.log ( sum ( 3,2 ) ) ; // 5 console.log ( sum ( ) ) ; // 0
arguments オブジェクトを使用して可変引数関数を作成することもできますが、これはfunctionキーワードを使用して作成された関数でのみ使用できます。
関数sum () { return Array . prototype . Reduce . call ( arguments , ( a , b ) => a + b , 0 ); }
console.log ( sum ( 1,2,3 ) ) ; // 6 console.log ( sum ( 3,2 ) ) ; // 5 console.log ( sum ( ) ) ; // 0
でルア
Lua関数は、キーワードを使用して他の値と同じように可変引数を他の関数に渡すことができますreturn。テーブルは、Luaバージョン5.2以上[9] table.unpack、またはLua 5.1以下[10] unpackを使用して可変引数関数に渡すことができます。可変引数を値としてテーブルを構築することにより、可変引数をテーブルとして使用することができます。
function sum (...) --... は可変引数を指定します
local sum = 0
for _ , v in pairs ({...}) do -- 可変引数を持つテーブルを作成することは、標準値を持つテーブルを作成することと同じです
sum = sum + v
end
return sum
end
values = { 1 , 2 , 3 , 4 }
sum ( 5 , table.unpack ( values )) -- 15 を返します。table.unpack は他の引数の後に記述する必要があります。そうしないと、すべての値が関数に渡されません。
function add5 (...)
return ... + 5 --これはvarargsの誤った使用法であり、最初に提供された値のみを返します
。
entrys = {}
function process_entries ()
local processes = {}
for i , v in pairs ( entries ) do
processes [ i ] = v --プレースホルダー処理コード
end
return table.unpack ( processed ) --varargとして使用できる方法ですべてのエントリを返します
end
print ( process_entries ()) --print関数はすべての可変引数を受け取り、改行で区切って標準出力に書き込みます。
パスカルでは
Pascal は、 ISO標準 7185 (“標準 Pascal”) および 10206 (“拡張 Pascal”)によって標準化されています。Pascal のどちらの標準化形式も、特定の組み込みルーチン( /および/ 、およびEP / ) を除き、可変長ルーチンをサポートしていません。
readreadLnwritewriteLn readStrwriteStr
それにもかかわらず、Pascalの方言は可変長ルーチン
に似たメカニズムを実装しています。Delphiは、最後の仮パラメータに関連付けられる可能性のあるデータ型を定義します。ルーチン定義内では、可変レコードの配列である です。[11]前述のデータ型のメンバーにより、
引数のデータ型を検査し、その後の適切な処理を行うことができます。Free Pascalコンパイラは、Delphiの可変長ルーチンもサポートしています。[12]array of const array of constarray of TVarRecVTyperecord
しかし、この実装では技術的に単一の引数、つまり が必要ですarray。Pascal では、配列は同質でなければならないという制限があります。この要件は、バリアントレコードを利用することで回避されます。GNU Pascal では、省略記号 ( ) を使用して実数の可変長引数の形式パラメータ仕様を定義しています...が、2022 年現在、これを使用するための移植可能なメカニズムは定義されていません。[13]
GNU Pascal と FreePascal の両方で、外部で宣言された関数は省略記号 ( ) を使用して可変長の形式パラメータ仕様を使用できます...。
PHPの場合
PHP は、引数が型指定されていない限り、可変引数の型を気にしません。
関数 sum ( ... $nums ) : int
{
return array_sum ( $nums );
}
エコー 合計( 1,2,3 ) ; // 6
型付き可変引数:
関数 sum ( int ... $nums ) : int
{
return array_sum ( $nums );
}
echo sum ( 1 , 'a' , 3 ); // TypeError: sum() に渡される引数 2 は int 型である必要があります (PHP 7.3 以降)
Pythonの場合
Python は可変長引数の型を気にしません。
def foo ( a , b , * args ):
print ( args ) # args はタプル (不変のシーケンス) です。
foo ( 1 , 2 ) # ()
foo ( 1 , 2 , 3 ) # (3,)
foo ( 1 , 2 , 3 , "hello" ) # (3, "hello")
キーワード引数は辞書に保存できますdef bar(*args, **kwargs)。
例:
楽楽では
Rakuでは、可変長関数を作成するパラメータの型はslurpy配列パラメータと呼ばれ、次の 3 つのグループに分類されます。
平らにしたスラーピー
これらのパラメータは単一のアスタリスク ( *) で宣言され、反復処理可能な要素 (つまり、Iterable) の 1 つ以上のレイヤーを解消することで引数をフラット化します。
sub foo ( $a , $b , * @args ) {
@argsと言います 。perl ;
}
foo ( 1 , 2 ) # []
foo ( 1 , 2 , 3 ) # [3]
foo ( 1 , 2 , 3 , "hello" ) # [3 "hello"]
foo ( 1 , 2 , 3 , [ 4 , 5 ], [ 6 ]); # [3, 4, 5, 6]
平らでないスラーピー
これらのパラメータは 2 つのアスタリスク ( ) で宣言され**、リスト内の反復可能な引数をフラット化せず、引数をほぼそのまま保持します。
sub bar ( $a、$b、 ** @args ) {
@argsと言います 。perl ;
}
bar ( 1 , 2 ); # []
bar ( 1 , 2 , 3 ); # [3]
bar ( 1 , 2 , 3 , "hello" ); # [3 "hello"]
bar ( 1 , 2 , 3 , [ 4 , 5 ], [ 6 ]); # [3, [4, 5], [6]]
文脈的スラッピー
これらのパラメータはプラス+記号 ( ) で宣言され、「単一引数ルール」が適用されます。これは、コンテキストに基づいて slurpy 引数の処理方法を決定します。簡単に言うと、単一の引数のみが渡され、その引数が反復可能である場合、その引数は slurpy パラメータ配列を埋めるために使用されます。それ以外の場合は、+@のように動作します**@(つまり、フラット化されていない slurpy)。
sub zaz ( $a , $b , + @args ) {
@argsと言います 。perl ;
}
zaz ( 1 , 2 ); # []
zaz ( 1 , 2 , 3 ); # [3]
zaz ( 1 , 2 , 3 , "hello" ); # [3 "hello"]
zaz ( 1 , 2 , [ 4 , 5 ]); # [4, 5]、単一の引数が配列を埋めます
zaz ( 1 , 2 , 3 , [ 4 , 5 ]); # [3, [4, 5]]、**@ として動作します
zaz ( 1 , 2 , 3 , [ 4 , 5 ], [ 6 ]); # [3, [4, 5], [6]]、**@ として動作します
ルビーでは
Ruby は可変長引数の型を気にしません。
def foo ( * args ) argsを印刷する終了
foo ( 1 )
# `[1]=> nil` と表示される
foo ( 1 , 2 ) # `[1, 2]=> nil` と表示される
Rustで
Rustは関数内で可変長引数をサポートしていない。代わりにマクロを使用する。[14]
macro_rules! calculate { // 単一の `eval` のパターン( eval $e : expr ) => {{ { let val : usize = $e ; // 型を整数に強制するprintln! ( "{} = {}" , stringify! { $e }, val ); } }};
// 複数の `eval` を再帰的に分解します
( eval $e : expr , $( eval $es : expr ), + ) => {{ calculate ! { eval $e } calculate ! { $( eval $es ), + } }}; }
fn main () { calculate ! { // 見てよママ!可変長引数の `calculate!`!eval 1 + 2 、eval 3 + 4 、eval ( 2 * 3 ) + 1 } }
Rustは機能スイッチを介してCの可変長システムと対話することができますc_variadic。他のCインターフェースと同様に、このシステムはRustと見なされますunsafe。[15]
Scalaで
object Program { // 可変長メソッドは、受け取った追加の引数を配列に格納します。 // 結果として、`printArgs` は実際には 1 つのパラメータ (可変長の `String` 配列)を持つメソッドです。 private def printArgs ( strings : String * ): Unit = { strings . foreach ( println ) }
def main ( args : Array [ String ]): Unit = { printArgs ( "hello" ); // printArgs (["hello"]) の省略形printArgs ( "hello" , "world" ); // printArgs(["hello", "world"]) の省略形} }
Swiftでは
Swift は可変長引数の型を考慮しますが、キャッチオールAny型も利用できます。
func greeting ( timeOfTheDay : String , names : String ...) {
// ここで、names は [String] です
print ( " \( names . count )人の人がいるようです" )
for name in names {
print ( "こんにちは\( name ) 、こんにちは\( timeOfTheDay ) " )
}
}
挨拶( timeOfTheDay : 「朝」、 名前: 「ジョセフ」、 「クララ」、 「ウィリアム」、 「マリア」)
// 出力:
// 4 人いるようです
// こんにちは、ジョセフ、おはようございます
// こんにちは、クララ、おはようございます
// こんにちは、ウィリアム、おはようございます
// こんにちは、マリア、おはようございます
Tclの場合
Tclのプロシージャまたはラムダは、最後の引数がargs: の場合に可変長引数となります。これには、残りのすべての引数のリスト(空の場合もあります)が含まれます。このパターンは、他の多くのプロシージャのようなメソッドで一般的です。[16] [17]
proc greeting { timeOfTheDay args } { puts "[llength $args] 人の人がいるようです"
foreach name $args { puts "こんにちは $name、おはようございます $timeOfTheDay" } }
挨拶「おはよう」「ジョセフ」「クララ」「ウィリアム」「マリア」
# 出力:
# 4 人いるようです
# こんにちはジョセフ、おはようございます
# こんにちはクララ、おはようございます
# こんにちはウィリアム、おはようございます
# こんにちはマリア、おはようございます
参照
- Javaプログラミング言語における可変引数
- 可変長マクロ(Cプログラミング言語)
- 可変長テンプレート
注記
- ^ 名前付きパラメータをオプションにすることは、K&Rスタイルの関数定義が削除された後のC23では不特定の数の引数を取る関数を指定する方法がなかったため必要でした。C++ではすでに同じ目的でこの構文を使用していたため、この変更は言語間の互換性を高める方法でもありました。[5]
参考文献
- ^ Henry S. LeonardとHN Goodman、「個体の計算」。1936年12月28~30日にマサチューセッツ州ケンブリッジで開催された記号論理学会第2回会議での講演要旨、[1]、Journal of Symbolic Logic 2 (1) 1937、63。
- ^ クレメンス、ベン (2014)。21世紀のC:ニュースクールからのCのヒント。オライリーメディア社、p. 224。ISBN 978-1491904442。
- ^ CLP (H): ヘッジのための制約論理プログラミング
- ^ 「<cstdarg> (stdarg.h) - C++ リファレンス」。www.cplusplus.com。
- ^ 「C23 が終了しました: メニューの内容は次のとおりです §N2975 - 可変パラメータ リストの要件を緩和」。2022 年 7 月 31 日。
- ^ Gilding, Alex; Meneide, JeanHeyd (2022-04-15). 「WG14-N2975: 可変長パラメータリストの要件の緩和、v3」(PDF)。
- ^ 「DCL50-CPP。C スタイルの可変長関数を定義しないでください」。
- ^ 「Go by Example: 可変個引数関数」。
- ^ 「Lua 5.2 リファレンスマニュアル」www.lua.org . 2023年2月5日閲覧。
- ^ 「Lua 5.1 リファレンスマニュアル」www.lua.org . 2023年2月5日閲覧。
- ^ 「パラメータ (Delphi)」. 2023年8月28日閲覧。
- ^ 「Free Pascal - リファレンスガイド」 。 2023年8月28日閲覧。
- ^ 「GNU Pascal マニュアル」 。2023年 8 月 28 日閲覧。
- ^ 「可変長引数」。Rust By Example。
- ^ "2137-variadic". Rust RFC ブック。
- ^ 「proc マニュアル ページ」。Tcl /Tk ドキュメント。
- ^ "args". TclerのWiki。
外部リンク
- 可変引数関数。120を超えるプログラミング言語での可変引数関数の実装を示すRosetta コードタスク。
- 可変引数関数 — C++ の可変引数関数に関するチュートリアル
- GNU libc マニュアル
