C语言【指针篇】(一)

前言

指针基础概念理解,从底层出发理解指针

C语言【指针篇】(一)

    • 前言
    • 正文
      • 1. 内存和地址
        • 1.1 内存
        • 1.2 究竟该如何理解编址
      • 2. 指针变量和地址
        • 2.1 取地址操作符(&)
        • 2.2 指针变量和解引用操作符(*)
        • 2.3 指针变量的大小
      • 3. 指针变量类型的意义
        • 3.1 指针的解引用
        • 3.2 指针+-整数
        • 3.3 void* 指针
      • 4. 指针运算
        • 4.1 指针+-整数
        • 4.2 指针 - 指针
        • 4.3 指针的关系运算
    • 总结

正文

1. 内存和地址

1.1 内存

在讲解内存和地址前,先看一个生活案例。假设有一栋宿舍楼,楼里有100个房间但未编号,朋友来找你时,若想找到你就得挨个房间找,效率很低。但如果给每个房间按楼层和房间情况编号,如一楼为101、102、103……二楼为201、202、203……有了房间号,朋友就能快速找到房间和你。

在计算机中,CPU处理数据时,数据在内存中读取,处理后也放回内存。我们买电脑时,内存有8GB、16GB、32GB等不同规格。为高效管理这些内存空间,会把内存划分为一个个内存单元,每个内存单元大小为1个字节。

计算机中常见单位:

  • 一个比特位可以存储一个二进制的位1或者0。
  • 单位换算:
    • 1Byte = 8bit
    • 1KB = 1024Byte
    • 1MB = 1024KB
    • 1GB = 1024MB
    • 1TB = 1024GB
    • 1PB = 1024TB

每个内存单元就像一个学生宿舍,一个字节空间能放8个比特位,好比八人间,每个人是一个比特位。每个内存单元都有编号,这个编号就像宿舍房间的门牌号,有了它,CPU就能快速找到内存空间。在生活中我们把门牌号叫地址,在计算机中把内存单元的编号称为地址,C语言中给地址起了新名字叫指针。
所以可以理解为:内存单元的编号 == 地址 == 指针

1.2 究竟该如何理解编址

在这里插入图片描述

计算机内有很多硬件单元,它们需协同工作,协同工作至少要能相互进行数据传递。硬件之间相互独立,通过“线”连接实现通信。CPU和内存之间有大量数据交互,也用线连接,这里我们关心一组线——地址总线。

CPU访问内存中的某个字节空间,必须知道该字节空间的位置,由于内存中字节很多,所以需要给内存编址,就像给很多宿舍编号一样。计算机中的编址并非记录每个字节的地址,而是通过硬件设计完成的。

简单理解,32位机器有32根地址总线,每根线只有两态,即表示0、1(电脉冲有无)。一根线能表示2种含义,2根线能表示4种含义,依此类推,32根地址线能表示2^32种含义,每种含义都代表一个地址。地址信息下达给内存后,就能找到对应数据,并通过数据总线传入CPU内寄存器。

2. 指针变量和地址

2.1 取地址操作符(&)

在C语言中创建变量就是向内存申请空间。例如:

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

若想得到a的地址,需学习取地址操作符(&)。示例代码如下:

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

&a取出的是a所占4个字节中地址较小的字节的地址,虽然整型变量占用4个字节,但知道第一个字节地址,就能访问到4个字节的数据。

2.2 指针变量和解引用操作符(*)

指针变量也是一种变量,专门用来存放地址,存放在指针变量中的值都会被理解为地址。

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

指针变量的类型可按如下方式理解:如int *pa*说明pa是指针变量,前面的int说明pa指向的是整型(int)类型的对象。
在这里插入图片描述

若有char类型变量ch,存放ch地址的指针变量类型应为char *

char ch = 'w';
char *pc = &ch;

将地址保存起来后,在C语言中可通过解引用操作符(*)使用地址。示例代码如下:

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

在上述代码中,*pa表示通过pa中存放的地址找到指向的空间,*pa其实就是a变量,所以*pa = 0就是把a改成了0。使用指针修改a的值,为修改a提供了另一种途径,使代码编写更灵活。

2.3 指针变量的大小

