您当前的位置: 首页 > 技术文章 > 编程语言

切,不就是指针吗

作者: 时间:2022-03-01阅读数:人阅读


针对于指针这一块区域,很多人都很头疼,但是又没有什么好办法,在这篇文章,我会带着大家一点一点分析指针,过程虽然会很痛苦,但是一旦学懂之后发现其实也没啥的,加油!冲冲冲!
在这里插入图片描述


学习重点

  1. 字符指针
  2. 数组指针
  3. 指针数组
  4. 数组传参和指针传参
  5. 函数指针
  6. 函数指针数组
  7. 指向函数指针数组的指针
  8. 回调函数
  9. 指针和数组面试题的解析

知识回顾

  1. 指针的作用: 可以通过指针间接访问内存

    内存编号是从0开始记录的,一般用十六进制数字表示

    可以利用指针变量保存地址

  2. 指针变量的定义和使用

    指针变量定义语法: 数据类型 * 变量名;

    指针变量可以通过" * "操作符,操作指针变量指向的内存空间,这个过程称为解引用

  3. 指针所占内存空间:在32位平台下是四个字节,在64位平台是8个字节

  4. 空指针:指针变量指向内存中编号为0的空间

    野指针:指针变量指向非法的内存空间

  5. 指针±整数:int 4个字节 char 1个字节

    指针-指针:中间的元素个数

  6. 二级指针

1.字符指针

  1. 字符指针的使用:

    //实例1
    #include <stdio.h>
    
    int main()
    {
      char ch = 'w';
      char *pc = &ch;
      *pc = 'w';
      return 0;
    }
    
    //实例2
    #include <stdio.h>
    
    int main()
    {
       char* p="abcdef";
       printf("%s\n",p);
    }
    
    //实例三
    #include <stdio.h>
    
    int main()
    {
    	char* p = "abcdef";
    	*p = 'w';
    	printf("%s\n", p);
    	return 0;
    }
    

    解析:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K8zrShCb-1645607593207)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1644660671354.png)]

那么有一道来自《剑指Offer》的题目,让我们look look吧

#include<stdio.h>

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

	const char* str1 = "abcdef";
	const char* str2 = "abcdef";

	if (arr1 == arr2)
		printf("arr1==arr2\n");
	else
		printf("arr1!=arr2\n");

	if (str1 == str2)
		printf("str1==str2\n");
	else
		printf("str1!=str2\n");

	return 0;
}

答案是arr1!=arr2  str1==str2,做对了没?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qSNFFB2E-1645607593209)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1644660699309.png)]

那么接下来我在强调一下大家可能会出现错误的地方

1.int *pa,pb;请问pa pb是什么类型的?
答案:pa->int* pb->int

2.typedef int* PINT
  PINT pa,pb;
答案:pa->int* pb->int*

3.#define pint int*
  pint pa,pb;
答案:pa->int* pb->int

结论:推荐不要去连续定义指针

2.指针数组

整型数组-存放整型的数组

字符数组-存放字符的数组

指针数组->存放指针的数组

下面有几个例子,我们来看一下:

1.int* arr1[10]; //整形指针的数组:arr1先与[]结合,代表是10个元素的数组,每个数组都是int*类型的
  char *arr2[4]; //一级字符指针的数组:把字符串的首字母存入数组中
  char **arr3[5];//二级字符指针的数组
  备注:数组的类型 去掉数组名与元素个数
2.int a = 10;
  int* p = &a;
  int** pp = &p;//二级指针
  int** arr2[4] = {0};//指针数组,存放二级指针
3.
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[] = {arr1, arr2, arr3};//数组名->首元素地址,把每个数组的首元素地址存入指针数组中

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

	return 0;
}//相当于模拟二维数组

备注:
arr[i] 相当于找到每一行的起始地址
arr[i][j]是找到当前行后面的数
*(*(arr+i)+j)的含义:
arr+i:找到行数
*(arr+i):对找到的行进行解引用操作
*(arr+i)+j:在当前行找到每个元素
*(*(arr+i)+j):对每个元素进行解引用操作


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hbfWauMo-1645607593209)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645577218088.png)]

4.
int main()
{
	char* arr[] = { "abcdef", "qwer", "zhangsan" };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);

	for (i = 0; i < sz; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9GZ85RuD-1645607593210)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645577228407.png)]

3.数组指针

3.1 定义

整形指针-指向整型的指针

