C语言指针详解

一、指针的基本概念

要想理解指针,首先我们得清楚内存和地址都是一些什么东西。

(1)内存和地址

在讲内存和地址之前,我们先联想生活中的一个案例,在我们住宾馆的时候,我们得先知道自己要住的房间的门牌号是什么,我们只有知道我们住的门牌号我们才可以找到我们住的房间。其实,内存就可以看作是一个旅馆,我们给这个旅馆每一个房间都给上了一个门牌号,这个门牌号其实就是地址。而指针就是存储地址的一个变量

(2)指针的声明

指针的声明格式如下:

int* p;

int* 说明这是一个指针变量,而这个指针指向的是一个 int 类型的地址。
p 是这个指针变量的变量名。

二、指针的操作符

(1)取地址操作(&)

在C语言中,我们可以通过这个操作符取出变量的地址,示例如下:

int i = 0;
int* p = &i;

经过上面的操作,我们就已经把变量 i 的地址存放在指针变量 p 里面了。

(2)解引用操作符(*)

在C语言中,我们可以通过这个操作符来访问指针所指向的变量中的值,具体示例如下:

int i = 0;
int *p = &i;
*p = 1;

经过上面的操作,我们就通过指针变量 p 来访问变量 i 的地址,并将变量 i 中的值改为了 1。

(3)下标访问操作符([])

在C语言中,我们使用下标访问操作符使用最多的场景是在数组中,在数组中我们使用下标访问操作符来访问数组中的元素。具体示例如下:

#include <stdio.h>

int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
	printf("%d", arr[5]);
	return 0;
}

运行结果如下:
在这里插入图片描述
从上面的代码我们可以知道:我们可以使用下标访问操作符来访问数组中的元素。
但是下标访问操作符其实可以改写为指针的形式,我们可以看以下的代码:

#include <stdio.h>

int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
	printf("%d", *(arr + 5));
	return 0;
}

运行结果如下:
在这里插入图片描述
通过比较上面两个代码我们发现:不管我们使用arr[5]还是使用*(arr + 5)都可以达到访问数组中第五个元素的效果。

三、指针运算

(1)指针±整数

先看一段代码,观察地址的变化

#include <stdio.h>

int main()
{
	int i = 0;
	int* ptr = &i;
	char arr[6] = "abcdef";
	char* p = &arr[0];
	printf("%p\n", p);
	printf("%p\n", p + 1);
	printf("%p\n", ptr);
	printf("%p\n", ptr + 1);
	return 0;
}

代码运行结果如下:
在这里插入图片描述
我们可以看出,char* 类型的指针 +1 后一次跳过一个字节。int* 的指针 +1 后一次跳过4个字节。我们可以得出当指针±整数后,指针所指向的地址跨越的幅度的大小和指针变量的类型是有关系的。

(2)指针±指针

先看一段代码:

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	int* parr = &arr[0];
	int* ptrarr = &arr[10];
	printf("%d", ptrarr - parr);
	return 0;
}

代码运行结果如下:

从上面的代码我们可以看出,指针-指针最后返回的是两个指针变量之间相差的元素的个数。(也可以理解为两个指针变量之间相差了多少个地址)

1.strlen函数的模拟实现

我们都知道,strlen函数是求一个字符串的大小的,那么strlen函数到底是怎么实现求两个字符串的大小的呢?通过上面我们所了解到的指针-指针得到的是两个指针变量之间相差的元素的个数。所以我们现在就可以模拟实现一个strlen函数了。
具体实现代码如下:

#include <stdio.h>

int my_strlen(char* str)
{
	char* p = str;
	while(*p != '\0')
	{
		p++;
	}
	return p - str;
}

int main()
{
	int arr = "abcdef";
	int ret = my_strlen(arr);
	printf("%d\n", ret);
	return 0;
}

