文章目录

  • 一、内存和地址
    • 1.1 内存的基本概念
    • 1.2 编址的原理
  • 二、指针变量和地址
    • 2.1 取地址操作符(&)
    • 2.2 指针变量和解引用操作符(*)
      • 2.2.1 指针变量
      • 2.2.2 指针类型的解读
      • 2.2.3 解引用操作符
    • 2.3 指针变量的大小
  • 三、指针变量类型的意义
    • 3.1 影响解引用的权限
    • 3.2 影响指针 ± 整数的步长
    • 3.3 void* 指针
  • 四、const修饰指针
    • 4.1 const修饰变量
    • 4.2 const修饰指针变量
  • 五、指针运算
    • 5.1 指针±整数
    • 5.2 指针-指针
    • 5.3 指针的关系运算
  • 六、野指针
    • 6.1 野指针成因
    • 6.2 如何规避野指针
  • 七、assert断言
  • 八、指针的使用和传址调用
    • 8.1 strlen的模拟实现
    • 8.2 传值调用和传址调用

一、内存和地址

1.1 内存的基本概念

在计算机中,CPU处理数据时需要从内存中读取数据,处理后的数据也会放回内存。为了高效管理内存,内存被划分为一个个大小为1字节的内存单元,每个内存单元都有唯一的编号,这个编号就是我们所说的地址,在C语言中也被称为指针。

举个例子:一栋宿舍楼有100个房间,给每个房间编上号(如101、102等),根据房间号就能快速找到房间。内存就像这栋宿舍楼,每个内存单元就像一个房间,地址则是房间号,有了地址,CPU就能快速找到对应的内存单元。

计算机中常见的存储单位及换算关系如下:

  • 1byte(字节)= 8bit(比特位)
  • 1KB = 1024byte
  • 1MB = 1024KB
  • 1GB = 1024MB
  • 1TB = 1024GB
  • 1PB = 1024TB

1.2 编址的原理

CPU访问内存中的某个字节空间,必须知道该字节空间的地址。计算机的编址是通过硬件设计实现的,就像钢琴、吉他等乐器,制造商在硬件层面设计好,演奏者就能准确找到相应位置。

CPU和内存之间通过大量的线连接,其中一组重要的线是地址总线。32位机器有32根地址总线,每根线有0和1两种状态(表示电脉冲有无),32根地址线能表示2^32种不同的地址。地址信息通过地址总线传给内存,内存根据地址找到对应数据,再通过数据总线传入CPU寄存器。

在这里插入图片描述

二、指针变量和地址

2.1 取地址操作符(&)

在C语言中,创建变量的本质是向内存申请空间

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

变量a占用4个字节的内存空间,每个字节都有自己的地址。我们可以使用取地址操作符&来获取变量的地址,如&a得到的是a所占4个字节中地址较小的那个字节的地址。
在这里插入图片描述

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

2.2.1 指针变量

通过取地址操作符&得到的地址是一个数值,我们可以将其存储在指针变量中。指针变量就是专门用来存放地址的变量

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

2.2.2 指针类型的解读

指针变量的类型由*和前面的类型组成,如int*表示该指针变量指向的是整型(int)类型的对象。对于char类型的变量ch,其地址应存放在char*类型的指针变量中。

2.2.3 解引用操作符

有了指针变量存储地址后,我们可以使用解引用操作符*通过地址找到对应的变量并进行操作。

#include <stdio.h>
int main()
{int a = 100;int* pa = &a;*pa = 0; // 通过pa中存放的地址找到a,并将a的值改为0return 0;
}

这里*pa就相当于变量a,通过*pa可以对a进行修改,这为操作变量提供了另一种途径。

2.3 指针变量的大小

指针变量的大小取决于地址的大小:

  • 在32位平台下,地址是32个比特位,指针变量大小为4个字节。
  • 在64位平台下,地址是64个比特位,指针变量大小为8个字节。

需要注意的是,指针变量的大小和其类型无关,在相同平台下,所有指针类型的变量大小都是相同的。例如:

