ナニーレベルポインタ上級チュートリアル - [C言語]

前回のブログでは、ポインタの基礎知識、ポインタのサイズ、演算、二次ポインタなどを簡単に紹介しましたが、次回はポインタについて詳しく説明し、誰もがポインタについてよく理解できるようにします。


目次

文字ポインタ

ポインタの配列

 配列ポインタ

配列名 VS& 配列名

配列ポインタの使い方

関数ポインタ

関数ポインタの配列

関数ポインタの配列へのポインタ


文字ポインタ

ポインタの型では、文字ポインタであるポインタ char* の型を見てきました。この型のポインタが指す型オブジェクトは文字です。

一般的な使用法:

#include<stdio.h>

int main(void)
{
    char ch = 'w';
    char *pc = &ch;//*pc就是字符指针
    *pc = 'w';
    return 0;
}

例を通じて文字ポインタをよりよく理解できます。値を変更するために ch を使用するのが不便な場合、文字ポインタを使用して間接的に内容を変更できます。これはナイフで人を殺すのと同じです。

ただし、文字ポインタには特別な使用法があります。文字ポインタを使用して、char*p = "abcd" などの定数文字列を直接作成できます。元々は文字列の作成に文字配列を使用していましたが、文字ポインタも作成できます。 *p が指す文字列はコード領域に格納され、文字配列はスタック領域に格納されます。

int main(void)
{
	char arr[] = "abcdef";
	const char* p = "abcdef";//常量字符串
	printf("%s\n", p);
	printf("%c\n", *p);

	return 0;
}

 文字ポインタ p を使用して文字列全体を出力し、p を逆参照すると、出力結果は次のようになります。

 このことから、文字ポインタ p は文字列全体ではなく、文字列の最初の要素のアドレスを指していると結論付けることができます。

典型的な例を通して、配列を使用して文字列を作成する場合と、文字ポインターを使用して文字列を作成する場合の違いがわかります。

#include <stdio.h>
int main()
{
	char str1[] = "hello world.";
	char str2[] = "hello world.";
	const char* str3 = "hello world.";
	const char* str4 = "hello world.";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");

	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");

	return 0;
}

上記の一連のプログラムの出力はどのようになるべきでしょうか?

ここで、str3 と str4 は同じ定数文字列を指します。C/C++ は定数文字列を別のメモリ領域に保存します。複数のポインタが同じ文字列を指す場合、実際には同じメモリ ブロックを指します。ただし、同じ定数文字列を使用して異なる配列を初期化すると、異なるメモリ ブロックが開かれます。したがって、str1 と str2 は異なり、str3 と str4 は同じです。

文字ポインターについて知っておくべきことはこれですべてです。


ポインタの配列

ポインタの配列とは何ですか? いくつかの例え話から始めましょう。整数配列: 整数を格納する配列、文字配列: 文字を格納する配列、ポインタ配列は、名前が示すように、ポインタを格納する配列です。 

int* arr1[10]; //整数ポインタの配列

char *arr2[4]; //第 1 レベルの文字ポインターの配列

char **arr3[5];//二次文字ポインタの配列

これらはポインターの配列と呼ばれます。

 ポインター配列の最も一般的に使用される機能は、2 次元配列をシミュレートすることです。

int main(void)
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* arr[] = { arr1,arr2,arr3 };
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}

	return 0;
}

 同じ型の 1 次元配列を 3 つポインタ配列に入れ、そのポインタ配列を使用してアクセスして 2 次元配列をシミュレートします。配列名は配列の最初の要素のアドレスであるため、複数の配列がある場合は、処理のために複数の配列をポインター配列に入れ、これらの配列を 1 つずつ出力できます。プログラムを実行した結果は次のようになります。

これはポインターの配列の内容の一部です。 


 配列ポインタ

配列ポインタはポインタですか?それとも配列でしょうか?

答えは「ポインタ」です。

整数ポインター: int * pint; 整数データを指すことができるポインターは誰もが知っています。浮動小数点ポインタ: float * pf; 浮動小数点データを指すことができるポインタ。配列ポインタは、配列を指すことができるポインタである必要があります。

