0601-指针的基础

news/2025/2/12 5:10:56/

内存

物理存储器和存储地址空间

物理存储器:实际存在的具体存储器芯片。比如:内存条、RAM芯片、ROM芯片。
存储地址空间:对存储器编码的范围。

  • 编码:对每个物理存储单元(一个字节)分配一个号码
  • 寻址:可以根据分配的号码找到相应的存储单元,完成数据的读写

内存地址

  • 将内存抽象成一个很大的一维字符数组。
  • 编码就是对内存的每一个字节分配一个32位或64位的编号(与32位或者64位处理器相关)。
  • 这个内存编号我们称之为内存地址。
    内存中的每一个数据都会分配相应的地址:
  • char:占一个字节分配一个地址
  • int: 占四个字节分配四个地址
  • float、struct、函数、数组等

指针和指针变量

  • 指针就是地址,地址就是指针

    int a;
    int b[];
    int* p;
    

    上述代码中&a、和bp都是地址,也都是指针。

  • 指针变量是存放地址的变量

  • 通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样

指针的基础知识

指针变量的定义和使用

定义指针的方式如下:
数据类型* 变量名
其中*表示该变量为指针变量,数据类型表示该指针变量保存的是哪一种数据类型的地址,也就是指针变量指向哪一种数据类型。

访问指针变量指向的内存空间的数据:
*指针变量
此处的*为取值运算符。
指针变量保存的是内存地址,*指针变量就表示访问该地址对应的内存空间。对*指针变量操作,其实就是读写该内存空间的数据。

#include<stdio.h>int main() {int a = 10;int* p;p = &a;printf("a = %d\n", a);printf("p指向的数据=%d\n", *p);printf("变量a的地址:%p\n", &a);printf("指针变量p存的地址:%p\n", p);return 0;
}

运行上面代码,结果如下:
在这里插入图片描述
注意:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。

使用指针访问数据的原理
不同类型的数据在内存中占用不同的字节数,指针变量保存的是数据首个字节的地址。系统根据指针变量保存的地址找到首个字节后,再根据指针指向的数据类型,顺位读取不同的字节长度,从而访问到完整的数据。

比如,一个int型变量a占4字节,指针变量int* p只保存了a在内存中的首个字节的地址。当操作*p时,系统会先根据该地址找到首个字节,然后再顺位读取3个字节,总共读取了4个字节,从而取出变量a的值。

通过指针间接修改变量的值

#include<stdio.h>int main() {int a = 10;int* p;p = &a;printf("a = %d\n", a);printf("p指向的数据=%d\n", *p);printf("变量a的地址:%p\n", &a);printf("指针变量p存的地址:%p\n", p);*p = *p + 1;printf("p指向的数据加1后,a = %d\n", a);printf("p指向的数据加1后,p指向的数据=%d\n", *p);return 0;
}

运行上面的代码,结果如下:
在这里插入图片描述
在定义指针类型时一定要和指向的变量类型是一样的才行。

#include<stdio.h>int main() {char a = 97;int* p = &a;printf("a的值=%d\n", a);printf("*p的值=%d\n", *p);return 0;
}

运行上面的代码,结果如下:

在这里插入图片描述
上面代码中,虽然指针变量p保存的是字符变量a的地址,但是p是指向int型数据的,所以在找到a的地址后还会顺位读取3个字节的数据。
在这里插入图片描述

指针大小

windows中数据存储采用小端对齐的方式。也就是,数据的低位放在地址小的内存中。
在这里插入图片描述
指针也是一种数据类型,所以可以使用sizeof()测量指针的大小,得到的总是:4或8。
这是因为指针存储的是内存地址。在32位处理器中内存地址是一个32位的编号,在64位处理器中内存地址是一个64位的编号。
所以在32位处理器中sizeof(指针)得到的总是4,在64位处理器中sizeof(指针)得到的总是8。

