C语言 —— 此去经年梦浪荡魂音 - 深入理解指针(卷一)

目录

1. 内存和地址

2. 指针变量和地址

2.1 取地址操作符(&)

2.2 指针变量

2.3 解引用操作符 (*)

3. 指针的解引用

3.1 指针 + - 整数

3.2 void* 指针

4. const修饰指针

4.1 const修饰变量

4.2 const修饰指针变量

5. 指针运算

5.1 指针 ± 整数

5.2指针 - 指针

5.3 指针的关系运算

6. 野指针

6.1 野指针成因

6.2 如何规避野指针

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

7.1 strlen的模拟实现

7.2 传值调用和传址调用


1. 内存和地址

什么是内存,我们先举个例子:假设有⼀栋宿舍楼,把你放在楼⾥,楼上有100个房间,但是房间没有编号,你的⼀个朋友来找你玩,如果想找到你,就得挨个房⼦去找,这样效率很低,但是我们如果根据楼层和楼层的房间的情况,给每个房间编上号

  

一楼:101 102 103...
二楼:201 202 203...以此类推...

有了房间号,如果你的朋友得到房间号,就可以快速的找房间找到你

  

将这个例子对应我们的计算机里面就是:

  

CPU在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,这些内存空间是把内存划分为⼀个个的内存单元,每个内存单元的大小取1个字节(1个字节=8 个比特位)

    

每个内存单元就相当于每间酒店房间,每个房间能住 8 个比特位,房间(内存单元)都有一个门牌编号(地址),有了门牌号,就能快速找到相应的房间,即CPU能通过地址快速找到内存空间,在C语言中,给地址起了个名字叫指针

所以我们可以理解为:

  
内存单元的编号 == 地址 == 指针

  

1byte = 8bit
1KB = 1024byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB


2. 指针变量和地址


2.1 取地址操作符(&)

创建变量其实就是向内存申请空间,调试下面代码:

#include <stdio.h>int main()
{int a = 10;&a;//取出a的地址printf("%p\n", &a);return 0;
}

&a取出的是a所占4个字节中地址较⼩的字节的地址也就是第一个地址

  

虽然整型变量占用4个字节,我们只要知道了第⼀个字节地址,顺藤摸⽠访问到4个字节的数据也是可行的


2.2 指针变量

我们通过取地址操作符(&)拿到的地址是⼀个数值,这个数值有时候也是需要存储起来,⽅便后期再使用的,那我们就可以把这样的地址值存放在指针变量

int main()
{int a = 10;int* pa = &a;//取出a的地址并存储到指针变量pa中return 0;
}

指针变量就是用来存放地址的,存放在指针变量中的值都可以当成为地址

2.3 解引用操作符 (*)

我们把地址存储在指针变量后要如何将存放在里面的东西取出使用呢?在知道地址的前提下,可以通过解引用操作符找到指针指向的对象

int main()
{int a = 100;int* p = &a;*p = 0;return 0;
}

上面这段代码就是p 通过解引用(*)找到 a 并将其值改成 0 ,就像是通过门牌号找到特定酒店房间里的特定物品,并将其替换


3. 指针的解引用
 

#include <stdio.h>int main()
{int n = 0x11223344;int* pi = &n;*pi = 0;return 0;
}

调试一下这段代码,代码会将n的4个字节全部改为0,如果把指针类型改成 char ,那么代码只是将n的第⼀个字节改为0

int main()
{int n = 0x11223344;char* pc = (char*)&n;*pc = 0;return 0;
}

结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)

   
⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字 

3.1 指针 + - 整数

#include <stdio.h>int main()
{int n = 10;char* pc = (char*)&n;int* pi = &n;printf("%p\n", &n);printf("%p\n", pc);printf("%p\n", pc + 1);printf("%p\n", pi);printf("%p\n", pi + 1);return 0;
}

char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节

   
也就是说:指针类型决定了指针加减整数时一步走多大距离

3.2 void* 指针

在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进⾏指针的+-整数和解引⽤的运算

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

这⾥我们可以看到, void* 类型的指针可以接收不同类型的地址,但是无法直接进⾏指针运算

   
那么 void* 类型的指针到底有什么⽤呢?

   
⼀般 void* 类型的指针是使⽤在函数参数的部分,⽤来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果。使得⼀个函数来处理多种类型的数据


4. const修饰指针