上述代码分析:首先,我们都知道传入strlen函数的参数是一个字符串,也就是说我们可以用一个指针变量去接收这个字符串的首元素的地址,然后因为数组在内存中是连续存放的,我们就可以顺藤摸瓜的通过指针把这个字符串中所有的元素拿出来,而字符串的结束标志是 ‘\0’ ,所以当我们遇到 ‘\0’ 的时候就可以让我们的指针变量停下来,不再读取数组中的元素。而这个时候我们的指针变量已经指向了 ‘\0’ 这一个元素,所以我们只需要让我们的指针变量减去我们传入的地址就可以计算这个字符串的大小了。
代码运行结果如下:
在这里插入图片描述

(3)指针的关系运算

指针的关系运算其实就是指针所指向的内存空间的地址的大小比较。
具体示例如下:

#include <stdio.h>

int main()
{
	char arr[10] = { 0 };
	char* p = &arr[0];
	int sz = sizeof(arr) / sizeof(arr[0]);
	while (p < arr + sz)
	{
		printf("%p\n", p);
		p++;
	}
	return 0;
}

代码运行结果如下:
在这里插入图片描述

四、二级指针

(1)二级指针是什么

要学会使用二级指针,首先我们得了解二级指针是什么东西。
二级指针其实就是一个存放指针变量的指针,它里面存放的一个一级指针变量。一级指针变量是什么呢?一级指针变量就是一个存放了一个变量的地址的指针。具体代码示例如下:

int a = 10;//这是一个变量
int* p = &a;//这是一个一级指针变量,里面存放的是变量a的地址
int** ptr = &p;//这是一个二级指针,里面存放的是指针变量p的地址

(2)二级指针的用法

五、指针与数组

(1)数组名的理解

在我们上面写下标引用操作符时我们看到过这样的代码:

#include <stdio.h>

int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
	printf("%d", *(arr + 5));
	return 0;
}

我们知道 (*) 这个操作符是解引用的意思,但是我们会发现,我们这里并没有取地址,但是程序却依然可以运行并且没有报错,这是为什么呢?原因是:==在我们编写C语言代码中,数组名其实代表的是数组首元素的地址。==正因为数组名代表的是数组首元素的地址而且数组元素在内存当中是连续存放的,我们才可以通过指针访问我们的一维数组。
以下是指针访问一维数组的代码示例:

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int* ptr = &arr[0];
	printf("%p\n", p);
	printf("%p\n", p + 1);
	printf("%p\n", ptr);
	printf("%p\n", p + 1);
	return 0;
}

代码运行结果如下:
在这里插入图片描述
在上述代码中,我们可以发现不管是指针 p 所指向的地址还是指针 ptr 所指向的地址都是同一个地址,而且两个指针变量所跳过的内存空间的大小都是四个字节。所以我们可以得知在C语言中,数组名就是数组首元素的地址。
虽然数组名在C语言中代表的是首元素的地址,但是其实也是有两个特例的。下面我们就来介绍在C语言中数组不是首元素的地址的两个特例:

1.sizeof(数组名)

在sizeof(数组名)中,数组代表的是我们整个数组的地址。我们可以看以下的代码示例:

#include <stdio.h>

int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
	int ret = sizeof(arr);
	printf("%d\n", ret);
	return 0;
}

以下是代码运行结果:

如果是按照我们对数组名代表的是数组首元素的地址的理解,这里的代码运行结果应该是4/8,因为地址在x86和x64的环境下应该是4个或者8个字节,而 sizeof 求的是首元素地址的大小,所以按照我们之前对数组名的理解这里的运行结果应该是 4/8 。但是实际的运行结果却是40,数组名在 sizeof 内部存放的并不是首元素的地址,而是整个数组的地址。
易错点:只有当数组名单独出现在 sizeof 内部才代表的是整个数组的地址,其余情况都代表数组首元素的地址。
具体示例如下:

#include <stdio.h>