在visio studio中可以在下图中选择基于32位或64位的处理器编译,从而使sizeof(指针)得到不同的结果。
在这里插入图片描述

int main() {int* p1;int** p2;char* p3;char** p4;printf("sizeof(p1) = %d\n", sizeof(p1));printf("sizeof(p2) = %d\n", sizeof(p2));printf("sizeof(p3) = %d\n", sizeof(p3));printf("sizeof(p4) = %d\n", sizeof(p4));printf("sizeof(double *) = %d\n", sizeof(double*));return 0;
}

上述代码基于32位处理器编译运行后结果如下:
在这里插入图片描述
上述代码基于64位处理器编译运行后结果如下:
在这里插入图片描述

野指针和空指针

野指针

任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统可能不允许操作此指针指向的内存区域)。所以,野指针不会直接引发错误,操作野指针指向的内存区域可能会出问题。

int main() {int* p = 100;/*操作系统将0~255的内存空间作为系统占用,不允许被访问操作*/printf("%d\n", *p);return 0;
}

空指针

为了标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针。

int *p = NULL;

NULL是一个值为0的宏常量:

#define NULL    ((void *)0)

空指针可用于条件判断

	int* p = NULL;if (p == NULL) {printf("我是一个空指针\n");}else {printf("我不是一个空指针\n");}

万能指针void *

万能指针可以接收任意数据类型的地址。但不能直接访问万能指针指向的内存空间,因为系统找到数据的首个字节后,不知道接下来要顺位读取多少个字节。

必须先将万能指针强转为某个具体类型的指针变量后才可以访问。

#include<stdio.h>int main() {int a = 10;void* p = &a;printf("变量a的值:%d\n", a);//printf("万能指针p指向的值:%d\n", *p);//不能直接访问万能指针指向的内存空间printf("万能指针p指向的值:%d\n", *(int*)p);*(int*)p = *(int*)p + 1;printf("加1后,变量a的值:%d\n", a);printf("加1后,万能指针p指向的值:%d\n", *(int*)p);return 0;
}

执行上面代码,结果如下:
在这里插入图片描述

const修饰的指针变量

const 数据类型* 变量名const修饰数据类型,说明该指针变量指向的内存空间可读不可写。

数据类型* const 变量名const修饰变量名,说明该指针变量保存的内存地址可读不可写。

const 数据类型* const 变量名const既修饰数据类型又修饰变量名,说明该指针变量指向的内存空间可读不可写,保存的内存地址也是可读不可写的。

#include<stdio.h>int main() {const int a = 10;//a = 20;//errint b = 20;const int* p = &a;printf("指针变量p指向的值:%d\n",*p);//*p = 30;//errp = &b;//okprintf("指针变量p指向的值:%d\n", *p);int* const p2 = &a;printf("指针变量p2指向的值:%d\n", *p2);//p2 = &b;//err*p2 = 30;//okprintf("指针变量p2指向的值:%d\n", *p2);printf("a的值:%d\n", a);const int* const p3 = &b;//*p3 = 40;//err//p3 = &a;//errprintf("指针变量p3指向的值:%d\n", *p3);b = 40;printf("指针变量p3指向的值:%d\n", *p3);return 0;
}

运行上面代码,结果如下:
在这里插入图片描述
const只能限定它所修饰的指针变量,我们不能通过这个指针变量来修改指向的内存空间的数据或修改这个指针变量保存的内存地址。但是,我们能通过其它指针变量来修改它们。