字符指针-指向字符的指针

数组指针->应该是一种指针,是指向数组的指针

判断一下,下面代码哪个是数组指针?

int *p1[10];//指针数组-p1先与[]结合,指向数组元素为10的数组,数组每个元素是int类型
int (*p2)[10];//数组指针-p2先和*结合,说明p2是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p2是一个指针,指向一个数组,叫数组指针。

注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

3.2 &数组名与数组名

arr是数组名,数组名表示数组首元素的地址。类型:int*

&arr 表示的是数组的地址,而不是数组首元素的地址。类型:数组指针

演示1:

int arr[10] = { 0 };
//数组名->首元素地址
printf("%p\n", arr);//027CFA8C
printf("%p\n", arr+1);//027CFA90
//首元素地址
printf("%p\n", &(arr[0]));//027CFA8C
printf("%p\n", &(arr[0])+1);//027CFA90
//数组的地址->放到数组指针里面
printf("%p\n", &arr);//027CFA8C
printf("%p\n", &arr+1);//027CFAB4

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jFaLmoVL-1645607593210)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645589607373.png)]


演示2:数组指针的类型就是去掉名字即可

char arr[5];
char (*pa)[5] = &arr;

int* parr[6];
int* (*pp)[6] = &parr;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MVRUqFqn-1645607593211)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645589755385.png)]


演示3:数组指针的用途

//数组指针有什么⽤?
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));//1 2 3 4 5 6 7 8 9 10
	}

	return 0;
}

//数组指针很少应⽤于⼀维数组
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *((*p) + i));
	}
	return 0;
}

//数组指针常应⽤于⼆维数组
void print(int a[3][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 ", a[i][j]);
		}
		printf("\n");
	}
}

//int(*p)[5]是数组指针

void print(int(*p)[5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			//*(p+i) 相当于拿到了⼆维数组的第i⾏,也相当于第i⾏的数组名
			//数组名表示⾸元素的地址,其实也是第i⾏第⼀个元素的地址
			printf("%d ", *(*(p + i) + j));
			//
			//p是第⼀⾏的地址
			//p+i是第i⾏的地址
			//*(p+i) 是第i⾏第⼀个元素的地址
			//
		}
		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;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1fZ9XU7a-1645607593211)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645590252840.png)]


演示4:

int main()
{
	int arr[10];
	int i = 0;
	arr[i] == *(arr + i) == p[i] == *(p + i);
	int* p = arr;//p和arr是一回事
	*(p + i);
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g4gpOOx3-1645607593212)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645590375400.png)]


练习: 看看下面代码的意思

1.int arr[5];//arr是⼀个整型数组,有5个元素,每个元素都是int类型的
2.int *parr1[10];//parr1是⼀个数组,数组有10个元素,每个元素的类型是int*,所以parr1是指针数组
3.int (*parr2)[10];//parr2和*结合,说明parr2是⼀个指针,该指针指向⼀个数组,数组是10个元素,每个元素都是int类型。parr2是数组指针
4.int (*parr3[10])[5];//parr3和[]结合,说明parr3是⼀个数组,数组是10个元素,数组的每个元素是⼀种数组指针,类型是int (*)[5],该类型的指针指向的数组有5个int类型的元素

第4题的图解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rt07hXmO-1645607593213)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645590585565.png)]

类型的判断:

  • 指针数组->去掉数组名和元素个数
  • 数组指针->去掉名字

数组名其实就是个地址,举个例子

int a=10;
int arr[5];

我们的变量以及数组名在编译器处理过之后就是地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WiBd6PQ8-1645607593214)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645590783804.png)]

我们在看这样一句代码

int (*p)[5];//这是个数组指针

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gaaKpGLH-1645607593214)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645591141156.png)]

4.数组参数 指针参数

4.1 一维数组传参

#include <stdio.h>
void test(int arr[])//数组传参的时候,形参用指针接受也是没问题的
{ }
void test(int arr[10])//本质:其实并不会新创建一个数组,哪怕你形参元素个数写错了也无所谓
{ }
void test(int* arr)//本质:一维数组传参,传过去的是地址,地址用指针接收没问题
{ }
void test2(int* arr[20])//数组传参,形参写成数组没问题,20个元素每个元素都是int*(指针类型)
{ }
void test2(int** arr)//(地址->类型:)一级指针,用二级指针接收没问题
{ }
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fhVSBauT-1645607593215)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645591526375.png)]

