C言語の復習

プログラミング言語 C の復習

さて、初回はプログラミング言語 C の基本をおさらいしてしまいましょう。 よく分かっていない人は、来週までに理解を深めておくこと。

講義資料

今回は、プログラミング言語 C の基本について、簡単におさらいするのが目的です。 やや細かい話は、補助資料にまとめておいたのでこれを機会に整理をしておきたい人はどうぞ。

関数

関数:「一連の作業をひとつにまとめたもので、他から呼べるようにしたもの」

  • 関数が終わったら、呼出側(caller)に戻る
  • 関数の中で関数を実行することも可能。
#include <stdio.h>
int max(int x, int y) {
    printf("max(%d, %d) is called¥n", x,y);
    if (x<y) return y;
    return x;
}
int main(int argc, char* argv[]) {
    int a = 10;
    int b = max(a*3, a+4);
    printf("result: %d¥n", b);
    return 0;
}

関数スタック

変数と式と値

変数:値を格納する箱

  • 値: 1とか 10 とか
  • 式: i とか i+10 とか max(i*3, i+3) とか
  • 式を評価して値を求める。で、変数に格納する。

配列

配列:同じ型のデータを格納するための箱が並んだもの

  • int a[4]: int 4個分の箱を確保して、a という名前をつける。

多次元配列:配列が複数並んだもの

  • int M[3][4]: int[4]を 3つ並べたもの

配列の絵

多次元配列への典型的アクセス例。

for(i = 0; i < 3; i++) {
    for(j = 0; j < 4; j++) {
        r += M[i][j];
    }
}

構造体

構造体というのは、複数の変数を内部に格納したデータ構造です。 構造体を表す変数を確保すると、内部の変数用の領域をまとめて確保します。

各変数のことをフィールドとも呼びます。

struct record {
    int i;
    int a[4];
};
struct record r;   // 構造体変数(構造体を一つ確保)

struct.png

ポインタ

今まで、変数について述べてきましたが、 C 言語では、に対し、その場所=メモリアドレスをデータとして扱うことができ、 メモリアドレスを介して、「箱」自体の中身にもアクセスができます。

C 言語では、そのようなアドレスの事をポインタ(pointer)型として扱います。 箱の型に応じて、 int 型へのポインタdouble型へのポインタという風にポインタの型も分かれます。

struct recrod * rp;
  • *rp: rp の指す変数の中身を意味する。この場合は、構造体。
  • (*rp).i もしくは rp->i: rp の指す構造体のフィールド i へのアクセス。

typedef

struct とかポインタを使っていると、型表現がややこしくなってきます。 ということで、struct ??? や、そのポインタには名前をつけることをおすすめします。

typedef struct record {
    int i;
    int a[4];
} record_t, * record_tp;

これで、record_tstruct record という型を表し、record_tpstruct recordへのポインタを表すことができます。便利ですね。 鎌田も多用するので、なれておきましょう。

各種変数&メモリ領域

  • 局所変数 (local 変数, 正確には local auto 変数)
    • 関数内で宣言された変数。関数の引数も、局所変数の一種と理解してください。
    • 関数呼出ごとに、局所変数用の領域が確保されていく。
    • 内部構造的にも、スタックというデータ構造を用いて実現されている
      • Call Stack: 関数の呼出関係を表すスタック
      • Stack Frame: 各「関数呼出し」相当。局所変数はここに作成される。
  • ヒープ領域
    • malloc などで確保することができる領域。自分で確保&開放を管理できる。
  • 大域変数 (static 変数)
    • プログラム実行に対して、変数を一つ確保するだけ。

callStack.png

動的メモリ確保

  • ヒープ領域から必要なデータサイズを切り出してきて、変数領域として利用。
  • 主な手順は
    1. サイズを調べて(sizeof)
    2. メモリを確保して(malloc)
    3. もらったアドレスを対応するポインタ型などにキャスト
    record_tp rp2 = (record_tp)malloc(sizeof(record_t)); // 例

malloc.png

スコープ

  • スコープ: 変数と「その名前」は、 変数を宣言した { } の内部でのみ有効
    • もし、変数名がスコープ内外で衝突した場合は、内側の名前が有効(分かりにくくなるので、無闇に衝突させないように)
    • 変数は、どんどんつかってもらっても大丈夫。値に名前をつけるつもりで、わかりやすいプログラムを作りましょう。
void dfs(node_tp node) {
    ...
    if(node->visited == 1) { // ここから
        node_tp s = node->s; // 変数 s は then 節内部でのみ有効
        if(s != NULL) dfs(s);
    } // ここまで
}

関数呼出

  • 値渡し (call by value): 引数の評価値をもらってきて、変数に格納するだけ。
  • 呼出側(caller)と呼ばれた側(callee)は、別スコープ
  • 呼出側の変数にアクセスしたい場合は、ポインタをもらいましょう。
#include <stdio.h>
int max(int x, int y) {
    printf("max(%d, %d) is called¥n", x,y);
    if (x<y) return y;
    return x;
}
int main(int argc, char* argv[]) {
    int a = 10;
    int b = max(a*3, a+4);
    printf("result: %d¥n", b);
    return 0;
}

max が呼ばれた時の Call Stack の状態

callByValue.png

再帰呼出し

難しく考えるのは止めましょう。 再帰関数なんて、必要がなければ使わなければいいんです。

でも、アルゴリズムを表現するのに再帰が便利なことは多いです。 で、そんなとき、再帰関数があれば、素直に表現できてハッピーってな訳です。

  • 例: 木構造の合計の量は、「左の部分木」の重さと「右の部分木」の重さと、自分の重さを足したもの。
    • sumup(node) = sumup(node->left)+sumup(node->right)+ node->weight;

recursion.png

デバッガで理解

デバッガの使い方は、情報知能工学科プログラミング環境wikiをみてください。 授業中、デモを行う予定です。