C语言多角度帮助你深入理解指针(1. 字符指针2. 数组指针和 指针数组 、数组传参和指针传参3. 函数指针4. 函数指针数组5. 指向函数指针数组的指针6. 回调函数)

目录

1. 字符指针:

2.数组指针和指针数组:

3.函数指针:

4. 函数指针数组:

5.指向函数指针数组的指针:

6.回调函数:


       对于指针,许多C语言初学者会自然性的畏惧,本篇文章会带着初学的读者从初识向上进阶,认真读完本篇文章,相信读者会对指针有着更加深刻的认识,以后也不再会“畏惧指针”。
首先,我们回顾一下指针的一些 基础知识
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的 4/8 个字节( 32 位平台 /64 位平台)。
3. 指针是有类型,指针的类型决定了指针的 +- 整数的步长,指针解引用操作的时候的权限。
有了这些,我们下面开始分版块一一深入介绍不同情境下的指针:

1. 字符指针:

字符指针,顾名思义,指向字符型数据的指针,也是本文介绍的最简单的一类指针,但是有几点易错的注意点,所以借此机会详解一下。下面是字符指针最简单的用法

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'w';
    return 0; }

除此之外,我们常会遇到字符指针指向字符串的情况:

int main()
{
    const char* pstr = "hello world.";
    printf("%s\n", pstr);
    return 0; }

这里是把一个字符串放到pstr指针变量里了吗?

显然不是,但是为什么能打印出来这个字符串呢?

原因是,字符指针存放了字符串首元素的地址,然后printf函数中%s凭借着首元素地址向下找,打印出整个字符串。所以,事实上,字符指针只存放了字符串首元素的地址

下面我们出一道题目,巩固一下字符指针最要注意的地方:

#include<stdio.h>
int main()
{
	char str1[] = "hello world.";
	char str2[] = "hello world.";
	const char* str3 = "hello world.";//3 4常量字符串,不能被改,在只读数据区
	const char* str4 = "hello world.";
	if (str1 == str2)//数组名是首元素地址,
		//1 和 2是两个独立的数组,所以首地址不一样
		printf("str1 == str2\n");
	else
		printf("str1 != str2\n");
	if (str3 == str4)//说明str3和str4指向同一字符串,都是a的地址
		printf("str3 == str4\n");
    else
		printf("str3 != str4\n");
	return 0;
}

2.数组指针和指针数组:

深入了解指针可就避免不了分清指针数组和数组指针并学会应用。

首先明确的是数组指针是指针!而指针数组是存放指针的数组

//指针数组:
int *p1[10];
//数组指针:
int (*p2)[10];

对于数组指针的解释:

p2 先和 * 结合,说明 p 是一个指针变量,然后指着指向的是一个大小为 10 个整型的数组。所以 p2 是一个 指针,指向一个数组,叫数组指针。
// 这里要注意: [] 的优先级要高于 * 号的,所以必须加上()来保证 p 先和 * 结合。
在数组中,我们在很多地方都会使用数组名当做数组首元素的地址,那么数组名和&数组名表示的都是首元素的地址吗?    
#include <stdio.h>
int main()
{
    int arr[10] = {0};
    printf("%p\n", arr);
    printf("%p\n", &arr);
    return 0; }
    
这里看出输出的是一样的,我们下面分别加1看一看结果:
 

 这里我们就发现了,&arr表示的其实arr整个数组的地址,+1后跳过整个数组的长度。

根据上面的代码我们发现,其实 &arr arr ,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是 数组的地址 ,而不是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址 +1 ,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是 40.
(sizeof(arr)表示的也是整个数组,C语言中大概上只有这两个数组名表示整个数组而非首元素地址。)
数组指针的应用:
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。见下
#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
    //但是我们一般很少这样写代码
    return 0; }
#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;
    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);
    //数组名arr,表示首元素的地址
    //但是二维数组的首元素是二维数组的第一行
    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    //可以数组指针来接收
    print_arr2(arr, 3, 5);
    return 0; }

在第二段代码的传参中就用到了数组指针,大家注意区别两者

3.函数指针:

对于不同的函数,其实我们也可以用指针指向对应的函数,并且通过对函数指针解引用来调用函数,下面看一个简单的例子便于理解:

int Add(int x,int y)
{
	return x + y;
}
int main()
{
	int arr[5] = { 0 };
	int(*p)[5] = &arr;//数组指针

	//&函数名 -- 取出的是函数的地址呢?
	printf("%p\n", &Add);//函数是有地址的
	printf("%p\n", Add);//两种写法没有区别,&函数名和函数名都是函数的地址


	int (*pf)(int, int) = &Add;//和数组指针类似
	int ret=(*pf)(2, 3);//Add(2,3)
	int ret = pf(2, 3);//也可以

	printf("%d\n", ret);
	return 0;
}

