当前位置: 首页 > news >正文

C语言——指针进阶

在这里插入图片描述
本章重点

  1. 字符指针
  2. 数组指针
  3. 指针数组
  4. 数组传参和指针传参
  5. 函数指针
  6. 函数指针数组
  7. 指向函数指针数组的指针
  8. 回调函数
  9. 指针和数组面试题的解析

1. 字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char*

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

还有一种方式是

int main()
{
const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
printf("%s\n", pstr);
return 0;
}

这样就能完整打印整个字符串了。
但是我们不是把整个字符串放到pstr,而是放了字符串首元素的地址。

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

下面看一道笔试题。

#include <stdio.h>
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;
}

我们运行起来的结果是什么呢,让我们看一下编译器给的结果吧。
在这里插入图片描述
这里原因是我们第一个是数组,数组创造会开辟空间的,而开辟的是两个不同的空间,这样他们的地址肯定也是不同的,而第二个为什么相同呢,首先我们的hello bit.是常量字符串,只给常量字符串开辟了空间所以str3和str4是指向同一个空间的,这样的话地址是相同的,我们这道题其实就是比较他们的地址是不是相同的。我们也可以通过调试来看到我们想要的结果。
在这里插入图片描述
这样就很明显的可以看出我们给数组的时候是重新分配空间的。

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

#include<stdio.h>
int main()
{int a = 10;int* p = &a;int* ps = &a;return 0;
}

在这里插入图片描述
其实就是和上面这个代码一样的效果,这样大家可能就好理解了。
2. 指针数组

在我们学这个的时候,大家经常搞混,一下子字符数组,一下字指针数组,还有数组指针,其实我们只要抓住它是什么就可以解决好多问题,比如数组指针,他就是指向数组的指针,那相反的话,指针数组就是存放指针的数组。

在《指针》章节我们也学了指针数组,指针数组是一个存放指针的数组。
这里我们再复习一下,下面指针数组是什么意思?

int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

3. 数组指针
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。

int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?

首先p1先和括号联系,在和联系,那表示的意思就是指针数组,元素个数是10个。
p2就是先和
联系,在和[]联系,那就是代表指向数组的指针。

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

3.2 &数组名VS数组名
这个内容其实我们在之前就讲过好几次了,数组名是首元素的地址,除了两个特例之外,一个是&,一个是sizeof,这两个代表整个数组。

#include <stdio.h>
int main()
{
int arr[10] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}

看上面这个例子。
在这里插入图片描述
我们可以看到他们的内容虽然是一样的,但是效果就是不同,让我们来看看他们+1的结果。
在这里插入图片描述
很明显不一样了,这是因为取出地址+1是跳过整个数组。

实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是(16进制是28)40,刚刚好整个数组。

3.3 数组指针的使用
那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
//但是我们一般很少这样写代码
return 0;
}

我们用数组指针来打印数组。

#include<stdio.h>
void print_arr(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(arr, 3, 5);return 0;
}

在这里插入图片描述
学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

第一个就是存放int 类型的数组。
第二个就是存放int的数组,指针数组。
第三个就是我们数组指针。
第四个parr3先和[10]结合,然和在和
结合,所以parr3它的意思就是指向数组的数组指针

画个图让大家更好的理解

在这里插入图片描述
4. 数组参数、指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

4.1 一维数组传参
在这里插入图片描述
那我们看上面的传参那些结果是正确的吧
从上往下,第一个数组传参,以数组的形式接收,肯定对,其实它的本质就是int* arr,
下一个[]中的参数其实没有意义,甚至可以乱写,所以也对,
第三个就是我们说i的本质。
那我们再看看下面的指针数组传参是怎样的。
第四个也是对,我们以数组的形式来接收。
第五个也是对的,我们认为后一个*表示的是它是一个指针,指向的内容是是int 的,也可以认为arr是属于int*
4.2 二维数组传参

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok?
{}
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);
}

那再让我们来看看这个二维数组传参。
我直接写代码里面了,这样看起来就能一一对应

void test(int arr[3][5])//ok?二维数组传参用二维数组接收,肯定没问题
{}
void test(int arr[][])//ok?这个不行,因为我们后面的列必须有参数
{}
void test(int arr[][5])//ok?可以
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok?不行
{}
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);
}

我们上面讲过二维数组的传参,所以判断起来就快一点了

4.3 一级指针传参
其实对于这个问题,我们只要掌握一个诀窍就可以了,一级指针传用一级指针接收,二级指针传参用二级指针来接收,但是一级指针的地址得用二级指针来接收。

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

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