では、次のコードのうち、配列ポインタはどれでしょうか?

int* p1[10];

int (*p2)[10];

p1 と p2 はそれぞれ何ですか?

答え: p1 はポインタの配列、p2 は配列ポインタです

[] の優先順位は * よりも高いため、p2 は最初に * と結合され、p2 がポインター変数であることを示し、次に 10 個の整数の配列を指します。したがって、p2 は配列を指すポインタであり、配列ポインタと呼ばれます。


 

配列名 VS& 配列名

int arr[10]; の配列の場合、arr は何を表し、&arr は何を表しますか?

arr は配列の名前であり、配列の最初の要素のアドレスを表すことがわかります。&arr は不要ですか? まずコードの一部を見てみましょう。

#include <stdio.h>
int main()
{
 int arr[10] = {0};
 printf("%p\n", arr);
 printf("%p\n", &arr);
 return 0;
}

 配列名と&配列名で出力されるアドレスは同じであることがわかりますが、実際にはarrと&arrに違いはないのでしょうか? 答えは、違いがあるということです。

arr は int* 型で、&arr は int(*)[10] 型です。 

#include <stdio.h>
int main()
{
 int arr[10] = { 0 };
 printf("arr = %p\n", arr);
 printf("&arr= %p\n", &arr);
 printf("arr+1 = %p\n", arr+1);
 printf("&arr+1= %p\n", &arr+1);
 return 0;
}

 上記のコードによると、実際には &arr と arr は同じ値ですが、意味は異なるはずであることがわかりました。

実際: &arr は、配列の最初の要素のアドレスではなく、配列全体のアドレスを表します。&arr のデータ型は int (*)[10] ですが、これは配列ポインター型であり、int* ではありません。また、配列 +1 は配列全体のサイズをスキップするため、&arr+1 と arr+1 の差は 40 になります。


配列ポインタの使い方

配列ポインタはどのように使用すればよいでしょうか?

配列ポインタは配列を指し、配列のアドレスは配列ポインタに格納される必要があります。一般に、2 次元配列で使用する方が便利です。

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
 int i = 0;
 for(i=0; i<row; i++)
 {
 for(j=0; j<col; j++)
 {
 printf("%d ", arr[i][j]);
 }
 printf("\n");
 }
}
void print_arr2(int (*arr)[5], int row, int col)
{
 int i = 0;
 for(i=0; i<row; i++)
 {
 for(j=0; j<col; j++)
 {
 printf("%d ", arr[i][j]);
 }
 printf("\n");
 }
}
int main()
{
 int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
 print_arr1(arr, 3, 5);
 print_arr2(arr, 3, 5);
 return 0;
}

 それぞれ二次元配列のパラメータ受け渡しを実現できる二次元配列受け取りと配列ポインタ受け取りを使用しましたが、本質的にはポインタです。

上記プログラムの配列名 arr は最初の要素のアドレスを表しますが、2 次元配列の最初の要素は 2 次元配列の最初の行であるため、ここで渡される arr は実際には1 次元の最初の行のアドレス 配列のアドレスは、配列ポインタで受け取ることができます。


関数ポインタ

名前が示すように、関数へのポインターは関数ポインターです。

関数にはアドレスがありますか? そうであれば、ポインターを使用して関数のアドレスを指すことができます。まずコードの一部を見てみましょう。

#include <stdio.h>
void test()
{
 printf("hehe\n");
}
int main()
{
 printf("%p\n", test);
 printf("%p\n", &test);
 return 0;
}

出力結果:  2 つのアドレスが出力されます。どちらもテスト関数のアドレスです。これは、関数がアドレスを持っていることを証明するため、関数ポインターが存在する必要があります。そして、関数名と関数ポインタ内の & 関数名は同じ意味であることがわかり、ここで配列と区別できます。

では、関数ポインタを受け取るにはどのようなポインタを使用すればよいのでしょうか? これは、配列ポインターに同様に記述する方法です。

