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

C语言超详细指针知识(二)

在上一篇有关指针的博客中,我们介绍了指针的基础知识,如:内存与地址,解引用操作符,野指针等,今天我们将更加深入的学习指针的其他知识。

1.指针的使用和传址调用

1.1strlen的模拟实现

库函数strlen的功能是求字符串长度,统计的是字符串中\0之前的字符个数

 函数原型如下:

size_t strlen (const char* str);

 参数str接收一个字符串的起始地址,然后开始统计字符串中\0之前的字符个数,最终返回长度。关于strlen函数的详细介绍网页:
strlen - C++ Referencehttps://legacy.cplusplus.com/reference/cstring/strlen/?kw=strlen我们要实现该函数的模拟实现,就要从字符串起始地址开始逐个遍历字符,只要不是\0字符,计数器就加一, 直到\0结束。

#include<stdio.h>
#include<assert.h>int my_strlen(char* str)
{assert(str != NULL);char* pz = str;while (*pz != '\0'){pz++;}return pz - str;
}int main()
{int len = my_strlen("abcdef");printf("%d\n", len);return 0;
}

 在代码里,我们用到了assert断言,他帮助我们判断str是否为空指针,如果是,程序会报错。


1.2传值调用和传址调用

学习指针的目的是使⽤指针解决问题,那什么问题,非指针不可呢?
例:写一个函数,交换两个整型变量的值
我们可能会写出这样的代码:
 
void Swap(int x, int y)
{int temp = x;x = y;y = temp;
}int main()
{int num1 = 10;int num2 = 20;printf("交换前:num1 = %d,num2 = %d\n", num1, num2);Swap(num1, num2);printf("交换后:num1 = %d,num2 = %d\n", num1, num2);return 0;
}

 代码运行结果:
 

我们发现没有产生预期的效果,为什么呢?调试观察一下:

 

我们发现在main函数内部,创建了num1和num2,num1的地址是0x0019f7e8,num2的地址是0x00197fdc,在调用Swap函数时,将num1和num2传递给了Swap函数,在Swap函数内部创建了形参x和y接收num1和num2的值,但是x的地址是0x0019f704,y的地址是0x0019f708,x和y确实接收到了num1和num2的值,不过x的地址和num1的地址不⼀样,y的地址和num2的地址不⼀样,相当于x和y是独⽴的空间,那么在Swap函数内部交换x和y的值,⾃然不会影响num1和num2,当Swap函数调⽤结束后回到main函数,num1和num2的没法交换。Swap1函数在使用的时候,是把变量本身直接传递给了函数,这种调用函数的⽅式我们之前在函数的时候就知道了,这种叫传值调用。
实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实
参。

 所以上述代码实际是不符合题目要求的。

该怎样设计代码呢,既然我们把交换数值传到函数内无法实现交换,那如果传输的是地址呢,同一份内存空间还会不会失败?

void Swap(int* px, int* py)
{int* temp = px;*px = *py;*py = *temp;
}int main()
{int num1 = 10;int num2 = 20;printf("交换前:num1 = %d,num2 = %d\n", num1, num2);Swap(&num1, &num2);printf("交换后:num1 = %d,num2 = %d\n", num1, num2);return 0;
}

查看结果,我们发现交换成功了。

 调用Swap函数的时候是将变量的地址传递给了函数,这种函数调用方法叫:传址调用。

传址调用,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量;所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用。如果函数内部要修改主调函数中的变量的值,就需要传址调用。

 2.数组名的理解

在上篇博客我们在使用指针访问数组的内容时,有这样的代码:
 

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;

 这里我们使用arr的方式拿到了数组第一个元素的地址,有些人可能会有疑惑?不应该&arr[0]才是数组第一个元素的地址吗,其实,大部分情况这两种写法是一样的意思,并没有含义的不同,我们来做一个测试。

我们发现数组名和数组首元素的地址打印出的结果一模一样 ,数组名就是数组首元素的地址

