悪魔からの挑戦の招待状で、あなたが見たことがあるか見たことがないC言語のポインタはここにあります.
9416 ワード
前言
多くの学生が最初に言語に触れることができるのはC/C++だと信じています.その中のポインタも頭が痛い部分です.ポインタだけで「Cとポインタ」という本を出すことができるので、ポインタの強さを見ることができます.しかし、誤ってポインタを使うと、これらのポインタは悪魔のようにあなたのプログラムを直接崩壊させる可能性が高い.
3ヶ月前、私は
前の宣言:テーマには、配列、ポインタ、関数、およびそれらの様々な謎の複合体が含まれます. 本文の後に言及したいくつかのポインタは実用性を考慮しないで、ゲームをしていると思っていますが、適切な場合はこれらのポインタについて必要な説明をします.理性的にこれらのポインタに対処してください. ポインタに不快感や恐怖感を感じ始めたら、C言語への情熱を傷つけないように、早めに離れることをお勧めします.
これらの針はすべて悪魔ですか?
次のすべてのテーマは、自分の考えをコメントに書くことができます.
ブロンズ
次のポインタ、配列の具体的なタイプをテキストで説明します.
次は適当にセリフを残して考えて、考えてから答えを下に見ることができます.
ブロンズ問題解
初心者のCポインタについては、基本的に答えられるはずです.
白銀
次のポインタ、配列、関数の具体的なタイプをテキストで説明します.
これらのポインタはよく見られる実用的なもので、よく考えてから答えを下に見ることができます.
白銀の問題解
以下の関数の具体的なタイプを文字で説明してください.ポインタの場合は、読み書きが可能な場合(コードで説明できます):
警告:ここまでこれらのポインタに不快感がある場合は、C/C++言語を放棄する考えが起こらないように、早めに離れることをお勧めします.もしあなたが無理に堅持するなら...考えたら答えを下に見ることができます.
黄金の問題
ダイヤモンド
以下のポインタ、関数、関数ポインタの具体的なタイプをテキストで説明してください.
実用性が徐々に低下しています...
ダイヤモンド
マスター
もしあなたがこの一歩を堅持することができて、あるいはすでに治療を放棄して後続の内容を見たいならば、それからあなたが直面するのは各種の不思議な考え、悪魔のような指針かもしれません.これらの奇妙な書き方はコンパイルを通じて、まるで悪魔です.
後のテーマを文字で記述するのは気持ち悪いことを考慮して、偽lambdaの記述方式を使って、関数や関数ポインタを分解することができます.例は次のとおりです.
矢印は、戻り値のタイプを指します.
では...幸運を祈って、偽lambdaの説明で次の関数と関数ポインタを分解してください.
マスター
もしあなたが上のすべてのテーマを完成することができたら、あなたは隠れた称号を獲得します:人形のコンパイラ.
ここのポインタはほとんどあなたの一生に会えるすべてのポインタです.残りの変種ポインタについては,基本的には上記の方法をめぐって構成されている.結局私たちはまだC++の引用を加えていません...
穴が大きすぎても間違いがあるのは避けられないので、指摘を歓迎します.
今、私たちは悪魔です.
多くの学生が最初に言語に触れることができるのはC/C++だと信じています.その中のポインタも頭が痛い部分です.ポインタだけで「Cとポインタ」という本を出すことができるので、ポインタの強さを見ることができます.しかし、誤ってポインタを使うと、これらのポインタは悪魔のようにあなたのプログラムを直接崩壊させる可能性が高い.
3ヶ月前、私は
?.c
の書類を編纂しました.中には多くのよく使われているポインタのタイプから、コンパイラを覗くだけで発生する恐怖の造物まで広がっています.面白さを増すために、私はこれらのポインタをセグメントに分けて、このセグメントのほとんどのポインタを認識してこそ、セグメントを上げることができます.今、私はこれらの悪魔のような指針を世界に公開し、自虐に挑戦することを歓迎します.前の宣言:
これらの針はすべて悪魔ですか?
次のすべてのテーマは、自分の考えをコメントに書くことができます.
ブロンズ
次のポインタ、配列の具体的なタイプをテキストで説明します.
int * p0;
int arr0[10];
int ** p1;
int arr1[10][10];
int *** p2;
int arr2[10][10][10];
次は適当にセリフを残して考えて、考えてから答えを下に見ることができます.
ブロンズ問題解
初心者のCポインタについては、基本的に答えられるはずです.
int * p0; // p0 int
int arr0[10]; // arr0 int (10 )
int ** p1; // p1 int
int arr1[10][10]; // arr1 int (10*10 )
int *** p2; // p2 int
int arr2[10][10][10]; // arr2 int (10*10*10 )
白銀
次のポインタ、配列、関数の具体的なタイプをテキストで説明します.
int (*p3)[10];
int *p4[10];
int *func0(int);
int func1(int * p);
int func2(int arr[]);
これらのポインタはよく見られる実用的なもので、よく考えてから答えを下に見ることができます.
白銀の問題解
int (*p3)[10];
のうちp3
は*
と結合し、p3
がポインタであることを示し、(*p3)
を外し、残りはp3
というポインタが指すもの(すなわちint[10]
)である.答え:p3
は [int (10 )]
で、符号化記述であるp3
はint(*)[10]
タイプです.int *p4[10];
のうちのp4
は優先度を考慮して、[]
ではなく*
と先に結合し、p4
が10要素を含む配列であることを示し、p4[10]
を外し、要素タイプはint*
である.答え:p4
はint (10 )
で、符号化記述であるp4
はint* [10]
タイプです.int *func0(int);
のfunc0
は、先に括弧と結合され、括弧内はパラメータタイプのみであり、func0
が関数であり、戻り値タイプはint*
であることを示している.答え:f0 ( int, int )
int func1(int * p);
回答:func1 ( int , int)
int func2(int arr[]);
では、int arr[]
の書き方に注意して、関数でしか書けないのは、コンパイラがarr
をポインタタイプ、すなわちint * arr
の書き方と等価であると判定しているからである.答え:func2 ( int , int)
ゴールド以下の関数の具体的なタイプを文字で説明してください.ポインタの場合は、読み書きが可能な場合(コードで説明できます):
int func3(int arr[10]);
int func4(int *arr[10]);
int func5(int(*arr)[10]);
int func6(int arr[10][10]);
int func7(int arr[][10]);
int func8(int **arr);
const int * p5;
int const * p6;
int * const p7;
const int * const p8;
警告:ここまでこれらのポインタに不快感がある場合は、C/C++言語を放棄する考えが起こらないように、早めに離れることをお勧めします.もしあなたが無理に堅持するなら...考えたら答えを下に見ることができます.
黄金の問題
int func3(int arr[10]);
ここでint arr[10]
と思ったら、この関数のパラメータはint[10]
ほど簡単だと思いますか?それは間違っています.実はここのarr
はint *
タイプです!1つの配列を値で渡すと、1つの配列をコピーして関数に使う必要があると思って、10個でいいと思っています.int arr[1000000000]
ですね.1回のcopyで爆桟の楽しみを楽しむことができます.したがって、ここでコンパイラはint *
タイプと見なし、後ろの10を無視し、実際にはポインタを値で渡すことで、メモリを大幅に節約できますが、間接性と限界リスク(収益はリスクよりはるかに大きい)が増加します.ここの10は実際には開発者であるあなたに注意するだけで、伝達された配列(orポインタ)はそのアドレスの後ろのsizeof(int) * 10
バイトがアクセスできることを保証しなければならない.要素の個数が10以上の配列を入力できます.10未満の場合は...結果は自負する.答え:func3 ( int , int)
int func4(int *arr[10]);
という問題も言えますが、arr
は実際にはint **
タイプですが、開発者であるあなたは、要素の個数が10以上のintポインタ配列が入ることを保証する必要があります.答え:func4 ( int , int)
ガイドライン1:関数パラメータの配列とは実際にはポインタタイプであるint func5(int(*arr)[10]);
arr
自体が配列ではなくポインタであることに注意!配列を指すポインタ!答え:func5 ( [int (10 )] , int)
int func6(int arr[10][10]);
arr
はint**
だと思いますか?それはまた間違っている.int**
のタイプに劣化すると、arr[3][5]
のような動作は、送信されたポインタに対して非常に危険である.通常、int**
は、2つの次元が動的に割り当てられた2次元配列(各ポインタが動的配列である動的ポインタ配列)を指すために使用され、すなわち、int*
ではなくint
として第1行の要素を扱う.1つの2次元配列をint**
に強制的に変え,再び参照を解除すると野ポインタの危険な操作を引き起こす.したがって、実際には、コンパイラは、第1次元の[10]
を*として処理するだけであり、int func6(int (*arr)[10]);
に等価である.答え:func6 ( [int (10 )] , int)
ガイドライン2:関数パラメータの多次元配列では、最初の次元のみがポインタとして処理されます.int func7(int arr[][10]);
は前の問題と等価です.答え:func7 ( [int (10 )] , int)
int func8(int **);
ここでは、2つの次元が動的に割り当てられた2次元配列(すなわちintポインタ配列)のみが受け入れられる.答え:func8 ( int , int)
const int * p5;
「C++Primer」は、下位const、すなわち定数を指すポインタと呼ばれ、データは変更できないが、ポインタ自体は置き換えることができる.例:p5 = NULL; // !
*p5 = 5; // !
const int num = 5
のような最上位constでは、データ自体を修正することはできません.int const * p6;
とp5
は等価である.int * const p7;
「C++Primer」は最上位constと呼ばれています.すなわち、ポインタ自体は定数であり、その指すデータは修正できますが、ポインタ自体は置換できません.例:p5 = NULL; // !
*p5 = 5; // !
const int * const p8;
は、指し示すデータとポインタ自体が変更できないように、最上位と最下位のconstを含む.ダイヤモンド
以下のポインタ、関数、関数ポインタの具体的なタイプをテキストで説明してください.
int (*pfunc1)(int);
int (*pfunc2[10])(int);
int (*(*pfunc3)[10])(int);
int func9(int (*pf)(int, int), int);
const int ** p9;
int * const * p10;
int ** const p11;
int * const * const p12;
実用性が徐々に低下しています...
ダイヤモンド
int (*pfunc1)(int);
答え:pfunc1 ( int, int)
、符号化記述、すなわちint(*)(int)
int (*pfunc2[10])(int);
f 2は、まず[10]と結合し、f 2が配列であることを示し、f2[10]
を取り外すと、要素タイプはint(*)(int)
である.答え:pfunc2 ( int, int) (10 )
int (*(*pfunc3)[10])(int);
関数は配列の要素として使用できませんが、関数ポインタは使用できます.前の苦難を経て、これは配列を指すポインタであり、配列の要素は関数ポインタであることがわかるはずだ.答え:pfunc3 [ ( int, int) (10 )]
int func9(int (*pf)(int, int), int);
の1つの関数には、通常、このように伝達される関数をコールバック関数と呼ぶ関数ポインタをパラメータとして受け入れる必要があります.答え:func9 ( { ( {int, int}, int) , int}, int)
const int ** p9;
具体的には、以下の例を参照することができる.p9 = NULL; // !
*p9 = NULL; // !
**p9 = 5; // !
int * const * p10;
具体的には、以下の例を参照することができる.p10 = NULL; // !
*p10 = NULL; // !
**p10 = 5; // !
int ** const p11;
具体的には、以下の例を参照することができる.p11 = NULL; // !
*p11 = NULL; // !
**p11 = 5; // !
int * const * const p12;
具体的には、以下の例を参照することができる.p12 = NULL; // !
*p12 = NULL; // !
**p12 = 5; // !
マスター
もしあなたがこの一歩を堅持することができて、あるいはすでに治療を放棄して後続の内容を見たいならば、それからあなたが直面するのは各種の不思議な考え、悪魔のような指針かもしれません.これらの奇妙な書き方はコンパイルを通じて、まるで悪魔です.
後のテーマを文字で記述するのは気持ち悪いことを考慮して、偽lambdaの記述方式を使って、関数や関数ポインタを分解することができます.例は次のとおりです.
int (*pfunc1)(int); // (*pfunc1)(int)->int
int f1(int); // f1(int)->int
int func9(int (*pf)(int, int), int); // func9((*pf)(int, int)->int, int)->int
矢印は、戻り値のタイプを指します.
では...幸運を祈って、偽lambdaの説明で次の関数と関数ポインタを分解してください.
int (*pfunc4)(int*());
int (*func10(int[]))[10];
int (*func11(int[], int))(int, int);
int (*(*pfunc5)(int))(int[10], int);
int (*(*pfunc6)(int[10]))[10];
int (*(*pfunc7[10])(int[10]))[10];
int (*func12(int(*(int(*())))));
int (*(*(*pfunc8)[10])(int[], int))(int, int);
マスター
int (*pfunc4)(int*());
基本的には形参のint*()
に倒れているという鬼の書き方ですね.当初はどうやって編集できるのか分かりませんでしたが、そうでなければ悪魔の針とは言えませんでした.ハハハ...どうせこの文章の中で、可読性をすべて幽霊に会わせましょう.Visual Studioがあれば、この声明をVSに貼り付け、カーソルを上に置くと、実際に形参のint*()
がint*(*)()
と解読されることがわかります.答え:(*pfunc4)((*pf)()->int*)->int
int (*func10(int[]))[10];
というのは『C++Primer』で知り合ったようなもので、前に似たような問題をしたことがあると、この関数がわかり、配列を指すポインタが返されます.関数呼び出しのような関数の部分func10(int*)
を取り除くことができます.残りは戻り値タイプint(*)[10]
です.答え:func10(int*)->int(*)[10]
int (*func11(int[], int))(int, int);
関数は、関数ポインタを返します.答え:func11(int*, int)->int(*pf)(int, int)
int (*(*pfunc5)(int))(int[10], int);
関数ポインタで、関数が関数ポインタを返します.答え:(*pfunc5)(int)->((*pf)(int*, int)->int)
int (*(*pfunc6)(int[10]))[10];
回答:(*pfunc6)(int*)->int(*)[10]
int (*(*pfunc7[10])(int[10]))[10];
回答:(*pfunc7[10])(int*)->int(*)[10]
int (*func12(int(*(int(*())))));
これはまた何の鬼ですか???我々はまず既存の経験に基づいて積層解結合を行った.まずこのようなint(*())
の外層の括弧は取り除くことができて、ただ1つの誤導で、それからint*()
の鬼の形式になって、それからコンパイラはそれがint*(*)()
だと思っています.その答えも出てきた.答え:(*func12)((*pf1)((*pf2)()->int*)->int*)->int*
int (*(*(*pfunc8)[10])(int[], int))(int, int);
回答:((*pfunc8)[10])(int*, int)->((*pf)(int, int)->int)
締めくくりもしあなたが上のすべてのテーマを完成することができたら、あなたは隠れた称号を獲得します:人形のコンパイラ.
ここのポインタはほとんどあなたの一生に会えるすべてのポインタです.残りの変種ポインタについては,基本的には上記の方法をめぐって構成されている.結局私たちはまだC++の引用を加えていません...
穴が大きすぎても間違いがあるのは避けられないので、指摘を歓迎します.
今、私たちは悪魔です.