32位机器假设有32根地址总线,一个地址是32个bit位,需4个字节存储,所以指针变量大小是4个字节。64位机器假设有64根地址线,一个地址由64个二进制位组成,存储需要8个字节,指针变量大小就是8个字节。通过以下代码可验证:

#include <stdio.h>
//指针变量的大小取决于地址的大小
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{printf("%zd\n", sizeof(char *));printf("%zd\n", sizeof(short *));printf("%zd\n", sizeof(int *));printf("%zd\n", sizeof(double *));return 0;
}

在X86环境(32位)下输出结果均为4,在X64环境(64位)下输出结果均为8。由此可得结论:32位平台下指针变量大小是4个字节;64位平台下指针变量大小是8个字节;指针变量的大小和类型无关,只要是指针类型的变量,在相同平台下,大小都相同。

3. 指针变量类型的意义

指针变量大小和类型无关,但指针类型有特殊意义。

3.1 指针的解引用

对比以下两段代码,调试时观察内存变化:

//代码1
#include <stdio.h>
int main()
{int n = 0x11223344;int *pi = &n; *pi = 0;return 0;
}
//代码2
#include <stdio.h>
int main()
{int n = 0x11223344;char *pc = (char *)&n;*pc = 0;return 0;
}

调试发现,(如何调试的文章马上补上)代码1会将n的4个字节全部改为0,代码2只是将n的第一个字节改为0。由此可得结论:指针的类型决定了对指针解引用时的权限,即一次能操作几个字节。char*的指针解引用只能访问一个字节,int*的指针解引用能访问四个字节。

3.2 指针±整数

通过以下代码调试观察地址变化:

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

运行结果

运行结果显示,char*类型的指针变量+1跳过1个字节,int*类型的指针变量+1跳过了4个字节。这表明指针变量的类型差异会带来这样的变化,指针+1其实跳过1个指针指向的元素,指针也可以-1。
结论:指针的类型决定了指针向前或向后走一步的距离。

3.3 void* 指针

在指针类型中,void *类型比较特殊,可理解为无具体类型的指针(也叫泛型指针),这种类型的指针能接受任意类型地址,但有局限性,不能直接进行指针的±整数和解引用运算。

#include<stdio.h>
int main()
{int a = 10;//int* pa = &a;void* pv = &a;////*pv = 20;//错误//pv++;//错误*(int*)pv = 20;//对return 0;
}

例如,将一个int类型变量的地址赋值给一个char*类型的指针变量时,编译器会给出警告,因为类型不兼容。
而使用void*类型则不会有此问题。示例代码如下:

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

使用void*类型指针接收地址:

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

上述代码编译时会报错,显示非法的间接寻址,报错
这表明void*类型的指针虽能接收不同类型的地址,但无法直接进行指针运算。void*类型的指针一般用在函数参数部分,接收不同类型数据的地址,实现泛型编程效果,使一个函数能处理多种类型的数据,在后续文章也有讲解

4. 指针运算

指针的基本运算有三种:指针±整数、指针-指针、指针的关系运算。

4.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;
}

当然循环内也可以改为:

printf("%d ", *p );
p++;

上面是加的实例,那么指针-整数可以吗?当然可以

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

(1)指向同一块空间
(2)得到两个指针的元素个数
下面代码演示自己写strlen函数
方法一:逐个计数

//指针-指针
#include <stdio.h>
size_t my_strlen(char *p)
{//注释的也是一种小方法,如有误请指正/*int sz = sizeof(arr) - sizeof(arr[0]);return &arr[sz - 1] - &arr[0];*/size_t cnt = 0;while (*p != '\0'){cnt++;p++;}return cnt;
}int main()
{char arr[] = "abcdef";size_t len = my_strlen(arr);printf("%zd", len);return 0;
}

方法二:指针相减

#include <stdio.h>
size_t my_strlen(char *p)
{char* q = p;while (*q != '\0'){q++;}return q - p;//指针 - 指针 
}
int main()
{char arr[] = "abcdef";size_t len = my_strlen(arr);printf("%zd", len);return 0;
}
4.3 指针的关系运算
使用指针打印数组内容,但通过指针关系实现
//指针的关系运算
int main()
{int arr[] = { 0,1,2,3,4,5,6,7,8,9 };int sz = sizeof(arr) / sizeof(arr[0]);int* p = &arr[0];while (p < &arr[sz])//指针比较大小{printf("%d ", *p);p++;}return 0;
}

