「C とポインタ」の読書メモ (第 8 章 配列)

0 はじめに

C言語では配列が重要な役割を果たします。配列とポインタの間には密接な関係があるため、就職活動、勉強、仕事の際の技術的な議論の焦点にもなっています。コンピューター プログラミング言語にはスターがたくさんいますが、なぜ C 言語配列が長年にわたってインタビューで人気を維持しているのでしょうか? 一体どんな不思議な魅力があるのでしょうか?今日は関連するトピックについて一緒に話し合います。

まず、この記事の主な内容を見てみましょう (章番号は書籍内の章番号と一致しています)。マクロを把握しておきましょう。
ここに画像の説明を挿入します

1. 1次元配列

1 次元配列は最も一般的な配列であり、最も一般的に使用されますが、実際の開発では反復や読み取りを容易にするために、複数の配列を複数の 1 次元配列に分割することがあります。

1.1 配列名

1 次元配列の配列名は、配列の最初の要素を指すポインター定数です。次のプログラムを参照できます。

#include <stdio.h>
int main()
{
    
    
	int temp[] = {
    
    1,2,3};
	printf("%d \n", *(temp));
	printf("%d \n", *(temp + 1));
	printf("%d \n", *(temp + 2));
	return 0;
}

印刷する:
ここに画像の説明を挿入します

1.2 添え字の参照

優先順位を除いて、添字付き参照は間接アクセスとまったく同じです。次の手順を参照できます。

#include <stdio.h>
#include <stdlib.h>
int main()
{
    
    
	int temp[] = {
    
    1,2,3};
	int *p = temp + 1;
	printf("数组的第一个元素是:%d \n", *(temp));
	printf("数组的第二个元素是:%d \n", *(temp + 1));
	printf("数组的第三个元素是:%d \n", *(temp + 2));
	//打印数组的第一个元素
	printf("数组的第一个元素是:%d \n", p[-1]);
	system("pause");
	return 0;
}

出力の印刷:
ここに画像の説明を挿入します
上記のプログラムからわかるように、2この時点ではポインター p は配列の要素を指しているため、p[-1]配列の最初の要素を指すことになります。

1.3 ポインタと添字

ポインタと添字はどちらも配列要素にアクセスする効果的な方法です。ただし、添字がポインタより効率的であることはありませんが、ポインタの方が添字より効率的である場合もあります。これらは基礎となる命令を含むため、ここでは展開しません。

1.4 ポインタの効率

ポインタは、正しく使用されれば添え字よりも効率的な場合があります。これには基礎となる命令が関係するため、ここでは詳しく説明しません。

1.5 配列とポインタ

配列とポインタは等価ではありません。配列を宣言するときはメモリが確保されていますが、ポインタを宣言するときはそれが指すデータ型だけがわかり、具体的に指すアドレスがわからないか、意味のないアドレスになります。
たとえば、次の 2 つのステートメントがあるとします。

	int a[5];
	int *p;

以下の手順で確認できます

#include <stdio.h>
#include <stdlib.h>
int main()
{
    
    
	int a[5];
	int *p;
	printf("数组a的大小是%d \n", sizeof(a));
	printf("指针p的大小是%d \n", sizeof(p));
	system("pause");
	return 0;
}

印刷出力:
ここに画像の説明を挿入します
コンパイル後、int 型のデータが 4 バイトを占めることがわかります。この時点で、システムはすべてのメモリを配列に割り当てています。ポインタ p については、それが int 型の変数を指していることだけがわかります。 . ただし、どの変数が指されているかは不明です

1.6 関数パラメータとしての配列名

配列名がパラメーターとして関数に渡されると、ポインターのコピーが関数に渡されます。関数が添字参照を行う場合、実際にはこのポインターに対して間接アクセス操作が実行され、この間接アクセスを通じて関数は呼び出し側プログラムの配列要素にアクセスして変更することができます。

次の手順を参照できます。