4.1 const修饰变量

变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量

   

当我们希望⼀个变量加上⼀些限制,不能被修改那么这个时候我们就可以加上一个const

#include <stdio.h>int main()
{int m = 0;m = 20;//m是可以修改的const int n = 0;n = 20;//n是不能被修改的return 0;
}

上述代码中n是不能被修改的,其实n本质是变量,只不过被const修饰后,在语法上加了限制,只要我们在代码中对n就⾏修改,就不符合语法规则,就报错,致使没法直接修改n

但是如果我们绕过n,使⽤n的地址,去修改n就能做到了

#include <stdio.h>int main()
{const int n = 0;printf("n = %d\n", n);int* p = &n;*p = 20;printf("n = %d\n", n);return 0;
}

4.2 const修饰指针变量

当const修饰指针变量的时候:

   
1. const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变,但是指针变量本⾝的内容可变

int const * const p = &a;

 当const放在*的左边时,限制的就是p所指向的内容,也就是&a

2. const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变

当const放在*的右边时,限制的就是p本身


5. 指针运算

  

5.1 指针 ± 整数

因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素

#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){printf("%d ", *(p + i));//p+i 这⾥就是指针+整数}return 0;
}

5.2指针 - 指针

代替 strlen 函数(计算字符或字符串长度),实现一个自定义的函数 my_strlen 来计算输入字符串的长度

#include <stdio.h>int my_strlen(char* s)
{char* p = s;while (*p != '\0')p++;return p - s;
}int main()
{printf("%d\n", my_strlen("abc"));return 0;
}

5.3 指针的关系运算

//指针的关系运算
#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);while (p < arr + sz) //指针的⼤⼩⽐较{printf("%d ", *p);p++;}return 0;
}

指针的关系运算,实际上就是:


6. 野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

6.1 野指针成因


1. 指针未初始化

#include <stdio.h>int main()
{int* p;//局部变量指针未初始化,默认为随机值*p = 20;return 0;
}

2. 指针越界访问

#include <stdio.h>int main()
{int arr[10] = { 0 };int* p = &arr[0];int i = 0;for (i = 0; i <= 11; i++){//当指针指向的范围超出数组arr的范围时,p就是野指针*(p++) = i;}return 0;
}

当循环执行到 i = 10 及之后时,指针 p 已经超出了数组 arr 的范围指向了数组 arr 所占用内存空间之外的未知区域,此时 p 就变成了野指针

3. 指针指向的空间释放

6.2 如何规避野指针

1.对指针变量都进行初始化操作

   
2.注意数组等变量的范围,小心指针越界

   
3.指针不使用时,及时置之为NULL空指针

    
4.不要返回局部变量的地址

 


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

   

7.1 strlen的模拟实现

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

size_t strlen ( const char * str );

参数str接收⼀个字符串的起始地址,然后开始统计字符串中 \0 之前的字符个数,最终返回⻓度

    
如果要模拟实现只要从起始地址开始向后逐个字符的遍历,只要不是 \0 字符,计数器就+1,这样直到 \0 就停⽌ 

strlen链接:

  

strlen - C++ Referencehttps://legacy.cplusplus.com/reference/cstring/strlen/?kw=strlen

int my_strlen(const char* str)
{int count = 0;assert(str);while (*str){count++;str++;}return count;
}int main()
{int len = my_strlen("abcdef");printf("%d\n", len);return 0;
}

7.2 传值调用和传址调用

先写一个普通的代码: 

#include <stdio.h>void Swap1(int x, int y)
{int tmp = x;x = y;y = tmp;
}int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换前:a=%d b=%d\n", a, b);Swap1(a, b);printf("交换后:a=%d b=%d\n", a, b);return 0;
}

我们发现其实没产⽣交换的效果,调试⼀下

Swap1函数在使⽤的时候,是把变量本⾝直接传递给了函数,这种调⽤函数的⽅式我们之前在函数的时候就知道了,值是会出了作用域就会自动销毁,这种叫传值调用

   

实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实

我们使用指针的方法:

void Swap2(int* px, int* py)
{int tmp = 0;tmp = *px;*px = *py;*py = tmp;
}int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换前:a=%d b=%d\n", a, b);Swap1(&a, &b);printf("交换后:a=%d b=%d\n", a, b);return 0;
}

