C言語ポインタの説明

まえがき: 複合型の仕様

ポインタを理解するには、多かれ少なかれ複雑な型が登場します。そのため、まず、複雑な型を完全に理解する方法を紹介します。複雑な型を理解するのは、実際には非常に簡単です。型には多くの演算子があります。それらにも、通常の式と同様に優先順位があります。それらの優先順位は、演算の優先順位と同じです。そこで、原理をまとめました。変数名から始めて、演算子の優先順位と組み合わせて、段階的に分析していきます。単純な型から始めて、ゆっくりと分析してみましょう:

1. int p; //这是一个普通的整型变量 

2. int *p; //首先从P 处开始,先与*结合,所以说明P 是一个指针,
            //然后再与int 结合,说明指针所指向的内容的类型为int 型.
            //所以P是一个返回整型数据的指针 

3. int p[3]; //首先从P 处开始,先与[]结合,说明P 是一个数组
            //然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组 

4. int* p[3]; //首先从P 处开始,先与[]结合,因为其优先级比*高,//指针的数组
                //所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型
                //然后再与int 结合,说明指针所指向的内容的类型是整型的
                //所以P 是一个由返回整型数据的指针所组成的数组 

5. int (*p)[3]; //首先从P 处开始,先与*结合,//数组的指针
                //说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针 
 
6. int **p; //首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,
            //说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.
            //由于二级指针以及更高级的指针极少用在复杂的类型中,
            //所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针. 

7. int p(int); //从P 处起,先与()结合,说明P 是一个函数
                //然后进入()里分析,说明该函数有一个整型变量的参数
                //然后再与外面的int 结合,说明函数的返回值是一个整型数据 

//8的指针是指向函数的指针
8. int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针
                    //然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合
                    //说明函数有一个int 型的参数,再与最外层的int 结合
                    //说明函数的返回类型是整型
                    //所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针 
   int* p() //P代表的是返回是指针,而且指针指向的类型是int
9. int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合
                         //说明P 是一个函数,然后进入()里面,与int 结合
                    //说明函数有一个整型变量参数,然后再与外面的*结合
                    //说明函数返回的是一个指针,,然后到最外面一层,先与[]结合
                    //说明返回的指针指向的是一个数组,然后再与*结合
                    //说明数组里的元素是指针,然后再与int 结合
                    //说明指针指向的内容是整型数据
       //所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.

それだけで、タスクはたくさんあります。これらの型を理解すれば、他の型も簡単ですが、あまり複雑な型はプログラムの可読性を大幅に低下させるため、通常は使用しません。慎重に使用してください。私たちは上記の型で十分です。

1. 指針を詳しく説明する

ポインタは、値がメモリ内のアドレスとして解釈される特別な変数です。ポインターを理解するには、ポインターの 4 つの側面を理解する必要があります。ポインターの型、ポインターが指す型、ポインターの値またはポインターが指すメモリー領域、ポインター自体が占有するメモリー領域です。個別に分解してみましょう。

例としていくつかのポインターを宣言します。

例一:

(1)int*ptr; 
(2)char*ptr; 
(3)int**ptr; 
(4)int(*ptr)[3]; 
(5)int*(*ptr)[4];

1. ポインタの種類

文法の観点から見ると、ポインタ宣言ステートメントでポインタ名を削除するだけで済み、残りはポインタの型になります。これはポインター自体が持つ型です。例 1 で各ポインターの型を見てみましょう。

(1)int*ptr;//指针的类型是int*
(2)char*ptr;//指针的类型是char*
(3)int**ptr;//指针的类型是int**
(4)int(*ptr)[3];//指针的类型是int(*)[3]
(5)int*(*ptr)[4];//指针的类型是int*(*)[4]

どうでしょうか?ポインターの型を調べる簡単な方法はありませんか?

2. ポインタが指す型

ポインタを使用して、ポインタが指すメモリ領域にアクセスする場合、ポインタが指す型によって、コンパイラがそのメモリ領域の内容をどのように扱うかが決まります。

