文档库 最新最全的文档下载
当前位置:文档库 › C++指针与动态分配内存new关键字专题

C++指针与动态分配内存new关键字专题

C++指针与动态分配内存new关键字专题
C++指针与动态分配内存new关键字专题

本文作者:黄邦勇帅

本文是学习C++的基础内容,指针是C或C++所特有的,因此应熟练掌握指针的使用,本文集中介绍C或C++中的各种指针,包括指针数组,数组指针,常量(const)指针,指向指针的指针,尤其是对二维数组和指针进行了详细精辟的解释,在读完本文的二维数组和指针的讲解之后,相信你就会对指针有一个车底的了解了。

本文内容完全属于个人见解与参考文现的作者无关,其中难免有误解之处,望指出更正。

声明:禁止抄袭本文,若需要转载本文请注明转载的网址,或者注明转载自“黄邦勇帅”。

主要参考文献:

1、C++.Primer.Plus.第五版.中文版[美]Stephen Prata著孙建春韦强译人民邮电出版社2005年5月

2、C++.Primer.Plus.第四版.中文版Stanley B.Lippman、Barbara E.Moo著李师贤等译人民邮电出版社2006年3月

3、C++.Primer.Plus.第三版.中文版Stanley B.Lippman等著潘爱民张丽译中国电力出版社2002年5月

4、C++入门经典第三版[美]Ivor Horton著李予敏译清华大学出版社2006年1月

5、C++参考大全第四版[美]Herbert Schidt著周志荣朱德芳于秀山等译电子工业出版社2003年9月

6、21天学通第四版C++ [美]Jesse Liberty著康博创作室译人民邮电出版社2002年3月

第一部分:指针

11.1 基础

1.指针是一个变量,它存储着另一个变量或函数的地址,也就是说可以通过指针间接地引用变量。指针变量包含一个地址,而且可以存储任何数据类型的内存地址,但指针变量却被声明为特定的数据类型,一个指向整型数据类型的指针不能存储一个浮点型的变量地址。

2.指针声明的形式为,数据类型*指针变量名;其中*星号是指针运算符,例如int *x;声明x为int型指针.11.2 指针运算符*和&地址运算符

1.&地址运算符是一元运算符,能反回它的操作数的内存地址.如y=&x;把变量x的地址输入到y中,它与x的值无关,比如x的值为1000,而x的地址为55则,y将接收到地址55.

2.*指针运算符是一元运算符,它是&运算符的相反形式,*运算符能反回位于其操作数所指定的地址的变量的值.例如y = &x;z = *y;假设x的值为1000,地址为55,则第二条语句说明z的值为1000,*y把由y所指向的内存的地址的变量x的值赋给z。*运算符可理解为“在地址中”,则z=*y可描术为“z接收了在址址y中的值。”,3.其实可以把*y当成一个变量来使用,即可以为*y赋值等,例如*y=100;(*y)++;等,但要注意的是对*y的操作相当于是对此指针指向的地址中的变量的操作,即对*y=100的赋值语句,相当于是x=100,而(*y)++则相当于x++。11.3 指针的运算

0.指针只支持4种算术运算符:++,――,+,-.指针只能与整数加减.指针运算的原则是:每当指针的值增加时,它将指向其基本类型的下一个元素的存储单元.减少时则指向上一个元素的存储单元.

1.++,――运算符,假设int型x的地址为200,且int型占4个字节,定义int *p;p=&x;则p++的地址将是204,而不是201,因为当指针p的值增加时,它都将指向下一个int型数据.减少时也是这样,如p――则,p的地址将是196.2.+,-,运算符,注意两个指针不能相加.例int *p;p=&x;假设x的地址为200,则p+9将的指针地址将是200+4*9=236,即p指向了从当前正指向的元素向下的第9个元素.

3.两指针相减,同类型的一个指针减去另一个指针的值将是两个指针分开的基本类型的元素的个数.

11.4 指针和数组

1.在C++语言中使用没有下标的数组名会产生一个指向数组中第一个元素的指针.如char x[20];char *p;p=x;此语句说明将x数组的第一个元素的地址赋给指针p.

2.*(p+4)和x[4]两句都可以访问数组中第5个元素,这里假设int x[33];int *p;p=x;因为p是指向数组x的第一个元素地址的指针,而p+4就是指向第五个元素的指针,而*(p+4)就是第五的个元素了.

3.p[i]语句相当于*(p+i)或x[i]即数组中第i+1个元素的值,假设char x[20];char *p;p=x;

11.5 字符串常量

在C++中字符串常量会被存储到程序的串表中,所以语句char *p ;p=”hyong”;是合法的,该语句将字符串常量存储在串表中的地址赋给指针变量p .

11.6 指针数组

声明形式int *p[10];该语句声明了10个指针数组,每个数组中存储一个整数值地址.p[2]=&x ;语句为指针变量的第三个元素赋予x 变量的地址,现在要访问x 变量的值需要编写*p[2].即访问指针数组第三个元素的地址指向的变量的值.11.7 空指针

如果指针包含了空(0)值,就假定其未指向任何存储,这样能避免意外使用未初始化的指针.例:int *p=0;初始化为空11.8指向指针的指针语句:int **x ,*y ,z ;

z=25;y=&z ;//z 的地址赋给指针y //注意y 没有加星号*x=&y ;//指针y 的地址赋给

//指向指针的指针x

cout<<**x ;//输出z 的值25cout<<*x ;//输出y 的值或z 的地址50cout<

1.int x=1; const int *p=&x;声明了一个指向int 型常量的指针,也就是说不能用p 来修改x 的值,也就是说*p 的值不能被修改。即语句*p=2将是错误的。虽然p 不能修改x 的值,但可以通过变量x 来修改x 的值。p 的声明并不代表p 指向的值实际上是一个常量,而只是对p 而言这个值是常量。可以让p 指向另一个地址,如int y=2;p=&y;这时p 指向的值为2,但指针p 同样不能修改他所指向的变量y 的值。

2.const int x=1; const int *p=&x;表明既不能用变量x 来改变x 的值,也不能用指针p 来改变变量x 的值。

const int x=1; int *p=&x;这样做将发生错误,因为如果把x 的地址给了p ,而指针p 又修改了x 的值时,这时x 就违反了是常量的规定,所以不允许这样做。

3.int x=1; int * const p=&x;这种方式使得指针p 只能指向x 的地址,即指针p 的地址不能改变,但可以通过指针p 来修改p 所指向的变量x 的值。

4.int x=1;const int * const p=&x;这种方式使得指针p 既不能修改变量x 的值,也不能改变p 所指向的地址。

5.当const 指针用作函数形参时int hyong(const int *p, int x)意味着,函数不能修改传递给指针p 的变量的值。11.10 指针与二维数组(对指针的透彻理解)

1、两条基本准则:

a 、首先要明白,指针运算符的作用,我用一言以概之,你在哪里使用都不会错。指针运算符*的作用是求出*后面所指地址里的值。因此只要*后面的变量表示的是一个地址就可以使用*运算符,来求出这个地址中的值,你不用管这个地址的表示形式是怎样的,只要是地址就可以使用*来求出地址中的值。

b 、[ ]这个运算符的的运算法则是,把左侧的地址加上[ ]内的偏移量然后再求指针运算,注意有[ ]运算符的地方就有个隐含的指针,比如x[2]表示的就是将指针x 偏移2个单位量后再求指针运算。也就说x[2]与*(x+2)是相等的。

2、对二维数组的讲解:

a 、对一维数组地址的详细讲解:

比如一维数组a[5],众所周知,一维数组的数组名a 表示的是第一个元素a[0]的地址,也就是说数组名与&a[0]是等价的,因此数组名a 的地址,并不是这个一维数组的数组的地址。那么&a ,表示的又是什么呢?因为,a 是一个一维数组,所以&a 表示的就是这个一维数组的地址,也就是说&a 中的地址是一个包含有4个元素的一维数组的地址。就好比int i 中的&i ,才表示的是这个变量i 的地址一样。

b 、对二维数组地址的讲解:

比如二维数组b[3][4],我们首先从一维开始分析,众所周知b[0],b[1]分别表示的是二维数组中第一行第一个元素和第二行第二个元素的地址,也就是说b[0]是与&b[0][0]等价的,b[1]是与&b[1][0]等介的。因此数组名就与&b[0]等介的,也就是说数组名表示的是二维数组中第一行所包含的一维数组的地址,说简单一点,就是说二维数组名是二维数组中第一行的行地址。因此二维数组名b 所包含的地址中包含有二维数组中第二维中元素的个数的一维数组,也就是b 的地址中包含一个含有4个元素的一维数组的地址(也就是所谓的数组的数组了)。

c

、对二维数组中地址的相加运算的讲解:

*y 的

内存形式

同样以b[3][4]为例来讲解,在上面讲到b[0]表示的是&b[0][0],因此对b[0]进行相加运算,比如b[0]+1,那么就将使地址偏移一个单位,也就是地址被偏移到了&b[0][1]处,也就是b[0]+1表示的是b[0][1]的地址。上面也讲到数组名b,表示的是一个一维数组的地址,因此对数组名进行偏移,比如b+1,则将使指针偏移一个一维数组的长度,也就是b+1,将是&b[1]的地址,因此b+1的地址,表示的是二维数组中第二行所包含的一维数组的地址,简单点就是第二行的行地址。

