不会指针?还不进来看看——进阶指针详解

专栏:C语言
每日一句:人贵有自知之明,知道什么可为和不可为。若不可为,怎样做才能可为,那何时可为。


前言

如若不知道指针是什么,点击传送门
本文是指针的主题,在前面我们已经稍微了解了指针,
1.指针就是个变量,用来存放地址,
2.指针大小在32位中是4字节,在64位中是8字节
3.指针是有类型的,指针的类型决定了指针的±整数的步长。
4.指针的运算

在这里插入图片描述

一、字符指针

字符指针是什么?
C语言的基本类型中有char类型,那么在指针的类型中也有一种指针类型为字符的指针char*,那么字符指针是怎么使用的呢?

int main()
{
    
    
	char s = 'a';
	char* ps = &s;
	*ps = 'b';
	return 0;
}

这是一种用法,还有下面一种用法。

#include <stdio.h>
int main()
{
    
    
	const char* s = "Hao Hao Xue Xi";
	printf("%s", s);
	return 0;
}

在这里插入图片描述

const char* s = "Hao Hao Xue Xi";为什么通过s可以把这个字符串打印出来?是因为把这个字符串存到s指针变量里面了吗?
其实不是的,这里只是把这个字符串的首元素地址,存在了ps里,由第一个元素的地址可以找到后面元素的地址,直到遇到\0

#include <stdio.h>

int main()
{
    
    
	char a1[] = "Hello World";
	char a2[] = "Hello World";

	const char* ps1 = "Hello World";
	const char* ps2 = "Hello World";

	if (a1 == a2)
	{
    
    
		puts("a1 == a2");
	}
	else
	{
    
    
		puts("a1 != a2");
	}

	if (ps1 == ps2)
	{
    
    
		puts("ps1 == ps2");
	}
	else
	{
    
    
		puts("ps1 != ps2");
	}

	return 0;
}

那么,这个代码会输出什么结果呢?
在这里插入图片描述
结果为什么不是a1 == a2 和 ps1 == ps2呢?
我们分别把a1,a2,ps1,ps2的地址打印出来观察一下
在这里插入图片描述
看代码结果,ps1和ps2指向的是同一个地址,因为他们指向的是同一个字符串常量,在C语言中会把字符串常量存储到单独的一个内存区域,当n个指针指向同一个字符串常量的时候,他们实际指向的是同一块内存区域,所以,打印出来的地址是相同的。但是用不同的数组初始化为相同的字符串常量,结果就不是这样的,在创建数组的时候,计算机会自动在内存的某一区域开辟一段属于数组的空间,把数组的内容存在这个空间里,所以a1和a2的地址不相同。

二、指针数组

指针数组是指针还是数组?
答案:是数组,但是,指针数组是存放指针的数组

1.指针数组的介绍

我们经常用整型数组,字符数组等

int arr[]
char ch[]

整型数组,数组里面存放的都是整型,
字符数组,数组里面存放的都是字符,
同理:
指针数组,数组里面存放的应该是指针,指针是什么?指针就是地址,也可以这样说,数组里面存放的是地址。

int *arr[]
char *ch[]

在这里插入图片描述

2.指针数组的使用

那么,指针数组是怎么使用的呢?
看代码:

#include <stdio.h>

int main()
{
    
    
	const char ch1[4] = "hao";
	const char ch2[4] = "hao";
	const char ch3[4] = "xue";
	const char ch4[4] = "xi";
	char* pch[5] = {
    
     ch1, ch2, ch3, ch4 };
	for (int i = 0; i < 4; i++)
	{
    
    
		printf("%s\n", pch[i]);
	}
	return 0;
}

在这里插入图片描述
我们把ch[1,4]的地址都存到了pch里面,在通过ch[1,4]的地址,打印出字符串。
在这里插入图片描述
这就是指针数组的用法。

三、数组指针

上面刚刚介绍了指针数组,现在让我们来了解一下什么是数组指针。
数组指针是指针还是数组呢?
答案是:指针。

整型指针:int* ps:能够指向整型数据的指针
字符指针:char* ps:能够指向字符数据的指针
同理
那数组指针就应该是能够指向数组的指针

1.数组指针的介绍

int *ps[10]:是指针数组
int (*ps)[10]:是数组指针

ps先与结合,说明ps是一个指针变量,可以用来存放地址,在与[]结合,说明数组,所以,ps是一个指针,指向一个数组,叫数组指针
这里要注意:[]的优先级高于
的优先级,所以要加上()

数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

2.&数组名和数组名

&数组名是什么?
数组名又是什么?

#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("数组名:%p\n", arr);
	printf("&数组名:%p\n", &arr);

	printf("数组名+1:%p\n", arr + 1);
	printf("&数组名+1:%p\n", &arr + 1);

	return 0;
}

