目录

  • 前言
  • 1.指针与C++原理
    • 1.1声明和初始化指针
    • 1.2指针的危险
  • 2. new —— 分配内存
    • 2.1 普通变量
    • 2.2 delete释放内存
    • 2.3 动态数组
  • 3.指针算数原理

前言


相信很多小伙伴在日常编程中都会用到指针,所以在这里,我们再来看一看指针到底是怎样的呢,到底是怎样去执行程序的,懂得了底层原理,大家对指针将更加掌握。

1.指针与C++原理

《C++ Primer Plus》中指出“面向对象编程与传统的过程性编程的区别在于,面向对象强调的是在运行阶段进行决策,而不是编译阶段”。也说明运行阶段决策更具有灵活性,而在数组在分配内存时传统方法是需要提前申明数组的大小,int arry[3]={1,2,3},数组的长度是提前设置好的,为3,这就是编译阶段的决策,这样做固然很安全,但是如果我的数组元素多了,几千个甚至更多,我们也用这种方法,或者说我们不确定我们究竟要用多少内存,所以我们会尽可能的把内存扩大,但是这时会有一个新的问题,如果元素没那么多,那么将会浪费很多内存,当然也有人说当下无论手机还是电脑内存都可以很大,不用在乎这么小的内存,但如果两款软件应用实现的功能相同内存占用却不一样,那么用户就会选择内存较小的,这时候,我们就想,怎么样才能让开辟出来的内存恰好合适,那就是我们要推荐的面向对象编程(OPP),它使得程序更加灵活。在程序运行后,可以这次告诉它只需要20个元素,也可以告诉它下次需要多少元素。总之,OPP可以在运行阶段确定数组长度,为了使用这种方法,C++采用的是使用关键词new请求正确数量的内存以及使用我们今天主要讲的指针来跟踪新分配的内存的位置。在运行阶段做决策并不是OPP特有的,但使用C++编写这样的代码比C语言更加就简单。

1.1声明和初始化指针

通常我们会看到 int ∗ * ∗p=nullptr这样的C++语句去描述一个指针,但是你真的懂为什么吗?

这里的p是指针名,表示的是地址, ∗ * ∗运算符被称为间接值或解除引用运算符,将它用于指针p可得到该地址储存的值(C++可根据上下文判断 该符号是乘法还是解除引用)。
int是指针的类型,但是为什么一定要在指针前面指定数据类型呢?可以举例char和double,它们的使用的字节数不同,储存的内部格式也不同,所有,指针必须指定指向的数据的类型。
我们可以说p是指向int类型,还可以说p的类型是指向int的指针,有点绕,但是你仔细品。
一般的,C程序员喜欢这样:int ∗ * ∗p;这里强调 ∗ * ∗p是一个int类型的值。
而C++程序员更喜欢使用:int ∗ * ∗ p;这是强调int ∗ * ∗是一种指向int的指针。
当然在哪里添加本身对程序运行不会出问题,不过可以看出你是什么理解指针的。
这里也说出了指针变量不仅仅是指针,而且是特定类型的指针,也说明了指针都是基于其他类型的。
注意:
如果我们定义不同类型的指针,比如char ∗ * ∗p1和double ∗ * ∗p2,一般char变量是占1个字节,double是占8个字节,p1和p2是指向两个长度不一样的数据类型,但是这两个指针变量本身的长度通常是相同的(为4个字节),也就是说char的地址与double的地址的长度是相同的,这就像1000是超市的地址,1005是学校的地址,地址取决于计算机系统,而不是类型。
而nullptr是c++中常用于指针的初始化,表示空指针,c语言中常用NULL或者0表示,而在C++11中指出其的不严谨,因为0表示的是整数,而不是指针,NULL只是一个代名词,0的代名词,而对于C++,编译器会更加的小心谨慎,故用nullptr表示空指针,当然用NULL与0不会错,但是为了避免歧义,我们在编写C++的时候最好是用前者。

1.2指针的危险

对于指针的危险,它更容易发生在用指针不仔细的人身上。

1.在C++中创建指针时,计算机将分配地址的内存,但是不会分配指针数据类型的内存,为数据类型提供内存是一个独立的事件,如果你这样做:

int *p;
*p=8;//错

p确实是指针,但是并没有说它指向哪里,系统随机指向的某个内存空间(野指针),可能这个地址空间上面的内容是另外一个值,这时候,你将8赋值在那个p的地址内容上,将覆盖原来的值,从而造成错误,并且这个错误是隐匿的、最难跟踪的bug,不容易被发现,所有编写程序的人应该避免这种错误。
注意:一定要在解除引用运算符之前将指针初始化一个确定的、适当的地址。
2.计算机会将地址当作整数来处理,当指针不是整形。因此,不能简单地将整数赋给指针,如

int *p;
p=0xB8000000;//错

0xB8000000我们认为的认为它是一个地址,但是在计算机中,我们并没有告诉系统它是一个地址,系统会将它处理为一个数字,在C99标准发布之前,C语言允许这样的赋值,但C++方面的一致严格要求,编译器会显示的报错,不过我们可以通过强制转换:

