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