C语言——深入理解指针_学习笔记

关于指针的初步理解可以浏览之前写的博客C语言——指针_学习笔记

借助一些经典例题来深入理解C语言指针

一、指针运算

对于指针运算,在前面C语言——指针_学习笔记一文中,关于指针加减法和指针的自增自减运算已经做过详细介绍,

本文将结合数组相关知识,对指针运算进行深入剖析。(主要以题目为主)

1.1题目一

#include<stdio.h>

int main()
{
    
    
	char* a[] = {
    
     "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);

	return 0;
}

输出结果为:

at

如下图:
题目意思是,有一个一维数组,有3个元素,数组中每个元素的类型都是 char* 类型,存的是三个字符常量首字母的地址(a[]为指针数组,数组的每个元素是指针类型);又接着把指针数组a[]的首元素地址存在pa 中,然后进行pa++操作,而后以 %s的形式打印 *pa 。
在这里插入图片描述

1.2 题目二

#include <stdio.h>

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

输出结果为:
在这里插入图片描述
首先分析一下这段代码
通过前三行代码,可以大致画出一个指向关系的图,如下:

在这里插入图片描述

  1. 下面分析 printf(“%s\n”, **++cpp); ++cpp使得cpp指向c+2元素,*++cpp 得到的是数组c中的第三个元素,也就是首元素P的地址,所以 **++cpp就是对P的地址进行解引用,以%s 的形式打印出来,所以结果为 POINT。

  2. 接着,指向关系图发生变化,如下
    在这里插入图片描述
    在这里插入图片描述

  3. 因为上一行代码又进行了++cpp 的操作,所以指向关系图又变了,如下图:
    在这里插入图片描述
    在这里插入图片描述

  4. 指向关系图没变
    在这里插入图片描述
    在这里插入图片描述

二、常量指针

常量指针(const pointer)是一种指针,它指向的内容不可以被修改,但指针本身可以移动。常量指针的使用场景包括但不限于以下几种情况:

2.1 保护数据

有时,我们希望指针指向的数据是常量,即不能被修改。这通常出现在需要保护数据的完整性或者安全性的时候。例如,在函数中传递指向敏感数据的指针时,可以使用常量指针来防止数据被修改。

const int *ptr = &secret_data;

2.2 函数参数传递

在函数参数传递中,常量指针可以用于防止函数修改传入的数据。例如,在实现一个排序函数时,我们可能不希望该函数修改传入的数据,可以使用常量指针。

void sort(const int* arr, int n) {
    
    
    // ...
}

2.3 字符串处理

在C语言,字符串通常被表示为字符数组或字符指针。当处理字符串时,常量指针可以确保字符串的内容不会被修改,这有助于防止潜在的错误

const char* str = "Hello, World!";

2.4 优化性能

常量指针和普通指针在底层硬件上可能有不同的行为。在一些情况下,使用常量指针可能会提高性能,特别是在涉及位操作和高精度计算时。然而,这种优化通常需要对底层硬件和编译器有深入的理解,因此,如果没有充分的理由,一般不推荐进行这种优化。

2.5 数据结构的const成员

在某些数据结构中,常量指针可以用于表示该数据结构是常量,例如const STL容器。

2.6 避免类型转换

常量指针也可以用于避免不必要的类型转换。例如,如果你有一个函数返回一个const double*,那么你就不需要将返回值转换为其他类型。

三、变量指针

变量指针的使用场景举例包括但不限于以下几种情况:

3.1 写一个函数交换两个变量的值

通过使用指针,可以在函数中交换两个变量的值。

#include <stdio.h>

void swap(int *a, int *b) {
    
    
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    
    
    int x = 10;
    int y = 20;
    printf("Before swap: x=%d, y=%d\n", x, y);
    swap(&x, &y);
    printf("After swap: x=%d, y=%d\n", x, y);
    return 0;
}

3.2 函数返回运算的状态,但结果通过指针返回

例如,让函数返回一个特殊的不属于有效范围内的值来表示出错。

#include <stdio.h>

int calculate(int *result) {
    
    
    // ... some calculation ...
    if (error) {
    
    
        *result = -1;  // set result to -1 if there is an error
        return 0;
    } else {
    
    
        *result = 123;  // set result to some value if no error
        return 1;
    }
}

int main() {
    
    
    int result = -1;  // Initialize result to -1
    calculate(&result);
    if (result == -1) {
    
    
        printf("Error occurred!\n");
    } else {
    
    
        printf("Result: %d\n", result);
    }
    return 0;
}

四、数组指针和指针数组

4.1 概念

  1. 数组指针是指针,是指向数组的指针。
    例如:
int arr[] = {
    
    1, 2, 3, 4, 5};
int *ptr = arr; // ptr是指向数组的指针,指向数组首元素的地址,ptr+1是跨过4个字节
int *ptra = &a;//ptr是指向整个数组的指针,也是数组首元素的地址,但是ptra+1跨过20个字节
  1. 指针数组是数组,数组中的每个元素存的是一个地址。
char* a[] = {
    
     "work","at","alibaba" };//这里的数组a就是一个指针数组,数组中的每个元素都是char*类型的指针

在这里插入图片描述
指针数组的每个元素是地址,⼜可以指向⼀块区域。

注意:前文指针运算的时候有提到。

4.2 指针数组模拟实现二维数组

既然指针数组的每个元素是地址,而这个地址还可以接着指向一块区域,那么就可以利用这一点,用指针数组来模拟实现二维数组

代码如下:

#include <stdio.h>
int main() 
{
    
    
	int arr1[] = {
    
     1,2,3,4,5 };
	int arr2[] = {
    
     2,3,4,5,6 };
	int arr3[] = {
    
     3,4,5,6,7 };
	//数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中
	int* parr[3] = {
    
     arr1, arr2, arr3 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
    
    
		for (j = 0; j < 5; j++)
		{
    
    
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

通过指针数组来访问并打印arr1,arr2和arr3三个数组中的内容,运行结果如下:
在这里插入图片描述
他们之间的关系画图表示如下:
在这里插入图片描述
parr是一个指针数组,有三个元素,都是地址,第一个元素存的是数组 arr1首元素的地;第二个元素存的是数组arr2首元素的地址;第三个元素存的是数组arr3 首元素的地址。要访问arr[1]的话,可以写成parr[0][1] ,神似二维数组。

4.3 数组传参的本质

在C语言中,数组的传递通常是通过指针进行的。当你将数组作为参数传递给函数时,实际上传递的是数组的地址。这是因为在C语言中,数组名实际上就是一个指向数组第一个元素的指针。

例如:

#include <stdio.h>

void printArray(int arr[], int size) {
    
        //其实这里函数写成 void printArray(int* , int size)这种形式也是可以的
    for (int i = 0; i < size; i++) {
    
    
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    
    
    int arr[] = {
    
    1, 2, 3, 4, 5};
    int size = sizeof(arr) / sizeof(arr[0]);
    printArray(arr, size);
    return 0;
}

补充:一维数组传参的本质是数组首元素的地址,二维数组传参的本质是数组第一行的地址

五、函数指针

函数指针是指针,是指向函数的指针,存放的是函数的地址,可以通过函数指针变量调用函数。

5.1 函数的地址:

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

运行结果如下:
在这里插入图片描述
说明函数也是有地址的,函数名,&(函数名)都可以拿到函数的地址。

函数指针类型解析

int       (*pf3) 			(int x, int y)
 | 			| 				------------ 
 | 			| 					|
 | 			| 					pf3指向函数的参数类型和个数的交代
 | 			函数指针变量名
 pf3指向函数的返回类型
 
int (*) (int x, int y) //pf3函数指针变量的类型

5.2 回调函数

谈到函数指针就不得不提到回调函数,回调函数是什么?

回调函数就是一个通过函数指针调用的函数。

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

举个例子来理解:
现在要写一个计算器程序,实现两个整形数字的加减乘除运算。
请添加图片描述

利用函数指针,把调用的函数的地址以参数的形式传递过去,使用函数指针接收,函数指针指向什么函数就调用什么函数,这里其实使用的就是回调函数的功能。

5.3 转移表

函数指针数组的实现;转移表
前文已经介绍了指针数组的相关内容,那么这里也就很好理解了,函数指针数组是一个数组,数组中每个元素都是函数指针,数组中每个元素存储的是一个函数的地址。

上面计算器的程序可以利用函数指针数组进一步优化:

#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 input = 0;
	int ret = 0;
	int a = 0;
	int b = 0;
	//初始化一个函数指针数组,5个元素
	int (*p[5])(int, int) = {
    
    0,Add,Sub,Mul,Div}; //这里加入一个元素0,是为了使数组下标和选择匹配
	do
	{
    
    
		printf("*****************************\n");
		printf("******* 1. Add   2.Sub ******\n");
		printf("******* 3.Mul    4.Div ******\n");
		printf("******* 0.Exit (退出)  ******\n");
		printf("*****************************\n");

		printf("请选择你要使用的运算法则:");
		scanf("%d", &input);

		if ((input <= 4 && input >= 1))
		{
    
    
			printf("输入操作数:");
			scanf("%d %d", &a, &b);
			ret = (*p[input])(a, b);
			printf("ret = %d\n", ret);
		}
		else if (input == 0)
		{
    
    
			printf("退出计算器\n");
		}
		else
		{
    
    
			printf("输入有误\n");
		}
	} while (input);

	return 0;
}

六、二级指针和多级指针

二级指针是指向指针的指针,它通常用于处理指向指针的指针变量。例如,如果你有一个指向整数的指针,并且你想通过一个指针来存储该指针的地址,那么你需要使用二级指针。

多级指针是指向指针的指针,可以有多级,通常用于处理指向指针的指针变量。例如,如果你有一个指向整数的一级指针,并且你想通过一个指针来存储该指针的地址,那么你需要使用二级指针。同理,如果你有一个指向二级指针的一级指针,并且你想通过一个指针来存储该一级指针的地址,那么你需要使用三级指针。以此类推,可以有多级指针。

多级指针解题建议多画内存布局图,搞清楚指针到底指向什么位置,是什么内容!!!

多级指针一般不常用,后面如果接触到了实例再补充。

猜你喜欢

转载自blog.csdn.net/yjagks/article/details/132676213