第十二章-类和动态内存分配

本章前面演示了一个stringbad类。
12.1.1中主要说了两个事:
1、如果类成员为指针,则类对象中保存的仅仅是指针,指针指向的数据不保存在对象中,而是单独在堆内存中。对象仅仅保存了去哪里查找数据的信息。
2、由于类成员为指针,所以需在构造函数中用new去开辟内存存放数据,对应的,在析构函数中则必须有delete来释放存放数据的内存。new对应delete,new[]对应delete[]。

C++自动提供如下成员函数,如果没有相应定义的话,则会提供默认:
1、默认构造函数
2、默认析构函数
3、复制构造函数
4、赋值运算符
5、地址运算符(也就是this指针)
6、移动构造函数(C11)
7、移动赋值运算符(C11)

stringbad类出现的问题就在复制构造函数和赋值运算符上:
复制构造函数用于将一个对象复制到新创建的对象中。也就是说它用于初始化过程中,而不是常规赋值!而且每当程序生成对象副本时,都会调用复制构造函数。所以按值传递会慢,引用传递不会创建副本,会快。

Stringbad A("abc");
Stringbad B(A);//调用了默认复制构造函数,生成B对象。

默认的复制构造函数只会逐个复制非静态成员的值(浅复制)。所以在上面语句中,只复制了A对象中指针值,让AB两个对象的指针成员指向了同一块数据。。这就尴尬了。。。
所以解决的方法是自己重新定义一下复制构造函数,在函数中为B开辟新的内存,并把数据copy过去,这样AB的指针各有自己指向的数据。

StringBad::StringBad(const StringBad& st)
{
    len = st.len;
    str = new char [len+1];//新开一个内存,把数据copy过去就好了。
    std::strcpy(str, st);
}

搞两张图一目了然:
这里写图片描述

这里写图片描述

同理赋值运算符也是因为浅复制导致的问题,解决办法跟复制构造函数差不多,略有差别:
1、由于赋值运算符操作的对象可能本身有数据(因为赋值运算并不限定只在初始化时使用),所以第一步应先将之前指向的数据释放掉。
2、应当避免赋值给自身,因为上一步释放,再copy数据的话,发现数据没了。。。
3、返回一个指向对象的引用。这样就可以连=了。

就像这样写:

StringBad& StringBad::operator=(const StringBad& st)
{
    if (this == &st)//先检查一下是不是本身,如果是的话直接返回本身
        return *this;
    delete[] str;//正常情况,先释放原来指向的数据
    len = st.len;
    str = new chat[len+1];//然后开个新内存
    std::strcpy(str, st.str);//把数据copy过来
    return *this;//在返回本身就好了,整个过程下来,类指针成员和指向的地址都被刷新了。
}

C11中用nullptr表示空指针,空指针对delete和delete[]都适用。

静态类成员函数:
在函数声明前加static关键字,将函数变为静态成员函数。如果声明和定义一起,没啥说的,加上static;如果声明和定义分开,只在声明处添加static,定义处不要加
举个栗子:

//类声明中:
static int HowMany(){return num_string;}//前面加static,只能使用静态成员。

//调用时:
int count = String::HowMany();//使用类名作用域解析符来调用,不能通过对象调用。

说几点:
1、不能通过对象调用静态成员函数。
2、静态成员函数不能使用this指针。
3、静态成员函数只能使用静态数据成员。不与特定的对象关联
4、若声明为公有,可以使用类名解析符来调用它。
其实这些特性也好理解,因为所有静态成员,不论静态数据还是静态函数,都不与某个具体的对象关联,它是对应整个类型的,而且只有一个副本,所有对象共用。所以跟具体对象有关的特性它都不具备。

在构造函数中使用new应该注意的事项:
1、构造函数中使用new初始化指针成员,则析构函数中应有对应的delete。
2、new对应delete、new[]对应delete[]。
3、如果有多个构造函数,new的方式必须一样,要么都为new要么都为new[],因为析构函数只有一个,要对应起来。
4、应定义复制构造函数。
5、应定义赋值构造函数。

12.4有关返回对象
返回对象一般有四种方式:
1、返回指向const对象的引用
如果函数返回传递给它的对象,通过返回引用来提高效率!注意一点就是不要返回函数局部变量的引用,因为函数结束后会被释放。传入对象和返回对象都为const,要对应好。
2、返回指向非const对象的引用
此种情况有两个典型的用法就是重载赋值运算符(=)和重载有cout一起使用的<<
运算符。=可以提高效率,而<<是因为必须要这样做。说一下为什么<<必须这样做,因为如果返回ostream对象的话,会要求调用ostream类的复制构造函数,但是ostream类中并没有公有的复制构造函数。
3、返回对象
如果返回函数中的局部变量,则不能返回引用,只能返回对象。被重载的算数运算符基本属于这一类(很明显A+B的结果既不是A也不是B,也不想更改A或者B,没办法,只能再创建一个实体对象,然后返回这个对象了)。
4、返回const对象
这种仅仅是为了防止第三种返回对象情况的误操作。比如,带三种情况返回对象的情况下,不能限制如下语句:A+B=C因为A+B是一个对象,可以被赋值为C。但是这种语句基本毫无意义,为了防止误写,所以在返回对象时,如果不对其更改的话最好加上const限制。
总结:返回局部对象不要用引用。返回没有复制构造函数的对象必须用引用。两者皆可的首选引用,效率更高。

12.5使用指向对象的指针
主要就是new:

String* favorite1 = new String//调用默认构造函数创建对象,此对象无名,只用指针定位和调用。
String* favorite2 = new String("robin");//同理,调用自定义构造函数。
*favorite1; //解引用获取对象本身跟基本类型无区别。
favorite1->...; //->运算符调用对象成员和方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值