其他分享
首页 > 其他分享> > 一次学透C指针【C进阶】

一次学透C指针【C进阶】

作者:互联网

前言:本章主要内容是C指针进阶部分,旨在一次性讲透指针,主要以程序代码块形式讲解。有些难度,坚持住兄弟们!

文章目录



数组与指针

实例1
	char* ps = "hello";
	char arr[] = "hello";

完成以上赋值后,进行如下输出,思考下输出结果?

printf("%c\n", *ps);
printf("%s\n", ps);
printf("%s\n", arr);

答案:分别输出h、hello、hello,第一个h是因为*ps指针指向的就是hello的首字符h,故输出h;后俩个输出本质上一样的,数组名arr和ps一样都是hello的首字符地址,故均输出hello。



实例2

思考下列程序输出结果是两个都相同呢?还是存在不同?(题选自剑指offer)


    char str1[] = "hello";
    char str2[] = "hello";
    const char* str3 = "hello";
    const char* str4 = "hello";
    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");

在这里插入图片描述

答案:str1和str2不同,str3和str4相同。因为str1[]、str2[]两个数组分别创建了一块空间,分别放了hello,而数组名又代表首元素地址,str1和str2分别指向两个hello的首地址,这俩地址不同,故str1和str2不同;而str3和str4指向的hello是常量字符串的首元素地址,该字符串不能被修改,内存中只存一份,故str3和str4相同。



指针数组

定义:本质上是数组,但数组中存放的是指针(地址)

int* arr[3];//存放整形指针的数组
实例1
int a = 10;
int b = 20;
int c = 30;
int* arr[3] = {&a, &b, &c};
int i = 0;
for (i = 0; i < 3; i++)
{
	printf("%d ", *(arr[i]));
}

解析:arr[i]内存放的分别是a、b、c的地址,想要打印出具体数字就需要对arr[i]使用解引用操作符*



实例2
int a[5] = { 1,2,3,4,5 };
int b[] = { 2,3,4,5,6 };
int c[] = { 3,4,5,6,7 };

int* arr[3] = {a,b,c};
int i = 0;
for (i = 0; i < 3; i++)
{
	int j = 0;
	for (j = 0; j < 5; j++)
	{
		printf("%d ", *(arr[i] + j));
		printf("%d ", arr[i][j]);
	}
	printf("\n");
}

在这里插入图片描述

解析:int* arr[3] = {a,b,c};相当于int* arr[3] 里边三个指针类型的元素分别指向数组a、数组b、数组c的首地址。输出结果将分别输出数组a、b、c所有元素。



数组指针

定义:是一种指针,是指向数组的指针

实例1

怎么取一个数组的地址,直接用数组名行不行?

int arr[10]={1,2,3};
arr;
&arr;
int (*parr)[10] = &arr;

注释:“&arr”取出的是数组的地址,“arr”是数组首元素的地址,二者本质上不同,但是打印出来显示的值是一样的。parr就是一个数组指针,parr中存放的就是数组的地址。

思考下对于存放以下指针数组应该用什么样子的数组指针

double* d[5];

应该用:

double* (*pd)[5] = &d;//ok pd就是一个数组指针

在这里插入图片描述



实例2

对于数组arr[]而言,arr与&arr意义完全是不一样的,以下代码感受以下:

	int arr[10] = {0};

	int* p1 = arr;
	int (*p2)[10] = &arr;

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

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

在这里插入图片描述

注释:p1是整形指针,+1会跳过四个字节,p2是一个数组指针,+1会跳过一个数组。

再提一次,数组名是数组首元素的地址,但是有2个例外:

  1. sizeof(数组名) - 数组名表示整个数组,计算的是整个数组大小,单位是字节
  2. &数组名 - 数组名表示整个数组,取出的是整个数组的地址


实例3

数组指针的数组遍历:

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int (*pa)[10] = &arr;
int i = 0;
for (i = 0; i < 10; i++)
{
	printf("%d ", *((*pa) + i));
}

在这里插入图片描述

但是多多少少有点麻烦,没必要用到数组指针,单纯使用指针int *pa=&arr就足够了。



实例4

二维数组通过数组指针传参

void print2(int(*p)[5], int r, int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + 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 } };
    
	print2(arr, 3, 5);//arr数组名,表示数组首元素的地址
	return 0;
}

