指针进阶篇

指针的基本概念:

  1. 指针是一个变量,对应内存中唯一的一个地址
  2. 指针在32位平台下的大小是4字节,在64位平台下是8字节
  3. 指针是有类型的,指针类型决定该指针的步长,即走一步是多长
  4. 指针运算:指针-指针表示的是两个指针之间的元素个数

回顾完指针的初阶用法,今天我们要来讲讲指针更高阶的用法

一、字符指针

字符指针就是一个指针,指向一个字符

int main()
{
	char ch = 'a';
	char* pc = &ch;
	printf("%c\n", *pc);//打印a

	return 0;
}

还有另一种用法:

int main()
{
	char* p = "abcdefg";
	printf("%c\n", *p);//打印a

	return 0;
}

该怎么理解呢?其实右边的"abcdefg"是一个字符常量,p中存放的是该字符常量首元素的地址。也就是'a'的地址。

来看看一组练习:

int main()
{
	char arr1[] = "abcdefg";
	char arr2[] = "abcdefg";

	char* arr3 = "abcdefg";
	char* arr4 = "abcdefg";

	if (arr1 == arr2)
		printf("arr1和arr2相等\n");
	else
		printf("arr1和arr2不相等\n");

	if(arr3 == arr4)
		printf("arr3和arr4相等\n");
	else
		printf("arr1和arr2不相等\n");

	return 0;
}

解答:

二、指针数组

首先,我们得明白指针数组是一个数组,数组中的每个元素是指针类型

int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int d = 4;
	int* p1[] = { &a,&b,&c,&d };//数组中的每个元素是整型指针

	char ch1 = 'a';
	char ch2 = 'b';
	char ch3 = 'c';
	char ch4 = 'd';
	char* p2[] = { &ch1,&ch2,&ch3,&ch4 };//数组中的每个元素是字符指针

	return 0;
}

指针数组可以用来模拟实现一个二维数组,代码如下:

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* arr[3] = { 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;
}

打印时,arr[i]相当于每行首元素的地址,通过下标j就可以依次访问每个整型数组中的元素了。 

在内存中对应的关系如下:

指针数组还有另一种用法:

int main()
{
	char* arr[] = { "baiyahua","dashidai","tassel" };
	for (int i = 0; i < 3; i++)
	{
		printf("%s ", arr[i]);
	}

	return 0;
}

我们得知道,arr中存的是字符'b','d','t'的地址,因此分别打印三个字符长常量。

三、数组指针

3.1数组指针的定义

要同前面的指针数组区别开,数组指针本质上是一个指针,该指针指向一个数组。

int arr[] = { 1,2,3,4,5 };

拿这个代码举例,我们想要将该数组的地址存一个指针当中,得到的那个指针就是一个数组指针。要完成这个操作,需要解决两个问题。

1.怎么取出数组的地址?

2.数组指针的类型改怎么写?

对于第一个问题,我们先来梳理一下数组名的概念:

数组名表示首元素的地址,两个情况除外:

  • sizeof(数组名):表示整个数组的大小,单位是字节
  • &数组名:表示取出整个数组的地址

到这里,第一个问题就解决了,要得到一个数组的地址,只需要&数组名就行了。

对于第二个问题,C语言规定的语法是这样写的:

int(*p)[5] = &arr;

int *p[5] = &arr;//错误的写法

注意:不能写成下面的形式,因为p首先会跟[]结合,表明这是一个数组,数组中的每个元素是int*类型,p就变成了一个指针数组了。所以一定要将*和p括号起来

对于上面的形式,我们可以这样理解,p首先跟*结合,表明这是一个指针,在跟[]结合,表明指针指向一个数组,数组中的每个元素是int类型,就是一个数组指针了。

3.2&数组名和数组名

刚刚我们提到,平常的数组名表示的是数组首元素的地址,而&数组名表示的是整个数组的地址,在内存中怎么体现出来呢?

还记得我们最开始讲的指针类型决定该指针的步长,如果是数组指针,那么+1会跳过一个数组。

int main()
{
	int arr[5] = { 1,2,3,4,5 };

	printf("%p\n", arr);
	printf("%p\n", arr + 1);
	printf("\n");

	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0] + 1);
	printf("\n");

	printf("%p\n", &arr);
	printf("%p\n", &arr + 1);
	printf("\n");

	return 0;
}

 可以看到,虽然&arr和arr,&arr[0]的值相等,当时它们+1的步长不同,这是因为&arr取出数组的地址,+1跳过了整个数组。