d、对二维数组的指针运算:

*b

在上面讲解过,因为b表示的是二维数组中第一行所包含的一维数组的地址,因此*b=*(b+0)=*(&b[0])=b[0],所以*b 表示的是二维数组中第一行第一个元素的地址,即*b=&b[0][0],用语言来描术*(b+0)就是,把数组名的地址偏移0个单位,然后再求这个地址所包含的值。在二维数组中,这个值就是指的b[0],因此这个值是与b[0][0]的地址相等的,结果就是*(b+0)与b是相等的,这在多维数组中是个怪现象,至少本人无法理解这是为什么。因为对同一个地址,进行指针运算得到了不同的结果,比如*b和*b[0],因为b和b[0]都是相同的地址,但对他们进行指针运算却得到了不同的结果,*b得到了&b[0][0]的地址,而*b[0]得到了b[0][0]的值,这是对同一个地址进行指针运算却得到了不同的值,对于这个问题,无法理解。

*(b+1)和*(b+0)

对于*(b+1)也和*(b+0)是同样的道理,*(b+1)=b[1]。我们再来看*b[1],因为*b[1]=*(b[1]+0)=*(&b[1][0])=b[1][0],可以看出,这就是二维数组中第二行第一个元素的地址。

*(*(b+1)+1)

因为*(*(b+1)+1)=*(*(&b[1])+1)=*(b[1]+1)=*(&b[1][0]+1)=*(&b[1][1])=b[1][1],语言描术就是,b+1使地址偏移到了二维数组中第二行所包含的一维数组的地址(或第二行的行地址),然后再对这个行地址求指针(或求值)运算,因此就得到第二行第一个元素的地址,然后再对这个地址偏移一个单位,就得到第二行第二个元素的地址,再对这个地址进行指针运算,就得到了这个元素的值,即b[1][1],其他的内容可以以止类推。

e、对二维数组的指针和[ ]的混合运算

在下面的指针和[ ]的混合计算中,要记住两点关键法则,记住了这两点在哪里计算都不会出错

a、对于像b[1]这样的地址,最好应表示为&b[1][0]再进行偏移计算,比如对于b[1]+1,这不是直接在对b[1]加1,也

就是b[1]+1不等于b[2],因为b[1]表示的是第二行行1个元素的地址,对其加1,应该表示的是第二行第二个元素的地址,也就是&b[1][1],而b[2]则表示的是第二行第一个元素的地址,因此错误,所以在计算时应把b[1]转换为&b[1][0]之后,才能直接进行地址的偏移,也就是说b[1]+1=&b[1][0]+1=&b[1][1],这样才能得到正确的结果,并且不会出错。

b、对于有小括号的地方,一定不要省略小括号。比如(&b[1])[1]与&b[1][1]将表示的是不同的结果,第二个是显然的,

对于第一个(&b[1])[1]=*((&b[1])+1)=*(&b[1]+1)=*(&b[2])=b[2],可以看到,表示的是第3行第1个元素的地址,因此这两个的结果是显然不一样的。因此对于(b+1)[1]这样的运算,不能省略小括号,即(b+1)[1]=(&b[1])[1]=*((&b[1])+1)=*(&b[1]+1)=*(&b[2])=b[2],如果省略了小括号,则是(b+1)[1]=&b[1][1],这将是不易发现的错误。因此这是两个完完全全不同的符案。

c、总结,指针和[ ]混合运算2点关键,

第1:应把是地址的[ ]运算,转换为地址的形式,比如b[1]应转换为&b[1][0]。因为只有这样才能进行直接的地址相加运算,即&b[1][0]+1=&b[1][1],而b[1]+1不等于b[2]。

第2:有小括号的地方不能省略小括号,如(b+1)[1]=(&b[1])[1]=*((&b[1])+1)=*(&b[1]+1)=*(&b[2])=b[2],也&b[1][1]是完全不同的。

(*(b+1))[1] ,(*b)[1]

最简单的理解方法为*(b+1)和语句b[1]等价,即(*(b+1))[1]和语句b[1][1]是相同的,也就是二组数组第2行第2个元素的值b[1][1],理解方法2逐条解释,如上面的解释*(b+1)表示的是二维数组b的第二行第一个元素的地址,也就是b[1],然后再与后面的[1]进行运算,就得到b[1][1]。或者(*(b+1))[1]=*((*(b+1))+1)=*(*(b+1)+1)这个语句上面解释过。

同理(*b)[1]=b[0][1]

*(b+1)[1]:

计算方法1把[ ]分解为指针来计算:因为[ ]运算符高于指针,因此应先计算[ ]再计算指针,因为[1]表示的是把左侧的地址偏移1个单位,再求指针,因此(b+1)[1]=*((b+1)+1),最后再计算一次指针运算,也就是*(b+1)[1]=**((b+1)+1)=**(b+2)=*(*(b+2))=*b[2]=b[2][0],可以看到,最后表示的是b中第3行第一个元素的值。

计算方法2把指针化为[ ]来计算:*(b+1)[1]=*(&b[1])[1]=*(*(&b[1]+1))=**(&b[2])=*b[2]=b[2][0],注意*((&b[1])[1])表达式中应把(&b[1])括起来,若不括起来,则[ ]运算符的优先级高于&运算符,因此(&b[1])[1]与&b[1][1]是不一样的,后一个表示的是第二行第二个元素的地址,而头一个(&b[1])[1]则表示的是,对b的第二行的行地址偏移1个单位后再求指针的结果,也就是*(&b[1]+1)=*(&b[2])=b[2],所以性质是不一样的。

(*b+1)[2]

计算方法1化简[ ]运算符:(*b+1)[2]=*((*b+1)+2)=*(*b+3)=*(&b[0][0]+3)=*(&b[0][3])=b[0][3],这里要注意*b表示的是b[0]=&b[0][0],在计算时最好不要代入b[0]来计算,而应把b[0]转换为&b[0][0]后再计算,因为b[0]+3,很容易被错误的计算为b[3],而实际上b[0]指向的是第一行第一个元素的地址,对其偏移3个单位应该是指向第一行第4个元

素的地址,即&b[0][3],而b[3],则指向了第3行第1个元素的地址,这是不同的。

计算方法2化简*运算符:(*b+1)[2]=(b[0]+1)[2]=(&b[0][0]+1)[2]=(&b[0][1])[2]=*(&b[0][1]+2)=*(&b[0][3])=b[0][3],注意,在计算过程中小括号最好不要省略,省略了容易出错,因为[ ]运算符的优先给更高,如果省略了,某些地方将无法计算。比如(&b[0][0]+1)[2]=(&b[0][1])[2],如果省略掉括号,则成为&b[0][1][2],这对于二维数组来讲,是无法计算的。

(*(b+1))[5]

计算方法:(*(b+1))[5]=(*(&b[1]))[5]=(b[1])[5]=*(b[1]+5)=*(&b[1][0]+5)=*(&b[1][5])=b[1][5],结果等于第二行第6个元素的值。

f、注意,在二维或者多维数组中有个怪现象,比如对于多维数组a[n][m][i][j],那么这些地址是相同的,即数组名a, a[0],

a[0][0], a[0][0][0], &a[0][0][0][0],都是相同的地址。而且对数组名依次求指针运算将得到,比如*a=a[0],*a[0]=a[0][0], *a[0][0]=a[0][0][0], *a[0][0][0]=a[0][0][0][0],可以看到,只有对最后这个地址求指针运算才真正得到了数组中的值,因此对数组名求指针运算,要得到第一个元素的值,应该****a,也就是对4维数组需要求4次指针运算。同样可以看到,对数组名进行的前三次指针运算的值都是相同的,即*a, **a, ***a和a的值都是&a[0][0][0][0]的值,这就是这个怪问题,按理说对地址求指针应该得到一个值,但对多维数组求指针,却得到的是同一个地址,只是这些地址所包含的内容不一样。

3、数组指针与二维数组讲解:

下面我们将以y[4]={1,2,3,4}这个一维数组为例来层层讲解,指针和数组的关系。

1、数组指针:

定义形式为:int (*p)[4];表示定义了一个指向多维数组的指针,即指针p指向的是一个数组,这个数组有4个元素,对这个指针p的赋值必须是有4个int元素的数组的地址,即只要包含有四个元素的数组的地址都能赋给指针p,不管这个数组的行数是多少,但列数必须为4。即int y[4],x[22][4];都可以赋给指针p。赋值方式为p=&y或p=x,对于&y和二维数组数组名前面已讲过,&y中的地址是一个包含有4个元素的一维数组的地址,二维数组名x也是一个含有4个元素的一维数组的地址,因此可以这样赋值。而这样赋值将是错误的p=y,或者p=x[0]; 因为y和x[0]的地址只包含一个元素,他们不包含一个数组,因此出错。

2.注意()必须有,如果没有的话则,int *p[4];则是定义了一个指针数组,表示每个数组元素都是一个指针,即p[2]=&i;

指向一个int型的地址,而*p[2]则表示p[2]所指向的元素的值。

3.初始化数组指针p:

a、当把int y[4]赋给指针p时p=y将是错误的,正确的方式为p=&y因为这时编译器会检查赋给指针p的元素是否是

