指针数组和数组指针文档格式.docx

上传人:b****6 文档编号:16833774 上传时间:2022-11-26 格式:DOCX 页数:10 大小:22.62KB
下载 相关 举报
指针数组和数组指针文档格式.docx_第1页
第1页 / 共10页
指针数组和数组指针文档格式.docx_第2页
第2页 / 共10页
指针数组和数组指针文档格式.docx_第3页
第3页 / 共10页
指针数组和数组指针文档格式.docx_第4页
第4页 / 共10页
指针数组和数组指针文档格式.docx_第5页
第5页 / 共10页
点击查看更多>>
下载资源
资源描述

指针数组和数组指针文档格式.docx

《指针数组和数组指针文档格式.docx》由会员分享,可在线阅读,更多相关《指针数组和数组指针文档格式.docx(10页珍藏版)》请在冰豆网上搜索。

指针数组和数组指针文档格式.docx

“下标表达式”不是常量表达式,即它的值不必在编译时确定,可以在运行时确定。

这就是堆的一个非常显著的特点,有的时候程序员本身都不知道要申请能够多少内存的时候,堆就变的格外有用。

2、释放数组空间:

delete[]指向该数组的指针变量名;

方括号非常重要的,如果delete语句中少了方括号,因编译器认为该指针是指向数组第一个元素的,会产生回收不彻底的问题(只回收了第一个元素所占空间),我们通常叫它“内存泄露”,加了方括号后就转化为指向数组的指针,回收整个数组。

delete[]的方括号中不需要填数组元素数,系统自知。

即使写了,编译器也忽略。

<

Thinkinc++>

>

上说过以前的delete[]方括号中是必须添加个数的,后来由于很容易出错,所以后来的版本就改进了这个缺陷。

下面是个例子,VC上编译通过

#include<

iostream>

usingnamespacestd;

//#include<

iostream.h>

 //forVC

#include<

string.h>

voidmain(){

intn;

char*p;

cout<

"

请输入动态数组的元素个数"

endl;

cin>

n;

//n在运行时确定,可输入17

p=newchar[n];

//申请17个字符(可装8个汉字和一个结束符)的内存空间strcpy(pc,“堆内存的动态分配”);

//

p<

delete[]p;

//释放pc所指向的n个字符的内存空间return;

 

}

通过指针使堆空间,编程中的几个可能问题

1.动态分配失败。

返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败。

data=newdouble[m];

//申请空间

if((data)==0)……//或者==NULL

2.指针删除与堆空间释放。

删除一个指针p(deletep;

)实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除p本身,释放堆空间后,p成了空悬指针,不能再通过p使用该空间,在重新给p赋值前,也不能再直接使用p。

3.内存泄漏(memoryleak)和重复释放。

new与delete是配对使用的,delete只能释放堆空间。

如果new返回的指针值丢失,则所分配的堆空间无法回收,称内存泄漏,同一空间重复释放也是危险的,因为该空间可能已另分配,而这个时候又去释放的话,会导致一个很难查出来的运行时错误。

所以必须妥善保存new返回的指针,以保证不发生内存泄漏,也必须保证不会重复释放堆内存空间。

4.动态分配的变量或对象的生命期。

无名变量的生命期并不依赖于建立它的作用域,比如在函数中建立的动态对象在函数返回后仍可使用。

我们也称堆空间为自由空间(freestore)就是这个原因。

但必须记住释放该对象所占堆空间,并只能释放一次,在函数内建立,而在函数外释放是一件很容易失控的事,往往会出错,所以永远不要在函数体内申请空间,让调用者释放,这是一个很差的做法。

你再怎么小心翼翼也可能会带来错误。

类在堆中申请内存:

通过new建立的对象要调用构造函数,通过deletee删除对象要调用析构函数。

CGoods*pc;

pc=newCGoods;

//分配堆空间,并构造一个无名对象

//的CGoods对象;

…….

deletepc;

//先析构,然后将内存空间返回给堆;

堆对象的生命期并不依赖于建立它的作用域,所以除非程序结束,堆对象(无名对象)的生命期不会到期,并且需要显式地用delete语句析构堆对象,上面的堆对象在执行delete语句时,C++自动调用其析构函数。

正因为构造函数可以有参数,所以new后面类(class)类型也可以有参数。

这些参数即构造函数的参数。

但对创建数组,则无参数,并只调用缺省的构造函数。

见下例类说明:

classCGoods{

charName[21];

int 

Amount;

floatPrice;

floatTotal_value;

public:

CGoods(){};

//缺省构造函数。

因已有其他构造函数,系统不会再自动生成缺省构造,必须显式声明。

CGoods(char*name,intamount,floatprice){

strcpy(Name,name);

Amount=amount;

Price=price;

Total_value=price*amount;

  }

……};

//类声明结束

下面是调用机制 :

CGoods*pc,*pc1,*pc2;

pc=newCGoods(“hello”,10,118000);

//调用三参数构造函数 

pc1=newCGoods();

//调用缺省构造函数 

”输入商品类数组元素数”<

pc2=newCGoods[n];

//动态建立数组,不能初始化,调用n次缺省构造函数 