3.3数组指针的使用

理解完数组指针,就该谈谈数组指针的使用了,实际写代码中,数组指针一般用在二维数组。

一般的二维数组打印:

void Print(int arr[3][5], int row, int col)
{
	int i = row;
	for (i = 0; i < row; i++)
	{
		int j = col;
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	Print(arr,3,5);

	return 0;
}

这里的数组传参,形参我们用了数组的形式,实参部分,我们传了二维数组的数组名,数组名是首元素的地址,只不过对于二维数组,数组的首元素是第一行的地址,也就是说,实参传了一个数组的地址,那形参是不是就可以用我们刚刚学的数组指针来接受了。于是就有了第二种写法:

void Print(int(*p)[5] , int row, int col)
{
	int i = row;
	for (i = 0; i < row; i++)
	{
		int j = col;
		for (j = 0; j < col; j++)
		{
			printf("%d ", p[i][j]);
		}
		printf("\n");
	}
}

这里来解释一下p[i][j]

p[i]相当于*(p+i),p+i是每行的地址

*(p+i)就是每行的数组名,而数组名又是数组首元素的地址

因此p[i]就相当于每行首元素的地址,通过j就可以依次访问每行的元素。

四、数组传参和指针传参

写代码时,我们难免要将一个数组或指针传参,那么形参部分该如果设计?

判断下面的传参是否正确:

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[][])//ok?
{}
void test(int arr[][5])//ok?
{}
void test(int* arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int(*arr)[5])//ok?
{}
void test(int** arr)//ok?
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

 4.3一级指针传参

void Print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
}

int main()
{
	int arr[] = { 1,2,3,4,5,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	Print(p, sz);

	return 0;
}

思考:当函数的形参是一级指针的时候,可以接受什么参数?

void test(int* p)
{}

int main()
{
	int a = 1;
	int* p = &a;
	int arr[] = { 1,2,3 };

	test(p);//传整型指针
	test(&a);//传整型变量的地址
	test(arr);//传整型一维数组的数组名

	return 0;
}

4.4二级指针传参

void test(int** pp)
{
	printf("%d\n", **pp);
}

int main()
{
	int a = 1;
	int* p = &a;
	int** pp = &p;
	test(pp);
	test(&p);

	return 0;
}

思考:当函数的参数是二级指针的时候,可以传什么参数?

void test(int** pp)
{}

int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int* p = &a;
	int** pp = &p;
	int* arr[] = { &a,&b,&c };

	test(pp);//传二级指针变量
	test(&p);//传一级指针变量的地址
	test(arr);//传整型指针数组的数组名

	return 0;
}

五、函数指针

5.1函数指针的定义

函数指针就是存放一个函数地址的指针,同理解数组指针一样,我们得弄明白

1.怎么得到函数的地址?

2.函数指针变量怎么写?

这里就直接给出结论了,&函数名==函数名,都是取出函数的地址。

函数指针变量的写法:

int test(int x ,int y)
{}

int main()
{
	int(*p)(int, int) = test;
	int(*p)(int, int) = &test;
	//两种写法都可以

	return 0;
}

5.2函数指针的使用

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

int main()
{
	int a = 1;
	int b = 2;
	int c = Add(1, 2);
	int d = (*Add)(1, 2);

	printf("c = %d d = %d", c, d);

	return 0;
}

对于函数指针,*我们可以加也可以不加,两者是同等的,*并没有实际的作用,但是如果要加*,必须跟p括号起来。

来看看两个有趣的代码:

//代码1
(*(void (*)())0)();

//代码2
void (*signal(int , void(*)(int)))(int);

代码1解读:

该代码我们从0开始,一个整数前面加上括号,第一想到的应该是强制转换。

再看括号里面的部分,void (*)(),是一个函数指针类型,该函数没有参数,返回值是void类型。

加上*,就是引用该函数。

于是,代码的总体意思就是:引用0地址处的函数,该函数没有参数,返回值为void类型

代码2解读:

首先来看signal,后面加上(),容易想到是函数的定义或声明。

