c++入门笔记(14)指针

来源:互联网 发布:淘宝买精密管被警察问 编辑:程序博客网 时间:2024/06/05 15:13

什么是地址

例子:计算机是如何获得变量i的地址
关键字:取地址符 &
它的作用是获得变量i在内存中的地址。

取得了i的地址以后,然后我们通过std::cout语句将它输出到屏幕上。

#include <iostream>int main(){    int i = 1;    int j = 1;    std::cout << &i << std::endl;  //屏幕输出i在内存中的地址    std::cout << &j << std::endl;  //屏幕输出j在内存中的地址    return 0;}

输出:

0x7ffe32d4a3400x7ffe32d4a344

用指针来保存地址

假如我们拥有一大批客户的地址,那么我们首先的第1件事就是用通讯录将它们统统记录下来,同样,计算机也有保存地址的工具,那就是指针

指针就是用来保存内存地址的变量。

指针是如何保存内存地址的?
由于每个被定义的变量都有自己的地址,因此你完全可以使用指针来存放任何已被定义的变量的地址,即使它没有被赋值。

int i;int *p;p = &i;

定义了一个指针p,星号”*”代表变量p是一个指针。要注意它的类型也是int。

#include <iostream>int main(){    int i;    int *p;    p = &i;    std::cout << "i的地址:\t" << &i << "\n"              << "p保存的地址:\t" << p << "\n"              << "p的地址:\t" << &p << "\n"              << std::endl;    return 0;}

空指针

我们知道指针就是用来保存内存地址的变量,因此我们定义一个指针后一定要用它来保存一个内存地址,假如我们不那么做,那么该指针就是一个失控指针,它可以指向任何地址,并且对该地址的数值进行修改或删除,后果非常可怕。

解决的办法就是将该指针初始化为0

    int *p;     //定义一个指向整型变量的指针p    p = 0;      //将变量p中所保存的内存地址清0

这样指针p变不会因为我们的疏忽而随意指向任意一个地址并且修改该地址的值。

以上两句可以合并为一句

    int *p = 0;    std::cout << "p所保存的地址:" << p << std::endl;

这样可以在定义指针的同时对其进行初始化。初始化的值为0。

输出:

p所保存的地址:0

这个地址是不会存放任何数据的,我们大可以对它进行任意的操作,都不会产生严重的后果。


指针与类型

由于不同类型的变量在内存中所占用的字节不同,而指针又是用来保存内存地址的变量,因此指针只能存储与它类型相同的变量的地址。

    double a;

定义了一个双精度型变量a。编译器接到此定义通知后会在内存中开辟一块内存区域。该区域的大小刚好可以存放双精度型数值。

    int *p;

该语句定义了一个指向整型变量的指针变量p。编译器知道了指针指向的类型,才能对其进行正确的处理与运算。

#include <iostream>int main(){    double a = 3.14;    int b = 6;    int *p1 = &b;   //定义指向整型变量的p1指针    double *p2 = &a;    //定义指向双精度型变量的p2指针    std::cout << "p1所保存的地址:" << p1 << std::endl;    std::cout << "p2所保存的地址:" << p2 << std::endl;    p1++;    p2++;    std::cout << std::endl;    std::cout << "p1所保存的地址:" << p1 << std::endl;    std::cout << "p2所保存的地址:" << p2 << std::endl;    return 0;}

输出:

p1所保存的地址:0x7ffcd5deecfcp2所保存的地址:0x7ffcd5deed00p1所保存的地址:0x7ffcd5deed00p2所保存的地址:0x7ffcd5deed08

p1移动了4个字节,p2移动了8个字节

由于指针类型的不同决定了指针运算方式的不同,所以我们不能将一种类型的指针赋给另一种类型的指针。

这就是指针与变量类型的关糸,指针的类型必须与它所指向的变量的类型相匹配。假如不相匹配,那么就会报错。


用指针来访问值

我们在得到某人的地址后,就可以根据该地址找到此人的家,并见到此人。
同理,指针的运算符”*”也可以为我们做到这些。

” * “: 间接引用运算符。当使用”*”星号时,就读取它后面变量中保存的地址处的值。

----------#include <iostream>int main(){    int a = 1;    int *p;     //定义指针变量p    p = &a;     //用指针变量p来保存变量a的地址。    std::cout << "读取变量p中所保存的地址:" << p << std::endl;    std::cout << "读取变量p中所保存地址的值:" << *p << std::endl; //读取变量p中所保存地址的值。    std::cout << std::endl;    std::cout << "变量a的地址:" << &a << std::endl;    std::cout << "变量a的值:" << a << std::endl;    return 0;}

输出:

读取变量p中所保存的地址:0x7ffc47be6c6c读取变量p中所保存地址的值:1变量a的地址:0x7ffc47be6c6c变量a的值:1

指针变量p前面的间接运算符”*”的含义是:”存储在此地址处的值”。 *p的意思就是读取该地址处的值。
由于p存储的是a的地址,因此执行的结果是输出a的值:1。


容易混淆的概念

指针最容易令人混淆的概念是:指针地址、指针保存的地址和该地址的值。
也就是说指针它自身的地址、指针保存的地址和指针保存的地址处的值,是最容易令初学者混淆的三个概念。


#include <iostream>int main(){    int i;    int *p=0;       //指针初始化    std::cout << "i的地址:" << &i << std::endl;    std::cout << "指针p的值为:" << p << std::endl;    std::cout << "指针p的内存地址为:" << &p << std::endl;    std::cout << std::endl;    i = 3;    p = &i;    std::cout << "i的地址:" << &i << std::endl;    std::cout << "指针p的值为:" << p << std::endl;    std::cout << "指针p的内存地址为:" << &p << std::endl;    return 0;}

输出:

i的地址:0x7ffdd42668ec指针p的值为:0指针p的内存地址为:0x7ffdd42668f0i的地址:0x7ffdd42668ec指针p的值为:0x7ffdd42668ec指针p的内存地址为:0x7ffdd42668f0

指针p的值是保存别人的地址。
指针p的内存地址才是它自身的地址。


我们了解到指针的地址与指针中保存的某个变量的地址是不一样的,每个指针都有一个地址,而在该地址中保存的则是另一个变量的地址。


指针对数值的操作

计算机通过间接运算符“*”访问并且读取到该地址的数据,那么它就可以修改这些数据。

#include <iostream>int main(){    typedef unsigned short int ut;    ut i = 5; //变量i的类型就是 unsigned short int    ut *p = 0;    p = &i;    std::cout << "i:" << i << std::endl;    std::cout << "*p:" << *p << std::endl;    std::cout << std::endl;    std::cout << "用指针来修改存放在i中的数据!\n";    *p = 90;    std::cout << "i:" << i << std::endl;    std::cout << "*p:" << *p << std::endl;    std::cout << std::endl;    std::cout << "用i来修改存放在i中的数据!\n";    i = 9;    std::cout << "i:" << i << std::endl;    std::cout << "*p:" << *p << std::endl;    return 0;}

输出:

i:5*p:5用指针来修改存放在i中的数据!i:90*p:90i来修改存放在i中的数据!i:9*p:9

更换指针保存的地址

#include <iostream>int main(){    int i = 0;    int j = 1;    int *p = &i;    std::cout << " i:\t" << i  << "\t&i:\t" << &i << std::endl;    std::cout << "*p:\t" << *p << "\t p:\t" << p << std::endl;    p = &j;    //更换指针保存地址    std::cout << "更换地址后..." << std::endl;    std::cout << " j:\t" << j << "\t&j:\t" << &j << std::endl;    std::cout << "*p:\t" << *p << "\t p:\t" << p << std::endl;    return 0;}

输出:

 i: 0   &i: 0x7ffe9a21ac38*p: 0    p: 0x7ffe9a21ac38更换地址后... j: 1   &j: 0x7ffe9a21ac3c*p: 1    p: 0x7ffe9a21ac3c

为什么使用指针

既然通过变量名就可以访问数据,为什么还要使用繁琐而又容易出错的指针呢?

这是因为在操作大型数据和类时,由于指针可以通过内存地址直接访问数据,从而避免在程序中复制大量的代码,因此指针的效率最高,一般说来,指针会有三大用途。
1.处理堆中存放的大型数据。
2.快速访问类的成员数据和函数。
3.以别名的方式向函数传递参数。


栈和堆

一般来说,程序就是与数据打交道,在执行某一功能的时候,将该功能所需要的数据加载到内存中,然后在执行完毕的时候释放掉该内存。

数据在内存中存放共分以下几个形式:
1.栈区(stack):由编译器自动分配并且释放,该区域一般存放函数的参数值、局部变量的值等。

2.堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统回收。

3.寄存器区:用来保存栈顶指针和指令指针。

4.全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量是在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后由系统释放。

5.文字常量区:常量字符串就是放在这里的,程序结束后由系统释放。

6.程序代码区:存放函数体的二进制代码。

函数参数和局部变量存放在栈中,当函数运行结束并且返回时,所有的局部变量和参数就都被系统自动清除掉了,为的是释放掉它们所占用的内存空间,全局变量可以解决这个问题,但是全局变量永远不会被释放,而且由于全局变量被所有的类成员和函数所共享,所以它的值很容易被修改。使用堆可以一举解决这两个问题。

堆是采用匿名的方式来保存数据的。只能通过指针来访问到这些匿名的数据。因此它的安全性是最好的。
同时由于堆区中的内存是由程序员来分配和释放的,所以它的自由度也是最高的。


堆和栈的区别

一.内存申请方式上的不同
:由系统自动分配,例如我们在函数中声明一个局部变量int a;那么系统就会自动在栈中为变量a开辟空间。

:需要程序员自己申请,因此也需要指明变量的大小。

二.系统响就不同
:只要栈的剩余空间大于所申请的空间,糸统将为程序提供内存,否则将提示overflow,也就是栈溢出。

:系统收到程序申请空间的要求后,会遍历一个操作糸统用于记录内存空闲地址的链表,当找到一个空间大于所申请空间的堆结点后,就会将该结点从记录内存空闲的链表中删除。并将该结点的内存分配给程序,然后在这块内存区域的首地址处记录分配的大小,这样我们在使用delete来释放内存的时候,delete才能正确地识别并删除该内存区域的所有变量。另外,我们申请的内存空间与堆结点上的内存空间不一定相等,这时系统就会自动将堆结点上多出来的那一部分内存空间回收到空闲链表中。


三.空间大小的不同
:在windows下,栈是一块连续的内存区域,它的大小是2M,也有的是说1M,总之该数值是一个编译时就确定的常数。是由系统预先根据栈顶的地址和栈的最大容量定义好的。假如你的数据申请的内存空间超过了栈的空间,那么就会提示overflow。因此,别指望栈能存储比较大的数据。

:堆是不连续的内存区域。各块区域由链表将它们串联起来。这些串联起来的内存空间叫做堆,它的上限是由系统中有效的虚拟内存来定的。因此获得的空间比较大,而获得空间的方式也比较灵活。


四.执行效率的不同
:栈由系统自动分配,因此速度较快。但是程序员不能对其进行操作。

:堆是由程序员分配的内存,一般速度也较慢,而且容易产生内存碎片,不过用起来很方便。