int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
	int ret = sizeof(arr + 1);
	printf("%d\n", ret);
	return 0;
}

代码运行结果如下:
在这里插入图片描述

2.&(数组名)

在&(数组名)中,数组名代表的也是整个数组的地址。
具体代码示例如下:

#include <stdio.h>

int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
	printf("&arr[0]     = %p\n", &arr[0]);
	printf("&arr[0] + 1 = %p\n", &arr[0] + 1);
	printf("arr         = %p\n", arr);
	printf("arr + 1     = %p\n", arr + 1);
	printf("&arr        = %p\n", &arr);
	printf("&arr + 1    = %p\n", &arr + 1);
	return 0;
}

代码的运行结果如下:
在这里插入图片描述
我们会发现当 &arr + 1 的时候的地址和上面的 &arr[0] + 1 和 arr + 1 的地址完全不一样,而且我们会发现 &arr + 1的地址正好比 &arr 的地址多了40,这是为什么呢?原因就是当我们 &arr[0] 的时候取出的是首元素的地址,+1后自然就变成了数组中下一个元素的地址,arr 代表的是首元素的地址,arr + 1 之后代表的就是数组第二个元素的地址,但是 &arr 取出的是整个数组的地址,&arr + 1后自然就会跳过整个数组的地址,从而指向数组后面的地址。

(2)一维数组传参的本质

一维数组传参的本质其实就是传了一个指针。
下面是我们以前写的一个一维数组传参的代码:

#include <stdio.h>

//写一个函数,求出数组中所有的数字之和
int sum(int arr[], int sz)
{
	int sum = 0;
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		sum += arr[i];
	}
	return sum;
}

int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int ret = sum(arr,sz);
	printf("%d\n", ret);
	return 0;
}

因为我们知道 [] 是一个下标引用操作符,所以 int arr[] 可以被理解为 int* (arr + 0)这不就是数组首元素的地址吗?所以,一维数组传参的本质就是传入了一个指向数组首元素地址的指针。而且,从我们传入的参数是 arr 也可以得知我们传入的其实是数组首元素的地址,可以佐证我们上面得出的结论。
那么上面的代码又可以被改写为下面的样子:

#include <stdio.h>

//写一个函数,求出数组中所有的数字之和
int sum(int* arr, int sz)
{
	int sum = 0;
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		sum += *(arr + i);
	}
	return sum;
}

int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int ret = sum(arr,sz);
	printf("%d\n", ret);
	return 0;
}

代码的运行结果如下:
在这里插入图片描述

(3)指针数组

指针数组是什么

我们不妨先猜一猜指针数组是什么,是数组?还是指针?根据语法我们可以猜测指针数组是数组的可能性更大一点。其实指针数组就是一个数组,但是这个数组里面的元素并不是单纯的像我们之前写的整型变量和浮点型变量之类的东西,这个数组里面的元素都是指针变量。
代码示例如下:

#include <stdio.h>

int main()
{
	int i = 0;
	int arr[] = { 0 };
	int* p = arr;
	int* ptr = &i;
	int* arr1[2] = { p,ptr };
	return 0;
}

数组 arr1 中存放的就是 p 和 ptr 两个指针变量,p 和 ptr 所指向的对象分别是数组 arr 和变量 i 的地址。数组 arr1 就被称为指针数组。

(4)使用指针模拟实现二维数组

先看一段代码:

#include <stdio.h>

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 6,7,8,9,10 };
	int arr3[] = { 11,12,13,14,15 };
	//用数组指针存放数组首元素的地址
	int* parr[] = { arr1, arr2, arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(*(parr + i) + j));
		}
		printf("\n");
	}
	return 0;
}