文法の観点から見ると、ポインタ宣言ステートメント内のポインタ名と名前の左側にあるポインタ宣言子 * のみを削除する必要があり、残りはポインタが指す型になります。例えば:

(1)int*ptr; //指针所指向的类型是int
(2)char*ptr; //指针所指向的的类型是char
(3)int**ptr; //指针所指向的的类型是int*
(4)int(*ptr)[3]; //指针所指向的的类型是int()[3]
(5)int*(*ptr)[4]; //指针所指向的的类型是int*()[4] 

ポインタ演算では、ポインタが指す型が大きな役割を果たします。

ポインタの型 (つまり、ポインタ自体の型) とポインタが指す型は 2 つの概念です。Cに慣れてくると、ポインタに混じる「型」の概念を「ポインタの型」と「ポインタが指す型」の2つの概念に分けることがポインタを使いこなすポイントの一つであることがわかります。私はたくさんの本を読みましたが、いくつかの下手に書かれた本ではポインタの 2 つの概念が混同されていることがわかり、それらの本は矛盾しているように見え、読めば読むほど混乱してきました。

3. ポインタの値 ---- またはポインタが指すメモリ領域またはアドレス

ポインタの値はポインタ自体に格納されている値であり、この値はコンパイラによって一般的な値ではなくアドレスとして扱われます。32 ビット プログラムでは、すべてのメモリ アドレスが 32 ビット長であるため、すべてのタイプのポインタの値は 32 ビット整数です。ポインタが指すメモリ領域は、ポインタの値が表すメモリ アドレスから始まり、サイズ zeof (ポインタが指す型) の長さを持ちます。将来、ポインタの値が○○であると言うときは、そのポインタはアドレスが○○で始まるメモリ領域を指していると言うのと同じであり、ポインタがあるメモリ領域を指すと言うときは、ポインタの値がそのメモリ領域の先頭アドレスであると言うのと同じである。ポインタが指すメモリ領域とポインタが指す型は全く異なる概念です。例 1 では、ポインタが指す型はすでに存在しますが、ポインタが初期化されていないため、ポインタが指すメモリ領域は存在しないか、意味がありません。

今後、ポインタに遭遇するたびに、「このポインタの型は何ですか?」と尋ねる必要があります。ポインタはどの型を指していますか? このポインタはどこを指しているのでしょうか? (基調講演)

4 ポインタ自体が占有するメモリ領域

ポインタ自体はどれくらいのメモリを占有しますか? 関数 sizeof (ポインター型) を使用するだけで測定できます。32 ビット プラットフォームでは、ポインター自体の長さが 4 バイトを占めます。ポインタ自体が占有するメモリの概念は、ポインタ式 (後述) が左辺値であるかどうかを判断するのに役立ちます。

2. ポインタの算術演算

ポインタにはプラスまたはマイナスの整数を指定できます。このポインタの演算の意味は、通常の値の加減算の意味とは異なり、単位は単位です。例えば:

例二:

1.     char a[20]; 
2.     int *ptr=(int *)a; //强制类型转换并不会改变a 的类型 
3.     ptr++; 

上記の例では、ポインター ptr の型は int* で、それが指す型は int で、整変数 a を指すように初期化されています。次の 3 番目の文では、ポインタ ptr に 1 が追加され、コンパイラはこれを次のように処理します。ポインタ ptr の値に sizeof(int) が追加され、32 ビット プログラムでは 4 が追加されます。これは、32 ビット プログラムでは int が 4 バイトを占有するためです。アドレスはバイト単位なので、ptr が指すアドレスは変数 a の元のアドレスから上位方向に 4 バイト増加します。char型の長さは1バイトなので、ptrは配列aの0番目から4バイトを指していたのが、配列aの4番目から4バイトを指すようになりました。ポインターとループを使用して配列を反復できます。例を参照してください。

例 3:

int array[20]={0}; 
int *ptr=array; 
for(i=0;i<20;i++) 
{ 
    (*ptr)++; 
      ptr++; 
} 

この例では、整数配列内の各セルの値に 1 を加算します。ポインタ ptr は 1 単位ずつインクリメントされるため、毎回配列の次の単位にアクセスできます。

もう一度例を見てください。

例 4:

1. char a[20]="You_are_a_girl"; 
2. int *ptr=(int *)a; 
3. ptr+=5;

この例では、ptr に 5 が加算され、コンパイラはこれを次のように処理します。ポインタ ptr の値に sizeof(int) の 5 倍を加算し、32 ビット プログラムでは 4=20 の 5 倍を加算します。アドレスの単位はバイトですので、現在のptrが指すアドレスは、ptrが指すアドレスに5を加算した値よりも20バイト上位方向に移動したことになります。

この例では、配列 a の 0 番目から始まる 4 バイトに 5 ポイントを加算する前の ptr は、5 を加算した後、すでに配列 a の正当な範囲外をポイントしています。この状況は応用的には問題がありますが、文法的には問題ありません。これはポインタの柔軟性も反映しています。上記の例で ptr が 5 減算される場合、プロセスは同様です。ただし、ptr の値は sizeof(int) の 5 倍減算され、新しい ptr が指すアドレスは、元の ptr が指すアドレスより 20 バイト下に移動します。

以下に別の例を挙げさせてください: (誤解)

例 5:

1.     #include<stdio.h> 
2.     int main() 
3.     { 
4.         char a[20]=" You_are_a_girl"; 
5.         char *p=a; 
6.         char **ptr=&p; 
7.         //printf("p=%d\n",p); 
8.         //printf("ptr=%d\n",ptr); 
9.         //printf("*ptr=%d\n",*ptr); 
10.         printf("**ptr=%c\n",**ptr); 
11.         ptr++; 
12.         //printf("ptr=%d\n",ptr); 
13.         //printf("*ptr=%d\n",*ptr); 
14.         printf("**ptr=%c\n",**ptr); 
15.     }  

誤解 1、出力される答えは Y と o

誤解: ptr は char の 2 次ポインタであり、ptr++; を実行するとポインタに sizeof(char) が追加されるため、上記の結果が出力されます これは一部の人だけの結果かもしれません。

誤解 2.出力された答えは Y で誤解: ptr は char * 型を指しています。ptr++; を実行すると、ポインタに sizeof(char *) が追加されます (この値を 1 だと思っている人もいますが、誤解 1 の答えが得られます。この値は 4 である必要があります。前の内容を参照してください)、つまり &p+4; 値演算は配列の 5 番目の要素を指していませんか? 出力結果は 5 番目ではありませんか?配列内の要素? 答えはノーです。

肯定的な解決策: ptr のタイプは char ** で、ポイントされたタイプは char * タイプで、ポイントされたアドレスは p (&p) のアドレスです。ptr++; が実行されると、sizeof(char*) がポインタに追加されます、つまり &p+4; *(&p+4) はどこを指しているのでしょうか? 神に尋ねれば、神がそれがどこにあるか教えてくれるでしょう? したがって、最終出力はランダムな値になり、おそらく不正な操作です。

結論は:

ポインタ ptrold に整数 n を加算 (減算) した結果は、新しいポインタ ptrnew になります。ptrnew の型は ptrold と同じであり、ptrnew が指す型も ptrold が指す型と同じです。ptrnew の値は、ptrold の値から sizeof(ptrold が指す型) バイトの n 倍増加 (減少) します。つまり、ptrnew が指すメモリ領域は、ptrold が指すメモリ領域の n 倍の sizeof (ptrold が指す型) バイトだけ上位 (下位) アドレス方向に移動します。ポインタとポインタの加算と減算: 2 つのポインタを加算することはできません。加算後の結果は未知の場所を指し、意味がなくなるため、これは不正な操作です。2 つのポインターを減算することもできますが、それらは一般に配列で使用される同じ型である必要があるため、多くは言いません。

