C言語の上級 ポインタの上級

画像の説明を追加してください


ポインターのトピックについては、C 言語初級ブログですでに触れており、ポインターの概念は理解しています。

  1. ポインタは、メモリ空間の一部を一意に識別するアドレスを格納するために使用される変数です。
  2. ポインタ サイズは 4/8 バイトに固定されています (32 ビット プラットフォーム/64 ビット プラットフォーム)。
  3. ポインタには型があり、ポインタの型によってポインタ±整数のステップ サイズと、ポインタ逆参照操作の権限が決まります。
  4. ポインタを使用した算術演算。
    次に、ポインタの高度なトピックの探索を続けます。

1. 文字ポインタ

ポインタ型には、文字ポインタ char* であるポインタ型があることがわかっています。
一般的には次のように使用されます。

int main()
{
    
    
 	char ch = 'a';
 	char *pc = &ch;
 	*pc = 'a';
 	return 0;
}

別の使用方法は次のとおりです。

int main()
{
    
    
 	const char* pstr = "hello C.";
 	printf("%s\n", pstr);
 	return 0;
}

では、ここで pstr ポインタ変数に文字列が入れられるのでしょうか?
実際にはそうではなく、ここでは実際には文字列の最初の文字、つまり h のアドレスをポインタ変数 pstr に格納しています。

キャラクターポインタに関する次のインタビューの質問を見てみましょう

#include <stdio.h>
int main()
{
    
    
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";
	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;
}

ここに画像の説明を挿入
まず、配列名は配列の最初の要素のアドレスを表すため、ここで str1 と str2 はそれぞれ対応する配列の最初の文字のアドレスを格納します。これらは同じではないため、最初の出力結果は str1 と str2 になります。 str2 は同じではありません。ここでは、str3 と str4 は同じ定数文字列を指します。C/C++ は定数文字列を別のメモリ領域に保存します。複数のポインタが同じ文字列を指す場合、実際には同じメモリ ブロックを指します。ただし、同じ定数文字列を使用して異なる配列を初期化すると、異なるメモリ ブロックが開かれます。したがって、str1 と str2 は異なり、str3 と str4 は同じです。

2. ポインタの配列


C 言語の初期段階でポインタ配列について次のように説明しました。

int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

3. 配列ポインタ

3.1 配列ポインタの定義

配列ポインタはポインタですか?それとも配列でしょうか?
答えは「ポインタ」です。
整数ポインター
: int * p; 整数データを指すことができるポインター。
浮動小数点ポインタ: float * pf; 浮動小数点データを指すことができるポインタ。
配列ポインタは、配列を指すことができるポインタである必要があります。
では、次のコードのうち配列ポインターはどれでしょうか?

int *p1[10];
int (*p2)[10];

答えは int (*p2)[10] です。

p は最初に * と結合され、p がポインター変数であることを示し、次に 10 個の整数の配列を指します。したがって、 p は配列を指すポインタであり、配列ポインタと呼ばれます。

[] の優先順位は * の優先順位よりも高いため、p が最初に * と結合されるように () を追加する必要があることに注意してください。

3.2 & 配列名 VS 配列名

配列arr
と &arr はそれぞれ何ですか?
arr が配列の名前であり、配列名が配列の最初の要素のアドレスを表すことがわかります。
&arr 配列名は何ですか?

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

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

ここに画像の説明を挿入
配列名と & 配列名によって出力されるアドレスが同じであることがわかりますが、それらは同じですか?
次のコードを見てください。

#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] です。これは配列ポインタ型で、配列のアドレス +1 で、配列全体のサイズをスキップします。そのため、&arr+1 は次のようになります。 &arr との相対値 差は 40

3.3 配列ポインタの使用

配列ポインタはどのように使用されますか?
配列ポインタは配列を指すため、配列のアドレスを配列ポインタに格納する必要があります。
コードを見てください:

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