……

deletepc1;

delete[]pc2;

申请堆空间之后构造函数运行;

释放堆空间之前析构函数运行;

再次强调:

由堆区创建对象数组,只能调用缺省的构造函数,不能调用其他任何构造函数。

如果没有缺省的构造函数,则不能创建对象数组。

---------------------下面我们再来看一下指针数组和数组指针―――――――――――――

如果你想了解指针最好理解以下的公式:

(1)int*ptr;

//指针所指向的类型是int

  

(2)char*ptr;

//指针所指向的的类型是char

(3)int**ptr;

//指针所指向的的类型是int*(也就是一个int*型指针)

(4)int(*ptr)[3];

//指针所指向的的类型是int()[3]//二维指针的声明

(1)指针数组:

一个数组里存放的都是同一个类型的指针,通常我们把他叫做指针数组。

比如int*a[10];

它里边放了10个int*型变量,由于它是一个数组,已经在栈区分配了10个(int*)的空间,也就是32位机上是40个byte,每个空间都可以存放一个int型变量的地址,这个时候你可以为这个数组的每一个元素初始化,在,或者单独做个循环去初始化它。

例子:

int*a[2]={newint(3),newint(4)};

//在栈区里声明一个int*数组,它的每一个元素都在堆区里申请了一个无名变量,并初始化他们为3和4,注意此种声明方式具有缺陷,VC下会报错

例如 :

int*a[2]={newint[3],newint[3]};

deletea[0];

deleta[10];

但是我不建议达内的学生这么写,可能会造成歧义,不是好的风格,并且在VC中会报错,应该写成如下 :

int*a[2];

a[0]=newint[3];

a[1]=newint[3];

这样申请内存的风格感觉比较符合大家的习惯;

由于是数组,所以就不可以deletea;

编译会出警告.delete 

a[1];

注意这里是一个数组,不能delete[];

(2)数组指针 :

一个指向一维或者多维数组的指针;

int*b=newint[10];

 指向一维数组的指针b;

注意,这个时候释放空间一定要delete[],否则会造成内存泄露,b就成为了空悬指针.

int(*b2)[10]=newint[10][10];

注意,这里的b2指向了一个二维int型数组的首地址.

在这里,b2等效于二维数组名,但没有指出其边界,即最高维的元素数量,但是它的最低维数的元素数量必须要指定!

就像指向字符的指针,即等效一个字符串,不要把指向字符的指针说成指向字符串的指针。

这与数组的嵌套定义相一致。

int(*b3)[30][20];

//三级指针――>

指向三维数组的指针;

int(*b2)[20];

//二级指针;

b3=newint[1][20][30];

b2=newint[30][20];

两个数组都是由600个整数组成,前者是只有一个元素的三维数组,每个元素为30行20列的二维数组,而另一个是有30个元素的二维数组,每个元素为20个元素的一维数组。

删除这两个动态数组可用下式:

delete[]b3;

//删除(释放)三维数组;

delete[]b2;

//删除(释放)二维数组;

再次重申:

这里的b2的类型是int(*),这样表示一个指向二维数组的指针。

b3表示一个指向(指向二维数组的指针)的指针,也就是三级指针.

(3)二级指针的指针

看下例:

int(**p)[2]=new(int(*)[3])[2];

p[0]=newint[2][2];

p[1]=newint[2][2];

p[2]=newint[2][2];

delete[]p[0];

delete[]p[1];

delete[]p[2];

delete[]p;

注意此地方的指针类型为int(*),碰到这种问题就把外边的[2]先去掉,然后回头先把int**p=newint(*)[n]申请出来,然后再把外边的[2]附加上去;

p代表了一个指向二级指针的指针,在它申请空间的时候要注意指针的类型,那就是int(*)代表二级指针,而int(**)顾名思义就是代表指向二级指针的指针了。

既然是指针要在堆里申请空间,那首先要定义它的范围:

(int(*)[n])[2],n个这样的二级指针,其中的每一个二级指针的最低维是2个元素.(因为要确定一个二级指针的话,它的最低维数是必须指定的,上边已经提到)。

然后我们又分别为p[0],p[1],p[2]…在堆里分配了空间,尤其要注意的是:

在释放内存的时候一定要为p[0],p[1],p[2],单独delete[],否则又会造成内存泄露,在delete[]p的时候一定先deletep[0];

deletep[1],然后再把给p申请的空间释放掉delete[]p……这样会防止内存泄露。

(3)指针的指针;

int**cc=new(int*)[10];

声明一个10个元素的数组,数组每个元素都是一个int*指针,每个元素还可以单独申请空间,因为cc的类型是int*型的指针,所以你要在堆里申请的话就要用int*来申请;

看下边的例子 

(vc&

GNU编译器都已经通过);

int**a=newint*[2];

     //申请两个int*型的空间

        //为a的第二个元素又申请了3个int型空间,a[1]指向了此空间首地址处

a[0]=newint[4];

        ////为a的第一个元素又申请了4个int型空间,a[0]指向了此空间的首地址处

int*b;

a[0][0]=0;

a[0][1]=1;

b=a[0];