3. 演算子 & および *

ここで、& はアドレス演算子、* は間接演算子です。

&aの演算結果はポインタであり、ポインタの型はプラス*の型であり、ポインタが指す型はaの型であり、ポインタが指すアドレスはaのアドレスである。

*p の演算結果は様々です。つまり、 *p の結果は p が指すものであり、このものは次のような特性を持っています。その型は p が指す型であり、それが占めるアドレスは p が指すアドレスです。

例 6:

1. int a=12; int b; int *p; int **ptr; 
2. p=&a; //&a 的结果是一个指针,类型是int*,指向的类型是 
3. //int,指向的地址是a 的地址。 
4. *p=24; //*p 的结果,在这里它的类型是int,它所占用的地址是 
5. //p 所指向的地址,显然,*p 就是变量a。 
6. ptr=&p; //&p 的结果是个指针,该指针的类型是p 的类型加个*, 
7. //在这里是int **。该指针所指向的类型是p 的类型,这 
8. //里是int*。该指针所指向的地址就是指针p 自己的地址。 
9. *ptr=&b; //*ptr 是个指针,&b 的结果也是个指针,且这两个指针 
10. //的类型和所指向的类型是一样的,所以用&b 来给*ptr 赋 
11. //值就是毫无问题的了。 
12. **ptr=34; //*ptr 的结果是ptr 所指向的东西,在这里是一个指针, 
13. //对这个指针再做一次*运算,结果是一个int 类型的变量。

4. ポインタ式

式の結果がポインターの場合、その式はポインター式と呼ばれます。

ポインタ式の例をいくつか示します。

例七:

1. int a,b; 
2. int array[10]; 
3. int *pa; 
4. pa=&a; //&a 是一个指针表达式。 
5. Int **ptr=&pa; //&pa 也是一个指针表达式。 
6. *ptr=&b; //*ptr 和&b 都是指针表达式。 
7. pa=array; 
8. pa++; //这也是指针表达式。

例 8:

1. char *arr[20]; 
2. char **parr=arr; //如果把arr 看作指针的话,arr 也是指针表达式 
3. char *str; 
4. str=*parr; //*parr 是指针表达式 
5. str=*(parr+1); //*(parr+1)是指针表达式 
6. str=*(parr+2); //*(parr+2)是指针表达式

ポインタ式の結果はポインタであるため、ポインタ式にはポインタが持つ 4 つの要素も含まれます。それは、ポインタの型、ポインタが指す型、ポインタが指すメモリ領域、およびポインタ自体が占有するメモリです。

ポインター式は、ポインター式の結果ポインターがすでに明示的にポインター自体によって占有されているメモリを持っている場合には左辺値ですが、それ以外の場合は左辺値ではありません。例 7 では、&a はまだ明示的メモリを占有していないため、左辺値ではありません。*ptr は左辺値です。ポインタ *ptr がすでにメモリを占有しているためです。実際、*ptr はポインタ pa です。pa はすでにメモリ内に独自の場所を持っているため、*ptr も当然独自の場所を持っています。

5、配列とポインタの関係

配列の配列名は実際にはポインタとみなすことができます。次の例を参照してください。

例 9:

  1. int 配列[10]={0,1,2,3,4,5,6,7,8,9},値;

  1. value=array[0]; // 次のように記述することもできます: value=*array;

  1. value=array[3]; //次のように記述することもできます: value=*(array+3);

  1. value=array[4]; // value=*(array+4); のように書くこともできます。

上記の例では、一般的に配列名arrayは配列そのものを表し、型はint[10]ですが、配列をポインタとみなすと配列の0番目の単位を指し、型はint*となります。指す型は配列単位の型、つまりintです。したがって、*array が 0 に等しいのも不思議ではありません。同様に、array+3 は配列の 3 番目の要素へのポインタであるため、*(array+3) は 3 と等しくなります。その他など。