我们可以看到实现成Swap2的⽅式,顺利完成了任务,这⾥调⽤Swap2函数的时候是将变量的地址传递给了函数,这种函数调⽤⽅式叫:传址调用

传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量

    

所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要修改主调函数中的变量的值,就需要传址调⽤ 


完结撒花~ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/72186.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【AI】从头到脚详解如何创建部署Azure Web App的OpenAI项目

【AI】从头到脚详解如何创建部署Azure Web App的OpenAI项目 在Azure Web应用上,您可以使用Python的OpenAI包方便快捷地调用官方API,上传您的训练数据,并利用他们的算法进行处理。本教程提供了一个逐步指南,帮助您在Azure Web应用上部署您的OpenAI项目,涵盖了从资源设置到…

机器视觉工程师红外相机的选择:红外长波工业相机和短波红外工业相机玄机大总结

红外长波(LWIR)和短波(SWIR)工业相机在原理、应用场景和技术特点上有显著差异。以下是它们的对比分析: 1. 波长范围与成像原理 2. 技术特点 3. 典型应用场景 4. 优缺点对比 LWIR优势: 无需光照,适用于完全黑暗环境。 直接反映物体温度分布。 对烟雾、灰尘穿透能力强。…

uni-app学习笔记——自定义模板

一、流程 1.这是一个硬性的流程&#xff0c;只要按照如此程序化就可以实现 二、步骤 1.第一步 2.第二步 3.第三步 4.每一次新建页面&#xff0c;都如第二步一样&#xff1b;可以选择自定义的模版&#xff08;vue3Setup——这是我自己的模版&#xff09;&#xff0c;第二步的…

DeepSeek模型本地化部署方案及Python实现

DeepSeek实在是太火了&#xff0c;虽然经过扩容和调整&#xff0c;但反应依旧不稳定&#xff0c;甚至小圆圈转半天最后却提示“服务器繁忙&#xff0c;请稍后再试。” 故此&#xff0c;本文通过讲解在本地部署 DeepSeek并配合python代码实现&#xff0c;让你零成本搭建自己的AI…

Vue3计算属性深度解析:经典场景与Vue2对比

一、计算属性的核心价值 计算属性&#xff08;Computed Properties&#xff09;是Vue响应式系统的核心特性之一&#xff0c;它通过依赖追踪和缓存机制优雅地解决模板中复杂逻辑的问题。当我们需要基于现有响应式数据进行派生计算时&#xff0c;计算属性总能保持高效的性能表现…

python-leetcode-删除链表的倒数第 N 个结点

LCR 021. 删除链表的倒数第 N 个结点 - 力扣&#xff08;LeetCode&#xff09; 可以使用双指针方法来解决这个问题&#xff0c;这样可以在一次遍历内完成删除操作&#xff0c;从而达到 O(n) 的时间复杂度。以下是 Python 代码实现&#xff1a; 解题思路&#xff1a; 初始化快…

vue2的webpack(vue.config.js) 怎么使用请求转发 devServer.proxy

首先用 express 搭建后端服务器&#xff0c;注意使用中间件解析json格式的请求体&#xff0c;才会获取到 post 参数 app.use(express.json()); app.js const express require(express) const app express() app.use(express.json()); const port 3000app.post(/api/vue2, …

Linux:基本指令与内涵理解

1.文件操作指令 1.1 ls ls指令用于查看指定层级文件夹下的文件或文件夹 基本格式&#xff1a;ls (选项) (查看层级&#xff09; 其中选项处不写就默认是显示文件名&#xff0c;查看层级默认是当前层级 选项1&#xff1a; -l 作用&#xff1a;将查找文件的详细信息显示出来 我们…

SpaceSync智能排班:重构未来办公空间的神经中枢

文心智能体平台可免费使用DeepSeek 满血版啦&#xff0c;使用DeepSeek模型创建并提交智能体&#xff0c;即有机会瓜分万元奖金&#xff01;有这等好事还不快冲&#xff01; 文心智能体官网&#xff1a;文心智能体平台AgentBuilder | 想象即现实 本片文章为作者参加文心智能体平…

flutter dio库 源码赏析

1. factory函数 //调用factory构造方法后&#xff0c;实际返回的是Dio的子类 Dio dio Dio();abstract class Dio {factory Dio([BaseOptions? options]) > createDio(options); } 2. CancelToken 作用:取消操作 CancelToken cancelToken CancelToken();//监听取消 ca…