delete[]a[0]       //一定要先释放a[0],a[1]的空间,否则会造成内存泄露.;

delete[]a[1];

delete[]a;

b++;

*b<

//随机数

注意 :

因为a是在堆里申请的无名变量数组,所以在delete的时候要用delete[]来释放内存,但是a的每一个元素又单独申请了空间,所以在delete[]a之前要先delete[]掉a[0],a[1],否则又会造成内存泄露.

(4)指针数组 :

   

我们再来看看第二种 :

二维指针数组

int*(*c)[3]=newint*[3][2];

如果你对上边的介绍的个种指针类型很熟悉的话,你一眼就能看出来c是个二级指针,只不过指向了一个二维int*型的数组而已,也就是二维指针数组。

例子 :

int*(*b)[10]=newint*[2][10];

b[0][0]=newint[100];

b[0][1]=newint[100];

*b[0][0]=1;

cout<

*b[0][0]<

//打印结果为1

delete[]b[0][0];

delete[]b[0][1];

delete[]b;

//打印随机数

 这里只为大家还是要注意内存泄露的问题,在这里就不再多说了。

如果看了上边的文章,大家估计就会很熟悉,这个b是一个二维指针,它指向了一个指针数组

第二种 :

int**d[2];

表示一个拥有两个元素数组,每一个元素都是int**型,这个指向指针的指针:

   d不管怎样变终究也是个数组,呵呵,

   如果你读懂了上边的,那下边的声明就很简单了:

   d[0]=newint*[10];

   d[1]=newint*[10];

delete[]d[0];

delete[]d[1];

具体的就不再多说了:

二 :

 函数指针 

关于函数指针,我想在我们可能需要写个函数,这个函数体内要调用另一个函数,可是由于项目的进度有限,我们不知道要调用什么样的函数,这个时候可能就需要一个函数指针;

inta();

这个一个函数的声明;

ing(*b)();

这是一个函数指针的声明;

让我们来分析一下,左边圆括弧中的星号是函数指针声明的关键。

另外两个元素是函数的返回类型(void)和由边圆括弧中的入口参数(本例中参数是空)。

注意本例中还没有创建指针变量-只是声明了变量类型。

目前可以用这个变量类型来创建类型定义名及用sizeof表达式获得函数指针的大小:

unsignedpsize=sizeof(int(*)());

获得函数指针的大小

//为函数指针声明类型定义

typedefint(*PFUNC)();

PFUNC是一个函数指针,它指向的函数没有输入参数,返回int。

使用这个类型定义名可以隐藏复杂的函数指针语法,就我本人强烈建议我们大内弟子使用这种方式来定义;

下面是一个例子,一个简单函数指针的回调(在GNU编译器上通过,在VC上需要改变一个头文件就OK了)

//GNU编译器g++实现

/* 

//vc的实现

#include"

stdafx.h"

*/

#defineDF(F)intF(){ 

thisisinfunction"

#F<

\

return0;

\

//声明定义DF(F)替代intF();

函数;

DF(a);

DF(b);

DF(c);

DF(d);

DF(e);

DF(f);

DF(g);

DF(h);

DF(i);

//声明定义函数abcdefghi

//int(*pfunc)();

//一个简单函数指针的声明

typedefint(*FUNC)();

//一个函数指针类型的声明

FUNCff[]={a,b,c,d,e,f,g,h,i};

//声明一个函数指针数组,并初始化为以上声明的a,b,c,d,e,f,g,h,i函数

FUNCfunc3(FUNCvv){ 

//定义函数func3,传入一个函数指针,并且返回一个同样类型的函数指针

vv();

returnvv;

/*FUNCfunc4(int(*vv)()){ 

//func3的另一种实现

}*/

intmain(){

for(inti=0;

i<

sizeof(ff)/sizeof(FUNC);

i++){ 

//循环调用函数指针

FUNCr=func3(ff[i]);

cout<

r()<

//输出返回值,只是返回了0

}

到目前为止,我们只讨论了函数指针及回调而没有去注意ANSIC/C++的编译器规范。

许多编译器有几种调用规范。

如在VisualC++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。

C++Builder也支持_fastcall调用规范。

调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。

好了,先到此为止吧,写这篇文章耗费了基本上快半天的时间了,很多事情还没有做,等改天有时间再回来整理,所有的源程序都放在openlab3服务器上我的目录下lib/cpp下,大家可以去拿。

不知道的登陆openlab3然后cd~chengx/lib/cpp就可以看到了。

还有很复杂的声明可能也是一种挑战比如<

里的

int(*(*f4())[10]();

的声明,f4是一个返回指针的函数,该指针指向了含有10个函数指针的数组,这些函数返回整形值;

不是这个函数有特别之处,而是BruceEckel说的“从右到左的辨认规则”是一种很好的方法,值得我们去学习,感谢他:

最后我想应该跟大家说一下,写程序应该就象JERRY所说的:

简单就是美;

我们应该遵循一个原则:

KISS(KeepItSimple,Stupid,尽量保持程序简单出自:

《PracticalCprogramming》),把自己的程序尽量的简单明了,这是个非常非常好的习惯。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高中教育 > 语文

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1