初級 C 言語 - ポインター

こんにちは、また会いましょう、時間が経つのは早いですね、あっという間にたくさんのブログを書きました、来年は編集者も勉強して真剣にコードを打ちます、一緒に進歩していきましょう、それでは今日からお話しましょうポインタについてです。ポインタの章は C 言語の学習において難しい章です。くだらない話はやめて、勉強を始めましょう。

1. ポインタとは何ですか?
ポインタとは何ですか?
ポインタを理解するための 2 つの重要なポイント:

  1. ポインタはメモリ内の最小単位の番号、つまりアドレスです。
  2. 音声言語でのポインタは通常、メモリアドレスを格納するために使用される変数であるポインタ変数を指します。
    要約: ポインタはアドレスであり、音声言語でのポインタは通常ポインタ変数を指します。

ここに画像の説明を挿入
アドレスをメモリに保存します。各メモリ単位は対応する数値に対応します。単位は 1 バイトで、ポインタはアドレスの保存に使用されます。

ポインタ変数

& (アドレス演算子) を使用して、変数のメモリの実際のアドレスを取り出し、そのアドレスを変数 (ポインター変数) に格納できます。

#include <stdio.h>
int main()
{
    
    
 int a = 10;//在内存中开辟一块空间
 int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
    //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
中,p就是一个之指针变量。
 return 0;
}

概要:
ポインタ変数、アドレスを格納するために使用される変数。(ポインタに格納された値はアドレスとして扱われます)。
ここで問題となるのは、
小型ユニットとはどのくらいの大きさなのかということです。(1バイト)
どのように対処されますか?
慎重な計算と比較検討の結果、対応するアドレスにバイトを割り当てることがより適切であることがわかりました。32 ビット マシンの場合、アドレス ラインが 32 本あると仮定し、アドレス指定が (1 または 0)
のときに各アドレス ラインがハイ レベル (高電圧) とロー レベル (低電圧) を生成すると仮定すると、32アドレス行によって生成されるアドレス?

このように理解できます。32 のアドレス行があり、各アドレス行が 0 と 1 で表される場合、0000000000000000000000000000000000000000000000000000000000000000000000000000 000001 と書くことができ
ます0 1000000000000000000000000000000 100000000000000000000000000000001 11111111111111111111111111111111111







このように続けると、2 の 32 個の保存方法に相当します。2 の 32 倍はいくらになります
(2^32Byte == 2^32/1024KB == 2 32/1024/1024MB==2 32/1024/1024/ 1024GB == 4GB) 4G はアドレス指定に無料です。

ここで理解できるのは、
32 ビット マシンでは、アドレスは 32 個の 0 または 1 で構成されるバイナリ シーケンスであり、アドレスは 4 バイトで格納される必要があるため、ポインター変数のサイズは 4 バイトである必要があります

64 ビット マシン上で 64 のアドレス行がある場合、アドレスを格納するためのポインター変数のサイズは 8 バイトになります。
2. ポインタとポインタの型

変数には整数、浮動小数点などのさまざまな型があることは誰もが知っています。ポインタには型がありますか?

答えははいです

int num = 10;
p = &num;

上記のコードを見て、num のアドレスを取り出して p に格納すると、誰もが p ポインターの型をどうすべきかを考えます。

char  *pc = NULL;
int   *pi = NULL;
short *ps = NULL;
long  *pl = NULL;
float *pf = NULL;
double *pd = NULL;

char*型のポインタは、char型変数のアドレスを格納するために使用されます。
short* 型ポインタは、short 型変数のアドレスを格納するために使用されます。
int* 型のポインタは、int 型の変数のアドレスを格納するために使用されます。

ここを見て疑問に思ったことはありませんか。つまり、サイズは同じですが、ポインタ変数の型を定義しますが、なぜこのように定義するのか考えたことはありますか?

答えは、ポインタの型によって 1 をどれだけ足すかが決まるということです。足が長い人と短い人のようなものです。誰もが 1 歩ですが、歩幅は異なります。これがポインタ変数の関数です。誰もがよりよく理解できるようにコードを使用しています

