ポインタとはメモリのアドレスを指し示す針のようなものです。本で言えばしおりにあたります。ポインタを使えばポインタ名を指定するだけでそのポインタが指し示すメモリの場所がすぐに分かります。本で言えば、しおりの刺さっている所を開けばそのページがすぐに分かるということです。そのしおりのように、プログラミングで使用するポインターは必要な時にだけ使えばいいのです。どういうときにポインタが必要になるのかは後で述べます。しかし、C言語でのプログラミングの際にポインタの使用は不可欠なものです。また、あらゆる場面で大活躍します。初心者は「ポインタが無くてもどんなプログラムでも力技で組める」と思っている人が多いでしょう。これは間違いです。この理論が通用するのは精々学校の授業のような「数学的な問題解決方法を書くだけのプログラムを組んでいる期間」だけですよ。また、ポインタには「ソフトウェアの処理速度が高速になる」「プログラムの行数を大幅に減らせる」「読みやすいプログラムになる」などたくさんのメリットもあります。ここが分かればどんなプログラムでも組めるようになるでしょう。
ポインタを使う場合、次のように宣言します。
型 *ポインタ変数名
例を挙げると
char *pstr;
int *p_of_address;
float *pdata;
という風に宣言します。(変数名の頭にポインタの頭文字 p を付けると私達にとって分かりやすくなります。)
変数名は例えれば「しおりの絵柄等」しおりそのものの目印(特徴)です。
では、こういう風に宣言されたポインタ変数には一体何が入るのでしょうか?ポインタ変数には、メモリ上のアドレスの値が一つだけ入ります。二つ以上は入れられません。
しかし、宣言したばかりではどこを指しているのか全く分かりません。とんでもないところを指している可能性もあります。(OSの中核部とか)また、どこを指しているか分からないポインタをそのまま使うことはありません。最初に、ポインタが指し示す位置を設定してやる必要があります。「位置を設定」ということは、指し示すアドレスを代入するということです。
例えば、次のような変数があったとします。
char string[10];
int address;
float data;
この変数達のメモリ上でのアドレスをポインタに代入(設定)するには
pstr = &string[0];
p_of_address = &address;
pdata = &data;
と、見てもらったら分かるとおり、&を変数の頭に付けて修飾すればアドレスがポインタに代入されます。
この&をアドレス演算子と言います。
普通の変数の頭に&を付けると、その変数がメモリ上に存在するアドレスを意味するようになります。
よって、
data = 5
のとき、
&data は 5ではなく、6208 とか、アドレスを意味する値となります。(どういう値を取るかは全く未知の世界です。)このアドレスの値そのものはプログラマーが意識する必要は全くありません(特殊なプログラムを作る時は必要がありますが)。このアドレスの値がどうだからといって関係はないのです。とにかく、上の例では
pdata が 変数 data のアドレスを指し示している
ということが重要なのです。上の例で
pstr = &string[0];
これは文字列を扱う時の場合です。文字列を扱う時にポインタを利用する機会が多いのが事実です。
また、上の例では
pstr = string;
とすることもでき、この方が一般的です。配列変数の変数名だけを記述したものは、配列の先頭のアドレスを意味します。このときにかぎり、&は不要です。
string[2] に対してポインタを設定したい場合はどうすればいいのでしょうか?
pstr = &string[2];
こうすれば、ポインタはstring[2] の存在するアドレスを指し示すようになります。
ポインタに対する演算は、一般に、ポインタが指し示しているメモリのアドレス(位置)を前後に移動するために使います。
加算すると、ポインタはメモリの後方へ進み、減算すると前方に戻ります。
例えば、
pstr = string;
pstr++;
とすると pstr は string[1] を指し示します。
pstr = string;
pstr++;
pstr--;
とすると pstr は string[0] を指し示します。つまり、元に戻ってきます。
pstr = string;
pstr+= 5;
pstr-= 1;
とすると pstr は string[4] を指し示します。また次の例ではどうでしょうか?
pstr = string;
pstr++; //string[1]
pstr--; //string[0]
pstr--; //string[-1]?????
こうすると、大変なことになってしまいます。string[-1]というものは存在しませんので、最悪コンピュータが止まってしまう可能性もあります。つまり、安全を保障された
string[0]〜string[9]の範囲を超えてしまうと何が起こるか分からないということです。よって、ポインタの操作は十分注意を払う必要があります。
(よくWindowsで一般保護違反とかいうメッセージが出て強制終了されますが、そこで「詳細」を押した時に「○●のページ違反です」とかいうメッセージが表示された時はこういう限られた範囲を逸脱したポインタ操作が原因です。)またUnix系で「core
dump」がなんちゃらとか表示された場合も大抵同じ原因です。
ポインタの演算には加減算しかありません。(メモリのアドレスに対する乗算、除算は無意味なものです。)
加減算とは、先ほども述べたようにポインタの移動です。一度移動したポインタはもう一度位置を初期化しない限り戻ってくることはありません。進めば進みっぱなしです。
さて、ポインタを宣言する時に型を指定しますが、この進む距離がこの型に関係してきます。
C言語で扱える型は数種類あり、メモリを確保する範囲が違います。
int 型 Windows系では 4バイト
double型 Windows系では 8 バイト
char型は 1バイト
このように、型によって必要となるメモリのサイズが違います。
char | ||||||||||||||||
int | ||||||||||||||||
double |
メモリのイメージは図で表わすと上のような感じです。
さて、
ポインタ pstr の加減算による移動距離はどのようにして決めるのでしょうか?この中にポインタの型を宣言時に決める答えがあります。
次のような場合を想定します。
int table[4];
もしint 型の配列要素の参照 にchar型のポインタを使ってみるとどうなるでしょうか?
次のようなプログラムでポインタを動かしてみます。
char *p;
(1) p = (char *)table; (char型ポインタで扱うために型キャスト。但しこういう変換は大抵の場合危険を伴います)
(2) p++;
(3) p+= 3;
次の図を見てください。
(1)
char | ||||||||||||||||
int | table[0] | table[1] | table[2] | table[3] | ||||||||||||
double | ||||||||||||||||
p | ↑ |
(2)
char | ||||||||||||||||
int | table[0] | table[1] | table[2] | table[3] | ||||||||||||
double | ||||||||||||||||
p | ↑ |
あれれ??? p++ が table[1] じゃありませんね・・・。 変なところを指しています。(このページ一番下・解説 「ポインタ遊び」を参照)
(3)
char | ||||||||||||||||
int | table[0] | table[1] | table[2] | table[3] | ||||||||||||
double | ||||||||||||||||
p | ↑ |
結果的に p + 4 の位置が table[1] を指し示ています。
分かりましたでしょうか?ポインタの型を宣言したからと言って、ポインタの中身が int であるとか、そう言う意味ではないのです。中身はあくまでメモリ上のポインタが指し示すアドレスです。この型がポインタを移動する際の移動距離(●○バイトという感じで)を決定するのです。では、ポインタの(アドレスを格納する)サイズはどれだけあるのか?といいますと、処理系に大きく左右されますが、Windowsでは16ビットまたは32ビットとなっています。
****************************
char *pstr; int *p_of_address; float *pdata; **************************** char string[10]; int address; float data; **************************** pstr = &string[0]; p_of_address = &address; pdata = &data; **************************** |
ポインタがアドレスを指し示すものだという事は分かってもらえたと思います。
では、そのポインタが指し示す先にあるデータは何であるのか?ということを参照する必要性がプログラミングをしていく上であります。
ポインタの真髄は変数名(上の例では string,data など)を利用しなくてもポインタ名を使えばそこのアドレスにあるデータにアクセスできるということにあります。
pstr これはアドレスを意味します。
*pstr これはアドレスに存在するデータを意味します
ポインタ名を*で修飾することでポインタが指し示すアドレスにあるデータを参照できます。この*を間接演算子と呼びます。
乗算の*とは意味が違います。(プログラムを書くときは混合しないように!(^_^;)
次のようなプログラムがあります。
int i;
int numtable[5] ;
int *p_ntable;
for(i = 0;i < 5;i++)
numtable[i] = i;
p_ntable = &numtable[2];
numtable[5] には 0〜4の数字が順番に格納されます。
numtable[0] | 0 | |
numtable[1] | 1 | |
numtable[2] | 2 | ← |
numtable[3] | 3 | |
numtable[4] | 4 |
p_ntable には numtable[2] のデータが存在するメモリ上のアドレスが格納されます。
このアドレスをもとにnumtable[2] の数値”2” を取り出すには
*p_ntable
とします。printf文では
printf("%d",*p_ntable);
とすることで、2 が表示されます。
* と & を混同して訳が分からなくなる人が沢山いますが(僕(藤原)もそうでした・・・)、
char **ppstr;
これを*が二つでポインタのポインタといいますが(後述)こういうのを使わない場合、つまり
char *pstr;
int *pdata;
など、単純なポインタを使用する場合、宣言した時点で
変数の場合
data;
&data;
ポインタの場合
pdata;
*pdata;
しか表現方法がありません。変数に*をつけたり、ポインタに&をつけるのは意味がありません。
また、ちょっと面白いことに
char *pch;
char ch;
という変数は
pch = *&pch = *&*&pch = ・・・
ch = *&ch = *&*&ch = ・・・
という性質があります。つまり*と&を同時に使うと打ち消しあいます。ま、使いませんけど(笑)
今までの説明を次の表でまとめます。
文字配列 string に "abcdefghi "という順でデータが格納されているとします。
配列要素 | 配列の中身 | 配列の要素へのポインタの設定 | 左表をポインタによる記述に置き換え | ポインタの指し示すアドレスにある内容 |
string[0] | a | pstr = string;
(pstr = &string[0];) |
pstr = string;
(pstr = &string[0];) |
*pstr -> 'a' |
string[1] | b | pstr = &string[1]; | pstr + 1 | *pstr -> 'b' |
string[2] | c | pstr = &string[2]; | pstr + 2 | *pstr -> 'c' |
string[3] | d | pstr = &string[3]; | pstr + 3 | *pstr -> 'd' |
string[4] | e | pstr = &string[4]; | pstr + 4 | *pstr -> 'e' |
string[5] | f | pstr = &string[5]; | pstr + 5 | *pstr -> 'f' |
string[6] | g | pstr = &string[6]; | pstr + 6 | *pstr -> 'g' |
string[7] | h | pstr = &string[7]; | pstr + 7 | *pstr -> 'h' |
string[8] | i | pstr = &string[8]; | pstr + 8 | *pstr -> 'i' |
string[9] | \0 | pstr = &string[9]; | pstr + 9 | *pstr -> '\0' |
極普通のプログラムを組んでいる分にはポインタを使わなくても大丈夫だと思えます。しかし、次の章で扱う関数というものを利用するとき、ポインタは切っても切り離せない存在になります。関数利用時以外で良く使うのは配列を扱う場合です。
※この項目以降は特に必要を感じた場合だけ読んでください。学校の授業でやる程度のプログラムならこれ以降の項目は特に必要ありません。関数の章へ行って下さい。
ポインタではなく配列で扱うと効率が悪いことが良くあります。「効率が悪い」とはここではメモリの使用効率を主に指します。
例えば、コンピュータネットワークで電子メールを受信するプログラムを考えます。
配列で電子メールの文章を格納する入れ物を宣言するとします。
char MailTextData[100000];
電子メールはどんな長さのメールが届くか分からないのでこうやって万が一のことも考え余裕を持っておく必要があります。
これでも、100000バイト以上のサイズのメールが届いた時はあふれてしまいます。しかも、宣言でサイズを100000バイトに指定してしまうと、例えば大きさ10バイト(10文字)程度のメールでもこの変数に格納することになり、確保した分のうちのメモリ使用効率は0.01%となり明らかに無駄です。
こういう時、ポインタが有効に使えます。
まず、
char *MailTextData;
と、ポインタとして宣言だけしておきます。この時点ではポインタの指し示す中身が何であるかは不定です。
次に、メールの長さが100000バイトであれば
MailTextData = (char *)malloc(100000);//C言語流
MailTextData = new char[100000];//C++言語流
10バイトであれば
MailTextData = (char *)malloc(10);//C言語流
MailTextData = new char[10];//C++言語流
と、ポインタの位置から必要量だけメモリを確保できます。
これをメモリの動的確保といいます。
確保が終われば、あとはMailTextDataを普通の配列のように扱うことが出来ます。
つまり、メモリを必要時に確保するときのメモリ上の位置決めとしてポインタが利用できるということです。
(例 : メモリの動的確保のプログラム)
main( )
printf("メモリを動的に確保します。文字列で実験します。サイズを入力してください\n"); scanf("%d",&size); //サイズの取得 p_string = (char *)malloc(size + 1);//char型なのでサイズと(半角の)文字数は等しくなります。+1はNULL文字を考慮して。 gets(p_string);//文字列をキーボードより入力します。確保したサイズ以上の文字列が入力された場合はどうなるかは分かりません。 printf("確保したサイズ:%d\n入力された文字列:%s\n", size , p_string ); //結果を表示します。 free(p_string);//動的に確保したメモリは必ず開放しなければなりません。ここで開放すれば同変数名でまた新たに確保できます。 printf( "メモリ領域が解放されました。\n" );
|
しつこいようですが、ポインタはアドレスを格納するものです。このアドレスは何かの変数のアドレスである必要はありません。
ポインタへのアドレスを格納したり、関数へのポインタを設定することも出来ます。なぜかというと、ポインタが単純にアドレスを指すだけの道具だからです。
ポインタそのものがアドレス以外の値を持っているわけではありません。
さて、次のようなプログラムを考えてみます。
int data = 8;
int *p_data;
int **pp_data;
int ***ppp_data;
int ****pppp_data;
一つ目がポインタで、二つ目はポインタのポインタ、3つ目はポインタのポインタのポインタ、4つ目はポインタのポインタのポインタのポインタになります。
「え!!こんなややこしいの使うことがあるの???」と思うでしょう。まぁ、普段はあまりお目にかかりません。どういうときに使うかというと、ポインタ配列、多次元配列を扱う時などに役立つことがあります。詳しくやると深みにはまるのであまり解説しませんが、上の例ではどういう関係になるのかだけお話します。
p_data = &data;
これはいつもと同じです。
pp_data = &p_data;
これで、pp_data には ポインタ p_data のアドレスが設定されます。
同じく
ppp_data = &pp_data;
とすることで、pp_data のアドレスが設定されます。
では、ppp_data を使って data の値を参照するにはどうしたらいいのでしょうか?
printf("%d",***ppp_data);
こうすると、値8が表示されます。間接演算子を3つ付けます。
また、次の例ではどうなるでしょうか?
printf("%d",**ppp_data);
間接演算子が2つです。この場合、ポインタp_data の アドレスが表示されます。私のマシンで先ほど試したところ 6684136 という値になりました。
printf("%d",*ppp_data);
とすると、6684108 と表示されました。これはポインタ pp_data のアドレスです。このように、多次元に関連させた場合はつながりを把握することが大切です。
この指定を誤ると大変な結果になってしまうかもしれません。
また、もう一つ注意があります。
pp_data++;
こうしてしまうと、pp_data は p_data を指さなくなってしまいます。p_dataがポインタの配列
p_data[150] という具合に、配列になっている場合は pp_data++ とすると
p_data[1] を指すようになります。しかし、指し示す先が単純なポインタであるならば、この操作は謝りです。
ここで、ポインタの配列というのが出てきました。これは、ポインタを配列として扱うことが出来るこというこです。
例えば、
int a;
int b[2];
int c;
とあり、
int *p[4];
と、ポインタを配列として宣言したとします。
初期の状態ではポインタpはどこを指しているのか分かりませんので、アドレスを与えてやります。
a = 1;
b[0] = 2;
b[1] = 3;
c = 4;
p[0] = &a;
p[1] = &c;
p[2] = &b[0];
p[3] = &b[1];
このように、初期化してみました。指し示すメモリの内容を見るには
*p[0]
*p[1]
・・・
とすればOKです。しかし、ポインタの演算を利用してもっと簡単に変数a b c にアクセスする方法は無いでしょうか?
p++;
p+= 4;
・・・
これはエラーになってしまいます。なぜなら、p は配列名ですから規則にのっとって&p[0] となり、ポインタが指し示すところではなくポインタ自体が存在する場所を移動してしまうことになるからです。では、ポインタ演算を扱うにはどうしたら言いのか?ここでポインタのポインタが活用できます。
int **pp;
pp = &p;
とします。pp にはポインタ配列 p の最初の要素が設定されます。
すなわち、
printf("%d",**pp);
とすると *pp = p[0] = &a ですから **pp とすることで **pp = *p[0] = a となり、変数a の値1が表示されます。
pp++;
とすると、今度は *pp = p[1] = &c となります。故に **pp = *p[1] = c となり、変数cの値4が表示されます。同様に
pp += 2;
pp--;
2つすすんで1つ戻ると **pp = *p[2] = b[0] となり、配列b の0番目の要素
3が表示されます。
おぉっ、すごいですね!一つのポインタを前後に動かすだけで一度に3つの変数(配列含む)にアクセスできましたね。これが何かに応用できそうな気がしてきませんか??
このように、ポインタ配列を使うことで一つのポインタ名で複数の変数を一度に操作できるという大きな利点を持っています。また、その操作をしやすくするためにはポインタへのポインタが便利であるという事も分かっていただけると思います。
関数には名前(関数名)が付いています。たとえばprintfなどが関数名です。
関数名はそのままで使うとアドレスを示します。言葉ではいいにくいので次のようなプログラムを書いてみます。
printf("%d",printf);
これは、上手く動作します。
printf("%d",scanf);
これも正常に動作します。この時表示されるのがこの関数が持つアドレスです。では、関数がなぜアドレスを持っているのでしょうか?
この答えは単純です。「プログラムを動かす時メモリ上にデータ領域とプログラム領域が確保され、プログラム領域に関数も読み込まれる」からです。つまり、メモリ上のある位置に「関数」の処理を行うための処理部分が読み込まれます。その処理の「開始位置」が「関数のアドレス」と等しくなります。
では、関数へのアドレスを持った変数(関数ポインタ)はどのように宣言するのでしょうか。
例えば次のような関数があったとします。
int compare(char x,char y);
この関数へのポインタは以下のように宣言します。
int (*p_compare)( );
ちょっと見た目がややこしいですね。
上の宣言を3つに分割して説明します。
int ・・・ポインタの中身(ここでは戻り値)がint型ですよ
(*p_compare) ・・・p_compareという名前のポインタですよ
( )
・・・これは関数ポインタですよ
という意味を混ぜると「これは戻り値がint型の関数へのポインタp_compareですよ」になります。
アドレスの代入には以下の2つの記述法が扱えます。
(1)p_compare = &compare;
(2)p_compare = compare;
どちらでもOKです。好みの方を使ってください。
では、関数へのポインタをどのように使うのか説明します。
result = compare(a,b);
これは文字aとbを比較して何らかの値を返す命令です。同様に
result = p_compare(a,b);
と、ポインタ変数を使って同じような文法で関数を呼び出すことが出来ます。
では、いろいろと練習してみましょう。
void func1(void);
void (*p_func1)( ); p_func1 = &func1; func1( );
|
char *string = "I am a pen.(^_^;";
void func2(char *); void (*p_func2)( ); p_func2 = &func2; func2(string);
|
float result;
float func3(int); float (*p_func3)( ); p_func3 = &func3; result = func3(20);
|
char *buff;
char *func4(int ,int , long ,float); (char *)(*p_func4)( ); p_func4 = &func4; buff = func4(12,8,21893819312,3.1415926535);
|
int result;
int func5(void); int func6(int); int func7(float) int (*p_func)( ); p_func = &func5; result = p_func( ); p_func = &func6; result = p_func(7); p_func = &func7; result = p_func(1.732); |
上記のプログラムはいずれも「同じ処理を関数でもそのポインタ変数でも行える」ということを示したものです。
どちらで処理を行っても結果は等しくなります。
ここで5つ目のプログラムに注目してください。
1つのポインタ変数で様々な関数の肩代わりをしています。これが関数ポインタを使う上での利点の一つとなっています。
異なる引数の関数でも1つのポインタ変数で表現できます。
但し、1つだけ条件があります。それは「戻り値」の型が違うものは扱えないというものです。
例えば次のような操作は出来ません。
int func1(void);
float (*p_func1)( )
p_func1 = &func1;
では「何に使うの?」というと主に次の2点が考えられます。
(1)プログラムを書く量を減らす(同じ物を使いまわす)
(2)処理を高速にする(switch 〜 case 文で)。俗に「関数スイッチ」とも呼びます(^_^)
(2)についてはこちらで説明します。
ここでは(1)について簡単に説明します。
例えば、有名な例題に「ソート(並び替え)問題」があります。
データの並び替えを行う時は並び替えるための「判断基準」が必要になります。
例えば「日付の新しいもの順」とか「年齢の若いもの順」とか「大きさの大きいもの順」などといった概念です。
例えば構造体配列dataが日付、年齢、大きさという3つの情報を持っているとします。
普通なら並び替える方法に分けて並び替え関数を作ります。
sort_by_date(data)
{
・・・
}
sort_by_age(data)
{
・・・
}
sort_by_size(data)
{
・・・
}
しかし、並び替え処理で有名な基本部分
temp = x;
x = y;
y = temp;
といったところは全てにおいて共通しています。すなわち、「並び替えるかどうか」を判断する部分だけ異なり、それ以外の処理部分は共通しています。
この時「同じ処理を書く」という手間を省くために「関数ポインタ」を使えます。
このソート処理の場合、まず次のような親関数を作ります。
sort(data,int (*p_how_to_sort)( ))
{
・・・
/*並び替えるかどうかの「判断基準」*/
if(p_how_to_sort(data[x],data[y]) == 1)
{
temp = data[x];
data[x] = data[y];
data[y] = temp;
}
・・・
}
関数の第2引数に「関数ポインタ」が入っていることに注意してください。
そして「判断基準」のみを書いた関数を3つ用意します。
int sort_by_date(data x,data y)
{
if(x.date > y.date)
return 1;
else
return 0;
}
int sort_by_age(data x,data y)
{
if(x.age < y.age)
return 1;
else
return 0;
}
int sort_by_size(data x,data y)
{
if(x.size > y.size)
return 1;
else
return 0;
}
これらは2つのデータの大小関係をある基準で判断するための関数です。
この場合、関数が1を返した場合を並び替えの対象とします。
上のプログラムを使って並び替えを行う時は次のようにします。
/*日付で並び替え*/
sort(data,&sort_by_date);
/*↑sort(data,sort_by_date); でもよい。確かめてみてください。*/
/*年齢で並び替え*/
sort(data,&sort_by_age);
/*大きさで並び替え*/
sort(data,&sort_by_size);
親関数sort( )の第2引数に「判断基準」関数のアドレスを渡すことで、簡単に並び替える方法を変更することが出来ます。
今回は「並び替え」を例にとって説明しましたが、基本の処理部分が共通していて与えるパラメータにより一部の振る舞いが変更される場合や、複数人数でプログラムを組む時に他人の関数を使わせてもらったりする時に比較的利用できるテクニックです。テクニックと書いたのは、関数ポインタを使わなくても解決できる場面がほとんどだからです(^_^;
構造体へのポインタもよく使います。
まず、使い方について簡単に説明します。
次のような構造体があったとします。
struct tagCellularPhone{
char MakerName[256];
long BatteryCapacity;
short DisplayMode;
struct tagAddress{
short Priority_no;
char name[256];
char tel_no[20];
char E-mail_Address[256];
long reserved1;
char reserved2[50];
}Address[300];/*アドレス帳は300件対応*/
short History[10];
};
この構造体の変数MyPhoneを宣言するときは次のようにすればよかったですね。
struct tagCellularPhone MyPhone;
同様に、構造体へのポインタpMyPhoneを宣言する時は次のようにします。
struct tagCellularPhone *pMyPhone;
簡単ですね。*を付けるだけです。
では、構造体へのポインタを構造体の実体に関連付けましょう。
pMyPhone = &MyPhone;
こうすれば、構造体へのポインタに構造体のアドレスをセットすることが出来ます。
あとは、普通のポインタ同様ポインタを使って間接的に操作ができるようになります。
この時ポインタから構造体内部の変数(メンバ変数)を操作するには次のような記号を使います。
->
ちょっと目をディスプレイから離して眺めると矢印に見えますよね?これを「アロー演算子」といいます。
構造体変数からメンバ変数へアクセスするには次のような構文を利用しました。
MyPhone.BatteryCapacity = 255;/*フル充電*/
同じように、構造体へのポインタを利用してメンバ変数にアクセスするには次のようにします。
pMyPhone->BatteryCapacity = 255;/*フル充電*/
普通のポインタなら
*pSum = 50;
のように、ポインタの指し示す先の内容を利用するには*を付ける必要がありました。しかし、構造体へのポインタを利用する場合には不要です。アロー演算子がこの手続きを肩代わりしてくれます。もし、普通のポインタ同様の書き方でアプローチするなら次のような構文になります。
(*pMyPhone).BatteryCapacity = 255;/*フル充電*/
これでも同じ意味ですが、こういう書き方をする人はほとんどいません(^_^;
構造体へのポインタをよく使うのは「関数」へ構造体を引数として渡す時です。
構造体は多くの場合メモリ空間をたくさん使います。(データのかたまりですからね・・・)
関数へ構造体のデータそのものを渡すとそれだけ処理が遅くなりメモリもたくさん消費することになってしまいます。
この時、構造体へのポインタを渡してやることで、処理系にも左右されますが4バイト程度の情報を渡すだけで構造体の全メンバ変数へフルアクセス出来るようになります。上の例の構造体の場合、簡単に計算して168KBものサイズになります。これだけの情報を関数に渡す時はポインタを使うと使わないで大きな差が出てきます。
では、アロー演算子を使っていくつか処理を行ってみます。
/*優先度を変える*/
pMyPhone->Address[30].Priority_no = 10;
/*名前をセットする*/
strcpy(pMyPhone->Address[16].name,"藤原");
/*アドレス帳を1つ消去する*/
/*方法その1*/
pMyPhone->Address[30].Priority_no = 0;
strcpy(pMyPhone->Address[30].name,"");
strcpy(pMyPhone->Address[30].tel_no."");
strcpy(pMyPhone->Address[30].E-mail_Address,"");
pMyPhone->Address[30].reserved1 = 0;
strcpy(pMyPhone->Address[30].reserved2,"");
/*方法その2*/
memset(&pMyPhone->Address[89], 0, sizeof(pMyPhone->Address[89]));
/*「方法その2」では構造体の中にある構造体の先頭アドレス(&pMyPhone->Address[89])から構造体のサイズsizeof(pMyPhone->Address[89])バイト分だけ0で初期化している。分かるかな?*/
ポインタの移動距離の項目でお話したことを利用し、int型 のデータがどのようにメモリ上に配置されているのかを調べてみましょう。
unsigned int data;
char *pointer;
int型は4バイトで unsigned で修飾してるので正の数のみ扱えるとします。
ポインタはchar型を指定し、移動距離を1バイトとします。
つまり、int型を左から1バイトごとに見ていき、どういうデータが格納されているのかを確かめようというわけです。
unsigned int data | 4バイト | |||
ポインタ pointer | 1バイト目 | 2バイト目 | 3バイト目 | 4バイト目 |
unsigned int は4バイトですから、単純計算して
1バイト=0〜255
4バイト=256の4乗=0〜4294967295
の間の範囲の値が代入できることになります。
では、
int data;
char *pointer;
data = 調べる数値;
/*VisualC++では型キャスト無しでも動作しますが(笑)、確実にキャストしましょう*/
pointer = (char *)&data;
printf("%d:",*pointer);
pointer++;
printf("%d:",*pointer);
pointer++;
printf("%d:",*pointer);
pointer++;
printf("%d\n",*pointer);
このプログラムで「調べる数値」に値を代入し、4バイトのメモリ上でどういう配置がされるのか検証してみます。
まず、3を代入すると次のような結果になります。
3:0:0:0
unsigned int data | 3 | 0 | 0 | 0 |
図で表わすとこうなります。あれ、右に詰めるようではなさそうですね。人間の感覚では右から詰めていきそうなものですが。
では、256を代入したらどうなるでしょうか?
0:1:0:0
unsigned int data | 0 | 1 | 0 | 0 |
図で表わすとこうなります。0×2560+1×2561=256 です。
次に、0×2560+0×2561+0×2562+1×2563=16777216を試してみましょう。
予想どおり
0:0:0:1
となります。
unsigned int data | 0 | 0 | 0 | 1 |