这时候可能有同学有疑问?数组名如果是数组首元素的地址,那下面的代码又该如何理解?

 我们看到上述代码的结果是40,如果arr是数组首元素的地址,其结果应该是4/8才对。

其实数组名就是数组首元素的地址是对的,但是有两个例外:

• sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
• &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)
除此之外,任何地方使用数组名,数组名都表示首元素的地址。

 这时有好奇的同学,尝试了如下代码:

三个打印结果一模一样,他就不会区分了,明明结果是一样,含义却不同吗?

我们再看下面这个代码:
 

我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1 也相差4个字节,是因为&arr[0] 和 arr 都是首元素的地址,+1就是跳过⼀个元素。
但是&arr 和 &arr+1相差40个字节,这就是因为&arr是整个数组的地址,+1 操作是跳过整个数组的。
到这里⼤家应该搞清楚数组名的意义了吧。
数组名是数组首元素的地址,但是有2个例外。

3.使用指针访问数组

有了前⾯知识的⽀持,再结合数组的特点,我们就可以很⽅便的使⽤指针访问数组了。
int main()
{int arr[10] = {0};int* p = arr;int sz = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i < sz; i++){scanf("%d", p + i);}for (int i = 0; i < sz; i++){printf("%d\n", *(p + i));printf("%d\n", p[i]);//这一行代码效果与上一行代码效果完全相等}return 0;
}

在该代码中,将*(p+i)换成p[i]也是能够正常打印的,所以本质上p[i] 是等价于 *(p+i)。


4.一维数组传参的本质

我们发现在函数内部无法正确获取数组的元素个数。这是为什么呢?

这时候就要学习数组传参的本质了 ,之前说过,数组名是数组首元素的地址,那么在数组传参的时候,传递的是数组名,本质上传递的其实是数组首元素地址,并不会传递整个数组,这两者是有区别的。

所以函数形参的部分理论上应该使用指针变量来接收首元素的地址。那么在函数内部我们写
sizeof(arr) 计算的是⼀个地址的大小(单位字节)而不是数组的大小(单位字节)。
void test(int arr[])
{int sz2 = sizeof(arr) / sizeof(arr[0]);printf("sz2 = %d\n", sz2);
}void test(int* p)
{int sz2 = sizeof(p) / sizeof(p[0]);printf("sz2 = %d\n", sz2);
}

这两者是等效的。

总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。

 5.二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪⾥? 答案是二级指针。

 如上图,a是整型变量,他的地址存放在pa中,pa的类型是int*,pa的地址又存放在ppa中,ppa的类型是int**,这就是二级指针。

对于二级指针的运算有:

int a = 20;
*ppa = &a;

 *ppa 通过对ppa中的地址进⾏解引用,这样找到的是 pa *ppa 其实访问的就是 pa。

**ppa = 30;
//等价于*pa = 30
//等价于a = 30

 **ppa 先通过 *ppa 找到 pa ,然后对 pa 进⾏解引⽤操作: *pa ,那找到的是 a 。

6.指针数组

6.1指针数组概念

指针数组是指针还是数组?
我们类比⼀下,整型数组,是存放整型的数组,字符数组是存放字符的数组。
那指针数组呢?是存放指针的数组。

 这是一个典型的指针数组,他的每一个元素都是地址,只想某一块区域。