数组名+1和&数组名+1的结果会是一样的吗?
在这里插入图片描述
很显然,不一样,数组名+1-数组名=4 &数组名+1-&数组名=40,这个40哪里来的呢?是sizeof(arr)为什么&数组名+1跨过了整个数组呢?实际上,&arr代表的是数组的地址,而不是数组首元素的地址,数组的地址+1才能跨过整个数组,而数组某一元素+1跨过的是一个元素的大小,这就是为什么&数组名+1-&数组名=40的原因

3.数组指针的使用

前面简单的说了数组指针,数组指针是怎么使用的呢?

#include <stdio.h>

int main()
{
    
    
	int arr[3] = {
    
     6 ,6 ,6 };
	int(*parr)[3] = &arr;//把arr的地址取出来赋给parr,
	for (int i = 0; i < 3; i++)
	{
    
    
		printf("%d ", (*parr)[i]);
	}
	return 0;
}

在这里插入图片描述
这样的代码少写,这样写没有任何的实际意义。
看下面的代码:

#include <stdio.h>
//这里的int(*parr)[3]等价于int arr[3][3]
void my_print(int(*parr)[3], int col, int row)
{
    
    
	for (int i = 0; i < row; i++)
	{
    
    
		for (int j = 0; j < col; j++)
		{
    
    
			printf("%d ", parr[i][j]);
		}
		puts("");
	}
	puts("");
	for (int i = 0; i < row; i++)
	{
    
    
		for (int j = 0; j < col; j++)
		{
    
    
			printf("%d ", *(*(parr + i) + j));
		}
		puts("");
	}
}

int main()
{
    
    
	int arr[3][3] = {
    
     1,2,3,4,5,6,7,8,9 };
	//数组名表示首元素的地址
	//在二维数组中,数组名表示第一行的地址
	//所以这里传arr,传的其实是二维数组第一行的地址,
	//可以拿指针来接收
	my_print(arr, 3, 3);

	return 0;
}

这个写法 *(*(parr + i) + j)表示什么意思呢?在这里,parr表示的是二维数组第一行的地址,第一行的地址+1表示二维数组第二行的地址,+n表示第n-1行的地址,对(parr + i)进行解引用操作*(parr + i),找到的是某一行的第一个元素的地址,在+j,就能找到某一个元素的地址了 (*(parr + i) + j)在进行解引用操作*(*(parr + i) + j),就可以精确到某一元素了。
在这里插入图片描述

四、函数指针

函数指针是什么呢?为什么会有函数指针这一概念呢?
首先看一段代码:

#include <stdio.h>

int add(int a, int b)
{
    
    
	return a + b;
}

int main()
{
    
    
	printf("add :%p\n", add);
	printf("&add:%p\n", &add);

	return 0;
}

在这里插入图片描述
可以看出,add和&add是一样的,都是函数add的地址,既然是地址,那就可以用指针进行保存,怎么保存呢?这就引出了函数指针的概念。

#include <stdio.h>

int add(int a, int b)
{
    
    
	return a + b;
}

int main()
{
    
    
	int (*pf)(int, int) = &add;

	printf("(*pf)(1,2)=%d\n", (*pf)(1, 2));
	printf("add(1,2)=%d\n", add(1, 2));

	return 0;
}

在这里插入图片描述
这是一个很新颖的用法,把函数add的地址传给pf,利用指针把函数的地址存起来,通过这个指针去调用这个函数。

五、函数指针数组

数组是用来存放相同类型数据的存储空间,前面我们介绍过字符数组,整型数组,指针数组,那么也有函数指针数组这一概念。
函数指针数组就是,把函数的地址存到一个数组中,那个数组就叫函数指针数组,函数指针数组该如何定义呢?

int (*pf[10])(int,int....);

pf先和[]结合,说明pf是数组,数组的内容就是int (*)()类型的函数指针
看下代码就知道函数指针的用法了:
这是一个简单的计算器,不具有高精度,想了解高精度的点击传送门

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>
void menu()
{
    
    
	printf("1.Add  2.Sub 3.Mul 4.Div");
}
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 x = 0;
	int y = 0;
	int input = 0;
	int(*parr[5])(int, int) = {
    
     0, Add,Sub,Mul, Div };//这个就是函数指针数组,数组里面存放的是函数,
	//通过下标就能访问函数
	do
	{
    
    
		menu();

			printf("选择");
			scanf("%d", &input);
		if (input <= 5 && input >= 1)
		{
    
    
			
			printf("请输入数字");
			scanf("%d %d", &x, &y);
			int ret = parr[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
    
    
			printf("退出");
		}
		else
		{
    
    
			printf("出错");
		}
	} while (input);
	 
	return 0;
}

其实还有一个指向函数指针数组的指针的概念,在这里就不多做介绍了。

