c,c++におけるポインタについて
まだ加筆予定
目標は
char (*p)[5]を完全に理解するぐらいまでにはポインタを理解すること。
ポインタと配列の違いは一言でいうならば、書き換えができるか否かである。
ポインタと配列の混乱の元
1:配列名aを読み込む場合、aは&a[0]と解釈される。
2:ポインタbの参照先が配列の場合、でアクセスできる。(もちろん書き込み、読み込み両方の意味で)
3:文字を''で囲めばchar型になるし、""で囲めば、char* 型になる.
(ただし、char型配列を""で初期化(代入ではない)するときは""で囲まれた文字列をいったん別のところにコピーしてから配列に
入れなおしている。)
ようするにchar型,char*型のときポインタと配列の規則がかなりややこしい。
4:プロトタイプ宣言では、引数にchar sと配列型を指定したとしても、ポインタとして扱う。
int a[5] = {0,1,2,3,4}; int* b; int c[5] = {6,7,8,9,10}; int d = 10; b; //bはint *型 b = &d; a = &d; //できない。 a = c; //できない。
これは普通にわかると思うが、仕組みを解説する。(後で)
決定的な違いはbは箱が用意されているので、書き込みできるが、aは箱が用意されていないので、上のように書き込みできない。
vc++の「式は変更可能な左辺値である必要があります」というのがそれ。
b = a; //aは読み込み可能なので、あたかもaがポインターであるかのようにふるまう。 b++; //++演算子はb(=&a[0]:つまりアドレス)をインクリメントする。(つまり、bの値を書き込む) a++; //aは配列名なので、書き込めない。よってこういう風にはかけない。
aは値を書き込めないが、1:厄介なことにaをload(読み込み)することはできる。このばあい、aは&a[0]と解釈される。
ここでよくわからなくなってきそうだが、ポインターbは書き込み可能だが、配列aは書き込みできないことを常に念頭に置こう。
b=a; b[2] = 10; //b++でアクセスするのはめんどくさい。
さらに、厄介なことに 2:ポインターbは[]演算子でアクセスできる。
//#include <stdlib.h> //C言語でのmalloc,free関数用にインクルード int a[5]; //このままつかっていいよ// int* b; int num = 5; b = new int[num]; //領域を手動で確保する。 // b = (int*)malloc(num); Cならこういう書き方。 //これでbは自由に動かせる。(配列と同じようにつかってよい) delete[] b; //bは配列として領域を格納したので、deleteではなくdelete[]で解放させる。 // free(b); C言語なら //ちなみに、calloc---cfreeは覚えなくても、malloc---freeペアを使ったほうがよい。
配列の場合、宣言すれば自動的に領域が確保されるが、ポインタの場合、宣言しても領域を確保してくれることはない。
bは自由に書き換え可能なので、なんども値を変更するときは自分で後始末しろよ、ということである。
対して、配列aは書き換えできないので、簡単に管理が可能であり、かっこを抜ければ自動的に領域が解放される仕様になっている。
ポインタの場合、手動で領域を確保しなければならないので、めんどくさいが、配列数を定数にする必要がなくなる。
newとかの基準について、
例をあげると、bというポインタが、「別の家に引っ越したい」とき、新しく「家」を作らなくてよい(newで領域を確保 しなくて良い)から、newはいらない。 一方、「家を持たない」bというポインタが、「新しく家を建てたいとき」、新しく「家」を作る必要があるので、 newする必要がある。
まあ、実際やればわかる。
int型はもういいとして、char型のポインタ関連の話に移る。これがかなりややこしい。
考えるのがめんどくさい人は、文字列を扱うのは const char* str2 = "yoro"; //constは必ず付けること で char str[5] = "yoro"; もしくは、 char str2[5] = {'y', 'o', 'r', 'o', '\0'}; //char* str2とすると、領域を確保していないのでダメ。 char* str2; //初期化をしないとき の略記バージョンだとおもってくれればよい。どうしても文字列の書き換えを許したければ、後半のように書く。
ここで、いろいろ納得できないことが出てくる。気になったら読むといい。
char* str = "yoro"; 、一番上はポインタなのに、エラーにならないんだろうか。 さっき、領域を手動で確保しなきゃいけないって言ったのに矛盾しないか? 答え:"yoro"(""で囲まれた文字列を文字列リテラルという。)自体がポインタなのだ。 文字列は一旦文字列専用の領域(メモリ上の静的な領域のどこか)に書き込まれる.(だから、constをつける) (規格上、文字列リテラルの書き換えは未定義) アドレスの値は、'y', 'o', 'r', 'o','\0' と連続してメモリ上に置かれた時の'y'へのポインタである。 ということで、 char* str; str = "yoro"; とかいても問題ない。(もっとも、constがついていない。文字列リテラルを書き換える可能性があるので、 const char str2 = str; //として隠ぺいできるがいずれにしろ、strが残るので、素直に、 char* str = "yoro"; //でいこう。 char* str; str = new char[50]; みたいなのは見たことないはずだ。
文字列リテラルがポインタなら、 char str[5] = "yoro"; は、なんでOKなの? int* b; b = new int[5]; b={1,2,3,4,5}; int a[5] = b; ではエラーなのに? 答え:配列をリテラル文字列("で囲まれた文字列")で初期化しても普通の配列になる。 char型配列を""で初期化(代入ではない)するときは ""で囲まれた文字列(文字列リテラル)をいったん別のところにコピーしてから配列に入れなおしているらしい。(未確定) だから、constはいらない。 char str2[5] = {'y', 'o', 'r', 'o', '\0'}; //とおなじこと。 char str[5]; str = "yoro"; はstrがchar[5]型の配列の配列名なので、書き込みできず、ダメ。
まとめると、
実際には、 char str[5] = "yoro"; const char* str = "yoro"; の二つの書き方を覚えておけばよい。
それでは
char *p[5]; char (*p)[5];
の説明。
http://www.geocities.jp/kuronishi_kakiteru/C/c_31.htmlのはじめ
くらいに詳しめに書いてある。
char *p[5];
char (*p)[6];
//演算子の結合順序がわからなければ、かっこを付けてしまおう。 p; //ポインタなので、書き換え可能 char(*)[6]型 &p; //アドレスは書き換えできない。安全だが、制約でもある。 *p; //(*p)はchar[6]型の配列の配列名なので、当然書き換えできない。 *(p++); //かっこを忘れずに! ポインタが移動している点以外は上と全く同じ。 *p++; //(*p)++;と読む。(*p)はchar[6]型の配列の配列名なので、書き換え不可。 //よって、++演算子(値が変更されてしまう:書き換え操作)を置けない。こういう形はありえない! (*p)[1]; //char型なので、書き換え可能。p[0][1]と同じです。 *p[1]; //結合の順序から(*p)[1]と同じ。 //ポインタは書き換え可能なので、p++とかいうアクセスのほかにp[num]みたいなアクセスもOKとしている。 p[1]; //p[1]はpをインクリメントした後の*pと同じ。
補足:constについて、
const int* pNum; //ポインタの中身が定数でなければならない。 int* const pNum = pUtint; //ポインタ自体が定数でなければならない。なので、必ず初期化が必要になる。 //配列とできることとしては変わらなくなる。 const int* const pNum = pUtint; //ポインタの中身もポインタ自体も定数でなければならない。
補足:[]について
初期化を伴う宣言のとき、第一次元をかかなくてよい
int a[] = { 1, 2, 3 }; は int a[3] = {1,2,3}; とおなじ。 おなじく、 char a[][ 2 ] = { { 1, 2 }, { 3, 4,}, { 5, 6 } }; は第一次元に3を指定したものと同じになる。
このように、は省略された配列型を思っていいが、例外がある。
4:プロトタイプ宣言では、引数にchar sと配列型を指定したとしても、ポインタとして扱う。
その前に関数についての用語を改めて確認、定義する。
定義 仮引数... 実引数... プロトタイプ宣言...
4:プロトタイプ宣言では、引数にchar s[]と配列型を指定したとしても、ポインタとして扱う。 つまり、 void func(char s[]); と void func(char* s); は全く同じ。 また、 void func( char * p[] ); は void func(char** p); と同じだし、 void func (char p[][3]); は、 void func (char (*p)[3]); とおなじ。
ちなみに、プロトタイプ宣言では配列を引数に置けないのはなぜだろうか? void func(char s[5]);という書き方も認められているが、実はこれは void func(char s[]); と全く同じになる。(ということは、配列を引数におけないし、配列の個数を5に指定しても無意味である。) なぜか、というのはもし、仮引数が配列でもOKだとするなら、 int main(void) { int s[5] = {1,2,3,4,5}; func(s); return 0; } void func(int ms[5]) { //仮に配列でプロトタイプ宣言できるとする。 // } 仮引数に値を渡すとき、int ms[5] = s; という操作が行われると考えるのが自然である。 しかし、このコードはありえない!なぜなら、msというのは書き換えできないから、&s[0](sを読み込んだときこう解釈される) という値を書き込めない。 だから、プロトタイプ宣言ではポインタを引数に置く仕様になっているのだ。 void func(char p[5][3]); //と宣言したとしても、ポインタを引数に置くという決まりにより、 void func(char p[][3]); //と全く同じになる。 関数を呼ぶときにはポインタ、もしくは配列変数ではない変数を引数にとる。
しかし、どうしても、配列しか引数に渡さない場面はそれなりにあるだろう。その時は、 配列の要素数を別の引数で指定する(sizeofで図ることはできない。) というやり方が一番簡単だ。 void func(int a[5], int n); //nは配列の要素数 aは配列の形をしているが、3番目の意味と全く同じ. void func(int a[], int n); void func(int* a, int n); //この3つは全く同じ。
配列の一次元目の要素数は、 1. 要素数を渡すのではなく、配列の最後にそれと分かるデータを入れる 2. 配列の要素数を別の引数で指定する のほうほうがあるが、2が一番良い。 void func( char * p[] ); //なら、2つ指定する必要があるし、 void func (char (*p)[3]); //なら、一つでよい。 ((*p)[3]は[3]がついているから、配列じゃないか、と混乱するかもしれないが、p自体はポインタである。 図解を参照すること。)
int aa[9]; //配列 //aa = hin; aaは書き換え可能ではないので、このコードはかけない。 //書き換えはできないが、参照はできる。この参照というのが、ポインタとの混同を引き起こさせる原因。 //参照の時はaa = &aa[0]と同じになる。 &aa; //参照時の型はint (*)[6] 書き換え不可 aa; //参照時の型はint * 書き換え不可 aa[1]; //参照時の型はint constを付けない限り書き換え可能
&という演算子について。-->後で
クラスが引数のとき、値渡しだと、クラスが大きいとき、まるまる一つコピーすることになるので、めちゃくちゃ遅くなる。 書き換えるときはポインタ渡し、書き換えないときは参照渡し。にすればよい。
swapを寝渡し、ポインタ渡し、参照渡しで実装してみよう。