指针收尾,指针进阶版。

本文深入探讨C/C++指针,涵盖字符指针、指针数组、数组指针、数组参数、指针参数、函数指针、函数指针数组和回调函数的概念及使用。通过实例代码解析了各种指针类型的定义、赋值和操作,特别强调了数组和指针在函数参数传递中的差异,以及如何利用函数指针实现简单的计算器。最后介绍了回调函数的原理,并展示了使用qsort函数进行排序的例子。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1. 字符指针

2. 指针数组

3. 数组指针

4. 数组参数、指针参数

5. 函数指针

6. 函数指针数组

7. 指向函数指针数组的指针

8. 回调函数 


指针的主题,我们在初级阶段已经接触过了,我们知道了指针的概念:
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的 4/8 个字节( 32 位平台 /64 位平台)。
3. 指针是有类型,指针的类型决定了指针的 +- 整数的步长,指针解引用操作的时候的权限。
4. 指针的运算。
现在让我们来深入理解一下指针

1. 字符指针

字符指针类型 char*

一般的使用

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

再看看下面代码

这里的并不是将整个字符串放到pstr里而是hello zijian.的首元素地址放到pstr里,而const char* pstr==hello zijian.。

上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。 

让我们看下面这段代码

int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char* str3 = "hello bit.";
    const char* str4 = "hello bit.";
    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");

    return 0;
}

结果是

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当 几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化 不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。 

2. 指针数组

指针数组如名字所言是一个数组

int* arr1[10]; //整形指针的数组

char *arr2[4]; //一级字符指针的数组

char **arr3[5];//二级字符指针的数组

具体可以去看之前写的指针初阶

3. 数组指针

3.1 数组指针的定义:

如指针数组我们知道是数组,那么数组指针就是指针

整形指针: int * pa; 能够指向整形数据的指针。

浮点型指针: float * pf; 能够指向浮点型数据的指针。

int *p1[10];

int (*p2)[10];

//p1, p2分别是什么?

解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针

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

3.2 &数组名VS数组名:

int arr[];   arr

数组名我们知道是首元素的地址

但是有两个例外

1.sizeof(arr);这里的arr是整个数组那么sizeof(arr)就是表示整个数组的大小。

2.&arr这里的arr也是表示整个数组取出的是整个数组的地址。

正如我指针初阶写的

 根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。 实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。

3.3 数组指针的使用:

数组指针既然是一个指针那么就是那来存数组地址的。

看下面代码

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	int(*p)[10] = &arr;
	return 0;
}

将arr的地址存放在数组指针变量p中,但是我们一般不这样使用数组指针。

看下面这段代码

#include<stdio.h>

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

print_arr2(int(*arr)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		{
			for (int 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 };
	print_arr1(arr, 3, 5);
	print_arr2(arr, 3, 5);
	return 0;
}

 数组名arr,表示首元素的地址,但是二维数组的首元素是二维数组的第一行,所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址,可以数组指针来接收。

学习完了数组指针和指针数组让我们来看看下面这写代码的意思。

int arr[5];                  //这是这个整形数组

int *parr1[10];              //这是一个指针数组

int (*parr2)[10];         //这是这个数组指针

int (*parr3[10])[5];          //

4. 数组参数、指针参数

在我们写函数的时候,总要把数组或者指针传给函数,那么到底怎么传参呢?

这也是困扰了我很久的问题,接下来就让我们看看数组与指针的传参。

4.1 一维数组传参

首先既然是数组的传参,那么我们可以将参数写成数组的形式

#include<stdio.h>
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	print_arr(arr, sz);

	return 0;
}

又因为传的是数组名,既是数组首元素的地址,既然是地址那么我们就可以用指针来接收

#include<stdio.h>
void print_arr(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	print_arr(arr, sz);

	return 0;
}

这就是一维数组传参的两种方式。

4.2 二维数组传参

与一维数组的想法一样既然是数组我们就用数组的形式接收