int *p;
p=(int*)0xB8000000;//对

这样两边都是地址,不会报错。
注意: p是int值的地址并不意味着本身类型是int,就像char ∗ * ∗p,char类型是1个字节,而地址是4个字节。

2. new —— 分配内存

接下来,我们看看程序在运行时是什么分配内存的吧。我们把指针初始化为变量的地址,此时变量在编译时分配有名称的内存,指针为通过名称直接访问内存提供了别名,而指针的可以在运行阶段分配未命名的内存以储存值,这就是我们 最开始说的动态分配内存,只能通过指针来访问内存。在C语言中,可以用库函数malloc()来分配内存,当然C++也可以用,不过,C++中允许使用new来实现动态分配,这将更简单。

2.1 普通变量

//用传统赋值,在编译阶段
int h = 50;
int *p1 = &h;
//在运行阶段赋值
int *p2 = new int;//指明类型
*p2=100;//可以,因为p2指向的内存是已知的

我们需要哪种数据类型就分配哪种,new将找到一个长度正确的内存块,并返回该内存块的地址,此时用指针p2来接收, ∗ * ∗p2是储存在这块内存的值,用传统赋值时可以看出h是一个p指向的一个内存块的名称,而运行阶段的内存块没有名称,专有名词为“数据对象”(变量也是数据对象,但p2指向的内存不是变量)。
注意:new分配的内存与常规变量分配的内存不同,变量h和p1的值都存储在栈的内存区域中,而new分配的内存是从堆或自由存储区的内存区域来的。

不了解栈和堆的小伙伴将在以后的学习中学习到

2.2 delete释放内存

程序用new申请一个内存块使用完后我们要用delete手动释放申请的内存,归还的内存才可以供其他程序使用,使用了new一定要使用delete且只能使用delete来释放内存,否则会发生内存泄漏也即是被分配的内存无法使用了,严重的内存泄漏会导致你的计算机没有内存。

int *p = new int;
...
delete p;

delete释放的是p指向的内存,而不会删除p这个指针,也并不意味着使用new的指针,而是用于new的地址

int *p1 = new int;
int *p2 = p1;
delete p2;


这时还可以用p指针指向其他内存地址。不要重复释放同一个内存块,C++标准指出这样做的结果是不确定的;不能用delete来释放声明变量的内存块。

int *p1 = new int;
delete p1;
delete p;//错,重复释放
int h = 5;
int p2 = &h;
delete p2;//错

2.3 动态数组

在程序运行时选择数组的长度成为动态联编,意味着程序是在运行时创建的,这种数组也叫动态数组。

int *p = new int [10];
...
delete [] p;

int类型的指针p指向一个内存块,内存块里事先空出10个元素的内存,new运算符返回首元素的地址,该地址被赋值给指针p,程序执行完后删除这个内存块,new与delete应相对应,否则后果不确定。
总之:

1.不要使用delete来释放不是new分配的内存。
2.不要使用delete释放同一个内存块两次。
3.如果使用new[ ]为数组分配内存,则应使用delete[ ]来释放。
4.如果使用new为一个实体分配内存,则应使用delete(没有括号)来释放。
5.对空指针使用delete是安全的。

如何使用动态数组?
数组与指针基本等价是C和C++的优点之一。

#include<iostream>
using namespace std;
int mian()
{int *p1 = new int [3];p1[0] = 1;p1[1] = 2;p1[2] = 3;cout << "p1[1]是:" << p1[1] << endl;p1 = p1 + 1;cout << "p1[0]是:" << p1[0] << endl;cout << "p1[1]是:" << p1[1] << endl;p1 = p1 - 1;delete p1;return 0;
}

输出结果为

p1[1]是:2
p1[0]是:2
p1[1]是:3

这是因为指针p1指向了一个含3个元素的数组的第一个元素地址,即p1[0]的地址,所以p1=p1+1(相当于加了4个字节长度)会使指针向后偏移一个这样数组元素的单位长度,也即会让p1指向第二个元素的地址。


直到恢复为原来的样子再用delete删除,便于delete找到这个动态数组的位置。

3.指针算数原理

可以看看下面这个程序将得出什么样得结论

#include<iostream>
#include<iostream>
using namespace std;
int main()
{int a[3] = { 3,2,3 };int* p = nullptr;p = a;cout << p << " " << a << " " << *p << " " << p[0] << " " << a[0] << endl;p = p + 1;cout << p << " " << a << " " << *p << " " << p[0] << " " << a[0] << endl;p = &a[0];cout << p << " " << a << " " << &a[0] << " " << p[0] << " " << a[0] << endl;cout << "----------------" << endl;cout << a << " " << &a << " " << &a[1] << " " << (a + 1) << " " << (&a + 1) << endl;
}

结果是

这说明数组名a是第一个元素的地址,&a是整个数组的地址,对于a+1,是第二个元素的地址,&a+1是跳过了整个数组。