上述代码运行后的结果是:
在这里插入图片描述
代码分析:
parr是一个指针数组,为了访问 arr1、arr2、arr3 中的元素我们得先进行一个 * (parr + i)操作访问到 parr 中的元素,也就是 arr1,arr2,arr3。我们只有先访问 parr 中的元素我们才可以访问到 arr1、arr2、arr3 中的元素。当我们经过这个操作访问到了 parr 中的元素后,由于 arr1、arr2、arr3 都是地址,我们还得对它们内部的元素进行解引用访问。所以在访问 arr1、arr2、arr3 中的元素时,我们得进行这个操作 * ( * (parr + i) + j) 才可以访问到arr1、arr2、arr3 中的元素。

(5)数组指针

根据上面的指针数组,我们可以推理出:数组指针其本质就是一个指针,这个指针是用来存放数组变量的地址的。
以下是数组指针的代码示例:

#include <stdio.h>

int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
	int(*p)[10] = &arr;
	return 0;
}

int 是 p 指向的数组的元素类型,p 是数组指针变量名,10是 p 指向数组的元素个数。
以下是这代码调试的结果:
在这里插入图片描述
通过调试我们发现 &arr 和 p 中的元素个数是一样的。

(6)二维数组传参的本质

二维数组传参的本质其实就是传入了一个数组指针。
我们过去写二维数组传参是这样子写的:

#include <stdio.h>

//写一个函数用来打印二维数组中的内容

void Print(int arr[3][5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[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} };
	Print(arr, 3, 5);
	return 0;
}

在这里插入图片描述
我们可以将二维数组理解为是一个存放一维数组的数组,当我们采用这种理解方式我们就能够很轻松地理解二维数组传参的本质是什么了。我们可以假设有一个arr1数组,里面存放的是{1,2,3,4,5}。还有一个arr2数组,里面存放的是{2,3,4,5,6}。还有一个arr3数组,里面存放的是{3,4,5,6,7}。我们会发现,当我们采取这个思路的时候,二维数组像极了一个数组指针。那我们不妨大胆推测一下,二维数组传参的本质就是数组指针。那么我们上述代码就可以改写为:
以下是二位数组传参的代码改写:

#include <stdio.h>

//写一个函数用来打印二维数组中的内容

void Print(int(*arr)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[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} };
	Print(arr, 3, 5);
	return 0;
}

代码运行结果如下:
在这里插入图片描述
我们发现我们改写出的代码完美地符合我们的要求。所以二维数组传参的本质就是传入了一个数组指针。

二维数组传参的理解方式

我们可以这样子去理解二维数组传参的本质:二维数组可以理解为是一个存放了一维数组的数组。我们之前学过使用指针来访问一维数组。那么我们也可以使用指针来访问二维数组中的一维数组,但是我们不知道这个一维数组中有多少元素个数,所以我们还得传给数组指针这个数组有多少个元素。

六、指针与函数

(1)函数指针变量

函数指针变量的创建

我们根据这个名字可以知道函数指针变量其实是一个指针,这个指针存放的是一个函数的地址。但是函数是否和其他的变量一样有地址呢?我们可以来做一个测试。
具体示例如下:

#include <stdio.h>

void test()
{
	printf("hello world\n");
}

int main()
{
	printf("test: %p\n", test);
	printf("&test: %p\n", &test);
	return 0;
}

运行结果如下:
在这里插入图片描述
我们可以看到,即使是函数也是有一个地址的,而且函数的函数名就是函数的地址。并不需要专门的取地址运算符来取出函数的地址。
那么有地址就可以将地址存放到指针变量里面去啊。那么我们应该如何将函数的地址传给指针变量呢?
具体示例如下:

#include <stdio.h>

int sum(int num1, int num2)
{
	return num1 + num2;
}

void test()
{
	printf("hello world!");
}

int main()
{
	int num1 = 10;
	int num2 = 20;
	void(*ptest) = test;
	int (*psum)(int, int) = sum;
	printf("ptest: %p\n", ptest);
	printf("psum : %p\n", psum);
	return 0;
}

