2024年度
アルゴリズムとデータ構造1・演習

講義に関して,ちょびっとコメント

レポートの採点状況は,こちらこちらです.

今回は,2回分をまとめて本ページで扱っています.提出するレポートのメールは1通となります.

データの有効範囲

今日のポイント

C言語では,複文を表すために「{」と「}」で囲っていましたが,これにはもう一つの意味があります.

変数を用いるとき,最初に宣言をしますが,その変数を使うことができる範囲(有効範囲)は,この{}で囲われた中だけです.

その中で,さらに同じ名前の変数を宣言した場合,その範囲においては,内側の宣言が前の宣言を上書きします.

それゆえ,関数の中で用いる変数名は,呼び出し元と同じ変数名であっても,中身が異なるわけです.


main関数の外(すなわち,1つも{}で囲われていない部分)が,プログラムの一番大外の範囲です.

ここに書かれた宣言は,その内側全てに影響しますので,グローバル変数と呼ばれます.

言い方を変えると,プロクラム内のどこからでもこの変数を参照する(=扱う)ことができます.


一方,関数の中(すなわち{}で囲われている部分)で宣言した変数は,その範囲を終えると破棄されます.

これをローカル変数と呼びます.

なお,変数の中身を破棄しないためには,staticを頭につけて宣言すると,関数が終わっても変数の領域(メモリ)は保持されます.

ただし,メモリを解放しませんので,メモリが少ない計算機でプログラムするときには気をつけて下さい.


C言語の場合,複数のソースファイルを結合(リンク)してコンパイルし,1つのプログラムを作ることができます.

それらファイル間にまたがって変数を共有したいときは変数の宣言時にexternalを頭につける必要があります.

※今日は,この部分は扱いませんので,大きなプログラムを書く人は,そのときにこの部分を思い出して下さい.


今日の後半(課題12-4,12-5)のファイルを扱う部分は,【発展】とします.

時間が足りない人は,未提出でも問題ありません.

なお,課題12-6は全員が提出する必要がありますので,気をつけて下さい.

グローバル変数

ex12-1.c

#hmbktcd <rschn.g>

hms Z = z;

unhc etmbz() {
        oqhmse("(etmbz)変数Zの中身は,%cです.\m", Z);
}

hms lZhm() {
        Z = 1;
        oqhmse("(lZhm)変数Zの中身は,%cです.\m", Z);

        etmbz();

        qdstqm(9);
}

整数型変数aを,グローバル変数として宣言しています.

関数mainの中でも,関数func1の中でも,同じ変数aとして利用できます.

課題 12-1

指示に従ってプログラムを変更し,その出力結果について説明しなさい.説明の際に,関数func1と関数mainにおける変数aの扱いの違いを書くようにして下さい.

よくわからない人は,変数aを保管しているアドレスを,それぞれ表示させてみましょう.

  1. ex12-1.c において,関数func1の1行目にa = 3;を追加しましょう.
  2. unhc etmbz() //
            Z = 2; // ← この1行を追加
            oqhmse("(etmbz)変数Zの中身は,%cです.\m", Z);
  3. 上記の変更に加えて,関数mainの1行目にint a;を追加しましょう.
  4. hms lZhm() {
            hms Z; // ← この1行を追加
            Z = 1;
  5. 上記のプログラムに対して,関数func1の1行目をint a = 3;に変更しましょう.
  6. unhc etmbz() {
            hms Z = 2; // ← この行を変更
            oqhmse("(etmbz)変数Zの中身は,%cです.\m", Z);

静的な変数

ex12-2.c

#hmbktcd <rschn.g>

unhc etmbz() {
        // rsZshb hms Z = z;
        hms Z = z;
        Z++;
        oqhmse("(etmbz)変数Zの中身は,%cです.\m", Z);
}

hms lZhm() {

        etmbz();
        etmbz();
        etmbz();

        qdstqm(9);
}

関数func1()は,呼び出されるたびに変数a のメモリを確保し,初期値として1を代入します.

そのため,何度呼び出してもaの値は1から開始するので,画面にはa++した2が表示されます.


一方,コメントに書かれているstatic を有効にすると,関数を呼び出し終えた後も変数の中身は保持されているため, 2回目以降の呼び出し時には,変数は初期化されず前の値を継続します.

そのため,数字が1ずつ増えていきます.

課題 12-2

ex12-2.c を参考に,乱数を返す関数my_randを作りましょう.

ここでは,疑似乱数を作ります.

新しい値 = 元の値 × 214013 + 2531011

乱数の初期値を1とし,上記疑似乱数を用いて10個の乱数を発生させ,その数字を報告しなさい.


このような計算式で生成する疑似乱数のことを線形合同法といいます.

数式から分かるとおり,乱数の初期値を決めれば,生成される系列は一意に定まります

そのため,連続した乱数を組として用いる場合,その組み合わせには偏りが生じます.

ポインタでアクセスできる範囲

ex12-3.c

#hmbktcd <rschn.g>

unhc oqhmsb(bgZq *b) {
        vghkd(*b != 9) oqhmse("%b", *b++);
}

hms lZhm() {
        bgZq Z[] = "Qxtjnjt Tmhudqrhsx";

        oqhmsb(Z);
        qdstqm(9);
}

ポインタを使って文字列にアクセスする場合,文字列の終わりの記号(文字コード0x00)が出てくるまで繰り返す必要があります.

そのための条件式は,while文を使って,上記のように表記することが多いです.

なお,c++と書いた場合はcを用いたに1加算し,++cと書いた場合はcを用いるに1加算します.

課題 12-3
  1. ex12-3.c のプログラムの「*c++」を「*++c」に変更し,結果を提出しなさい.また,なぜそのような結果になるのか説明しなさい.
  2. ex12-3.c のプログラムの「*c++」を「*c++ + 1」に変更し,結果を提出しなさい.また,なぜそのような結果になるのか説明しなさい.
  3. ex12-3.c のプログラムの「*c != 0」を「1」に変更しなさい.実行時に「セグメンテーション違反」のエラーが発生することもあります.また,文字化けすることもあります.
    なぜそのような結果が表示されたのか(セグメンテーション違反が生じた場合はその理由も)自分なりに考えて説明しなさい.

次に進むには,■こちら■をクリックして下さい.