不过在sizeof()库函数中却是例外,大家可以自己动手去试一试。
相信大家看完这篇介绍应该更理解指针了吧,希望能帮到你
ok,小编干饭去啦

(还是干饭…)

【C++】指针深度解析相关推荐

  1. 深度解析Objective-C笔试题

    2011-08-11 17:39 佚名 互联网 字号:T | T 本文介绍的是Objective-C笔试题,先来问一个,为什么很多内置类如UITableViewController的delegate属 ...

  2. ConcurrentHashMap源码深度解析(一)(java8)——不可不知的基本概念(助你拿下源码事半功倍)

    文章目录 一.前言 二.基本定义 1.基本常量 2.基本属性 三.构造器优化 1.ConcurrentHashMap(int initialCapacity, float loadFactor, in ...

  3. 用户研究:深度解析用户画像

    "用户画像作为一种设计工具,可以很好得帮助设计师跳出"为自己设计"的惯性思维,聚焦目标用户,发现核心价值,赋能产品,在互联网各类型产品中有广泛的应用. 在产品研发过程中, ...

  4. Go netpoll I/O 多路复用构建原生网络模型之源码深度解析

    原文 Go netpoll I/O 多路复用构建原生网络模型之源码深度解析 导言 Go 基于 I/O multiplexing 和 goroutine 构建了一个简洁而高性能的原生网络模型(基于 Go ...

  5. 直击招聘程序员面试笔试C语言深度解析,直击招聘 程序员面试笔试C++语言深度解析(直击招聘) pdf epub mobi txt 下载...

    直击招聘 程序员面试笔试C++语言深度解析(直击招聘) pdf epub mobi txt 下载 图书介绍 ☆☆☆☆☆ 李春葆,李筱驰 著 下载链接在页面底部 发表于2021-05-18 类似图书 点 ...

  6. Java LockSupport以及park、unpark方法源码深度解析

    介绍了JUC中的LockSupport阻塞工具以及park.unpark方法的底层原理,从Java层面深入至JVM层面. 文章目录 1 LockSupport的概述 2 LockSupport的特征和 ...

  7. 腾讯反病毒实验室:深度解析AppContainer工作机制

    深度解析AppContainer Win8开始,Windows引入了新的进程隔离机制AppContainer,MetroAPP以及开启EPM的IE Tab进程都运行在AppContainer隔离环境, ...

  8. ConcurrentHashMap1.7深度解析

    ConcurrentHashMap1.7深度解析 1.ConcurrentHashMap原型图 1.1 总结 1.1.1 分段锁定 从原型图我们可以看出concurrentHashMap1.7是采用的 ...

  9. 《C语言内核深度解析》——笔记及拓展(1)

    文章是我前几天读了朱有鹏,张先凤老师的<嵌入式Linux与物联网软件开发:C语言内核深度解析>写的,拜读之后,虽没有醍醐灌顶,至少解开了我之前的一些疑惑. <嵌入式Linux与物联网 ...

最新文章

  1. 2021年夏季学期“清华大学大数据能力提升项目” 招募《大数据实践课》企业合作项目...
  2. shell训练营Day18
  3. java的枚举类enum
  4. tableau实战系列(四十六)-如何用Tableau实现动态报表?​某咖啡店的销售数据报表(看板)​
  5. Mybatis的xml文件中$ 与 #的区别
  6. 上班族的10大经典哲学,还有什么能难倒你?[轉自太平洋電腦網]
  7. JDK源码解析之 java.lang.Long
  8. shell条件检查原理:command echo ‘success‘ || echo ‘error‘
  9. wegame饥荒一直连接中_英雄联盟手游:腾讯WeGame发布了,可以玩云顶之弈自走棋...
  10. 华三 h3c super vlan配置
  11. 为什么晚睡晚起的人会更聪明?
  12. TEANMA(天玛)-公共广播系统中扬声器配置及相关注意事项解析
  13. maya餐具图片_有哪些价格低但是逼格高很文艺又实用的物品呢?
  14. PyTorch模型定义和训练技巧
  15. 聚观早报 | 吉利正式收购魅族科技;雷军:对标iPhone不是口号
  16. Android开发者要搬运多少年代码才能突破年薪40W?
  17. hadoop发行商介绍:Hortonworks
  18. java 九大行星运行_html5 canvas太阳系九大行星运行动态图代码
  19. 前端模块化- ES Module 和 CommonJS 的使用
  20. cadence的parametric analysis设置

热门文章

  1. C617 redhat smicmmrf 工艺库安装 乞丐版
  2. 安装x86版 OS X的系统要求
  3. TCP RTT 采集方法
  4. 大学计算机实践论文,大学计算机实践论文.docx
  5. 从零基础到精通的Python学习路线(附教程)
  6. linux 7 开启远程桌面,配置CentOS 7允许来自Windows的远程桌面访问
  7. 使用Python编写程序,输入一个大于 2 的自然数,然后输出小于该数字的所有素数组成的集合。
  8. WPF工控组态软件之管道和冷却风扇开发
  9. 13.JVM-垃圾回收相关概念
  10. 我也来读jquery