C语言进阶之指针的进阶

news2024/11/17 21:41:28

请添加图片描述

指针的进阶

  • 1. 字符指针
  • 2. 指针数组
  • 3. 数组指针
    • 3.1 数组指针的定义
    • 3.2 &数组名VS数组名
    • 3.3 数组指针的使用
  • 4. 数组参数、指针参数
    • 4.1 一维数组传参
    • 4.2 二维数组传参
    • 4.3 一级指针传参
    • 4.4 二级指针传参
  • 5. 函数指针
  • 6. 函数指针数组
  • 7. 指向函数指针数组的指针
  • 8. 回调函数
  • 9. 指针和数组笔试题解析
    • 9.1 一维数组
    • 9.2 字符数组
    • 9.3 字符指针
    • 9.4 二维数组
  • 10. 指针笔试题
    • 10.1 笔试题1
    • 10.2 笔试题2
    • 10.3 笔试题3
    • 10.4 笔试题4
    • 10.5 笔试题5
    • 10.6 笔试题6
    • 10.7 笔试题7
    • 10.8 笔试题8
  • 结语

指针的主题,我们在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 and str2 are not same,这里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,表示首元素的地址
但是二维数组的首元素是二维数组的第一行
所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
可以数组指针来接收

我们再来看看下面代码分别代表什么意思

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

4. 数组参数、指针参数

4.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 二维数组传参

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

总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。这样才方便运算。

4.3 一级指针传参

#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 二级指针传参

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

在这里插入图片描述
不难看出函数名即为函数地址,这两个地址是 test 函数的地址。 那我们的函数的地址要想保存起来,怎么保存?

#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 一维数组

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. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  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是跳过整个数组后的地址

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,地址是以十六进制打印,所以是10000014
第二个结果是先将结构体地址强转为长整形,而整形计算则是直接加1
第三个结果是先将结构体地址强制转换为指针,加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是跳过二维数组第一行,即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[]存储的就是三个首字母的地址,而char** pa存储的是指针数组中首元素w的地址,所以pa++,pa指向的就是第二个元素a的地址,解引用后打印即为at。

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们的观看是我坚持下去的动力
在时间的催化剂下,让我们彼此都成为更优秀的人吧!!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/734253.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【程序员必须掌握的算法】【Matlab】GRNN神经网络遗传算法(GRNN-GA)函数极值寻优——非线性函数求极值

上一篇博客介绍了BP神经网络遗传算法(BP-GA)函数极值寻优——非线性函数求极值&#xff0c;神经网络用的是BP神经网络&#xff0c;本篇博客将BP神经网络替换成GRNN神经网络&#xff0c;希望能帮助大家快速入门GRNN网络。 1.背景条件 要求&#xff1a;对于未知模型&#xff08;…

使用trtexec工具多batch推理tensorrt模型(trt模型)

文章目录 零、pt转onnx模型一、onnx转trt模型二、推理trt模型 零、pt转onnx模型 参考&#xff1a;https://github.com/ultralytics/yolov5 用根目录下的export.py可以转pt为onnx模型&#xff0c;命令如下可以转换成动态batch的onnx模型 python3 export.py --weights./yolov5s…

一款强大易用的截图控件:跨平台,界面简洁,功能丰富,易于集成

当我们在日常工作中沟通交流&#xff0c;或是在开发过程中跟踪反馈问题时&#xff0c;截图无疑是一种最直观有效的方式。然而&#xff0c;传统的截图工具在功能上的局限性&#xff0c;往往无法满足我们日益增长的需求。这时&#xff0c;一款功能强大&#xff0c;易于集成&#…

垃圾收集算法和CMS详解

一、垃圾收集算法 1、分带收集理论 基于新生代和老年代选择不同垃圾回收算法&#xff0c;比如新生代&#xff0c;都是一些暂存对象&#xff0c;而且内存分区域的&#xff0c;可以采用标记复制算法。而老年代只有一块内存区域&#xff0c;使用复制算法比较占用内存空间&#x…

DEVICENET转ETHERCAT网关连接ethercat通讯协议详细解析

你有没有遇到过生产管理系统中&#xff0c;设备之间的通讯问题&#xff1f;两个不同协议的设备进行通讯&#xff0c;是不是很麻烦&#xff1f;今天&#xff0c;我们为大家介绍一款神奇的产品&#xff0c;能够将不同协议的设备进行连接&#xff0c;让现场的数据交换不再困扰&…

MySQL数据库 - 库的操作

目录​​​​​​​ 一、创建数据库 二、创建数据库案例 三、字符集和校验规则 四、校验规则对数据库的影响 五、操纵数据库 1、查看数据库 2、显示创建语句 3、修改数据库 4、删除数据库 六、数据库的备份与恢复 1、数据库的备份 2、数据库的恢复 3、表的备份 4…

【网络系统集成】Pfsense防火墙实验

