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