• 検索結果がありません。

02: 変数と標準入出力

N/A
N/A
Protected

Academic year: 2021

シェア "02: 変数と標準入出力"

Copied!
33
0
0

読み込み中.... (全文を見る)

全文

(1)

09: ポインタ・文字列

Linux にログインし、以下の講義ページを開いておく

こと

http://www-it.sci.waseda.ac.jp/teachers/w

483692/CPR1/

C プログラミング入門

基幹7 (水5)

1

2017-06-14

(2)

関数できなかったこと

配列を引数として渡す, 戻り値として返す

文字列を扱う

呼び出し元の変数を直接書き換える

例: 2 つの変数の値を入れ替える関数

例: scanf() はそのようなことを行う関数の一つ

複数の値を返す

⇒ポインタにより実現

2

(3)

メモリとアドレス

メモリには 1 byte ごとに

アドレス

(番地; address)

という

数値が振られている

地球でいえば緯度経度などの位置情報

変数は変数名を介してメモリの操作をするのでアドレスを意

識することはない

コンパイラが生成するマシン語はアドレスを使ってメモリ

操作を行っている

3

2014 -3.14159 'C' 5000 5001 5002 5003

int year

double pi

char c

(4)

実験:アドレスを確認してみよう

変数のアドレスはアドレス演算子

&

で取得

printf() で表示するには %p を使う

4

