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