您的位置:首页 > 其它

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

2017-12-03 19:13 218 查看
本章前面演示了一个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)//先检查一下是不是本身,如果是的话直接返回本身

4000
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->...; //->运算符调用对象成员和方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: