可変個引数関数

ウィキペディアから、無料の百科事典
ナビゲーションにジャンプ 検索にジャンプ

数学およびコンピュータープログラミングは、可変個引数関数不定のアリティの関数です。つまり、可変数の引数を受け入れる関数です。可変個引数関数のサポートは、プログラミング言語によって大きく異なります

可変個引数という用語は、1936年から1937年にまでさかのぼる造語です[1] この用語は、1970年代まで広く使用されていませんでした。

概要

可変個引数関数として自然に出くわす数学および論理演算はたくさんあります。たとえば、数値の合計や文字列やその他のシーケンスの連結は、任意の数のオペランドに適用できると考えることができる演算です(これらの場合、正式には結合法則が適用されますが)。

多くの言語で可変個引数関数として実装されているもう1つの操作は、出力フォーマットです。C関数printfCommonLisp関数はそのformatような2つの例です。どちらも、出力のフォーマットを指定する1つの引数と、フォーマットする値を提供する 任意の数の引数を取ります。

可変個引数関数は、一部の言語で型安全性の問題を明らかにする可能性があります。たとえば、Cを慎重に使用すると、フォーマット文字列攻撃printfと呼ばれるセキュリティホールのクラスが発生する可能性があります。可変個引数関数の言語サポートはタイプセーフではないため、攻撃が可能です。これにより、関数はスタックに配置されたよりも多くの引数をスタックからポップしようとし、スタックが破損し、予期しない動作が発生します。この結果として、CERT Coordination Centerは、Cの可変個引数関数を重大度の高いセキュリティリスクと見なしています。[2]

関数型言語では、可変個引数は、関数とリスト/シーケンス/配列を引数として受け取り、そのリストで指定された引数を使用して関数を呼び出し、可変数の引数を関数に渡すapply関数を補完すると見なすことができます。[要出典]関数型言語Haskellでは、型クラス の値を返すことで可変個引数関数を実装できますTのインスタンスがT最終的な戻り値rと関数である場合(T t) => x -> t、これにより、任意の数の追加の引数が可能になりますx[さらに説明が必要]

項書き換え研究の関連する主題は、ヘッジまたはヘッジ変数と呼ばれます。[3]引数を持つ関数である可変個引数とは異なり、ヘッジは引数自体のシーケンスです。また、可変長ではない点(「正確に4つの引数を取る」など)に制約(「4つ以下の引数を取る」など)を設定することもできます。したがって、可変個引数と呼ぶと誤解を招く可能性があります。ただし、それらは同じ現象を参照しており、フレージングが混在している場合があり、可変個引数変数(ヘッジと同義)などの名前になります。変数という単語の二重の意味に注意してください関数型プログラミングと項書き換えにおける引数と変数の違い。たとえば、項(関数)は3つの変数を持つことができ、そのうちの1つはヘッジであるため、項は3つ以上の引数(またはヘッジが空である場合は2つ以上)を取ることができます。

Cで

Cプログラミング言語で可変個引数関数を移植可能に実装するために、標準stdarg.hヘッダーファイルが使用されます。古いvarargs.hヘッダーは廃止され、 。が採用されましたstdarg.hC ++では、ヘッダーファイルcstdargが使用されます。[4]

#include <stdarg.h> 
#include <stdio.h> 

二重平均int count ...){    
    va_list ap ; 
    int j ; 
    ダブルサム= 0 ; _   

    va_start ap count ); / *最後の固定パラメータが必要です(アドレスを取得するため)* /  
    for j = 0 ; j < count ; j ++ {        
        合計+ = 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関数が信じるよりも少ない引数が渡された場合、または引数のタイプが正しくない場合、メモリの無効な領域に読み込まれ、フォーマット文字列攻撃などの脆弱性が発生する可能性があります。

stdarg.hタイプを宣言し、、、、およびva_list4つのマクロを定義しva_startますおよびの各呼び出しは、の対応する呼び出しと一致する必要があります変数の引数を操作する場合、関数は通常、マクロによって操作される タイプの変数(例では)を宣言します。va_argva_copyva_endva_startva_copyva_endva_listap

  1. va_startva_listオブジェクトと関数の最後のパラメーターへの参照(省略記号の前のパラメーター。マクロはこれを使用してベアリングを取得します)の2つの引数を取ります。またはva_listで使用するためにオブジェクトを初期化しますコンパイラーは通常、参照が正しくない場合(たとえば、最後のパラメーターとは異なるパラメーターへの参照、またはまったく異なるオブジェクトへの参照)に警告を発行しますが、コンパイルが正常に完了するのを妨げることはありません。va_argva_copy
  2. va_argva_listオブジェクト(以前に初期化された)と型記述子の2つの引数を取ります。次の変数引数に展開され、指定された型になります。の連続呼び出しによりva_arg、各可変引数を順番に処理できます。タイプが正しくないか、次の変数引数がない場合、不特定の動作が発生します。
  3. va_end1つの引数、va_listオブジェクトを取ります。クリーンアップに役立ちます。たとえば、変数引数を複数回スキャンしたい場合、プログラマーはオブジェクトを呼び出してから再度呼び出すことでオブジェクトを再初期化va_listますva_endva_start
  4. va_copy2つの引数を取ります。どちらもva_listオブジェクトです。2番目(初期化されている必要があります)を最初のクローンに複製します。「変数引数を複数回スキャンする」の例に戻ると、これはva_start、最初のを呼び出してからva_list、を使用va_copyして2番目のに複製することで実現できますva_list変数引数を最初にでスキャンした後va_argva_listで破棄va_end)、プログラマーは変数引数を2回目でスキャンして2番目でスキャンすることができva_argますva_listva_endクローンを作成することを忘れないでくださいva_list

C#で

C#paramsは、キーワードを使用して可変個引数関数を記述します。object[]キャッチオールとして使用できますが 、引数には型を指定する必要があります。

 システムを使用する;

クラス プログラム
{
    static  int  Foo int  a  int  b  params  int []  args 
    { 
        // aとbを無視して、argsの整数の合計を返します。
        int  sum  =  0 ; 
        foreach  int  i  in  args 
            sum  + =  i ; 
        合計を返す ; }
    
        
    static  void  Main string []  args 
    {
        コンソールWriteLine Foo 1、2  ; // 0  
        コンソールWriteLine Foo 1、2、3、10、20 ; _  _ _  _ _  _ // 33   
    } 
}

C ++では

C ++の基本的な可変個引数機能は、Cの機能とほぼ同じです。唯一の違いは、省略記号の前のコンマを省略できる構文にあります。

#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' {      
            
//整数型への自動変換に注意してくださいintc = 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> 

テンプレート<タイプ名... 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 ++で可変個引数テンプレート(パラメーターパック)を使用することを強く推奨しています。[5]

Goで

Goの可変個引数関数は、任意の数の末尾の引数を使用して呼び出すことができます。[6] 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 ) // "[ 12]の合計は3" sum(1、2、3  // " [ 1 23 ]合計6"  
	   

	//スライスにすでに複数の引数がある場合は、次のように
	func(slice ...)を使用して//可変個引数関数に適用します。
	nums  :=  [] int { 1、2、3、4 } sum nums ... // "[1 2 34]合計10です"  }  
	 

出力:

[12]の合計は3です
[1 23]の合計は6です
[1 2 34]の合計は10です

Javaの場合

C#と同様に、 JavaObjectの型はキャッチオールとして利用できます。

public  class  Program  { 
    //可変個引数メソッドは、受け取った追加の引数を配列に格納します。
    //したがって、 `printArgs`は、実際には1つのパラメータを持つメソッドです
    。//` String`の可変長配列です。
    private  static  void  printArgs String ...  strings  { 
        for  String  string   strings  { 
            System アウトprintln 文字列); 
        } 
    }

    public  static  void  main String []  args  { 
        printArgs "hello" );           // printArgs(["hello"])
        の略printArgs "hello"  "world" );  // printArgs(["hello"、 "world"])の略
    }} 
}

JavaScriptで

JavaScriptは、可変個引数のタイプを気にしません。

