加油鸭
本章前面演示了一个stringbad类。
类成员为指针
- 如果类成员为指针,则类对象中保存的仅仅是指针,指针指向的数据不保存在对象中,而是单独在堆内存中。对象仅仅保存了去哪里查找数据的信息。
- 由于类成员为指针,所以需在构造函数中用new去开辟内存存放数据,对应的,在析构函数中则必须有delete来释放存放数据的内存。new对应delete,new[]对应delete[]。
特殊成员函数
C++自动提供如下成员函数,如果没有相应定义的话,则会提供默认:
- 默认构造函数
- 默认析构函数
- 复制构造函数
- 赋值运算符
- 地址运算符(也就是this指针)
- 移动构造函数(C11)
- 移动赋值运算符(C11)
复制构造函数
1 | Stringbad A("abc"); |
- 上面两个都调用了复制构造函数,复制构造函数用于将一个对象复制到新创建的对象中。也就是说它用于初始化过程中,而不是常规赋值!而且每当程序生成对象副本时,都会调用复制构造函数。所以按值传递会慢,引用传递不会创建副本,会快。
- 默认的复制构造函数只会逐个复制非静态成员的值(浅复制)。
浅复制
浅复制就是逐个复制非静态的值,那么当类成员有指正类型时,复制会带来什么问题?
当类成员有指正类型
默认构造函数(浅复制)
- 默认构造函数(浅复制),会让两个对象的指针成员指向了同一块数据,两个对象析构函数都会删除属于自己的指正成员,出现错误。
- 解决办法
重新定义一下复制构造函数,在函数中为B开辟新的内存,并把数据copy过去,这样AB的指针各有自己指向的数据。
赋值运算符(浅复制)
同理赋值运算符也是因为浅复制导致的问题,解决办法跟复制构造函数差不多,略有差别。
- 由于赋值运算符操作的对象可能本身有数据(因为赋值运算并不限定只在初始化时使用),所以第一步应先将之前指向的数据释放掉。
- 应当避免赋值给自身,因为上一步释放,再copy数据的话,发现数据没了。
- 返回一个指向对象的引用。这样就可以连=了。
静态类成员函数
在函数声明前加static关键字,将函数变为静态成员函数。如果声明和定义一起,没啥说的,加上static;如果声明和定义分开,只在声明处添加static,定义处不要加。
eg:1
2
3
4//类声明中:
static int HowMany(){return num_string;}//前面加static,只能使用静态成员。
//调用时:
int count = String::HowMany();//使用类名作用域解析符来调用,不能通过对象调用。
注意事项
- 不能通过对象调用静态成员函数。
- 静态成员函数不能使用this指针。
- 静态成员函数只能使用静态数据成员。不与特定的对象关联。
- 若声明为公有,可以使用类名解析符来调用它。
构造函数中使用 new 应该注意的事项
- 构造函数中使用new初始化指针成员,则析构函数中应有对应的delete。
- new对应delete、new[]对应delete[]。
- 如果有多个构造函数,new的方式必须一样,要么都为new要么都为new[],因为析构函数只有一个,要对应起来。
- 应定义复制构造函数。
- 应定义赋值构造函数。
有关返回对象的说明
返回对象一般有四种方式
返回指向const对象的引用
如果函数返回传递给它的对象,通过返回引用来提高效率!注意一点就是不要返回函数局部变量的引用,因为函数结束后会被释放。传入对象和返回对象都为const,要对应好。
返回指向非const对象的引用
此种情况有两个典型的用法就是重载赋值运算符(=)和重载有cout一起使用的<< 运算符。=可以提高效率,而<<是因为必须要这样做。说一下为什么<<必须这样做,因为如果返回ostream对象的话,会要求调用ostream类的复制构造函数,但是ostream类中并没有公有的复制构造函数。
返回对象
如果返回函数中的局部变量,则不能返回引用,只能返回对象。被重载的算数运算符基本属于这一类(很明显A+B的结果既不是A也不是B,也不想更改A或者B,没办法,只能再创建一个实体对象,然后返回这个对象了)。
返回const对象
这种仅仅是为了防止第三种返回对象情况的误操作。比如,带三种情况返回对象的情况下,不能限制如下语句:A+B=C因为A+B是一个对象,可以被赋值为C。但是这种语句基本毫无意义,为了防止误写,所以在返回对象时,如果不对其更改的话最好加上const限制。
总结
返回局部对象不要用引用。返回没有复制构造函数的对象必须用引用。两者皆可的首选引用,效率更高。
使用指向对象的指针
主要就是new1
2
3
4String* favorite1 = new String;//调用默认构造函数创建对象,此对象无名,只用指针定位和调用。
String* favorite2 = new String("robin");//同理,调用自定义构造函数。
*favorite1; //解引用获取对象本身跟基本类型无区别。
favorite1->...; //->运算符调用对象成员和方法。
头文件
双引号和尖括号
如果文件名包含在尖括号中,则C++编译器将在存储标准头文件的主机系统的文件系统的中查找。如果文件名包含在双引号中,则编译器将在当前目录下查找。
条件编译
ifndef使用条件编译防止多次包含头文件。
注意还有ifdef。1
2
3
4
...