C语言---------深入理解指针

ops/2024/10/9 0:40:59/

目录

C%A6%E6%8C%87%E9%92%88-toc" style="margin-left:0px;">一、字符指针

C%E3%80%81%E6%8C%87%E9%92%88%E6%95%B0%E7%BB%84%EF%BC%9A-toc" style="margin-left:0px;">二、指针数组:

C%87%E9%92%88%EF%BC%9A-toc" style="margin-left:0px;">三、数组指针

C%9A-toc" style="margin-left:40px;">1、定义:

C%E6%95%B0%E7%BB%84%E5%90%8D%E5%8C%BA%E5%88%AB%EF%BC%9A-toc" style="margin-left:40px;">2、&数组名和数组名区别:

C%87%E9%92%88%E7%9A%84%E4%BD%BF%E7%94%A8%EF%BC%9A-toc" style="margin-left:40px;">3、数组指针的使用:

C%8C%E6%8C%87%E9%92%88%E5%8F%82%E6%95%B0%EF%BC%9A-toc" style="margin-left:0px;">四、数组参数,指针参数:

C%A0%E5%8F%82%EF%BC%9A-toc" style="margin-left:40px;">1、一维数组传参:

C%E7%BB%B4%E6%95%B0%E7%BB%84%E4%BC%A0%E5%8F%82%EF%BC%9A-toc" style="margin-left:40px;">2、二维数组传参:

C%87%E9%92%88%E4%BC%A0%E5%8F%82%EF%BC%9A-toc" style="margin-left:40px;">3、一级指针传参:

C%E7%BA%A7%E6%8C%87%E9%92%88%E4%BC%A0%E5%8F%82%EF%BC%9A-toc" style="margin-left:40px;">4、二级指针传参:

C%87%E9%92%88%EF%BC%9A-toc" style="margin-left:0px;">五、函数指针

C%9A-toc" style="margin-left:40px;">1、定义:

C%26%E5%87%BD%E6%95%B0%E5%90%8D%EF%BC%9A-toc" style="margin-left:40px;">2、函数名和&函数名:

C%87%E9%92%88%E7%9A%84%E8%B0%83%E7%94%A8%EF%BC%9A-toc" style="margin-left:40px;">3、函数指针的调用:

C%87%E9%92%88%E6%95%B0%E7%BB%84%EF%BC%9A-toc" style="margin-left:0px;">六、函数指针数组:

C%87%E5%90%91%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E6%95%B0%E7%BB%84%E7%9A%84%E6%8C%87%E9%92%88-toc" style="margin-left:0px;">七、指向函数指针数组的指针


C%A6%E6%8C%87%E9%92%88">一、字符指针

定义:字符指针就是指向一个字符串的指针指针类型为char*

int main()
{char c = 'z';char* pc = &c;printf("%c\n", c);return 0;
}

或者:

int main()
{const char* pc = "abcdef";printf("%s\n", pc);return 0;
}

这里因为是个常量字符串,所以可以用const修饰来达到后面不会被修改的问题。

上面const char* pc = "abcdef"这里并不是将abcdef赋给pc的指针变量里面,而是将字符串的首字符的地址放在pc中。

接下来看一个经典题目:

#include <stdio.h>
int main()
{char str1[] = "hello ppr.";char str2[] = "hello ppr.";char *str3 = "hello ppr.";char *str4 = "hello ppr.";if(str1 ==str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if(str3 ==str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

这串代码的意思是:

将hello ppr.给到数组str1和str2中,判断二者是否相等。

将hello ppr.的地址给到str3和str4中,判断二者是否相等。

那么为什么会出现这样的结果呢?

对于str1和str2,这两个是开辟了两块不同的空间,比较数组名就是比较首元素的地址,毕竟空间不同肯定比较出来就会不同。

对于str3和str4

首先要知道这是一个常量字符串是不能够被修改的,所以在内存就没有必要存储两份了,那么这两个字符指针就都会指向那同一块空间。

C%E3%80%81%E6%8C%87%E9%92%88%E6%95%B0%E7%BB%84%EF%BC%9A">二、指针数组:

定义:存放指针的数组就是指针数组。

int main()
{int arr1[5] = { 1,2,3,4,5 };int arr2[5] = { 2,3,4,5,6 };int arr3[5] = { 3,4,5,6,7 };int arr4[5] = { 0,0,0,0,0 };int* arr[4] = {arr1,arr2,arr3,arr4};for (int i = 0; i < 4; i++){for (int j = 0; j < 5; j++){//printf("%d ", arr[i][j]);printf("%d ", *(*(arr + i) + j));}printf("\n");}return 0;
}

如上代码所示:

arr先和右边的[]结合,就是一个数组,然后里面每一个元素都是int* ,这就是一个存放整形指针的数组。

例如:

int* arr1[10]//这是一个整形指针的数组,里面有10个元素。

char* arr2[10]//这是一个这是一个字符指针的数组,里面有10个元素。

char** arr3[10]这是一个二级字符指针的数组,里面有10个元素。

C%87%E9%92%88%EF%BC%9A">三、数组指针

C%9A">1、定义:

定义:这是一个指针,是能够指向数组的指针

写法:int* p1[10]       or        int (*p2)[10]

这两种写法是哪一种呢?

很显然是后面的,因为前一个和指针数组一样。

因为p2和[]结合的优先级高于*,所以需要用小括号来改变其结合的优先顺序。

C%E6%95%B0%E7%BB%84%E5%90%8D%E5%8C%BA%E5%88%AB%EF%BC%9A">2、&数组名和数组名区别:

例如:

对于int arr[10];

arr和&arr分别有啥区别?

同样从代码入手:

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };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和&arr的话,二者是相等的,但是如果对二者都进行指针运算+1后,arr跳过了增加了4,跳过了一个整型,而&arr增加了28(十六进制),换算成十进制为40,所以就是跳过了10个整型,也就是这个数组。

故有结论:arr是这个数组的首元素的地址

                &arr是这整个数组的地址,所以+1是跳过这个数组的大小

C%87%E9%92%88%E7%9A%84%E4%BD%BF%E7%94%A8%EF%BC%9A">3、数组指针的使用:

数组指针大多时候是在二维数组的传参中使用的,在一维数组中的作用不大。

依然从代码入手:

void print_arr1(int arr[3][5], int row, int col)
{int i = 0;for (i = 0; i < row; i++){for (int 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 (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,10 };print_arr1(arr, 3, 5);printf("\n");//数组名arr,表示首元素的地址//但是二维数组的首元素是二维数组的第一行//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址//可以数组指针来接收print_arr2(arr, 3, 5);return 0;
}

如上代码所示:

这里用了自己创建的两个打印二维数组的函数,在其中唯一不同的是形参的接收不同,在print_arr1中,用传统的二维数组来接收,而在print_arr2中,用数组指针(指向数组的指针)来接收是一样的效果。

拓展:int (*parr[10])[5] 这是个啥?

首先parr和[10]结合成为一个数组,所以parr是个数组,在这个数组里面有10个元素,

其中每个元素的类型是int(*)[5]这个数组指针

C%8C%E6%8C%87%E9%92%88%E5%8F%82%E6%95%B0%EF%BC%9A">四、数组参数,指针参数:

C%A0%E5%8F%82%EF%BC%9A">1、一维数组传参:

#include <stdio.h>
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int* arr)
{}
void test2(int* arr[20])
{}
void test2(int** arr)
{}
int main()
{int arr[10] = { 0 };int* arr2[20] = { 0 };test(arr);test2(arr2);
}

如上代码中,所有传参均是可以的,用数组接收或者用指针接收。

C%E7%BB%B4%E6%95%B0%E7%BB%84%E4%BC%A0%E5%8F%82%EF%BC%9A">2、二维数组传参:

C%87%E9%92%88%E4%BC%A0%E5%8F%82%EF%BC%9A">3、一级指针传参:

当一个函数的参数部分为一个一级指针的时候,函数能接收的参数有:

void test(int* a)
{}int main()
{int a = 10;int* pa = &a;int arr[10];test(&a);test(pa);test(arr);return 0;
}

C%E7%BA%A7%E6%8C%87%E9%92%88%E4%BC%A0%E5%8F%82%EF%BC%9A">4、二级指针传参:

当一个函数的参数部分为一个二级指针的时候,函数能接收的参数有:

void test(int** a)
{}int main()
{int** a ;int* pa ;int* arr[10];test(a);test(&pa);test(arr);return 0;}

C%87%E9%92%88%EF%BC%9A">五、函数指针

1、定义:

类比一下:

数组指针是指向数组的指针,函数指针就是指向函数的指针

(类比于数组指针

那么函数指针该怎么写呢?

例如有一个函数int Add(int x,int y);

假设我要定义一个函数指针变量名为ppr只想Add函数

那么首先要是一个指针就需要用()来进行:(*ppr)

然后需要是个函数里面写形参的类型就是:(*ppr)(int,int)

最后看返回值:int (* ppr)(int,int)= &Add;        

C%26%E5%87%BD%E6%95%B0%E5%90%8D%EF%BC%9A">2、函数名和&函数名:

函数名和&函数名在C语言中大多数情况下都是等效的,都表示函数的地址。然而,使用&可以使代码更加清晰明确。

C%87%E9%92%88%E7%9A%84%E8%B0%83%E7%94%A8%EF%BC%9A">3、函数指针的调用:

int Add(int x, int y)
{return x + y;
}int main()
{//int (*pf)(int, int) = &Add;int (* pf)(int, int) = Add;//int ret = Add(2, 3);int ret = pf(2, 3);int ret = (*pf)(2, 3);printf("%d\n", ret);return 0;
}

如上代码所示:

将Add赋给pf后pf就可以像Add函数一样地调用了,也可以对pf解引用后调用,语法上可以这样理解。

C%87%E9%92%88%E6%95%B0%E7%BB%84%EF%BC%9A">六、函数指针数组:

存放函数指针的数组就是函数指针数组。

写法:

在函数指针的基础上改变:

如上所示:

int (* ppr)(int,int)这是一个函数指针

int (* ppr[5])(int,int)此时ppr会和[5]先结合,就是一个数组,去掉数组名和个数剩下的

int (*)(int,int)这个就是函数指针类型。

应用场景:转移表

C%87%E5%90%91%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E6%95%B0%E7%BB%84%E7%9A%84%E6%8C%87%E9%92%88">七、指向函数指针数组的指针

我们从函数指针数组入手:

int (* pr[5])(int,int)这是一个函数指针

现在我想要一个指向函数指针数组的指针,所以就可以将ppr与一个*结合使其成为指针

int (* (*ppr)[5])(int,int) = &pr;

这个就成为了 指向函数指针数组的指针

这个的函数指针类型就是去掉 (*ppr)

这个int (* [5] )(int,int)就是一个函数指针类型

接下来画个图来理解:


 


http://www.ppmy.cn/ops/49940.html

相关文章

递归解析 LXML 树并避免重复进入某个节点

1、问题背景 我们在使用 LXML 库解析 MathML 表达式时&#xff0c;可能会遇到这样一个问题&#xff1a;在递归解析过程中&#xff0c;我们可能会重复进入同一个节点&#xff0c;导致解析结果不正确。例如&#xff0c;我们希望将以下 MathML 表达式解析为 Python 表达式&#x…

【车载AI音视频电脑】200万像素迷你一体机

产品主要特点&#xff1a; -设备安装方便简洁&#xff0c;可通过3M胶直接将设备粘 贴到车前挡风玻璃上 -支持IE预览&#xff0c;手机&#xff0c;PAD实时预览&#xff0c; 支持电脑客 户端实时预览功能 -内置2路模拟高清, 每路均可达到200万像素。另 外可扩充2路1080P模拟…

OpenCV绘制直线

一 绘制图形 画线 画矩形 画圆 画椭圆 画多边形 绘制字体 二 画线 line(img,开始点&#xff0c;结束点&#xff0c;颜色…) 参数结束 img&#xff1a;在那个图像上画线 开始点,结束点&#xff1a;指定线的开始与结束位置&#xff1b; 颜色&#xff0c;线宽&#xff0c;线体…

【Linux】进程_6

文章目录 五、进程8. 进程地址空间 未完待续 五、进程 8. 进程地址空间 上图可能很多人都看过了&#xff0c;这里再来验证一下&#xff1a; 验证位置&#xff1a; 验证堆栈的生长方向&#xff1a; 在上面的空间布局图中&#xff0c;有一个疑问&#xff0c;画的空间是 内存…

Android中蓝牙设备的状态值管理

在Android中&#xff0c;蓝牙状态可以通过多种方式来描述&#xff0c;主要包括蓝牙适配器状态、蓝牙设备连接状态以及蓝牙广播状态&#xff0c;其关键的蓝牙状态实现类有BluetoothAdapter、BluetoothDevicePairer、BluetoothDevice、BluetoothProfile&#xff0c;详细介绍如下&…

C++ 算法教程

归并排序 #include<iostream> using namespace std; template <class T> void Merge(T data[],int start,int mid,int end) {int len1 mid - start 1, len2 end - mid;int i, j, k;T* left new int[len1];T* right new int[len2];for (i 0; i < len1; i)…

Linux各目录的作用

Linux各目录的作用 目录作用~登录用户对应的目录.当前工作目录$PATH环境变量/根目录/boot启动Linux使用的文件&#xff0c;例如Linux内核&#xff0c;包括连接文件和镜像文件&#xff0c;&#xff08;删了就启动不了了&#xff09;/bin(/usr/bin,/usr/local/bin)Binary&#x…

2024.6.12 玄子Share-Docker 安装与镜像拉取

2024.6.12 玄子Share-Docker 安装与镜像拉取 卸载 Docker 如果已安装旧版 Docker 则先卸载 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine安装 Docker yum-utils 是一…