4.2 二维数组传参

void test(int arr[3][5])//ok-数组传参,形参部分写成数组没问题
{}
void test(int arr[][])//err-形参部分的二维数组行可省,列不可省
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)//err-二维数组传参,传过来的是第一行的地址,是int*的,直接用int*接受是不可以的
{}
void test(int* arr[5])//err-传过来的是地址,应该用数组指针而不是指针数组来接收
{}
void test(int(*arr)[5])//ok-传过来的是第一行的地址,用数组指针接受没问题
{}
void test(int** arr)//err-只有传过来⼀级指针的地址时 才⽤⼆级指针接收
{}
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]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}

思考:当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

void test(int* p)
{}

int main()
{
	int a = 10;
	int* ptr = &a;
	int arr[10] = { 0 };
	test(&a);//整个数组
	test(ptr);//整型指针
	test(arr);//数组名

	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;//二级指针,保存的是p的地址
	test(pp);
	test(&p);//一级指针变量的地址
	return 0;
}

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

例子1:

void test(char** p)
{}

int main()
{
	char ch = 'w';
	char* p = &ch;
	char** pp = &p;
	char* arr[5];//指针数组

	test(&p);//一级指针变量的地址
	test(pp);//二级指针(存放一级指针变量的地址)
	test(arr);//数组名-指针数组是char*类型的,用char**接收没问题

	return 0;
}

例子2:二维数组的类型名->类型(*)[列数]

//可以通过这种方法检测两边类型是否相同
//报错信息:"int*"与"int(*)[5]的间接级别不同
int main()
{
	int arr[3][5];
	int p = arr;//err,arr是第一行的地址,是int(*)[5]类型的,应该用int* p来接收

	return 0;
}

5.函数指针

函数指针:指向函数的指针

首先看一段代码:

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

void test(char* str)
{}

int main()
{
    //下面这两个相同,都是函数的地址
    //注意:函数是没有首元素的,不能说首元素地址
	//printf("%p\n", &Add);
	//printf("%p\n", Add);
	
	//int arr[5];
	//void (*pt)(char*) = test;//test函数的函数指针

    //int pf(int, int) = &Add;//err->这样就成函数了
	//int (* pf)(int, int) = &Add;//pf是函数指针
	int (*pf)(int, int) = Add;//pf存放的是Add的地址
	
	//下面两种写法都可以->*是个摆设
	//int sum = (*pf)(2,3);//(*pf)->找到函数 (2,3)->函数调用 e.g.Add(2,3);
	int sum = pf(2, 3);//pf从此和Add一样,可以替换

	//int sum = Add(2, 3);
	//int sum = *pf(2, 3);//err,先执行pf(2,3);在执行解引用操作
	printf("%d\n", sum);

	return 0;
}

函数指针到底是怎么回事呢?

void test()
{
	printf("hehe\n");
}

void (*pfun1)();//pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参
数,返回值类型为void。

阅读两段有趣的代码:

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

代码一:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-epKAbzFp-1645607593216)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645600523862.png)]

代码二:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xTrXTvWK-1645607593216)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645601043789.png)]

6.函数指针数组

函数指针数组:函数指针数组-存放函数指针的数组,每个元素都是函数指针类型

1.函数指针的数组定义:

int (*parr1[10])();
//parr1先和[]结合,说明parr1是数组,数组的内容是什么呢?是 int (*)() 类型的函数指针。

2.函数指针数组的用途:转移表

例子:计算器

//版本1
#include<stdio.h>

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;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输⼊2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输⼊2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输⼊2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输⼊2个操作数:>");
			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;
}
//版本2:转移表
#include<stdio.h>

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;
	int x = 0;
	int y = 0;
	int ret = 0;
	int (*pfArr[5])(int, int) = { 0, Add, Sub, Mul, Div };
	//pfArr是⼀个函数指针的数组,也叫转移表
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else if (input >= 1 && input <= 4)
		{
			printf("输⼊2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("ret = %d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);
	return 0;
}

7.回调函数

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

1.qsort的实现以及使用

1.1 qsort的原理:

//void qsort(void* base,
// size_t num,
// size_t width,
// int(* compare)(const void* e1, const void* e2)
// );

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RUlTiWQg-1645607593217)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645602481091.png)]

2.最普通的排序

#include <stdio.h>

