指针的进阶(提高篇)

ops/2025/3/4 16:27:57/

序言:

前面概要:

1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4. 指针的运算

1.字符指针 

一般情况有:

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

还有这种:

#include<stdio.h>
int main()
{const char* pc = "abcdef";printf("%c\n", *pc);return 0;
}

 这里并不是把abcdef这个字符串放到字符指针pc里,而是将字符串的首地址放到字符指针pc里

ba256783b30241199ea3391ace5ecf3f.png

#include <stdio.h>
int main()
{const char str1[] = "abcdef";const char str2[] = "abcdef";const char* str3 = "abcdef";const char* str4 = "abcdef";if (str1 == str2)printf("str1 == str2\n");elseprintf("str1 != str2\n");if (str3 == str4)printf("str3 == str4\n");elseprintf("str3 != str4\n");return 0;
}

 str1[]创建了一个数组,存放了 a b c d e f \0

 str2[]创建了一个数组,存放了 a b c d e f \0

创建了2个数组,所以它们的首地址的地址是不一样的。

这里str1和str2指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当 几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。

const char* str3 = "abcdef";

是一个常量字符串,是无法改变的,这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。所以结果应该是:

8a956b582f0f419a971cc8398404848f.png

所以str1和str2不同,str3和str4不同。

 2.指针数组

指针数组是一个存放指针的数组。
int* arr1[10]; //存放整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
#include<stdio.h>
int main()
{int arr1[5] = { 1,2,3,4,5 };int arr2[5] = { 3,4,5,6,7 };int arr3[5] = { 5,6,7,8,9 };int* pc[3] = { arr1,arr2,arr3 };  //将arr1,arr2,arr3的首地址存放在指针数组里int i = 0;for (i = 0; i < 3; i++)	{int j = 0;for (j = 0; j < 5; j++){printf("%d", *(pc[i] + j));//通过pc[i] + j 可以访问到某个数组里的某个数据,这就成了二维数组//printf("%d", pc[i][j]);这两种打印结果是等价的}printf("\n");}return 0;
}

3.数组指针 

1.定义

数组指针是指针?还是数组?
答案是:指针。
那数组指针应该是:能够指向数组的指针。
例如:下面p1 和p2哪个是数组指针,哪个是指针数组?
int *p1[10];
int (*p2)[10];
注:
int (*p1)[10];
解释:p1先和*结合,说明p1是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p1是一个 指针,指向一个数组,叫指针数组。
这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p2先和*结合。p2是指针,指向数组,所以int(*p2)[10]是数组指针 。
#include<stdio.h>
int main()
{int a = 10;int* p = &a;int arr[10] = { 0 };printf("%p\n", arr);         //类型:int*printf("%p\n", arr+1);printf("%p\n", &arr[0]);     //类型:int*printf("%p\n", &arr[0]+1);printf("%p\n", &arr);        //类型:int(*)[10]printf("%p\n", &arr+1);return 0;
}

3fed27091fc5413a904256df27367eb8.png

 可以看出arr和&arr[0]的打印结果一样的,也说明了数组名是首元素的地址。

根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是 数组的地址,而不是数组首元素的地址。
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

2.&数组名   和   数组名    的区别 

数组名该怎么样理解?

通常情况下我们说的数组名都是数组首元素的地址。

但是有两个例外:

1.sizeod(数组名),这里的数组名表示整个数组,sizeof(数组名)是计算的整个数组的大小。

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

3.数组指针的使用

数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
#include <stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,0};int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量preturn 0;
}
int arr [ 5 ];                 //整型数组    数组5个元素
int * parr1 [ 10 ];         //指针数组,数组10个元素,每个元素是int*类型
int ( * parr2 )[ 10 ];       //parr2是数组指针,该指针指向一个数组,数组是10个元素,每个元素是int类型的
int ( * parr3 [ 10 ])[ 5 ];   //parr3是指针数组,数组10个元素,数组的每个元素类型是int(*)[5]
(判断该类型就把该名称去掉)

4.数组传参,指针传参

一维数组传参 :