再看参数只有类型,没有参数,能确定是函数的声明。

将signal(int,void(*)(int))去掉,余下的void (*)(int)就是函数的返回值类型了。

该代码的意思是:声明一个函数名为signal的函数,该函数的参数类型分别是int和void(*)(int),返回值类型是void(*)(int)

我们还可以对该代码进行一个简化

typedef void (*ptr)(int);

将void(*)(int)类型重命名为ptr,以后我们想用该类型创建变量时,可以直接用ptr。

ptr signal(int, void(*)(int));

六、函数指针数组

函数指针数组,本质上是数组,数组中每个元素都是函数指针。

6.1函数指针数组的定义

int (*arr[])(int, int);

arr首先与[]结合,表明arr是个数组,数组中每个元素的类型是int (*)(int, int)。

6.2函数指针数组的使用

首先我们来模拟一个简单的计算器,可以实现加减乘除法

//计算器version1
void menu()
{
	printf("*************************\n");
	printf("***  1.Add    2.Sub  ****\n");
	printf("***  3.Mul    4.Div  ****\n");
	printf("***      0.Exit     ****\n");
	printf("*************************\n");
}

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

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;

	do
	{
		menu();
		printf("请输入操作:>");
		scanf("%d", &input);
		
		switch (input)
		{
		case 1:
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:printf("退出程序\n");
			break;
		default:printf("输入错误,请重新输入\n");
			break;
		}

	}while (input);

	return 0;
}

该代码虽然能完成我们的要求,但试想,如果我们要更多的运算,一方面我们得增加运算的定义,另一方面我们还要增加case语句的长度,代码就会变得非常长,这时就可以使用函数指针数组来优化我们的代码。

优化后的代码如下:

//计算器version2
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;

	int(*pf[5])(int, int) = { NULL, Add, Sub, Mul, Div };
	//将运算函数的地址存到一个函数指针数组中
	//输入数字,通过数组下标执行相应的函数
	//为了保证输入的数字和我们想要执行的函数对应
	//加上NULL
	do
	{
		menu();
		printf("请输入操作:>");
		scanf("%d", &input);

		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			ret = pf[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出程序\n");
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}

	} while (input);

	return 0;
}

这时,我们想要增加运算时,只要添加运算的定义,在函数指针数组中增加新增函数的地址,修改一下if条件,而我们的代码长度不变,大大增加了代码的可读性和维护性。

需要注意的是,由于数组是存储一系列相同类型的元素,因此所有函数的参数类型和返回值必须相同才能使用函数指针数组。

七、指向函数指针数组的指针

首先,指向函数指针数组的指针是一个指针,指向一个数组,数组中每个元素的类型都是函数指针类型。

定义:

int (*pf[5])(int, int) = { NULL,Add,Sub,Mul,Div };

int (*(*p)[5])(int, int) = &pf;
//*先跟p结合,表明这是个指针
//往后看到[],表明指针指向一个数组,数组中有5元素
//剩下的int (* )(int, int)就是元素的类型

当然,我们日常写代码时很少用到指向函数指针数组的指针,大家可以做个了解。

八、回调函数

8.1回调函数的定义和使用

回调函数的定义:回调函数不是由函数的实现方直接调用,而是处于某种特定的情境下,由另一个函数通过函数指针的方式调用的。

这里用上面计算器 version1举例,我们发现代码有很多冗余的部分。

这些部分能不能只写成一份?于是就有了我们的计算器version3。

//计算器version3
void Calc(int (*p)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	
	printf("请输入两个操作数:>");
	scanf("%d%d", &x, &y);
	ret = p(x, y);
	printf("%d\n", ret);
}

int main()
{
	int input = 0;
	
	do
	{
		menu();
		printf("请输入操作:>");
		scanf("%d", &input);

		switch (input)
		{
		case 1:
			Calc(Add);
			break;
		case 2:
			Calc(Sub);
			break;
		case 3:
			Calc(Mul);
			break;
		case 4:
			Calc(Div);
			break;
		case 0:printf("退出程序\n");
			break;
		default:printf("输入错误,请重新输入\n");
			break;
		}

	} while (input);

	return 0;
}

 计算器version3中的Add,Sub,Mul,Div就是回调函数,这些函数不是本身直接调用,而是作为一个参数,传到Calc函数中,由Calc函数调用的。

