Cの基本15 ~関数の引数宣言省略と可変個の関数引数~ 備忘録

### はじめに
私は初心者である。この備忘録を参考にされようとしている他の初心者の方は、ここに書かれていることを鵜呑みになさらぬようお願いしたい。何か間違いを見つけた方はコメント欄でご報告頂きたい。

___

### 関数の引数宣言の省略

前編で関数へのポインタを入れる変数の宣言で、
```c
int (*p)(int, int);
```
というのがあったが、引数の型宣言は下記のように省略できるらしい。
```c
#include <stdio.h>

int add(int, int);

int add(int x, int y){
    return x + y;
}

int main(void){
    int z;
    int (*p)();    // ここ

    p = &add;
    z = (*p)(2,3);
    printf("%d\n", z);
    return 0;
}    // 出力は変わらず5
```

こうすると、勝手に実引数に対応した動作になる。
省略した方がより多くの関数に対応できて便利だ。

注意点として引数の宣言を省略すると、
コンパイラが実引数の型チェックと、仮引数の型への変換ができないらしい。

どういうことか？

仮引数を省略して書くと、仮引数の型が分からないから実引数の型から仮引数を想定するしかなくなるらしい。想定の仕方は以下だ。
* 整数型の実引数について、[整数拡張](https://qiita.com/iyoshiha/items/e97f53a364811542edc6)をした結果の型を仮引数の型とする。
*  float型の実引数については、仮引数はdouble型とする。

このルールには名前が付いていて既定の実引数拡張と呼ぶらしい。

```c
#include <stdio.h>

int f(float, short int);

int f(float f, short int s){
    return f + s;
}

int main(void){
    int (*p)();
    int n;

    p = &f;
    n = (*p)(3.0F, (short int)2);
    printf("%d\n", n);
    return 0;
}    // 2と出力
```
出力もあるが（私の環境では）同時に警告文も出た。
```
incompatible function pointer types assigning to 'int (*)()' from 'int (*)(float, short)' [-Wincompatible-function-pointer-types]
```
型省略しているから実引数拡張が起こる。
こういう時は大人しく、（関数への）ポインタの宣言時に引数をつけるのが良さそうだ。
```c
#include <stdio.h>

int f(float, short int);

int f(float f, short int s){
    return f + s;
}

int main(void){
    int (*p)(float, short int);
    int n;

    p = &f;
    n = (*p)(3.0F, (short int)2);
    printf("%d\n", n);
    return 0;
}    // 警告なし、出力5
```
諸々のエラーを起こすリスクを考えたら、引数の宣言はなるべくしときたいな。


##### 関数へのポインタの相互変換
(新しいC言語の教科書 P469)

___

### 可変個の関数引数

##### 引数の個数が可変である関数

```c
printf("Hello, world\n");
printf("Your age is %d\n", age);
```
printf関数を見てみよう。引数1つでも呼べるし2つでも呼べる。
*引数が1個以上あれば* 呼べるのだ。

このように引数が可変な関数について見ていく。

*以下のコードは、可変個のint型の引数をとりその総和を返す関数sumupとメイン。*

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

int sumup(int, ...);

int sumup(int n, ...){
    va_list ap;
    int sum = 0;

    va_start(ap, n);

    while(n-->0)
        sum += va_arg(ap, int);
    va_end(ap);
    return sum;
}

int main(void){
    int x;

    x = sumup(1, 100);
    printf("%d\n", x);
    x = sumup(3, 100, 150, 200);
    printf("%d\n", x);

    return 0;
}
```
まず関数宣言で可変個引数の場合は、`int sumup(int, ...);`
引数の可変部をピリオドで表す。関数定義の先頭でも同様。

呼ばれる側の可変個引数の関数では次のことが必要だ。
* そのファイルで`stdarg.h`を取り込む
* 引数を一つずつ取り出して処理する

引数の処理は以下のようになっている。
1. `va_list`型の変数apを宣言する
1. `va_start`マクロを使ってapを初期化する
1. `va_arg`マクロを使って引数を一つずつ取り出す
1. `va_end`マクロを使ってapの後始末をする

`va_list`型の変数は、可変個引数を扱っている状態を保持する。

仮引数の中の最後の固定引数（ここでいうn）を`va_start`マクロに指定してapを初期化。

`va_arg`はapを使って引数を一つずつ取り出し、apの状態を変えて、次の引数が取り出せるようにすると。`va_arg`を繰り返すことで、次々と引数が取れるらしい。
`va_arg(ap, int)`のように、引数の型を指定しなければならない。
(新しいC言語の教科書 P476)

関数から戻る前には,`va_end`を使って状態の後始末をするのが決まりだと。

##### 実引数拡張に注意せよ
関数宣言では可変部分の型の宣言はしてない。だから実引数拡張が起こる。

*上記で書いたsumupの浮動小数点数verを作れ　に対する私のコード*
```c
#include <stdio.h>
#include <stdarg.h>

double sumup(int, ...);

double sumup(int n, ...){
    va_list ap;
    double sum = 0.0;

    va_start(ap, n);

    while(n-->0)
        sum += va_arg(ap, double);
    va_end(ap);
    return sum;
}

int main(void){
    double x;
    float f = 100.5;
    double d = 50.5;

    x = sumup(1, f);
    printf("%f\n", x);
    x = sumup(3, f, f, d);
    printf("%f\n", x);

    return 0;
}     // 出力は100.500000と251.500000
```

仮引数が指定できないときは実引数拡張が起こるかもと忘れないようにしよう。
* 整数型の実引数について、[整数拡張](https://qiita.com/iyoshiha/items/e97f53a364811542edc6)をした結果の型を仮引数の型とする。
*  float型の実引数については、仮引数はdouble型とする。
___



キリがいいので〆る

> 今回の内容は富永和人氏の[新しいC言語の教科書](https://www.amazon.co.jp/%E6%96%B0%E3%81%97%E3%81%84C%E8%A8%80%E8%AA%9E%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8-%E5%86%A8%E6%B0%B8%E5%92%8C%E4%BA%BA-ebook/dp/B07P1TC98P)を参考に書き留めてます。