含有四个元素的数组,如果是就能正确的赋值,但语句p=y中的y代表的是数组y[4]第一行第一列的元素的地址也就是&y[0]的地址,因此y指向的地址只有一个元素,而指针p要求的是有4个元素的数组的地址,因此语句p=y将出错。而&y也表示的是一个地址,因为数组名y表示的是&y[0]的地址,因此&y=&(&y[0])。可以把&y理解为是数组y[4]的第一行的行地址,即&y包含了数组y[4]的第一行的所有元素,在这里&y包含有4个元素,因则p=&y才是正确的赋值方法,在这里要注意,数组的某行的行地址是第本行的第一个元素的地址是相同的。

b、把x[22][4]赋给指针p有几种方法,方法一:p=x;我们这样来理解该条语句,首先x[0]表示的是二维数组x[22][4]

的第1行第一列元素的地址,这个地址包含一个元素,这是显而易见的,而数组名x也表示一个地址,但这个地址包含的是一个数组(即数组的数组),这个数组是包含4个元素的数组,这4个元素就是数组x第一行的4个元素,也就是说x表示的是数组x[22][4]的第1行的行地址,即数组名x就包含了数组x[22][4]第1行的4个元素,因此这种赋值方式是正确的。这时指针p就相当于是数组名一样,比如p[2][1]访问的就是数组x的第3行的第2个元素。

c、方法二:p=x+1或者p=&x[1];注意必须要有地址运算符&,同理语句&x[1]表示的是数组x[22][4]第2行的行地址,

因为x[1]表示的是数组x[22][4]第的第2行第1列的元素的地址,因此&x[1]表示的就是数组x的第2行的行地址,因为&x[1]这个地址包含了一个数组,这个数组的起始地址是从x[1]这个地址开始的,这,即数组x[22][4]中x[1]这一行的4个元素。在这一行中包含了4个元素。而x+1本身就是指的x[1]的地址。这时指针p的起始地址是&x[1],所以p[0][1]不再是访问的x的第一行第二个元素,而是访问的x的第二行第二个元素。

d、注意,再次提示,数组的某行的行地址是与本行的第一个元素的地址是相同的。

4.访问成员:

a.可以把数组指针p当成数组名来理解,即可以像使用数组一样使用指针,比如p[1][2]访问数组x中的第二行第三个元素7。这种方法容易理解。

b.访问数组元素的方法:记住二条法则*(p+1)与语句p[1]或者x[1]等价。

法则二:[ ]这个运算符的的运算法则是,把左侧的地址加上[ ]内的偏移量然后再求指针运算,注意有[ ]运算符的地方就有个隐含的指针,比如x[2]表示的就是将指针x偏移2个单位量后再求指针运算。

5.int (*p)[4]与语句int x[][4]等价。

6.int (&p)[4]引用和指针有一些差别因为对引用赋值必须是变量的名字,所以int y[4];int (&p)[4]=y;是正确的而用&y将得到一个错误的结果,同样int x[22][4]; int (&p)[4]=x[1];才是正确的,不能用x+1或者&x[1]来初始化引用。int (&p)[4]=x 也将得到一个错误的结果,只能是int (&p)[4]=x[0];对于引用来说语句p[0][1]将是错误的,只能是p[0],p[1],依次访问p所引用的对象的第一个元素的值和第二个元素的值,int (&p)[4]=x[1];p[1]将访问的是p所引用的第二个元素的值也就是数组x第二行第2个元素的值。

例:指针与二维数组int (*p)[4].

int main()

{ int x[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};

int y[4]={13,14,15,16};

int(*p)[4]=&y; //必须要有&运算符,因为这时的y只是一个有四个元素的名字和int i中的i是一样的只是个名字,所以必须要有&运算符

//p=y; //错误y现在不是地址,而是一个名字。

cout<

cout<<(*p)[1];//输出p所指向的第二个元素,即y的第二个元素14。

cout<<*p[0];//输出p所指向的第一行的第一个元素,即y的第一个元素13。

cout<<(*p)[5];//输出p所指向的第六个元素的值,这里超出数组y的范围,因为y只有一行元素,只有四个值,所以为随机值

cout<<*p[1];//输出p所指向的第二行的第一个元素,这里p只有一个数组元数y,所以输出随机值。

cout<

cout<

cout<

p=x; //把数组x的地址赋给指针p

cout<

cout<<(*p)[5];//输出p所指向的元素的第六个元素的值,即x的第二行第二个元素的值6。

cout<<*p[1];//输出p所指向元素的第二行第个一元素的值,即x的第二行第一个元素的值5。

cout<

p=x+1;//或者p=&x[1];

cout<

}

第二部分:动态分配内存new关见字

1.全局对象和局部对象的生命期都是严格定义的,程序员不能以任何方式改变他们的生命期。但是有时候需要创建一些生命期能被程序员控制的对象,他的分配和释放可以根据程序运行中的操作来决定。这时就需要使用new操作符了。

2.动态分配内存,将从堆中分配内存,动态分配的存储区是在运行时确定的,动态分配的存储区可以随着需求而自动扩大.

3.局部变量一般存储在堆栈中,堆栈是先进后出的存储结构,而堆却不是.

4.在C++中使用new动态分配内存,delete释放以前new分配的内存.

5.使用new运算符系统将从空闲存储区中为对象分配内存,并返回一个指向该对象的指针即该对象的地址。new运算符的特点是,用new运算符分配的对象没有名字,对该对象的操作都要通过指针间接地完成操作。例如new int,就是从空闲存储区分配了一个int型对象,但没法对这个对象进行操作,只是从存储区分配了这么一个空间。语句int *p=new int表示从空闲存储区分配一个int对象并把这个对象的地址赋给p,现在p就是用new分配的int对象的地址,而*p就是那里的值。语句int i;int*p=&i;和int *p=new int都是将int变量的地址赋给了指针,但不同的是前句可以用名称i和*p来访问该int型变量,而后句则只能用*p来访问该变量,也就是说p指向的内存没有名称。

6.动态创建数组:int *p=new int [11];创建动态数组时必须有[]方括号,且里面要有创建的维数,但该数组的第一维可以是一个复杂的表达式。访问地址中的内容的方法为*p访问数组中的第一个元素,p[1]该问第二个元素,以此类推。

创建二组数组的例子:int (*p)[102]=new int [4][102]。

7.动态创建对象的初始化:int *p=new int(102)该语句表明由p指向的新创建你对象被初始化为102。动态创建对象的初始化方式为在类型名后面用一对括号来被始化。

8.动态创建对象的默认初始化:方式为在类型名后面跟一对空的圆括号初始化,int *p=new int (); int *ps=new string();

cls *pc=new cls();第一条语句把对象的值初始化为0,第二条语句对于提供了默认构造函数的string类,不论程序是要明确的不初始化,还是要求进行值初始化都会调用默认构造函数初始化该对象。而对于内置类型或没有默认构造

函数的类型,则采用不同的初始化方式就会有显著不同的差别。例如:int *p=new int; int *p=new int();第一条语句没有被初始化,而第二条被初始化为0。

9.用new动态创建的数组不能被初始化,不能创建初始化值集。

10.耗尽内存:如果程序用完了所有可用的内存,new表达式就有可能失败。如果new表达式无法获得要需要的内存空间,系统将会抛出名为bad_alloc异常。

11.可以在一个函数内使用new运算符分配内存,而在另一个函数中使用delete释放内存空间.delete只能用于释放前次使用new分配的空间,不要使用delete来释放不是new分配的内存,不要使用delete释放相同的内存两次,应使用delete []来释放动态数组,例如:int *p=new int [10];delete [] p;删除数组必须要有[]方括号。delete不一定要用于new 的指针,例如int *p=new int ; int *x=p; delete x;将是合法的.如果指针的值为0,则在其上作delete操作是合法的,但没有任务意义。

12.悬垂指针:执行delete p后只是把p所指向的地址的内容给释放掉了,并没有删掉指针p本身,还可以将p重新指向到另一个新的内存块,因此p还指向原来他指向的对象的地址,然而p所指向的内容却已经被释放了,因此p不再有效而变得没有定义了。这样的指针称为悬垂指针。悬垂指针往往导致程序错误而且很难检测出来。

13.静态联编:如果通过声明来创建数组,则在程序被编译时为他分配内存空间,不管程序是否使用数组,数组都在那里,占用了内存。在编译时给数组分配内存被称为静态联编。意味着数组是在编译时加入到程序中的。但使用new 时,如果在运行阶段需要数组,则创建他,如果不需要,则不创建,还可以在程序运行时选择数组的长度,这被称为动态联编。意味着数组是在程序运行时创建的,这种数组叫做动态数组,使用静态联编时必须在编写程序的时候指定数组的长度,使用动态联编时,程序将在运行时确定数组的长度。

14.const常量对象的动态分配和回收:与其他常量一样,动态创建的const对象必须在创建时初始化,并且一经初始化,其值不能再修改。例如:const int *p= new const int(111);删除方法为:delete p;尽管程序员不能修改const对象的值,但可以撤消对象本身。

15.注意:不能在空闲存储区上创建内置类型元素(除类数组string外)的const数组。因为我们不能初始化用new创建的内置类型数组的元素。如const int *p=new const int [11];将是错误的。