这里再用库中的qsort函数来帮助大家更好的理解回调函数

8.2qsort的使用

在讲qsort函数之前,简单回顾一下冒泡排序,由于该排序比较简单,就直接给出代码了。

void Bubble_Sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

void Print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Print(arr, sz);
	Bubble_Sort(arr, sz);
	Print(arr,sz);

	return 0;
}

该代码的缺陷是只能排序整型数据,当我们想要排序结构体类型,字符类型等的数据,它就不能完成要求,但qsort函数是可以做到的。

qsort函数是C语言的库函数,大家可以自行查找具体用法,这里就给出它大致的用法。

void qsort(void* base, 
	       size_t num, 
	       size_t size,
	       int (*compar)(const void* p1, const void* p2));
//base指向待排序的数组
//num是待排序数组元素的个数
//size是待排序数组一个元素的大小
//compar是一个函数指针,p1和p2是两个待比较的元素

void*类型可以接受任意类型的数据,但不能进行解引用和+-1操作,它没有具体的类型

为什么base,p1和p2要写成void*类型呢?

因为qsort要实现的是对任意类型的数据进行排序,如果是int*,char*等具体类型的指针,就只能接收对应类型的数据,想要排序其他类型的数据,就得改变base,p1和p2的类型,而我们的库函数肯定是不能随便乱动的。

对于qsort,我们只需要实现数据的比较方法函数,再将该函数作为函数指针参数,传到qsort函数中,qsort就会自动为我们排序。

而对于compar函数,我们要完成的功能是:

当p1所指对象<p2所指对象,返回负数

当p1所指对象>p2所指对象,返回正数

当p1所指对象==p2所指对象,返回0

下面使用qsort来排序一个结构体

struct Stu
{
	char name[20];
	int age;
};

void PirntStruct(const void* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d\n", (((struct Stu*)arr)+i)->name, ((struct Stu*)arr+i)->age);
	}
	printf("\n");
}

//以年龄排序
int compar_by_age(const void* p1, const void* p2)
{
	return (((struct Stu*)p1)->age - ((struct Stu*)p2)->age);
}

//以名字排序
int compar_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
    //字符串的比较用strcmp函数,strcmp函数的返回值正好与我们要求的返回值一致
}

int main()
{
	struct Stu stu[] = { {"baiyahua",20},{"zhangsan",18},{"lisi",13} };
	int sz = sizeof(stu) / sizeof(stu[0]);
	printf("排序前:\n");
	PirntStruct(stu, sz);

	printf("以名字排序后:\n");
	qsort(&stu, sz, sizeof(stu[0]), compar_by_name);
	PirntStruct(stu, sz);

	printf("以年龄排序后:\n");
	qsort(&stu, sz, sizeof(stu[0]), compar_by_age);
	PirntStruct(stu, sz);

	return 0;
}

8.3qsort的模拟实现

知道了qsort的使用方法,我们能不能自己实现一个qsort函数,这里底层用冒泡排序来模拟一个qsort函数。

需要解决的问题:

函数的参数是怎么设计的?这里可以参考库中的qsort函数

怎么比较两个数据?由于需要排序不同类型的数据,就需要不同类型数据的比较方法,我们将方法独立写成一个函数,将该函数的指针传为我们的qsort函数,再我们的qsort函数中调用

怎么交换两个数据?我们将两个数据的内存全部交换相当于交换的两个数据

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

void Swap(char* e1, char* e2, int size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = e1[i];
		e1[i] = e2[i];
		e2[i] = tmp;
	}
}

