C++Primer学习笔记(类的动态内存)

一、动态内存和类

首先要强调的一点是,C++中的类是描述如何使用内存的,但并不参与分配内存的工作,所以,在类的声明中,不能出现给类成员赋值的情况,即使是类中有静态类型成员的时候,也不能够这样做,所有的对类成员的初始化以及赋值工作都必须拿到类的外部来实现,或者写入到构造函数里面。

一般来说,静态成员的初始化都是要在类的外部进行的,但是如果静态数据成员是const或者是枚举类型的话,那么就 可以在类的声明的内部进行初始化。


其实,当我们设计一个类的时候,类的实例化将会创建出对象,对象本身就是有内存的,但是删除对象并不能删除对象在实例化时所分配的内存空间。假设一个类中有一个用new申请的一个数组,那么每次创建一个对象,也都会初始化一个属于这个对象的数组,但是这个数组并不跟对象存储在一起,他只是把这个数组的地址存放在这个对象的内存里,方便寻找,所以在执行析构函数删除一个对象的时候,必须利用delete删除这个属于对象的数组,不然,就会造成内存泄露。


一般来说,析构函数在定义对象的代码块执行完毕后会自动调用。


当我们在进行对象直接赋值的时候,我们的编译器将会为我们创造一个默认的构造函数,这个构造函数就是

类名(const 类名 &)

这个函数被称为复制构造函数。


二、特殊的成员函数-------复制构造函数。

在C++中,自动提供了下列这些成员函数:

1、当没有创建构造函数和析构函数的时候,编译器会默认为我们提供不带任何参数的构造函数以及析构 函数。

2、复制构造函数

3、赋值运算符

4、取地址符


复制构造函数用于将一个对象复制到另外一个对象中。

当我们新建一个对象,想把这个新建的对象初始化为同类已经存在的对象时,这时候,复制构造函数就会调用。另外,在函数传参的时候,如果是两个类对象进行值的传递的时候,还有函数的返回值的类型是对象的时候,都会调用复制构造函数。

复制函数在产生对象副本然后赋值给新对象的时候,只会复制非静态成员,因为静态成员是被所有的对象公有的,不属于哪一个对象。复制函数在复制对象的时候,基本上属于对象中成员的复制,而且是单纯的值的复制,这种复制方式也成为浅拷贝。


浅拷贝在程序当中容易出现的问题有这样一种情况,因为它是单纯的值的传递:

class A()

{

char *p;

A(const char *ptr)

{

p = ptr;

}

}


上面的这个类有一个成员还有一个构造函数,作用就是在创建对象的时候,给一个字符串的参数,然后把他的地址赋值给成员变量p;

如果我现在 创建一个对象a: A a = A("haha");

之后我执行这样的一句代码 A b = a;

你可能感觉这样的赋值是可行的,我们前面说到了,每一个对象都有一套属于他自己的成员变量,也就是说,a和b两个对象都有自己的一个成员变量p然后指向自己所属的字符串。但是事实上,上面的A b = a的这个语句并没有实现这种功能,这个语句将会隐式的调用复制构造函数,也就是前面说的浅拷贝。浅拷贝的原则就是只拷贝值,不管其他的东西。a对象里面的P成员变量明明存的是他的字符串的地址,在浅拷贝的时候,b对象里面的p成员,存的不是自己所属字符串的地址,存的是a所属字符串的地址。你再输出的时候可能感觉一切正常,但是假如你一旦觉得a对象没用了,删掉a对象,反正b里面也是存的a的东西没有关系,那么当你再输出b对象中的字符串的时候,你就应该知道,b对象中p变量存的字符串地址标识的那一段内存空间的内容早已经被删掉了。这就是浅拷贝的问题。

所以说,有了以上的问题之后,我们就要想出一个叫做深拷贝的办法,真正的做一次对象的复制操作,用来初始化另外一个新对象,使得新的对象也能够拥有独立的数据成员。


深拷贝和浅拷贝都有他们合适的用处,其中一个就是,当类中的成员有指针类型的时候,那么应该添加一个显式的赋值构造函数用于实现深拷贝,为了避免因为默认的浅拷贝而造成的两个对象的数据出现重叠的情况。