{ int a, b[4]; double c; printf("a %p\n", &a);

printf("b %p\n", &b); printf("b[0] %p\n", &b[0]); printf("b[1] %p\n", &b[1]); printf("c %p\n", &c); a 0x7fff3bee15fc b 0x7fff3bee15e0 b[0] 0x7fff3bee15e0 b[1] 0x7fff3bee15e4 c 0x7fff3bee15d8 ? int a int b[4] ? ? ? ? ? double c 0x7fff3bee15fc 普段は変数名を使い、自 動的にメモリにアクセス するので、具体的なアド レス値を見ることはほと んどない。 0x7fff3bee15d8 実は scanf で 使ったのと同じ

(5)

この実験でわかること

配列変数 b のアドレスと、先頭要素 b[0] のアドレスが同じ。

5

{ int a, b[4]; double c; printf("a %p\n", &a); printf("b %p\n", &b); printf("b[0] %p\n", &b[0]); printf("b[1] %p\n", &b[1]); printf("c %p\n", &c); a 0x7fff3bee15fc b 0x7fff3bee15e0 b[0] 0x7fff3bee15e0 b[1] 0x7fff3bee15e4 c 0x7fff3bee15d8 ? int a int b[4] ? ? ? ? ? double c &b[0] &b

(6)

例題:変数の入れ替え

関数から直接変数を操作することはふつうできない

6

int main(void) { int a = 1, b = 5; int temp; // swap a and b // 一時的に別の変数に入れて行う temp = a; a = b; b = temp; // swap(a, b); この計算を関数化したい 1 a 5 b ? temp 1 a 5 b 1 temp 5 a 5 b 1 temp 5 a 1 b 1 temp

(7)

例題:変数の入れ替え

関数から直接変数を操作することはふつうできない

7

// Swap a and b ?

void swap(int a, int b) { int temp; temp = a; a = b; b = temp; } int main(void) { int a = 1, b = 5; swap(a, b); // a, b は変わらない 1 a 5 b ? temp 1 a 5 b main swap コピー される この場所を直接 アクセスしたい 変数は関数ごとに 別々に管理されて いる

(8)

ポインタ

アドレスを格納して、メモリの内容にアクセスするための変数

(9)

ポインタ変数 (pointer)

アドレスを格納するための変数

メモリの位置を指し示すのでポインタという

「何の値を指しているか」を表すために型を持つ

変数宣言時に

*

を名前の前につける

9

{ int a; int *p; p = &a; ? int a 5000 int* p 非常に古いプログラムでは、ポインタ変数のサイズ (アド レスのサイズ) とint 型のサイズが同じであることを仮定 して書かれてたものがある。しかし、64bit アーキテク チャではまず正しく動作しない。 5000 ポインタ変数の表示方法 ポインタ変数の宣言 普通の変数の宣言 ポインタ変数 p に変数 a のアドレスを代入 具体的なア ドレス値は 必要な時以 外書かない

(10)

ポインタの宣言

ポインタ変数の宣言は普通の変数の宣言と混在可能

型と * の関係に注意

10

{ int a, *p; int *q, b; int* c; int* d, e; int *r, *s; int* と続けて書くと、「int へのポインタ」を表すよ うに見えるので、好まれることもある。 しかし、あくまでも変数名それぞれに * を付けるのが C の文法なので注意 int* 型ではなく、 int 型の変数となる a, b, e は単なる int 型

(11)

ポインタの初期化と代入

ポインタ変数の定義時に初期化が可能

通常の式では、代入演算子 = が使用可能

11

{ int a, *p = &a; int b, *q; q = &b; ? int a int* p 初期化 代入演算子による書き換え。 ポインタ変数に * は付けない ? int b int* q 初期化をしない ポインタ変数は どこを指してい るか不明

未初期化

代入後

(12)

ポインタ変数を通したメモリアクセス

ポインタ変数にデリファレンス演算子

(dereference)

*

を付け

ることで、ポインタが指すメモリ領域にアクセスできる

12

{ 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 となる デリファレンス演算子 デリファレンス演算子 間接演算子、参照はがしなどの別名がある

(13)

例題:変数の入れ替え

関数から直接変数を操作することはふつうできない

13

int main(void) { int a = 1, b = 5; int temp; // swap a and b // 一時的に別の変数に入れて行う temp = a; a = b; b = temp; // swap(a, b); この計算を関数化したい 1 a 5 b ? temp 1 a 5 b 1 temp 5 a 5 b 1 temp 5 a 1 b 1 temp

(14)

例題:変数の入れ替え

関数から直接変数を操作することはふつうできない

14

int main(void) { int a = 1, b = 5; int temp; // swap a and b // 一時的に別の変数に入れて行う noswap(a, b); // v1 と v2 を入れ替える

void noswap(int v1, int v2) { // この関数には、値のコピーが // 渡されるので、 main 関数の // a, b を書き換えることは // 絶対にできない… } 単に値をコピーして渡すだけ a, b が書き変わることはない 仮引数名は実引数と関係がないので、 a, b という名前を含め、なんでもよい。 1 5 1 5 a b v1 v2 コピー

(15)

例題:変数の入れ替え

関数から直接変数を操作することはふつうできない

15

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; } それぞれのアドレスを渡す ポインタの指す値 を操作するので、 * が必要 仮引数の型をポインタにして、 アドレスのコピーを受け取る 1 5 a b *v2 *v1

(16)

配列とポインタ

配列なんて実はただのポインタ演算だった…

(17)

配列のアドレス

配列変数名は、配列の先頭アドレスに変換される

17

{ 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 番要素のアドレス 0 番要素のアドレス なお、 &a も同じ配列の先頭を指すアドレスに変換され るが、型が異なるので後述する &a+1 と a+1 の計算が 異なる

(18)

アドレスの演算

以下の2つの計算だけが許されている

アドレスに整数を加減

• 「型」のサイズだけアドレスが移動する

• バイト単位で変化するわけではない

アドレス同士の差

• 「型」のサイズの倍数

18

1 int a[3] 3 5

a[0] a[1] a[2]

5000 5004 { int a[3], *p = a; *p = 1; p++; *p = 3; *(p+1) = 5; int* p 説明は省略

(19)

添字演算子

配列で使う

[]

はアドレス演算の一種である

添字演算子

(subscript operator, indexer)

配列専用の記法ではない

19

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] などと書い ても同じ意味になる。しかし、 この記法が役立つことは多分 ない。

(20)

配列を渡す

配列そのものを関数に渡す機能はない

配列の先頭のアドレスを渡すことで疑似的に可能

配列のサイズは関数からはわからない

サイズなどは個別に情報として渡す

20

1 int a[3] ? ?

a[0] a[1] a[2]

func(a, 3)

void func(int *arr, int n) { ... 配列の先頭アドレス (& はいらない) ポインタ変数で、 アドレスのコピー を受け取る

(21)

例題:配列の総和

配列の先頭アドレスとサイズを渡す

21

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, i; for(i = 0, s = 0; i < n; ++i) { s += arr[i]; } return s; } 配列サイズを自動的に計算するには、 sizeof(a)/sizeof(int) という式を使う 配列の先頭アドレス & はなくても可 int arr[] と書く こともできる。た だし、配列として 認識されるわけで はないので、サイ ズを調べることは できない。 n 個の情報が本当 にあるかどうかを 関数側では知るこ とができない。 個数を別途渡す。

(22)

const ポインタ

関数の引数にポインタがある場合、値のコピーではなくその

メモリの場所を直接アクセスしようとしている

const キーワードによって読み込みしかしないことを表せる

22

// 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 が書き換え //られているかもしれない…

(23)

文字列 (1)

(24)

システムのメモリ領域 (書き換え禁止)

文字列 (string)

文字列は、文字型 char の列として扱われる

文字列リテラルが式中に書かれると

システムによって自動的にメモリに配置され

末尾には null 文字 ('\0') が付き

その先頭のアドレスを表す

24

{ const char *str

= "Hello, world!\n"; 'H' 'e' 'l' 'd' '!' '\n' '\0' null 文字で 終わる 文字列リテラルはシ ステム領域のアドレ スになる char* str システム領域を書き換える ことはできないので、 const を付ける方がよい ナル文字と読まれるのが普通

(25)

システムのメモリ領域

文字列の関数での扱い

文字列は以下のどちらかの引数で受け取る

char *

文字列を書き換える

const

char * 文字列は読むだけである

25

{ const char *str = "Hello, world!\n"; printf("Hello, world!\n"); printf(str); printf("%s", str); ... 'H' 'e' 'l' 'd' '!' '\n' '\0' null 文字で 終わる char* str int printf(const char *format, ...);

printf() のプロトタイプ

文字列を表示 する指定

(26)

文字配列

配列の要素として文字列を書いたもの

専用の初期化記法を用いる

26

{

char greeting[] = "Hello!";

printf("Greeting: %s\n", greeting); Greeting: Hello! ■ 'H' 'e' 'l' 'o' '!' '\0' null 文字が自動的に 付加される 配列変数の宣言 初期値として文字列リテラルを指定 char greeting[7] greeting[6] null 文字を入れて 7 要素の 配列として確保される 'l'

(27)

文字配列の初期化

文字配列の初期化では末尾に null 文字が自動的に付加される

文字列リテラルの指すアドレスによるポインタ変数の初期化

との違いに注意

27

{

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 を付ける方がよい

(28)

文字配列と文字列へのポインタの違い

文字配列変数は配列の一種なので、自由に書き換えることが

できる

文字列へのポインタ変数は、指し示す場所が配列変数の領域

なのか、システム領域なのかは区別しない

28

システムのメモリ領域 "Hello!" "Hello!" char greeting[7] char *greeting_ptr

(29)

例題:文字列の長さを調べる (#1)

文字列の末尾は常に null 文字があるので、それが出現するま

での文字数をカウントする

29

int length(const char *str) { int len = 0; // 文字列の長さ while(str[len] != '\0') { ++len; } return len; }

(30)

例題:文字列の長さを調べる (#2)

文字列の末尾は常に null 文字があるので、それが出現するま

での文字数をカウントする

30

int length(const char *str) {

int len = 0; // 文字列の長さ

for(len = 0 ; str[len] != '\0'; ++len) {

// do nothing

}

return len; }

(31)

文字列を扱う標準ライブラリ関数

<string.h> に

は多くの文字列

操作関数が含ま

れる

暗記の必要は

ない

次回、いくつ

かは練習する

31

関数名 操作 strlen 文字列の長さを計算する strcmp, strncmp 文字列が同じかどうか比較する n 付きは、比較の長さを指定 strchr, strrchr 文字列の先頭(末尾)から特定の1文字を検索 する strspn, strcspn 文字列から文字群を含む(含まない)最大の 長さを調べる strpbrk 文字列から文字群のいずれかを含む最初の位 置を探す strstr 文字列から指定した部分文字列の位置を探す strtok 文字列を指定した区切り文字で区切って、そ れぞれの位置を調べる(トークンという) strcpy, strncpy 文字列を別の領域にコピーする n 付きは、コピーの長さを指定 strcat, strncat 文字列を別の文字列の末尾に連結する n 付きは、連結の長さを指定

(32)

例題:文字列の長さを調べる (#3)

先ほどの例題は、 strlen() を使うとよい

32

#include <string.h>

size_t strlen(const char *s);

strlen() のプロトタイプ 渡したアドレスの先 を書き換えないこと が明示されている メモリ上のサイ ズを十分表せる 無符号整数型

(33)

次回予告

ファイルに文字列を出力する

文字列を数値に変換する

複雑な文字列を作成する

文字列をファイルから読み込む

33

参照

関連したドキュメント

管理画面へのログイン ID について 管理画面のログイン ID について、 希望の ID がある場合は備考欄にご記載下さい。アルファベット小文字、 数字お よび記号 「_ (アンダーライン)

奥付の記載が西暦の場合にも、一貫性を考えて、 []付きで元号を付した。また、奥付等の数

奥付の記載が西暦の場合にも、一貫性を考えて、 []付きで元号を付した。また、奥付等の数

“〇~□までの数字を表示する”というプログラムを組み、micro:bit

ダウンロードした書類は、 「MSP ゴシック、11ポイント」で記入で きるようになっています。字数制限がある書類は枠を広げず入力してく

近年は人がサルを追い払うこと は少なく、次第に個体数が増える と同時に、分裂によって群れの数

いてもらう権利﹂に関するものである︒また︑多数意見は本件の争点を歪曲した︒というのは︑第一に︑多数意見は

その太陽黒点の数が 2008 年〜 2009 年にかけて観察されな