#include <stdio.h>
#include <stdlib.h>
void reverse_array(int arr[], int size)
{
    
    
	for (int i = 0; i < size / 2; i++)
	{
    
    
		int temp = arr[i];
		arr[i] = arr[size - i - 1];
		arr[size - i - 1] = temp;
	}
}
int main()
{
    
    
	int a[5];
	for (int i = 0; i < 5; i++)
		a[i] = i;
	reverse_array(a, 5);
	for (int i = 0; i < 5; i++)
	{
    
    
		printf("数组a第%d个元素是%d \n", i, a[i]);
	}
	system("pause");
	return 0;
}

1.7 配列パラメータの宣言

興味深い質問があります。配列をパラメータとして関数に渡すとき、正しい関数パラメータはどのようになるべきでしょうか? ポインタまたは配列として宣言する必要がありますか?

厳密な意味ではどれも正しい。次のコードを参照できます

#include <stdio.h>
#include <stdlib.h>
//声明为数组
void reverse_array1(int arr[], int size)
{
    
    
	for (int i = 0; i < size / 2; i++)
	{
    
    
		int temp = arr[i];
		arr[i] = arr[size - i - 1];
		arr[size - i - 1] = temp;
	}
}
//声明为指针
void reverse_array2(int *arr, int size)
{
    
    
	for (int i = 0; i < size / 2; i++)
	{
    
    
		int temp = arr[i];
		arr[i] = arr[size - i - 1];
		arr[size - i - 1] = temp;
	}
}
int main()
{
    
    
	int a1[5];
	int a2[5];
	for (int i = 0; i < 5; i++)
	{
    
    
		a1[i] = i;
		a2[i] = i;
	}
	//翻转数组
	reverse_array1(a1, 5);
	reverse_array2(a2, 5);
	//打印输出
	for (int i = 0; i < 5; i++)
	{
    
    
		printf("数组a1第%d个元素是%d \t", i, a1[i]);
		printf("数组a2第%d个元素是%d \n", i, a2[i]);
	}
	system("pause");
	return 0;
}

出力:
ここに画像の説明を挿入します
上記の例からわかるように、2 つの初期化は実質的に同等です。ただし、より正確にするには、ポインターを使用する必要があります。実際のパラメータは実際には配列ではなくポインタであるためです。

1.8 初期化

配列の初期化が関数 (またはコード ブロック) に対してローカルである場合、プログラムの実行フローがその関数 (またはコード ブロック) に入るたびに配列を再初期化する価値があるかどうかを慎重に検討する必要があります。答えが「いいえ」の場合は、配列を静的として宣言して、配列の初期化をプログラムの開始前に 1 回だけ実行するだけで済むようにします。

static キーワードについては、こちらの記事を参照してください: static キーワードの詳細な説明 (C/C++)

1.9 不完全な初期化

いわゆる不完全な初期化とは、配列を初期化するときに、一部の要素にのみ値を代入すると、残りの要素が自動的に代入されることを意味します0次のコードを参照できます。

#include <stdio.h>
#include <stdlib.h>
int main()
{
    
    
	int a1[5] = {
    
    0,1};

	//打印输出
	for (int i = 0; i < 5; i++)
	{
    
    
		printf("数组a1第%d个元素是%d \n", i, a1[i]);
	}
	system("pause");
	return 0;
}

印刷出力:
ここに画像の説明を挿入します
初期化されていないいくつかの要素が自動的に 0 に初期化されることがわかりますが、この自動初期化には制限があり、自動的に値を割り当てることができるのは次の要素のみであり、先頭と中央の要素には割り当てられません。

1.10 配列の長さを自動的に計算する

配列の定義時に初期化されている場合は、配列の長さを指定する必要はありません。次の例を参照してください。

#include <stdio.h>
#include <stdlib.h>
int main()
{
    
    
	int a1[] = {
    
    0,1};

	//打印输出
	printf("数组a1的大小是%d \n", sizeof(a1) / sizeof(int));
	system("pause");
	return 0;
}

印刷する:
ここに画像の説明を挿入します

1.11 文字配列の初期化

