C プログラミング入門 基幹 2 ( 月 4) 09: ポインタ 文字列 Linux にログインし 以下の講義ページを開いておくこと http://www-it.sci.waseda.ac.jp/ teachers/w483692/cpr1/ 2014-06-09 1
関数できなかったこと 配列を引数として渡す, 戻り値として返す 文字列を扱う 呼び出し元の変数を直接書き換える 例 : 2 つの変数の値を入れ替える関数 例 : scanf() はそのようなことを行う関数の一つ 複数の値を返す ポインタにより実現 2014-06-02 C プログラミング入門基幹 2 ( 月 4) 2
メモリとアドレス メモリには 1 byte ごとにアドレス ( 番地 ; address) という数値が振られている 変数は変数名を介してメモリの操作をするのでアドレスを意識することはない コンパイラが生成するマシン語はアドレスを使ってメモリ操作を行っている int year double pi char c 2014-3.14159 'C' 5000 5001 5002 5003 アドレスの例 3 2014-06-09 C プログラミング入門基幹 2 ( 月 4)
実験 : アドレスの確認 変数のアドレスはアドレス演算子 & で取得 printf() で表示するには %p を使う a at 0x7fff3bee15fc int a, b[4]; double c; b at 0x7fff3bee15e0 printf("a at %p n", &a); b[0] at 0x7fff3bee15e0 printf("b at %p n", &b); b[1] at 0x7fff3bee15e4 printf("b[0] at %p n", &b[0]); c at 0x7fff3bee15d8 printf("b[1] at %p n", &b[1]); printf("c at %p n", &c); int a int b????? このアドレスを変数に保存することで 具体的な値を double c 気にしなくてもよくなる 0x7fff3bee15fc? 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 4
ポインタ 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 5
ポインタ変数 (pointer) アドレスを格納するための変数 メモリの位置を指し示すのでポインタという 何の値を指しているか を表すために型を持つ 変数宣言時に * を名前の前につける ポインタ変数の表示方法 int a; int *p; 普通の変数の宣言 ポインタ変数の宣言 int a? 5000 int* p 5000 p = &a; ポインタ変数 p に変数 a のアドレスを代入 非常に古いプログラムでは ポインタ変数のサイズ ( アドレスのサイズ ) と int 型のサイズが同じであることを仮定して書かれてたものがある しかし 64bit アーキテクチャではまず正しく動作しない 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 6
ポインタの宣言 ポインタ変数の宣言は普通の変数の宣言と混在可能 型と * の関係に注意 int a, *p; int *q, b; int* c; int* d, e; int *r, *s; int* と続けて書くと int へのポインタ を表すように見えるので 好まれることもある しかし あくまでも変数名それぞれに * を付けるのが C の文法なので注意 int* 型ではなく int 型の変数となる 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 7
ポインタの初期化と代入 ポインタ変数の定義時に初期化が可能 通常の式では 代入演算子 = が使用可能 初期化 int a? int* p int a, *p = &a; int b, *q; q = &b; 初期化をしないポインタ変数はどこを指しているか不明 int b? int* q 代入演算子による書き換え ポインタ変数に * は付けない 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 8
ポインタ変数を通したメモリアクセス ポインタ変数にデリファレンス演算子 * を付けることで ポインタが指すメモリ領域にアクセスできる間接演算子 参照はがしなどの別名がある int a, *p = &a; a = 100; // (1) *p = 120; // (2) int a? int* p printf("%d n", a); printf("%d n", *p); (1) の代入で 100 となり (2) の代入で 120 となる デリファレンス演算子 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 9
配列のアドレス 配列変数名は 配列の先頭アドレスに変換される 0 番要素のアドレス int a[3]; int *p = a; int *q = &a[0]; int a[3] int* p int* q 1?? a[0] a[1] a[2] // p == q が成り立つ // 以下の操作はすべて同じ a[0] = -5; *a = -5; p[0] = -5; *p = -5; 配列変数名そのまま書いた場合 配列の 0 番要素のアドレス 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 10
以下の 2 つの計算だけが許されている アドレスに整数を加減 型 のサイズだけアドレスが移動する バイト単位で変化するわけではない アドレス同士の差 型 のサイズの倍数 int a[3], *p = a; アドレスの演算 説明は省略 int* p int a[3] *p = 1; a[0] a[1] a[2] p++; *p = 3; *(p+1) = 5; 5000 5004 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 11 1 3 5
配列で使う [] はアドレス演算の一種である 添字演算子 添字演算子 (subscript operator, indexer) 配列専用の記法ではない int a[3], *p = a; // 以下はすべて等価 *p = 1; *a = 1; *(p+0) = 1; *(a+0) = 1; p[0] = 1; a[0] = 1; 実は仕様上 0[p] などと書いても同じ意味になる しかし この記法が役立つことは多分ない これは 配列の宣言なので演算子ではない 先頭アドレス 一般にアドレス a と整数 n に対して が成り立つ a[n] == *(a+n) int a[3] 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 12 1?? a[0] a[1] a[2] 配列のアクセスは常に *(a+0) と解釈される
例題 : 変数の入れ替え 関数から直接変数を操作することはふつうできない int main(void) int a = 1, b = 5; int temp; // swap a and b // 一時的に別の変数に入れて行う temp = a; a = b; b = temp; // swap(a, b); この計算を関数化したい // v1 と v2 を入れ替える void swap(int v1, int v2) // この関数には 値のコピーが // 渡されるので main 関数の // a, b を書き換えることは // 絶対にできない } 仮引数名は実引数と関係がないので a, b に変えても効果はない 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 13
例題 : 変数の入れ替え 関数から直接変数を操作することはふつうできない int main(void) int a = 1, b = 5; int temp; // swap a and b // 一時的に別の変数に入れて行う // temp = a; a = b; b = temp; swap(&a, &b); // v1 と v2 を入れ替える void swap(int *v1, int *v2) } int temp; temp = *v1; *v1 = *v2; *v2 = temp; 仮引数の型をポインタにして アドレスのコピーを受け取る それぞれのアドレスを渡す ポインタの指す値を操作するので * が必要 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 14
配列を渡す 配列そのものを関数に渡す機能はない 配列の先頭のアドレスを渡すことで疑似的に可能 配列のサイズは関数からはわからない サイズなどは個別に情報として渡す int a[3] 1?? a[0] a[1] a[2] 配列の先頭アドレス (& はいらない ) func(a, 3) ポインタ変数で アドレスのコピーを受け取る void func(int *arr, int n)... 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 15
例題 : 配列の総和 配列の先頭アドレスとサイズを渡す int main(void) int a[] = 1, 2, 3, 4, 5 }; printf("%d n", sum(a, 5));... 配列の先頭アドレス (& はいらない ) // arr から n 個分の総和 int sum(int *arr, int n) int s = 0, i; for(i = 0; i < n; ++i) s += arr[i]; return s; } n 個の情報が本当にあるかどうかを確かめることはできない 配列サイズを自動的に計算するには sizeof(a)/sizeof(int) という式を使う int arr[] と書くこともできる ただし 配列として認識されるわけではないので サイズを調べることはできない 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 16
関数の引数にポインタがある場合 値のコピーではなくそのメモリの場所を直接アクセスしようとしている const キーワードによって読み込みしかしな いことを表せる // arr から n 個分の総和 int sum(const int *arr, int n) int s = 0, i; for(i = 0; i < n; ++i) s += arr[i]; return s; 読むだけ } const ポインタ int main(void) int a[] = 1, 2, 3, 4, 5 }; int s; s = sum(a, 5); // もし const がないと // この時点で配列 a が書き換え // られているかもしれない 17 2014-06-09 C プログラミング入門基幹 2 ( 月 4)
文字列 (1) 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 18
文字列 (string) 文字列は 文字型 char の列として扱われる 文字列リテラルが式中に書かれると システムによって自動的にメモリに配置され 末尾には null 文字 (' 0') が付き その先頭のアドレスを表す ナル文字と読まれるのが普通 null 文字で終わる システムのメモリ領域 ( 書き換え禁止 ) const char *str = "Hello, world! n"; 'H' 'e' 'l' 'd' '!' ' n'' 0' システム領域を書き換えることはできないので const を付ける方がよい 文字列リテラルはシステム領域のアドレスになる char* str 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 19
文字列の関数での扱い 文字列は以下のどちらかの引数で受け取る char * 文字列を書き換える const char * 文字列は読むだけである printf() のプロトタイプ int printf(const char *format,...); const は * の前ならどの位置でも可 null 文字で終わる const char *str = "Hello, world! n"; システムのメモリ領域 'H' 'e' 'l' 'd' '!' ' n' ' 0' printf("hello, world! n"); char* str printf(str); 文字列を表示 printf("%s", str); する指定... 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 20
文字配列 配列の要素として文字列を書いたもの 専用の初期化記法を用いる 配列変数の宣言 初期値として文字列リテラルを指定 char greeting[] = "Hello!"; printf("greeting: %s n", greeting); char greeting[7] Greeting: Hello! null 文字が自動的に付加される 'H' 'e' 'l' 'l' 'o' '!' ' 0' greeting[6] null 文字を入れて 7 要素の配列として確保される 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 21
文字配列の初期化 文字配列の初期化では末尾に null 文字が自動的に付加される 文字列リテラルの指すアドレスによるポインタ変数の初期化との違いに注意 char greeting[] = "Hello!"; // 以下の様に書くのと等価 // char greeting[] // = 'H', 'e', 'l', 'l', 'o', '!', ' 0' }; const char *greeting_ptr = "Hello!"; システムのメモリ領域 "Hello!" char greeting[7] "Hello!" char *greeting_ptr システム領域を書き換えることはできないので 常に const を付ける方がよい 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 22
文字配列と文字列へのポインタの違い 文字配列変数は配列の一種なので 自由に書き換えることができる 文字列へのポインタ変数は 指し示す場所が配列変数の領域なのか システム領域なのかは区別しない システムのメモリ領域 "Hello!" char greeting[7] "Hello!" char *greeting_ptr 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 23
例題 : 文字列の長さを調べる (#1) 文字列の末尾は常に null 文字があるので それが出現するまでの文字数をカウントする int length(const char *str) int len = 0; // 文字列の長さ while(str[len]!= ' 0') ++len; } } return len; 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 24
例題 : 文字列の長さを調べる (#2) 文字列の末尾は常に null 文字があるので それが出現するまでの文字数をカウントする int length(const char *str) int len = 0; // 文字列の長さ for(len = 0 ; str[len]!= ' 0'; ++len) // do nothing } } return len; 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 25
<string.h> には多くの文字列操作関数が含まれる 暗記の必要はない 次回 いくつかは練習する 文字列を扱う標準ライブラリ関数 関数名 strlen strcmp, strncmp strchr, strrchr strspn, strcspn strpbrk strstr strtok strcpy, strncpy strcat, strncat 文字列を別の文字列の末尾に連結する n 付きは 連結の長さを指定 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 26 操作 文字列の長さを計算する 文字列が同じかどうか比較する n 付きは 比較の長さを指定 文字列の先頭 ( 末尾 ) から特定の 1 文字を検索する 文字列から文字群を含む ( 含まない ) 最大の長さを調べる 文字列から文字群のいずれかを含む最初の位置を探す 文字列から指定した部分文字列の位置を探す 文字列を指定した区切り文字で区切って それぞれの位置を調べる ( トークンという ) 文字列を別の領域にコピーする n 付きは コピーの長さを指定
例題 : 文字列の長さを調べる (#3) 先ほどの例題は strlen() を使うとよい strlen() のプロトタイプ #include <string.h> size_t strlen(const char *s); メモリ上のサイズを十分表せる無符号整数型 渡したアドレスの先を書き換えないことが明示されている 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 27
次回予告 ファイルに文字列を出力する 文字列を数値に変換する 複雑な文字列を作成する 文字列をファイルから読み込む 2014-06-09 C プログラミング入門基幹 2 ( 月 4) 28