在这里插入图片描述

解析:二维数组的数组名表示首元素的地址,而二维数组的首元素是:第一行。二维数组把每一行看成一个元素。int(*p)[5]是一个指向一维数组的指针,每个数组有5个元素,每个元素都是int类型。

区分下以下数据类型:

int arr[5] 整型数组

*int parr1[10] 整型指针的数组

*int (parr2)[10] 数组指针,该指针能够指向一个数组,数组有10个元素,每个元素的类型是int

*int (parr3[10])[5] 一个存放数组指针的数组,能存放10个数组指针,每个数组指针指向一个数组,数组有5个元素,每个元素是int类型,图片拆解如下:

在这里插入图片描述



一维数组参数

判断哪些传参类型可以?哪些不行?

在这里插入图片描述

答案:都正确,这里分析下最后一个二级指针来传递指针函数

在这里插入图片描述



二维数组传参

判断哪些传参类型可以?哪些不行?

在这里插入图片描述

答案见下图(直接手写分析了):

在这里插入图片描述



插播内容(非指针内容),让大家大脑休息下,如下代码思考输出是>还是<呢?
i--;
if (i > sizeof(i))//
{
    printf(">\n");
}
else
{
    printf("<\n");
}

第一眼看是不是觉得一点难度都没有,i没有初始化默认是0,i–执行后i的数值是-1,sizeof(i)得到4,-1<4所以认为输出是<,但很遗憾,你成功被坑了。

解析:因为sizeof()得到的数据类型是无符号整型unsigned int,所以-1直接转换成无符号整形的形式,变成一个巨大的数字,所以结果是>。



一级指针传参

void test(char* p)
{
}

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//p是一级指针
	print(p, sz);
	
	char ch = 'w';
	char* p1 = &ch;
	test(&ch);//char*
	test(p1);

	return 0;
}

解析:遇见函数形式参数类型为一级指针(比如char*)时要知道实际参数传递过来的是地址。



二级指针传参

二级指针的3种传参方式,如下程序所示。

void test(int** p2)
{
	**p2 = 20;
}

int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	//怎么把二级指针进行传参呢?
    
	test(ppa);
    printf("%d\n", a);//
	test(&pa);
    printf("%d\n", a);//

	int* arr[10] = {0};
	test(arr);
	printf("%d\n", a);//

	return 0;
}

解析:pa是一级指针,ppa是二级指针,test(ppa)把二级指针ppa进行传参;test(&pa)传一级指针变量的地址;test(arr)传存放一级指针的数组,传递了arr数组名,数组名代表首元素的地址,而arr数组每个元素都是int *类型,int *的地址相当于int * *,这样一来,符合咱们 void test(int ** p2)中形参的要求,也可以传递。



函数指针

定义:存放函数地址的指针

程序1
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//&函数名 - 取到的就是函数的地址
	//pf就是一个函数指针变量
	int (*pf)(int, int) = &Add;

	printf("%p\n", &Add);
	printf("%p\n", Add);
	return 0;
}

在这里插入图片描述

解释:函数名Add和&Add的效果是一样的。&函数名 - 取到的就是函数的地址;函数名-取到的也是函数的地址。



程序2
int main()
{
	//pf就是一个函数指针变量
	//int (*pf)(int, int) = &Add;

	int (*pf)(int, int) = Add;//Add === pf
	int ret1 = (*pf)(3, 5);//方式1
	int ret2 = pf(3, 5);//方式2
	int ret3 = Add(3, 5);//方式3 
	//int ret4 = * pf(3, 5);//err

	printf("%d %d %d \n", ret1, ret2, ret3);
	return 0;
}

在这里插入图片描述

解释:int ret1 = (*pf)(3, 5); int ret2 = pf(3, 5);int ret3 = Add(3, 5);三种调用方式效果一样,int ( *pf )(int, int) = Add;Add能把地址放进pf,说明两者一样的,Add==pf。方式1和方式2一模一样,那个解引用操作符 * 没有任何作用,就是一个摆设之所以放着 * 就是为了便于理解。



OK,fine,到这里咱们进行一个小总结!

在这里插入图片描述
在这里插入图片描述



阅读两段有趣的代码(程序选自<c陷阱与缺陷>):

程序1
(*(void(*)())0)();

在这里插入图片描述