16.注意:如果用new分配了资源,而没有用delete释放该资源的话,那么用new分配的资源将一指被占用。

17.常见错误:如果对某个指针动态分配了内存,又把另一个变量的地址付给这个指针,这时这个指针就指向了一个静态地址,而不是原先的动态地址。如果再用delete删掉这个指针时就会出错,因为这时这个指针是指向静态地址的,不能用delete删除一个指向静态地址的指针。比如int *p =new int(1),这时指针p指向一个动态内存可以对他进行delete删除,但如果再执行语句int a=2; p=&a;这时就改变了指针指向的内容,使原先指向的动态内存地址变成了指向现在的静态内存地址,如果这时对指针p进行delete操作就会出错,因为你在对一个静态指针静行删除,而delete 只能删除动态指针。

例:动态分配对象new

class hyong

{public:int a,b,c; hyong (){a=b=c=0;} hyong(int i){a=b=c=i;} ~hyong(){cout<<"xigou"<<"\n";} };

int main()

{ hyong *p=new hyong; hyong *p1=new hyong(); cout<a<a<<"\n"; //输出两个0,都调用默认构造函数初始化指针。

int*p2=new int; int*p3=new int(); int*p4=new int(1); //new分配内存的初始化方式。对于类置类型来说p2没有被初始化得到的是一个随机值,p3被初始化为0,p4被初始化为1。

cout<<*p2<<"\n"<<*p3<<"\n"<<*p4<<"\n"; //输出一个随机值,一个0,一个1

int i=10; delete p4; cout<<*p4<<"\n"; //p4现在是悬垂指针,delete只是释放掉了指针p4所指向地址的内容,但指针p4仍然指向原来的地址,但没有内容,指针p4仍然可以再指向其他地址。

p4=&i; cout<<*p4<<"\n"; //可以对悬垂指针p4重新赋地址。

const int*p5=new int; const int*p6=new int(4) ;cout<<*p5<<*p6<<"\n";//输出一个随机值和4,const常量必须在声明时初始化。int*p7=new int[2]; p7[0]=5;p7[1]=6; cout<

//const int *p8=new int[2]; //错误,因为动态数组不能在声明时初始化,而const又必须要求在声明时初始化,发生冲突,出错。delete p1; delete p;

//delete p,p1; //注意,如果使用该语句将则只调用一次析构函数。

delete p2,p3,p4,p5,p6; delete[] p7;

//int *p8=new int(9); int a=8; p8=&a; delete p8; //错误,现在的指针p8重新指向了一个静态的地址,用delete删掉一个静态地址将发生错误。

}

作者:黄邦勇帅

2010年3月22日

动态内存分配和回收

实验五可变分区存储管理方式的内存分配和回收 一.实验目的 通过编写和调试存储管理的模拟程序以加深对存储管理方案的理解,熟悉可变分区存储管理的内存分配和回收。 二.实验属性 设计 三.实验内容 1.确定内存空间分配表; 2.采用最优适应算法完成内存空间的分配和回收; 3.编写主函数对所做工作进行测试。 四.实验背景材料 实现可变分区的分配和回收,主要考虑的问题有三个:第一,设计记录内存使用情况的数据表格,用来记录空闲区和作业占用的区域;第二,在设计的数据表格基础上设计内存分配算法;第三,在设计的数据表格基础上设计内存回收算法。 首先,考虑第一个问题,设计记录内存使用情况的数据表格,用来记录空间区和作业占用的区域。 由于可变分区的大小是由作业需求量决定的,故分区的长度是预先不固定的,且分区的个数也随内存分配和回收变动。总之,所有分区情况随时可能发生变化,数据表格的设计必须和这个特点相适应。由于分区长度不同,因此设计的表格应该包括分区在内存中的起始地址和长度。由于分配时空闲区有时会变成两个分区:空闲区和已分分区,回收内存分区时,可能会合并空闲分区,这样如果整个内存采用一张表格记录己分分区和空闲区,就会使表格操作繁琐。分配内存时查找空闲区进行分配,然后填写己分配区表,主要操作在空闲区;某个作业执行完后,将该分区变成空闲区,并将其与相邻的空闲区合并,主要操作也在空闲区。由此可见,内存的分配和回收主要是对空闲区的操作。这样为了便于对内存空间的分配和回收,就建立两张分区表记录内存使用情况,一张表格记录作业占用分区的“己分分区表”;一张是记录空闲区的“空闲区表”。这两张表的实现方法一般有两种:一种是链表形式,一种是顺序表形式。在实验中,采用顺序表形式,用数组模拟。由于顺序表的长度必须提前固定,所以无论是“已分分区表”还是“空闲区表”都必须事先确定长度。它们的长度必须是系统可能的最大项数。 “已分分区表”的结构定义 #define n 10 //假定系统允许的最大作业数量为n struct { float address; //已分分区起始地址 float length; //已分分区长度、单位为字节 int flag; //已分分区表登记栏标志,“0”表示空栏目,实验中只支持一个字符的作业名 }used_table[n]; //已分分区表 “空闲区表”的结构定义 #define m 10 //假定系统允许的空闲区最大为m struct

操作系统内存动态分配模拟算法

实验四存分配算法 1.实验目的 一个好的计算机系统不仅要有一个足够容量的、存取速度高的、稳定可靠的主存储器,而且要能合理地分配和使用这些存储空间。当用户提出申请主存储器空间时,存储管理必须根据申请者的要求,按一定的策略分析主存空间的使用情况,找出足够的空闲区域分配给申请者。当作业撤离或主动归还主存资源时,则存储管理要收回作业占用的主存空间或归还部分主存空间。主存的分配和回收的实现是与主存储器的管理方式有关的,通过本实验帮助学生理解在动态分区管理方式下应怎样实现主存空间的分配和回收。 背景知识: 可变分区方式是按作业需要的主存空间大小来分割分区的。当要装入一个作业时,根据作业需要的主存量查看是否有足够的空闲空间,若有,则按需要量分割一个分区分配给该作业;若无,则作业不能装入。随着作业的装入、撤离、主存空间被分成许多个分区,有的分区被作业占用,而有的分区是空闲的。 2.实验容 采用首次适应算法或循环首次算法或最佳适应算法分配主存空间。 由于本实验是模拟主存的分配,所以当把主存区分配给作业后并不实际启动装入程序装入作业,而用输出“分配情况”来代替。(即输出当时的空闲区说明表及其存分配表) 利用VC++6.0实现上述程序设计和调试操作。 3.实验代码 #include #include using namespace std; //定义存的大小 const int SIZE=64; //作业结构体,保存作业信息 struct Project{ int number; int length; }; //存块结构体,保存存块信息 struct Block{

C++第七章 动态内存分配习题解答

第七章动态内存分配习题 一、基本概念与基础知识自测题 7.1 填空题 7.1.1 C/C++定义了4个内存区间:(1)、(2)、(3)和(4)。 答案:(1)代码区,存放程序代码; (2)全局变量与静态变量区,存放全局变量或对象(包括静态); (3)局部变量区即栈(stack)区,存放局部变量; (4)动态存储区,即堆(heap)区或自由存储区(free store)。 7.1.2 静态定义的变量和对象用标识符命名,称为(1);而动态建立的称为(2),动 态建立对象的初始化是通过(3)来(4)。 答案:(1)命名对象 (2)无名对象 (3)初始化式(initializer) (4)显式初始化 7.1.4 当动态分配失败,系统采用(1)来表示发生了异常。如果new返回的指针丢失, 则所分配的堆空间无法收回,称为(2)。这部分空间必须在(3)才能找回,这是因为无名对象的生命期(4)。 答案:(1)返回一个空指针(NULL) (2)内存泄漏 (3)重新启动计算机后 (4)并不依赖于建立它的作用域 7.1.5 按语义的缺省的构造函数和拷贝构造赋值操作符实现的拷贝称(1),假设类对象 obj中有一个数据成员为指针,并为这个指针动态分配一个堆对象,如用obj1按成员语义拷贝了一个对象obj2,则obj2对应指针指向(2)。 答案:(1)浅拷贝 (2)同一个堆对象 7.2简答题(以下习题题号可能和教材不一致!) 7.2.1用delete删除p所指向的无名对象时,p指针也同时被删除了,对不对?为什么?答:不对。注意这时释放了p所指向的无名对象占用的内存空间,也就是撤销了该无名对象,称动态内存释放(dynamic memory deallocation),但指针p本身并没有撤销,它仍然存在,该指针所占内存空间并未释放。 7.2.2为什么动态建立类对象数组时,类的定义一定要有缺省的构造函数? 答:new后面类(class)类型也可以有参数。这些参数即构造函数的参数。但对创建数组,没有参数,只能调用缺省的构造函数。 7.2.3要实现深拷贝,自定义的拷贝构造函数应该怎样设计? 答:如果类中有一个数据成员为指针,该类的一个对象中的这个指针p,指向了动态分配的一个堆对象。深拷贝时要给新建立的对象独立分配一个堆对象。这时拷贝的构造函数应

动态内存分配