#include <stdio.h>
void test(int arr[])          //ok?  可以   传的数组参数,数组接收
{}
void test(int arr[10])        //ok?  可以    数组只是指定了10个元素
{}
void test(int *arr)           //ok?  可以   数组传参可以用数组首元素地址
{}
void test2(int *arr[20])      //ok?  可以    形参写成数组也可以
{}
void test2(int **arr)         //ok?  可以     arr2是int*类型,传int **arr也可以
{}
int main()
{int arr[10] = {0};
int *arr2[20] = {0};test(arr);test2(arr2);
}

二维数组传参:

void test(int arr[3][5])//ok?      可以
{}
void test(int arr[][])//ok?         不可以
{}
void test(int arr[][5])//ok?      可以
{}
//二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
void test(int *arr)//ok?       不可以,数组3行5列,数组名是首元素的地址
{}
void test(int* arr[5])//ok?不可以,传参要么是数组要么是指针,这还是一维数组
{}
void test(int (*arr)[5])//ok?   可以
{}
void test(int **arr)//ok?   不可以,这是个二级指针
{}
int main()
{int arr[3][5] = {0};test(arr);
}
//传参得保证和要传的类型一样

指针传参

#include <stdio.h>
void print(int *p, int sz)
{int i = 0;for(i=0; i<sz; i++){printf("%d\n", *(p+i));}
}
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9};int *p = arr;int sz = sizeof(arr)/sizeof(arr[0]);//一级指针p,传给函数print(p, sz);return 0;
}
//二级指针
#include <stdio.h>
void test(int** ptr)
{printf("num = %d\n", **ptr); 
}
int main()
{int n = 10;int*p = &n;int **pp = &p;test(pp);test(&p);return 0;
}

 5.函数指针

int  Add(int a,int b)
{return a+b;
}
int main()
{int (*pf)(int ,int)=&Add;int (*pf)(int ,int)=Add;return 0;
}
//&函数名和函数名都是函数的地址
//pf是一个存放函数的指针变量——函数指针

 #指针数组,数组指针,函数指针的区分

int main()
{
//指针数组    本质上是数组,里面存放的是指针char* ch[5]
int arr[10]={0};
//数组指针   本质上是指针,存的是地址
int (*pa)[5]=&arr;
//pf是函数指针
int my_strlen(const  char *str)
{
return 0;
}
int (*pf)(const char*)=&my_strlen;
}

 6函数指针数组

是用来存放  函数指针  的数组

int main()
{//pf是函数指针
int my_strlen(const  char *str)
{
return 0;
}
int (*pf)(const char*)=&my_strlen;
//函数指针数组,那么在函数指针上加个数组呗!
int (*pfA[5])(const char*)={&my_strlen};
}

那么能干什么???

下面是一个简易的计算机小代码:

#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;do{printf( "*************************\n" );printf( " 1:add           2:sub \n" );printf( " 3:mul           4:div \n" );printf( "*************************\n" );printf( "请选择:" );scanf( "%d", &input);switch (input){case 1:printf( "输入操作数:" );scanf( "%d %d", &x, &y);ret = add(x, y);printf( "ret = %d\n", ret);break;case 2:printf( "输入操作数:" );scanf( "%d %d", &x, &y);ret = sub(x, y);printf( "ret = %d\n", ret);break;case 3:printf( "输入操作数:" );scanf( "%d %d", &x, &y);ret = mul(x, y);printf( "ret = %d\n", ret);break;case 4:printf( "输入操作数:" );scanf( "%d %d", &x, &y);ret = div(x, y);printf( "ret = %d\n", ret);break;case 0:printf("退出程序\n");breark;default:printf( "选择错误\n" );break;}} while (input);return 0;
}

用函数指针数组改一下

#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);}elseprintf( "输入有误\n" );printf( "ret = %d\n", ret);}return 0;
}

这样就不需要do while循环中的一直case!

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

int main()
{int arr[10];  //整型数组int  (*pa)[10]=&arr;  int (*pf[5])(int ,int);int(*(*ppf)[5](int ,int)=&pf;
//pf是函数指针数组,&pf后,ppf再指向它,那ppf就是指向函数指针数组的指针
}

8回调函数

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

 