文字配列には 2 つの初期化方法があり、1 つは次のような従来の初期化です。

	char a1[] = {
    
    '0','1'};

文字列を定義する方法と似た、便利で高速な別の方法があります。

	char a2[] = "01";

実際、この 2 つは完全に同等ではなく、2 番目の初期化メソッドでは\0デフォルトで余分な ' ' が存在するため、配列 a2 には 3 つの要素があります。次のテストコードを参照できます。

#include <stdio.h>
#include <stdlib.h>
int main()
{
    
    
	char a1[] = {
    
    '0','1'};
	char a2[] = "01";

	//打印输出
	printf("数组a1的大小是%d \n", sizeof(a1) / sizeof(char));
	printf("数组a2的大小是%d \n", sizeof(a2) / sizeof(char));
	system("pause");
	return 0;
}

印刷出力:
ここに画像の説明を挿入します
データ型 string は C 言語には存在しません。代わりに、文字配列を使用して文字列が保存されます。

2. 多次元配列

多次元配列は 2 次元以上の配列であり、2 次元配列が最も一般的に使用されます。注意が必要なのは、多次元配列の要素の格納順序です。要素へのアクセス方法など。

多次元配列が登場する場合、ポインター、またはポインターと添え字の組み合わせを使用して配列要素にアクセスするのは、少し難しい問題になります。

2.1 保存順序

C 言語では、多次元配列の要素の格納順序は、右端の添え字が最初に変更されるという原則に従います。これは、と呼ばれます。行メインシーケンス

#include <stdio.h>
#include <stdlib.h>
#define ROW 3
#define COL 8
int main()
{
    
    
	int matrix[ROW][COL];
	int *p = &matrix[0][0];
	for (int i = 0; i < ROW; i++)
	{
    
    
		for (int j = 0; j < COL; j++)
		{
    
    
			matrix[i][j] = i * ROW + j;
		}
			
	}
	//打印输出
	printf("第一个值是%d \n", *p);
	printf("第二个值是%d \n", *++p);
	printf("第三个值是%d \n", *++p);
	system("pause");
	return 0;
}

出力の印刷:
ここに画像の説明を挿入します
上記の例からわかるように、ポインターが大きくなると、右端の変更の順​​序で配列の要素を最初に指します。スキャン行が終了すると、自動的に次の行を指し、アクセスを継続します。

2.2 配列名

1 次元配列名の値は、配列を指すポインタ定数です。要素、多次元配列の最初の次元の要素は別の配列です。たとえば、次のステートメント:

	int matrix[3][10];

これは要素を含む 1 次元配列とみなすことができ3、各要素は10整数要素を含む配列です。

あるいは、この記事の以降の章を読んで、ゆっくり理解していくと自然と理解できるようになります。

2.3 添え字

多次元配列の要素を識別する場合は、配列が宣言されたのと同じ順序で各次元に添え字を指定する必要があり、各次元を別々の角かっこで囲む必要があります。

#include <stdio.h>
#include <stdlib.h>
#define ROW 8
#define COL 3
int main()
{
    
    
	int matrix[ROW][COL];
	for (int i = 0; i < ROW; i++)
	{
    
    
		for (int j = 0; j < COL; j++)
		{
    
    
			matrix[i][j] = i * COL + j;
		}
			
	}
	//打印输出
	printf("第一个值是:%d \n", **matrix);
	printf("第二个值是:%d \n", *(*(matrix + 1)));
	printf("第三个值是:%d \n", *(*(matrix) + 2));
	printf("第四个值是:%d \n", *(*(matrix + 1) + 2));
	system("pause");
	return 0;
}

ここに画像の説明を挿入します
上の例は少し難しいかもしれませんが、ポインタの移動方向をよく考えてみると理解できます。

2.4 配列へのポインタ

多次元(2次元)配列へのポインタはどのように定義すればよいでしょうか?

	int matrix[ROW][COL] = {
    
    {
    
    0,1,2},{
    
    3,4,5}};
	int(*p)[COL] = matrix;