调用0地址处的函数
该函数无参,返回类型是void

  1. void(*)() - 函数指针类型
  2. (void(*)())0 - 对0进行强制类型转换,被解释为一个函数地址
  3. (void()())0 - 对0地址进行了解引用操作
  4. ((void()())0)() - 调用0地址处的函数


程序2
void (* signal(int, void(*)(int) ) )(int);
  1. signal 和()先结合,说明signal是函数名
  2. signal函数的第一个参数的类型是int,第二个参数的类型是函数指针
    该函数指针,指向一个参数为int,返回类型是void的函数
  3. signal函数的返回类型也是一个函数指针
    该函数指针,指向一个参数为int,返回类型是void的函数
    signal是一个函数的声明

在这里插入图片描述



函数指针数组

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int main()
{
	int (*pf1)(int, int) = Add;
	int (*pf2)(int, int) = Sub;
	int (*pfArr[2])(int, int) = {Add, Sub};//pfArr就是函数指针数组

	return 0;
}

解析:int (*pfArr[2])(int, int) = {Add, Sub};函数指针数组,用于存放同类型的函数指针。



设计一个计算器,计算整型变量的加减乘除。

计算器解法1

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

void menu()
{
	printf("**************************\n");
	printf("**** 1. add    2. sub ****\n");
	printf("**** 3. mul    4. div ****\n");
	printf("****     0. exit      ****\n");
	printf("**************************\n");
}

int main()
{
	int input = 0;
	//计算器-计算整型变量的加、减、乘、除
	//a&b a^b a|b a>>b a<<b a>b

	do {
		menu();

		//pfArr就是函数指针数组
		//转移表 - 《C和指针》

		int (*pfArr[5])(int, int) = { NULL, Add, Sub, Mul, Div };
		int x = 0;
		int y = 0;
		int ret = 0;
		printf("请选择:>");
		scanf("%d", &input);//2

		if (input >= 1 && input <= 4)
		{
			printf("请输入2个操作数>:");
			scanf("%d %d", &x, &y);
			ret = (pfArr[input])(x, y);
			printf("ret = %d\n", ret);
		}
		else if(input == 0)
		{
			printf("退出程序\n");
			break;
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);
	return 0;
}
回调函数

定义:回调函数就是一个通过函数指针调用的函数。 如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时, 我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的- -方调用的,用于对该事件或条件进行响应。

在这里插入图片描述

考虑用回调函数简化计算器解法1



计算器(使用回调函数的解法2)

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

void menu()
{
	printf("**************************\n");
	printf("**** 1. add    2. sub ****\n");
	printf("**** 3. mul    4. div ****\n");
	printf("****     0. exit      ****\n");
	printf("**************************\n");
}

int Calc(int (*pf)(int, int))
{
	int x = 0;
	int y = 0;
	printf("请输入2个操作数>:");
	scanf("%d %d", &x, &y);
	return pf(x, y);
}

int main()
{
	int input = 0;
	//计算器-计算整型变量的加、减、乘、除
	//a&b a^b a|b a>>b a<<b a>b

	do {
		menu();
	
		int ret = 0;
		printf("请选择:>");
		scanf("%d", &input);

		switch (input)
		{
		case 1:
			ret = Calc(Add);
			printf("ret = %d\n", ret);
			break;
		case 2:
			ret = Calc(Sub);
			printf("ret = %d\n", ret);
			break;
		case 3:
			ret = Calc(Mul);//
			printf("ret = %d\n", ret);
			break;
		case 4:
			ret = Calc(Div);//
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误,重新选择!\n");
			break;
		}
		
	} while (input);
	return 0;
}


OK,fine,到这里咱们在进行一个小结!

在这里插入图片描述

qsort库函数

作用:排序任何类型的变量

在这里插入图片描述

为什么这里使用的都是void 指针?*

void * 指针是无具体类型的指针,可接受任意数据类型的指针,但不能直接进行加减整数等操作,并不能直接进行解引用。
因为要接收的数组类型我们不能确定,可能是整型,也可能是浮点型,所以只能用void* 来接收所有类型的元素。
并且下面我们在使用void*指针进行比较时,都会先进行强制类型转换。

在这里插入图片描述

参考下图对整型数据的冒泡排序,思考如何实现对不同数据类型的冒泡排序?

在这里插入图片描述

当所计算数据类型为int时,如下所示即可。

#include <stdio.h>

//qosrt函数的使用者得实现一个比较函数

int cmp1(const void * p1, const void * p2) //提供的是整型数组元素大小的比较方式
{

  return (*( int *)p1 - *(int *) p2);
  //强制类型转换为int*,说明比较的是整型元素
  //p1,p2接收的是对应元素的地址
  //如果返回值大于0,则两元素进行交换
  }

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), cmp1);
    //sizeof(arr) / sizeof(arr[0])即数组元素个数
    //sizeof (int)即数组元素大小

    for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)

   {

       printf( "%d ", arr[i]);

   }

    printf("\n");

    return 0; 
}