下面是运行结果:
在这里插入图片描述
这里的 int* 类型的指针变量和 void* 类型的指针变量是根据函数的返回值来设立的,ptest 和 psum都是指针变量的变量名。后面括号里的 (int, int) 是函数的参数的类型和个数的交代。
以上就是一个函数指针变量的创建。

函数指针变量的使用

我们可以通过函数指针来调用函数指针所指向的函数。
具体代码示例如下:

#include <stdio.h>

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

int main()
{
	int (*pAdd)(int, int) = Add;
	printf("%d\n", (*pAdd)(2, 5));
	printf("%d\n", pAdd(3, 8));
	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;
}

int main()
{
	int (*arr[5])(int, int) = { Add, Sub, Mul, Div };
	return 0;
}

通过以上的代码,我们就将函数 Add(),Sub(),Mul(),Div() 的地址存放在指针数组 arr 中了。

转换器的实现

我们以前写一个计算器可能是这样子写的:

#define _CRT_SECURE_NO_WARNINGS 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;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		printf("请输入两个数字:>");
		scanf("%d %d", &x, &y);
		switch(input)
		{
		case 1:
			Add(x,y);
			break;
		case 2:
			Sub(x,y);
			break;
		case 3:
			Mul(x,y);
			break;
		case 4:
			Div(x,y);
			break;
		case 0:
			printf("已退出计算器!\n");
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} while (input);
	return 0;
}

我们会发现,如果我们这样子写一个计算器的话要改动的地方会非常大,并且代码量会逐渐增大。那么有没有什么办法让我们的代码量减小呢?我们可以用这里的函数指针数组实现成转换器来解决这个问题。
示例如下:

#define _CRT_SECURE_NO_WARNINGS 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;
	do
	{
		int (*pfarr[])(int, int) = { 0, Add, Sub, Mul, Div };
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个整数:>");
			scanf("%d %d", &x, &y);
			int ret = pfarr[input](x, y);
			printf("%d\n", ret);
		}
		else if (input)
		{
			printf("选择错误,请重新选择\n");
		}
		else
		{
			printf("退出计算器!\n");
			break;
		}
	} while (input);
	return 0;
}

代码分析:
我们先把计算器中所需要的四个函数的地址存放在一个指针数组中,然后在我们需要的时候我们可以通过这个指针数组去调用所需的函数的地址,然后根据这个地址去调用相应的函数。这样子我们就利用了函数指针数组实现了一个转换器的功能。

七、野指针

(1)野指针是什么

野指针就是一个指向那一块未知的内存空间的指针。

(2)野指针的成因

野指针的成因有三个,现在就来介绍野指针的成因。

指针未初始化

先看一段代码:

#include <stdio.h>

int main()
{
	int* p;//指针没有初始化,指针指向的内存空间为随机值。
	*p = 10;
	return 0;
}

这里的指针变量 p 并未进行初始化,我们不知道这个指针变量指向的内存空间中的地址是哪一块。这样子就形成了野指针。

指针越界访问

先看一段代码:

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 12; i++)
	{
		*(p++) = i;
	}
	return 0;
}

这一段代码指针出现了越界访问的问题,当指针变量 p 超出了数组 arr 所能访问到的范围时,p 就是一个野指针。当我们运行这一段代码的时候程序会直接崩溃。
在这里插入图片描述
因此,我们要尽量避免野指针的出现。

指针指向的空间释放

先看一段代码:

#include <stdio.h>

int* test()
{
	int num = 20;
	return &num;
}

int main()
{
	int* p = test();
	printf("%d\n", *p);
	return 0;
}

这一段代码出现了指针所指向的空间释放的问题。在 test 函数中的 num 一经 test 函数执行完就会被内存回收,但是指针变量 p 所指向的地址还是那一块内存空间被回收了的地址,这个时候指针变量 p 就是一个野指针了。

(3)如何规避野指针

规避野指针有三个操作,以下来分别介绍这三种操作:

