LoginSignup
0
0

More than 1 year has passed since last update.

Cの基本14 ~関数とポインタと配列~ 備忘録

Posted at

はじめに

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


関数の引数に配列を渡す

Cでは配列に代入ができない。

#include <stdio.h>

int main(void){
    int a[10];
    int b[10] = {1, 2, 3, 4, 5};

    a = b;
    return 0;
}

上記のようなコードを書くとエラーをはく。

error: array type 'int[10]' is not assignable

代入ができないのと同じように、
配列は関数の引数として渡せない、そして戻り値も返せない。

ではなぜ! 前編の最後で扱った関数findは動いたのか!

#include <stdio.h>

int* find(int [], int);

int* find(int a[], int n){
    int i;
    for (i=0; a[i]!=-1; i++){
        if (a[i]==n)
            break;
        
    }
    return &a[i];  
}
int main(void){
    int a[] = {2, 5, 9, 3, 4, 5, 1, 6, -1};
    int* p;

    p = find(a, 4);
    
    if (*p == -1)
        printf("Not Found\n");
    else
        printf("Found %d\n", *p);
    return 0;
}

書籍によると、 Cでは関数の仮引数をint a[]のように配列として宣言すると、その要素へのポインタ型 int*aとして宣言したと解釈される。

aはポインタ変数ということになり、関数findの中ではa[i]として配列にアクセスしているということ。

「単に配列名だけを書いた式aの値は、先頭要素へのポインタ&a[0]になる」(新しいC言語の教科書 P414より)

この配列を実引数に指定してポインタを渡して、関数から配列にアクセスすることを「配列を渡す」と表現しているらしい。

引数をポインタとして扱うことを意識するとこのようにも書ける。

#include <stdio.h>

int* find(int*, int);

int* find(int* a, int n){
    while(*a != -1 && *a!=n)
        a++;
    return a;  
}
int main(void){
    int a[] = {2, 5, 9, 3, 4, 5, 1, 6, -1};
    int* p;

    p = find(a, 4);
    
    if (*p == -1)
        printf("Not Found\n");
    else
        printf("Found %d\n", *p);
    return 0;
}    // 出力は前と同様
配列の大きさを知らせる。

上記の配列aには都合よく-1なんて値が入っていたが、関数の方で配列の大きさを知るにはどうすれば良いか?
例えば以下のようなシチュエーション

void add1(int a[]){
    int i;
    for (i=0; i<N; i++)    // 配列の長さが分かるといい
        a[i]++;
    return;
}

書籍によると、配列の要素数を引数aから知る方法はないそうだ。
関数を呼ぶ側が引数として渡す必要があるらしい。
(もしかしてだからatcoderの問題には渡される配列の数が明示されているのか?)

つまりこういうこと!

#include <stdio.h>
# define NELEMS(a) (sizeof(a)/sizeof((a)[0]))

void add1(int[], int);

void add1(int a[], int n){
    int i;
    for (i=0; i<n; i++)
        a[i]++;
    return;
}
int main(void){
    int a[] = {1, 2, 3, 4, 5};
    int i;

    add1(a, NELEMS(a));

    for (i=0; i<NELEMS(a); i++)
        printf("%d ", a[i]);
    puts("");

    return 0;
}

出力は

2 3 4 5 6

受けっとたint型の配列を逆順にして並び替える関数とそのプログラム 
演習問題に対する私のコード

#include <stdio.h>
# define NELEMS(a) (sizeof(a)/sizeof((a)[0]))

void rev(int[], int);

void rev(int a[], int n){
    int temp[n];
    int j;
    j = 0;
    for (int i=0; i<n; i++)
        temp[i] = a[i];
    for (int i=n-1; i>=0; i--){
        a[i] = temp[j];
        j++;
    }
    return;
}

int main(void){
    int a[] = {1, 2, 3, 4, 5};

    rev(a, NELEMS(a));

    for(int i=0; i<NELEMS(a); i++)
        printf("%d ", a[i]);
    puts("");

    return 0;
}    // 出力は 5 4 3 2 1

関数へのポインタ

(新しいC言語の教科書 P452)

関数へのポインタを得る

関数はメモリ内にあるコードだから、そのアドレスを表すポインタを得ることができるらしい。
それを関数へのポインタと呼ぶ。

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

上記のような関数があった場合、&addのようにしてポインタを得られる。

p = &add;    // pにaddのポインタを代入

pを使ってaddを呼び出すには下記のようにする。

z = (*p)(2,3);

括弧について解説する。*p(2,3)にしてしまうと関数呼び出し演算子()の方が間接演算子より高く、グループ化がうまくいかないらしい。 注意しよう。

関数へのポインタを使うには、関数へのポインタを宣言しなければならない。
以下のようにする。

#include <stdio.h>

int add(int, int);

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

int main(void){
    int z;
    int (*p)(int, int);    // 宣言

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

以下は2つの整数を入力すると、その和、差、積、商を表示するプログラムだ。

#include <stdio.h>

int add(int, int);
int sub(int, int);
int mul(int, int);
int div(int, int);

int oper(int, int, int (*)(int, int));

int debug = 1;

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

int sub(int x, int y){
    return x - y;
}

int mul(int x, int y){
    return x * y;
}

int div(int x, int y){
    return x / y;
}

int oper(int x, int y, int (*p)(int, int)){
    int z;

    z = (*p)(x, y);
    if (debug)
        printf("%d, %d -> %d\n", x, y, z);
    return z;
}

int main(void){
    int x, y, z;

    printf("x = ");
    scanf("%d", &x);
    printf("y = ");
    scanf("%d", &y);

    z = oper(x, y, &add);
    printf("%d\n", z);
    z = oper(x, y, &sub);
    printf("%d\n", z);
    z = oper(x, y, &mul);
    printf("%d\n", z);
    z = oper(x, y, &div);
    printf("%d\n", z);
    return 0;
}

出力は

x = 5
y = 3
5, 3 -> 8    // デバッグ表示
8
5, 3 -> 2    // これも
2
5, 3 -> 15    // これも
15
5, 3 -> 1    // あれも
1

答えが重複してるように見えるのはデバッグ表示を真にしてるから。

なお今回は関数へのポインタを

z = (*p)(2,3);

のように使ったが、伝統的には

z = p(2, 3)

のように使えるらしい。規格化以前の処理系では最初に扱ったような使い方を受け付けないことがあるらしいことは書いておく。
関数へのポインタ宣言と利用の書き方に統一感を持たせるために書籍では伝統的なスタイルは採用しなかったとのこと。

関数呼び出しの評価

省く。気になったら見る。
(新しいC言語の教科書 P458)


今回まとめるにあたり2038年問題というワードに掠ったのでそのことだけ記録しておく。
全てのプログラマーが知っておくべきなんて大胆な主張があったもんで一応。

力尽きたので今日は終了。

今回の内容は富永和人氏の新しいC言語の教科書を参考に書き留めてます。

0
0
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0