ポインタ(C,C++)
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
検索
|
最終更新
|
ヘルプ
|
ログイン
]
開始行:
[[プログラミング言語/C]]
ポインタですー
単純な概念や使い方自体は簡単。
ただ、ポインタに関連するmalloc関枢やnew演算死を使うたびに...
ちょっとでもおかしな部分があればメモリリーク(メモリ使いす...
を引き起こす。マジクソ視ね
#contents
**ポインタ[#oa1e7a6e]
ではいよいよポインタの説明です。ポインタとはなんでしょう...
ズバリ言うと、~
&size(20){''ポインタとは、アドレスを入れるための&color(...
そう、int x=10;やchar c='s';と基本的に変わらねーよバーカw
ただそんだけ。~
intなら整数、charなら文字、と入れるものによって変数の型を...
では復唱しましょう。~
''ポインタとは、アドレスを入れるための変数である''~
''ポインタとは、アドレスを入れるための変数である''~
''ポインタとは、アドレスを入れるための変数である''~
はい、覚えましたね。~
では具体的にポインタはどうやって使うのでしょうか。~
一例として、int型変数のアドレスを入れるためのポインタを作...
int型変数のアドレスを入れるためのポインタを作るには、
int* ポインタの名前;
とします。例えば、
int* ptr;
のように。~
それでは実際のプログラムにしてみましょう。
#include <stdio.h>
main(){
int a = 5;
int* ptr; /** int型変数のアドレスを入れるためのポインタ...
ptr = &a; /** aのアドレスを入れた **/
}
このように使います。まだ何の役にも立ちませんね。~
ここでちょっと注意があります。~
世の中の9割以上の本やソースコードには、 int* ptr; ではな...
int *ptr;
と書いてあります(スペースの位置が違う)。~
これは int* ptr; と同じ意味なので慣れればどっちを書いても...
&size(20){''int* ptr; と書くことを強くすすめます''};~
これには三つほど理由があるのですが、現時点で言える理由は、~
+int *ptr; と書くと、int型の変数 *ptr に見える
+''int* という型が、int型変数のアドレスを入れるための型だ...
ということです。~
intと同様に、char型変数のアドレスを入れるためのポインタは...
またその他の型用のポインタについても、
型の名前* ポインタの名前;
で宣言できます。
**ポインタの使い道 [#a2c2261e]
さて、ポインタの概念と作り方はわかりましたが、このままで...
そこで使い道を説明していきます。~
***演算子 * について [#m6916aa3]
まず、変数の頭に & を付けるとその変数のアドレスを表したよ...
ポインタの先頭に * を付けると「''そのポインタに書いてある...
''アドレスとは変数の住所のことである''でしたから、アドレ...
「え? * ってポインタを作るときに使うやつでしょ?」はい、...
-は普通「引く」を表すけど、「数字の前についたらマイナスを...
では早く具体例をみて把握してしまいましょう。
#include <stdio.h>
main(){
int a = 5;
int* ptr;
ptr = &a; /** aのアドレスを、ptrに代入した **/
printf("%d\n", *ptr); /** 「ptrに書いてあるアドレス」に...
}
実行結果
5
~
ここで int *ptr; と書いてほしくないもうひとつの理由が出て...
上のプログラムを int *ptr; と書くと、
#include <stdio.h>
main(){
int a = 5;
int *ptr;
ptr = &a;
printf("%d\n", *ptr);
}
となります。''(ポインタを宣言するための * と、中身を得る...
しかし数学的センスのあるあなたはこう考えます。~
「そうか! *ptr が int型の変数だ と思えばいいんだ!」~
確かに5行目( int *ptr; )と7行目( printf(...) )を見るとそ...
ptr = &a;
が意味不明になります。つい
*ptr = a;
としてしまいそうです。~
これがなぜ間違いか考えてみましょう。~
5行目(int *ptr;)までの状態では、ptrには「何が入っているか...
なので、この状態で *ptr = a; とすると、「ptrに書いてある...
これはいけませんよね。
***本題 [#ufc3017d]
さて、だいぶ * の説明が長くなってしまいましたが、いよいよ...
次のコードがどんな結果を生じるか考えてみてください。
#include <stdio.h>
void inc(int x){
x = x + 1;
}
main(){
int a = 5;
inc(a);
printf("%d\n", a);
}
さてどうでしょうか?一見6が表示されそうですが、残念ながら...
学歴が低い人がinc を呼び出すと、「 x に a の値がコピー」...
例で理解しましょう。~
いま、あなたがmain関数だと思いましょう。あなたは a とい...
あなたにはincという友達がいます。あなたは彼の持っている...
そして彼が x にかいてある 5 を 6 に書き換えて、その後あ...
さて、どうなるでしょうか?~
そうです。''この方法ではaの値変わらない''のです。~
ここでポインタの登場です。上のプログラムをポインタを使っ...
#include <stdio.h>
void inc(int* x){
*x = *x + 1;
}
main(){
int a = 5;
inc(&a);
printf("%d\n", a);
}
友達のinc君が持っている int* 型の変数x(int型変数のアドレ...
この状態でincは *x に、 *x + 1 を代入します。~
*x は「xに書いてあるアドレスにある変数の、中身」でし...
(aの中身) = (aの中身) + 1;
となり、めでたくmainの持つ a が 6 に書き換わります!~
このように、ポインタは呼び出された関数から呼出し元の持つ...
**まとめ [#lb0799a6]
重要ポイントのまとめです。~
''アドレスとは、変数の住所のことである''~
''ポインタとは、アドレスを入れるための変数である''~
''*ptr とは、ptrに書いてあるアドレスにある変数の、中身...
分からなくなったときはこれを思い出してください。
**最後にちょっと注意 [#wd753712]
int* ptr; と書け と強くすすめましたが、ちょっとだけ問題が...
それは次のようにポインタをいくつかまとめて宣言する場合で...
int* a, b, c;
これは
int *a, b, c;
と同じだとみなされて、b と c はただのint型になってしまい...
int *a, *b, *c;
と書いてください。~
(本当は (int*) a, b, c; と書きたいんだけど、こうかくとキ...
*ポインタのいろいろ [#fe8e394c]
**演算子 -> について [#k44715ff]
さて、今、ある構造体と、それを指すポインタ(その構造体の...
struct PC{
double CPU_Hz;
int Monitor_size_tate;
int Monitor_size_yoko;
int HDD_GB;
};
main(){
struct PC myPC = {1.66, 1024, 1240, 500};
struct PC* PC_ptr = &myPC;
}
このとき、PC_ptr経由でmyPCのメンバにアクセスするにはどう...
printf("%f\n", PC_ptr.CPU_Hz);
こうでしょうか?~
ポインタの基礎でやった通り、PC_ptr の中には myPC のアドレ...
では構造体の本体はどこにあるかというと、「ポインタに入っ...
その中身を取得するには、演算子 * を使うのでした。なので、...
printf("%f\n", (*PC_ptr).CPU_Hz);
これで「PC_ptrに入っているアドレスにある構造体(myPC)の、C...
さて、(*PC_ptr).CPU_Hz のカッコがいるのかという問題ですが...
なぜなら、 10*1.5 を正しく 10*(1.5) と解釈( (10*1).5では...
なので、*PC_ptr.CPU_Hz とすると、*(PC_ptr.CPU_Hz) だと解...
これがどういう問題を引きこすかというと、例えば構造体がさ...
(*(*ptr1).ptr2).member
のようになり、非常に煩雑でわかりにくくなってしまいます。~
そこで、救世主 -> の登場です。~
この演算子を使うと、先ほどの例が次のようにかけます。~
printf("%f\n", PC_ptr->CPU_Hz);
これで「PC_ptrに書いてあるアドレスにある構造体の、CPU_Hz...
この演算子が威力を発揮するのは、さきほどわかりにくくなる...
ptr1->ptr2->member
カッコがなくなってスッキリしました。ポインタが何個連なっ...
(*(*(*(*ptr1).ptr2).ptr3).ptr4).member /** -> を使わない...
ptr1->ptr2->ptr3->ptr4->member /** 分かりやすい...
構造体とポインタを使ったプログラムでは、積極的に -> を使...
// 入門用とそれ以外の区切り
#br
#hr
CENTER:&color(red){ここから下は初心者の入門用ではありませ...
#hr
#br
// 入門用とそれ以外の区切り
// 自作関数の項目から外してポインタの後に移動しました
*関数ポインタ [#d011c48b]
**関数ポインタ [#d171c1de]
''(注意!)これは初学者向けではありません。最低でも関数と...
関数のアドレスを保持するポインタです。
実行する関数を動的に変えられたり、関数の配列を作ったり、...
つまり、func[0]()が「VIPPER死ね」と表示する、func[1]()が...
普通関数の引数には関数はいれられないけど~
func(func2){~
func2();~
}~
みたいなことができる!便利!
まずは関数の宣言から見ていきます。
int MyFunc(float);
これはint型の値を返し、引数にfloat型をとる関数の宣言です...
定義時に引数名を省略しても、関数内部で使えないだけで問題...
関数ポインタは、入る関数の引数と戻り値が分かっている必要...
宣言の形は、
関数の戻り値 (*関数ポインタ名)(関数のとる引数)
です。
上記のMyFunc関数が入る関数ポインタpFuncを宣言するには、
int (*pFunc)(float);
とします。
これは、
-int型の戻り値を返す
-ひとつのfloat型が仮引数の
-"pFunc"という名前の関数ポインタ
です。
今まで秘密にしてきましたが(?)、関数ポインタは通常のポ...
入れる値とは、関数のアドレスです。
以下は関数、関数ポインタ、それへの代入の一通りの例です。
int MyFunc_1(flaot a){ return (int)a + 1; }
int MyFunc_2(flaot a){ return (int)a + 99; }
int (*pFunc)(float);
/* .... */
pFunc=MyFunc_1;
printf("MyFunc_1 %d\n",pFunc(2.0f));
pFunc=MyFunc_2;
printf("MyFunc_2 %d\n",pFunc(1.0f));
実行結果は
MyFunc_1 3
MyFunc_2 100
となります。
関数ポインタですが、変数というからには型があります。型が...
では、関数ポインタの型とは何なのでしょうか。
下のpFuncの場合、
int (*pFunc)(float);
型は
int (*)(float);
です。どう見てもアナルです。爆発はしません。
typedefの使い方は、
typedef int INTEGER;
これでintの別名"INTEGER"の完成でしたね。
よく見てください。
int INTEGER;
後ろ部分は、『int型の変数の宣言』と形が同じです。
という事は、関数ポインタのtypedefも、
『int (*)(float)型の変数の宣言』に"typedef"をくっつけたも...
つまり、下です。
typedef int (*pFunc_t)(float);
これで『int (*)(float)の別名pFunc_t』のできあがりです。
次からは、
pFunc_t pFunc;
として関数ポインタを宣言することができます。
すっきりしましたね。
通常のポインタには、多重ポインタがあります。『ポインタの...
int v=0;
int* p = &v; //int型へのポインタ
int** pp = &p; //int*型へのポインタ
int*** ppp = &pp; //int**型へのポインタ
***ppp = 99; //v = 99
関数ポインタも例外でなく、『関数ポインタへのポインタ』な...
typedef int (*pfunc_t)(void);
pfunc_t* ppfunc; //int (**ppfunc)(void)と同等
pfunc_t** pppfunc; //int (***pppfunc)(void)と同等
pfunc_t*** ppppfunc; //int (****ppppfunc)(void)と同等
int (*****pppppfunc)(void); //pfunc_t**** pppppfuncと同等
こんなに多重ポインタを作ることはまずありえませんが、『関...
関数の引数に関数ポインタのポインタを渡して見る例です。
#include <stdio.h>
int add(int x,int y){ return x+y; } //足し算をする関数
int mlt(int x,int y){ return x*y; } //掛け算をする関数
typedef int (*pfunc_t)(int,int);
void set_1(pfunc_t*f){ *f=add; return; }
void set_2(pfunc_t*f){ *f=mlt; return; }
int main(void){
pfunc_t pf;
set_1(&pf);
printf("add %d\n",pf(99,1));
set_2(&pf);
printf("mlt %d\n",pf(33,3));
return 0;
}
pfunc_t pfの宣言外部で、pfの内容を操作しています。
実行結果は次のようになります。
add 100
mlt 99
**C++に対するアプローチ [#w0199a12]
''(注意)ここはCの範囲ではありません、Cではできないことを...
いやむしろなぜCの項に用意したんだよww(関数ポインタの有...
***関数オーバーロード時の動作 [#g698fbf5]
関数Functionをオーバーロードしています。
#include <stdio.h>
void Function(long int){
printf_s("long intはintよりも大きいから豊乳じゃと!...
}
void Function(short){
printf_s("shortはintよりも小さいからは貧乳じゃと!?...
}
void Function(void){
printf_s("ツルペタ始まったな\n");
}
int main(void){
{
void (*pfunc)(short);
pfunc=Function; //コンパイル時に型が自動推論される
pfunc(short());
}
{
void (*pfunc)(void);
pfunc=Function; //コンパイル時に型が自動推論される
pfunc();
}
return 0;
}
実行結果は
shortはintよりも小さいからは貧乳じゃと!?
ツルペタ始まったな
となります。
***メンバ関数に対する動作 [#vbe096d6]
メンバ関数のポインタをとってみましょう。
class CFoo{
public:
void func_v(){
printf_s("仮想関数だと!?\n");
}
static void func_s(){
printf_s("ぼくは静的関数ちゃん!\n");
}
};
/* .... */
void (*pfunc)();
pfunc=CFoo::func_v; //ここでコンパイルエラー
CFoo::func_vはCFooのインスタンスがないと実行できない関数...
staticがないので当たり前ですね。
では以下はどうでしょうか。
インスタンスを生成してメンバ関数をとってみます。
void (*pfunc)();
CFoo a;
pfunc=&a.func_v; //error C3867 , error C2440
これもエラーです。エラー内容を見てみましょう。~
:error C3867:"CFoo::func_v"のポインタを取得したかったら"...
どうやら、ポインタを取得するには"a.func_v"ではなく、"&CFo...
でもこれではせっかくインスタンスを作ったのに意味がありま...
:error C2440:"a.func_v"は"void(__thiscall CFoo::*)()"型...
という事は"pfunc"を"void(__thiscall CFoo::*pfunc)()"と宣...
結論から言えば、理論的にはこれで解決です。コンパイルでき...
ただし、重要な問題が発生します。~
それは、この場合のpfuncは『関数ポインタ』ではなく『メンバ...
メンバポインタについては『関数ポインタのC++に対するアプロ...
追記:~
gccで同様のコードをコンパイルしたところ、
error: ISO C++ forbids taking the address of a bound mem...
Say ‘&CFoo::func_v’
とのエラーが出ます。'''(訳:ISO C++では、インスタンスのメ...
要は仕様で禁止されている、ということです。~
理由は知りません。ごめんネ
*&color(red){第二番目の説明!!!!!うおおおおおおお};~~...
**アドレス [#ga610581]
int i = 10;
というコードがあるとします。
これがコンパイルされたとき、EXEのなかではどう表現されてい...
変数の値は、実際にはメモリのどこかに保存されています。
そして「どこに保存されているか」というメモリの住所は、番...
上記のコードは、たとえば
45450721番目のメモリに 10 を代入せよ
のようにコンパイルされます。
何番目のメモリを使うかは、コンパイラが勝手に決めてくれま...
C言語を使えば、このようにどの変数にどこのメモリを割り当て...
自動的に割り振ってくれるわけですね。
この、"何番目のメモリ" という番号のことを「アドレス」と呼...
**ポインタ [#b9e4e979]
さて、上記のコードにおいて、変数 i が何番目のメモリに格納...
用語を用いて言い換えれば、変数 i のアドレスが知りたいわけ...
そういう時は &i と書けばおkです。上記の例では &i は 4545...
C言語には、アドレスを格納するための特別な変数型があります...
ポインタの宣言は
int *p;
のように書きます。たとえば
int i = 10;
int *p = &i;
などとします。この p には、たとえば 45450721 などが格納さ...
この例では、p のことを「i へのポインタ」といいます。
「i へのポインタ」とは、"i のアドレスが格納されているポイ...
では 45450721番目のメモリに入っている値(すなわち i に格...
printf("%d", *p);
と書きます。
*p のことを、「p が指す値」と呼びます。
**ポインタの宣言 [#f183fa34]
int i;
という宣言文は「i は int型である」と宣言しています。これ...
int *p;
という宣言文は「*p は int型である」、もしくは「pとは、*p ...
ちょっと複雑な例を考えましょう。intへのポインタを10個並べ...
ap[index] はポインタですね。その指す値(int型)は、 *(ap[...
int *(ap[10]);
と書けばよいのです。
次に、intを10個並べた配列へのポインタ pa が欲しいときはど...
*pa は配列ですから、(*pa)[index] は int 型ですね。で...
int (*pa)[10];
と宣言すればよいです。
*関数の引数について [#r19e389b]
**値渡し [#j2c6d7ac]
#include <stdio.h>
void func(int a)
{
a = 100;
}
int main()
{
int b = 1;
func(b);
printf("%d", b);
}
というプログラムを実行すると、"1" と表示されます。決して ...
具体的に追っていきましょう。&a は 45450721、&b は 6741 だ...
main関数のなかでは、まず
int b = 1;
で 6741番目のメモリが 1 になります。次に
func(b);
では、45450721番目のメモリに 1 がコピーされて
func の先頭にジャンプします。
func のなかで、45450721番目のメモリに 100 が代入されます...
printf("%d", b);
に戻ってきたとき、6741番目のメモリには何が入っているでし...
1 ですね。
このように、関数の引数には、呼び出し元の変数の値がコピー...
関数内部ではその''コピーされたものしかいじれません''。
**関数の引数にポインタを用いると [#m8becd41]
#include <stdio.h>
void func(int *p)
{
*p = 100;
}
int main()
{
int b = 1;
func(&b);
printf("%d", b);
}
を最初から追っていきましょう。&p は 45450721、&b は 6741 ...
まず
int b = 1;
で 6741番目のメモリが 1 になります。つぎに
func(&b);
で、45450721番目のメモリに 6471 がコピーされて、funcの先...
funcのなかで、45450721番目のメモリに格納されている番号(6...
メモリに 100 が代入されます。そうして
printf("%d", b);
に戻ってきたとき、6741番目のメモリには 100 が入っています。
ですから、画面には "100" と表示されることになります。
このように、ポインタを関数の引数に用いると、
関数内で呼び出し元の変数をいじることができるようになりま...
*配列 [#k8dd863f]
**配列 [#afd152dd]
char ac[100];
と配列を宣言したとします。配列は、''メモリ上に連続して並...
&(ac[0]) が 45450721 だとすると
&(ac[0]) = 45450721
&(ac[1]) = 45450722
&(ac[2]) = 45450723
&(ac[3]) = 45450724
.
.
.
とならびます。そうして、ac を単独で用いると、&(ac[0])...
printf("%p\n", ac); // %d が int を表示するように...
printf("%p\n", &(ac[0]));
このコードを実行すると、同じ値が表示されます。
このように、配列の識別子は、その配列の先頭のアドレスに変...
変換されるだけで、配列がアドレスそのもの、というわけでは...
また、先頭のアドレスが格納されているポインタから、
配列へ逆変換することは''できません''。
**配列とポインタ [#ie104a63]
さて、上記の ac を使って
*(ac + 3);
は何を表すでしょう? ac + 3 は 45450724 ですから、
それに * をつけると ac[3] にアクセスできますね。
一般に
ac[index] ⇔ *(ac + index)
の関係が成り立ちます。
ac は &(ac[0]) に変換されるといいましたが、ac[0] は c...
&(ac[0]) は char へのポインタです:
char ac[100];
char *pc = ac;
と書くことができます。配列をポインタに代入しているように...
実際には''配列の先頭のアドレスをポインタに代入しているだ...
このポインタを使って、ac の要素にアクセスすることを考えま...
たとえば ac[3] にアクセスしたければ
*(pc + 3)
と書けばよいですね。この節を先頭から読み直すと、これが
pc[3]
と書ける気がしませんか? 書けます。ですが
pc[3] は、''単なる *(pc + 3) の言い換えに過ぎません''。
ac[3] は、「配列 ac の 3 番目の要素」を指しますが、
pc[3] は、単に*(pc + 3) を言い換えただけです。
この違いは今は気になりませんが、2次元配列を考えたときに...
何はともあれ、ポインタをつかって配列と同じような書き方が...
でもポインタは(ここでは)配列の要素を指し示すものであっ...
char *pc = ac + 5; // &(ac[0]) + 5 のこと
とすれば、pc[3] ⇔ *(pc + 3) ⇔ *(ac + 5 + 3) ⇔ ac[5 + 3] ...
このように、ポインタを使って、配列の先頭をずらしたように
見せかけることができます。あくまで''見せかけるだけ''です。
**配列を関数に渡すには [#ka5f6988]
配列を関数に渡したいとき、仮引数の宣言は 2 種類あります。
void func(char pc[]);
void func(char *pc);
これはどちらも''全く同じ''です。ポインタの宣言になってい...
そしてこの関数を呼び出すとき、配列そのものを渡すことはで...
代わりに配列の先頭の要素のアドレスを渡します。
char ac[10];
func(ac); // func(&(ac[0])); だとみなされる
それでもこれまで見てきたように、func のなかで
このポインタをあたかも配列のように用いることができるので...
*オブジェクトのサイズ [#j3e4b75f]
話は変わりまして、今度はオブジェクト(≒変数)のサイズの話...
**char, short, int, long [#k0c3df0f]
これらは全て整数を表しますが、どうしてこんなに沢山の種類...
整数をノートに記録するとき、
大きな数は沢山の桁数が必要です。
"4" なら1桁ですみますが、"123456" なら 6 桁も必要です。
コンピュータでも同じで、大きな数を表すには沢山のメモリが...
使用するメモリ量の順に char ≤ short ≤ int ≤ long...
つまり、char より long のほうが、大きな数まで表すことがで...
メモリ量の最小単位を「1バイト」といいます。
たとえば、Intel 32bit CPU ならば、int は4バイトです。
int i = 200000000;
と書くと、i の内容は 4 つに分割して格納されます。
たとえば &i が 45450721 なら、45450721番 ~ 4545074番のメ...
そうして、&i は使用されているメモリのうち、先頭の番号を表...
これまで「○○番目のメモリに格納される」と表現してきました...
これは正しくは、「○○番から××番までのメモリに格納される」...
(が、誤解の生じない限り、これからも従来の表現を用います)
**ポインタの加減算 [#wd8f6ca8]
上のほうで
char ac[100];
と宣言したら、5 番目の要素のアドレスは ac + 5 で得られる...
これは char 型が1バイトだからです。ですが、int 型の配列を
int ai[100];
と宣言したとしましょう。そして &(ai[0]) は 45450721 だと...
すると、(1 つの要素の大きさが 4 バイトなので) 5 番目の...
45450721 + (5 * 4) になります。
では 5 番目の要素のアドレスは ai + (5 * 4) と書かないとい...
実はこれは間違いです。単に
ai + 5
と書くだけでよいのです。x4 の部分は、コンパイラが勝手に行...
同様に
int ai[100];
int* pi = ai;
++pi;
というコードを実行すると、&(ai[0]) が 45450721 ならば、pi...
なります。++pi; は単なるインクリメントではなく、
ポインタが指す型のサイズ分だけ加算されるのです。
逆に言えば、ポインタを使うときは、コンパイラがポインタが...
知っている必要があります。
*配列再び [#b678afb4]
今度は2次元配列について考えます。
**2次元配列 [#vf3c36bc]
char aac[3][2];
と2次元配列を宣言したとします。するとこれは
「要素が2つの配列を、3つ並べたもの」になります。
aac[0][0], aac[0][1], aac[1][0], aac[1][1], aac[2][0], ...
の順に、''全ての要素が連続して''並べられます。
そして &aac[0][0] が 45450721 だとすると、aac[x][y] のア...
45450721 + (x * 2) + y
で計算できます。
aac[x] と一つ目の添え字だけを指定すると、{ aac[x][0], aac...
とならんでいる部分を char 型の配列だとみなしたものが得ら...
すなわち aac[x] は &(aac[x][0]) に変換されます。
char* pc1 = aac[1];
などと書けるわけです。pc1 の値は、実際には
45450721 + (1 * 2)
と計算されます。
そうして pc1[x] ⇔ aac[1][x] になります。
では aac を単独で用いたときは、何に変換されるのでしょうか?
aac[x] が char 型の配列なのですから、aac は「char型の配列...
ですから、aac は、「char 型の配列」へのポインタに変換され...
配列へのポインタは上のほうでやりましたね。
char (*pac)[2] = aac;
と変換できるわけです。型はややこしいですが、値は 45450721...
この場合、++pac; などと書けば、要素 2 つの配列の長さ分だ...
になります。すると pac[x][y] は aac[x + 1][y] と同じもの...
気づいた方がおられるでしょうか?
char aac[3][2];
char (*pac)[2] = aac;
char *pc0 = aac[0];
printf("%p\n%p\n", pac, pc0);
を実行すると、pac も pc0 も同じ値であることがわかります。...
値は同じでも''型が違う''のです。
pac[x] は char 型の配列になりますが、pc0[y] は char 型の...
**関数に渡すには [#c8ea3c1f]
関数の引数に int aai[3][2] を渡したいとしましょう。仮引数...
よいでしょうか?
配列を渡すには、その先頭の要素へのアドレスを受け渡しする...
今回の場合、aai は「配列の」配列ですから、受け取る側は「...
なければいけません。すなわち
void func(int (*pai)[2]);
と宣言します。もっと簡単に
void func(int pai[][2]);
と書いても同じです。この "2" を省略することはできません。...
++pai;
というコードがあったとき、「要素 2 つの int 型配列」分の
大きさだけ加算する必要があります。同様に
pai[2]; // *(pai + 2) のこと
というコードがあったとき、「pai の値よりも『要素 2 つの i...
進んだところ」から始まる配列を取得しないといけません。
このように要素 2 つ、というのは重要な情報なのです。
**似非2次元配列 [#u8e14d09]
お気づきの方はいるでしょうか? main 関数の宣言は
int main(int argc, char *(argv[])); // これは char *argv...
int main(int argc, char (*argv)[]);
int main(int argc, char **argv);
int main(int argc, char argv[][]);
などと書きます(全部同じです。char **argv; が宣言されたこ...
最後の宣言方法を見るとわかりますが、argv[][ここがない] で...
普段 argv[1][0] などと2次元配列と同じように用いています...
これは本来の2次元配列ならありえない話です。
なぜなら、argv[1] というのが、argv[0] から「要素いくつ分...
離れたところにあるか分からないのですから。
実は argv に渡されるのは、2次元配列ではなく、「ポインタ...
これの宣言は次のようにします。
char ac0[3];
char ac1[6];
char ac2[2];
char *(apc[3]) = { ac0, ac1, ac2 }; // { &(ac0[0]), &(ac...
apc[x] は、配列先頭の要素へのポインタですから
*(apc[x] + y)
もしくは
apc[x][y]
と書けば、x 番目の配列の、第 y 要素が得られることになりま...
この場合、コンパイラは要素にアクセスするために、アドレス...
そのアドレスは最初から配列内に格納されているのですから。
そして、「ポインタがならんだ配列」を関数に渡したいときは...
アドレスを渡すのでしたね。ですから関数の仮引数は「ポイン...
宣言しないといけません。それが
void func(char **ppc);
void func(char *(ppc[]));
などとなるのです。そのほかの書き方でもかまいません。
ちなみに、今回の変数を使って
printf("%p\n%p\n", apc, apc[0]);
を実行してみてください。前回と違い、今度は違うアドレスが...
*関数ポインタ [#b693a8df]
**関数のアドレス [#p1450f63]
プログラムが実行されているとき、変数の値はメモリのどこか...
そのメモリの番号をアドレスというのでした。
今回は、そのプログラムのコード自体に焦点を当てます。
実はプログラムというのは、メモリにロードされて実行されま...
つまり、プログラム自体もメモリのどこかに格納されているの...
プログラム中で関数を使うなら、その関数自体がメモリのどこ...
そこで、関数が格納されているメモリ領域の、先頭の番号を「...
呼ぶことにしましょう。
**関数の呼び出され方 [#naa2d704]
void func(void)
{
printf("hello, world!\n");
}
int main()
{
func();
}
というプログラムがあって、func のアドレス(func の先頭の...
が 45450721 だとしましょう。すると main 関数内の
func();
という行は「45450721番目のメモリに格納されているコードへ...
されます。
ですから、アドレスさえわかっていれば、その関数を呼び出す...
**関数ポインタ [#rd08d18a]
そこで、関数のアドレスを格納するポインタ pfn があるとしま...
このポインタを使って、関数を呼び出すには
int result = (*pfn)(x, y);
などと書けばよいのです。ただし
int result = pfn(x, y);
と書いても''全く同じに''解釈されます。*をつけてもつけなく...
この関数ポインタ独特の仕様は、関数ポインタをわかりにくく...
気がします。
さて、このように使う pfn を宣言するにはどうすればよいので...
今までと同じで「(*pfn)(int型, int型) (の返り値)は int ...
int (*pfn)(int, int);
先ほど述べたように pfn(int, int); と呼び出してもよいので...
× int pfn(int, int);
では、pfn という関数のプロトタイプ宣言になってしまうので...
必ず前者の方法を取ります。
関数ポインタに関数のアドレスを代入するには
int (*pfn)(int, int) = func; // func は int func(int x, ...
と書けばよいです。簡単ですね。
**関数ポインタの使い道 [#i4c16399]
int Kakeru(int x, int y)
{
return x * y;
}
int Tasu(int x, int y)
{
return x + y;
}
int Enzan(x, y, int (*pfn)(int, int))
{
return pfn(x, y);
}
int main()
{
printf("%d, %d\n",
Enzan(3, 4, Kakeru),
Enzan(3, 4, Tasu)
);
}
のように使えます。Enzan という関数ひとつで、掛け算と足し...
選べるようになるわけです。
C++にステップアップしたときに、virtual 関数というものに出...
それは関数ポインタを用いて実装されているのです。
*練習問題 [#o26cc95e]
つぎの文章に答えろ
この程度のクイズに答えられないならこのページをもう一度全...
第一問
int* a;と宣言したとき、aの型は&color(red){int型};である~
○ or ×~
第二問~
int x=10;~
int* p;~
p=&x;~
p[0]=20;~
pは配列ではないので、これはエラーが出てしまう~
○ or ×~
第三問
int x=20;
変数xは&color(red){スタック領域};におかえるか&color(red){...
第四問
ポインタと配列は同じものなので
char* c="VIPPER";
char c[]="VIPPER";
は同じ意味である。○ or ×
第五問
int** p;
p=(int)x;
このようなことはやってはいけない。○ or ×
第六問
int* p;
int a[10][10][10];
p=(int*)a;
p[500]=345;
こうするとa[?][?][?]が500に変わる。どこか。
終了行:
[[プログラミング言語/C]]
ポインタですー
単純な概念や使い方自体は簡単。
ただ、ポインタに関連するmalloc関枢やnew演算死を使うたびに...
ちょっとでもおかしな部分があればメモリリーク(メモリ使いす...
を引き起こす。マジクソ視ね
#contents
**ポインタ[#oa1e7a6e]
ではいよいよポインタの説明です。ポインタとはなんでしょう...
ズバリ言うと、~
&size(20){''ポインタとは、アドレスを入れるための&color(...
そう、int x=10;やchar c='s';と基本的に変わらねーよバーカw
ただそんだけ。~
intなら整数、charなら文字、と入れるものによって変数の型を...
では復唱しましょう。~
''ポインタとは、アドレスを入れるための変数である''~
''ポインタとは、アドレスを入れるための変数である''~
''ポインタとは、アドレスを入れるための変数である''~
はい、覚えましたね。~
では具体的にポインタはどうやって使うのでしょうか。~
一例として、int型変数のアドレスを入れるためのポインタを作...
int型変数のアドレスを入れるためのポインタを作るには、
int* ポインタの名前;
とします。例えば、
int* ptr;
のように。~
それでは実際のプログラムにしてみましょう。
#include <stdio.h>
main(){
int a = 5;
int* ptr; /** int型変数のアドレスを入れるためのポインタ...
ptr = &a; /** aのアドレスを入れた **/
}
このように使います。まだ何の役にも立ちませんね。~
ここでちょっと注意があります。~
世の中の9割以上の本やソースコードには、 int* ptr; ではな...
int *ptr;
と書いてあります(スペースの位置が違う)。~
これは int* ptr; と同じ意味なので慣れればどっちを書いても...
&size(20){''int* ptr; と書くことを強くすすめます''};~
これには三つほど理由があるのですが、現時点で言える理由は、~
+int *ptr; と書くと、int型の変数 *ptr に見える
+''int* という型が、int型変数のアドレスを入れるための型だ...
ということです。~
intと同様に、char型変数のアドレスを入れるためのポインタは...
またその他の型用のポインタについても、
型の名前* ポインタの名前;
で宣言できます。
**ポインタの使い道 [#a2c2261e]
さて、ポインタの概念と作り方はわかりましたが、このままで...
そこで使い道を説明していきます。~
***演算子 * について [#m6916aa3]
まず、変数の頭に & を付けるとその変数のアドレスを表したよ...
ポインタの先頭に * を付けると「''そのポインタに書いてある...
''アドレスとは変数の住所のことである''でしたから、アドレ...
「え? * ってポインタを作るときに使うやつでしょ?」はい、...
-は普通「引く」を表すけど、「数字の前についたらマイナスを...
では早く具体例をみて把握してしまいましょう。
#include <stdio.h>
main(){
int a = 5;
int* ptr;
ptr = &a; /** aのアドレスを、ptrに代入した **/
printf("%d\n", *ptr); /** 「ptrに書いてあるアドレス」に...
}
実行結果
5
~
ここで int *ptr; と書いてほしくないもうひとつの理由が出て...
上のプログラムを int *ptr; と書くと、
#include <stdio.h>
main(){
int a = 5;
int *ptr;
ptr = &a;
printf("%d\n", *ptr);
}
となります。''(ポインタを宣言するための * と、中身を得る...
しかし数学的センスのあるあなたはこう考えます。~
「そうか! *ptr が int型の変数だ と思えばいいんだ!」~
確かに5行目( int *ptr; )と7行目( printf(...) )を見るとそ...
ptr = &a;
が意味不明になります。つい
*ptr = a;
としてしまいそうです。~
これがなぜ間違いか考えてみましょう。~
5行目(int *ptr;)までの状態では、ptrには「何が入っているか...
なので、この状態で *ptr = a; とすると、「ptrに書いてある...
これはいけませんよね。
***本題 [#ufc3017d]
さて、だいぶ * の説明が長くなってしまいましたが、いよいよ...
次のコードがどんな結果を生じるか考えてみてください。
#include <stdio.h>
void inc(int x){
x = x + 1;
}
main(){
int a = 5;
inc(a);
printf("%d\n", a);
}
さてどうでしょうか?一見6が表示されそうですが、残念ながら...
学歴が低い人がinc を呼び出すと、「 x に a の値がコピー」...
例で理解しましょう。~
いま、あなたがmain関数だと思いましょう。あなたは a とい...
あなたにはincという友達がいます。あなたは彼の持っている...
そして彼が x にかいてある 5 を 6 に書き換えて、その後あ...
さて、どうなるでしょうか?~
そうです。''この方法ではaの値変わらない''のです。~
ここでポインタの登場です。上のプログラムをポインタを使っ...
#include <stdio.h>
void inc(int* x){
*x = *x + 1;
}
main(){
int a = 5;
inc(&a);
printf("%d\n", a);
}
友達のinc君が持っている int* 型の変数x(int型変数のアドレ...
この状態でincは *x に、 *x + 1 を代入します。~
*x は「xに書いてあるアドレスにある変数の、中身」でし...
(aの中身) = (aの中身) + 1;
となり、めでたくmainの持つ a が 6 に書き換わります!~
このように、ポインタは呼び出された関数から呼出し元の持つ...
**まとめ [#lb0799a6]
重要ポイントのまとめです。~
''アドレスとは、変数の住所のことである''~
''ポインタとは、アドレスを入れるための変数である''~
''*ptr とは、ptrに書いてあるアドレスにある変数の、中身...
分からなくなったときはこれを思い出してください。
**最後にちょっと注意 [#wd753712]
int* ptr; と書け と強くすすめましたが、ちょっとだけ問題が...
それは次のようにポインタをいくつかまとめて宣言する場合で...
int* a, b, c;
これは
int *a, b, c;
と同じだとみなされて、b と c はただのint型になってしまい...
int *a, *b, *c;
と書いてください。~
(本当は (int*) a, b, c; と書きたいんだけど、こうかくとキ...
*ポインタのいろいろ [#fe8e394c]
**演算子 -> について [#k44715ff]
さて、今、ある構造体と、それを指すポインタ(その構造体の...
struct PC{
double CPU_Hz;
int Monitor_size_tate;
int Monitor_size_yoko;
int HDD_GB;
};
main(){
struct PC myPC = {1.66, 1024, 1240, 500};
struct PC* PC_ptr = &myPC;
}
このとき、PC_ptr経由でmyPCのメンバにアクセスするにはどう...
printf("%f\n", PC_ptr.CPU_Hz);
こうでしょうか?~
ポインタの基礎でやった通り、PC_ptr の中には myPC のアドレ...
では構造体の本体はどこにあるかというと、「ポインタに入っ...
その中身を取得するには、演算子 * を使うのでした。なので、...
printf("%f\n", (*PC_ptr).CPU_Hz);
これで「PC_ptrに入っているアドレスにある構造体(myPC)の、C...
さて、(*PC_ptr).CPU_Hz のカッコがいるのかという問題ですが...
なぜなら、 10*1.5 を正しく 10*(1.5) と解釈( (10*1).5では...
なので、*PC_ptr.CPU_Hz とすると、*(PC_ptr.CPU_Hz) だと解...
これがどういう問題を引きこすかというと、例えば構造体がさ...
(*(*ptr1).ptr2).member
のようになり、非常に煩雑でわかりにくくなってしまいます。~
そこで、救世主 -> の登場です。~
この演算子を使うと、先ほどの例が次のようにかけます。~
printf("%f\n", PC_ptr->CPU_Hz);
これで「PC_ptrに書いてあるアドレスにある構造体の、CPU_Hz...
この演算子が威力を発揮するのは、さきほどわかりにくくなる...
ptr1->ptr2->member
カッコがなくなってスッキリしました。ポインタが何個連なっ...
(*(*(*(*ptr1).ptr2).ptr3).ptr4).member /** -> を使わない...
ptr1->ptr2->ptr3->ptr4->member /** 分かりやすい...
構造体とポインタを使ったプログラムでは、積極的に -> を使...
// 入門用とそれ以外の区切り
#br
#hr
CENTER:&color(red){ここから下は初心者の入門用ではありませ...
#hr
#br
// 入門用とそれ以外の区切り
// 自作関数の項目から外してポインタの後に移動しました
*関数ポインタ [#d011c48b]
**関数ポインタ [#d171c1de]
''(注意!)これは初学者向けではありません。最低でも関数と...
関数のアドレスを保持するポインタです。
実行する関数を動的に変えられたり、関数の配列を作ったり、...
つまり、func[0]()が「VIPPER死ね」と表示する、func[1]()が...
普通関数の引数には関数はいれられないけど~
func(func2){~
func2();~
}~
みたいなことができる!便利!
まずは関数の宣言から見ていきます。
int MyFunc(float);
これはint型の値を返し、引数にfloat型をとる関数の宣言です...
定義時に引数名を省略しても、関数内部で使えないだけで問題...
関数ポインタは、入る関数の引数と戻り値が分かっている必要...
宣言の形は、
関数の戻り値 (*関数ポインタ名)(関数のとる引数)
です。
上記のMyFunc関数が入る関数ポインタpFuncを宣言するには、
int (*pFunc)(float);
とします。
これは、
-int型の戻り値を返す
-ひとつのfloat型が仮引数の
-"pFunc"という名前の関数ポインタ
です。
今まで秘密にしてきましたが(?)、関数ポインタは通常のポ...
入れる値とは、関数のアドレスです。
以下は関数、関数ポインタ、それへの代入の一通りの例です。
int MyFunc_1(flaot a){ return (int)a + 1; }
int MyFunc_2(flaot a){ return (int)a + 99; }
int (*pFunc)(float);
/* .... */
pFunc=MyFunc_1;
printf("MyFunc_1 %d\n",pFunc(2.0f));
pFunc=MyFunc_2;
printf("MyFunc_2 %d\n",pFunc(1.0f));
実行結果は
MyFunc_1 3
MyFunc_2 100
となります。
関数ポインタですが、変数というからには型があります。型が...
では、関数ポインタの型とは何なのでしょうか。
下のpFuncの場合、
int (*pFunc)(float);
型は
int (*)(float);
です。どう見てもアナルです。爆発はしません。
typedefの使い方は、
typedef int INTEGER;
これでintの別名"INTEGER"の完成でしたね。
よく見てください。
int INTEGER;
後ろ部分は、『int型の変数の宣言』と形が同じです。
という事は、関数ポインタのtypedefも、
『int (*)(float)型の変数の宣言』に"typedef"をくっつけたも...
つまり、下です。
typedef int (*pFunc_t)(float);
これで『int (*)(float)の別名pFunc_t』のできあがりです。
次からは、
pFunc_t pFunc;
として関数ポインタを宣言することができます。
すっきりしましたね。
通常のポインタには、多重ポインタがあります。『ポインタの...
int v=0;
int* p = &v; //int型へのポインタ
int** pp = &p; //int*型へのポインタ
int*** ppp = &pp; //int**型へのポインタ
***ppp = 99; //v = 99
関数ポインタも例外でなく、『関数ポインタへのポインタ』な...
typedef int (*pfunc_t)(void);
pfunc_t* ppfunc; //int (**ppfunc)(void)と同等
pfunc_t** pppfunc; //int (***pppfunc)(void)と同等
pfunc_t*** ppppfunc; //int (****ppppfunc)(void)と同等
int (*****pppppfunc)(void); //pfunc_t**** pppppfuncと同等
こんなに多重ポインタを作ることはまずありえませんが、『関...
関数の引数に関数ポインタのポインタを渡して見る例です。
#include <stdio.h>
int add(int x,int y){ return x+y; } //足し算をする関数
int mlt(int x,int y){ return x*y; } //掛け算をする関数
typedef int (*pfunc_t)(int,int);
void set_1(pfunc_t*f){ *f=add; return; }
void set_2(pfunc_t*f){ *f=mlt; return; }
int main(void){
pfunc_t pf;
set_1(&pf);
printf("add %d\n",pf(99,1));
set_2(&pf);
printf("mlt %d\n",pf(33,3));
return 0;
}
pfunc_t pfの宣言外部で、pfの内容を操作しています。
実行結果は次のようになります。
add 100
mlt 99
**C++に対するアプローチ [#w0199a12]
''(注意)ここはCの範囲ではありません、Cではできないことを...
いやむしろなぜCの項に用意したんだよww(関数ポインタの有...
***関数オーバーロード時の動作 [#g698fbf5]
関数Functionをオーバーロードしています。
#include <stdio.h>
void Function(long int){
printf_s("long intはintよりも大きいから豊乳じゃと!...
}
void Function(short){
printf_s("shortはintよりも小さいからは貧乳じゃと!?...
}
void Function(void){
printf_s("ツルペタ始まったな\n");
}
int main(void){
{
void (*pfunc)(short);
pfunc=Function; //コンパイル時に型が自動推論される
pfunc(short());
}
{
void (*pfunc)(void);
pfunc=Function; //コンパイル時に型が自動推論される
pfunc();
}
return 0;
}
実行結果は
shortはintよりも小さいからは貧乳じゃと!?
ツルペタ始まったな
となります。
***メンバ関数に対する動作 [#vbe096d6]
メンバ関数のポインタをとってみましょう。
class CFoo{
public:
void func_v(){
printf_s("仮想関数だと!?\n");
}
static void func_s(){
printf_s("ぼくは静的関数ちゃん!\n");
}
};
/* .... */
void (*pfunc)();
pfunc=CFoo::func_v; //ここでコンパイルエラー
CFoo::func_vはCFooのインスタンスがないと実行できない関数...
staticがないので当たり前ですね。
では以下はどうでしょうか。
インスタンスを生成してメンバ関数をとってみます。
void (*pfunc)();
CFoo a;
pfunc=&a.func_v; //error C3867 , error C2440
これもエラーです。エラー内容を見てみましょう。~
:error C3867:"CFoo::func_v"のポインタを取得したかったら"...
どうやら、ポインタを取得するには"a.func_v"ではなく、"&CFo...
でもこれではせっかくインスタンスを作ったのに意味がありま...
:error C2440:"a.func_v"は"void(__thiscall CFoo::*)()"型...
という事は"pfunc"を"void(__thiscall CFoo::*pfunc)()"と宣...
結論から言えば、理論的にはこれで解決です。コンパイルでき...
ただし、重要な問題が発生します。~
それは、この場合のpfuncは『関数ポインタ』ではなく『メンバ...
メンバポインタについては『関数ポインタのC++に対するアプロ...
追記:~
gccで同様のコードをコンパイルしたところ、
error: ISO C++ forbids taking the address of a bound mem...
Say ‘&CFoo::func_v’
とのエラーが出ます。'''(訳:ISO C++では、インスタンスのメ...
要は仕様で禁止されている、ということです。~
理由は知りません。ごめんネ
*&color(red){第二番目の説明!!!!!うおおおおおおお};~~...
**アドレス [#ga610581]
int i = 10;
というコードがあるとします。
これがコンパイルされたとき、EXEのなかではどう表現されてい...
変数の値は、実際にはメモリのどこかに保存されています。
そして「どこに保存されているか」というメモリの住所は、番...
上記のコードは、たとえば
45450721番目のメモリに 10 を代入せよ
のようにコンパイルされます。
何番目のメモリを使うかは、コンパイラが勝手に決めてくれま...
C言語を使えば、このようにどの変数にどこのメモリを割り当て...
自動的に割り振ってくれるわけですね。
この、"何番目のメモリ" という番号のことを「アドレス」と呼...
**ポインタ [#b9e4e979]
さて、上記のコードにおいて、変数 i が何番目のメモリに格納...
用語を用いて言い換えれば、変数 i のアドレスが知りたいわけ...
そういう時は &i と書けばおkです。上記の例では &i は 4545...
C言語には、アドレスを格納するための特別な変数型があります...
ポインタの宣言は
int *p;
のように書きます。たとえば
int i = 10;
int *p = &i;
などとします。この p には、たとえば 45450721 などが格納さ...
この例では、p のことを「i へのポインタ」といいます。
「i へのポインタ」とは、"i のアドレスが格納されているポイ...
では 45450721番目のメモリに入っている値(すなわち i に格...
printf("%d", *p);
と書きます。
*p のことを、「p が指す値」と呼びます。
**ポインタの宣言 [#f183fa34]
int i;
という宣言文は「i は int型である」と宣言しています。これ...
int *p;
という宣言文は「*p は int型である」、もしくは「pとは、*p ...
ちょっと複雑な例を考えましょう。intへのポインタを10個並べ...
ap[index] はポインタですね。その指す値(int型)は、 *(ap[...
int *(ap[10]);
と書けばよいのです。
次に、intを10個並べた配列へのポインタ pa が欲しいときはど...
*pa は配列ですから、(*pa)[index] は int 型ですね。で...
int (*pa)[10];
と宣言すればよいです。
*関数の引数について [#r19e389b]
**値渡し [#j2c6d7ac]
#include <stdio.h>
void func(int a)
{
a = 100;
}
int main()
{
int b = 1;
func(b);
printf("%d", b);
}
というプログラムを実行すると、"1" と表示されます。決して ...
具体的に追っていきましょう。&a は 45450721、&b は 6741 だ...
main関数のなかでは、まず
int b = 1;
で 6741番目のメモリが 1 になります。次に
func(b);
では、45450721番目のメモリに 1 がコピーされて
func の先頭にジャンプします。
func のなかで、45450721番目のメモリに 100 が代入されます...
printf("%d", b);
に戻ってきたとき、6741番目のメモリには何が入っているでし...
1 ですね。
このように、関数の引数には、呼び出し元の変数の値がコピー...
関数内部ではその''コピーされたものしかいじれません''。
**関数の引数にポインタを用いると [#m8becd41]
#include <stdio.h>
void func(int *p)
{
*p = 100;
}
int main()
{
int b = 1;
func(&b);
printf("%d", b);
}
を最初から追っていきましょう。&p は 45450721、&b は 6741 ...
まず
int b = 1;
で 6741番目のメモリが 1 になります。つぎに
func(&b);
で、45450721番目のメモリに 6471 がコピーされて、funcの先...
funcのなかで、45450721番目のメモリに格納されている番号(6...
メモリに 100 が代入されます。そうして
printf("%d", b);
に戻ってきたとき、6741番目のメモリには 100 が入っています。
ですから、画面には "100" と表示されることになります。
このように、ポインタを関数の引数に用いると、
関数内で呼び出し元の変数をいじることができるようになりま...
*配列 [#k8dd863f]
**配列 [#afd152dd]
char ac[100];
と配列を宣言したとします。配列は、''メモリ上に連続して並...
&(ac[0]) が 45450721 だとすると
&(ac[0]) = 45450721
&(ac[1]) = 45450722
&(ac[2]) = 45450723
&(ac[3]) = 45450724
.
.
.
とならびます。そうして、ac を単独で用いると、&(ac[0])...
printf("%p\n", ac); // %d が int を表示するように...
printf("%p\n", &(ac[0]));
このコードを実行すると、同じ値が表示されます。
このように、配列の識別子は、その配列の先頭のアドレスに変...
変換されるだけで、配列がアドレスそのもの、というわけでは...
また、先頭のアドレスが格納されているポインタから、
配列へ逆変換することは''できません''。
**配列とポインタ [#ie104a63]
さて、上記の ac を使って
*(ac + 3);
は何を表すでしょう? ac + 3 は 45450724 ですから、
それに * をつけると ac[3] にアクセスできますね。
一般に
ac[index] ⇔ *(ac + index)
の関係が成り立ちます。
ac は &(ac[0]) に変換されるといいましたが、ac[0] は c...
&(ac[0]) は char へのポインタです:
char ac[100];
char *pc = ac;
と書くことができます。配列をポインタに代入しているように...
実際には''配列の先頭のアドレスをポインタに代入しているだ...
このポインタを使って、ac の要素にアクセスすることを考えま...
たとえば ac[3] にアクセスしたければ
*(pc + 3)
と書けばよいですね。この節を先頭から読み直すと、これが
pc[3]
と書ける気がしませんか? 書けます。ですが
pc[3] は、''単なる *(pc + 3) の言い換えに過ぎません''。
ac[3] は、「配列 ac の 3 番目の要素」を指しますが、
pc[3] は、単に*(pc + 3) を言い換えただけです。
この違いは今は気になりませんが、2次元配列を考えたときに...
何はともあれ、ポインタをつかって配列と同じような書き方が...
でもポインタは(ここでは)配列の要素を指し示すものであっ...
char *pc = ac + 5; // &(ac[0]) + 5 のこと
とすれば、pc[3] ⇔ *(pc + 3) ⇔ *(ac + 5 + 3) ⇔ ac[5 + 3] ...
このように、ポインタを使って、配列の先頭をずらしたように
見せかけることができます。あくまで''見せかけるだけ''です。
**配列を関数に渡すには [#ka5f6988]
配列を関数に渡したいとき、仮引数の宣言は 2 種類あります。
void func(char pc[]);
void func(char *pc);
これはどちらも''全く同じ''です。ポインタの宣言になってい...
そしてこの関数を呼び出すとき、配列そのものを渡すことはで...
代わりに配列の先頭の要素のアドレスを渡します。
char ac[10];
func(ac); // func(&(ac[0])); だとみなされる
それでもこれまで見てきたように、func のなかで
このポインタをあたかも配列のように用いることができるので...
*オブジェクトのサイズ [#j3e4b75f]
話は変わりまして、今度はオブジェクト(≒変数)のサイズの話...
**char, short, int, long [#k0c3df0f]
これらは全て整数を表しますが、どうしてこんなに沢山の種類...
整数をノートに記録するとき、
大きな数は沢山の桁数が必要です。
"4" なら1桁ですみますが、"123456" なら 6 桁も必要です。
コンピュータでも同じで、大きな数を表すには沢山のメモリが...
使用するメモリ量の順に char ≤ short ≤ int ≤ long...
つまり、char より long のほうが、大きな数まで表すことがで...
メモリ量の最小単位を「1バイト」といいます。
たとえば、Intel 32bit CPU ならば、int は4バイトです。
int i = 200000000;
と書くと、i の内容は 4 つに分割して格納されます。
たとえば &i が 45450721 なら、45450721番 ~ 4545074番のメ...
そうして、&i は使用されているメモリのうち、先頭の番号を表...
これまで「○○番目のメモリに格納される」と表現してきました...
これは正しくは、「○○番から××番までのメモリに格納される」...
(が、誤解の生じない限り、これからも従来の表現を用います)
**ポインタの加減算 [#wd8f6ca8]
上のほうで
char ac[100];
と宣言したら、5 番目の要素のアドレスは ac + 5 で得られる...
これは char 型が1バイトだからです。ですが、int 型の配列を
int ai[100];
と宣言したとしましょう。そして &(ai[0]) は 45450721 だと...
すると、(1 つの要素の大きさが 4 バイトなので) 5 番目の...
45450721 + (5 * 4) になります。
では 5 番目の要素のアドレスは ai + (5 * 4) と書かないとい...
実はこれは間違いです。単に
ai + 5
と書くだけでよいのです。x4 の部分は、コンパイラが勝手に行...
同様に
int ai[100];
int* pi = ai;
++pi;
というコードを実行すると、&(ai[0]) が 45450721 ならば、pi...
なります。++pi; は単なるインクリメントではなく、
ポインタが指す型のサイズ分だけ加算されるのです。
逆に言えば、ポインタを使うときは、コンパイラがポインタが...
知っている必要があります。
*配列再び [#b678afb4]
今度は2次元配列について考えます。
**2次元配列 [#vf3c36bc]
char aac[3][2];
と2次元配列を宣言したとします。するとこれは
「要素が2つの配列を、3つ並べたもの」になります。
aac[0][0], aac[0][1], aac[1][0], aac[1][1], aac[2][0], ...
の順に、''全ての要素が連続して''並べられます。
そして &aac[0][0] が 45450721 だとすると、aac[x][y] のア...
45450721 + (x * 2) + y
で計算できます。
aac[x] と一つ目の添え字だけを指定すると、{ aac[x][0], aac...
とならんでいる部分を char 型の配列だとみなしたものが得ら...
すなわち aac[x] は &(aac[x][0]) に変換されます。
char* pc1 = aac[1];
などと書けるわけです。pc1 の値は、実際には
45450721 + (1 * 2)
と計算されます。
そうして pc1[x] ⇔ aac[1][x] になります。
では aac を単独で用いたときは、何に変換されるのでしょうか?
aac[x] が char 型の配列なのですから、aac は「char型の配列...
ですから、aac は、「char 型の配列」へのポインタに変換され...
配列へのポインタは上のほうでやりましたね。
char (*pac)[2] = aac;
と変換できるわけです。型はややこしいですが、値は 45450721...
この場合、++pac; などと書けば、要素 2 つの配列の長さ分だ...
になります。すると pac[x][y] は aac[x + 1][y] と同じもの...
気づいた方がおられるでしょうか?
char aac[3][2];
char (*pac)[2] = aac;
char *pc0 = aac[0];
printf("%p\n%p\n", pac, pc0);
を実行すると、pac も pc0 も同じ値であることがわかります。...
値は同じでも''型が違う''のです。
pac[x] は char 型の配列になりますが、pc0[y] は char 型の...
**関数に渡すには [#c8ea3c1f]
関数の引数に int aai[3][2] を渡したいとしましょう。仮引数...
よいでしょうか?
配列を渡すには、その先頭の要素へのアドレスを受け渡しする...
今回の場合、aai は「配列の」配列ですから、受け取る側は「...
なければいけません。すなわち
void func(int (*pai)[2]);
と宣言します。もっと簡単に
void func(int pai[][2]);
と書いても同じです。この "2" を省略することはできません。...
++pai;
というコードがあったとき、「要素 2 つの int 型配列」分の
大きさだけ加算する必要があります。同様に
pai[2]; // *(pai + 2) のこと
というコードがあったとき、「pai の値よりも『要素 2 つの i...
進んだところ」から始まる配列を取得しないといけません。
このように要素 2 つ、というのは重要な情報なのです。
**似非2次元配列 [#u8e14d09]
お気づきの方はいるでしょうか? main 関数の宣言は
int main(int argc, char *(argv[])); // これは char *argv...
int main(int argc, char (*argv)[]);
int main(int argc, char **argv);
int main(int argc, char argv[][]);
などと書きます(全部同じです。char **argv; が宣言されたこ...
最後の宣言方法を見るとわかりますが、argv[][ここがない] で...
普段 argv[1][0] などと2次元配列と同じように用いています...
これは本来の2次元配列ならありえない話です。
なぜなら、argv[1] というのが、argv[0] から「要素いくつ分...
離れたところにあるか分からないのですから。
実は argv に渡されるのは、2次元配列ではなく、「ポインタ...
これの宣言は次のようにします。
char ac0[3];
char ac1[6];
char ac2[2];
char *(apc[3]) = { ac0, ac1, ac2 }; // { &(ac0[0]), &(ac...
apc[x] は、配列先頭の要素へのポインタですから
*(apc[x] + y)
もしくは
apc[x][y]
と書けば、x 番目の配列の、第 y 要素が得られることになりま...
この場合、コンパイラは要素にアクセスするために、アドレス...
そのアドレスは最初から配列内に格納されているのですから。
そして、「ポインタがならんだ配列」を関数に渡したいときは...
アドレスを渡すのでしたね。ですから関数の仮引数は「ポイン...
宣言しないといけません。それが
void func(char **ppc);
void func(char *(ppc[]));
などとなるのです。そのほかの書き方でもかまいません。
ちなみに、今回の変数を使って
printf("%p\n%p\n", apc, apc[0]);
を実行してみてください。前回と違い、今度は違うアドレスが...
*関数ポインタ [#b693a8df]
**関数のアドレス [#p1450f63]
プログラムが実行されているとき、変数の値はメモリのどこか...
そのメモリの番号をアドレスというのでした。
今回は、そのプログラムのコード自体に焦点を当てます。
実はプログラムというのは、メモリにロードされて実行されま...
つまり、プログラム自体もメモリのどこかに格納されているの...
プログラム中で関数を使うなら、その関数自体がメモリのどこ...
そこで、関数が格納されているメモリ領域の、先頭の番号を「...
呼ぶことにしましょう。
**関数の呼び出され方 [#naa2d704]
void func(void)
{
printf("hello, world!\n");
}
int main()
{
func();
}
というプログラムがあって、func のアドレス(func の先頭の...
が 45450721 だとしましょう。すると main 関数内の
func();
という行は「45450721番目のメモリに格納されているコードへ...
されます。
ですから、アドレスさえわかっていれば、その関数を呼び出す...
**関数ポインタ [#rd08d18a]
そこで、関数のアドレスを格納するポインタ pfn があるとしま...
このポインタを使って、関数を呼び出すには
int result = (*pfn)(x, y);
などと書けばよいのです。ただし
int result = pfn(x, y);
と書いても''全く同じに''解釈されます。*をつけてもつけなく...
この関数ポインタ独特の仕様は、関数ポインタをわかりにくく...
気がします。
さて、このように使う pfn を宣言するにはどうすればよいので...
今までと同じで「(*pfn)(int型, int型) (の返り値)は int ...
int (*pfn)(int, int);
先ほど述べたように pfn(int, int); と呼び出してもよいので...
× int pfn(int, int);
では、pfn という関数のプロトタイプ宣言になってしまうので...
必ず前者の方法を取ります。
関数ポインタに関数のアドレスを代入するには
int (*pfn)(int, int) = func; // func は int func(int x, ...
と書けばよいです。簡単ですね。
**関数ポインタの使い道 [#i4c16399]
int Kakeru(int x, int y)
{
return x * y;
}
int Tasu(int x, int y)
{
return x + y;
}
int Enzan(x, y, int (*pfn)(int, int))
{
return pfn(x, y);
}
int main()
{
printf("%d, %d\n",
Enzan(3, 4, Kakeru),
Enzan(3, 4, Tasu)
);
}
のように使えます。Enzan という関数ひとつで、掛け算と足し...
選べるようになるわけです。
C++にステップアップしたときに、virtual 関数というものに出...
それは関数ポインタを用いて実装されているのです。
*練習問題 [#o26cc95e]
つぎの文章に答えろ
この程度のクイズに答えられないならこのページをもう一度全...
第一問
int* a;と宣言したとき、aの型は&color(red){int型};である~
○ or ×~
第二問~
int x=10;~
int* p;~
p=&x;~
p[0]=20;~
pは配列ではないので、これはエラーが出てしまう~
○ or ×~
第三問
int x=20;
変数xは&color(red){スタック領域};におかえるか&color(red){...
第四問
ポインタと配列は同じものなので
char* c="VIPPER";
char c[]="VIPPER";
は同じ意味である。○ or ×
第五問
int** p;
p=(int)x;
このようなことはやってはいけない。○ or ×
第六問
int* p;
int a[10][10][10];
p=(int*)a;
p[500]=345;
こうするとa[?][?][?]が500に変わる。どこか。
ページ名: