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.rhkb.cn/news/32849.html

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

相关文章

【Linux】线程

文章目录 线程&#xff08;Thread&#xff09;1. 什么是线程&#xff1f; 创建线程多线程中的重入问题线程异常线程等待总结 线程&#xff08;Thread&#xff09; 1. 什么是线程&#xff1f; 线程是进程中的一个执行单元&#xff0c;它是 CPU 调度的基本单位。线程依赖于进程…

SpringBoot第二天

目录 1.Web开发 1.1简介 1.2SpringBoot对静态资源的映射规则 1.3模板引擎 1.3.1引入thymeleaf&#xff1b; 1.3.2Thymeleaf语法 1.3.2.1标准表达式语法 1.变量表达式 1.3.2.2表达式支持的语法 1.3.2.3常用的thymeleaf标签 1.4Springboot整合springmvc 1.4.1Springmvc…

如何接入DeepSeek布局企业AI系统开发技术

在当今科技飞速发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;已成为企业提升竞争力、实现创新突破的关键驱动力。DeepSeek作为一款强大的AI工具&#xff0c;为企业开发自身AI系统提供了有力支持。那么&#xff0c;企业该如何接入DeepSeek进行AI系统开发呢&#xf…

日期累加(注意点)

注意点&#xff1a;①月可能超过12月 ②新年需要重新判断闰年 日期累加 #include <stdio.h>int pd(int year) {return (year % 4 0 && year % 100 ! 0) || (year % 400 0); }int main() {int m;int year, month, day, add;scanf("%d", &m);f…

vue3 前端路由权限控制与字典数据缓存实践(附Demo)

目录 前言1. 基本知识2. Demo3. 实战 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 从实战中出发&#xff1a; 1. 基本知识 Vue3 和 Java 通信时如何进行字典数据管理 需要了解字典数据的结构。通常&#…

用于 RGB-D 显著目标检测的点感知交互和 CNN 诱导的细化网络

摘要 通过整合来自RGB图像和深度图的互补信息&#xff0c;能够提升在复杂且具有挑战性场景下的显著性目标检测&#xff08;SOD&#xff09;能力。近年来&#xff0c;卷积神经网络&#xff08;CNNs&#xff09;在特征提取和跨模态交互方面的重要作用已得到充分挖掘&#xff0c;但…

基于SpringBoot的“校园周边美食探索及分享平台”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“校园周边美食探索及分享平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 校园周边美食探索及分享平台结构图…

chrome浏览器插件拓展捕获页面的响应体内容

因为chrome extension官方没有的直接获取响应体的方法&#xff0c;所以需要自己实现方法来获取&#xff0c;实现的方式有很多种&#xff0c;这是记录的第二种&#xff0c;第一种就是使用vconsole来实现&#xff0c;vconsole是一个开源框架&#xff0c;一个轻量、可拓展、针对手…

【Linux指北】Linux的重定向与管道

一、了解Linux目录配置标准FHS FHS本质&#xff1a;是一套规定Linux目录结构&#xff0c;软件建议安装位置的标准。 (使用Linux来开发产品或者发布软件的公司、个人太多&#xff0c;如果每家公司或者个人都按照自己的意愿来配置文件或者软件的存放位置&#xff0c;这无疑是一…

Qt6.8.2中JavaScript调用WebAssembly的js文件<1>

前段时间已经学习了如何在QtAssembly中编译FFmpeg资源了&#xff0c;接下来需要使用Html来调用QtCreator中WebAssembly套件写的功能&#xff0c;逐步实现javascrpt与c复杂功能的视线。 接下来我先为大家介绍一个非常简单的加法调用吧&#xff01; 功能讲解 开发环境&#xf…

3.13-进程

进程 进程和程序 程序&#xff1a;编译好的二进制文件&#xff0c;不占用系统资源&#xff08;内存&#xff09;。进程&#xff1a;活跃的程序&#xff0c;不消耗系统图资源&#xff08;内存&#xff09;。 MMU PCB 进程控制块 本质&#xff1a;结构体&#xff1a;struct …

在 CentOS 7 上安装 PHP 7.3

在 CentOS 7 上安装 PHP 7.3 可以按照以下步骤进行操作&#xff1a; 1. 安装必要的依赖和 EPEL 仓库 EPEL&#xff08;Extra Packages for Enterprise Linux&#xff09;是为企业级 Linux 提供额外软件包的仓库&#xff0c;yum-utils 用于管理 yum 仓库。 sudo yum install -…

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

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

C++从入门到入土(七)——多态

目录 前言 多态的概念 多态的定义 虚函数的介绍 虚函数的重写/覆盖 析构函数的重写 override和final关键字 纯虚函数和抽象类 重写/重载/隐藏总结 多态的原理 小结 前言 C一共有三个特性&#xff0c;封装、继承和多态&#xff0c;在前面的文章中&#xff0c;我们分别…

浅谈时钟启动和Systemlnit函数

时钟是STM32的关键&#xff0c;是整个系统的心脏&#xff0c;时钟如何启动&#xff0c;时钟源如何选择&#xff0c;各个参数如何设置&#xff0c;我们从源码来简单分析一下时钟的启动函数Systemlnit&#xff08;&#xff09;。 Systemlnit函数简介 我们先来看一下源程序的注释…

【数据结构】6栈

0 章节 3&#xff0e;1到3&#xff0e;3小节。 认知与理解栈结构&#xff1b; 列举栈的操作特点。 理解并列举栈的应用案例。 重点 栈的特点与实现&#xff1b; 难点 栈的灵活实现与应用 作业或思考题 完成学习测试&#xff12;&#xff0c;&#xff1f; 内容达成以下标准(考核…

HOT100——链表篇Leetcode160. 相交链表

文章目录 题目&#xff1a;Leetcode160. 相交链表原题链接思路代码 题目&#xff1a;Leetcode160. 相交链表 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表…

江科大51单片机笔记【16】AD/DA转换(下)

写在前言 此为博主自学江科大51单片机&#xff08;B站&#xff09;的笔记&#xff0c;方便后续重温知识 在后面的章节中&#xff0c;为了防止篇幅过长和易于查找&#xff0c;我把一个小节分成两部分来发&#xff0c;上章节主要是关于本节课的硬件介绍、电路图、原理图等理论知识…

【CF】Day5——Codeforces Round 921 (Div. 2) BC

B. A Balanced Problemset? 题目&#xff1a; 思路&#xff1a; 这道题要我们分成n个子问题&#xff0c;我们假设这几个子问题分别是a1,a2,a3,...an&#xff0c; 那么就是让我们求 gcd(a1,a2,a3,....,an)&#xff0c;我们假设这个值是d 那么就有 d | a1&#xff0c;d | a2…

Mininet 自定义拓扑类型详解

Mininet 通过 --topo 参数支持多种自定义网络拓扑结构&#xff0c;适用于不同场景的网络模拟需求。以下是所有内置拓扑类型及其参数说明&#xff1a; 一、基础拓扑类型 拓扑类型参数格式说明示例命令singlesingle,<n>单一交换机连接所有主机&#xff08;默认 2 台主机&a…