1.实验名称 Pfsense防火墙实验 2.实验目的 通过动手实践配置pfsense对加深对防火墙的原理与应用的理解。 3.实验内容 (1)安装并完成pfsense防火墙软件的基本配置(WAN, LAN,局域网

刘积仁:东软不太喜欢风口,更看重长期主义

作为数字和软件服务产业一年一度的行业盛宴&#xff0c;2003年&#xff0c;中国国际软件和信息服务交易会&#xff08;简称“软交会”&#xff09;正式诞生。2019年&#xff0c;大会更名为中国国际数字和软件服务交易会&#xff08;简称“数交会”&#xff09;&#xff0c;至今…

【C++修炼之路】string 概述

&#x1f451;作者主页&#xff1a;安 度 因 &#x1f3e0;学习社区&#xff1a;StackFrame &#x1f4d6;专栏链接&#xff1a;C修炼之路 文章目录 一、string 为何使用模板二、string 类认识1、构造/析构/赋值运算符重载2、容量操作3、增删查改4、遍历5、迭代器6、非成员函数…

[NSSRound#13 Basic]flask?jwt?解题思路过程

过程 打开题目链接&#xff0c;是一个登录框&#xff0c;不加验证码&#xff0c;且在注册用户名admin时提示该用户名已被注册&#xff0c;因此爆破也是一种思路。不过根据题目名字中的提示&#xff0c;jwt&#xff0c;且拥有注册入口&#xff0c;注册一个用户先。 注册完用户…

Flink DataStream之使用filter实现分流

新建类 package test01;import org.apache.flink.api.common.JobExecutionResult; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.SingleOut…

Pygame Zero(pgzrun)游戏库介绍

Pygame Zero&#xff08;pgzrun&#xff09;游戏库介绍 pgzero是python的一个第三方库。pgzrun 是 python game zero run 的缩写, 它对 Pygame 进行了封装, 屏蔽了繁琐枯燥的框架代码, 让学习者可以更专注于游戏的实现逻辑, 并且更快看到成果。 官网https://pygame-zero.read…

单样本微调给ChatGLM2注入知识~

前方干货预警&#xff1a;这可能也是一篇会改变你对LLM微调范式&#xff0c;以及对LLM原理理解的文章。 同时这也是一篇非常有趣好玩&#xff0c;具有强大实操性的ChatGLM2微调喂饭级教程。 我们演示了使用AdaLoRA算法&#xff0c;使用1条样本对ChatGLM2-6b实施微调。几分钟就成…

【Redis】五大数据类型(操作命令)

&#x1f3af;Redis 命令 &#x1f6a9;Redis 键(key) 这些是 Redis 数据库中的命令&#xff0c;用于对数据类型进行操作和管理。以下是每个命令的含义和用法&#xff1a; DEL&#xff1a;删除一个或多个键。DUMP&#xff1a;将一个键的值转储到一个字符串中。EXPIRE&#x…

【数据结构二叉树OJ系列】4、翻转二叉树(又称求二叉树的镜像)

目录 法一、 法二、 题述&#xff1a; 翻转一颗二叉树。 输入&#xff1a; 输出&#xff1a; 题中已给&#xff1a; struct TreeNode {int val;struct TreeNode* left;struct TreeNode* right; }; TreeNode* invertTree(struct TreeNode* root) 法一、 思路&#xff1a;…

操作指南 | 如何使用Foundry在Moonbeam上进行部署

Foundry是一种以太坊开发环境&#xff0c;可帮助构建者管理依赖项、编译项目、测试或部署合约以及通过指令与区块链进行交互。Foundry已成为流行的开发智能合约开发环境&#xff0c;仅需要使用Solidity即可进行操作。Moonbeam在官方文档网站提供了有关将Foundry与Moonbeam网络结…

vector [] 赋值出现的报错问题

下面这段代码的作用是创建了一个整数类型的vector&#xff08;std::vector<int>&#xff09;并对其进行操作。以下是代码的详细说明&#xff1a; 使用reserve(10)方法为向量分配至少10个元素的存储空间。reserve() 预留了额外的存储空间&#xff0c;以避免后续添加元素时…

C++之typeof和typeid用法(一百五十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

动态规划--Fibonacci数列 III

描述 众所周知&#xff0c;Fibonacci数列是一个著名数列。它的定义是&#xff1a; 本题要求采用第三种方法&#xff1a;简单的动态规划。 用数组把求出来的 Fibonacci 数列保存下来&#xff0c;以免后面要的时候再算一次。 输入描述 每行一个整数 i &#xff0c;表示 Fibona…

【C++修炼之路】string 模拟实现

&#x1f451;作者主页&#xff1a;安 度 因 &#x1f3e0;学习社区&#xff1a;StackFrame &#x1f4d6;专栏链接&#xff1a;C修炼之路 文章目录 一、默认成员函数1、全缺省构造2、析构3、拷贝构造&#xff08;深拷贝&#xff09;4、赋值重载&#xff08;深拷贝&#xff09;…