#include <stdio.h>
int main()
{printf("%zd\n", sizeof(char*));printf("%zd\n", sizeof(short*));printf("%zd\n", sizeof(int*));printf("%zd\n", sizeof(double*));return 0;
}

在32位环境下输出结果均为4,在64位环境下输出结果均为8。

三、指针变量类型的意义

虽然指针变量的大小和类型无关,但指针类型有着重要的意义。

3.1 影响解引用的权限

指针的类型决定了对指针解引用时的操作权限,即一次能操作的字节数。例如:

  • char*类型的指针解引用只能访问1个字节。
  • int*类型的指针解引用能访问4个字节。

看下面两段代码:

// 代码1
int main() {int a = 0x11223344;int* p = &a;*p = 0;return 0;
}

逐语句调试,代码运行到15行时。int*可以访问四个字节,将四个字节都改为0
在这里插入图片描述

// 代码2
int main() {int a = 0x11223344;char* p = &a;*p = 0;return 0;
}

逐语句调试,代码运行到22行时。char*只访问访问一个字节,将第一个字节改为0
在这里插入图片描述

3.2 影响指针 ± 整数的步长

指针的类型决定了指针向前或者向后走一步的距离。例如:

int* + 1 跳过四个字节(int大小),char* + 1 跳过一个字节(char大小)
在这里插入图片描述

3.3 void* 指针

void*类型的指针可以接受任意类型的地址,可以理解为无具体类型指针(或者叫泛型指针)但它不能直接进行指针的±整数和解引用运算。通常用于函数参数部分,实现泛型编程的效果,以处理多种类型的数据。

四、const修饰指针

4.1 const修饰变量

const修饰的变量不能直接被修改。

#include <stdio.h>
int main()
{const int n = 0;n = 20; // 报错,n不能被直接修改return 0;
}

此时变量具有常属性,称为常变量,但本质依旧是变量而不是常量。

在C++中被const修饰则为常量。

但如果通过指针获取其地址,还是可以修改该变量的值,这显然打破了const的限制,所以需要用const修饰指针变量。

在这里插入图片描述

4.2 const修饰指针变量

  • const放在*的右边:修饰的是指针变量本身,指针变量不可以再指向其他变量。但可以通过指针修改指向的内容。
int main()
{int a = 10;int b = 20;int * const p = &a;p = &b;//err*p = 100;//可以通过编译return 0;
}
  • const放在*的左边:限制指向的内容,不可以通过指针来修改,但可以修改指针指向的变量。
int main()
{int a = 10;int b = 20;int const* p = &a;p = &b;//可以通过编译*p = 100;//errreturn 0;
}

在这里插入图片描述

五、指针运算

指针的基本运算有三种:

5.1 指针±整数

原理同本文3.2部分。

由于数组在内存中是连续存放的,知道第一个元素的地址后,通过指针±整数可以访问数组中的其他元素。

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)); // 通过指针+整数访问数组元素}return 0;
}

在这里插入图片描述
注意:

  • *(p+1)不要写成*p+1,前者表示指针变量+1,后者表示p指向的内容+1
  • sizeof()中,输入数组名arr,计算整个数组的大小。

5.2 指针-指针

通过上述,我们可以明确:

指针1 + 整数 = 指针2

以此推理出

整数 = 指针2 - 指针1

类比“日期 - 日期”,得到之间的天数。两个指针相减的结果是它们之间的元素个数,常用于计算字符串长度等场景

  • strlen()求字符串长度,统计字符串\0之前字符个数
  • 数组名arr是数组首元素的地址。arr等价于&arr[0]

模拟实现strlen()函数:

  • 方法1 计数器
int my_strlen(char* s)
{int cnt = 0; //计数器while(*s != '\0'){cnt++;str++}return cnt; // 计算两个指针之间的元素个数,即字符串长度
}
int main()
{printf("%d\n", my_strlen("abc")); //输出3return 0;
}
  • 方法2 指针 - 指针
int my_strlen(char* s)
{char* p = s;while (*p != '\0')p++;return p - s; // 计算两个指针之间的元素个数,即字符串长度
}
int main()
{printf("%d\n", my_strlen("abc")); //输出3return 0;
}