2.1 ポインタ ± 整数

#include <stdio.h>
//演示实例
int main()
{
    
    
 int n = 10;
 char *pc = (char*)&n;
 int *pi = &n;
 
 printf("%p\n", &n);
 printf("%p\n", pc);
 printf("%p\n", pc+1);
 printf("%p\n", pi);
 printf("%p\n", pi+1);
 return  0;
}

ここに画像の説明を挿入

最初の &n は整数全体のサイズを取り出します。そのアドレスは他のものと同じですが、これは変数 n の変数全体であり、4 バイトを占めるため、1 を加算して 4 つのアドレスをスキップします。

2 番目の pc は、整数変数の 4 バイトの最初のバイトに格納されているアドレスなので、アドレスをスキップするには 1 を追加します。
pi は、n 変数全体のアドレスでもあり、整数ポインタを使用して を受け取ります。 1を追加するとスキップされます。サイズも4バイトで、1バイトがアドレスです

概要:
ポインターのタイプによって、ポインターを逆参照する際の権限 (操作できるバイト数) が決まります。
たとえば、char* ポインターの逆参照では 1 バイトしかアクセスできませんが、int* ポインターの逆参照では 4 バイトにアクセスできます。

3. ワイルドポインター

概念: ワイルド ポインターは、ポインターが指す場所が不明であることを意味します (ランダム、不正確、明確な制限がない)。

3.1 ワイルドポインタの原因

  1. ポインタが初期化されていません
#include <stdio.h>
int main()
{
    
     
 int *p;//局部变量指针未初始化,默认为随机值
    *p = 20;
 return 0;
}

*p ポインター変数の型を定義していませんでした。

  • 2 ポインタの範囲外アクセス
#include <stdio.h>
int main()
{
    
    
    int arr[10] = {
    
    0};
    int *p = arr;
    int i = 0;
    for(i=0; i<=11; i++)
   {
    
    
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
   }
    return 0;
}

この配列には 10 個の要素しかありませんが、アクセスの範囲は配列の範囲を超えており、
ここでの逆参照の背後にあるアドレスの内容は、配列のアドレスの背後にあるアドレスの内容であり、これは乱数である可能性があります。

ここでは、ワイルドポインターを比喩的に説明します。ワイルドポインターは、実際には凶暴な犬です。合理的に使用しなければ、取り返しのつかない結果を引き起こします。しかし、ロープで彼を縛るだけで十分なので、彼は大丈夫です。ここで、文字列としては通常 NULL (ヌルポインタ) が使用されます。

  • ワイルドポインタを回避する方法
  1. ポインタの初期化
  2. 境界外への注意深いポインタ
  3. NULL に設定されている場合でも、ポインターは解放するスペースを指します。
  4. ローカル変数のアドレスを返さないようにする
  5. ポインタを使用する前にその有効性を確認してください
#include<stdio.h>
int* test()
{
    
    
	int a = 10;
	return &a;
}
int main()
{
    
    
	int* p = test();
	printf("%p", p);

	return 0;
}
#include<stdio.h>
int* test()
{
    
    
	int a = 10;
	return &a;
}
int main()
{
    
    
	int* p = test();
	*p = 100;

	return 0;
}

また、ローカル変数はスコープ外に出ると自動的に破棄されてしまいますが、最後にアドレスは破棄されないので、上記のコードは避けたいと思います。分析

  1. ポインタを使用する前にその有効性を確認してください
#include <stdio.h>
int main()
{
    
    
    int *p = NULL;
    //....
    int a = 10;
    p = &a;
    if(p != NULL)
   {
    
    
        *p = 20;
   }
    return 0;
}

4.1 ポインタ±整数

#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
    
    
     *vp++ = 0;
}

浮動小数点 vp アドレスの内容を 0 に変更し、アドレスを 5 つ変更
vp は float 型のポインタなので、float 型のポインタの移動は float 単位になります

4.2 ポインタ-ポインタ
以前は strlen 関数を使用して文字列の長さを検索しましたが、今度はポインタを使用して文字列の長さを検索することもできます。