#include<stdio.h>int main() {int a = 10;int b = 20;const int* p = &a;//*p = 20;//err 不能通过指针变量p改变a的值a = 30;//okprintf("a的值:%d\n", a);printf("*p的值:%d\n", *p);const int* const p2 = &a;int** p3 = &p2;//*p2 = 20;//err 不能通过指针变量p2改变a的值//p2 = &b;//err 不能改变指针变量p2的指向,即不能改变p2保存的内存地址**p3 = 40;//ok 通过指针变量p3改变a的值printf("通过指针变量p3改变a的值后,a的值:%d\n", a);printf("通过指针变量p3改变a的值后,*p2的值:%d\n", *p2);printf("通过指针变量p3改变a的值后, **p3的值:%d\n", **p3);*p3 = &b;//ok 通过指针变量p3改变指针变量p2的指向printf("通过指针变量p3改变指针变量p2的指向后,*p2的值:%d\n", *p2);printf("通过指针变量p3改变指针变量p2的指向后, **p3的值:%d\n", **p3);return 0;
}

运行上面的代码,结果如下:
在这里插入图片描述


http://www.ppmy.cn/news/74072.html

相关文章

Python学习笔记——《吴恩达Machine Learning》逻辑回归例程

文章目录 逻辑回归和线性回归的区别&#xff1f;正则化逻辑回归逻辑回归中的梯度下降&#xff1a; 模型预测案例解决二分类问题&#xff1a;不同的 λ \lambda λ会产生不同的分类结果: 逻辑回归和线性回归的区别&#xff1f; 逻辑回归可以理解为线性回归的一个plus版&#xf…

Google 广告投放实操,小白入门看这篇就够了!

相信很多人做跨境电商都迈不过Google广告这道坎&#xff0c;许多卖家尝试在Google上投放广告&#xff0c;但却发现效果并不理想。今天东哥和大家来讨论一下Google广告投放的一些策略&#xff0c;看看能不能帮助大家取得更好的效果。 Google 广告投放实操 在实操开始前&#xff…

UE5中如何新建C++类?

UE5 插件开发指南 前言0.如何在UE编辑器内创建C++类?1.如何在UE编辑器外创建C++类?前言 这个问题应该细分成两个问题: (1)如何在编辑器内创建C++类? (2)如何在编辑器外创建C++类? 问题(1)主要针对那些可以在编辑器内继承并创建的类,然而有些内是无法在编辑内继承的,必须在…

Python中的logging模块

logging是Python中常见的日志工具&#xff0c;能够把一次运行的关键信息记录成日志&#xff0c;以便debug。为了让读者更快掌握这个工具&#xff0c;咱们逐步深入&#xff1a; import logginglogging.basicConfig(levellogging.INFO) logger logging.getLogger("MyLogge…

C语言函数大全-- _w 开头的函数(3)

C语言函数大全 本篇介绍C语言函数大全-- _w 开头的函数 1. _wmkdir 1.1 函数说明 函数声明函数功能int _wmkdir(const wchar_t* dirname);用于创建指定路径名的新目录 参数&#xff1a; dirname &#xff1a; 指向以 null 结尾的宽字符数组&#xff0c;该数组包含要创建的目…

浏览器的进程和线程

浏览器是多进程多线程的应用程序 浏览器进程 主要负责界面显示、用户交互、子进程管理等。浏览器进程内部会启动多个线程处理不同的任务。 网络进程 负责加载网络资源。网络进程内部会启动多个线程来处理不同的网络任务。 渲染进程 渲染进程启动后&#xff0c;会开启一个染主线…

无公网IP,SSH远程连接Linux CentOS

转载自cpolar内网穿透的文章&#xff1a;无公网IP&#xff0c;SSH远程连接Linux CentOS【内网穿透】 本次教程我们来实现如何在外公网环境下&#xff0c;SSH远程连接家里/公司的Linux CentOS服务器&#xff0c;无需公网IP&#xff0c;也不需要设置路由器。 视频教程 【SSH远程…

整除分块学习笔记

整除分块学习笔记 文章目录 整除分块学习笔记前言整除分块code后记 前言 最近在学习 莫比乌斯反演 &#xff0c;好像要用到一个小小的知识点&#xff1a; 整除分块 所以为了让大家都能更好地学习莫比乌斯反演&#xff0c;我来水一篇博客 那么正片开始。 整除分块 现在要求…