对指针变量进行初始化操作

在使用指针变量时,我们先对指针变量进行一个初始化操作。防止指针变量随意指向内存空间的地址。
示例:

int a = 0;
int* p = &a;

检查指针是否出现越界访问

在用指针访问数组的时候,要检查是否出现了越界访问的现象,如果出现了越界访问要及时去修改程序代码。
示例:

int arr[10] = {0,1,2,3,4,5,6,7,8,9};
int i = 0;
int* p = arr;
for(i = 0; i < 10; i++)
{
	*(p++) = i;
}
p = arr;

在不使用这个指针变量后及时的将该指针变量设置为NULL

在使用完这个指针变量之后对指针变量进行设置为 NULL 操作,这个操作需要包含一个头文件 <stdio.h> 。
示例:

int i = 0;
int* p = &i;
p = NULL;

(4)assert断言

assert 宏在头文件 <assert.h> 内,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。我们可以利用 assert 宏来判断这个指针是否为空,如果为不为空我们就执行下列语句,如果为空就会报错终⽌运⾏。
代码示例如下:

#include <assert.h>
#include <stdio.h>

int main()
{
	int i = 0;
	int* p = &i;
	p = NULL;
	assert(p);
	return 0;
}

代码执行结果如下:
在这里插入图片描述
如果将assert宏注释掉的话或者用 #define NDEBUG 代码可以正常运行。
具体示例如下:
在这里插入图片描述

八、const修饰指针变量

被const修饰的指针变量是无法被改变的。
但是当我们使用 const 修饰指针变量时,我们要注意 const 修饰指针变量的位置是在 * 的右边还是左边。
当 const 修饰指针变量放在 * 的右边时就是让指针变量无法存储别的地址。
具体示例如下:

#include <stdio.h>

int main()
{
	int n = 0;
	int m = 1;
	int* const p = &n;
	p = &m;
	return 0;
}

当我们敲完上述代码时,我们会发现编译器会报错。
在这里插入图片描述
说明我们无法修改指针变量 p 所指向的内存空间。
当const修饰指针变量放在 * 左边时,我们就无法修改该指针变量所指向的内存空间内的值。
具体实例如下:

#include <stdio.h>

int main()
{
	int n = 0;
	int m = 1;
	const int*  p = &n;
	*p = 10;
	return 0;
}

当我们敲完上述代码时,我们会发现编译器会报错。
在这里插入图片描述
说明我们就无法修改该指针变量所指向的内存空间内的值。
我们也可以这样去理解 const 修饰指针变量:当 const 放在星号的右边时我们修饰的是 p 指针,并没有修饰 p 指针指向的内存空间里面的值,也就无法更改 p 指针所指向的内存空间。当 const 放在星号的左边时我们修饰的是 p 指针经过解引用后的值,也就无法更改 p 指针所指向的内存空间里的值。

九、回调函数

(1)回调函数是什么

回调函数就是⼀个通过函数指针调⽤的函数。
如果我们把指针的地址作为参数传递给另外一个函数,当这个指针被用来调用其所指向的函数时,这个被调用的函数就是回调函数。

(2)回调函数的使用

我们现在了解了回调函数是什么,那我们应该如何去使用呢?
代码示例如下:

#include <stdio.h>

//写一个函数,实现将两个数相加并打印出相应的结果。

void Add(int x, int y, void(*Print)(int))
{
	int sum = x + y;
	Print(sum);
}

//这是我们自己写的一个打印函数。
void Print(int x)
{
	printf("%d\n", x);
}

int main()
{
	int a = 10;
	int b = 20;
	Add(a, b, Print);
	return 0;
}

运行结果如下:
在这里插入图片描述
这里我们将我们自己写的 Print 函数的地址传给 Add 函数并在 Add 函数内部我们调用了 Print 函数来打印我们求和的结果,这里的 Print 函数就是一个回调函数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值