例 10:

  1. char *str[3]={

  1. 「こんにちは、これはサンプルです!」、

  1. "こんにちはおはよう。"、

  1. "こんにちは世界"

  1. };

  1. 文字 s[80];

  1. strcpy(s,str[0]); // strcpy(s,*str); と書くこともできます。

  1. strcpy(s,str[1]); // strcpy(s,*(str+1)); と書くこともできます。

  1. strcpy(s,str[2]); // strcpy(s,*(str+2)); と書くこともできます。

上記の例では、str は 3 つの要素からなる配列であり、配列の各要素はポインターであり、これらのポインターはそれぞれ文字列を指します。ポインタ配列名 str をポインタとみなすと、配列のユニット 0 を指し、その型は char ** で、指す型は char * になります。

*str もポインタで、その型は char *、それが指す型は char で、それが指すアドレスは文字列「Hello, thisisasasample!」の最初の文字のアドレス、つまり 'H' のアドレスです。注: 文字列は配列と同等であり、メモリに配列の形式で格納されますが、文字列は配列定数であり、内容は変更できず、右辺値のみにすることができます。ポインタとみなされる場合、定数ポインタとポインタ定数の両方になります。

str+1 はポインタでもあり、配列の最初のユニットを指し、その型は char** で、それが指す型は char* です。

*(str+1) もポインターであり、その型は char* で、それが指す型は char で、「こんにちは、おはようございます」の最初の文字 'H' を指します。

配列の配列名 (配列も配列に格納される) の問題を要約してみましょう。

配列 TYPE array[n] が宣言されている場合、配列名 array には 2 つの意味があります。

まず、これは配列全体を表し、その型は TYPE[n] です。

2つ目は定数ポインタです ポインタの型はTYPE*​​です ポインタの指す型は配列ユニットの型であるTYPEです ポインタが指すメモリ領域は配列の0番ユニットです ポインタ自体は別のメモリ領域を占有します 配列の0番ユニットが占有するメモリ領域とは異なることに注意してください このポインターの値は変更できません。つまり、array++ のような式は間違っています。配列名の配列は、さまざまな式でさまざまな役割を果たすことができます。式 sizeof(array) では、配列名 array が配列自体を表すため、関数 sizeof は配列全体のサイズを測定します。

式 *array では、array がポインタとして機能するため、この式の結果は配列のセル 0 の値になります。sizeof(*array) は配列単位のサイズを測定します。

式 array+n (n=0、1、2、....) では、array はポインターとして機能するため、array+n の結果はポインターであり、その型は TYPE * で、それが指す型は TYPE で、配列の n 番目のユニットを指します。したがって、sizeof(array+n) はポインター型のサイズを測定します。32 ビット プログラムでは、結果は 4 になります。

例 11:

  1. int 配列[10];

  1. int (*ptr)[10];

  1. ptr=&array;

上記の例では、ptr はポインターであり、その型は int(*)[10]、それが指す型は int[10] であり、配列全体の最初のアドレスを使用して初期化します。ステートメント ptr=&array では、array は配列自体を表します。

このセクションでは関数 sizeof() について言及されていますが、sizeof (ポインタ名) はポインタ自体の型のサイズを測定するのでしょうか、それともポインタが指す型のサイズを測定するのでしょうか?

答えは前者です。例えば:

int(*ptr)[10];

32 ビット プログラムには次のものがあります。

sizeof(int(*)[10])==4

sizeof(int[10])==40

サイズ(ptr)==4

実際、sizeof (object) は、他の型のサイズではなく、オブジェクト自体の型のサイズを測定します。

六、ポインタと構造体型の関係

構造体型のオブジェクトへのポインタを宣言できます。

