43
ポインタと配列と文字列 HN:MARISHI 牛乳おいしいよ>

Tora pointer3

  • Upload
    marishi

  • View
    735

  • Download
    3

Embed Size (px)

Citation preview

ポインタと配列と文字列

HN:MARISHI

牛乳おいしいよ>

やること

● ポインタ● ポインタと配列● 文字列

ポインタとは

● メモリのアドレスメモリのアドレスを格納する変数

● バグの温床

● でもこれがないとC言語ははじまらない

アドレス メモリの内容

0x00000000

0x00000001

0x00000002

0x00000003

0x00000004

0x00000005

0x00000006

0x00000007

0x00000008

0x00000009

0x0000000A

0x0000000B

0x0000000C

0x0000000D

0x0000000E

ポインタ・・・の前に

int hoge = 12;char piyo = 'a';double fuga = 0.123456;

32ビット環境ではint型は4バイトchar型は1バイトdouble型は8バイト

アドレス メモリの内容

0x00000000

0x00000001 12(hoge)0x00000002

0x00000003

0x00000004

0x00000005

0x00000006 'a'(piyo)

0x00000007 0.123456(fuga)0x00000008

0x00000009

0x0000000A

0x0000000B

0x0000000C

0x0000000D

0x0000000E

ポインタ関連の文法

int hoge = 12;

printf(“%p\”,&hoge);//結果:0x00000001

変数のアドレスを得る&変数名

アドレス メモリの内容

0x00000000

0x000000010x00000001 12(hoge)0x00000002

0x00000003

0x00000004

0x00000005

0x00000006

0x00000007

0x00000008

0x00000009

0x0000000A

0x0000000B

0x0000000C

0x0000000D

0x0000000E

ポインタ関連の文法

int hoge = 12;int *phoge;

phoge = &hoge;

ポインタの定義型の名前 *変数の名前;

/*32ビット環境ではポインタは4バイト*/

アドレス メモリの内容

0x00000000

0x000000010x00000001 12(hoge)0x00000002

0x00000003

0x00000004

0x00000005

0x00000006 0x00000001

0x00000007

0x00000008

0x00000009

0x0000000A

0x0000000B

0x0000000C

0x0000000D

0x0000000E

ポインタ関連の文法

int hoge = 12;int *phoge;

phoge = &hoge;

printf(“%d” , *phoge);

ポインタを通して間接的に変数を見る*変数名

アドレス メモリの内容

0x00000000

0x000000010x00000001 12(hoge)0x00000002

0x00000003

0x00000004

0x00000005

0x00000006 0x00000001

0x00000007

0x00000008

0x00000009

0x0000000A

0x0000000B

0x0000000C

0x0000000D

0x0000000E

ダブルポインタの動作int hoge = 22;int *phoge;int **pphoge;

phoge = &hoge;pphoge = &phoge;

printf("%p\n" , phoge );printf("%p\n" , *pphoge );//結果:0x00000000

printf("%p\n" , &phoge );printf("%p\n" , pphoge );//結果:0x00000004

printf(“%d\n”,**pphoge);//結果:22

アドレス メモリの内容

0x00000000 22(hoge)0x00000001

0x00000002

0x00000003

0x00000004 0x00000000(phoge)0x00000005

0x00000006

0x00000007

0x00000008 0x00000004(pphoge)0x00000009

0x0000000A

0x0000000B

0x0000000C

0x0000000D

0x0000000E

ポインタの例:スワップ

swap1(int a , int b){ int tmp = b; b = a; a = tmp;}

swap2(int *a , int *b){ int tmp = *b; *b = a; *a = tmp;}

int hoge = 5;int piyo = 10;

swap1(hoge,piyo);//結果?

swap2(&hoge,&piyo);//結果?

スワップswap1(int a , int b){ int tmp = b; b = a; a = tmp;}...

int hoge = 5;int piyo = 10;//←今ココ