void calc(int (*pf)(int ,int)){int x = 0;int y = 0;int ret = 0;printf("请输入两个操作数:");scanf( "%d %d", &x, &y);ret = pf(x, y);printf( "%d\n", ret);
}int main(){int x, y;int input = 1;int ret = 0;do{printf( "*************************\n" );printf( " 1:add           2:sub \n" );printf( " 3:mul           4:div \n" );printf( "*************************\n" );printf( "请选择:" );scanf( "%d", &input);switch (input){case 1:calc(Add);case 2:calc(Sub);break;case 3:calc(Mul);break;case 4:calc(Div);break;case 0:printf("退出程序\n");breark;default:printf( "选择错误\n" );break;}} while (input);return 0;
}

 通过定义一个calc函数,把AddSubMulDiv的函数的地址作为参数传给calc函数,去调用这些函数就是回调函数!


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

相关文章

Linux常见操作命令以及编辑器VI命令

一.复制(cp)和移动(mv) 1.复制文件 格式&#xff1a;cp 源文件 目标文件 2.复制目录 格式&#xff1a;cp -r 源文件夹 目标文件夹 3.重命名和移动 重命名格式&#xff1a;mv 源文件 目标文件 移动格式&#xff1a;mv 源文件 目录/源文件 二.查看文件内容 1.cat命令 格式&#x…

三维数据可视化与表面重建:Marching Cubes算法的原理与应用

1. 引言 随着现代医学影像技术的飞速发展&#xff0c;三维数据的可视化与重建已成为医学研究、临床诊断和手术规划的重要工具。在众多三维重建算法中&#xff0c;Marching Cubes算法因其高效、稳定的特性成为从离散数据场中提取等值面的经典方法。本报告将深入探讨Marching Cu…

智慧农业中光谱相机对土壤成分的无损检测应用‌

可浏览之前发布的一篇文章&#xff1a;光谱相机在农业中的具体应用案例 一、土壤成分定量分析 ‌养分检测‌ 光谱相机通过捕捉土壤反射的特定波长光线&#xff0c;可精准检测氮、磷、钾等主要养分含量&#xff0c;以及有机质和水分比例。例如&#xff0c;不同养分对近红外波段…

什么是RPC,和HTTP有什么区别?

RPC是Remote ProcedureCall的缩写&#xff0c;译为远程过程调用。要想实现RPC通常需要包含传输协议和席列化协议的实现。 而我们熟知的HTTP&#xff0c;他的中文名叫超文本传输协议&#xff0c;所以他就是一种传输协议。所以&#xff0c;我们可以认为RPC和HTTP并不是同一个维度…

前端水印实现方式

一、简介 简单来说&#xff0c;前端水印就是在网页或应用程序的前端界面上添加的一种标记&#xff0c;通常是文本、图标或图案等形式。它就像给你的数字内容贴上了一个独特的 “标签”&#xff0c;用于标识内容的归属、防止未经授权的使用和传播。比如&#xff0c;一些在线图片…

C#委托(delegate)的常用方式

C# 中委托的常用方式&#xff0c;包括委托的定义、实例化、不同的赋值方式以及匿名委托的使用。 委托的定义 // 委托的核心是跟委托的函数结构一样 public delegate string SayHello(string c);public delegate string SayHello(string c);&#xff1a;定义了一个公共委托类型 …

WPF创建DeepSeek本地自己的客户端-进阶版

本次文章接上次写的“基础版”继续 WPF快速创建DeepSeek本地自己的客户端-基础思路版本 1 开发环境与工具 开发工具&#xff1a;VS 2015 开发环境&#xff1a;.Net 4.0 使用技术&#xff1a;WPF 本章内容&#xff1a;WPF实现一个进阶版的DeepSeek客户端。 效果图如下&#x…

OpenCV计算摄影学(10)将一组不同曝光的图像合并成一张高动态范围(HDR)图像的实现类cv::MergeDebevec

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 resulting HDR 图像被计算为考虑了曝光值和相机响应的各次曝光的加权平均值。 cv::MergeDebevec 是 OpenCV 中用于将一组不同曝光的图像合并成一…