例 12:

  1. 構造体 MyStruct

  1. {

  1. int a;

  1. int b;

  1. int c;

  1. };

  1. struct MyStruct ss={20,30,40};

  1. //構造体オブジェクト ss を宣言し、 ss のメンバーを 20、30、40 に初期化します。

  1. struct MyStruct *ptr=&ss;

  1. //構造体オブジェクト ss へのポインタを宣言します。その種類は

  1. //MyStruct *、それが指す型は MyStruct です。

  1. int *pstr=(int*)&ss;

  1. //構造体オブジェクト ss へのポインタを宣言します。しかし、pstrと

  1. // 指している ptr 型が異なります。

ポインター ptr を介して ss の 3 つのメンバー変数にアクセスする方法を聞いてもいいですか?

答え:

ptr->a; // 演算子を指すか、これら (*ptr).a を指すので、前者の使用をお勧めします

ptr->b;

ptr->c;

また、ポインター pstr を介して ss の 3 つのメンバー変数にアクセスするにはどうすればよいですか?

答え:

*pstr; //ss のメンバー a にアクセスします。

*(pstr+1); //ss のメンバー b にアクセスします。

*(pstr+2) //ss のメンバー c にアクセスします。

上記のコードは MSVC++6.0 でデバッグしましたが、この方法で pstr を使用して構造体メンバーにアクセスするのは通常ではないことを知っておく必要があります。なぜ通常ではないのかを説明するために、ポインターを介して配列の各ユニットにアクセスする方法を見てみましょう: (構造体を配列に置き換えます)

例十三:

  1. int 配列[3]={35,56,37};

  1. int *pa=配列;

  1. //ポインタ pa を介して配列 array の 3 つのユニットにアクセスする方法は次のとおりです。

  1. *pa; // ユニット 0 を訪問

  1. *(pa+1); // ユニット 1 を訪問しました

  1. *(pa+2); // ユニット 2 を訪問しました

形式の点では、ポインターを介して構造体のメンバーにアクセスする非公式な方法と同じです。

すべての C/C++ コンパイラは、配列のユニットを配置するときに各配列ユニットを常に連続した記憶領域に格納し、ユニット間に隙間がありません。ただし、構造体オブジェクトの各メンバーを格納する場合、特定のコンパイル環境では、ワード アラインメント、ダブルワード アラインメント、またはその他のアラインメントが必要な場合があり、隣接する 2 つのメンバー間にいくつかの「充填バイト」を追加する必要があり、その結果、各メンバー間に数バイトのギャップが生じる可能性があります。

したがって、例 12 では、*pstr が構造体オブジェクト ss の最初のメンバ変数 a にアクセスしたとしても、*(pstr+1) が構造体メンバ b にアクセスできる保証はありません。メンバー a とメンバー b の間にいくつかのスタッフィング バイトがある可能性があるため、おそらく *(pstr+1) はこれらのスタッフィング バイトにアクセスするだけかもしれません。これは、ポインタの柔軟性も示しています。さまざまな構造体のメンバー間にパディングバイトがあるかどうかを確認することが目的の場合、これは良い方法です。

ただし、ポインターによって構造体のメンバーにアクセスする正しい方法は、例 12 のポインター ptr を使用する方法である必要があります。

7、ポインタと関数の関係

ポインタは関数へのポインタとして宣言できます。

int fun1(char *,int);

int (*pfun1)(char *,int);

pfun1=ファン1;

int a=(*pfun1)("abcdefg",7); //関数ポインタを介して関数を呼び出します。

ポインタは関数の仮パラメータとして使用できます。関数呼び出しステートメントでは、ポインター式を実パラメーターとして使用できます。

例 14:

  1. int fun(char *);

  1. いくら;

  1. char str[]="abcdefghijklmn";

  1. a=楽しい(文字列);

  1. int fun(char *s)

  1. {

  1. int num=0;

  1. for(int i=0;;)

  1. {

  1. num+=*s;s++;

  1. }

  1. 数値を返します。

  1. }