六、回调函数

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

给大家看一个代码

//这里面用到了回调函数
#include <iostream>
#include <time.h>
#include <stdlib.h>
using namespace std;
#define N 10
int score = 0;
int add(int x, int y)
{
    
    
	cout << x << "+" << y << " =";
	return x + y;
}
int sub(int x, int y)
{
    
    
	cout << x << "-" << y << " =";
	return x - y;
}
int mul(int x, int y)
{
    
    
	cout << x << "*" << y << " =";
	return x * y;
} 
int div_(int x, int y)
{
    
    
	cout << x << "/" << y << " =";
	return x / y;
}
void shuru(int (*pf)(int,int))//这里用函数指针来接收函数的地址,然后通过pf就能调动函数
{
    
    //shuru函数就是一个回调函数,,如果不用这个函数,那么代码会增加很多
	int x = rand() % 100 + 1;
	int y = rand() % 100 + 1;
	int ret = (*pf)(x,y);//在这里就调用了函数
	int num = 0;
	cin >> num;
	cout << "         ";
	if (ret == num) 
		cout << "对" << endl, score += 10;
	else 
		cout << "错" << endl;
}
int main()
{
    
    
	int x, y, num;
	int count = 0;
	srand((unsigned int)time(NULL));
	for (int i = 1;; i++)
	{
    
    
		int suiji = rand();
		if (suiji % 10 == 0)
		{
    
    
			count++;
			shuru(&add);//传函数的地址
		}
		else if (suiji % 10 == 1)
		{
    
    	
			count++;
			shuru(&sub);//传函数的地址
		}
		else if (suiji % 10 == 2)
		{
    
    	
			count++;
			shuru(&mul);//传函数的地址
		}
		else if (suiji % 10 == 3)
		{
    
    	
			count++;
			shuru(&div_);传函数的地址
		}
		
		if (count == N)
		{
    
    	
			break;
		}
	}
	cout << "总分" << score << endl;
	return 0;
}

在这里,给大家展示一个C语言排序函数:qsort
在这里插入图片描述

qsort有四个参数,1.待排序数组的首元素的地址。2.待排序数组的元素个数。3.带排序元素的每个元素的大小,单位是字节。4.函数指针,比较两个元素的所用函数的地址,函数的参数是:带比较的两个元素的地址。qsort的排序机制是快速排序,有不懂的小伙伴可以点击传送门学习一下快速排序。

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

int cmp(const void* a ,const void* b)
{
    
    
	return *(int*)b - *(int*)a;
}

int main()
{
    
    
	int arr[9] = {
    
     7,4,1,74,1,747,4741,741,7474741 };

	int sz = sizeof(arr) / sizeof(arr[0]);

	qsort(arr, sz, sizeof(arr[0]), cmp);

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

在这里插入图片描述

这里是降序排序,也可以升序排序,只需要把cmp函数里面的返回值换成*(int*)a - *(int*)b;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct STU
{
    
    
	char name[20];
	int length;
}S;

int cmp_name(const void* a, const void* b)
{
    
    
	return strcmp(((S*)a)->name, ((S*)b)->name);
}

int main()
{
    
    
	S student[3] = {
    
     {
    
    "zhangsan",20}, {
    
    "lisi", 50}, {
    
    "wangwu", 33} };

	int sz = sizeof(student) / sizeof(student[0]);

	qsort(student, sz, sizeof(student[0]), cmp_name);

	for (int i = 0; i < 3; i++)
	{
    
    
		printf("%s %d ", student[i].name, student[i].length);
	}

	return 0;
}

这是给结构体排序,以名字排的序
在这里插入图片描述
再以身高排个序,把排序这个回调函数改变一就行

int cmp_length(const void* a, const void* b)
{
    
    
	return((S*)a)->length - ((S*)b)->length;
}

在这里插入图片描述

注:给字符排序不能直接用-,需要用strcmp,专门用于比较字符串的函数

注:本文不解释qsort的底层实现原理,若感兴趣的话,可以自己模拟实现一下,在这里给点提示:1.要想实现qsort的底层,就得考虑什么类型能够存任意类型的地址,(void*)可以,void可以存任意类型的地址,可以把void比作万能钥匙,什么门都能打开。2.元素的个数,决定了要给多少个元素排序。3.每个元素的大小,每个类型有每个类型的大小,int4个字节,double8字节,那么,怎样才能去访问任意类型呢?可以用char类型,char是一个字节,char*+1访问的是第二个字节,这样就能访问任意类型了,只要知道带排序元素的大小即可。4.写一个比较的函数。

总结

以上就是对指针进阶的讲解,希望对大家有所帮助。

猜你喜欢

转载自blog.csdn.net/weixin_73888239/article/details/128552590