五.执行函数时的不同
:在函数调用时,第一个进栈的是被调用函数下一行的内存地址。其次是函数的参数,假如参数多于一个,那么次序是从右往左,最后才是函数的局部变量。
由于栈的先进后出原则,函数结束时正好与其相反,首先是局部变量先出栈,然后是参数,次序是从左到右,这时所有的变量都已出栈,指针自然地指到第一个进栈的那行内存地址,也就是被调用函数的下一行内存地址。程序根据该地址跳转到被调用函数的下一行自动执行。
由于栈的先进后出原则,所以它永远都不可能产生内存碎片。它们排列的是如此有序,弹出时也非常有序,碎片想要产生也是非常艰难的。

:堆是一大堆不连续的内存区域,在系统中由链表将它们串接起来,因此在使用的时候必须由程序员来安排。它的机制是很复杂的,有时候为了分配一块合适的内存,程序员需要按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有满足条件的空间,那么就要向系统发出申请增加一部分内存空间,这样就才有机会分到足够大小的内存,然后将计算后的数值返回。显然,堆的运行效率比栈要低的多,而且也容易产生碎片。但是好处是堆可以存储相当大的数据,并且一些细节也可以由程序员来安排。


总结

栈的内存小,但是效率高,不过存储的数据只在函数内有效,超出函数就消失了。

堆的可存储空间可非常大,但是容易产生内存碎片,效率也较低,好处是灵活性比较强。比如说我们需要创建一个对象,能够被多个函数所访问,但是又不想使其成为全局的,那么这个时候创建一个堆对象无疑是良好的选择。

由于堆和栈各有优缺点,因此好多时候我们是将堆和栈结合使用的,比如在存储一些较大数据的时候,我们将数据存放在堆中,却将指向该数据的指针放到栈中。这样可以有效地提高程序的执行速度,避免一些不该有的碎片。不过,一般来说,假如不是特大的数据,我们都是使用栈,比如:函数调用过程中的参数,返回地址,和局部变量都存放在栈中。这样可以大大加快程序的运行速度。


指针与堆

堆的好处是可以存储比较大的数据,而存储的数据只要不是程序员手动将其释放那么就会永远保存在堆中,不像栈,存储的数据只在函数内有效,超出函数就消失了。也不像全局变量,保存的数据只有程序结束才会释放,而且很容易被修改。

为了数据隐秘起见,堆中的每个内存单元都是匿名的,因此你必须先在堆中申请一个内存单元的地址,然后把它保存在一个指针中。这样你只有使用该指针才可以访问到该内存单元的数据。

采取这种匿名的内存访问方式,而不是使用公开的全局变量,好处是只有使用特定的指针才能访问特定的数据。这样就避免了任何试图修改它的非法操作。

要做到这一点,我们首先得创建一个堆,然后定义一个指向该堆的指针。这样就只能通过该指针才能访问堆中数据。

c++中使用关键字new 创建一个堆并分配内存,在new后面跟一个要分配的对象类型,编译器根据这个类型来分配内存。
我们来看示例:

int *p;p = new int;

第一行定义了一个指向整型的指针变量p。
第二行用new关键字在堆中创建一个int类型的区域。假如这个内存区域创建成功,那么它会返回一个地址,我们使用指针p来保存这个返回的地址。这样指针p它所指向的就是这块在堆中创建的内存区域。

注:堆中创建内存的大小,是根据new关键字后面的类型来决定的。

两条语句也可以合并为一条语句。

    int *p = new int;    *p = 5;    std::cout << "*p: " << *p << std::endl;

输出:

*p: 5

第二行,我们使用指针p将5这个数据存储到了这个在堆中创建的区域中。


注:由于计算机的内存是有限的,因此可能会出现没有足够内存而无法满足new的请求,在这种情况下,new会返回0,该值被赋给指针后,那么该指针就是一个空指针,空指针不会指向有效数据。new除了返回空值之外,还会引发异常


用指针删除堆中空间

关键字:delete

用delete来删除指针指向的堆中空间。

由于使用new创建的内存空间不会被系统自动释放,因此假如你不去释放它,那么该区域的内存将始终不能为其他数据所使用,而指向该内存的指针是局部变量,当定义该指针的函数结束并返回时,指针也就消失了,那么我们就再也找不到该块内存区域。

假如指向该内存区域的指针自动消失了,计算机就再也找不到该区域了,就好像是丢失了这块内存一样,我们把这种情况叫做内存泄漏

这种糟糕的情况将一直持续到程序结束,该区域的内存才能恢复使用。因此假如你不需要一块内存空间,那么就必须对指向它的指针使用关键字:delete

    int *p = new int;    delete p;     //释放指针p所指向的堆中内存    p = 0;

这将释放指针所指向的内存,而不会释放指针,因此你还可以使用该指针。

注:使用delete后,该指针内所保存的地址是一个随机地址,所以使用delete后,还需将该指针赋0.

注:一个new对应一个delete,如果对同一个内存释放两次,那么程序就会崩溃。因为指针所指的内存区域已经被释放了。
不过,如果我们将该指针赋为0的话,那么删除一个空指针将是安全的。

    int *p = new int;    delete p;     //释放内存    p = 0;    delete p;

#include <iostream>int main(){    int *p = new int;    *p = 3600;    std::cout << "*p: " << *p << std::endl;    delete p;    std::cout << "*p: " << *p << std::endl;    p = 0;    p = new int;    *p = 8;    std::cout << "*p: " << *p << std::endl;    delete p;    return 0;}

输出:

*p: 3600*p: 5533912*p: 8

内存泄漏

假如没有删除一个指针就对其重新赋值 ,这样就会造成内存泄漏。如:

    int *p = new int;    p = new int;

第一行定义了一个指针p并使其指向一块内存空间。
第二行又将一块新的内存空间的地址赋给了p。这样第一行所开辟的那块内存空间就无法再使用了,因为指向它的指针现在已经指向了第二块空间。

指针变量p只能保存一个地址,对它重新赋值则表示以前的地址被覆盖,假如该地址的内存空间没有被释放,那么你将无法再次通过指针p访问它。因为此时的指针变量p记录的是第二块内存的地址。

正确的方式:

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

假如暂时不想删除第一块内存空间,那么你可以这么做:

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

分别用两个指针来指向两块内存空间,这样每块空间都有一个指针来指向,也就不会造成找不到某块空间的内存泄漏现象。

注:你在使用new以后,假如不再使用该块内存空间,那么一定要用delete来释放它。


在堆中创建对象

我们既然可以在堆中保存变量,那么也就可以保存对象,我们可以将对象保存在堆中,然后通过指针来访问它。

    Human *p;    p = new Human;

第一行定义了一个Human类的指针p。
第二行使用new创建一块内存空间,同时又调用了Human类的默认构造函数来构造一个对象,它所占用的内存大小根据Human类对象的成员变量来决定,假如该类有两个int型成员变量,那么该对象占用的为2*4=8个字节,构造函数一般都是在创建对象时被自动调用,它的作用就是初始化该对象的成员数据。
本行的右半部分创建一个对象完毕后,跟着将该对象的内存地址赋给左边的指针变量p。

两行语句也可以合并为一行:

Human *p = new Human;

这样,我们在定义一个Human类指针p的同时将它的值初始化为新建Human类对象的内存地址。


#include <iostream>class Human{public:    Human() {std::cout << "构造函数在执行...\n"; i = 999;}private:    int i;};int main(){    Human *p = new Human;    return 0;}

输出:

构造函数在执行...

由于创建对象时会自动调用类的构造函数来初始化对象成员数据,因此调用了这一行。

Human() {std::cout << "构造函数在执行...\n"; i = 999;}

在堆中删除对象

假如我们要删除在堆中创建的对象,我们可以直接删除指向该对象的指针,这样会自动调用对象的析构函数来销毁该对象同时释放内存。

