2021-7-19 指针:强大而危险的灵魂【C++指针的应用---中上】(炉边小坐)

指针是一个变量,我们反复强调这个概念,因为它真的很重要.既然是变量,那应该就可以参与运算,而指针变量较为复杂,它只参与+和-运算,事实上,指针变量可以参与自增自减运算,但是指针常量/const 型指针无法参加自增自减运算,但const*int (iP++)是允许的,关于 const 型我们后文详细讲解.尤其要注意的是,虽然数组名是一个指针,但他却是个指针常量,对于数组名不能运用自加自减运算符,虽然   *(Name++)是可实现的,但是明显 Name[ ]会效率更高.而且不要妄图给数组名赋值,原因请看上篇.

也许是上上篇炉边小坐?我们聊到了堆内存.堆(heap)是一个内存空间,区别于栈\全局数据区和代码区,它允许程序在运行(Run)时申请储存空间而不是编译时,注意,是申请,或者说声明,而不是分配,编译时是不分配内存的。此时只是根据声明时的类型进行占位,到以后程序执行时分配内存才会正确。所以声明是给编译器看的,聪明的编译器能根据声明帮你识别错误。这就突出了堆内存的特殊性,它允许运行时而不是编译时声明/申请内存.

举个例子:一般来说,一旦定义了一个数组,无论这个数组是局部(在栈区分配)还是全局(全局数据区分配),它的大小都根据声明/数组内容决定好了,但是在编写程序的时候,数组大小并不一定是事先知道的,例如需要用户来输入的数组,当然你可以通过定义一个远超预期需要大小的数组避免大小不足,缺点是造成内存浪费;另一种是在运行时根据实际情况向系统获取/请求内存,这种在运行中申请的内存被称为堆内存,也称动态内存.

在学习 C语言的时候,老师就讲述了申请动态内存的函数 malloc( ),它在  stdlib.h  或者  cstdlib  头文件中被声明.

void*malloc(size_t size) 

size_t就是unsign long,该函数就是从堆中"切下"一块 size 大小的内存,将该内存的地址返回,内存中的数据未指定(可能是垃圾数据)

//heap 2021-7-17 21:36
#include<iostream>
#include <cstdlib>
using namespace std;
int main()
{
    int n;
    cout<<"Please enter the number:"<<endl;
    int Num;
    cin>>Num;

    n=Num*sizeof (int);
    int*ip=(int*)malloc(n);
    //堆内存分配,注意 malloc 原型是 void 型,返回是地址,若有调用这些数据要将其赋予一个指针,且注意赋值时要强制类型转换

    //输出模块
    for(int Ct=0;Ct<Num;Ct++)
        ip[Ct]=Ct*2;
    for(int Ct=0;Ct<Num;Ct++)
        cout<<ip[Ct]<<" ";
    cout<<endl;
    return 0;
}

这是一个获取堆内存并从堆内存中获取一个数组的程序

"/Users/yuwenao/C++ Learning/cmake-build-debug/untitled5"
Please enter the number:
10
0 2 4 6 8 10 12 14 16 18 

进程已结束,退出代码为 0

结果如上.

程序编译和连接时,在栈中分配了Num整型变量和ip整型指针空间。程序运行中,调用函数 malloc( )并以键盘输入的整数值作为参数。malloc( )函数在堆中寻找未被使用的内存,找够所需的字节数后返回该内存的起始地址。因为 malloc( )函数并不知道用这些内存干什么,所以它返回一个没有类型的指针。但对整数指针ip来说,malloc( )函数的返回值(记得它原型的返回值是 void 型)必须显式转换(强制类型转换)成整数类型指针才能被接受.(ANSI C++标准)。

一个拥有内存的指针完全可以被看作一个数组,而且位于堆中的数组和位于栈中的数组结构是一样的。在上面的代码中,ip申请到堆内存后,在据赋值中的表达式,"ip[Ct]=Ct*2"正是这样应用的.

但是上例中并没有保证一定可以从堆中获得所需内存。有时,系统能提供的堆空间不够分,这时系统会返回一个空指针值NULL.这时所有对该指针的访问都是破坏性的,准确来说,是"野指针",因此调用malloc()函数更完善的代码应该加上这段,作为保险措施:

if(ip=(int*)malloc(n)==NULL)
    {
        cout<<"can't get more memory,create pointer failed!\n";
        exit(1);
    }

在分配的堆内存使用完后,我们要释放这个空间,如果久而久之没有释放空间,就会导致堆内存饱和,导致程序奔溃或者死机.

void free(void*)

我们使用这一函数来释放空间,void*就是上文的 ip,则完整版程序如下:

//heap 2021-7-17 22:15
#include<iostream>
#include <cstdlib>
using namespace std;
int main()
{
    int n;
    cout<<"Please enter the number:"<<endl;
    int Num;
    cin>>Num;

    n=Num*sizeof (int);
    int*ip=(int*)malloc(n);
    if(ip==0)//=(int*)malloc(n)==NULL)//预防措施
    {
        cout<<"can't get more memory,create pointer failed!\n";
        exit(1);
    }
    //堆内存分配,注意 malloc 原型是 void 型,返回是地址,若有调用这些数据要将其赋予一个指针,且注意赋值时要强制类型转换

    //输出模块
    for(int Ct=0;Ct<Num;Ct++)
        ip[Ct]=Ct*2;
    for(int Ct=0;Ct<Num;Ct++)
        cout<<ip[Ct]<<" ";
    cout<<endl;
    free(ip);//释放空间
    return 0;
}

以上的 malloc 与 free 是 C 和 C++共有的声明/释放堆内存的函数,下面介绍 C++所独有的 new 和 delete 函数,他们不用头文件声明,new 与 malloc 类似而又不同的是,它返回一个带有操作数数据类型的指针  ,这说明上面程序的这条语句可以改写为:

int*ip=(int*)malloc(Num*sizeof (int));
//---------------------------
int*ip=new int[Num];//<-----|

可见,new 比 malloc 来简练得许多.因为 new 的返回值赋予其他指针时无需显示类型转换,只需类型匹配即可.

返还空间的 delete 与 free 类似,delete 的操作数与 free 相似,是 new 返回的指针,当返还的是 new 分配的数组时,应该带上[ ],如:

free(ip);//释放空间
|
delete[]ip;

虽然 new 和 delete 性能略逊于前者,但安全性得到大大提升.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值