#include<stdio.h>
#include<string.h>
int main()
{
    
    
	char str[] = "abcdef";
	int len = strlen(str);
	printf("%d ", len);
	return 0;
}

これは strlen 関数を使用して検索する文字列の長さです。今度はポインターを使用して検索し、関数 my_strlen を定義して文字列の長さを検索します。

#include<stdio.h>
int my_strlen(char* str)
{
    
    
	int count = 0;
	while (*str != '\0')
	{
    
    
		count++;
		str++;
	}
	return count;
}
int main()
{
    
    

	char str[] = "abcdef";
	int len = my_strlen(str);
	printf("%d", len);
	return 0;
}

もちろん、以前にも関数再帰のアイデアを使用してコードを作成したことがあります。ここで思い出してください。

#include<stdio.h>
int my_strlen(char* str)
{
    
    
	if (*str != '\0')
	{
    
    
		return 1 + my_strlen(str+1);
	}
}
int main()
{
    
    

	char str[] = "abcdef";
	int len = my_strlen(str);
	printf("%d", len);
	return 0;
}

文字列の長さを調べる方法が少なくとも 3 つあります。

4.3 ポインタの関係演算

for(vp = &values[N_VALUES]; vp > &values[0];)
{
    
    
    *--vp = 0;
}
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
    
    
    *vp = 0

実際、このタスクはほとんどのコンパイラで正常に完了できますが、標準では動作が保証されていないため、この方法で記述することは避けるべきです。

標準: 配列要素へのポインタは、配列の最後の要素の後のメモリ位置へのポインタと比較できますが、最初の要素より前のメモリ位置へのポインタとは比較できません。

5. ポインタと配列

次のコード文字列を見てみましょう

#include <stdio.h>
int main()
{
    
    
 int arr[10] = {
    
    1,2,3,4,5,6,7,8,9,0};
    printf("%p\n", arr);
    printf("%p\n", &arr[0]);
    return 0;
}

ここに画像の説明を挿入

それらのアドレスの内容は同じであることがわかります。以前の研究では、2 つの特別な例外 (sizeof) (&) を除いて、配列名は最初の要素のアドレスであることを常に強調してきました。配列名は最初の要素のアドレスです

配列名をアドレスとしてポインタに格納できるため、ポインタを使用して配列名にアクセスすることが可能になります。

#include<stdio.h>
int main()
{
    
    
	int arr[] = {
    
     1,2,3,4,5,6,7,8,9,0 };
	int* p = arr;//存放首元素地址
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
    
    
		printf("&arr[%d] = %p   <====> p+%d = %p\n", i, &arr[i], i, p + i);
	}
	return 0;
}

ここに画像の説明を挿入
したがって、ポインタを使用して配列アドレスにアクセスできます

したがって、p+i は実際に配列 arr の添え字 i のアドレスを計算します。
6. セカンダリポインタ

ポインタ変数も変数であり、変数にはアドレスがありますが、ポインタ変数のアドレスはどこに格納されているのでしょうか?

ここに画像の説明を挿入
*ppa は、ppa 内のアドレスを逆参照することによって pa を見つけ、*ppa は実際に pa にアクセスします。
int b = 20;
*ppa = &b;//pa = &b; と同等です。

**ppa は最初に *ppa までの pa を検索し、次に pa: *pa を逆参照してから、

**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;

7. ポインタの配列

ポインタの配列はポインタですか、それとも配列ですか?
答え: 配列です。ポインタの配列です。
配列 整数配列と文字配列についてはすでに知っています。

ここに画像の説明を挿入

上記は int arr[4] である必要があります

ここに画像の説明を挿入
それで、私たちのポインタ配列はどのようなものですか?
ここに画像の説明を挿入
ポインタも完成しました。ポインタについては後で詳しく学びます。ポインタは本当に重要だからです。C 言語を知っているなら、ポインタを知っておく必要があります。今日の共有はこれで終わりです。ありがとうございました」あなたをみんながサポートしなければなりません、あなたのサポートが私のモチベーションです、さようなら

おすすめ

転載: blog.csdn.net/2301_76895050/article/details/131648435