n 個の要素を持つ配列を指すポインター p を定義しますCOLp を整数値に加算する場合、加算が実行される前に、まず整数値が整数値の長さに調整されます。以下の例を参照してください。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ROW 2
#define COL 3
int main()
{
    
    
	//定义二维数组
	int matrix[ROW][COL] = {
    
    {
    
    0,1,2},{
    
    3,4,5}};
	//定义指向数组的指针
	int(*p)[COL] = matrix;
	//打印输出
	printf("(*(*p))的值为:%d \n",(*(*p)));
	printf("(*(*p + 1))的值为:%d \n", *(*(p + 1)));
	printf("(*(*p) + 1的值为:%d \n", (*(*p) + 1));
	printf("(*(*p + 1) + 1)的值为:%d \n", *(*(p + 1) + 1));
	system("pause");
	return 0;
}

印刷する:
ここに画像の説明を挿入します

p ポインタを直接使用して間接アクセスを行うと、必ず 0 行目の 0 番目の要素 ( ) にアクセスすることがわかります(*(*p))pに直接1を足す場合は、1次元配列を移動してから間接的にアクセスするので、得られるのは配列の1行目の0番目の要素、というような式になりますが、間接的にアクセスする場合は一度1を足した後*(*(p + 1))、最初にアクセスされるのは 2 次元配列の行 0 です。1 を加算すると、当然、行 0 の最初の要素になります。これは上記の式です。最後の式 ( ) は当然のことながら自明(*(*p) + 1)です*(*(p + 1) + 1)

2.5 関数パラメータとしての多次元配列

多次元(2次元)配列を関数の引数として使用する場合、関数の宣言も1次元配列の場合とは異なります。宣言方法は 2 つあります。
方法 1:

void func1(int(*mat)[5])

方法 2:

void func2(int mat[][5])

これら 2 つの宣言方法は実質的に同等であり、どちらも使用できます。これは次のプログラムから証明できます。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ROW 3
#define COL 5
//声明方式1
void func1(int(*mat)[5])
{
    
    
	int add = 1;
	printf("在函数func1中\n");
	for (int i = 0; i < ROW; i++)
	{
    
    
		for (int j = 0; j < COL; j++)
		{
    
    
			mat[i][j] += 10;
			printf("数组的第%d个元素是:%d\n", add, mat[i][j]);
			add++;
		}
	}
}
//声明方式2
void func2(int mat[][5])
{
    
    
	int add = 1;
	printf("在函数func2中\n");
	for (int i = 0; i < ROW; i++)
	{
    
    
		for (int j = 0; j < COL; j++)
		{
    
    
			mat[i][j] += 10;
			printf("数组的第%d个元素是:%d\n", add, mat[i][j]);
			add++;
		}
	}
}
int main()
{
    
    
	//定义二维数组
	int matrix1[ROW][COL];
	int matrix2[ROW][COL];
	//定义累加变量
	int add = 1;
	//数组初始化
	for (int i = 0; i < ROW; i++)
	{
    
    
		for (int j = 0; j < COL; j++)
		{
    
    
			matrix1[i][j] = i * COL + j;
			matrix2[i][j] = i * COL + j;
			printf("数组1的第%d个元素是:%d \t", add, matrix1[i][j]);
			printf("数组2的第%d个元素是:%d \n", add, matrix1[i][j]);
			add++;
		}
	}
	printf("-----------------");
	//函数调用
	func1(matrix1);
	printf("-----------------");
	func2(matrix2);
	system("pause");
	return 0;
}

出力の印刷:上の例では、 2 つの 2 次元配列が
ここに画像の説明を挿入します
定義され、同じ方法で初期化され、処理のために 2 つの関数に渡されます。2 つの関数は、仮パラメータの宣言方法が異なるだけです。3*52 つの関数は、元の配列の各要素に 10 を加算し、同じ結果を取得します。

したがって、これら 2 つの宣言方法は実質的に同等です。

2.6 初期化

多次元 (2 次元) 配列の初期化には 2 つの一般的な形式があります。

  1. 1 つは、保存された値を各要素に直接割り当てる方法です。
  2. 1 つは、各次元を中括弧で区切って値を割り当てる方法です。