注意:

  • 指针 - 指针 的前提时两个指针指向同一块空间!

例如:

int main()
{int arr[10] = {0};char ch[10] = {'0'};printf("%d\n",&ch[0] - &arr[0]);//errreturn 0;
}
  • "日期 + 日期"没有意义,同样的,“指针 + 指针”也没有任何意义。

5.3 指针的关系运算

指针与指针比较大小,其实就是地址与地质比较大小。

数组随下标变大,地址由低变高。

int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int* p = &arr[0];int sz = sizeof(arr) / sizeof(arr[0]);while (p < arr + sz) // 指针的大小比较,当p指向的地址小于arr+sz(相当于数组最后一个元素的地址)进入循环{printf("%d ", *p);p++;}//输出 1 2 3 4 5 6 7 8 9 10return 0;
}

六、野指针

野指针就是指针指向内容是不可知的(不正确、随机、没有明确限制)

6.1 野指针成因

  • 指针未初始化:局部变量指针未初始化时,其值是随机的。
int main()
{ int *p;//此时p是局部变量,指针未初始化,默认为随机值 *p = 20;return 0;
}
  • 指针越界访问:指针指向的范围超出数组等申请的内存空间。
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;
}
  • 指针指向的空间释放:返回局部变量的地址,该局部变量的空间在函数调用结束后会被释放。
int* test()
{int n = 100;return &n;
}int main()
{int*p = test();printf("%d\n", *p);//此时test()调用完成,栈帧被销毁, 内存被释放return 0;
}

6.2 如何规避野指针

  • 指针初始化:明确指向时直接赋值地址,否则赋值NULLNULL是值为0的标识符常量,该地址无法使用,读写地址也会报错)。
int main()
{int* p = NULL;*p = 20//err
}
  • 小心指针越界:不访问超出申请内存范围的空间。
  • 及时置NULL并检查:指针变量不再使用时置为NULL,使用前判断是否为NULL
  • 避免返回局部变量的地址。

七、assert断言

assert.h头文件中的assert()宏用于在运行时确保程序符合指定条件,如果不符合就报错终止运行。其表达式为真时程序继续运行,为假时报错并显示相关信息。

assert(p != NULL;//确保 p为有效指针
  • assert()断言相对if语句的优点:
    • 出现错误会直接报错,指明在什么文件,哪一行
    • 无需修改代码就可以禁用assert().可以通过在#include <assert.h>前定义NDEBUG宏来禁用assert()语句.
      在这里插入图片描述
  • 缺点:
    • 引入了额外的检查,增加了程序运行时间

通常在Debug版本中使用,Release版本中禁用,以不影响程序效率。VS2022中release版会直接禁用assert

八、指针的使用和传址调用

8.1 strlen的模拟实现

strlen函数用于求字符串长度,统计的是字符串中\0之前的字符个数。模拟实现如下:

#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)//限制内容,不能被修改
{int count = 0;assert(str); // 确保 str不为NULLwhile (*str){count++;str++;}return count;
}
int main()
{int len = my_strlen("abcdef");printf("%zd\n", len);return 0;
}

求出的长度不可能是负数,因此返回值类型使用size_t(无符号整型)更合适,打印应使用zd%作为占位符。

8.2 传值调用和传址调用

  • 传值调用:实参传递给形参时,形参创建临时空间接收实参。形参和实参是独立的两个空间。形参只是实参的一份临时拷贝,对形参的修改不影响实参。

  • 传址调用:将变量的地址传递给函数,函数内部通过地址间接操作主调函数中的变量,可实现对变量的修改。

例如交换两个整型变量的值,使用传址调用:

#include <stdio.h>
void Swap(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);Swap(&a, &b);printf("交换后:a=%d b=%d\n", a, b);return 0;
}

运行结果:
在这里插入图片描述

指针是C语言的精华,掌握好指针能让我们在编程中更加得心应手。希望本文能帮助大家更好地理解指针的相关知识,后续还会有更深入的探讨。

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

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

相关文章