当所计算数据类型为浮点数类型时,可修改为如下形式:

int cmp2(const void* elem1, const void* elem2)
{
	return	 (*(float*)elem1 - *(float*)elem2);
}

当所计算数据类型为结构体类型时,可修改为如下形式:

int cmp3(const void* elem1, const void* elem2)
{
	return  (strcmp(((struct stu*)elem1)->name, ((struct stu*)elem2)->name));//比较结构体成员中的字符串类型
}
int cmp4(const void* elem1, const void* elem2)//比较结构体成员中的int类型
{
	return  (((struct stu*)elem1)->num - ((struct stu*)elem2)->num);
}

使用回调函数,模拟实现qsort(采用冒泡的方式)。

#define  _CRT_SECURE_NO_WARNINGS  1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int cmp1(const void* p1, const void* p2) //提供的是整型数组元素大小的比较方式
{
	return (*(int*)p1 - *(int*)p2);
}

int cmp2(const void* elem1,const  void* elem2) //提供的是浮点型数组元素大小的比较方式
{
	return	 (*(float*)elem1 - *(float*)elem2);
}

struct stu2 {
	char name[20];
	int age = 0;
};
int cmp3(const void* elem1, const void* elem2) //提供的是结构体中stu2中name成员的比较方式
{
	return  (strcmp(((struct stu2*)elem1)->name, ((struct stu2*)elem2)->name));
}

int cmp4(const void* elem1, const void* elem2)//提供的是结构体中stu2中age成员的比较方式
{
	return  (((struct stu2*)elem1)->age - ((struct stu2*)elem2)->age);
}

void customer_sort(void*base, int num, int width, int (*cmp)(const void*elem1,const void* elem2))
{
	int i = 0;
	for (; i < num - 1; i++)//确定排序趟数
	{
		for (int b = 0; b < num - 1 - i; b++)//每趟排序进行比较的次数
				{
			if (cmp(((char*)base + b * width), ((char*)base + (b + 1) * width)) > 0)
			//如果cmp函数返回值大于0,则进行交换
			{
				char tmp = 0;
				for (int j = 0; j < width; j++)
				{
					tmp = *((char*)base + b * width + j);
					*((char*)base + b * width + j) = *((char*)base + (b + 1) * width + j);
					*((char*)base + (b + 1) * width + j) = tmp;
				}
			}
		}
	}
}
int main()
{
	int arr1[10] = { 2,4,6,8,1,3,9,5,7,10 };
	float arr2[5] = { 3.0,5.0,1.0,7.0,8.0 };
	int sz = sizeof(arr2) / sizeof(*arr2);
	customer_sort(arr2, sz, sizeof(*arr2),cmp2);//如果要排序其他类型数组
	//把cmp函数换成对应的就行了
	for (int a = 0; a < sz; a++)
	{
		printf("%f ", arr2[a]);
	}
	return 0;
}

注释:

1.因为我们无法确定传进来的数组元素是啥类型,所以接受数组起始位置的指针使用的是空指针void* base,方便接收所有类型的指针,cmp函数的两个参数也是如此。
2.传递数组元素给cmp函数时,因为无法确定元素类型,所以指针往后走一步的大小也无法确定,所以只能用char* 对base进行强制类型转换,然后循环跳到下一个元素时,使用b * width,width表示对应元素类型的大小,b * width即跳过b * width个字节,找到对应元素的地址,(char * )base + 1 * width即为数组第二个元素,(char*)base + b*width即为第b+1个元素,下标为b,这样可以跳到对应位置,交换元素时,也需要按照这种方式来进行交换。并且交换也只能按一个字节一个字节的内容进行交换,故第三个for循环就是让两元素的每个字节都进行交换,循环次数也取决于width的大小,这样才能保证一个元素的所有字节都进行了交换。