関数 の合計(...数値 {数値を
    返し ます削減((a  b  =>  a  +  b  0 ); 
}

コンソールlog sum 1、2、3 ; _  _ // 6コンソールlog sum 3、2 ; // 5コンソールlog 合計()); // 0  
     
        

パスカル

Pascalには、可変個引数として定義された4つの組み込みプロシージャがあります。これらは、この特別な条件のために、コンパイラに固有のものです。これらは、、、、およびreadプロシージャですただし、プロシージャまたは関数へのデフォルトの引数を許可する代替仕様と、プロシージャまたは関数が異なるパラメータを持つことを可能にする ポリモーフィズムがあります。readlnwritewriteln

およびプロシージャread[ln]write[ln]すべて同じ形式です。

read [ln] [([file、] variable [、variable ...])];
write [ln] [([file] [、value [、value ...])];

どこ

  • fileオプションのファイル変数であり、省略した場合、デフォルトでinputforreadreadln、またはデフォルトでoutputforwritewriteln;になります。
  • variablechar(文字)、整数、実数などのスカラー(または、一部のコンパイラーでは、特定のレコード型または文字列などの配列型)です。
  • valueは変数または定数です。

例:

var 
   f  テキスト;
   ch  char ;
   n a I B  整数;
   S  文字列;

始める
    Write '結果を書き込むファイルの名前を入力してください:' ;
    readln s ;    
    割り当てf S ;
    書き直しf ;
    書く'あなたの名前は何ですか?' ;
    readln 入力S ;        
    Write 'Hello、' S '!実行する計算の数を入力してください:' ;
    writeln 出力;
    書き込み'?' ;
    readln N ;
    書き込み' n '式のそれぞれについて、 ' ;と入力します。
    write '1つ以上のスペースで区切られた2つの整数' ;
    writeln ;
    for  i  :=  1  to  N  do
    始める   
       Write 'ペア番号を入力' i '?' ;
       読み取りa b ;
       READLN ;
       WRITELN Out 'A [' a '] + B [' B '] =' A + B ;
    終了;
    close OUT ;
終了

上記の例では、コンパイラに関する限り、9行目と13行目は同じです。これは、ファイル変数がorステートメントinputによって読み込まれる場合、ファイル変数を省略できるためです。また、コンパイラは15行目と20行目を同一と見なします。これは、書き込まれるファイル変数がである場合は省略できるためです。つまり、(20行目で)引数がプロシージャに渡されないため、引数をリストする括弧は次のようになります。省略してください。26行目は、ステートメントに任意の数の引数を含めることができ、引用符で囲まれた文字列、変数、または数式の結果にすることができることを示しています。 readreadlnoutputwriteln

Object Pascalは、多態的なプロシージャと関数をサポートしています。異なるプロシージャまたは関数は同じ名前を持つことができますが、提供された引数によって区別されます。

Pascalはデフォルトの引数もサポートしており、引数の値が指定されていない場合は、デフォルト値が与えられます。

最初の例であるポリモーフィズムについては、次のことを考慮してください。

関数 add a1 a2 整数整数;  addを開始 := a1 + a2 end ;                                   
関数 add r1 r2 実数実数;   addを開始 := a1 + a2 end ;                                    
関数 add a1 整数; r2 実数実数;   開始 追加 := 実数a1 + a2 終了;                                
関数 add r1 実数a2 整数実数;   開始 追加 :=  a1 +実数a2  終了;

上記の例では、add2つの整数値で呼び出された場合、1行目で宣言された関数が呼び出されます。引数の1つが整数で、もう1つが実数の場合、3行目または4行目の関数は、どちらが整数であるかに応じて呼び出されます。両方が実数の場合、2行目の関数が呼び出されます。

デフォルトのパラメータについては、次のことを考慮してください。

const
   3  =  3 ;
var 
   K  整数; 

関数 add i1  整数 =  0 ; 
             i2  整数 =  0 ;
             i3  整数 =  0 ; 
             i4  整数 =  0 ; 
             i5  整数 =  0 ; 
             i6  整数 =  0 ;  
             i7  整数 =  0 ;  
             i8  整数 =  0  整数;
始める
   追加 :=  i1 + i2 + i3 + I4 + I5 + i6 + I7 + I8 ;
終了;

始める
   K  := 追加;  {Kは0}
   K  :=  add K 1 ;  {Kは1}
   K  =  add 1、2 ; _ {Kは3} 
   K  =  add 1、2 Three ; _ {Kは6など} 
終了

6行目(およびその下の行)で、パラメーター= 0はコンパイラーに「引数が指定されていない場合は、引数がゼロであると推定します」と指示します。19行目では、引数が指定されていないため、関数はを返します020行目では、任意の引数に数値または変数を指定でき、22行目に示されているように定数を指定できます。

PHPの場合

PHPは、引数が型指定されていない限り、可変個引数の型を気にしません。

function  sum ... $ nums  int 
{ 
    return  array_sum $ nums ); 
}

エコー 合計1、2、3 ; _  _ _  // 6 

そして、入力された可変個引数:

関数 sum int  ... $ nums  int 
{ 
    return  array_sum $ nums ); 
}

エコー 合計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 arrayパラメーターと呼ばれ、次の3つのグループに分類されます。

平らにされたslurpy

これらのパラメーターは、単一のアスタリスク(*)で宣言され、反復可能な要素の1つ以上のレイヤー(つまり、Iterables)をディゾルブすることによって引数をフラット化します。

sub  foo$ a$ b、* @args){
     say @  argsperl ;
}

foo1、2[ ] foo1、2、3 [ 3 ] foo1、2、3" hello "[ 3 " hello " ]       foo1、2、3[ 45 ]、[ 6 ]); #[3、4、5、6]



平らにされていないslurpy