void bubble_sort(int arr[], int sz)
{
	//趟数:sz-1
	int i = 0;
	for (i = 0; i < sz - 1; 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_arr(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]);
	bubble_sort(arr, sz);
	print_arr(arr, sz);
	return 0;
}

3.测试qsort排序整型数组


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

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

//⽐较e1和e2指向的元素
//void*-无具体类型指针
int cmp_int(const void* e1, const void* e2)
{
    //默认升序,想要降序的话,把e1和e2交换位置
    return *(int*)e1 - *(int*)e2;//e1直接解引用不行,要先把e1强转成int*类型,再解引用
}

//测试qsort排序整型数组
void test1()
{
	int arr[] = { 1,4,2,6,5,3,7,9,0,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}

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

4.测试qsort排序结构体数据

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

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

//排序成绩
int cmp_stu_by_score(const void* e1, const void* e2)
{
    //把e1 e2强转成结构体类型
	if (((struct Stu*)e1)->score > ((struct Stu*)e2)->score)
	{
		return 1;
	}
	else if (((struct Stu*)e1)->score < ((struct Stu*)e2)->score)
	{
		return -1;
	}
	else
	{
		return 0;
	}
}

//排序年龄
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

//排序名字
int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
	//引头文件#include<string.h>
}

//打印
void print_stu(struct Stu arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d %f\n", arr[i].name, arr[i].age, arr[i].score);
	}
	printf("\n");
}

void test2()
{
	struct Stu arr[] = { {"zhangsan",20,87.5f},{"lisi",22,99.0f},{"wangwu", 10, 68.5f} };
	//按照成绩来排序
	int sz = sizeof(arr) / sizeof(arr[0]);
	//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_score);
	//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
	print_stu(arr, sz);
}

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

5.自制qsort

//自制qsort排序整型数组
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//排序数字
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

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

void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	//一对一对字符交换
	for (i = 0; i < width; i++)//交换width对字节
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

//⾃制qsort:并未通过宽度推断类型,用宽度计算偏移量
void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	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])
			//一次操作一个字节
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//两个元素的交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
				//void*并不知道会操作几个字节,所以我们要把偏移量width传进去
			}
		}
	}
}

void test3()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr1(arr, sz);
}

int main()
{
	test3();
	return 0;
}
//测试自制qsort排序结构体数据
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

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

//排序成绩
int cmp_stu_by_score(const void* e1, const void* e2)
{
	if (((struct Stu*)e1)->score > ((struct Stu*)e2)->score)
	{
		return 1;
	}
	else if (((struct Stu*)e1)->score < ((struct Stu*)e2)->score)
	{
		return -1;
	}
	else
	{
		return 0;
	}
}

//排序年龄
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

//排序名字
int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

void print_stu2(struct Stu arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d %f\n", arr[i].name, arr[i].age, arr[i].score);
	}
	printf("\n");
}

void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

//⾃制qsort
void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//两个元素的交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

void test4()
{
	struct Stu arr[] = { {"zhangsan",20,87.5f},{"lisi",22,99.0f},{"wangwu", 10, 68.5f} };
	//按照成绩来排序
	int sz = sizeof(arr) / sizeof(arr[0]);
	//⾃制qsort的调⽤
	//bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_socre);
	//bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
	print_stu2(arr, sz);
}

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

那么接下来我们从不同角度思考问题:

  • 实现排序的作者:他并不知道用户想排序什么类型的数据
  • 使用者:
    1. 知道待排序的数据类型
    2. 知道待排序的数据的比较方法(>或者strcmp)

那么接下来再重新看这块代码

//void qsort(void* base,      不知道排序什么类型 , 就用void*
// size_t num,                元素个数
// size_t width,              void*不知道类型,就不知道⼤⼩,⽆法知道⼀次 操作⼏个字节,所以我们要给出
// int(* compare)(const void* e1, const void* e2)把不同的操作抽象成函数
// );

8. 指针和数组笔试题

数组名:

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

练习1:

#include<stdio.h>
int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));//数组名a单独放在sizeof内部,计算的整个数组的⼤⼩,单位是字节,4*4 = 16
	printf("%d\n", sizeof(a + 0));//a表示的⾸元素的地址,a+0还是数组⾸元素的地址,是地址⼤⼩4/8
	printf("%d\n", sizeof(*a));//a表示的⾸元素的地址,*a就是对⾸元素的地址的解引⽤,就是⾸元素,⼤⼩是4个字节
	printf("%d\n", sizeof(a + 1));//a表示的⾸元素的地址,a+1是第⼆个元素的地址,是地址,⼤⼩就4/8个字节
	printf("%d\n", sizeof(a[1]));//a[1]是数组的第⼆个元素,⼤⼩是4个字节
	printf("%d\n", sizeof(&a)); //&a 表示是数组的地址,数组的地址也是地址,地址⼤⼩就是4/8字节
	printf("%d\n", sizeof(*&a));//可以理解为*和&抵消效果,*&a相当于a,sizeof(a)是16
	//&a -> int(*)[4] 数组的地址放在数组指针中
	//&a是数组的地址,它的类型是int(*)[4]数组指针,如果解引⽤,访问的就是4个int的数组,⼤⼩是16个字节
	printf("%d\n", sizeof(&a + 1));//&a是数组的地址,&a+1 跳过整个数组后的地址,是地址就是4/8
	printf("%d\n", sizeof(&a[0]));//&a[0]取出数组第⼀个元素的地址,是地址就是4/8
	printf("%d\n", sizeof(&a[0] + 1));//&a[0]+1就是第⼆个元素的地址,是地址⼤⼩就是4/8个字节
	//&a[0] - 地址的类型:int*
	return 0;
}

sizeof只关注占⽤空间的⼤⼩,单位是字节 ->南北通吃
sizeof不关注类型
sizeof是操作符,不是函数

strlen关注的字符串中\0的位置,计算的是\0之前出现了多少个字符
strlen指针对字符串
strlen是库函数

练习2:

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

int main()
{
	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };

	printf("%d\n", sizeof(arr));//arr作为数组名单独放在sizeof内部,计算的整个数组的⼤⼩,单位是字节,6
	printf("%d\n", sizeof(arr + 0));//arr就是⾸元素的地址,arr+0还是⾸元素的地址,地址⼤⼩就是4/8
	printf("%d\n", sizeof(*arr));//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取出的是数组的地址,&arr+1,跳过了整个数组,&arr+1还是地址,地址就是4/8个字节
	printf("%d\n", sizeof(&arr[0] + 1));//&arr[0]是第⼀个元素的地址,&arr[0]+1就是第⼆个元素的地址,地址就是4/8个字节
	printf("%d\n", strlen(arr));//arr是⾸元素的地址,但是arr数组中没有\0,计算的时候就不知道什么时候停⽌,结果是:随机值
	printf("%d\n", strlen(arr + 0));//arr是⾸元素的地址,arr+0还是⾸元素的地址,结果是:随机值
	printf("%d\n", strlen(*arr)); //err,strlen需要的是⼀个地址,从这个地址开始向后找字符,直到\0,统计字符的个数。
	//但是*arr是数组的⾸元素,也就是'a',这是传给strlen的就是'a'的ascii码值97,strlen函数会把97作为起始地址,统计字符串,会形成内存访问冲突
	printf("%d\n", strlen(arr[1]));//err 和上⼀个⼀样,内存访问冲突
	printf("%d\n", strlen(&arr));//&arr是arr数组的地址,虽然类型和strlen的参数类型有所差异,但是传参过去后,还是从第⼀个字符的位置向后数字符,结果还是随机值。

	printf("%d\n", strlen(&arr + 1));//随机值 数组地址+1->跳过整个数组
	printf("%d\n", strlen(&arr[0] + 1));//随机值 数组元素+1->跳过这个元素


	return 0;
}