どちらの方法も正しいですが、2 番目の方法には 2 つの利点があります。

  1. 読書に最適
  2. 初期化を容易にするために、各サブ初期化リストの末尾にあるいくつかの値を省略できます (不完全な初期化リスト)。

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

#include <stdio.h>
#include <stdlib.h>
#define ROW 2
#define COL 3
int main()
{
    
    
	//初始化形式1
	int matrix1[ROW][COL] = {
    
    0,1,2,3,4,5};
	//初始化形式2
	int matrix2[ROW][COL] = {
    
     {
    
    0,1,2},{
    
    3,4,5}};

	int add = 1;
	//打印输出
	for (int i = 0; i < ROW; i++)
	{
    
    
		for (int j = 0; j < COL; j++)
		{
    
    
			printf("matrix1的第%d个值为%d \t", add, matrix1[i][j]);
			printf("matrix2的第%d个值为%d \n", add, matrix2[i][j]);
			add++; 
		}
	}
	system("pause");
	return 0;
}

印刷出力:
ここに画像の説明を挿入します
上記の例からわかるように、これら 2 つの形式は実装効果の点で一貫しています。

2.7 配列長の自動計算

1多次元配列では、初期化リストに基づいてデフォルトで最初の次元のみが提供されます。コンパイラが各部分配列の次元の長さを推測できるように、残りの次元を明示的に書き出す必要があります。例えば:

#include <stdio.h>
#include <stdlib.h>
#define ROW 3
#define COL 5
int main()
{
    
    
	int matrix3[][5] = {
    
     {
    
    0,1,2},{
    
    3,4,5},{
    
    6,7,8}};

	int add = 1;
	//打印输出
	for (int i = 0; i < ROW; i++)
	{
    
    
		for (int j = 0; j < COL; j++)
		{
    
    
			printf("matrix1的第%d个值为%d \t", add, matrix3[i][j]);
			add++; 
		}
		printf("\n");
	}
	system("pause");
	return 0;
}

出力の印刷:
ここに画像の説明を挿入します
したがって、この時点で最初の次元の値が書き込まれていなくても、コンパイラーは実行時に初期化された値と中括弧に基づいて次元の値を自動的に推測できます。

3. ポインタ配列

ポインタ配列は配列の要素がポインタであるということでわかりやすいですが、そのポインタがどのようなデータを指すのかはユーザーが定義します。
たとえば、次の例では、文字列 (より厳密には文字配列) へのポインターを格納するためにポインター配列が使用されています。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ROW 3
#define COL 5
char const *keyword[] = {
    
    "do","for"};
int main()
{
    
    
	int add = 1;
	//打印输出
	char const desired_word[] = "do";
	char const **p;
	for (p = keyword; p < keyword + 2; p++)
	{
    
    
		if (strcmp(desired_word, *p) == 0)
		{
    
    
			printf("YES");
			system("pause");
			return 0;
		}
			
	}
	printf("NO");
	system("pause");
	return 0;
}

出力は次のとおりです。
ここに画像の説明を挿入します
上記の例では、配列は、いくつかの文字配列へのポインターの配列を格納するために使用されています。次に、複数の文字列のマッチング機能が実装されます。

または、2 次元配列を使用して実装することもできますが、最長の文字列のサイズを事前に知っておく必要があります。

4. まとめ

配列とポインタの関係は 1 ~ 2 文で明確に説明できるものではないため、具体的な開発の中でゆっくりと体験して理解する必要があります。

配列の要素には添え字ポインターを介してアクセスできますが、多くの場合、ポインターの方が効率的です。

ポインタ配列も開発でよく使用され、配列要素は単にポイントするだけではありません。文字列(文字配列)、これはまた、構造体変数およびその他のデータ型。

-------------------------------------------------- - - - - - - - 終わり - - - - - - - - - - - - - - - - - - ------------------------

おすすめ

転載: blog.csdn.net/weixin_43719763/article/details/130659740