定義した関数が決まったら、カスタム関数の戻り値とパラメータを使って関数ポインタを模倣します。例えば:

int add(int x, int y)

{

        x+y を返します。

}

// int (*pf)(int, int) = &add; または int (*pf)(int, int) = add; を定義します。

ボイドテスト()

{

       ……

}

// void(*pd)() = &test または void(*pd)() = test; を定義します。

関数を呼び出すときは、最も原始的な方法を使用するか、関数を直接記述するか、関数ポインターを使用して関数を呼び出すことができます。

int add(int x, int y)
{
	return x + y;
}

int main(void)
{
	int (*pf)(int,int) = add;
	int r = add(3, 5);
	int m = pf(4, 5);
	printf("%d %d\n", r, m);

	return 0;
}

このプログラムは、add 関数を呼び出すために 2 つのメソッドを使用しており、どちらも正常に呼び出すことができます。結果は次のようになります: これは、通常、 コールバック関数で使用する関数ポインタです。コールバック関数を知らない場合は、コールバック関数の説明がある作者のホームページにアクセスしてください。 


関数ポインタの配列

配列とは、同じ種類のデータを格納するための記憶空間です。ポインタ配列については上で説明しました。次に、関数のアドレスを配列に格納します。この配列を関数ポインタの配列と呼びます。配列の定義方法関数ポインタの?

//テンプレート

int (*parr1[10])();

Parr1 は最初に [] と結合されており、parr1 が配列であることを示しています。配列の内容は何ですか? int (*)() 型の関数ポインタです。

関数ポインタの配列の目的: 転送テーブル  

プログラミングをしていると、同じ種類の関数をいくつか定義する必要があり、main関数がそれぞれの関数を呼び出す際、基本的な動作はほぼ同じですが、コードを書くと冗長な内容やロジックが不明瞭になってしまいます。この時点で、コードの可読性を向上させることができる関数ポインターの配列を使用します。

計算機の実装は、関数ポインターの配列を使用して実行できます。

#include <stdio.h>
int add(int a, int b)
{
 return a + b;
}
int sub(int a, int b)
{
 return a - b;
}
int mul(int a, int b)
{
 return a*b;
}
int div(int a, int b)
{
 return a / b;
}
int main()
{
 int x, y;
 int input = 1;
int ret = 0;
 int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
 while (input)
 {
 printf( "*************************\n" );
 printf( " 1:add 2:sub \n" );
 printf( " 3:mul 4:div \n" );
 printf( "*************************\n" );
 printf( "请选择:" );
 scanf( "%d", &input);
 if ((input <= 4 && input >= 1))
 {
 printf( "输入操作数:" );
 scanf( "%d %d", &x, &y);
 ret = (*p[input])(x, y);
 }
 else
 printf( "输入有误\n" );
 printf( "ret = %d\n", ret);
 }
 return 0;
}

これは、switch case 言語を使用するよりも明確で、コードが簡潔かつ明確になります。


関数ポインタの配列へのポインタ

関数ポインタの配列へのポインタは配列を指すポインタであり、配列の要素はすべて関数ポインタですが、どのように定義すればよいでしょうか。

混乱を避けるために、上記の 2 つのモジュール定義と関数ポインターの配列へのポインターを区別します。

void test(const char* str)
{
 printf("%s\n", str);
}
int main()
{
 //函数指针pfun
 void (*pfun)(const char*) = test;
 //函数指针的数组pfunArr
 void (*pfunArr[5])(const char* str);
 pfunArr[0] = test;
 //指向函数指针数组pfunArr的指针ppfunArr
 void (*(*ppfunArr)[5])(const char*) = &pfunArr;
 return 0;
}

実際には、配列とポインタを追加して設定を続けることができるため、ますます複雑になっていきますが、興味がある場合は探索を続けることができます。ここではこれ以上は述べません。

以上がPointer Advancedの全内容です。ご覧いただきありがとうございます。私の至らない点や間違いをご指摘いただければ幸いです。これからも皆様のサポートで精進してまいります。! 

おすすめ

転載: blog.csdn.net/m0_74755811/article/details/131666246