配列 arr のアドレスを配列ポインター変数 p に代入しますが、通常、次のようにこのようなコードを記述することはほとんどありません

#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;
	int j = 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 次元配列のアドレスであり、
配列ポインタで受け取ることができます。

次のコードが何を表しているかを見てみましょう

int arr[5];//数组
int *parr1[10];//指针数组
int (*parr2)[10];//数组指针
int (*parr3[10])[5];//数组指针数组

4. 配列パラメータ、ポインタパラメータ

4.1 1次元配列パラメータの受け渡し

void test(int arr[])//ok?
{
    
    }
void test(int arr[10])//ok?
{
    
    }
void test(int* arr)//ok?
{
    
    }
void test2(int* arr[20])//ok?
{
    
    }
void test2(int** arr)//ok?
{
    
    }
int main()
{
    
    
	int arr[10] = {
    
     0 };
	int* arr2[20] = {
    
     0 };
	test(arr);
	test2(arr2);
}

これらの配送方法は全て可能です

4.2 2次元配列パラメータの受け渡し

void test(int arr[3][5])//ok
{
    
    }
void test(int arr[][])//不可以
{
    
    }
void test(int arr[][5])//ok
{
    
    }
void test(int* arr)//不可以
{
    
    }
void test(int* arr[5])//ok
{
    
    }
void test(int(*arr)[5])//ok
{
    
    }
void test(int** arr)//不可以
{
    
    }
int main()
{
    
    
	int arr[3][5] = {
    
     0 };
	test(arr);
}

概要: パラメータを 2 次元配列で渡す場合、関数パラメータの設計では最初の [] 番号のみを省略できます。
2 次元配列の場合、行数を知る必要はありませんが、1 行に要素がいくつあるかを知る必要があるためです。操作に便利です。

4.3 第 1 レベルのポインタパラメータの受け渡し

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

4.4 第 2 レベルのポインタパラメータの受け渡し

#include <stdio.h>
void test(int** ptr)
{
    
    
	printf("num = %d\n", **ptr);
}
int main()
{
    
    
	int n = 10;
	int* p = &n;
	int** pp = &p;
	test(pp);
	test(&p);
	return 0;
}

5. 関数ポインタ

まずはコードを見てください

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

ここに画像の説明を挿入
関数名が関数アドレスであり、これら 2 つのアドレスがテスト関数のアドレスであることを理解するのは難しくありません。では、関数のアドレスを保存したい場合、どのように保存すればよいでしょうか?

#include <stdio.h>
void test()
{
    
    
	printf("hehe\n");
}
void (*pfun1)();

pfun1 は最初に * と結合されます。これは、pfun1 がポインターであり、ポインターが関数を指し、指す関数にはパラメーターがなく、戻り値の型が void であることを示します。

6. 関数ポインタの配列

配列は同じタイプのデータを格納する記憶域であるため、ポインターの配列についてはすでに学習しました。たとえば、次のとおりです。

int *arr[10];
//数组的每个元素是int*

次に、関数のアドレスを配列に格納すると、この配列は関数ポインターの配列と呼ばれます。関数ポインターの配列を定義するにはどうすればよいですか?

int (*parr1[10])();

Parr1 は最初に [] と結合されており、parr1 が配列であることを示しています。配列の内容は何ですか? int (*)() 型の関数ポインタです。
関数ポインタ配列の目的:転送テーブル
例: 電卓の従来の書き込み

#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;
	do
	{
    
    
		printf("*************************\n");
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
    
    
		case 1:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

	return 0;

関数ポインターの配列を使用した実装:

#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;
}

こうすることでコードが冗長になりません

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

関数ポインタの配列へのポインタは ポイ​​ンタであり、そのポインタは配列を指し、配列の要素はすべて関数ポインタです。
その定義方法については、次のコードを参照してください。

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;
}

8. コールバック関数