关于复制构造函数还有 一点要明确的就是:对象初始化用的是复制构造函数,但是 对象的赋值用的 是重载的赋值运算符。



三、赋值运算符

一般来讲,C++的编译器会根据赋值的对象和被复制 的对象选择相应功能的赋值运算符。默认的赋值运算符和默认的复制构造函数一样,都是属于浅拷贝。所以出现的问题也就是一样的。

赋值运算符要注意的是:

首先要避免不能自己给自己赋值,其次,要删除目标对象里面原有的数据,也就是相当于一个清空操作。另外赋值运算符的重载只能作为成员函数来进行定义。


四、new和delete

delete一般来说经常出现于析构函数当中,delete只删除空指针,或者new函数返回的指针,如果将delete用于除了这两种指针类型之外,都会造成不确定的后果。


在构造函数中使用new,那么在析构函数中一定要出现对应的delete。new和delete必须相互兼容,new[]对应于delete[]。如果有多个构造函数,那么他们在使用new的时候,必须要保持一致,因为任何一个构造函数都必须与他的析构函数兼容,然而析构函数只有一个。delete只适用于new返回的指针以及空指针。


在C++中,空指针的表示有三种 形式:NULL , 0, nullptr。最后一种是c++11的最新添加进来的。


五、构造函数中使用new的注意事项


在类的成员中有数组等元素需要在构造函数里面利用new申请内存空间的时候,一定要记得定义一个参数为该类对象类型引用的一个构造函数,作为这个类的复制构造函数来进行深拷贝操作。否则,默认的复制构造函数很有可能在内存的使用上出现问题。并且要保证类中需要的静态数据成员不受影响。要定义类似操作的还有赋值运算符,在类中有必要进行重载,功能类似于复制构造函数。复制构造函数一般在新对象被初始化为另一个对象的时候比较常用。而赋值运算符则在出了初始化之外的赋值操作中调用。


六、关于函数返回值为对象的问题

类中的一些成员函数通常来 说,返回值可能会为对象,常量对象,对象引用,常量对象引用。

在返回值为对象的时候,将会调用复制构造函数。

但是返回值为对象的引用则不会调用。


七、类与New

如果类中的构造函数使用new来对类成员进行初始化,那么在析构函数中一定要记得用delete删除。delete只对new返回的指针以及空指针有删除的作用,其余的结果不确定。应该显示的定义一个复制构造函数来防止用一个旧的对象初始化另外一个新的对象而造成的内存泄露。

还要重载一个赋值运算符,防止对象之间的赋值也因为内存管理的问题出现错误。



八、构造函数的执行

从概念上说,在创建对象的时候要调用相应的构造函数,对象在执行构造函数代码之前就会被创建。也就是说,在调用之后,执行该函数代码之前,对象就已经被创建了,这个对象所需要的成员变量的内存就会被分配。其实构造函数内部代码的主要作用就是为对象所属的类成员进行赋值操作。 但是,如果类的成员里面有常量的话,常量是不能够被赋值的,只能够初始化,所以在构造函数里面给常量成员赋值是错误的。因为在执行这些代码的时候,常量就已经被创建好了。所以说,我们就需要在执行构造函数代码之前给常量初始化,也就是在创建对象的同时。

C++提供了一个特殊的工具,叫做成员初始化列表来完成这个工作。成员初始化列表是在构造函数后面加上一个分号,然后写上要初始化的成员,如果有多个成员可以依次用逗号隔开。在初始化成员列表中给成员初始化的值可以是常量也可以是构造函数里面的参数,并且初始化列表可以用于任意成员的赋值。出了常量成员在创建对象的时候必须使用这种初始化成员列表来进行初始化之外还有一种就是引用变量的成员,因为引用必须在初始化的时候就为他指定一个明确的数据。但是要明确,这种 初始化成员列表的形式只能用于构造函数。另外,成员被初始化的顺序和她们在类中被声明的顺序相同,也就是说,初始化列表 中的顺序是无关紧要的。




在使用定位new运算符创建对象的时候,要删除对象的时候,需要显式的调用对象的析构函数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值