void test1(int *p)
{}
//test1函数能接收什么参数?
void test2(char* p)
{}
//test2函数能接收什么参数?
void test1(int *p)
{}
//test1函数能接收什么参数?
可以是一维数组的数组名,可以是一个常量的地址,可以是一级指针
void test2(char* p)
{}
//test2函数能接收什么参数?
字符指针,字符数组的数组名,一个字符的地址

4.4 二级指针传参

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

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

void test(char **p)
{ }
int main()
{
char c = 'b';
char*pc = &c;
char**ppc = &pc;
char* arr[10];
test(&pc);
test(ppc);
test(arr);//Ok?
return 0;
}

5. 函数指针

我们先看一下下面的代码

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

我们看看地址输出的是什么
在这里插入图片描述
其实地址是一样的,我们函数加取地址其实和没加一样,都是对的
输出的是两个地址,这两个地址是 test 函数的地址。
那我们的函数的地址要想保存起来,怎么保存?
下面我们看代码

void test()
{
printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

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

其实函数的指针和我们数组指针特别相似,我们下面看两个特别有趣的代码,有点难度,但是有趣
在这里插入图片描述

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

那我们先看第一代码,先给结论,在0这个地址给了一个函数指针,没有返回类型,参数也没有
我们0之前有个大括号,这明显就是强制类型转换,在0这个地址放一个函数指针的类型,然后调用这个函数

下一个是signal这个指针。
我们拆分开来signal(int , void()(int)),先看这个,是一个函数,函数第一个参数是int 第二个参数是一个函数指针,类型就是void()(int),然后在看signal外面有一个*,代表就是一个指针,返回是void 参数是int 其实本质上就是一个函数指针。

typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

也可以这样写。
今天的分享就到这里,我们下次见!!!!

相关文章:

C语言——指针进阶

本章重点 字符指针数组指针指针数组数组传参和指针传参函数指针函数指针数组指向函数指针数组的指针回调函数指针和数组面试题的解析 1. 字符指针 在指针的类型中我们知道有一种指针类型为字符指针 char* int main() { char ch w; char *pc &ch; *pc w; return 0; }…...

heap pwn 入门大全 - 1:glibc heap机制与源码阅读(上)

本文为笔者学习heap pwn时&#xff0c;学习阅读glibc ptmalloc2源码时的笔记&#xff0c;与各位分享。可能存在思维跳跃或错误之处&#xff0c;敬请见谅&#xff0c;欢迎在评论中指出。本文也借用了部分外网和其他前辈的素材图片&#xff0c;向各位表示诚挚的感谢&#xff01;如…...

树莓派RP2040 用Arduino IDE安装和编译

目录 1 Arduino IDE 1.1 IDE下载 1.2 安装 arduino mbed os rp2040 boards 2 编程-烧录固件 2.1 打开点灯示例程序 2.2 选择Raspberry Pi Pico开发板 2.3 编译程序 2.4 烧录程序 2.4.1 Raspberry Pi Pico开发板首次烧录提示失败 2.4.2 解决首次下载失败问题 2.4.2.1…...

云安全攻防(八)之 Docker Remote API 未授权访问逃逸

Docker Remote API 未授权访问逃逸 基础知识 Docker Remote API 是一个取代远程命令行界面&#xff08;rcli&#xff09;的REST API&#xff0c;其默认绑定2375端口&#xff0c;如管理员对其配置不当可导致未授权访问漏洞。攻击者利用 docker client 或者 http 直接请求就可以…...

2023-08-13 LeetCode每日一题(合并两个有序数组)

2023-08-13每日一题 一、题目编号 88. 合并两个有序数组二、题目链接 点击跳转到题目位置 三、题目描述 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 …...

nbcio-boot升级springboot、mybatis-plus和JSQLParser后的LocalDateTime日期json问题

升级后&#xff0c;运行显示项目的时候出现下面错误 2023-08-12 10:57:39.174 [http-nio-8080-exec-3] [1;31mERROR[0;39m [36morg.jeecg.common.aspect.DictAspect:104[0;39m - json解析失败Java 8 date/time type java.time.LocalDateTime not supported by default: add Mo…...

「C/C++」C/C++搭建程序框架

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「UG/NX」NX二次开发「UG/NX」BlockUI集合「VS」Visual Studio「QT」QT5程序设计「C/C」C/C程序设计「Win」Windows程序设计「DSA」数据结构与算法「File」数据文件格式 目录 1. 分离职…...

Android 内存泄漏

名词解释 内存泄漏:即memory leak。是指内存空间使用完毕后无法被释放的现象&#xff0c;虽然Java有垃圾回收机制&#xff08;GC&#xff09;&#xff0c;但是对于还保持着引用&#xff0c; 该内存不能再被分配使用&#xff0c;逻辑上却已经不会再用到的对象&#xff0c;垃圾回…...

duckdb 源码分析之select执行流程

...

Android上的基于协程的存储框架

在Android上&#xff0c;经常会需要持久化本地数据&#xff0c;比如我们需要缓存用户的配置信息、用户的数据、缓存数据、离线缓存数据等等。我们通常使用的工具为SharePreference、MMKV、DataStore、Room、文件等等。通过使用现有的存储框架&#xff0c;结合协程&#xff0c;我…...

虚拟现实与增强现实技术的商业应用

章节一&#xff1a;引言 随着科技的不断发展&#xff0c;虚拟现实&#xff08;Virtual Reality&#xff0c;简称VR&#xff09;与增强现实&#xff08;Augmented Reality&#xff0c;简称AR&#xff09;技术正日益成为商业领域中的重要创新力量。这两种技术为企业带来了前所未…...

每日后端面试5题 第六天

1. Java中有几种类型的流 字符流、字节流 输入流、输出流 节点流、处理流 2 .Spring支持的几种bean的作用域 五种&#xff1a; 1.singleton bean在每个ioc容器中只有一个实例 2.prototype 可以有多个实例 3-5在web环境中才生效 3.request 每次请求才创建bean 4.se…...

LeetCode150道面试经典题-- 两数之和(简单)

1.题目 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可以按任意…...

转义字符\

转移字符&#xff0c;就是通过字符&#xff0c;来转变原来字符的意思 常见的转义字符&#xff1a; 1、 2 注&#xff1a;" 的作用和他是类似的 3 4、 当打印\a时&#xff0c;电脑会出现一个警告&#xff0c;蜂鸣的声音 5、 阿斯克码表...

什么是DNS欺骗及如何进行DNS欺骗

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、什么是 DNS 欺骗&#xff1f;二、开始1.配置2.Ettercap启动3.操作 总结 前言 我已经离开了一段时间&#xff0c;我现在回来了&#xff0c;我终于在做一个教…...

Android核心开发之——OpenGL

OpenGL是一种用于编程计算机图形的应用程序编程接口&#xff08;API&#xff09;。它提供了一系列函数和方法&#xff0c;用于绘制2D和3D图形&#xff0c;以及进行渲染和图形处理。OpenGL可以跨平台使用&#xff0c;支持各种操作系统和硬件设备。它被广泛应用于游戏开发、虚拟现…...

公共服务领域:西安新小区业主自立业主委员会年底分红83万以及103万事件区块链资金透明监管与投票解决方案的尝试

公共服务领域:西安新小区业主自立业主委员会年底分红83万以及103万事件区块链资金透明监管与投票解决方案的尝试 作者 重庆电子工程职业学院 | 向键雄 杜小敏 前言 本项目想法来源于,西安新小区业主开出物业自立业主委员会年底分红83万以及103万事件,对于此类事件,我们刨…...

ID3 决策树

西瓜数据集D如下: 编号色泽根蒂敲声纹理脐部触感好瓜1青绿蜷缩浊响清晰凹陷硬滑是2乌黑蜷缩沉闷清晰凹陷硬滑是3乌黑蜷缩浊响清晰凹陷硬滑是4青绿蜷缩沉闷清晰凹陷硬滑是5浅白蜷缩浊响清晰凹陷硬滑是6青绿稍蜷浊响清晰稍凹软粘是7乌黑稍蜷浊响稍糊稍凹软粘是8乌黑稍蜷浊响清晰…...

简单线性回归:预测事物间简单关系的利器

文章目录 &#x1f340;简介&#x1f340;什么是简单线性回归&#xff1f;&#x1f340;简单线性回归的应用场景使用步骤&#xff1a;注意事项&#xff1a; &#x1f340;代码演示&#x1f340;结论 &#x1f340;简介 在数据科学领域&#xff0c;线性回归是一种基本而强大的统…...

Vue2-收集表单数据、过滤器、内置指令与自定义指令、Vue生命周期

&#x1f954;&#xff1a;我徒越万重山 千帆过 万木自逢春 更多Vue知识请点击——Vue.js VUE2-Day4 收集表单数据1、不同标签的value属性2、v-model的三个修饰符 过滤器内置指令与自定义指令1、内置指令2、自定义指令定义语法&#xff08;1&#xff09;函数式&#xff08;2&am…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

在Ubuntu24上采用Wine打开SourceInsight

1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

Java数值运算常见陷阱与规避方法

整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...

mac 安装homebrew (nvm 及git)

mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用&#xff1a; 方法一&#xff1a;使用 Homebrew 安装 Git&#xff08;推荐&#xff09; 步骤如下&#xff1a;打开终端&#xff08;Terminal.app&#xff09; 1.安装 Homebrew…...