指针和数组笔试题

一维数组

sizeof(数组名) - 数组名表示整个数组的-计算的是整个数组的大小
&数组名 - 数组名表示整个数组,取出的是整个数组的地址
除此之外,所有的数组名都是数组首元素的地址

习题1
    int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a + 0));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(a[1]));

	printf("%d\n", sizeof(&a)); 
	printf("%d\n", sizeof(* &a));
	//&a -- int(*p)[4] = &a;
	printf("%d\n", sizeof(&a + 1));
	printf("%d\n", sizeof(&a[0]));
	printf("%d\n", sizeof(&a[0] + 1));

答案:

在这里插入图片描述



字符数组

习题2
    char arr[] = { 'a','b','c','d','e','f' };
    printf("%d\n", sizeof(arr));//这里没有\0之分
	printf("%d\n", sizeof(arr + 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));

答案:

在这里插入图片描述



习题3
    char arr[] = { 'a','b','c','d','e','f' };
    printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	//printf("%d\n", strlen(*arr));
	//printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));

答案:

在这里插入图片描述

因为strlen结束标志是\0,所以前俩个找不到结束标志结果就是随机值;第三和第四个错了,strlen()应该传递的是地址,他们却传递了具体数值,把数值97当成具体地址;第五个传递过去依然会被认为是arr首地址,随机值;第六个跳过一个数组,也是随机值;第七个同理,从b的地址向后寻找\0,也是随机值。



习题4
	char arr[] = "abcdef";
	//[a b c d e f \0]
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	//printf("%d\n", strlen(*arr));
	//printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));

答案:

在这里插入图片描述



习题5
	char arr[] = "abcdef";
	//[a b c d e f \0]
    printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 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));

答案:

在这里插入图片描述



习题6
	char* p = "abcdef";

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

答案:4/8;4/8;1;1;4/8;4/8;4/8



习题7
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));

答案:

6;5;err;err;

第五个随机(对p取地址,向后去\0压根不知道结果);

第六个随机,与第五个同理;

5 。



二维数组

习题8
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));
printf("%d\n", sizeof(*(a[0] + 1)));

printf("%d\n", sizeof(a + 1));
printf("%d\n", sizeof(*(a + 1)));

printf("%d\n", sizeof(&a[0] + 1));

printf("%d\n", sizeof(*(&a[0] + 1)));

printf("%d\n", sizeof(*a));

printf("%d\n", sizeof(a[3]));
printf("%d\n", sizeof(a[-1]));

答案:

在这里插入图片描述



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

答案:

2;5

在这里插入图片描述



习题10

由于还没学习结构体,这里告知结构体的大小是20个字节。

假设p 的值为0x100000。 如下表达式的值分别为多少?
已知,结构体Test类型的变量大小是20个字节

提示:指针类型决定了指针的运算

struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}* p;


int main()
{
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}




答案:

在这里插入图片描述



习题11
   int a[4] = { 1, 2, 3, 4 };
   int* ptr1 = (int*)(&a + 1);
   int* ptr2 = (int*)((int)a + 1);

   printf("%x,%x", ptr1[-1], *ptr2);

答案:

在这里插入图片描述



习题12

提示:坑爹的逗号表达式

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

答案:1

在这里插入图片描述



习题13
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]);

答案:

在这里插入图片描述



习题14
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));

第一反应是5;1,误以为&aa+1是跳过一行,aa+1是跳过一个。然而实际上&aa+1跳过整个二维数组,aa+1跳过一行。

答案:10;5



习题15
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);

答案:at

char* a[]是指针数组,数组每个元素都是char 类型,a[]中第一个元素是char ,那么指向其地址就应该是char

在这里插入图片描述



习题16(难)
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);

答案:

在这里插入图片描述

提示:只有第一步和第二步改变了cpp的指向,第三第四部均没有改变cpp的指向。

在这里插入图片描述
在这里插入图片描述

C指针进阶部分到此介绍结束了,感谢您的阅读!!!如果内容对你有帮助的话,记得给我三连(点赞、收藏、关注)——做个手有余香的人。

标签:arr,进阶,int,数组,printf,sizeof,学透,指针
来源: https://blog.csdn.net/weixin_48953972/article/details/117591550