浅析动态内存分配及Malloc/free的实现2011-03-18 22:47一、概述: 动态内存分配,特别是开发者经常接触的Malloc/Free接口的实现,对许多开发者来说,是一个永远的话题,而且有时候也是一个比较迷惑的问题,本文根据自己的理解,尝试简单的探究一下在嵌入式系统中,两类典型系统中动态内存分配以及Malloc/Free的实现机制。 二、内存分配方式 Malloc/Free主要实现的是动态内存分配,要理解它们的工作机制,就必须先了解操作系统内存分配的基本原理。 在操作系统中,内存分配主要以下面三种方式存在: (1)静态存储区域分配。内存在程序编译的时候或者在操作系统初始化的时候就已经分配好,这块内存在程序的整个运行期间都存在,而且其大小不会改变,也不会被重新分配。例如全局变量,static变量等。 (2)栈上的内存分配。栈是系统数据结构,对于进程/线程是唯一的,它的分配与释放由操作系统来维护,不需要开发者来 [url=javascript:;]管理[/url] 。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时,这些存储单元会被自动释放。栈内存分配运算内置于处理器的指令集中,效率很高,不同的操作系统对栈都有一定的限制。 (3)堆上的内存分配,亦称动态内存分配。程序在运行的期间用malloc申请的内存,这部分内存由程序员自己负责管理,其生存期由开发者决定:在何时分配,分配多少,并在何时用free来释放该内存。这是唯一可以由开发者参与管理的内存。使用的好坏直接决定系统的性能和稳定。 三、动态内存分配概述 首先,对于支持虚拟内存的操作系统,动态内存分配(包括内核加载,用户进程加载,动态库加载等等)都是建立在操作系统的虚拟内存分配之上的,虚拟内存分配主要包括: 1、进程使用的内存地址是虚拟的(每个进程感觉自己拥有所有的内存资源),需要经过页表的映射才能最终指向系统实际的物理地址。 2、主内存和磁盘采用页交换的方式加载进程和相关数据,而且数据何时加载到主内存,何时缓存到磁盘是OS调度的,对应用程序是透明的。 3、虚拟存储器给用户程序提供了一个基于页面的内存大小,在32位系统中,用户可以页面大小为单位,分配到最大可以到4G(内核要使用1G或2G等内存地址)字节的虚拟内存。 4、对于虚拟内存的分配,操作系统一般先分配出应用要求大小的虚拟内存,只有当应用实际使用时,才会调用相应的操作系统接口,为此应用程序分配大小以页面为单位的实际物理内存。 5、不是所有计算机系统都有虚拟内存机制,一般在有MMU硬件支持的系统中才有虚拟内存的实现。许多嵌入式操作系统中是没有虚拟内存机制的,程序的动态分配实际是直接针对物理内存进行操作的。许多典型的实时嵌入式系统如Vxworks、Uc/OS 等就是这样。 四、动态内存分配的实现 由于频繁的进行动态内存分配会造成内存碎片的产生,影响系统性能,所以在不同的系统中,对于动态内存管理,开发了许多不同的算法(具体的算法实现不想在这里做详细的介绍,有兴趣的读者可以参考Glib C 的源代码和附录中的资料)。不同的操作系统有不同的实现方式,为了程序的可移植性,一般在开发语言的库中都提供了统一接口。对于C语言,在标准C库和Glib 中,都实现了以malloc/free为接口的动态内存分配功能。也就是说,malloc/free库函索包装了不同操作系统对动态内存管理的不同实现,为开发者提供了一个统一的开发环境。对于我们前面提到的一些嵌入式操作系统,因为实时系统的特殊要求(实

《动态分配内存与数据结构》课后习题

《动态分配内存与数据结构》习题 学号姓名 一、选择题 1、是一种限制存取位置的线性表,元素的存取必须服从先进先出的规则。 A.顺序表B.链表C.栈D.队列 2、是一种限制存取位置的线性表,元素的存取必须服从先进后出的规则。 A.顺序表B.链表C.栈D.队列 3、与顺序表相比,链表不具有的特点是。 A.能够分散存储数据,无需连续内存空间 B.插入和删除无需移动数据 C.能够根据下标随机访问 D.只要内存足够,没有最大长度的限制 4、如果通过new运算符动态分配失败,返回结果是。 A.-1 B.0 C.1D.不确定 5、实现深复制中,不是必须自定义的。 A.构造函数B.复制构造函数 C.析构函数D.复制赋值操作符函数 6、分析下列代码是否存在问题,选择合适的选项:。 int main(void) { int *p = new int [10]; p = new int [10]; delete [] p; p = NULL; return 0; } A.没有问题 B.有内存泄漏 C.存在空悬指针 D.存在重复释放同一空间 7、通过new运算符动态分配的对象,存储于内存中的。 A.全局变量与静态变量区 B.代码区 C.栈区 D.堆区 8、下列函数中,可以是虚函数。 A.构造函数 B.析构函数 C.静态成员函数 D.友元函数 9、关于通过new运算符动态创建的对象数组,下列判断中是错误的。 A. 动态创建的对象数组只能调用默认构造函数 B. 动态创建的对象数组必须调用delete []动态撤销 C. 动态创建的对象数组的大小必须是常数或常变量 D. 动态创建的对象数组没有数组名 10、顺序表不具有的特点是 A. 元素的存储地址连续 B. 存储空间根据需要动态开辟,不会溢出 C. 可以直接随机访问元素 D. 插入和删除元素的时间开销与位置有关 11、假设一个对象Ob1的数据成员是指向动态对象的指针,如果采用浅复制的方式复制该对象得到对象Ob2,那么在析构对象Ob1和对象Ob2时会的问题。 A. 有重复释放 B. 没有 C. 内存泄漏 D. 动态分配失败 12、假设对5个元素A、B、C、D、E进行压栈或出栈的操作,压栈的先后顺序是ABCDE,则出栈的先后顺序不可能是。 A. ABCDE B. EDCBA C. EDBCA D. BCADE 13、假设对4个元素A、B、C、D、E进行压栈或出栈的操作,压栈的先后顺序是ABCD,则出栈的先后顺序不可能是。 A. ABCD B. DCBA C. BCAD D. DCAB 14、通过new运算符动态创建的对象的存放在中。 A. 代码区 B. 栈区 C. 自由存储区 D. 全局数据区 15、链表不具有的特点是。 A. 元素的存储地址可以不连续 B. 存储空间根据需要动态开辟,不会溢出 C. 可以直接随机访问元素 D. 插入和删除元素的时间开销与位置无关 16、有关内存分配和释放的说法,下面当中错误的是 A.new运算符的结果只能赋值给指针变量 B.动态创建的对象数组必须调用delete []动态撤销 C.用new分配的空间位置是在内存的栈区 D.动态创建的对象数组没有数组名 17、关于栈,下列哪项不是基本操作 A.删除栈顶元素 B.删除栈底元素 C.判断栈是否为空 D.把栈置空 18、关于链表,说法错误的是

动态内存管理知识总结

1.标准链接库提供四个函数实现动态内存管理: (1)分配新的内存区域: void * malloc(size_t size); void *calloc(size_t count , size_t size); (2)调整以前分配的内存区域: void *realloc(void *ptr , size_t size); (3)释放以前分配的内存区域: void free(void *ptr); 2.void * malloc(size_t size); 该函数分配连续的内存空间,空间大小不小于size 个字节。但分配的空间中的内容是未知的。该函数空间分配失败则返回NULL。 3.void *calloc(size_t count , size_t size); 该函数也可以分配连续的内存空间,分配不少于count*size个字节的内存空间。即可以为一个数组分配空间,该数组有count个元素,每个元素占size个字节。而且该函数会将分配来的内存空间中的内容全部初始化为0 。该函数空间分配失败则返回NULL。 4. 以上两个分配内存空间的函数都返回void * (空类型指针或无类型指针)返回的指针值是“分配的内存区域中”第一个字节的地址。当存取分配的内存位置时,你所使用的指针类型决定如何翻译该位置的数据。以上两种分配内存空间的方法相比较,calloc()函数的效果更好。原因是它将分配得来的内存空间按位全部置0 。 5. 若使用上述两种分配内存的函数分配一个空间大小为0 的内存,函数会返回一个空指针或返回一个没有定义的不寻常指针。因此绝不可以使用“指向0 字节区域”的指针。 6. void *realloc(void *ptr , size_t size); 该函数释放ptr所指向的内存区域,并分配一个大小为size字节的内存区域,并返回该区域的地址。新的内存区域可以和旧的内存区域一样,开始于相同的地址。且此函数也会保留原始内存内容。如果新的内存区域没有从原始区域的地址开始,那么此函数会将原始的内容复制到新的内存区域。如果新的内存区域比较大,那么多出来部分的值是没有意义的。 7. 可以把空指针传给realloc()函数,这样的话此函数类似于malloc()函数,并得到一块内存空间。如果内存空间不足以满足内存区域分配的请求,那么realloc()函数返回一个空指针,这种情况下,不会释放原始的内存区域,也不会改变它的内容。 8. void free(void *ptr); 该函数释放动态分配的内存区域,开始地址是ptr,ptr的值可以是空指针。若在调用此函数时传入空指针,则此函数不起任何作用。 9. 传入free() 和realloc()函数的指针(若不为空指针时)必须是“尚未被释放的动态分配内存区域的起始地址”。否则函数的行为未定义。Realloc()函数也可以释放内存空间,例如:Char *Ptr = (char *)malloc(20); 如只需要10个字节的内存空间,且保留前十个字节的内容,则可以使用realloc()函数。 Ptr = Realloc(ptr,10); // 后十个字节的内存空间便被释放

动态内存分配(C语言)

实验报告 实验课程名称:动态内存分配算法 年12月1日

实验报告 一、实验内容与要求 动态分区分配又称为可变分区分配,它是根据进程的实际需要,动态地为之分配内存空间。在实验中运用了三种基于顺序搜索的动态分区分配算法,分别是1.首次适应算法2.循环首次适应算法3.最佳适应法3.最坏适应法分配主存空间。 二、需求分析 本次实验通过C语言进行编程并调试、运行,显示出动态分区的分配方式,直观的展示了首次适应算法循环首次适应算法、最佳适应算法和最坏适应算法对内存的释放和回收方式之间的区别。 首次适应算法 要求空闲分区链以地址递增的次序链接,在分配内存时,从链首开始顺序查找,直至找到一个大小能满足要求的空闲分区为止,然后在按照作业的大小,从该分区中划出一块内存空间,分配给请求者,余下的空余分区仍留在空链中。 优点:优先利用内存中低址部分的空闲分区,从而保留了高址部分的大空闲区,为以后到达的大作业分配大的内存空间创造了条件。 缺点:低址部分不断被划分,会留下许多难以利用的、很小的空闲分区即碎片。而每次查找又都是从低址部分开始的,这无疑又会增加查找可用空闲分区时的开销。

循环首次适应算法 在为进程分配内存空间时,不是每次都从链首开始查找,而是从上次找到的空闲分区的下一个空闲分区开始查找,直到找到一个能满足要求的空闲分区。 优点:该算法能使内存中的空闲分区分布得更均匀,从而减少了查找空闲分区时的开销。 最佳适应算法 该算法总是把能满足要求、又是最小的空闲分区分配给作业,避免大材小用,该算法要求将所有的空闲分区按其容量以从小到大的顺序形成一空闲分区链。 缺点:每次分配后所切割下来的剩余部分总是最小的,这样,在存储器中会留下许多难以利用的碎片。 最坏适应算法 最坏适应算法选择空闲分区的策略正好与最佳适应算法相反:它在扫描整个空闲分区或链表时,总会挑选一个最大的空闲区,从中切割一部分存储空间给作业使用。该算法要求,将所有的空闲分区,按其容量以大到小的顺序形成一空闲分区链。查找时,只要看第一个分区能否满足作业要求即可。 优点:可使剩下的空闲区不至于太小,产生碎片的可能性最小,对中小作业有利,同时,最坏适应算法查找效率很高。 缺点:导致存储器中缺乏大的空闲分区 三、数据结构 为了实现动态分区分配算法,系统中配置了相应的数据结构,用以描述空闲分区和已分配分区的情况,常用的数据结构有空闲分区表和空闲分区链 流程图

指针与内存详解.

在计算机领域,堆栈是一个不容忽视的概念,但是很多人甚至是计算机专业的人也没有明确堆栈其实是两种数据结构。 堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top对数据项进行插入和删除。 要点: 堆:顺序随意 栈:后进先出(Last-In/First-Out [编辑本段]堆和栈的区别 一、预备知识—程序的内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分 1、栈区(stack)—由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 2、堆区(heap)—一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后由系统释放。 4、文字常量区—常量字符串就是放在这里的。程序结束后由系统释放。 5、程序代码区—存放函数体的二进制代码。 二、例子程序 这是一个前辈写的,非常详细 //main.cpp int a = 0; 全局初始化区 char *p1; 全局未初始化区 main( {

int b; 栈 char s[] = "abc"; 栈 char *p2; 栈 char *p3 = "123456"; 123456\0在常量区,p3在栈上。 static int c =0;全局(静态)初始化区 p1 = (char *malloc(10; p2 = (char *malloc(20; } 分配得来得10和20字节的区域就在堆区。 strcpy(p1, "123456"; 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 [编辑本段]堆和栈的理论知识 1.申请方式 stack: 由系统自动分配。例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间 heap: 需要程序员自己申请,并指明大小,在c中malloc函数 如p1 = (char *malloc(10; 在C++中用new运算符 如p2 = new char[20];//(char *malloc(10; 但是注意p1、p2本身是在栈中的。 2.申请后系统的响应

动态内存分配

动态内存分配 一、实验目的 动态分区分配是根据进程的实际需要,动态地为之分配内存空间,而在分配时,须按照一定的分配算法,从空闲分区表或空闲分区链中选出一分区分配给该作业。在本实验中运用了四种分配算法,分别是1.首次适应算法,2.循环首次适应算法,3.最坏适应算法4.最佳适应算法。 二、实验要求及功能介绍 1.实验要求 1.在实现关于内存管理的内存首选适应算法和最佳适用算法。 2.实现关于内存管理的内存动态分区分配布局初始化。 3.实现关于内存管理的内存动态分区分配申请分配。 4.实现关于内存管理的内存回收等基本功能操作函数。 2.功能介绍 (1)首次适应算法 在首次适应算法中,是从已建立好的数组中顺序查找,直至找到第一个大小能满足要求的空闲分区为止,然后再按照作业大小,从该分区中划出一块内存空间分配给请求者,余下的空间令开辟一块新的地址,大小为原来的大小减去作业大小,若查找结束都不能找到一个满足要求的分区,则此次内存分配失败。 (2)循环首次适应算法 该算法是由首次适应算法演变而成,在为进程分配内存空间时,不再是每次都从第一个空间开始查找,而是从上次找到的空闲分区的下一个空闲分区开始查找,直至找到第一个能满足要求的空闲分区,从中划出一块与请求大小相等的内存空间分配给作业,为实现本算法,设置一个全局变量f,来控制循环查找,当f%N==0时,f=0;若查找结束都不能找到一个满足要求的分区,则此次内存分配失败。 (3)最坏适应算法 最坏适应分配算法是每次为作业分配内存时,扫描整个数组,总是把能满足条件的,又是最大的空闲分区分配给作业。 (4)最佳适应算法 最坏适应分配算法是每次为作业分配内存时,扫描整个数组,总是把能满足条件的,又是最小的空闲分区分配给作业。 三、实验流程图

DELPHI和C指针与内存分配比较

delphi和c指针与内存分配比较 1.动态变量 对于动态分内存的变量,使用System单元的下面两个函数: procedure New(var P:Pointer);//为变更分配内存 procedure Dispose(var P:Pointer);//释放内存 其中P为变量地址 示例: type Str18=string[18]; var P:^Str18; begin New(P); P^:='Now you see it...'; Dispose(P);{Now you don't...} end; ///////////////////////////// Delphi里自己管理内存的两对函数new(),dispose()和getmem(),freemem() delphi的指针 大家都认为,C语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运用上。因此,说指针是C语言的灵魂,一点都不为过。同时,这种说法也让很多人产生误解,似乎只有C语言的指针才能算指针。Basic不支持指针,在此不论。其实,Pascal语言本身也是支持指针的。从最初的Pascal发展至今的Object Pascal,可以说在指针运用上,丝毫不会逊色于C 语言的指针。 以下内容分为八部分,分别是 一、类型指针的定义 二、无类型指针的定义 三、指针的解除引用 四、取地址(指针赋值) 五、指针运算 六、动态内存分配 七、字符数组的运算 八、函数指针 一、类型指针的定义。对于指向特定类型的指针,在C中是这样定义的: int*ptr; char*ptr; 与之等价的Object Pascal是如何定义的呢? var ptr:^Integer; ptr:^char; 其实也就是符号的差别而已。 二、无类型指针的定义。C中有void*类型,也就是可以指向任何类型数据的指针。Object Pascal为其定义了一个专门的类型:Pointer。于是, ptr:Pointer; 就与C中的 void*ptr; 等价了。 三、指针的解除引用。要解除指针引用(即取出指针所指区域的值),C的语法是(*ptr),ObjectPascal则是ptr^。 四、取地址(指针赋值)。取某对象的地址并将其赋值给指针变量,C的语法是 ptr=&Object; Object Pascal则是 ptr:=@Object; 也只是符号的差别而已。

类和动态内存分配

第12章类和动态内存分配 12.1 动态内存和类 C++使用new和delete运算符来动态控制内存。遗憾的是,在类中使用这些运算符导致许多编程问题。在这种情况下,析构函数将是必不可少的。 12.1.1 复习实例和静态成员 注意点: ●使用char指针来表示姓名。意味着类声明本身没有为字符串分配存储空间,而是在构 造函数中使用new来为字符串分配空间。避免了在类声明中预先定义字符串的长度。 ●将num_strings成员声明为静态存储类。特点:无论创建了多少对象,程序只创建一个 静态变量副本。

注: Stringbad sailor = sports; 等价于Stringbad sailor = Stringbad(sports); Stringbad(constStringbad&); 当使用一个对象来初始化另外一个对象时,编译器将自动生成上述构造函数(复制构造函数即拷贝构造函数) 12.1.2 特殊成员函数C++提供了下面这些成员函数 ●默认构造函数(如果没有定义构造函数) ●默认析构函数 ●复制构造函数 ●赋值运算符 ●地址运算符 C++11移动构造函数与移动赋值运算符(第18章) 1 默认构造函数 Klunk::Klunk(){}不接受任何参数,也不执行任何操作。原因:创建对象时,总会调用构造函数。 Klunk::Klunk(){ klunk_ct = 0; } 不接受任何参数,但可以用来设定特定的值。

Klunk(int n = 10) {klunk_ct = 0;} 带参数的默认构造函数,只要所有的参数都有默认值。 2 复制构造函数 复制构造函数用于将一个对象复制到新创建的对象中。用于初始化过程中(包括按值传递参数)。类的复制构造函数原型如下: Class_name(constClass_name&); Stringbad(constStringbad&); 何时调用复制构造函数?(每当程序生成对象副本的时候,复制构造函数将被调用)新建一个对象并将其初始化为同类现有对象时,复制构造函数将被调用。下面四种情况均会调用复制构造函数。 Stringbad ditto(motto); Stringbadmetoo = motto; Stringbad also = Stringbad(motto); Stringbad* pStringbad = new Stringbad(motto);使用motto初始化一个匿名对象,并将新对象的地址赋值给pStringbad指针。 默认的复制构造函数有什么功能? 默认的复制构造函数租个复制非静态的成员(成员复制称为浅复制),复制的是成员的值。

c语言中动态内存申请与释放的简单理解

c语言中动态内存申请与释放的简单理解 在C里,内存管理是通过专门的函数来实现的。与c++不同,在c++中是通过new、delete函数动态申请、释放内存的。 1、分配内存 malloc 函数 需要包含头文件: #include 或 #include 函数声明(函数原型): void *malloc(int size); 说明:malloc 向系统申请分配指定size个字节的内存空间。返回类型是 void* 类型。void* 表示未确定类型的指针。C,C++规定,void* 类型可以强制转换为任何其它类型的指针。 从函数声明上可以看出。malloc 和 new 至少有两个不同: new 返回指定类型的指针,并且可以自动计算所需要大小。比如: int *p; p = new int; //返回类型为int* 类型(整数型指针),分配大小为 sizeof(int); 或: int* parr; parr = new int [100]; //返回类型为 int* 类型(整数型指针),分配大小为sizeof(int) * 100; 而 malloc 则必须由我们计算需要的字节数,并且在返回后强行转换为实际类型的指针。 int* p; p = (int *) malloc (sizeof(int)); 第一、malloc 函数返回的是 void * 类型,如果你写成:p = malloc (sizeof(int)); 则程序无法通过编译,报错:“不能将 void* 赋值给 int * 类型变量”。所以必须通过 (int *) 来将强制转换。 第二、函数的实参为 sizeof(int) ,用于指明一个整型数据需要的大小。如果你写成:

C++指针与动态分配内存new关键字专题

本文作者:黄邦勇帅 本文是学习C++的基础内容,指针是C或C++所特有的,因此应熟练掌握指针的使用,本文集中介绍C或C++中的各种指针,包括指针数组,数组指针,常量(const)指针,指向指针的指针,尤其是对二维数组和指针进行了详细精辟的解释,在读完本文的二维数组和指针的讲解之后,相信你就会对指针有一个车底的了解了。 本文内容完全属于个人见解与参考文现的作者无关,其中难免有误解之处,望指出更正。 声明:禁止抄袭本文,若需要转载本文请注明转载的网址,或者注明转载自“黄邦勇帅”。 主要参考文献: 1、C++.Primer.Plus.第五版.中文版[美]Stephen Prata著孙建春韦强译人民邮电出版社2005年5月 2、C++.Primer.Plus.第四版.中文版Stanley B.Lippman、Barbara E.Moo著李师贤等译人民邮电出版社2006年3月 3、C++.Primer.Plus.第三版.中文版Stanley B.Lippman等著潘爱民张丽译中国电力出版社2002年5月 4、C++入门经典第三版[美]Ivor Horton著李予敏译清华大学出版社2006年1月 5、C++参考大全第四版[美]Herbert Schidt著周志荣朱德芳于秀山等译电子工业出版社2003年9月 6、21天学通第四版C++ [美]Jesse Liberty著康博创作室译人民邮电出版社2002年3月 第一部分:指针 11.1 基础 1.指针是一个变量,它存储着另一个变量或函数的地址,也就是说可以通过指针间接地引用变量。指针变量包含一个地址,而且可以存储任何数据类型的内存地址,但指针变量却被声明为特定的数据类型,一个指向整型数据类型的指针不能存储一个浮点型的变量地址。 2.指针声明的形式为,数据类型*指针变量名;其中*星号是指针运算符,例如int *x;声明x为int型指针.11.2 指针运算符*和&地址运算符 1.&地址运算符是一元运算符,能反回它的操作数的内存地址.如y=&x;把变量x的地址输入到y中,它与x的值无关,比如x的值为1000,而x的地址为55则,y将接收到地址55. 2.*指针运算符是一元运算符,它是&运算符的相反形式,*运算符能反回位于其操作数所指定的地址的变量的值.例如y = &x;z = *y;假设x的值为1000,地址为55,则第二条语句说明z的值为1000,*y把由y所指向的内存的地址的变量x的值赋给z。*运算符可理解为“在地址中”,则z=*y可描术为“z接收了在址址y中的值。”,3.其实可以把*y当成一个变量来使用,即可以为*y赋值等,例如*y=100;(*y)++;等,但要注意的是对*y的操作相当于是对此指针指向的地址中的变量的操作,即对*y=100的赋值语句,相当于是x=100,而(*y)++则相当于x++。11.3 指针的运算 0.指针只支持4种算术运算符:++,――,+,-.指针只能与整数加减.指针运算的原则是:每当指针的值增加时,它将指向其基本类型的下一个元素的存储单元.减少时则指向上一个元素的存储单元. 1.++,――运算符,假设int型x的地址为200,且int型占4个字节,定义int *p;p=&x;则p++的地址将是204,而不是201,因为当指针p的值增加时,它都将指向下一个int型数据.减少时也是这样,如p――则,p的地址将是196.2.+,-,运算符,注意两个指针不能相加.例int *p;p=&x;假设x的地址为200,则p+9将的指针地址将是200+4*9=236,即p指向了从当前正指向的元素向下的第9个元素. 3.两指针相减,同类型的一个指针减去另一个指针的值将是两个指针分开的基本类型的元素的个数. 11.4 指针和数组 1.在C++语言中使用没有下标的数组名会产生一个指向数组中第一个元素的指针.如char x[20];char *p;p=x;此语句说明将x数组的第一个元素的地址赋给指针p. 2.*(p+4)和x[4]两句都可以访问数组中第5个元素,这里假设int x[33];int *p;p=x;因为p是指向数组x的第一个元素地址的指针,而p+4就是指向第五个元素的指针,而*(p+4)就是第五的个元素了. 3.p[i]语句相当于*(p+i)或x[i]即数组中第i+1个元素的值,假设char x[20];char *p;p=x; 11.5 字符串常量

C++指针与动态分配内存new关键字专题

第一部分:指针 11.1 基础 1.指针是一个变量,它存储着另一个变量或函数的地址,也就是说可以通过指针间接地引用变量。指针变量包含一个地址,而且可以存储任何数据类型的内存地址,但指针变量却被声明为特定的数据类型,一个指向整型数据类型的指针不能存储一个浮点型的变量地址。 2.指针声明的形式为,数据类型*指针变量名;其中*星号是指针运算符,例如int *x;声明x为int型指针.11.2 指针运算符*和&地址运算符 1.&地址运算符是一元运算符,能反回它的操作数的内存地址.如y=&x;把变量x的地址输入到y中,它与x的值无关,比如x的值为1000,而x的地址为55则,y将接收到地址55. 2.*指针运算符是一元运算符,它是&运算符的相反形式,*运算符能反回位于其操作数所指定的地址的变量的值.例如y = &x;z = *y;假设x的值为1000,地址为55,则第二条语句说明z的值为1000,*y把由y所指向的内存的地址的变量x的值赋给z。*运算符可理解为“在地址中”,则z=*y可描术为“z接收了在址址y中的值。”,3.其实可以把*y当成一个变量来使用,即可以为*y赋值等,例如*y=100;(*y)++;等,但要注意的是对*y的操作相当于是对此指针指向的地址中的变量的操作,即对*y=100的赋值语句,相当于是x=100,而(*y)++则相当于x++。11.3 指针的运算 0.指针只支持4种算术运算符:++,――,+,-.指针只能与整数加减.指针运算的原则是:每当指针的值增加时,它将指向其基本类型的下一个元素的存储单元.减少时则指向上一个元素的存储单元. 1.++,――运算符,假设int型x的地址为200,且int型占4个字节,定义int *p;p=&x;则p++的地址将是204,而不是201,因为当指针p的值增加时,它都将指向下一个int型数据.减少时也是这样,如p――则,p的地址将是196.2.+,-,运算符,注意两个指针不能相加.例int *p;p=&x;假设x的地址为200,则p+9将的指针地址将是200+4*9=236,即p指向了从当前正指向的元素向下的第9个元素. 3.两指针相减,同类型的一个指针减去另一个指针的值将是两个指针分开的基本类型的元素的个数. 11.4 指针和数组 1.在C++语言中使用没有下标的数组名会产生一个指向数组中第一个元素的指针.如char x[20];char *p;p=x;此语句说明将x数组的第一个元素的地址赋给指针p. 2.*(p+4)和x[4]两句都可以访问数组中第5个元素,这里假设int x[33];int *p;p=x;因为p是指向数组x的第一个元素地址的指针,而p+4就是指向第五个元素的指针,而*(p+4)就是第五的个元素了. 3.p[i]语句相当于*(p+i)或x[i]即数组中第i+1个元素的值,假设char x[20];char *p;p=x; 11.5 字符串常量

动态分配内存管理源代码及讲解

动态分配内存算法以及源程序讲解 整体思路: 动态分区管理方式将内存除操作系统占用区域外的空间看成一个大的空闲区。当作业要求装入内存时,根据作业需要内存空间的大小查询内存中的各个空闲区,当从内存空间中找到一个大于或等于该作业大小的内存空闲区时,选择其中一个空闲区,按作业需求量划出一个分区装人该作业,作业执行完后,其所占的内存分区被收回,成为一个空闲区。如果该空闲区的相邻分区也是空闲区,则需要将相邻空闲区合并成一个空闲区。 设计所采用的算法: 采用最优适应算法,每次为作业分配内存时,总是把既能满足要求、又是最小的空闲分区分配给作业。但最优适应算法容易出现找到的一个分区可能只比作业所需求的长度略大一点的情行,这时,空闲区分割后剩下的空闲区就很小以致很难再使用,降低了内存的使用率。为解决此问题,设定一个限值minsize,如果空闲区的大小减去作业需求长度得到的值小于等于minsize,不再将空闲区分成己分分区和空闲区两部分,而是将整个空闲区都分配给作业。 内存分配与回收所使用的结构体: 为便于对内存的分配和回收,建立两张表记录内存的使用情况。一张为记录作业占用分区的“内存分配表”,内容包括分区起始地址、长度、作业名/标志(为0时作为标志位表示空栏目);一张为记录空闲区的“空闲分区表”,内容包括分区起始地址、长度、标志(0表空栏目,1表未分配)。两张表都采用顺序表形式。 关于分配留下的内存小碎片问题: 当要装入一个作业时,从“空闲分区表”中查找标志为“1”(未分配)且满足作业所需内存大小的最小空闲区,若空闲区的大小与作业所需大小的差值小于或等于minsize,把该分区全部分配给作业,并把该空闲区的标志改为“0”(空栏目)。同时,在已分配区表中找到一个标志为“0”的栏目登记新装人作业所占用分区的起始地址,长度和作业名。若空闲区的大小与作业所需大小的差值大于minsize。则把空闲区分成两部分,一部分用来装入作业,另外一部分仍为空闲区。这时只要修改原空闲区的长度,且把新装人的作业登记到已分配区表中。 内存的回收: 在动态分区方式下回收内存空间时,先检查是否有与归还区相邻的空闲区(上邻空闲区,

C51单片机动态内存分配

Keil51动态内存分配问题经验 动态内存一般分配在堆中,而静态的内存一般分配在栈中;Keil51中提供了一个建立堆的函数,就是init_mempool(首地址,大小),首地址被定义为xdata 的地址空间,这个函数可以在xdata中定义一个可以动态分配的堆;因为在51中,data区域的空间太小,要动态分配空间,考虑到程序的运行,是不合理的,所以必须在xdata中建立可以动态分配的堆。 STC12C5A60S2内部集成了256字节的RAM,存储类型为data,地址是00H~FFH。其中低128字节是工作寄存器组,包括R0~R7,地址为00H~1FH,20H~2FH地址区为位寻址区,30H~7FH为普通RAM区;高128字节为普通的RAM区。内部RAM中,30H~FFH都是普通用户RAM和堆栈区,可以用来进行内存分配,总共208字节;实际在程序运行中,要在这208字节分配一个堆栈进行动态的内存分配,对于其他的程序运行会有很多不便。 STC12C5A60S2可用的内部扩展RAM的地址空间是:0x000~0x3ff这一地址空间,存储类型为xdata,这部分空间总共占1K字节,可以用来作为堆栈区,进行内存动态分配。 STC12C5A60S2可以扩展64K外部xdata,在没有进行扩展外部存储器的情况下,最好使用上述内部扩展的1K字节,地址为0x000~0x3ff的存储器。 对于其他的51单片机,用户可以参考芯片手册查看系统内部的以及扩展的RAM空间大小和地址,确定data和xdata范围,根据需要自行定义。 目前,我使用STC12C5A60S2总结了两种动态定义的方式。 方式一:给定地址区域 init_mempool(0x0000,0x03ff);//内部扩展1K字节的空间, //都可以作为堆栈空间进行内存分配; 数据结构: typedef struct STU{ uint8id; struct STU*next; }*PSTU,STU_t; 注意:一定在使用init_mempool函数之后使用malloc calloc,,realloc realloc,,free等 malloc,,calloc 函数,因为只有先确定了堆,才能在堆中执行相应的操作; 定义并分配堆空间: PSTU stu; init_mempool(0x0100,500);//内部只能用0x300~0x3ff这一地址空间; 初始化: stu=(PSTU)malloc(sizeof(STU_t)); stu->id=8; stu->next=NULL; 函数:void insertlist(PSTU phead,uint8pos,PSTU stu); 方式二:让系统随机分配

C语言编程要点---指针和内存分配上-Read

C语言编程要点---指针和内存分配上-Read

C语言编程要点---第7章指针和内存分配(上) 指针和内存分配 指针为C语言编程提供了强大的支持——如果你能正确而灵活地利用指针,你就可以直接切入问题的核心,或者将程序分割成一个个片断。一个很好地利用了指针的程序会非常高效、简洁和精致。 利用指针你可以将数据写入内存中的任意位置,但是,一旦你的程序中有一个野指针("wild”pointer),即指向一个错误位置的指针,你的数据就危险了——存放在堆中的数据可能会被破坏,用来管理堆的数据结构也可能会被破坏,甚至操作系统的数据也可能会被修改,有时,上述三种破坏情况会同时发生。 此后可能发生的事情取决于这样两点:第一,内存中的数据被破坏的程度有多大;第二,内存中的被破坏的部分还要被使用多少次。在有些情况下,一些函数(可能是内存分配函数、自定义函数或标准库函数)将立即(也可能稍晚一点)无法正常工作。在另外一些情况下,程序可能会终止运行并报告一条出错消息;或者程序可能会挂起;或者程序可能会陷入死循环;或者程序可能会产生错误的结果;或者程序看上去仍在正常运行,因为程序没有遭到本质的破坏。 值得注意的是,即使程序中已经发生了根本性的错误,程序有可能还会运行很长一段时间,然后才有明显的失常表现;或者,在调试时,程序的运行完全正常,只有在用户使用时,它才会失常。 在C语言程序中,任何野指针或越界的数组下标(out-of-bounds array subscript)都可能使系统崩溃。两次释放内存的操作也会导致这种结果。你可能见过一些C程序员编写的程序中有严重的错误,现在你能知道其中的部分原因了。 有些内存分配工具能帮助你发现内存分配中存在的问题,例如漏洞(leak,见7.21),两次释放一个指针,野指针,越界下标,等等。但这些工具都是不通用的,它们只能在特定的操作系统中使用,甚至只能在特定版本的编译程序中使用。如果你找到了这样一种工具,最好试试看能不能用,因为它能为你节省许多时间,并能提高你的软件的质量。 指针的算术运算是C语言(以及它的衍生体,例如C++)独有的功能。汇编语言允许你对地址进行运算,但这种运算不涉及数据类型。大多数高级语言根本就不允许你对指针进行任何操作,你只能看一看指针指向哪里。 C指针的算术运算类似于街道地址的运算。假设你生活在一个城市中,那里的每一个街区的所有街道都有地址。街道的一侧用连续的偶数作为地址,另一侧用连续的奇数作为地址。如果你想知道River Rd.街道158号北边第5家的地址,你不会把158和5相加,去找163号;你会先将5(你要往前数5家)乘以2(每家之间的地址间距),再和158相加,去找River Rd.街道的168号。同样,如果一个指针指向地址158(十进制数)中的一个两字节短整型值,将该指针加3=5,结 果将是一个指向地址168(十进制数)中的短整型值的指针(见7.7和7.8中对指针加减运算的详细描述)。街道地址的运算只能在一个特定的街区中进行,同样,指针的算术运算也只能在一个特定的数组中进行。实际上,这并不是一种限制,因为指针的算术运算只有在一个特定的数组中进行才有意义。对指针的算术运算来说,一个数组并不必须是一个数组变量,例如函数malloc()或calloc()的返回值是一个指针,它指向一个在堆中申请到的数组。 指针的说明看起来有些使人感到费解,请看下例: char *p; 上例中的说明表示,p是一个字符。符号“*”是指针运算符,也称间接引用运算符。当程序间接引用一个指针时,实际上是引用指针所指向的数据。 在大多数计算机中,指针只有一种,但在有些计算机中,指向数据和指向函数的指针可以是不同的,或者指向字节(如char。指针和void *指针)和指向字的指针可以是不同的。这一点对sizeof运算符没有什么影响。但是,有些C程序或程序员认为任何指针都会被存为一个int型的值,或者至少会被存为一个long 型的值,这就无法保证了,尤其是在IBM PC兼容机上。

相关文档
相关文档 最新文档