この例の関数 fun は、文字列内の各文字の ASCII コード値の合計をカウントします。前述したように、配列の名前もポインターです。関数呼び出しで、str が実パラメータとして仮パラメータ s に渡されると、str の値が実際には s に渡され、s が指すアドレスは str が指すアドレスと同じになりますが、str と s は独自の記憶領域を占有します。関数本体の s に対する自己インクリメント演算は、同時に str に対する自己インクリメント演算を意味するわけではありません。

8. ポインタ型変換

ポインタを初期化するか、ポインタに値を代入する場合、代入番号の左側はポインタになり、代入番号の右側はポインタ式になります。上で述べた例では、ほとんどの場合、ポインターの型はポインター式の型と同じであり、ポインターが指す型はポインター式が指す型と同じです。

例 15:

  1. フロート f=12.3;

  1. float *fptr=&f;

  1. int *p;

上の例で、ポインタ p が実数 f を指すようにしたい場合はどうすればよいでしょうか?

次のステートメントが使用されていますか?

p=&f;

間違い。ポインタ p は int * 型であるため、それが指す型は int です。式 &f の結果はポインターであり、ポインターの型は float * で、ポインターが指す型は float です。

この 2 つは矛盾しており、直接代入する方法は受け入れられません。少なくとも私の MSVC++6.0 では、ポインターへの代入ステートメントでは、代入番号の両側の型が同じであり、指す型も同じである必要があります。他のコンパイラでは試していないので、試してみてください。目的を達成するには、「強制的な型変換」が必要です。

p=(int*)&f;

ポインター p がある場合は、その型と指す型を TYEP *TYPE に変更する必要があります。その場合、構文形式は次のようになります。 (TYPE *)p;

このような強制的な型変換の結果は新しいポインタです。新しいポインタの型は TYPE *、それが指す型は TYPE、そしてそれが指すアドレスは元のポインタが指すアドレスです。

また、元のポインター p のプロパティはすべて変更されません。(覚えて)

関数が仮パラメータとしてポインタを使用する場合、関数呼び出しステートメントの実パラメータと仮パラメータを結合するプロセスで型が一貫している必要があり、そうでない場合は変換する必要があります。

例 16:

  1. void fun(char*);

  1. int a=125,b;

  1. fun((char*)&a);

  1. void fun(char*s)

  1. {

  1. 炭;

  1. c=*(s+3);*(s+3)=*(s+0);*(s+0)=c;

  1. c=*(s+2);*(s+2)=*(s+1);*(s+1)=c;

  1. }

これは 32 ビット プログラムであるため、int 型は 4 バイト、char 型は 1 バイトを占有することに注意してください。fun の機能は、整数の 4 バイトの順序を逆にすることです。気づいてますか?関数呼び出しステートメントでは、実際のパラメーター &a の結果はポインターであり、その型は int * で、それが指す型は int です。このポインタの仮パラメータの型は char * で、このポインタが指す型は char です。このように、実パラメータと仮パラメータを組み合わせる過程で、int * 型から char * 型への変換を行う必要があります。

この例を組み合わせると、次のことができます

コンパイラの変換プロセスを想像してください。コンパイラは最初に一時ポインタ char *temp を構築し、次に temp=(char *)&a を実行し、最後に temp の値を s に渡します。したがって、最終結果は次のようになります。 s の型は char *、それが指す型は char、そしてそれが指すアドレスは a の最初のアドレスです。

ポインタの値はポインタが指すアドレスであることはすでにわかっていますが、32 ビット プログラムでは、ポインタの値は実際には 32 ビットの整数です。

ポインタの値として整数を直接ポインタに割り当てることはできますか? 次のようなステートメントのようになります。

  1. unsigned int a;

  1. TYPE *ptr; //TYPE は int、char、構造体の型などです。

  1. a=20345686;

  1. ptr=20345686; //私たちの目的は、ポインタ ptr がアドレス 20345686 を指すようにすることです。

  1. ptr=a; //私たちの目的は、ポインタ ptr がアドレス 20345686 を指すようにすることです。

  1. //コンパイルします。後の 2 つの文はすべて間違っていることが判明しました。それでは、私たちの目標は達成できないのでしょうか?いいえ、別の方法があります。

  1. unsigned int a;

  1. TYPE *ptr; //TYPE は int、char、構造体の型などです。

  1. a=N //N は正式な住所を表す必要があります。

  1. ptr=(TYPE*​​)a; //ふふ、これで大丈夫です。

