これまでの振り返り

復習キーワード

1. 関数(ユーザ関数)

関数には,あらかじめ利用可能なライブラリ関数(printf(), fopen(), strlen(), sin(), cos()等)と,自分で作成するユーザー関数とがある.
ここではユーザ関数の作成方法などのまとめを記す.

関数の定義方法

関数とは,ひとまとまりの処理の塊であり,処理したいデータを引数として受け取り,計算などの処理を行い,その結果を返すための文法である.
具体的に,足し算の関数で説明を行う.

double add_calc(double x, double y)
{
    return x + y ;
}

この関数は,仮引数としてdouble型の変数 x, y を用意し,受け取った値を足したものを,関数add_calcとしての戻り値(返り値とも言う)として結果を返す.

この関数は値を返すので,関数名の前にdoubleという戻り値の型が宣言されている.
引数が不要の場合や,戻り値なしの場合は,voidと記述する.

関数の宣言方法

関数の存在を他の関数に知らせ,利用可能とするためには,そのかたちを「宣言」する必要がある. 正確にはプロトタイプ宣言という.

例えば,main関数内で上記の関数を呼び出したければ,main関数よりも上方の行に

double add_calc(double x, double y);

のように,関数名と引数および戻り値の型を宣言しておく.(ここにはセミコロンが必要)

参考:関数のプロトタイプ宣言さえあれば,関数の定義は別の.cppファイルに書かれていても良い.
つまり,.cppファイルを複数作成してそれぞれ分割してコンパイルすることができる.
この場合は,コンパイルの後の「リンク」時に,関数が全て揃っていないとエラーになる.

関数の使い方

メイン関数や関数の中で,関数名および()で囲った引数を正しく記述すれば,関数の呼び出し(=関数への処理のジャンプ)ができる.
例えば,

#include <stdio.h>

int main(void)
{
    double a = 7.8;
    double b = 2.7;

    printf("a+b= %f", add_calc(a, b) );
    return 0;
}

となる.

引数の渡し方

関数への引数の渡し方には,値渡し,アドレス渡し,参照渡しの3種類がある.
基本的には値渡しでよいが,関数に渡した引数をその関数で変更したい場合は,アドレス渡しか参照渡しを使う.
関数に配列を渡す場合は,自動的にアドレス渡しとなるため,ポインタ変数で受け取ることもできる.

2.配列と構造体

配列は「同じ型」の複数の変数を扱うための手段である.
例えば,要素数100個の倍精度実数(double)の配列は

double x[100];

のような形で表される.2次元配列であれば,

double y[100][100];

と書ける.
ちょうど1次元配列ならベクトル,2次元配列なら行列のようにみなすこともできるが, 注意しなければならないのは,配列は「変数の集合体」に過ぎず, 各要素が「1個の変数」と考えておく必要がある.
言い換えると,数学的な「ベクトル」や「行列」の性質は無く,一括での初期化や代入はできない. (従って,繰り返し文を使って,1つ1つ処理するのが基本である.)

3.ポインタ(アドレス参照)

一言で言えば,ポインタ変数は,「アドレス」を格納するための変数である.

int main(void)
{
    int x = 0, y = 0;
    int* p;

    p = &x;  /* 変数xのポインタ(アドレス)を p に代入.変数の型とポインタの型はそろえる */
    *p = 100;    /* ポインタの中身(つまり変数x)を代入 */
    y = *p +50;  /* ポインタの中身に50を足したものを変数yに代入 */
}

ポインタ変数(ここではp)を宣言するとき,pの値はアドレスそのものであり,アスタリスク演算子*を用いて*pと記述すれば,ポインタの中身が参照できる.
ただし,予め p = &x として,ポインタ p が別の変数 (ここでは,x) を指すようにしておく必要がある.

上記の例では,初めx== 0 となっており,ポインタを使って*pに100を代入すると,pの指す変数 x も100となる.
逆に,x=100;と代入すれば,xの値は100となり,*pの値も100となる.

ポインタ演算子:& は変数のアドレスを取り出す演算子,* はそのアドレスに格納されているの変数の値を取り出す演算子.

ポインタ利用のメリット

関数の引数としての利用(アドレス参照)

ポインタの応用として,「関数」の引数をポインタとする意義をもう一度考えてみよう.
毎度おなじみの,2つの値を入れ替える関数 irekae を考える.
まず,ポインタを使わないと,

#include <stdio.h>

void irekae(int, int);       /* 関数のプロトタイプ宣言では,このように型名だけで変数名を省略することもできる. */

int main(void)
{
    int a, b;

    printf("整数2つ入力");
    scanf("%d", &a );
    scanf("%d", &b );
    irekae( a, b );
    printf("a = %d , b = %d \n", a, b );
    return 0;
}

void irekae(int a, int b)
{
    int temp;

    temp = a;
    a = b;
    b = temp;
}

このプログラムを実行して,例えば100, 50と入力し,irekae関数を呼び出しても入れ替えは行われず, a = 100, b = 50 と出力される.
これは,main関数内の変数a,bに値が入力されたとき,関数の引数にそのまま「コピー」され,irekae関数内で入れ替えてもmain関数内に反映されないためである.
main関数内の変数 a, bと,irekae関数内の変数 a, b は,ローカル変数なので,同じ変数名であっても全く別物である.

では,ポインタを使ってみよう.

#include <stdio.h>

void irekae(int*, int*);      /* 変数名は省略,型名のみ. */

int main()
{
    int a, b ;
    printf("整数2つ入力");
    scanf("%d", &a );
    scanf("%d", &b );
    irekae( &a, &b );      /* アドレスを渡す */
    printf("a = %d , b = %d \n", a, b );
    return 0;
}

void irekae ( int* a , int* b)
{
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

一見,ポインタに*をつけて中身をやり取りしているから,同じことをしているように見えるかもしれないが, 先に述べたとおり,main関数内のa, bと関数内のa, bは文字は同じでも別物であるら,ポインタの意味合いを考える必要がある.

  1. メイン関数でa, bが定義され,scanfで a,b に値が格納される.
  2. irekae関数を呼び出す際,関数の引数に値そのものではなく,main関数の変数a, bの「アドレス」を渡す.
  3. irekae関数の引数はポインタなので,ポインタの指す相手を操作できる.
  4. ポインタ演算子*を用いて入れ替えを行うと,main関数の変数が書き換えられる.
  5. その結果,returnで戻り値を設定しなくても,main関数の変数a,bが正しく書き換えられる.

重要なこと

プログラミングによる課題解決において重要なことは,まず文法の理解とその具体的な使い方の習得である.
これまで学んできたC/C++言語の文法は道具の使い方であり,その先の論理の組み立て方が重要となる. 最大値,最小値の探索や,合計値・平均値の計算,文字列の処理など処理の定石やアルゴリズムについても理解するよう心がけてほしい.

将棋やスポーツでいえば,駒の動かし方や競技規則を理解しておくことは必要条件ではあるが,勝つためにはそれだけでは十分ではない.
料理でいえば,包丁や鍋の使い方の習得だけではおいしいものを作るには十分とは言えない.