半导体企业选用的跨网文件交换系统到底应该具备什么功能?

在半导体行业的数字化转型过程中&#xff0c;跨网文件交换已成为连接研发、生产、供应链的关键纽带。半导体企业的跨网文件交换不仅涉及设计图纸、工艺参数等核心知识产权&#xff0c;还需要满足跨国协同、合规审计等复杂需求。那么&#xff0c;一款适合半导体行业的跨网文件交…

影刀RPA_初级课程_玩转影刀自动化_网页操作自动化

声明&#xff1a;相关内容来自影刀学院&#xff0c;本文章为自用笔记&#xff0c;切勿商用&#xff01;&#xff08;若有侵权&#xff0c;请联络删除&#xff09; 1. 基本概念与操作 1.1 正确处理下拉框元素&#xff08;先判断页面元素&#xff0c;后进行流程编制&#xff09;…

Spark初探:揭秘速度优势与生态融合实践

更多推荐阅读 Spark与Flink深度对比&#xff1a;大数据流批一体框架的技术选型指南-CSDN博客 LightProxy使用操作手册-CSDN博客 Sentry一看就会教程_sentry教程-CSDN博客 微前端架构解析&#xff1a;核心概念与主流方案特性对比_微前端方案对比-CSDN博客 目录 Spark为何比Hadoo…

详谈OSI七层模型和TCP/IP四层模型以及tcp与udp为什么是4层,http与https为什么是7层

一、网络模型&#xff1a;OSI七层 vs TCP/IP四层OSI七层模型 (理论参考模型):目的&#xff1a;提供一个标准化的理论框架&#xff0c;用于理解网络通信过程和各层的功能划分&#xff0c;促进不同厂商设备的互操作性。它是一个理想化的模型。分层 (从下到上):物理层&#xff1a;…

ClickHouse 高性能实时分析数据库-索引与数据跳过(查询的“瞬移”能力)

告别等待&#xff0c;秒级响应&#xff01;这不只是教程&#xff0c;这是你驾驭PB级数据的超能力&#xff01;我的ClickHouse视频课&#xff0c;凝练十年实战精华&#xff0c;从入门到精通&#xff0c;从单机到集群。点开它&#xff0c;让数据处理速度快到飞起&#xff0c;让你…

Jetpack - Room(Room 引入、Room 优化)

一、Room 引入 1、基本介绍 Room 在 SQLite 上提供了一个抽象层&#xff0c;以便在充分利用 SQLite 的强大功能的同时&#xff0c;能够流畅地访问数据库&#xff0c;官方强烈建议使用 Room 而不是 SQLite 2、演示 &#xff08;1&#xff09;Setting 模块级 build.gradle depend…

【江科大CAN】2.1 STM32 CAN外设(上)

2.1 STM32 CAN外设&#xff08;上&#xff09;2.1.1 STM32 CAN外设简介2.1.2 外围电路设计2.1.3 STM32 CAN内部结构2.1.4 发送流程详解2.1.5 接收流程详解2.1.6 关键配置位总结STM32 CAN外设讲解 大家好&#xff0c;欢迎继续观看CAN总线入门教程。本节开始&#xff0c;我们正式…

人工智能技术革命:AI工具与大模型如何重塑开发者工作模式与行业格局

引言&#xff1a;AI技术爆发的时代背景过去五年间&#xff0c;人工智能领域经历了前所未有的爆发式增长。从2020年GPT-3的横空出世到2023年多模态大模型的全面突破&#xff0c;AI技术已经从实验室走向了产业应用的前沿。开发者作为技术生态的核心推动者&#xff0c;其工作模式正…

傅里叶变换

傅里叶变换:运用频域的出发点就是能够将波形从时域变换到频域&#xff0c;用傅里叶变换可以做到这一点。有如下3种傅里叶变换类型&#xff1a;1.傅里叶积分(FI); 2.离散傅里叶变换(DFT); 3.快速傅里叶变换(FFT)。傅里叶积分是一种将时域的理想数学表达变换成频域描述的数学技术…

【IQA技术专题】纹理相似度图像评价指标DISTS