我们对比数组指针,把数组指针后的[ ]换成了函数所用的( )大致就是函数指针的雏形了,形如

int(*pf)(int x,int y)  这里的*表示pf是指针,( )表示是指向函数的指针。

#include <stdio.h>
void test()
{
 printf("hehe\n");
}
int main()
{
 printf("%p\n", test);
 printf("%p\n", &test);
 return 0; }

通过这段函数和输出我们发现了其实函数也是有地址的,这也证实了函数指针存在的合理性。初步了解函数指针对下面我们要介绍的三类奠定了基础。

4. 函数指针数组:

函数指针数组,通过上面我们的理解,就是存放函数指针的数组,那么它的形式怎么写呢?下面我们给出:int (*parr1[10])();

其实通过思考我们不难得出,在函数指针的基础上套上数组,这样这个数组就是用来存放函数指针的了。下面我们给出实现计算机的例子来进一步解释一下函数指针数组(该计算器比较简易,意在实现函数指针数组的例子):

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

 注意中间有一处表示转移表的地方就用到了函数指针数组,可以指向对应的函数实现。

5.指向函数指针数组的指针:

有了上面我们理解,我们可以试着写出指向函数指针数组的指针的形式,

下面我们剖析一下:

指向函数指针数组的指针是一个 指针

指针指向一个 数组 ,数组的元素都是 函数指针。
我们把上述三个例子都写出进行对比并推出指向函数指针数组的指针的形式
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; }

这里我们进行简单了解,最后我们给出回调函数的实现过程,这是重中之重的例子,也是指针中比较有技术含量实现的经典.

6.回调函数:

定义:

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

我们借用qsort函数进行讲解,并自己实现qsort函数再来进行回调

//冒泡排序只能排整形数据
//void bubble_sort(int arr[],int sz)
//{
//	int i = 0;
//	int j = 0;
//for(i=0;i<sz-1;i++)
//{int flag = 1;//假设数组是排好序
//	for (j = 0;j<sz-i-1;j++)
//	{
//		
//		if (arr[j] > arr[j + 1])
//		{
//			int tmp = arr[j];
//			arr[j] = arr[j + 1];
//			arr[j + 1] = tmp;
//			flag = 0;//
//		}
//	}
//	if(flag==1)
//	{
//		break;
//	}
//}
//}
//qsort库函数   使用快速排序的思想实现的一个排序函数
// 可以排序任意类型的数据
// 
// 
//void qsort(void* base,//你要排序的数据的起始位置
          //size_t num,//待排序的数据元素的个数
         //size_t width, //待排序的数据元素的大小
         //int(* cmp)(const void* e1, const void* e2)//函数指针,是一个比较函数
         //);

//比较两个整形元素
//e1指向一个整数
//e2指向一个整数
int cmp_int(const void* e1, const void* e2)
//{
//	if (*(int*)e1 > *(int*)e2)//强制类型转换
//		return 1;
//	else if (*(int*)e1 == *(int*)e2)
//		return 0;
//	else
//		return -1;
//}
// 
{return *(int*)e1 - *(int*)e2; }//强制类型转换


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++;
	}
}
								
//void*是不能直接解引用的
//void是无具体类型的指针,可以接受任意类型的指针
//无具体类型,所以不能解引用,也不能+-整数
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 flag = 1;//假设数组是排好序
		//一趟冒泡排序的过程
		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);
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}

struct Stu
{
	char name[20];
	int age;
};
int cmp_stu_by_name(const void* e1,const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

void test1()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	//把数组排成升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	//bubble_sort(arr, sz);


	qsort(arr, sz, sizeof(arr[0]), cmp_int);//默认排升序


	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}
void test2()
{//测试使用qsort来排序结构体数据

	struct Stu s[] = { {"zs",15 }, { "ls",30 }, { "ww",25 } };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]),cmp_stu_by_name);
}

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


	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);//默认排升序


	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}
void test4()
{
	//测试使用qsort来排序结构数据
	struct Stu s[] = { {"zhangsan", 15}, {"lisi", 30}, {"wangwu", 25} };
	int sz = sizeof(s) / sizeof(s[0]);
	bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	//bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
int main()
{
	//test1();
	//test2();
	test3();
	test4();
}

这里我们给出了详解代码注释在代码里。这里主要是我们在bubble_sort函数里回调了cmp_XX对应的函数,还请读者仔细理解。

​​​​​​​以上就是进阶指针的内容,以后在数据结构里我们更会多次利用这些知识。希望有帮助到您!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值