6.2指针数组模拟二维数组

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* arr[3] = { arr1,arr2,arr3 };for (int i = 0; i < 3; i++){for (int j = 0; j < 5; j++){printf("%d ", arr[i][j]);//printf("%d ", *(*(arr+i)+j));//两行效果一致}printf("\n");}return 0;
}

arr[i]是访问arr数组中的元素,arr[i]找到的数组元素指向了一个一维整型数组,所以arr[i][j]就是一维整型数组的元素。

要注意,上述的代码虽然模拟出了二维数组的效果,实际上却不是真正的二维数组,因为每一行并非是连续的。

相关文章:

C语言超详细指针知识(二)

在上一篇有关指针的博客中&#xff0c;我们介绍了指针的基础知识&#xff0c;如&#xff1a;内存与地址&#xff0c;解引用操作符&#xff0c;野指针等&#xff0c;今天我们将更加深入的学习指针的其他知识。 1.指针的使用和传址调用 1.1strlen的模拟实现 库函数strlen的功能是…...

华为机试—最大最小路

题目 对于给定的无向无根树&#xff0c;第 i 个节点上有一个权值 wi​ 。我们定义一条简单路径是好的&#xff0c;当且仅当&#xff1a;路径上的点的点权最小值小于等于 a &#xff0c;路径上的点的点权最大值大于等于 b 。 保证给定的 a<b&#xff0c;你需要计算有多少条简…...

[Linux]从零开始的ARM Linux交叉编译与.so文件链接教程

一、前言 最近在项目需要将C版本的opencv集成到原本的代码中从而进行一些简单的图像处理。但是在这其中遇到了一些问题&#xff0c;首先就是原本的opencv我们需要在x86的架构上进行编译然后将其集成到我们的项目中&#xff0c;这里我们到底应该将opencv编译为x86架构的还是编译…...

【模板】缩点

洛谷p3387 思路: 算法:tarjan算法 根据题意,我们只要找到一个路径,使得最终权重最大即可,首先,根据题目可知,如果一个点在一个环上,那么我们就将这整个环都选上,题目上允许我们能够重复走,因此,我们可以将环缩成点,将环所称点后,就可以转换成树,从没有父节点的结点开始,我们向…...

Rag实现流程

Rag实现流程 目录 Rag实现流程1. 加载问答链代码解释`chain_type="stuff"` 的含义其他 `chain_type` 参数选项及特点1. `map_reduce`2. `refine`3. `map_rerank`示例代码展示不同 `chain_type` 的使用其他参数类型2. 提出问题3. 检索相关文档代码解释其他参数类型4. …...

计算机网络- 传输层安全性

传输层安全性 7. 传输层安全性7.1 传输层安全基础7.1.1 安全需求机密性&#xff08;Confidentiality&#xff09;完整性&#xff08;Integrity&#xff09;真实性&#xff08;Authenticity&#xff09;不可否认性&#xff08;Non-repudiation&#xff09; 7.1.2 常见安全威胁窃…...

常青藤快速选择系统介绍

功能特点 支持多种属性和特性&#xff1a;可依据实体属性&#xff08;如实体类型、图层、颜色、线宽等&#xff09;以及实体特性&#xff08;如直线长度、圆面积、文字内容等&#xff09;进行筛选。多过滤条件与运算符号&#xff1a;支持多个过滤条件组合&#xff0c;基本涵盖实…...

【c语言】指针习题

练习一&#xff1a;使用指针打印数组内容 #include <stdio.h> void print(int* p, int sz) {int i 0;for (i 0; i < sz; i) {printf("%d ", *p);//printf("%d ", *(p i));} } int main() {int arr[] { 1,2,3,4,5,6,7,8,9,10 };int sz sizeof…...

KWDB创作者计划—KWDB认知引擎:数据流动架构与时空感知计算的范式突破

引言&#xff1a;数据智能的第三范式 在数字化转型进入深水区的2025年&#xff0c;企业数据系统正面临三重悖论&#xff1a;数据规模指数级增长与实时决策需求之间的矛盾、多模态数据孤岛与业务连续性要求之间的冲突、静态存储范式与动态场景适配之间的鸿沟。KWDB&#xff08;K…...

Sqoop常用指令

Sqoop&#xff08;SQL-to-Hadoop&#xff09;是一个开源工具&#xff0c;旨在将关系型数据库中的数据导入到Hadoop的HDFS中&#xff0c;或者从HDFS导出到关系型数据库中。以下是一些常用的Sqoop命令&#xff1a; 导入数据到HDFS 1. 基本导入 sqoop import \ --connect jdbc:mys…...

银行业务知识序言

银行业务知识体系全景解析 第一章 金融创新浪潮下的银行业务知识革命 1.1 数字化转型驱动金融业态重构 在区块链、人工智能、物联网等技术的叠加作用下&#xff0c;全球银行业正经历着"服务无形化、流程智能化、风控穿透化"的深刻变革。根据麦肯锡《2023全球银行业…...

智慧水务项目(八)基于Django 5.1 版本PyScada详细安装实战

一、说明 PyScada&#xff0c;一个基于Python和Django框架的开源SCADA&#xff08;数据采集与监视控制系统&#xff09;系统&#xff0c;采用HTML5技术打造人机界面&#xff08;HMI&#xff09;。它兼容多种工业协议&#xff0c;如Modbus TCP/IP、RTU、ASCII等&#xff0c;并具…...

畅游Diffusion数字人(23):字节最新表情+动作模仿视频生成DreamActor-M1

畅游Diffusion数字人(0):专栏文章导航 前言:之前有很多动作模仿或者表情模仿的工作,但是如果要在实际使用中进行电影级的复刻工作,仅仅表情或动作模仿还不够,需要表情和动作一起模仿。最近字节跳动提出了一个表情+动作模仿视频生成DreamActor-M1。 目录 贡献概述 核心动…...

【Unity网络编程知识】C#的 Http相关类学习

1、搭建HTTP服务器 使用别人做好的HTTP服务器软件&#xff0c;一般作为资源服务器时使用该方式&#xff08;学习阶段建议使用&#xff09;自己编写HTTP服务器应用程序&#xff0c;一般作为Web服务器或者短连接游戏服务器时使用该方式&#xff08;工作后由后端程序员来做&#…...

Python operator 模块介绍

operator 模块是 Python 标准库中的一个模块,它提供了一系列与 Python 内置运算符对应的函数。这些函数可以用于替代一些常见的运算符操作,在某些场景下能让代码更加简洁、高效,还能方便地用于函数式编程。以下是对 operator 模块的详细介绍: 1. 导入模块 使用 operator …...

SpringBoot企业级开发之【用户模块-更新用户头像】

功能如下所示&#xff1a; 我们先看一下接口文档&#xff1a; 为什么头像是一串字符串呢&#xff1f;因为我们是将头像图片放到第三方去存储&#xff0c;比如&#xff1a;阿里云等 开发思路&#xff1a; 实操&#xff1a; 1.controller 注意!这里使用【PatchMapping】注解…...

DAPP实战篇:使用ethersjs连接智能合约并输入地址查询该地址余额

本系列目录 专栏:区块链入门到放弃查看目录-CSDN博客文章浏览阅读400次。为了方便查看将本专栏的所有内容列出目录,按照顺序查看即可。后续也会在此规划一下后续内容,因此如果遇到不能点击的,代表还没有更新。声明:文中所出观点大多数源于笔者多年开发经验所总结,如果你…...

网络流量管理-流(Flow)

1. 传统网络的问题&#xff1a;快递员送信模式 想象你每天要寄100封信给同一个朋友&#xff0c;传统网络的处理方式就像一个固执的快递员&#xff1a; 每封信都单独处理&#xff1a;检查地址、规划路线、盖章、装车…即使所有信的目的地、收件人都相同&#xff0c;也要重复100…...

每日文献(十一)——Part two

今天从第四章&#xff1a;快速RCNN&#xff0c;方法细节开始介绍。 目录 四、快速RCNN&#xff1a;方法细节 4.1 快速R-CNN回顾 4.2 对抗网络设计 4.2.1 遮挡的对抗空间信息损失 4.2.2 对抗空间Transformer网络 4.2.3 对抗融合 五、实验 5.1 实验设置 5.2 PASCAL VOC…...

Laravel 实现 队列 发送邮件功能

一. 什么是队列 在构建 Web 应用程序时&#xff0c;你可能需要执行一些任务&#xff0c;例如解析文件&#xff0c;发送邮件&#xff0c;大量的数据计算等等&#xff0c;这些任务在典型的 Web 请求期间需要很长时间才能执行。 庆幸的是&#xff0c;Laravel 可以创建在后台运行…...

一、绪论(Introduction of Artificial Intelligence)

写在前面&#xff1a; 老师比较看重的点&#xff1a;对问题的概念本质的理解&#xff0c;不会考试一堆运算的东西&#xff0c;只需要将概念理解清楚就可以&#xff0c;最后一个题会出一个综合题&#xff0c;看潜力&#xff0c;前面的部分考的不是很深&#xff0c;不是很难&…...

Web攻防—SSRF服务端请求伪造Gopher伪协议无回显利用

前言 重学Top10的第二篇&#xff0c;希望各位大佬不要见笑。 SSRF原理 SSRF又叫服务端请求伪造&#xff0c;是一种由服务端发起的恶意请求&#xff0c;SSRF发生在应用程序允许攻击者诱使服务器向任意域或资源发送未经授权的请求时。服务器充当代理&#xff0c;执行攻击者构造…...

2025蓝桥杯python A组题解

真捐款去了&#xff0c;好长时间没练了&#xff0c;感觉脑子和手都不转悠了。 B F BF BF 赛时都写假了&#xff0c; G G G 也只写了爆搜。 题解其实队友都写好了&#xff0c;我就粘一下自己的代码&#xff0c;稍微提点个人的理解水一篇题解 队友题解 B 思路&#xff1a; 我…...

使用Python建模量子隧穿

引言 量子隧穿是量子力学中的一个非常有趣且令人神往的现象。在经典物理学中,我们通常认为粒子必须克服一个势垒才能通过它。但是,在量子力学中,粒子有时可以“穿越”一个势垒,即使它的能量不足以克服这个势垒。这种现象被称为“量子隧穿”。今天,我们将通过 Python 来建…...

微信小程序开发常用语法和api

vue写习惯了&#xff0c;小程序太久不做&#xff0c;一些语法和api都忘记。本文总结下小程序常用的语法和api 语法 绑定事件和传参 绑定事件还有很多&#xff0c;触摸反馈事件&#xff0c;表单事件&#xff0c;媒体事件后续更新细说。 <!-- 绑定事件 bindtap 事件传参 da…...

【时时三省】(C语言基础)选择结构程序综合举例

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 下面综合介绍几个包含选择结构的应用程序。 例题1&#xff1a; 写一程序&#xff0c;判断某一年是否为闰年。 程序1&#xff1a; 先画出判别闰年算法的流程图&#xff0c;见下图用变量le…...

Redis实现分布式定时任务

设计思路 任务表示&#xff1a;每个任务通过一个特定格式的键来表示。键名可以包含任务ID等信息&#xff0c;值可以是任务的具体内容或指向任务详情的引用。过期机制&#xff1a;利用Redis的EXPIRE命令为任务设置过期时间&#xff0c;当到达设定的时间点时&#xff0c;Redis会…...

File 类 (文件|文件夹操作)

一、File 类 1.1 前言 在 JDK 中 通过 java.io.File 类&#xff0c;可以实现操作系统重文件|文件夹的创建、删除、查看、重命名等操作。 1.2 File 类构造方法 File 一共提供了四个构造方法&#xff0c;都是有参构造。其中最常使用的是 File(String) 和 File(String, String)…...

Html页面Table表格导出导入Excel文件 xlsx.full

Html页面Table表格导出Excel文件 引用 xlsx.full.min.js 文件 导出 <!DOCTYPE html> <html> <head><meta charset"utf-8" /><title></title><script src"https://cdn.bootcdn.net/ajax/libs/jquery/3.6.3/jquery.min.j…...

【资料分享】瑞芯微RK3576,8核2.2GHz+6T算力NPU工业核心板说明书

核心板简介 创龙科技SOM-TL3576-S是一款基于瑞芯微RK3576J/RK3576高性能处理器设计的4核ARM Cor...