RGV调度算法

1、基于时间窗 https://wenku.baidu.com/view/470e9fd8b4360b4c2e3f5727a5e9856a57122693.html?_wkts_1741880736197&bdQuery%E7%8E%AF%E7%A9%BF%E8%B0%83%E5%BA%A6%E7%AE%97%E6%B3%95 2.2019年MathorCup高校数学建模挑战赛B题 2019-mathorcupB题-环形穿梭机调度模型&a…

基于CATIA VBA与Python的自动化音乐生成技术对比研究

在工程软件二次开发领域&#xff0c;CATIA 也可以许多另类的玩法。通过CATIA自带的VBA可以演奏歌曲&#xff0c;但实际效果往往差强人意。为了进一步优化实际演奏效果&#xff0c;本文以自动生成林宥嘉《说谎》钢琴前奏旋律为案例&#xff0c;探讨两种语言在多媒体控制领域的技…

最大数位置(信息学奥赛一本通-2038)

【题目描述】 输入n个整数,存放在数组a[1]至a[n]中&#xff0c;输出最大数所在位置(n≤1000)。 【输入】 第一行&#xff0c;数的个数n; 第二行&#xff0c;n个正整数&#xff0c;每个数在232−1之内。 【输出】 最大数所在位置。 【输入样例】 5 67 43 90 78 32 【输出样例】 …

【AIGC】OpenAI 集成 Langchain 操作实战使用详解

目录 一、前言 二、前置准备 2.1 安装 Langchain必须的依赖 2.1.1 python环境 2.1.2 langchain openai 环境 2.1.3 准备一个apikey 2.1.4 langchain 核心组件 三、Langchain 各组件使用 3.1 Chat models组件 3.1.1 Invocation 使用 3.1.1.1 结果解析 3.2 提示词模板…

【C#学习笔记04】深入掌握C语言格式化输出

引言 ​​printf()​​函数不仅可以将数据输出到控制台&#xff0c;还可以通过格式化字符串灵活地控制输出的格式。​​printf()​​​函数的使用规则&#xff0c;包括标志说明、字段宽度、转换精度、长度修饰、转换说明、转义字符和返回结果等内容。 1. ​​printf()​​函数…

python-leetcode-定长子串中元音的最大数目

1456. 定长子串中元音的最大数目 - 力扣&#xff08;LeetCode&#xff09; 可以使用 滑动窗口 方法来解决这个问题。步骤如下&#xff1a; 初始化&#xff1a;计算前 k 个字符中元音字母的个数&#xff0c;作为初始窗口的值。滑动窗口&#xff1a;遍历字符串&#xff0c;每次右…

蓝桥真题讲解

第一题 题目链接 0贪吃蛇长度 - 蓝桥云课 题目解析 题意&#xff1a;数#个数和个数再加上首尾 代码原理 略 代码编写 略 填空题技巧 眼看手数 当然并不是真的一个一个数&#xff0c;我们需要借助一些工具&#xff0c;不过各位小伙伴们放心&#xff0c;我们借助的工具…

【C++ 函数模板】—— 模板参数推导、实例化策略与编译优化

欢迎来到ZyyOvO的博客✨&#xff0c;一个关于探索技术的角落&#xff0c;记录学习的点滴&#x1f4d6;&#xff0c;分享实用的技巧&#x1f6e0;️&#xff0c;偶尔还有一些奇思妙想&#x1f4a1; 本文由ZyyOvO原创✍️&#xff0c;感谢支持❤️&#xff01;请尊重原创&#x1…

Java基础入门流程控制全解析:分支、循环与随机数实战

引言 流程控制是编程语言的核心逻辑结构&#xff0c;决定了程序的执行顺序与逻辑判断能力。本文以 分支结构、循环结构 和 随机数生成 为核心&#xff0c;结合代码示例与底层原理&#xff0c;全面解析Java中流程控制的应用场景与实战技巧。 一、分支结构 1. if分支 作用&am…

Redis 数据持久化之RDB

Redis数据持久化策略 持久化策略之RDB RDB:在指定的时间间隔&#xff0c;执行数据集的时间点快照。 实现类似照片记录效果的方式&#xff0c;就是把某一时刻的数据和状态以文件的形式写到磁盘上&#xff0c;也就是读快照。这样一来即使故障宕机&#xff0c;快照文件也不会丢失&…