void my_qsort(void* base, size_t num, size_t size, int compar(const void*,const void*))
{
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

这里解释一下两个地方

 想要排序结构体数据时只要给出结构体数据的比较方法即可

九、指针和数组笔试题解析

int main()
{
	//一维数组
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));// 16,sizeof(数组名),计算整个数组的大小
	printf("%d\n", sizeof(a + 0));// 4/8,32位机器下4字节,64位机器下8字节
	printf("%d\n", sizeof(*a));// 4,sizeof(1),整形数据的大小
	printf("%d\n", sizeof(a + 1));// 4/8,地址的大小
	printf("%d\n", sizeof(a[1]));// 4,sizeof(2)
	printf("%d\n", sizeof(&a));// 4/8,地址的大小
	printf("%d\n", sizeof(*&a));// 16,*和&相抵消,相当于sizeof(a)
	printf("%d\n", sizeof(&a + 1));// 4/8,地址的大小
	printf("%d\n", sizeof(&a[0]));// 4/8,地址的大小
	printf("%d\n", sizeof(&a[0] + 1));// 4/8,地址的大小

	return 0;
}
//字符数组
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));//6,计算数组所占空间
	printf("%d\n", sizeof(arr + 0));//4/8,地址的大小
	printf("%d\n", sizeof(*arr));//1,字符'a'的大小
	printf("%d\n", sizeof(arr[1]));//1,字符'b'的大小
	printf("%d\n", sizeof(&arr));//4/8,地址的大小
	printf("%d\n", sizeof(&arr + 1));//4/8,地址的大小
	printf("%d\n", sizeof(&arr[0] + 1));//4/8,地址的大小

	printf("%d\n", strlen(arr));//随机值
	printf("%d\n", strlen(arr + 0));//随机值
	printf("%d\n", strlen(*arr));
	//程序报错,'a'的ASCII码值是97,strlen认为97是个地址,而97对于的内存地址不属于我们
	printf("%d\n", strlen(arr[1]));//程序报错
	printf("%d\n", strlen(&arr));//随机值
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//随机值

	return 0;
}
int main()
{
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));// 7
	printf("%d\n", sizeof(arr + 0));// 4/8
	printf("%d\n", sizeof(*arr));//1
	printf("%d\n", sizeof(arr[1]));// 1
	printf("%d\n", sizeof(&arr));// 4/8
	printf("%d\n", sizeof(&arr + 1));// 4/8
	printf("%d\n", sizeof(&arr[0] + 1));// 4/8

	printf("%d\n", strlen(arr));// 6
	printf("%d\n", strlen(arr + 0));//6
	printf("%d\n", strlen(*arr));//程序报错
	printf("%d\n", strlen(arr[1]));//程序报错
	printf("%d\n", strlen(&arr));//6
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//5

	return 0;
}
int main()
{
	char* p = "abcdef";
	printf("%d\n", sizeof(p));// 4/8
	printf("%d\n", sizeof(p + 1));// 4/8
	printf("%d\n", sizeof(*p));// 1
	printf("%d\n", sizeof(p[0]));// 1
	printf("%d\n", sizeof(&p));// 4/8
	printf("%d\n", sizeof(&p + 1));// 4/8
	printf("%d\n", sizeof(&p[0] + 1));// 4/8

	printf("%d\n", strlen(p));//6
	printf("%d\n", strlen(p + 1));//5
	//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));//5

	return 0;
}

10.指针笔试题

笔试题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是数组名,既没有sizeof(数组名),也没有&数组名,所以就表示数组首元素地址;
    *(a+1)就表示数组第二个元素,也就是2
  • &a取出整个数组的地址,(&a+1)跳过一个数组,再强转成一个int*类型;因此ptr-1就指向数组倒数第一个元素,*(ptr-1)就是5

笔试题2: 

//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
	p = 0x100000;
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

题目解析:

  • p是一个结构体指针,+1跳过一个结构体的大小,因此(p+1)跳过20个字节,转换成16进制就是0x00000014,(p+0x1)就是00100014
  • p被强转成无符号长整型,+1就是整型+1,因此((unsigned long)p+0x1)就是00100001
  • p被强转成无符号整型指针,+1跳过一个整型指针类型,也就是4字节,((unsigned int*)p+0x1)就是00100004

笔试题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;
}

题目解析:

  • (&a+1)跳过数组,再强转成整型;ptr[-1]相当于*(ptr-1),答案是4
  • a被强转成整型,+1就是数值+1,再被强转整型指针,这里我们需要画出a数组的详细内存图
    *ptr2就是图中的红色部分,但内存中是以小端字节序存储的,拿出来要还原,因此答案就是02000000

笔试题4:

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

题目解析:

  • 需要注意,数组的初始化中有(),不要误以为这是在初始化数组,()中是一个表达式,其结果是最后一个表达式的结果,因此(0,1)是1,(2,3)是3,(4,5)是5,实际的数组中的数据

    a[0]是数组首行的地址,也是第一行的数组名,数组名表示首元素的地址,a[0]就是第一行第一个元素的地址,p[0]相当于*(p+0);答案是1