总结

本文是指针最基础的部分,新的概念比较多,需要仔细理解,后续将逐步进阶,达到深入理解并熟练掌握的程度,希望可以点赞关注订阅支持一下,谢谢。

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

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

相关文章

【每日八股】Redis篇(二):数据结构

Redis 数据类型&#xff1f; 主要有 STRING、LIST、ZSET、SET 和 HASH。 STRING String 类型底层的数据结构实现主要是 SDS&#xff08;简单动态字符串&#xff09;&#xff0c;其主要应用场景包括&#xff1a; 缓存对象&#xff1a;可以用 STRING 缓存整个对象的 JSON&…

文章精读篇——用于遥感小样本语义分割的可学习Prompt

题目&#xff1a;Learnable Prompt for Few-Shot Semantic Segmentation in Remote Sensing Domain 会议&#xff1a;CVPR 2024 Workshop 论文&#xff1a;10.48550/arXiv.2404.10307 相关竞赛&#xff1a;https://codalab.lisn.upsaclay.fr/competitions/17568 年份&#…

游戏引擎学习第119天

仓库:https://gitee.com/mrxiao_com/2d_game_3 上一集回顾和今天的议程 如果你们还记得昨天的进展&#xff0c;我们刚刚完成了优化工作&#xff0c;目标是让某个程序能够尽可能快速地运行。我觉得现在可以说它已经快速运行了。虽然可能还没有达到最快的速度&#xff0c;但我们…

HybridCLR+Adressable+Springboot热更

本文章会手把手教大家如何搭建HybridCLRAdressableSpringboot热更。 创作不易&#xff0c;动动发财的小手点个赞。 安装华佗 首先我们按照官网的快速上手指南搭建一个简易的项目&#xff1a; 快速上手 | HybridCLR 注意在热更的代码里添加程序集。把用到的工具放到程序集里…

多无人机协同路径规划(论文+仿真)

在现代技术的快速发展下&#xff0c;飞行器的种类也越来越多了&#xff0c;他们的应用场景和应用功能也越来越完善和复杂。举例来说&#xff0c;ps-x625型号就是大疆无人机生产的就是在植物保护方面有很好的应用&#xff0c;宝鸡的兴义生产的X8型号无人机在航空领域有很大突破&…

CentOS环境变量配置+解析

环境变量的作用就是让系统快速通过你的命令找到你的可执行程序&#xff0c;windows系统里也同理&#xff0c;也就是你每次输入个命令&#xff0c;系统就会找环境变量里到底有没有叫这个命令进程的 一、环境变量配置 1.编辑配置文件 vim /etc/profile export PATH$PATH:$JAVA…

einops测试

文章目录 1. einops2. code3. pytorch 1. einops einops 主要是通过爱因斯坦标记法来处理张量矩阵的库&#xff0c;让矩阵处理上非常简单。 conda : conda install conda-forge::einopspython: 2. code import torch import torch.nn as nn import torch.nn.functional as…

Unity教程(二十一)技能系统 基础部分

Unity开发2D类银河恶魔城游戏学习笔记 Unity教程&#xff08;零&#xff09;Unity和VS的使用相关内容 Unity教程&#xff08;一&#xff09;开始学习状态机 Unity教程&#xff08;二&#xff09;角色移动的实现 Unity教程&#xff08;三&#xff09;角色跳跃的实现 Unity教程&…

Docker:Docker从入门到精通(一)- Docker简介

一、前言 通过本专栏的学习&#xff0c;我们将了解   1. 掌握Docker基础知识&#xff0c;能够理解Docker镜像与容器的概念   2. 完成Docker安装与启动   3. 掌握Docker镜像与容器相关命令   4. 掌握Tomcat Nginx 等软件的常用应用的安装   5. 掌握docker迁移与备份相…

单机上使用docker搭建minio集群

单机上使用docker搭建minio集群 1.集群安装1.1前提条件1.2步骤指南1.2.1安装 Docker 和 Docker Compose&#xff08;如果尚未安装&#xff09;1.2.2编写docker-compose文件1.2.3启动1.2.4访问 2.使用2.1 mc客户端安装2.2创建一个连接2.3简单使用下 这里在ubuntu上单机安装一个m…