コールバック関数は、関数ポインターを通じて呼び出される関数です。関数ポインター (アドレス) をパラメーターとして別の関数に渡す場合、このポインターがそれが指す関数を呼び出すために使用されるとき、これをコールバック関数と呼びます。コールバック関数は、関数の実装者によって直接呼び出されるのではなく、特定のイベントまたは条件が発生したときに別の当事者によって呼び出され、イベントまたは条件に応答するために使用されます。

ライブラリ関数の qsort 関数を例として、この関数の定義を見てみましょう。

_ACRTIMP void __cdecl qsort(
    _Inout_updates_bytes_(_NumOfElements * _SizeOfElements) void*  _Base,
    _In_                                                    size_t _NumOfElements,
    _In_                                                    size_t _SizeOfElements,
    _In_                _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction
    );

ここに画像の説明を挿入
最初の仮パラメータは void* です。これは、任意の型のポインタに対応できるためです。最後の
_CoreCrtNonSecureSearchSortCompareFunction _CompareFunction は、実際にはコールバック関数です。qsort 関数を使用する場合、別の _CompareFunction 関数を作成して、異なる型を並べ替える必要があります。
例:

#include<stdlib.h>
#include <stdio.h>
int int_cmp(const void* p1, const void* p2)
{
    
    
	return (*(int*)p1 - *(int*)p2);
}
int main()
{
    
    
	int arr[] = {
    
     1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	int i = 0;

	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
    
    
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

このうち、 int_cmp 関数は、 qsort によって呼び出される _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction です
ここに画像の説明を挿入
. なお、このデフォルトの書き込み方法は通常昇順になっていますが、降順に変更したい場合は、以下のように p1 と p2 を入れ替えることができます。コード:

int int_cmp(const void* p1, const void* p2)
{
    
    
	return (*(int*)p2 - *(int*)p1);
}

構造体配列のソートを見てみましょう

#include<stdlib.h>
#include <stdio.h>
#include <string.h>
struct Stu
{
    
    
	char name[20];
	int age;
};

int cmp_stu_by_age(const void* p1, const void* p2)
{
    
    
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}

void test1()
{
    
    
	struct Stu arr[] = {
    
     {
    
    "zhangsan", 20}, {
    
    "lisi", 50},{
    
    "wangwu", 15} };
	int sz = sizeof(arr) / sizeof(arr[0]); 
	printf("初始序列:> ");
	for (int i = 0; i < sz; i++)
	{
    
    
		printf("%s,%d     ", arr[i].name, arr[i].age);
	}
	printf("\n");
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	printf("按年龄排序:> ");
	for (int i = 0; i < sz; i++)
	{
    
    
		printf("%s,%d     ", arr[i].name, arr[i].age);
	}
	printf("\n");
}

int cmp_stu_by_name(const void* p1, const void* p2)
{
    
    
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}

void test2()
{
    
    
	struct Stu arr[] = {
    
     {
    
    "zhangsan", 20}, {
    
    "lisi", 50},{
    
    "wangwu", 15} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
	printf("按名字排序:> ");
	for (int i = 0; i < sz; i++)
	{
    
    
		printf("%s,%d     ", arr[i].name, arr[i].age);
	}
}

int main()
{
    
    
	test1();
	test2();
	return 0;
}

ここに画像の説明を挿入
同じ原理に従って、複数の型に適応するバブル ソート コールバック関数を作成することもできます
。例:

#include<stdio.h>
void Swap(void* p1, void* p2, int size)
{
    
    
	for (int i = 0; i < size; i++)
	{
    
    
		char tmp = *((char*)p1 + i);
		*((char*)p1 + i) = *((char*)p2 + i);
		*((char*)p2 + i) = tmp;
	}
}
void Bubble_Sort(void* base, int num, int size, int(*cmp)(void*,void*))
{
    
    
	int i = 0;
	int j = 0;
	for (i = 0; i < num - 1; i++)
	{
    
    
		for (j = 0; j < num - i - 1; j++)
		{
    
    
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
				Swap((char*)base + j * size, (char*)base + (j + 1) * size,size);
		}
	}
}

int Int_Sort(const void* p1, const void* p2)
{
    
    
	return *(int*)p1 - *(int*)p2;
}
void Int_Print(int* arr,int sz)
{
    
    
	for (int i = 0; i < sz; i++)
		printf("%d ", arr[i]);
}

void Test1()
{
    
    
	int arr[] = {
    
     7,6,5,4,8,9,3,1,2 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("初始序列:> ");
	Int_Print(arr, sz);
	printf("\n");
	Bubble_Sort(arr, sz, sizeof(arr[0]), Int_Sort);
	printf("排序后  :> ");
	Int_Print(arr,sz);
}

int main()
{
    
    
	Test1();
	return 0;
}

ここに画像の説明を挿入

9. ポインタと配列のペンテスト問題の分析

以下のコードはすべて 64 プラットフォーム用にコンパイルされています

9.1 1次元配列

int a[] = {
    
    1,2,3,4};
printf("%d\n",sizeof(a));//sizeof中数组名代表整个数组
printf("%d\n",sizeof(a+0));//数组名+0为第一个元素的地址,也就是指针,指针大小就是4/8个字节,当前为64位,所以是8
printf("%d\n",sizeof(*a));//对数组名进行解引用操作,获取指向数组第一个元素
printf("%d\n",sizeof(a+1));//a+1代表第二个元素的地址
printf("%d\n",sizeof(a[1]));//代表第二个元素
printf("%d\n",sizeof(&a));//对数组名取地址,代表的是指针
printf("%d\n",sizeof(*&a));//&再*解引用等于没做操作,还是整个数组
printf("%d\n",sizeof(&a+1));//对数组名取地址再+1代表的是该数组之后的地址
printf("%d\n",sizeof(&a[0]));//代表第一个元素的地址
printf("%d\n",sizeof(&a[0]+1));//代表第二个元素的地址

ここに画像の説明を挿入

9.2 文字配列

char arr[] = {
    
    'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//整个数组
printf("%d\n", sizeof(arr+0));//数组名+0为第一个元素的地址
printf("%d\n", sizeof(*arr));//对数组名进行解引用操作,获取指向数组第一个元素
printf("%d\n", sizeof(arr[1]));//数组第二个元素
printf("%d\n", sizeof(&arr));//整个数组的地址
printf("%d\n", sizeof(&arr+1));//该数组之后的地址
printf("%d\n", sizeof(&arr[0]+1));//代表第二个元素的地址

ここに画像の説明を挿入

char arr[] = {
    
     'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));//因为字符数组arr中没有\0,所以在求字符串长度的时候,会一直往后找,产生的结构就是随机值
printf("%d\n", strlen(arr+0));//arr + 0是首元素的地址,和第一个一样,也是随机值
//printf("%d\n", strlen(*arr));//错误, arr是数组首元素的地址,*arr就是数组首元素,就是'a'-97
//strlen函数参数的部分需要传一个地址,当我们传递的是'a'时,'a'的ASCII码值是97,那就是将97作为地址传参
//strlen就会从97这个地址开始统计字符串长度,这就非法访问内存了
//printf("%d\n", strlen(arr[1]));//错误,同上,只不过这里传的是第二个元素b
printf("%d\n", strlen(&arr));//&arr是数组的地址,数组的地址和数组首元素的地址,值是一样的,那么传递给strlen函数后,依然是从数组的第一个元素的位置开始往后统计
printf("%d\n", strlen(&arr+1));//随机值-6,减去了上面6个元素的长度
printf("%d\n", strlen(&arr[0]+1));//&arr[0] + 1是第二个元素的地址。结果也是随机值

ここに画像の説明を挿入

char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//整个数组
printf("%d\n", sizeof(arr + 0));//arr + 0是首元素的地址
printf("%d\n", sizeof(*arr));//*arr其实就是首元素,1个字节
printf("%d\n", sizeof(arr[1]));//arr[1]是第二个元素,1个字节
printf("%d\n", sizeof(&arr));//&arr是数组的地址,是地址就是4/8个字节
printf("%d\n", sizeof(&arr + 1));//&arr + 1是跳过一个数组的地址,4/8
printf("%d\n", sizeof(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址 4/8

ここに画像の説明を挿入

char arr[] = "abcdef";
printf("%d\n", strlen(arr));//整个字符串的长度
printf("%d\n", strlen(arr+0));//首元素的地址开始,所以结果同上
//printf("%d\n", strlen(*arr));//错误, arr是数组首元素的地址,*arr就是数组首元素
//printf("%d\n", strlen(arr[1]));//错误,同上,只不过这里传的是第二个元素b
printf("%d\n", strlen(&arr));//整个数组的地址,还是从首元素开始
printf("%d\n", strlen(&arr+1));//整个数组后开始计算,所以是随机值
printf("%d\n", strlen(&arr[0]+1));//从第二个元素地址开始算

ここに画像の説明を挿入

9.3 文字ポインタ

文字の配列とも言え、配列自体もポインタです

char *p = "abcdef";
printf("%d\n", sizeof(p));//p为指针变量,大小为4/8
printf("%d\n", sizeof(p+1));//p+1是'b'的地址
printf("%d\n", sizeof(*p));//*p 就是字符a
printf("%d\n", sizeof(p[0]));//同上
printf("%d\n", sizeof(&p));//*p的地址
printf("%d\n", sizeof(&p+1));//*p之后的地址
printf("%d\n", sizeof(&p[0]+1));//&p[0] + 1得到是'b'的地址

ここに画像の説明を挿入

char *p = "abcdef";
printf("%d\n", strlen(p));//正常计算一个字符串的长度
printf("%d\n", strlen(p+1));//从第二个字符开始算
//printf("%d\n", strlen(*p));//错误,传的是首元素
//printf("%d\n", strlen(p[0]));//错误,同上
printf("%d\n", strlen(&p));//从首元素的地址中计算,是随机值,不确定的,和分配的地址有关
printf("%d\n", strlen(&p+1));//同上也是随机值
printf("%d\n", strlen(&p[0]+1));//从第二个字符开始算

ここに画像の説明を挿入

9.4 二次元配列

int a[3][4] = {
    
    0};
printf("%d\n",sizeof(a));//整个数组
printf("%d\n",sizeof(a[0][0]));//首元素
printf("%d\n",sizeof(a[0]));//第一行
printf("%d\n",sizeof(a[0]+1));
//a[0]作为第一行的数组名,没有单独放在sizeo内部,没有&
//a[0]表示数组首元素的地址,也就是a[0][0]的地址
//所以a[0]+1是第一行第二个元素的地址,是地址就是4/8个字节

ここに画像の説明を挿入

int a[3][4] = {
    
     0 };
printf("%d\n", sizeof(*(a[0] + 1)));//计算的是就是第一行第2个元素的大小
printf("%d\n", sizeof(a + 1));//a是数组首元素的地址,是第一行的地址 int(*)[4],a+1 就是第二行的地址
printf("%d\n", sizeof(*(a + 1)));//计算的是第二行的大小
printf("%d\n", sizeof(&a[0] + 1));//&a[0]是第一行的地址 int(*)[4],&a[0]+1 是第二行的地址 int(*)[4]
printf("%d\n", sizeof(*(&a[0] + 1)));//计算的是第二行的大小
printf("%d\n", sizeof(*a));//计算的是第一行的大小
printf("%d\n", sizeof(a[3]));//计算的是一行的大小,并不存在越界,因为实际并没有访问内存

ここに画像の説明を挿入
概要:
配列名の意味:

  1. sizeof(配列名)。配列名は配列全体を表し、計算は配列全体のサイズです。
  2. &Array name。配列名は配列全体を表し、配列全体のアドレスが取得されます。
  3. さらに、すべての配列名は最初の要素のアドレスを表します。

10. ポインターペンテストの問題

10.1 筆記試験問題 1

int main()
{
    
    
	int a[5] = {
    
     1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;
}

手続きの結果はどうなりますか?
ここに画像の説明を挿入
*(a + 1)、*ptr によってアクセスされる 2 番目の要素は、配列全体をスキップした後のアドレスです

10.2 筆記試験の質問 2

struct Test
{
    
    
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p= (struct Test*)0x10000000;
int main()
{
    
    
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

p の値が 0x10000000 であるとします。次の式の値は何ですか?
構造体 Test タイプの変数サイズは 20 バイトであることがわかっています。
ここに画像の説明を挿入
最初の結果は構造全体をスキップするため、20 を直接加算します。アドレスは 16 進数で出力されるため、10000014 になります。2 番目の結果は、最初
にadd 構造体のアドレスを長整数に変換し、整数の計算で直接 1 を加算します。 3 番目の
結果は、最初に構造体のアドレスをポインタに変換し、1 を加算することでポインタのサイズをスキップします。 、つまり 4/8 バイト

10.3 筆記試験問題 3

int main()
{
    
    
	int a[4] = {
    
     1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	int* ptr2 = (int*)((int)a + 1);
	printf("%x,%x", ptr1[-1], *ptr2);
	return 0;
}

ここに画像の説明を挿入
ここに画像の説明を挿入

10.4 筆記試験の質問 4

int main()
{
    
    
	int a[3][2] = {
    
     (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);
	return 0;
}

ここに画像の説明を挿入
ここで、初期化中に最初の 3 つの値のみを初期化したことに注意してください。これは、{} の代わりに () があるためです。そのため、カンマ式内の数値のみ、つまり 1、3、5 のみが初期化され、その後はすべて初期化される必要があります。 0; したがって、最初の行を指す値を出力するときは、a[0][0] (1) のみを出力します。

10.5 筆記試験問題 5

int main()
{
    
    
	int a[5][5];
	int(*p)[4];
	p = a;
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	return 0;
}

ここに画像の説明を挿入
ここに画像の説明を挿入

10.6 筆記試験問題 6

int main()
{
    
    
	int aa[2][5] = {
    
     1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int* ptr1 = (int*)(&aa + 1);
	int* ptr2 = (int*)(*(aa + 1));
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
	return 0;
}

ここに画像の説明を挿入
&aa+1 は配列全体をスキップするため、-1、出力される整数値は aa 配列の最後の要素です。
aa+1 は 2 次元配列の最初の行、つまり 6 の開始アドレスをスキップします、つまり - 1。出力される整数値は、aa 配列の最初の行の最後の要素です。

10.7 筆記試験問題 7

int main()
{
    
    
	char* a[] = {
    
     "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

ここに画像の説明を挿入
まず、char* a[] は最初の 3 文字のアドレスを格納し、char** pa はポインタ配列の最初の要素 w のアドレスを格納します。つまり、pa++、pa は 2 番目の要素 a のアドレスを指し、After を逆参照します。印刷は です。

10.8 筆記試験の質問 8

int main()
{
    
    
	char* c[] = {
    
     "ENTER","NEW","POINT","FIRST" };
	char** cp[] = {
    
     c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
	printf("%s\n", **++cpp);
	printf("%s\n", *-- * ++cpp + 3);
	printf("%s\n", *cpp[-2] + 3);
	printf("%s\n", cpp[-1][-1] + 1);
	return 0;
}

ここに画像の説明を挿入
ここに画像の説明を挿入

エピローグ

興味のある友達は作者に注目してください、内容が良いと思うなら、ワンクリックトリプルリンクをお願いします、カニカニ!
作るのは簡単ではありません、不正確な点があればご指摘ください
ご訪問ありがとうございます、UUの視聴が私が頑張れるモチベーションになっています
時間をきっかけに、お互いより良い人間になりましょう!
ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/kingxzq/article/details/131613390