プログラミング演習Ⅱ 第12回 文字列とポインタ(1) 情報・知能工学系 山本一公 kyama@tut.jp
前回の課題の解説・ポイント 課題10-1 課題10-2 ポインタ変数の扱いに慣れるのが目的 普通に平均を求めるだけ こちらも、ポインタ変数の扱いに慣れるのが目的 簡単なソートアルゴリズム 選択ソート(一番小さい数を見つけて入れ替え) バブルソート(隣合う2数を比較して入れ替え)
課題9の採点結果から 課題9-1 課題9-2 配列に対して、要素毎のアドレスではなく、 先頭アドレスのみを表示しているケースがあった OKになってない人は、テストが不十分です 自分の書いたプログラムがどういう挙動になるか、客観的に読めるようになりましょう
今日の内容 教科書 pp.248~253 文字列とポインタ 「文字列とポインタ」となっていますが、 半分は「配列とポインタ」です
文字列リテラルとポインタ 文字列リテラルは、メモリ上のどこかに確保されている 文字列リテラルを評価すると、文字列の先頭文字へのポインタが返される 配列strの最初の要素が’A’、2番目の要素が’B’、3番目の要素が’C’、4番目の要素が0で初期化される ptrには、メモリ上のどこかに確保された文字列の先頭アドレスが代入される ptrが確保される場所と文字列リテラルの場所は無関係 char str[] = “ABC”; /* &str[0] =‘A’のアドレス となる */ char *ptr = “123”; /* ptr = ‘1’のアドレス となる */
配列とポインタの共通点 配列で使う添字演算子“[” “]”をポインタに対しても使うことができる 効果も同じ ポインタを使って、配列と同じことができる この場合、ptr[0]は‘1’ 、ptr[1]は‘2’、 ptr[2]は‘3’である C言語では、配列の先頭アドレスしか見ていない(要素数は無視)ので全く同じになる char str[] = “ABC”; /* &str[0] =‘A’のアドレス となる */ char *ptr = “123”; /* ptr = ‘1’のアドレス となる */
配列とポインタの違い(1) 配列変数はconst扱いなので、代入できない ポインタ変数は代入できる 配列変数への文字列リテラルの代入は、初期化の場合のみ可能 ポインタ変数の場合は、メモリ上のどこかに確保される文字列リテラルの先頭文字へのポインタが代入されるだけなのでプログラム中でも可能(Fig.11-3) char str[] = “ABC”; char *ptr = “123”; str = “DEF”; /* エラー! */ ptr = “456”; /* 正しい! */
配列とポインタの違い(2) 配列は、初期化時に要素数を指定できるので、使用しない領域を確保しておくことが可能(Fig.11-4(a)) ポインタはあくまでもアドレスを保持するだけの単独の変数(Fig.11-4(b)) 文字列リテラルは、文字列に必要な領域しか確保されない
文字列の配列(1) 文字列の配列は、配列の配列で実現する場合と、ポインタの配列で実現する場合で(メモリ上の実体が)かなり異なる 配列による文字列の配列の場合 C言語での配列の配列(2次元配列)は、 1次元配列を2次元的に扱っているだけ char st[3][6] と定義されたものは実質 st[18] と同じ st[2][5] は st[2 * 6 + 5] としてアクセスされる すなわち、メモリ上には連続的に配置される
文字列の配列(2) ポインタによる文字列の配列の場合 Fig. 11-5 ポインタ配列の要素は、それぞれの文字列リテラルの先頭アドレスを指すだけ 文字列はそれぞれがメモリ上のどこかに存在する 実際にどこに配置されるかは処理系依存 Fig. 11-5 配列で実現する場合は、2次元配列の初期化と同様に、間に0が埋め込まれている ポインタの場合は0は入らないし、連続した領域に確保されてもいない ポインタの配列も、他の配列と同じように扱われるので、連続した領域に確保される
共通点・相違点まとめ 共通点 相違点 配列にもポインタにも添字演算子が使える 配列の配列(2次元配列)による文字列の配列 書き方も使い方も結果も同じ 区別なく使って特に問題ない 相違点 配列の配列(2次元配列)による文字列の配列 その実体が連続した領域に確保される(2次元配列なので、文字のないところには0が入る) 配列なので、文字列の代入は初期化時しかできない。文字単位の置き換えはできる。 ポインタの配列による文字列の配列 ポインタは連続した領域に確保されるが、実体は必ずしも連続した領域に確保されるわけではない 要素を別の文字列リテラルで置き換えることができる。
今週の課題 教科書p.253, List 11-5のプログラムを改造して、st[0][0]~st[2][5]、pt[0][0]~pt[2][5]の各要素の内容とアドレスを表示するようにし、「配列による文字列」と「ポインタによる文字列」のそれぞれの文字列リテラルがメモリ上でどのように配置されているか調べよ。文字列の内容はList 11-5から変更しないこと。 1次元配列の先頭アドレスをポインタ配列に代入すれば、2次元配列を実現できる。このようにして実現した2次元配列による行列の掛け算を行う関数(行列a×行列bの結果を行列ansに入れる関数。aはn×m行列、bはm×l行列、ansはn×l行列とする)void mat_mul(int *ans[], int *a[], int *b[], int n, int m, int l)を作成せよ。main関数なども作成して完成したプログラムを作ることとし、main関数の中で教科書p.103, 演習5-7の配列を与えて関数を呼び出し掛け算を計算せよ。
課題2のヒント(1) ポインタ配列を関数に渡す int main(void) { … char *ptr[3] = {“123”, “4567”, “78910”}; function(ptr); void function(char *ptr[]) { …
課題2のヒント(2) ポインタをポインタ配列に代入する int main(void) { … int a0[3], a1[3]; // 3要素の1次元配列 int *ptr[2]; // 2要素のポインタ配列 ptr[0] = a0; ptr[1] = a1; // これで2×3の行列ができる // 行列要素の代入 ptr[0][0] = 1; ptr[0][1] = 2; ptr[0][2] = 3; ptr[1][0] = 4; ptr[1][1] = 5; ptr[1][2] = 6;
課題2のヒント(3) これ何の役に立つの? 1次元配列は、malloc()関数によりプログラム中で可変長の領域を確保できるので、これを応用することによって任意の多次元可変長配列をプログラム中で作ることができる int n, m, i; int **ptr; … // n × m の2次元配列を作る ptr = (int**)malloc(sizeof(int*) * n); // n次元のポインタ配列 for (i = 0; i < n; i++) { ptr[i] = (int*)malloc(sizeof(int) * m); // m次元の実配列 }
レポートについて 電子メールで提出 提出先は prog2@slp.cs.tut.ac.jp Subjectを「プログラミング演習2 課題11提出 学籍番号・氏名 」とすること C言語ソースファイルを添付する メールの本文には何も書かなくて良いです ソースファイルの頭にコメントで以下の情報を入れる 学籍番号・氏名 プログラムの説明(どのように動くのか、工夫した点等) 実行結果(長い場合は一部)を貼る 提出締切は、1月30日(水) 12:00 (1週間後)
授業用Webサイト URL: http://www.slp.cs.tut.ac.jp/~kyama/programming2/ 課題のpdfファイルが置いてあります。 授業で使ったpptファイルを置いていきます。 質問メールは、以下のどちらかのアドレスまで kyama@tut.jp prog2@slp.cs.tut.ac.jp C-515へ直接質問しに来ても構いません