これらのパラメーターは2つのアスタリスク()で宣言されており、リスト内の反復可能な引数をフラット化しませんが、引数を多かれ少なかれそのままにします。

サブバー $ a$ b、** @args){
     say @ argsperl ; 
}

バー1、2; _ 
 [               ]バー1、2、3; _ #[3 ]バー1、2、3"     こんにちは"; # [ 3 "hello"]バー1、2、3[ 4、5 ] 、[ 6 ]; #[3、[4、5]、[6]]


状況に応じた不機嫌

これらのパラメーターはプラス(+)記号で宣言され、コンテキストに基づいてslurpy引数を処理する方法を決定する単一引数ルール」を適用します。簡単に言えば、1つの引数のみが渡され、その引数が反復可能である場合、その引数は、slurpyパラメーター配列を埋めるために使用されます。それ以外の場合は、次の+@ように機能します**@(つまり、平坦化されていないスラッピー)。

sub  zaz$ a$ b、+ @args){
     say @  argsperl ;
}

zaz1、2; _ 
 [               ] zaz1、2、3; _ #[3 ] zaz1、2、3"     こんにちは"; #[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で

Rubyは可変個引数のタイプを気にしません。

def  foo * args 
  print  args 
end

foo 1 
#は `[1] => nil`を出力します

foo 1、2 ) #は`[  1、2 ] => nil`を出力します

さびで

Rustは、関数の可変個引数をサポートしていません。代わりに、マクロを使用します。[7]

macro_rules!計算{  
    //単一の `eval`のパターン
eval $ eexpr => {{       
        {{
            let valusize = $ e ; //型を整数に強制しますprintln!"{} = {}" stringify!{ $ e }、val );    
              
        }
    }};

    //複数の `eval`を再帰的に分解します
eval $ eexpr $(eval $ esexpr )、+ => {{         
        計算する{ eval $ e }    
        計算する{ $(eval $ es )、+ }    
    }};
}

fn  main (){ 
    計算する{ // maを見て!可変個引数 `計算!`!eval 1 + 2   
           
        eval 3 + 4    
        eval 2 * 3 + 1     
    }
}

c_variadicRustは、フィーチャースイッチを介してCの可変個引数システムと対話できます。他のCインターフェースと同様に、システムはunsafeRustと見なされます。[8]

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  greet timeOfTheDay  String  names  String ...) { 
    //ここでは、namesは[String]です
    
    print " \(names .count peopleがあるようです" 
    
     名前  名前 について{ 
        print "Hello \(name 、good \(timeOfTheDay " 
    } 
}

greet timeOfTheDay  "morning"  names  "Joseph"  "Clara"  "William"  "Maria" 

//出力:
// 4人いるようです
//こんにちは、ジョセフ、おはようございます
//こんにちは、クララ、おはようございます
//こんにちは、ウィリアム、おはようございます
//こんにちは、マリア、おはようございます

Tclで

Tclプロシージャまたはラムダは、最後の引数が次の場合に可変個引数にargsなります。これには、残りのすべての引数のリスト(おそらく空)が含まれます。このパターンは、他の多くの手順のような方法で一般的です。[9] [10]

proc  greet { timeOfTheDay args } {   
    「[ llength $ args]人がいるようです」 

    foreach $ args {  
        puts "Hello $ name、good $ timeOfTheDay" } } 
    


「朝」「ジョセフ」「クララ」「ウィリアム」「マリア」に挨拶     

#出力:
#4人いるようです
#こんにちはジョセフ、おはようございます
#こんにちはクララ、おはようございます
#こんにちはウィリアム、おはようございます
#こんにちはマリア、おはようございます

も参照してください

参考文献

  1. ^ ヘンリーS.レナードとHNグッドマン、個人の微積分1936年12月28〜30日にマサチューセッツ州ケンブリッジで開催されたシンボリックロジック協会の第2回会議で行われた講演の要約[1] Journal of Symbolic Logic 2(1)1937、63。
  2. ^ クレメンス、ベン(2014)。21世紀C:新しい学校からのCのヒントO'Reilly Media、Inc.p。224. ISBN 978-1491904442
  3. ^ CLP(H):ヘッジのための制約論理プログラミング
  4. ^ "<cstdarg>(stdarg.h)-C ++リファレンス"www.cplusplus.com
  5. ^ 「DCL50-CPP。Cスタイルの可変個引数関数を定義しないでください」
  6. ^ 「例を見る:可変個引数関数」
  7. ^ 「可変個引数」例による錆
  8. ^ 「2137-可変個引数」RustRFCブック
  9. ^ 「procマニュアルページ」Tcl / Tkドキュメント
  10. ^ "args"TclerのWiki

外部リンク