练习3:

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));//arr是⾸元素的地址,向后寻找直到遇⻅\0,结果为6
	printf("%d\n", strlen(arr + 0));//arr就是⾸元素的地址,arr+0还是⾸元素的地址,结果同上
	printf("%d\n", strlen(*arr));//err,strlen需要的是⼀个地址,从这个地址开始向后找字符,直到\0,统计字符的个数。但是* arr是数组的⾸元素,也就是'a', 这是传给strlen的就是'a'的ascii码值97,strlen函数会把97作为起始地址,统计字符串,会形成内存访问冲突
	printf("%d\n", strlen(arr[1]));//err 和上⼀个⼀样,内存访问冲突
	printf("%d\n", strlen(&arr));//&arr是arr数组的地址,从第⼀个字符的位置向后数字符,结果是6
	printf("%d\n", strlen(&arr + 1));//数组地址+1->跳过整个数组 随机值
	printf("%d\n", strlen(&arr[0] + 1));//数组元素地址+1->跳过这个元素->跳到了b,向后数在\0之前有5个元素

	printf("%d\n", sizeof(arr));//数组名a单独放在sizeof内部,计算的整个数组的⼤⼩,单位是字节,有7个元素,1*7=7
	printf("%d\n", sizeof(arr + 0));//⽆单独的sizeof,⽆&->⾸元素地址,+0还是⾸元素地址,4/8个字节
	printf("%d\n", sizeof(*arr));//arr就是⾸元素的地址,*arr就是⾸元素,是⼀个字符,⼤⼩是⼀个字节,1
	//*arr->*(arr + 0)->arr[0]
    printf("%d\n", sizeof(arr[1]));//arr[1]就是数组的第⼆个元素,是⼀个字符,⼤⼩是1个字节
	printf("%d\n", sizeof(&arr));//&arr取出的是数组的地址,数组的地址也是地址,地址就是4/8个字节
	printf("%d\n", sizeof(&arr + 1));//&arr取出的是数组的地址,&arr+1,跳过了整个数组,&arr+1还是地址,地址就是4/8个字节
	printf("%d\n", sizeof(&arr[0] + 1));//&arr[0]是第⼀个元素的地址,&arr[0]+1就是第⼆个元素的地址,地址就是4/8个字节

	return 0;
}

练习4:

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

int main()
{
	char* p = "abcdef";
	printf("%d\n", sizeof(p)); //p是⼀个指针变量,sizeof(p)计算的就是指针变量的⼤⼩,4 / 8个字节
	printf("%d\n", sizeof(p + 1));//p是指针变量,是存放地址的,p+1也是地址,地址⼤⼩就是4/8字节
	printf("%d\n", sizeof(*p));//p是char*的指针,解引⽤访问⼀个字节,即*p访问1个字节
	printf("%d\n", sizeof(p[0]));//p[0]--> *(p+0) -> *p ⾸元素:1个字节
	printf("%d\n", sizeof(&p));//&p也是地址,是地址就是4/8字节,&p是⼆级指针
	printf("%d\n", sizeof(&p + 1)); //&p是地址, + 1后还是地址,是地址就是4 / 8字节
	&p + 1,是p的地址+1,在内存中跳过p变量后的地址

	printf("%d\n", sizeof(&p[0] + 1));//p[0]就是a,&p[0]就是a的地址,&p[0]+1就是b的地址,是地址就是4/8字节
	//p[0]-- > * (p + 0)->*p
	printf("%d\n", strlen(p));//p中存放的是'a'的地址,strlen(p)就是从'a'的位置向后求字符串的⻓度,⻓度是6
	printf("%d\n", strlen(p + 1));//p+1是'b'的地址,从b的位置开始求字符串⻓度是5
	printf("%d\n", strlen(*p));//err,strlen需要的是⼀个地址,从这个地址开始向后找字符,直到\0,统计字符的个数。但是* arr是数组的⾸元素,也就是'a', 这是传给strlen的就是'a'的ascii码值97,strlen函数会把97作为起始地址,统计字符串,会形成内存访问冲突
	printf("%d\n", strlen(p[0]));//err 和上⼀个⼀样,内存访问冲突
	printf("%d\n", strlen(&p));//随机值 向后找不到\0
	printf("%d\n", strlen(&p + 1));//随机值 同上
	printf("%d\n", strlen(&p[0] + 1));//p[0] -> *(p+0) -> *p ->'a' ,&p[0]就是⾸字符的地址,&p[0]+1就是第⼆个字符的地址
	//从第2个字符的位置向后数字符串,⻓度是5

	return 0;
}

练习5:

int main()
{
	//⼆维数组
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//数组名单独放在sizeof内部,计算的是整个数组的⼤⼩ 3*4*4=48
	printf("%d\n", sizeof(a[0][0]));//⼀个元素 -> 4个字节
	printf("%d\n", sizeof(a[0]));//a[0]表示第⼀⾏的数组名,a[0]作为数组名单独放在sizeof内部,计算的是第⼀⾏的⼤⼩(整个数组的⼤⼩)16
	printf("%d\n", sizeof(a[0] + 1));//a[0]作为第⼀⾏的数组名,没有&,没有单独放在sizeof内部,所以a[0]表示的就是⾸元素的地址,即a[0][0]的地址,a[0] + 1就是第⼀⾏第⼆个元素的地址,是地址就是4 / 8
	printf("%p\n", &a[0][0]);//02D8FB94
	printf("%p\n", a[0] + 1);//02D8FB98
	printf("%d\n", sizeof(*(a[0] + 1)));//a[0] + 1是第⼀⾏第⼆个元素的地址,对他进⾏解引⽤,就访问到了该元素,⼤⼩是4个字节
	printf("%d\n", sizeof(a + 1));//a是⼆维数组数组名,没有&,没有单独放在sizeof内部,a表示数组⾸元素(第⼀⾏的地址),a+1跳到了第⼆⾏的地址,是类型为int(*)[4]的数组指针,是地址就是4 / 8个字节。
	printf("%p\n", &a[0][0]);//008FF764
	printf("%p\n", &a + 1);//008FF794 跳过整个⼆维数组 还是地址4/8
	printf("%p\n", &a[0][0]);//0094FA4C 第⼀⾏地址
	printf("%p\n", a + 1);//0094FA5C 第⼆⾏地址
	printf("%d\n", sizeof(*(a + 1)));//a+1是第⼆⾏的地址,*(a+1)就是第⼆⾏,相当于第⼆⾏的数组名,*(a + 1)->a[1], sizeof(*(a + 1))计算的是第⼆⾏的⼤⼩,16字节
	printf("%d\n", sizeof(&a[0] + 1));//a[0]是第⼀⾏的地址,&a[0]是第⼀⾏的地址,&a[0]+1是第⼆⾏的地址,是地址就是4/8字节
	printf("%d\n", sizeof(*(&a[0] + 1)));//&a[0] + 1是第⼆⾏,*(&a[0] + 1))是对第⼆⾏解引⽤,相当于拿到了第⼆⾏的数组名,相当于第⼆⾏,也就是a[1], sizeof(a[1])⼤⼩是16字节
	printf("%d\n", sizeof(*a));//a是⼆维数组数组名,没有&,没有单独放在sizeof内部,a表示⾸元素地址,*a就是⼆维数组的⾸元素,也就是第⼀⾏,sizeof(*a)就是16个字节。* a->*(a + 0)->a[0]
	printf("%d\n", sizeof(a[3]));//感觉a[3]越界了,但是没关系,压根不会去计算括号内的东⻄,回去退到括号内的类型,括号⾥的类型是int[4], ⼤⼩是16个字节


	return 0;
}

练习6:

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;
}
//程序的结果是什么?5 6

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vxFQXIZc-1645607593218)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645606593265.png)]

练习7:

struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量⼤⼩是20个字节
int main()
{
	p = (struct Test*)0x100000;
	printf("%p\n", p + 0x1);//00100014
	printf("%p\n", (unsigned long)p + 0x1);//00100001
	printf("%p\n", (unsigned int*)p + 0x1);//00100004
	注意:0x1是1
	printf("%x\n", p + 0x1);//0x1->1;p+0x1->p+1 结构体指针+!跳过整个结构体,20个字节,20转换成16进制是14,0x100000 + 14->0x100014,打印的结果是10014
	printf("%x\n", (unsigned long)p + 0x1);//把p转化成unsigned long形式,p就是⼀个数字,0x100000不再是地址,0x100000 + 1->0x100001,打印的结果是100001
	printf("%x\n", (unsigned int*)p + 0x1);//整形指针+1->跳过四个字节,0x100000 + 4->0x100004, 打印的结果是100004
	//%p 以地址的形式打印,⾼位0不会省略
	//%x 就是打印16进制,⾼位0会省略
	return 0;
}

练习8:坑!

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M3wKkDZr-1645607593219)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645606972551.png)]

练习9:

#include <stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LqmvC8G6-1645607593219)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645607116434.png)]

练习10:

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZwVYTQVX-1645607593220)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645607166760.png)]

练习11:

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D8BMc7Uo-1645607593220)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645607269349.png)]

练习12:

#include <stdio.h>
int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H59uZ94i-1645607593221)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645607311545.png)]

练习13:难度略大

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l32FhFQ4-1645607593222)(E:\博客\C语言专区\C语言进阶版本\指针进阶\指针进阶.assets\1645607391505.png)]


肝了一天才将这篇博客完成,希望大家多多支持,喜欢的话请一件三连熬!
有不懂的私聊博主即可,或者在评论区表达你的问题
在这里插入图片描述

本站所有文章、数据、图片均来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:licqi@yunshuaiweb.com

标签: 指针 c语言
加载中~