笔试题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;
}

题目解析:

  • 这题需要我们画出数组内存图
    (&p[4][2]-&a[4][2])是一个高地址-低地址,得出的结果是-4;
    -4的补码是:11111111 11111111 11111111 11111100,表示成16进制就是FFFFFFFC
    以%d打印就是-4

笔试题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;
}

代码解析:

  • (ptr1-1)是数组倒数第一个元素的地址,*(ptr1-1)就是10
  • *(aa+1)可以看成a[1],是第二行的数组名,也就是第二行首元素的地址,被强转成了int*;因此(ptr2-1)是第一行倒数第一个元素的地址,答案是5

笔试题7:

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

题目解析:

  • 内存分布图
    答案就是at

面试题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;
}

题目解析:

  • 内存分布图
    需要注意:++会改变cpp的值
    因此答案就是POINT,ER,ST,EW

关于指针的内容就讲到这!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

协方差和相关系数,还有信号与系统里的 互相关函数

协方差和相关系数参考&#xff1a;https://www.bilibili.com/video/BV1vK411N7Yp/ 协方差和相关系数的思想就是&#xff1a;同增同减&#xff0c;找相关的变量 协方差公式是如下图老师的板书 可以发现&#xff0c;当 X Y 同增同减趋势明显时&#xff0c;协方差的值就越大 所…

STM32_启动流程详解

目录标题 前言 启动流程概述复位中断函数详解SystemInit函数详解 __main函数详解 附录 stm32单片机的存储器映像中断向量表的映射 前言 最近在学习IAP远程OTA升级单片机固件程序&#xff0c;发现自己对单片机的启动流程还不是那么了解&#xff0c;就总结整理一下吧。 启动流程…

记录一次API报文替换点滴

1. 需求 各位盆友在日常开发中&#xff0c;有没有遇到上游接口突然不合作了&#xff0c;临时需要切换其他接口的情况&#xff1f;这不巧了&#xff0c;博主团队近期遇到了&#xff0c;又尴尬又忐忑。 尴尬的是临时通知不合作了&#xff0c;事前没有任何提醒&#xff1b; 忐忑…

位图、布隆过滤器、海量数据处理

文章目录 位图布隆过滤器海量数据处理 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 位图 概念&#xff1a;所谓位图&#xff0c;就是用每一…

【Spring】08 BeanNameAware 接口

文章目录 1. 简介2. 作用3. 使用3.1 创建并实现接口3.2 配置 Bean 信息3.3 创建启动类3.4 启动 4. 应用场景总结 Spring 框架为开发者提供了丰富的扩展点&#xff0c;其中之一就是 Bean 生命周期中的回调接口。本文将聚焦于其中的一个接口 BeanNameAware&#xff0c;介绍它的作…

深度学习中的预测图片中的矩形框、标签、置信度分别是什么意思。

问题描述&#xff1a;深度学习中的预测图片中的矩形框、标签、置信度分别是什么意思。 问题解答&#xff1a; 目标框&#xff08;Bounding Box&#xff09;&#xff1a; 描述目标位置的矩形边界框。 类别标签&#xff1a; 表示模型认为目标属于哪个类别&#xff08;例如&#…

opencv 十六 python下各种连通域处理方法(按面积阈值筛选连通域、按面积排序筛选连通域、连通域分割等方法)

本博文基于python-opencv实现了按照面积阈值筛选连通域、按照面积排序筛选topK连通域、 连通域细化(连通域骨架提取)、连通域分割(基于分水岭算法使连通域在细小处断开)、按照面积排序赛选topK轮廓等常见的连通域处理代码。并将代码封装为shapeUtils类,在自己的python代码…

[Verilog] 设计方法和设计流程

主页&#xff1a; 元存储博客 文章目录 1. 设计方法2. 设计流程 3 Vivado软件设计流程总结 1. 设计方法 Verilog 的设计多采用自上而下的设计方法&#xff08;top-down&#xff09;。设计流程是指从一个项目开始从项目需求分析&#xff0c;架构设计&#xff0c;功能验证&#…

openEuler商业化进展可观:累计装机量超610万套,市场持续扩容

