$Chapter 12.classes and dynamic memory allocation
1.c++类中有五个特殊的成员函数——
(1)默认的无参构造函数
(2)默认的析构函数
(3)默认的复制构造函数
(4)默认的赋值符(=)重载函数
(5)默认取地址符(&)重载函数
这里所谓的默认就是指没有自定义以上函数编译器会给出默认的版本,&重载函数没什么可说的,默认就是返回当前this指针,这个没有任何问题;=重载函数默认的是调用复制构造函数,把右边的对象复制一份给左边;默认的复制构造函数是直接把原对象的地址复制一份。比如假设有Student类,如下语句:
Student tom(“Tom”);//1
Student tom2=tom;//2
以上语句2相当于Student tom2=Student(tom),即这里的=重载函数默认相当于复制构造函数,如果没有给出复制构造函数的定义,则直接把tom对象的地址复制给了tom2,那么他们表示的就是同一个对象了,这可能会出问题,所以对于一个类,一般来说除了构造函数和析构函数要定义外,一般还应定义复制构造函数。复制构造函数原型声明如下:
Student(const Student &);//进行逐个成员变量的复制
使用到复制构造函数的情况有(加入lily是一个Student类对象):
(1)Student s1(lily);
(2)Student s2=lily;//上面已说,这个等效于(3)
(3)Student s3=Student(lily);
(4)Student *ps4=new Student(lily);
(5)Student function();//返回Student类对象的函数会返回该对象复制的一个副本
(6)void function(Student s1);//以Student类对象作为函数参数实参会复制到形参
(7)总之是所有值传递的复制都会调用复制构造函数
另外,如果使用到赋值号还要考虑重载赋值号,以避免和复制构造函数一样的浅复制问题。
2.浅复制与深复制——浅复制只复制值,对于一些指针也是直接复制指针的值,深复制会复制指针指向的内容,即所谓的“深”复制。
3.函数返回对象——到底是返回对象还是常对象还是对象引用还是常对象引用?
(1)返回对象引用,因其高效性(无需隐式调用复制构造函数)和必要性(有时候需要对返回对象进行操作而且希望操作作用在原始对象上比如<<重载函数用于输出,因为cout没有复制构造函数,只能采用引用);
(2)返回对象,当返回的对象不是主调函数中定义的而是被调函数中新建的,因为被调函数执行完毕之后该对象会被销毁,所以也无从返回引用;
(3)返回常对象,显然当不希望返回的对象被更改是应该返回常对象,常对象引用也类似。
4.new和placement new——这是两种不同的内存分配方式,使用placement new需要加入头文件#include<new>且需要先开辟内存空间,如:
char *buffer=new char[512];//allocate 512 bytes buffer Class_name *pclass=new (buffer) Class_name;//create an object of Class_name type in //buffer and make pclass points to it //do some operations… Pclass->~Class_name();//explicitly call the destructor function,in the opposite order of objects’ creation delete buffer[];//delete the space(after destructor being called)
这种placement new使用起来既麻烦还需要做更多的内存管理容易出错,故知道下就好,不要滥用,除非想要对内存进行一些神奇的操作。
5.NULL和nullptr和0和\0——NULL和nullptr和0都可以表示空指针,0是为了兼容C里面的用法,NULL更直观,nullptr关键字是C++11里面的新用法,而\0表示字符串的结尾。
6.使用new和delete操作内存要对应(new对delete,new[]对delete[]),不同的构造函数使用new的方式也要对应,因为不管有几个构造函数,都只对应着一个析构函数。
7.复制构造函数或是=运算符重载函数要注意采用深复制而不是浅复制,但是复制之前检查一下,不要把自己复制给了自己。
8.C++中有成员初始化列表的语法,即在构造函数名后面冒号然后初始化成员变量,对于引用成员变量来说,这是其唯一初始化方法,因为他们都只能在创建的时候初始化,所以不能在构造函数体里初始化。初始化列表的语法只能用于构造函数。
9.C++11也支持类内初始化成员,即在声明成员变量后就初始化它(就像在c#中一样),这样可以方便地在成员声明时就给定一些默认值。
10.C++中会有一些默认自动生成的类成员函数,如默认构造函数,默认的复制构造函数和默认的=运算符重载函数,但是有时候我们暂时没定义这些函数也不想编译器随便给个默认的函数,可以声明这些函数原型为私有,一方面覆盖自动生成的默认版本,一方面让其无法在类外使用,以后可以进一步实现这个复杂的函数。这样对于返回该类对象的函数则会出错,因为函数返回对象时默认要使用复制构造函数,当然你可以选择返回对象引用。
11.成员变量有几种可以初始化的位置,先后顺序为:先进行类内初始化,然后进行成员初始化列表,最后是构造函数中的赋值;后进行的操作会覆盖前进行的,但是非静态常成员只可类内初始化或是成员初始化列表中初始化,引用成员对象只可在列表中初始化。
$Chapter 14.reusing code in c++(inheritance, containment and template class)
1.为什么用explicit——在c++中的一些函数有时候会被隐式地调用,加上explicit则只能显式地调用。比如含有一个参数构造函数默认为类型转换函数,在类型转换的时候会隐式调用,如
Student lucy;
lucy=15;
lucy是个Student类对象,下面却把int型15赋给它,这里会隐式地调用类型转换构造函数:Student(int n)构造一个临时对象然后使用赋值运算符重载函数把它赋值给lucy,因为单个参数的构造函数本身默认为类型转换函数。但是这可能不是我们的本意,我们本意这个int参数表示的可能是学生的年龄或是修过的科目数等,所以这里给这个单参构造函数加上explicit就在编译的时候杜绝了这种错误。
2.私有继承是has-a的关系而不是is-a的关系,即私有继承和包含(组合)是一样的,只不过包含是通过成员对象名称访问成员对象数据,而私有继承是通过将自己类型转化来获取相应父类数据,这里的父类对象称之为subobject,亚对象、部分对象,无论怎么翻译,意思就是该对象的一部分的意思,即要么是该类的数据中属于基类的那一部分(继承而来),要么是包含的成员对象的数据,他们都是该类的整体数据的一部分且又均为object,所以称为subobject。
3.何时用私有继承何时用包含,一般情况下包含更好,更不易出问题;且如果包含多个subobject,则只能用包含,因为私有继承同一个基类只能有一个subobject。而私有继承相对于包含的优势有两点:一私有继承可以访问基类的保护成员而包含不可以,二私有继承可以重定义基类的抽象方法而包含不可以。
4.类保护继承或私有继承而来的接口对外不可见,但是c++中提供了两种方法使得外部可使用这种继承来的基类方法——一种是定义一个公用接口,接口调用基类方法,而外部访问该接口,即可做到外部访问该类基类的方法;另一种是使用using关键字直接使基类方法对外可见(这种方法只可用于继承,不可用于包含)。如:
class A;//a class with public function funcA(); class B:private A{ public: using A::funcA;//declare funcA in base-class A to be accessible from outside world //using declaration just uses function name without parentheses and //parameter list … }; B b1; b1.funcA();//access funcA derived from base-class through b1
5.模板类——
(1)模板类的声明:
template<typename Type>//or template<class Type>,Type is an identifier representing a type class Stack{ //define private and public fields,use Type identifier to replace future data type //… }; template<typename Type> Stack<Type>::Stack() { //implementation of member function is no different from member function of normal class //except that the class scope identifier followed by a pair of angle brackets and type identifier //future data type(generic type) replaced by Type identifier }
有一点需要注意的是模板类并没有定义类,只是向编译器说明怎么构造类,所以其声明和实现不能分开在两个文件里(正常的类可以在头文件声明在源文件实现);在模板类声明里面可以直接用类名表示函数返回类型、函数参数类型等,但是在类声明以外,实现的时候用的必须是类跟尖括号加类型参数Type。
(2)模板类的使用:
Stack<int> st;//use a particular type to replace type parameter
模板类不像模板函数会根据使用的参数自动识别类型参数,模板类需要加尖括号和指定参数类型。
(3)模板类其他——模板类还可声明多个类型参数,还可使用non-type类型参数,还可进行嵌套(递归)等等特性,如:
//1.using more than one type parameter template<typename T1,typename T2>class Pair{/*more code abbreviated here*/} //2.using non-type parameter template<class Type,int n>class Stack{/*more code abbreviated here*/} //3.using recursive template class Stack<Stack<int>> st; //4.using default value for type parameter //note you cannot use default value in template function’s type parameter template<class T1,class T2=int>class Whatever{/*more code*/} //5.using default value for non-type parameter //note you can also use it in template function template<class T, int n=6>class Dontcare{…} //6.implicit instantiation, explicit instantiation and explicit specialization //6.1.normally,we use template class via implicit instantiation Stack<char *> *stStr; //implicit instantiation only generate class definition when needed stStr=new Stack<char *>;//here, definition of class Stack is generated when an object is in need //6.2.using explicit instantiation, we can generate class definition without creating objects template class Stack<char*>; //6.3.with explicit specialization, we can create class definition that is different from generic one //in case you need to redefine template class for a specific type instead of a generic type //specialized template class will override the generic one with the same type parameter template<>class ClassName<specialized-type-name>//a completely specialized template { //code that specially designed for specialized-type-name //for example, if the > operator function defined in generic template class doesn’t //apply to this specific type, we can redefine it here } //6.4.partial (explicit) specialization, compared to complete explicit specialization template<class T1,class T2> class Pair{..};//general template template<class T1>class Pair<T1,int>{…};//a partially specialized template template<class T>class WTF{…};//general edition of template class template<class T*>class WTF{…};//partially specialized edition //6.5.template class can be member of a class, structure or a template class //a generic template class can also be the type parameter of another template class //template class declaration can have friends //these are rather complicated hence not expounded here //refer to c++ primer plus 6th edition, page 852-866