纹理一致性图像评价指标: Image Quality Assessment: Unifying Structure and Texture Similarity&#xff08;2020 PAMI&#xff09;专题介绍一、研究背景二、方法总览2.1 初始变换2.2 纹理表示和结构表示2.3 DISTS指标2.4 优化DISTS指标三、实验结果四、总结本文将对统一图像…

windows下Docker安装路径、存储路径修改

一、命令行指定安装路径​ ​​下载安装包​​&#xff1a;从Docker官网获取安装程序&#xff08;如Docker Desktop Installer.exe&#xff09;。​​运行PowerShell​​&#xff1a; & "H:\Docker Desktop Installer.exe" install --installation-dir"F:…

thingsboard 自定义动作JS编程

在 ThingsBoard 中实现 自定义动作&#xff08;Custom Action&#xff09;的 JavaScript 编程&#xff0c;主要通过“Custom action (with HTML template&#xff09;”方式完成&#xff0c;适用于创建弹窗、编辑实体、控制设备等交互行为。 实现步骤&#xff08;以添加设备或资…

Spring Boot 简单接口角色授权检查实现

一、背景与目标在Spring Boot应用开发中&#xff0c;接口级别的权限控制是系统安全的重要组成部分。本文将介绍一种简单直接的接口角色授权检查实现方案&#xff0c;适合快速开发和安全合规检查场景。二、技术方案概述本方案采用自定义注解拦截器的方式实现&#xff0c;具有以下…

PytorchLightning最佳实践日志篇

在 PyTorch Lightning&#xff08;PL&#xff09;中&#xff0c;日志系统是 “炼丹” 过程中复现实验、对比效果、排查问题的核心工具。结合实际工程经验&#xff0c;总结以下最佳实践和技巧&#xff0c;帮助提升实验效率&#xff1a; 一、日志工具的选择与配置 PL 通过统一的s…

基于JavaWeb的兼职发布平台的设计与实现

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat12开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.6系统展示系统首页用户登录招聘信…

Linux学习--C语言(指针3)

1.指针函数和函数指针1.1 指针函数指针函数是函数&#xff0c;函数的返回值是指针不能返回局部变量的地址指针函数返回的地址可以作为下一个函数调用的参数1.2 函数指针函数指针是指针&#xff0c;指针指向一个函数#include <stdio.h>int Add(int x, int y) {return x y…

【JAVA EE初阶】多线程(上)

目录 1.预备知识 1.1 冯诺依曼体系结构&#xff1a; 1.2 现代CPU主要关心指标&#xff08;和日常开发密切相关的&#xff09; 1.3 计算机中&#xff0c;一个汉字占几个字节&#xff1f; 1.4 Windows和Linux的区别 1.5 PCB的一些关键要点 2.线程和进程 2.1 创建线程的写法…

用互联网思维扩展电商后台的 CRUD 功能

一、自定义实现MyBatis-Plus逆向工程 多数据源的问题解决了&#xff0c;接下来开始进行实际开发时&#xff0c;你会发现&#xff0c;最麻烦的一件事情就是要创建与数据库表对应的POJO了。这些没什么难度&#xff0c;但是繁琐的内容会占据大量的开发时间。比如一个PmsProducr对…

无代码测试平台ATECLOUD全场景测试方案

ATECLOUD 智能云测试平台是有纳米软件开发的一款以无代码架构与弹性扩展体系为核心的自动化测试平台&#xff0c;通过数据模型驱动的创新设计&#xff0c;为研发、产线等多场景提供高效可控的测试解决方案。​无代码架构 ATECLOUD 打破传统技术壁垒&#xff0c;构建完全可视化的…

当 AI 重构审计流程,CISA 认证为何成为破局关键

在南京审计大学最新发布的《面向审计行业 DeepSeek 大模型操作指南》中&#xff0c;一组数据引发行业深思&#xff1a;通过自动化数据处理、智能风险识别和定制化报告生成&#xff0c;AI 大模型能帮助审计人员降低 40% 以上的人工成本&#xff0c;同时将风险识别准确率提升至 9…