#include <iostream>class Human{public:    Human() {std::cout << "构造函数在执行...\n"; i = 999;}private:    int i;};int main(){    Human *p = new Human;    delete p;   //删除堆中创建的对象,并释放内存    return 0;}

输出:

构造函数在执行...析构函数在执行...

访问堆中数据成员

假如我们要访问栈中对象的数据成员和函数,我们使用成员运算符”.”。

如果我们要访问堆中对象的数据成员和函数,我们使用

(*p).get();

使用括号是为了保证先使用”*”星号读取p的内存地址中的值 ,即堆中对象,然后再使用成员运算符”.”来访问成员函数get()。

例:

#include <iostream>class Human{public:    Human() {std::cout << "构造函数在执行...\n"; i = 999;}    ~Human() {std::cout << "析构函数在执行...\n";}    inline int get() const {return i;}private:    int i;};int main(){    Human *p = new Human;    std::cout << "i: " << (*p).get() << std::endl; //使用成员指针运算符访问堆中对象成员    delete p;   //删除堆中创建的对象    return 0;}

输出:

构造函数在执行...i: 999析构函数在执行...

但是这样做会比较麻烦的,因此c++专门为用指针来间接访问对象的成员设置了一个运算符:成员指针运算符”->”
该符号可以实现读取对象的内存地址并访问该对象的成员的作用。

因此(*p).get()可以简化为:

p->get();

例:

#include <iostream>class Human{public:    Human() {std::cout << "构造函数在执行...\n"; i = 999;}    ~Human() {std::cout << "析构函数在执行...\n";}    inline int get() const {return i;}private:    int i;};int main(){    Human *p = new Human;    std::cout << "i: " << p->get() << std::endl; //使用成员指针运算符访问堆中对象成员    delete p;   //删除堆中创建的对象    return 0;}

输出:

构造函数在执行...i: 999析构函数在执行...

在构造函数中开辟内存空间

我们可以将类的数据成员定义为一个指针,然后在构造函数中开辟新空间,将该空间的地址赋给指针。而在析构函数中释放该内存。

#include <iostream>class Human{public:    Human() {std::cout << "构造函数在执行...\n"; i = new int(999);}//在构造函数中开辟的新空间,并把地址赋给指针i。    ~Human() {std::cout << "析构函数在执行...\n"; delete i;} //在析构函数中释放指针i所指向的堆中内存空间    inline int get() const {return *i;} //注意这里返回的是指针所指向地址里的值private:    int *i;     //指针};int main(){    Human *p = new Human;    std::cout << "i: " << (*p).get() << std::endl; //使用成员指针运算符访问堆中对象成员    delete p;   //删除堆中创建的对象    return 0;}

输出:

构造函数在执行...i: 999析构函数在执行...

注:在构造函数中开辟的新空间,由于int是整型,不是类对象,所以new int(999)不会调用构造函数,而是将999这个数值存储到新建的内存区域中。
这样,在构造函数中开辟新空间,将该空间的地址赋给指针。
对象被销毁以后,指针i就访问不到了,它指向的新空间自然也就访问不到了,出现了内存泄漏。所以我们需要在析构函数中释放这块指针i所指的堆中内存空间。
当对象被销毁时,自动调用析构函数,也就释放掉了指针i所指的堆中空间。

该例仅仅是为了说明构造函数中也可以开辟堆中空间,在实际程序中,一个在堆中创建的对象通过成员指针再创建新空间用来保存数据并没有什么意义。
因为在堆中创建对象时已经为它的所有数据成员提供了保存的空间。


对象在栈与堆中的不同

一个存储在栈中的对象,如:

Human Jack;

会在超出作用域时,比如说遇到右大括号,自动调用析构函数来释放该对象所占用的内存。

而一个存储在堆中的对象,如:

Human *Jack = new Human;

则需要程序员自行对其所占用的内存进行释放。否则该对象所占用的内存直到程序结束才会被系统回收。

