int

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