Image Downloader下载文章图片的WordPress插件

源码介绍 一个用于下载图片的WordPress插件&#xff0c;包含下载统计功能&#xff0c;支持任何主题使用 用户点击下载后自动打包该文章所有原始图片&#xff0c;并把文章标题作为压缩包的文件名。 不占用服务器空间&#xff0c;也不占网盘空间&#xff0c;直接利用浏览器的性…

PLC通讯

PPI通讯 是西门子公司专为s7-200系列plc开发的通讯协议。内置于s7-200 CPU中。PPI协议物理上基于RS-485口&#xff0c;通过屏蔽双绞线就可以实现PPI通讯。PPI协议是一种主-从协议。主站设备发送要求到从站设备&#xff0c;从站设备响应&#xff0c;从站不能主动发出信息。主站…

VScode+stfp插件,实现文件远程同步保存【2025实操有效】

目录 1 痛点2 准备工作3 操作步骤3.1 第一步&#xff0c;下载STFP插件3.2 第二步&#xff0c;修改配置文件3.3 第三步&#xff0c;测试是否成功 4 后记 1 痛点 我一直用vscode远程连接服务器&#xff0c;传代码文件等到服务器上面&#xff0c;突然有一次服务器那边尽心维修&am…

【quicker】调节PPT指定字号字体大小/快速调节WPS的PPT字体大小

在quicker的拓展动作中找不到直接指定字号大小方式的动作。 换个思路&#xff0c;既然无法通过alt键模拟&#xff0c;不如模拟右键菜单触发&#xff1f;尝试过失败了 所以有了第三种方法 &#xff0c;首先给字体窗口设置快捷键&#xff0c;此处设置的是altshiftf&#xff0c;然…

Grouped-Query Attention(GQA)详解: Pytorch实现

Grouped-Query Attention&#xff08;GQA&#xff09;详解 Grouped-Query Attention&#xff08;GQA&#xff09; 是 Multi-Query Attention&#xff08;MQA&#xff09; 的改进版&#xff0c;它通过在 多个查询头&#xff08;Query Heads&#xff09;之间共享 Key 和 Value&am…

百度百舸 DeepSeek 一体机发布,支持昆仑芯 P800 单机 8 卡满血版开箱即用

在私有云环境中成功部署 DeepSeek 满血版并实现性能调优&#xff0c;并不是一件容易的事情。选择合适的 GPU 配置、安装相应的环境、成功部署上线业务、加速推理任务加速、支撑多用户并发 …… 完成业务测试&#xff0c;成功融入生产业务中。 为了帮助企业快速实现 DeepSeek 服…

c++入门-------命名空间、缺省参数、函数重载

C系列 文章目录 C系列前言一、命名空间二、缺省参数2.1、缺省参数概念2.2、 缺省参数分类2.2.1、全缺省参数2.2.2、半缺省参数 2.3、缺省参数的特点 三、函数重载3.1、函数重载概念3.2、构成函数重载的条件3.2.1、参数类型不同3.2.2、参数个数不同3.2.3、参数类型顺序不同 前言…

tortoiseGit的使用和上传拉取

tortoiseGit的使用和上传拉取 下载TortoiseGit 通过网盘分享的文件&#xff1a;tortoiseGit.zip 链接: https://pan.baidu.com/s/1EOT_UsM9_OysRqXa8gES4A?pwd1234 提取码: 1234 在电脑桌面新建文件夹并进入 右击鼠标 将网址复制上去 用户名和密码是在git注册的用户名和…

Mybatis学习总结

官网 概念 用于简化JDBC的开发。 在配置mybatis的时候如果没有建立连接识别不了信息&#xff0c;我们需要在idea配置mysql的配置信息 JDBC是一套操作关系数据库的API&#xff0c;有效率&#xff0c;和mybatis比起来资源节约&#xff0c;性能高&#xff0c;不繁琐。 数据库连…

SQL笔记#数据更新

一、数据的插入(INSERT语句的使用方法) 1、什么是INSERT 首先通过CREATE TABLE语句创建表&#xff0c;但创建的表中没有数据&#xff1b;再通过INSERT语句向表中插入数据。 --创建表ProductIns CREATE TABLE ProductIns (product_id CHAR(4) NOT NULL,product_name …