12月15日至16日&#xff0c;以“崛起数字时代&#xff0c;引领数智未来”为主题的操作系统大会&#xff06;openEuler Summit 2023在北京国家会议中心举办。大会旨在汇聚全球产业界创新力量&#xff0c;构筑坚实的基础软件根基&#xff0c;推动基础软件技术持续创新&#xff0c…

Redis设计与实现之整数集合

目录 一、内存映射数据结构 二、整数集合 1、整数集合的应用 2、数据结构和主要操作 3、intset运行实例 创建新intset 添加新元素到 intset 添加新元素到 intset&#xff08;不需要升级&#xff09; 添加新元素到 intset (需要升级) 4、升级 升级实例 5、关于升级 …

帆软FCRP模拟题

制作步骤可见此博主&#xff1a;https://blog.csdn.net/Ipkiss_Yongheng/article/details/125594366 完成文件下载&#xff1a;【免费】帆软FCRP官网模拟题代码资源-CSDN文库

大创项目推荐 垃圾邮件(短信)分类算法实现 机器学习 深度学习

文章目录 0 前言2 垃圾短信/邮件 分类算法 原理2.1 常用的分类器 - 贝叶斯分类器 3 数据集介绍4 数据预处理5 特征提取6 训练分类器7 综合测试结果8 其他模型方法9 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 垃圾邮件(短信)分类算…

5个创建在线帮助文档的好方法!

在线帮助文档是企业为用户提供支持服务的重要工具&#xff0c;它能够帮助用户更好地了解和使用产品&#xff0c;提高用户体验。然而&#xff0c;创建一份优秀的在线帮助文档需要掌握一定的技巧和方法。接下来就介绍一下创建在线帮助文档的5个好方法&#xff0c;帮助企业更好地为…

day05-报表技术-图形报表

1、图表报表简介 ​ 在大数据时代&#xff0c;人们需要对大量的数据进行分析&#xff0c;帮助用户或公司领导更直观的察觉差异&#xff0c;做出判断&#xff0c;减少时间成本&#xff0c;而在web项目中除了表格显示数据外&#xff0c;还可以通过图表来表现数据&#xff0c;这种…

一维数组的定义

什么是数组&#xff1f; &#xff08;1&#xff09;数组是具有一定顺序关系的若干变量的集合&#xff0c;组成数组的各个变量统称为数组的元素 &#xff08;2&#xff09;数组中的各元素的数据类型要求相同&#xff0c;用数组名和下标确定&#xff0c;数组可以是一维的&#…

【java】数组遍历的方式:

文章目录 1、for循环遍历&#xff1a;2、forEach循环&#xff08;增强for循环&#xff09;&#xff1a;3、while循环 或者 do while循环&#xff1a;4、利用Arrays工具类当中的toString()&#xff1a;5、流式遍历:6、使用Arrays.asList()和forEach()方法进行遍历: 1、for循环遍…

java-IO流

File类 引入 【1】文件&#xff0c;目录&#xff1a; 文件&#xff1a; 内存中存放的数据在计算机关机后就会消失。要长久保存数据&#xff0c;就要使用硬盘、光盘、U 盘等设备。为了便于数据的管理和检索&#xff0c;引入了“文件”的概念。一篇文章、一段视频、一个可执…

博途WinCC专业版C/S架构入门指南

WinCC Professional V16 支持客户机/服务器架构&#xff0c;但目前只支持单个服务器或单对冗余服务器/多个客户机的模式&#xff0c;还不能支持像WinCC V7.5 SP1中的多个服务器/多个客户机的分布式架构。 博途工控人平时在哪里技术交流博途工控人社群 博途工控人平时在哪里技…

C语言 (指针)输入三个整数,由小到大输出

目录 1问题&#xff1a; 2代码&#xff1a; 3运行结果&#xff1a; 4总结&#xff1a; 1问题&#xff1a; 输入3个整数a,b,c,要求按由小到大的顺序将他们输出。用函数和指针实现》 2代码&#xff1a; #include<stdio.h> int main() {void exchange(int *q1,int *q2…

Cython(将Python编译为so)

环境 先配一下环境&#xff0c;我使用的是python3.8.5 pip install Cython 编译过程 我们准备一个要编译的文件 test.py def xor(input_string): output_string "" for char in input_string: output_string chr(ord(char) ^ 0x66) return output_string …