#include<stdio.h>
void print_arr(int arr[][5], int c, int r)
{
	int i = 0;
	for (i = 0; i < c; i++)
	{
		int j = 0;
		for (j = 0; j < r; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	print_arr(arr, 3, 5);

	return 0;
}

再来就是传的是arr数组名即为二维数组首元素的地址,那么就是第一行的地址第一行相当与一个一维数组有五个元素所以我们要用数组指针来接收

#include<stdio.h>
viod print_arr(int(*p)[5], int c, int r)
{
	int i = 0;
	for (i = 0; i < c; i++)
	{
		int j = 0;
		for (j = 0; j < r; j++)
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
	return 0;
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	print_arr(arr, 3, 5);

	return 0;
}

这里有一个地方我思考了很久就是 *(*(p+i)+j) 说明一下p是二维数组首元素的地址也就是第一个一维数组的地址+i就是找到第几行的地址接着 *(p+i)找到第几行,接下来 *(p+i)+j找到第几行第几个元素的地址然后 (*(*p+i)+j)找到第几行第几个元素,打印出来。

了解了他的本质之后我们可以这样写p[i][j],*(p[i]+j)

这就二维数组传参。

4.3 一级指针传参

一级指针传参我们可以用一级指针接收

#include<stdio.h>
void print_arr(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;

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

	print_arr(p, sz);
	return 0;
}

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

既然参数为一级指针那么可以用来接收地址所以我们可以传地址

int a = 10;
int *p = &a;
test(&a);
test(p);

我们也可以传一维数组名

int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
test(arr);

但是二维数组名不能传因为我们上面说了二维数组名是二维数组第一行的地址相当于一个一维数组,所以我们要用数组指针接收。

4.4 二级指针传参

二级指针传参我们可以用二级指针接收

#include<stdio.h>
void test(char** ppc)
{

}
int main()
{
	char a = 'w';
	char* pa = &a;
	char** ppa = &pa;

	test(ppa);
	return 0;
}

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

我们可以传

char a = 'w';
char* pa = &a;
test(&pa);
char a = 'w';
char* pa = &a;
char** ppa = &pa;
test(ppa);

我们可以还可以传指针数组

char* p[4] = { 0 };
test(p)

p的类型为char*所以要二级指针接收。

5. 函数指针

让我们来看看这样的代码

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

我们想打印出test函数的地址,并且用了test和&test这两种写法,让我们看看结果

 输出的是两个地址,这两个地址是 test 函数的地址。

那我们如何将函数的地址存起来呢?

首先既然是地址那就需要用指针存储

所以 void (*p)()

p可以存放。p先和*结合,说明p是指针,指针指向的是一个函数,指向的函数无参数,返回类型为viod。

6. 函数指针数组

数组是一个存放相同类型数据的存储空间。

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组。

我们要如何定义函数指针数组呢?

int (*parr1[5]) ();

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

函数指针数组的用途

下面举一个简单的例子:简单的计算器

#include<stdio.h>
void add(int x, int y)
{
	return x + y;
}
void sub(int x, int y)
{
	return x - y;
}
void mul(int x, int y)
{
	return x * y;
}
void div(int x, int y)
{
	return x / y;
}

int main()
{
	int x, y;
	int ret;
	int input = 1;
	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("**0.exi       ******\n");
			printf("请选择");
			scanf("%d", &input);
			if (input == 0)
			{
				printf("退出\n");
				break;
			}
			else 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;
}

这就是函数指针数组的用法可以将多个函数放在一起使用。

7. 指向函数指针数组的指针

int (*parr[4])(int,int)        //函数指针数组

int (*(*p)[4])(int,int) = &parr        //指向函数指针数组的指针

(p指向的数组是4个元素,每个元素是函数指针)

8. 回调函数 

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

我们接下来介绍一个库函数qsort函数

在那之前我们先来看看之前学习的冒泡排序

#include<stdio.h>
bubble_sort(int arr[],int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}
print_bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}
int main()
{
	int arr[10] = { 1,2,4,5,7,8,3,10,9,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	print_bubble_sort(arr, sz);
	return 0;
}

我们会发现冒泡排序有局限性,他只能排序整数,那我们要是想排序结构体呢?字符? 

再让我们看看qsort函数的内容

 qsort的使用

#include<stdio.h>
#include<stdlib.h>
cmp_int(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);
}
print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
}
test()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);

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

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

这个是排序和上面冒泡排序一样的整形,当然qsort也可以排序结构体

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct stu
{
	char name[20];
	int age;
	double score;
};
cmp_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
test()
{
	struct stu arr[3] = { {"zhangshang",20,50.0},{"lisi",10,60.0},{"wangwu",30,70.0} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_name);
}
int main()
{
	test();
	return 0;
}

我们来调试看看

 根据名字开头的名字排序的话应该是"lisi","wangwu","zhangshang",所以qsort就顺利排序了。

好了,这就是目前我理解的指针的全部了,虽然说是进阶但是我肯定还是有很多认识不足,但是我可以肯定的是,这些都是干货!!

如果对你有帮助麻烦三连一下。

谢谢!!!!

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子健变于晏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值