swap1(hoge,piyo);printf(“%d,%d”,hoge,piyo);

アドレス メモリの内容

0x00000000 5(hoge)0x00000001

0x00000002

0x00000003

0x00000004 10(piyo)0x00000005

0x00000006

0x00000007

0x00000008

0x00000009

0x0000000A

0x0000000B

0x0000000C

0x0000000D

0x0000000E

0x0000000F

スワップswap1(int a , int b)//←今ココ{ int tmp = b; b = a; a = tmp;}

...

int hoge = 5;int piyo = 10;

swap1(hoge,piyo);//←ココのprintf(“%d,%d”,hoge,piyo);

アドレス メモリの内容

0x00000000 5(hoge)0x00000001

0x00000002

0x00000003

0x00000004 10(piyo)0x00000005

0x00000006

0x00000007

0x00000008 5(a)0x00000009

0x0000000A

0x0000000B

0x0000000C 10(b)0x0000000D

0x0000000E

0x0000000F

スワップswap1(int a , int b){ int tmp = b; b = a; a = tmp;//←今ココ}

...

int hoge = 5;int piyo = 10;

swap1(hoge,piyo);//←ココのprintf(“%d,%d”,hoge,piyo);

アドレス メモリの内容

0x00000000 5(hoge)0x00000001

0x00000002

0x00000003

0x00000004 10(piyo)0x00000005

0x00000006

0x00000007

0x00000008 10(a)0x00000009

0x0000000A

0x0000000B

0x0000000C 5(b)0x0000000D

0x0000000E

0x0000000F

スワップswap1(int a , int b){ int tmp = b; b = a; a = tmp;}...

int hoge = 5;int piyo = 10;

swap1(hoge,piyo);printf(“%d,%d”,hoge,piyo);//↑今ココ

アドレス メモリの内容

0x00000000 5(hoge)0x00000001

0x00000002

0x00000003

0x00000004 10(piyo)0x00000005

0x00000006

0x00000007

0x00000008

0x00000009

0x0000000A

0x0000000B

0x0000000C

0x0000000D

0x0000000E

0x0000000F

スワップswap2(int *a , int *b){ int tmp = *b; *b = *a; *a = tmp;}...

int hoge = 5;int piyo = 10;//←今ココ

swap1(&hoge,&piyo);printf(“%d,%d”,hoge,piyo);

アドレス メモリの内容

0x00000000 5(hoge)0x00000001

0x00000002

0x00000003

0x00000004 10(piyo)0x00000005

0x00000006

0x00000007

0x00000008

0x00000009

0x0000000A

0x0000000B

0x0000000C

0x0000000D

0x0000000E

0x0000000F

スワップswap2(int *a , int *b)//←今ココ{ int tmp = *b; *b = *a; *a = tmp;}...

int hoge = 5;int piyo = 10;

swap1(&hoge,&piyo);//←ココのprintf(“%d,%d”,hoge,piyo);

アドレス メモリの内容

0x00000000 5(hoge)0x00000001

0x00000002

0x00000003

0x00000004 10(piyo)0x00000005

0x00000006

0x00000007

0x00000008 0x00000000(a)0x00000009

0x0000000A

0x0000000B

0x0000000C 0x00000004(b)0x0000000D

0x0000000E

0x0000000F

スワップswap2(int *a , int *b){ int tmp = *b; *b = *a; *a = tmp;//←今ココ}...

int hoge = 5;int piyo = 10;

swap1(&hoge,&piyo);//←ココのprintf(“%d,%d”,hoge,piyo);

アドレス メモリの内容

0x00000000 10(hoge)0x00000001

0x00000002

0x00000003

0x00000004 5(piyo)0x00000005

0x00000006

0x00000007

0x00000008 0x00000000(a)0x00000009

0x0000000A

0x0000000B

0x0000000C 0x00000004(b)0x0000000D

0x0000000E

0x0000000F

スワップswap2(int *a , int *b){ int tmp = *b; *b = *a; *a = tmp;}...

int hoge = 5;int piyo = 10;

swap1(&hoge,&piyo);printf(“%d,%d”,hoge,piyo);//↑今ココ

アドレス メモリの内容

0x00000000 10(hoge)0x00000001

0x00000002

0x00000003

0x00000004 5(piyo)0x00000005

0x00000006

0x00000007

0x00000008

0x00000009

0x0000000A

0x0000000B

0x0000000C

0x0000000D

0x0000000E

0x0000000F

ポインタを使うタイミング

● 動的メモリの管理● 複数の構造体などから同じデータを参照したい時● 関数の引数に構造体を利用する時● 関数の戻り値が2つ以上欲しい時

etc

動的メモリの確保

int num;

scanf(“%d\n”,&num);

int *ary = (int*)malloc( sizeof(int) * n );

複数の「何か」から同じデータを参照

int apple_num;//0x00334455

0x00334455

MARISHI

0x00334455

kano

アドレスさえあれば、同じデータを簡単に共有できる。

複数の「何か」から同じデータを参照

int apple_num;//0x00334455

0x00334455MARISHI

0x00334455kano

乱用すると、意図しない値の操作が行われた時に、どのプログラムが間違ってるか分かり辛い

0x00334455*apple_num -= 5

xALTx

誰だ5個も食いやがった奴は!

ククク・・・

関数の引数に構造体を使うとき

typedef struct Human{ int age; int height; int weight;} Human;

//構造体をまるごとコピーして重い。void print_human(Human h){ printf(“%d\n”,sizeof(h) ); //結果:16(環境依存}

//アドレスのみコピーvoid print_human_size_p( Human *ph){ printf(“%d\n”,sizeof(ph) ); //結果:4(環境依存}

関数の戻り値が複数欲しい時

void yanagisawa_info(int *age , int *weight)

{

*age = 22;

* weight = …//ヒミツ

}

やること

● ポインタ● ポインタと配列● 文字列

ポインタと配列

● ポインタと配列は深い関係ある● 配列で困ったときはポインタを思い出すと

納得行くことがあったり無かったり無かったり● そして配列死ねよと思う(?)

ポインタと配列の衝撃の事実

● 配列へのアクセスにはポインタも利用できる

int i;int ary[3] = {7,5,3};int *p = ary;

for(i = 0 ; i < 3 ; ++i){ printf(“%d\n” , ary[i] ); //出力結果は printf(“%d\n” , *(p+i) ); //一緒}

配列の先頭

int ary[3] = {7,5,3};int *p = ary;

配列の先頭だけ記述↓配列の先頭ポインタ

アドレス メモリの内容

0x00000000 7

0x00000001

0x00000002

0x00000003

0x00000004 5

0x00000005

0x00000006

0x00000007

0x00000008 3

0x00000009

0x0000000A

0x0000000B

0x0000000C 0x00000000

0x0000000D

0x0000000E

0x0000000F

ポインタに整数を足すと

int ary[3] = {7,5,3};int *p = ary;

printf(“%p”,ary);printf(“%p”,&ary[0]);printf(“%p”,p);//一緒

printf(“%p”,&ary[2]);printf(“%p”,p+2);//一緒

++p;printf(“%p”,&ary[1]);printf(“%p”,p);//一緒

アドレス メモリの内容

0x00000000 7

0x00000001

0x00000002

0x00000003

0x00000004 5

0x00000005

0x00000006

0x00000007

0x00000008 3

0x00000009

0x0000000A

0x0000000B

0x0000000C 0x00000000

0x0000000D

0x0000000E

0x0000000F

配列を関数の引数にする

//配列の先頭のポインタが渡される!void func( int a[]){ printf("%p\n", a );//出力:0x000000ff a[2] = 0;}

///

int ary[] = {1,2,3};printf("%p", ary );//出力:0x000000fffunc(ary);//ポインタを渡したので、関数の書き換えが反映されるprintf("%d\n",ary[2]);//出力:0

配列を関数の引数にする(応用)

//配列の先頭のポインタを渡すので、これでもいいvoid func2(int *a){ *(a+2) = 0; //a[2] = 0;//1次元の場合、これでもよい。}

///int ary[] = {1,2,3};func(ary);printf("%d\n",ary[2]);//0

2次元配列について

//一番左の要素数は省略可void func(int ary[][3]){ ary[1][1] = 2;}

int main(){ int ary[][3] = { {0,1,2}, {3,4,5} }; func(ary);}

2次元配列についての罠

void func(int ary[][3]){ ary[1][1] = 2;}

//ポインタでも扱いは同様と思ってるとvoid func(int *ary){ ary[1][1] = 2;//エラー!}

2次元配列についての罠

● 要素数がある程度分からないと要素の参照に困る

char hoge[][3] = {{'a','b','c'}, {'d','e','f'}};

//配列の最初を参照すればOKhoge[0][0] = 'e';

//配列の最初から3バイト先を//参照しなくてはいけないhoge[1][0] = 'f';

先ほどのポインタは、何バイト先を読み込めばいいか分からなかった。よってエラー

アドレス メモリの内容

0x00000000

0x00000001 'a' ( [0][0] )

0x00000002 'b' ( [0][1] )

0x00000003 'c' ( [0][2] )

0x00000004 'd' ( [1][0] )

0x00000005 'e' ( [1][2] )

0x00000006 'f' ( [1][3] )

0x00000007

0x00000008

0x00000009

0x0000000A

0x0000000B

0x0000000C

0x0000000D

0x0000000E

やること

● ポインタ● ポインタと配列● 文字列

文字列

● 文字列はchar型の配列として扱うことができる● 文字列の最後にはNULL文字がある。

char str1[5] = "abcd";char str2[5] = {'a','b','c','d','\0'};

printf("%s\n",str1);printf("%s\n",str2);

a b c d \0

文字列

● 文字列はchar型の配列として扱うことができる● 文字列の最後にはNULL文字がある

char str1[5] = "abcd";char str2[5] = {'a','b','c','d','\0'};int i;for(i=0;i<5;++i){ printf("%c",str1[i]); //一緒 printf("%c",str2[i]); //一緒}//char型、文字列として出力する時、//ヌル文字は出力されない

文字列

● 文字列の最後にはNULL文字があるので・・・

char str1[5] = "abcd";char str1[2] = '\0';

printf("%s\n",str1);

//出力:ab

a b \0 d \0

今日の確認+α

char str1[5] = "abcd";char str2[] = "abcd";char *str3 = "abcd";

どう違うか?

今日の確認+α

char str1[5] = "abcd";char str2[] = "abcd";

どちらも同じ。char型の配列、要素数5

a b c d \0

今日の確認+α

char *str3 = "abcd";

メモリ上の何処かに{a,b,c,d,\0}の配列が作られる

その配列の先頭のアドレスがstr3に格納

{a,b,c,d,\0}の書き換えなどの操作は動作未定義

今日の確認+α+β

void func1( char str1[5] ){ ... };

void func2( char str2[] ){...};

void func3( char *str3 ){...};

どう違うか?

今日の確認+α+β

void func1( char str1[5] ){ ... };void func2( char str2[] ){...};void func3( char *str3 ){...};

アドレスを受け取るのはどれも同じ。要素数の指定(一番上)は二次元以上の配列に必要となる。

例: void func4( int ary2[][5] ){...}; void func5( int ary3[][3][5]){...};

一番左の要素数は省略可。

最後に

● ポインタと配列の深い関係についてやったけど、「ポインタと配列は一緒」とか訳のわからない事をいう大人にはならないように

● printf("%c\n" , "abcde"[3] );

きもちわるいぃぃぃぃぃいいいい