所以需要用delete来释放对象。

Human *Jack = new Human;delete Jack;

这就是对象在栈与堆中的不同,对象在栈中由系统自动回收,对象在堆中由程序员手动使用delete来释放。


this指针

学生在发新课本时一般都要将自己的名字写在课本上,以说明该课本是自己的,避免与别的学生混淆,同样对象也要在属于自己的每个成员身上写下自己的名字,以证明该成员是自己的成员,而不是别的对象的成员。
this变量帮助对象做到这一点,this变量记录每个对象的内存地址,然后通过间接访问运算符”->”访问该对象的成员。

例:

#include <iostream>class A{public:    A() {}    inline int Get() const {return i;}    inline void Set(int x) {this->i = x; std::cout <<"this变量保存的内存:\t" << this << std::endl;}private:    int i;};int main(){    A a;    a.Set(9);    std::cout << "对象a的内存地址:\t" << &a << std::endl;    std::cout << "i:\t" << a.Get() << std::endl;    std::cout << std::endl;    A b;    b.Set(999);    std::cout << "对象b的内存地址:\t" << &b << std::endl;    std::cout << "i:\t" << b.Get() << std::endl;    return 0;}

输出:

this变量保存的内存:    0x28feac对象a的内存地址:       0x28feaci:      9this变量保存的内存:    0x28fea8对象b的内存地址:       0x28fea8i:      999

这说明this变量记录每个单独的对象的内存地址,而this指针则指向每个单独的对象。
因此不同的对象输出的this变量的内存地址也不同。

在默认情况下,this指针可以省略不写。
第8行,this->i=x;假如我们写i=x;编译器会自动在成员变量i前面加上this指针,用来表示这个i成员是属于某个对象的。

由于this指针保存了对象的地址,因此你可以通过该指针直接读取某个对象的数据,那么this指针就是指向对象的指针。另外this指针的创建与删除由编译器来完成,不需要操心。


指针的常见错误

删除一个指针后一定要将该指针设置为空指针,这是因为删除该指针只会释放它所指向的内存空间,不会删除指针,因此这个指针还存在,并且它仍然会指向原来的内存空间,因此这时如果你再次尝试使用该指针,那么将会导致程序出错。

#include <iostream>int main(){    int *p = new int;    *p = 3;    std::cout << "将3赋给p的地址后,指针p读取的值:\t" << *p << std::endl;    delete p;   //删除堆中空间后,指针p变成了迷途指针    std::cout << "删除空间后,指针p读取的值是:\t\t" << *p << std::endl;    std::cout << std::endl;    long int *p1 = new long int;    std::cout << "创建新空间后,指针p中保存的地址:\t" << p << std::endl;    *p1 = 99999;    std::cout << "指向新空间的指针p1保存的地址:\t\t" << p1 << std::endl;    *p = 23;    std::cout << "将23赋给p的地址后,指针p读取的值:\t" << *p << std::endl;    std::cout << "将23赋给p的地址后,指针p1读取的值:\t" << *p1 << std::endl;    delete p1;    return 0;}

输出:

将3赋给p的地址后,指针p读取的值:       3删除空间后,指针p读取的值是:           3567832创建新空间后,指针p中保存的地址:       0x3614b0指向新空间的指针p1保存的地址:          0x3614b0将23赋给p的地址后,指针p读取的值:      23将23赋给p的地址后,指针p1读取的值:     23

当delete p;时,释放了指针p所指的堆中内存空间,此时的指针p是迷途指针。并且等于告诉编译器可以用该内存区域保存其他数据。
因此随机数3567832被保存在了该区域。
由于编译器会默认将释放掉的内存空间回收然后分配给新开辟的空间。
因此第15行新定义的指向新开辟的long型变量空间的指针p1,这个p1其实指向的是释放掉的空间,也就是p所指向的空间。