厳密に言えば、ここでの (TYPE *) はポインタ型変換の (TYPE *) と同じではありません。ここで (TYPE*​​) は、符号なし整数 a の値をアドレスとして扱うことを意味します。上記では、 a の値は正当なアドレスを表す必要があることを強調しています。そうでない場合、ptr を使用すると不正な操作エラーが発生します。これを逆にして、ポインタの指すアドレス、つまりポインタの値を整数として取り出すことができないか考えてみましょう。全然大丈夫です。次の例は、ポインターの値を整数として取得し、その整数をアドレスとしてポインターに割り当てる方法を示しています。

例 17:

  1. int a=123,b;

  1. int *ptr=&a;

  1. 文字 * 文字列;

  1. b=(int)ptr; //ポインタ ptr の値を整数として取得します。

  1. str=(char*)b; //この整数値をアドレスとしてポインタ str に代入します

ポインタの値を整数として取り出すことができ、整数値をアドレスとしてポインタに割り当てることができることがわかりました。

9、ポインタのセキュリティ問題

以下の例を参照してください。

例 18:

  1. char s='a';

  1. int *ptr;

  1. ptr=(int *)&s;

  1. *ptr=1298;

ポインタ ptr は int * 型のポインタであり、それが指す型は int です。それが指すアドレスは s の最初のアドレスです。32 ビット プログラムでは、s は 1 バイト、int 型は 4 バイトを占めます。最後のステートメントは、s が占める 1 バイトを変更するだけでなく、s に隣接する上位アドレス方向の 3 バイトも変更します。この 3 バイトは何のためにあるのでしょうか? それを知っているのはコンパイラだけであり、プログラムを書いた人はおそらく知りません。おそらく、これらの 3 バイトには非常に重要なデータが格納されており、おそらくこれらの 3 バイトはプログラムのコードであり、ポインタのずさんな使用により、これらの 3 バイトの値が変更されている可能性があります。これにより、クラッシュ エラーが発生する可能性があります。

別の例を見てみましょう。

例 19:

  1. 文字a;

  1. int *ptr=&a;

  1. ptr++;

  1. *ptr=115;

この例は完全にコンパイルして実行できます。でも、わかりますか?3文目では、ポインタptrに対してセルフインクリメント演算を行った後、ptrは整変数aに隣接する上位アドレス方向の記憶領域を指している。この保管場所には何が入っているのでしょうか?我々は知りません。おそらくそれは非常に重要なデータであり、コードの一部である可能性もあります。

そして 4 番目の文では、実際にこのストレージ領域にデータを書き込みます。これは重大な間違いです。したがって、ポインタを使用するとき、プログラマは心の中で、自分のポインタがどこを指しているのかを明確にしておく必要があります。ポインタを使用して配列にアクセスする場合は、配列の下限と上限の境界を超えないように注意してください。超えない場合、同様のエラーが発生します。

ポインタの強制型変換: ptr1=(TYPE *)ptr2 では、sizeof (ptr2 の型) が sizeof (ptr1 の型) より大きい場合、ptr2 が指す記憶域にアクセスするためにポインタ ptr1 を使用しても安全です。sizeof(ptr2 のタイプ) が sizeof(ptr1 のタイプ) より小さい場合、ポインタ ptr1 を使用して ptr2 が指すメモリ領域にアクセスするのは安全ではありません。その理由については、読者は例 18 と合わせて考えれば理解できるはずです。

おすすめ

転載: blog.csdn.net/cyy1104/article/details/129585567