c++ primer plus速记

$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

 

$Chapter 16.the string class and the Standard Template Library(STL)

 

1.智能指针——auto_ptr(c++98)和unique_ptr、shared_ptr(c++11)

智能指针其实是模板类,可以像指针一样指向内存,但是有析构函数,以为着指针失效时可以自动调用析构函数释放指针指向的内存,这样可以免去一般指针的每次new都要记得delete的繁琐。用法如下:

#include<memory>

std::auto_ptr<double> ps(new double);

*ps=25;

double *pn=new double(10);

ps=pn;//implicit conversion not allowed

ps=auto_ptr<double>(pn);//allowed, but be careful, memory that ps points to will be deleted

                        //when ps expires meaning space pointed to by pn is also erased, is

              //that what you really want?

智能指针的一个构造函数如下:

Template<class X> auto_ptr

{

public:

  explicit auto_ptr(X *p=0) throw();

…

}

因为explicit关键字,所以智能指针不能由一般指针隐式转换,只能显式转换。

Unique_ptr, shared_ptr用法与auto_ptr一样,c++11中deprecate auto_ptr的使用,建议使用新的另两种智能指针,当然如果你的编译器不支持那就只能使用auto_ptr。

那么三种智能指针有什么区别?

//typedef auto_ptr<double> smart_ptr;                    //#1

//typedef unique_ptr<double> smart_ptr;                 //#2

//typedef shared_ptr<double> smart_ptr;                  //#3

smart_ptr p1(new double(32));

smart_ptr p2;

p2=p1;                                                   //#4

以上,如果多个智能指针指向同一段内存,那么当他们失效时都调用delete则会多次删除同一段内存。去掉#1的注释(smart_ptr为auto_ptr<double>),p2指向对象,p1则失去对象所有权,指向空;去掉#2注释,则编译错误,因为unique_ptr不允许多个指针同时指向同一个对象,除非右值是临时指针,如p2=unique_ptr<double>(new double(32));去掉#3注释没有问题,因为shared_ptr指针使用指针计数,只有计数为0即指向同一对象的最后一个指针销毁的时候才会调用析构函数。

2.STL(Standard Template Library)是泛型编程(generic programing)的典范。STL中提供了许多通用的容器以及容器操作访问元素的一些方法;STL中也提供了一些函数对各种容器进行通用的处理,如for_each()函数提供遍历操作,random_shuffle()函数提供打乱顺序操作,sort()函数提供排序操作,他们都不是容器的成员函数,属于通用算法,其中for_each()函数有三个参数,第一个、第二个是指示范围的迭代器,第三个是函数对象,表示每次迭代进行的操作,for_each循环代替for循环可以使代码更整洁。

//for_each loop in three languages

for(int i:arr){                                    //java style: arr is a array of ints

   //do something here                              //this same usage also exists in c++

}

foreach(int ele in arr){                            //c# style

  //do something here

}

for_each(books.begin(),books.end(),function);       //c++ style: function is the name of a functor

                               //c++ style is more concise and elegant. 

                               //books here is a container object, iterators mark the range

C++的类型推断(type deduction):
vector<double> arr;

for(auto x:arr)std::cout<<x<<std::endl;   //c++ auto type deduction

for(auto &x:arr)x=0;              //you can alter contents in arr as long as using reference

                                         //a for_each loop can’t achieve this as functor can’t alter element in arr

 

 

3.泛型编程。面向对象编程面向的是数据类型,而泛型编程则是面向算法。实现一个算法以实现一个功能,而不管数据类型是什么,算法使用方法不变。

vector<double> ctn;                                      //#1

//list<double> ctn;                                      //#2

vector<double>::iterator pr;                            //#1

//list<double>::iterator pr;                             //#2

for(pr=ctn.begin();pr!=ctn.end();++pr){    //the usage is the same, whatever #1 or #2 is in effect

  //do something

}

//you can even simplify usage by auto type deduction

for(auto pt=ctn.begin();pt!=ctn.end();++pt){

  //do whatever

}

以后要使用这个算法处理自定义的数据类型,可以在类型中定义iterator类型,重载*运算符解引用(dereference),重载++运算符、实现begin(),end()函数获取一个首地址一个“尾后地址”(past-the-end)以实现迭代等。

要实现泛型编程,首先确定一个通用算法实现的功能,然后尽可能以宽泛的语言描述进行的操作,使其独立于数据类型和容器类型,然后针对具体类型在容器中定义满足算法要求的迭代器,如上面的查找算法的迭代器要满足要求:能获取表示首尾元素的迭代器,能递增迭代,能解引用获取元素。不同的算法可能对迭代器有不同的要求,所以需要不同种的迭代器,STL给出了通用容器类和一些通用算法并提供了五种迭代器。

4.五种迭代器——

1=>InputIterator,OutputIterator

2=>ForwardIterator

3=>BidirectionalIterator

4=>RandomAccessIterator

a.高层级的迭代器除了有低层级的迭代器的功能外再加上自己的功能,之所以定义不同层级的迭代器是因为算法对迭代器要求不同,算法要求越低的(即使用越低层级迭代器的)能使用越大范围的容器所以越通用。高层级的迭代器可以用于要求低层级迭代器的算法。不同的容器可能会把其中的iterator定义为不同层级的迭代器,会适用于不同的算法。

b.迭代器的这种层级类似于继承,但是满足一定要求的迭代器并不能说是一个类,这里称之为概念(concept),而高层级的迭代器概念上继承自低层级,这叫做提炼(refinement),特定的一个迭代器满足特定的一组要求称之为这个概念(这组要求)的模型化(model)。比如说find()函数使用的是input iterator,所以我们可以使用input iterator concept的一个model作为参数,也可以使用这个concept的提炼random access iterator concept的一个model,因为就像子类对象也是父类对象,random access iterator concept的model也是input iterator concept的一个model。一般的指针就是一个random access iterator concept 的一个model,是最高层级的iterator,所以他可以用于任何以iterator为参数的函数,这样STL里的通用算法不但可用于通用容器,也可用于普通数据,因为指向普通数组元素的指针本身也是符合函数所有要求的迭代器。

//the prototype of general algorithm find() and sort() are like below

//they only imply what level of concept is being used without designate a certain type

//thanks to template

template<class InputIterator,class T>

InputIterator find(InputIterator first,InputIterator last,const T& value);

template<class RandomAccessIterator>

void sort(RandomAccessIterator first,RandomAccessIterator last);

c.一些预定义的特殊迭代器——ostream_iterator,istream_iterator,back_insert_iterator,front_insert_iterator,insert_iterator如下:

#include<iterator>

vector<int> dice(10);

int arr={1,2,3,4,5,6,7};

//an out stream iterator, the first constructor argument is an output stream object,

//the second is a separator between each element being output

ostream_iterator<int,char>out_iter(cout,” ”);
*out_iter++=15;//assign 15 to container pointed to by out_iter and augment out_iter

copy(dice.begin(),dice.end(),out_iter);//output dice to cout

//copy from istream_iterator(which is cin here) to dice vector

copy(istream_iterator<int,char>(cin),istream_iterator<int,char>(),dice.begin());

copy(dice.rbegin(),dice.rend(),out_iter);//using a reverse iterator

//others like back_insert_iterator, front_insert_iterator and insert_iterator, like the name

//suggests, can be used to insert element(s) to container at the end or front or wherever

//they convert copy function’s overriding operation to insertion

back_insert_iterator<vector<int> >back_iter(dice);

copy(arr,arr+7,back_iter);

insert_iterator<vector<int> >insert_iter(dice,dice.begin());

copy(arr,arr+7,insert_iter);

预定义的特殊迭代器拓展了通用算法的功能,以上迭代器使得copy算法可以从输入流复制到容器,从容器复制到输出流,正向输出或是反向输出,还可以向容器插入元素等等,可见特殊的迭代器大大拓展了通用算法的功能。

5.容器介绍,deque, list, queue, priority_queue, stack, vector, map, multimap, set, multiset, bitset, forward_list, unordered_map, unordered_multimap, unordered_set, unordered_multiset,略。C++ primer plus 6th edition, page 1007 to 1026.

6.Function object.任何对象只要能够加上括号当函数用的就叫functor,比如一般函数名,函数指针以及类对象(只要这个类对象重载了()运算符,如下:)

class Linear

{

private:

  double slope;

  double y0;

public:

  Linear(double s1=1,double y1=0):slope(s1),y0(y1){}

  double operator()(double x){return y0+slope*x;}

};

//usage     =>the class object can be used as a function as long as its () operator is overloaded

Linear f1,f2(2,5);

double y1=f1(3);                  //y1=0+1*3;

double y2=f2(12.5);              //y2=5+2*12.5;

函数对象简单来讲就是用函数来当参数,就像一个对象一样,如下使用函数对象的函数声明:

template<class InputIterator, class Function>

Function for_each(InputIterator first, InputIterator last, Function f);

//two declaration of transform() function

template<class InputIterator, class OutputIterator, class UnaryOperation>

OutputIterator transform(InputIterator first, InputIterator last,OutputIterator first, UnaryOperation op);

template
<class InputIterator1, class InputIterator2,class OutputIterator, class BinaryOperation> OutputIterator transform(InputIterator1 first1,InputIterator1 last1,InputIterator2 first2,InputIterator2 last2, BinaryOperation op);

那么STL的functor概念有哪些?STL的functor概念有:无参的函数,一元函数,二元函数;进一步有提炼:如果一元函数返回值为bool值,则为一个预测(predict),如果二元函数返回值为bool,则为一个二元预测(binary predict),所以通用函数不同可能采取的functor也不同。

Functor的概念里还有一些operator的functor equivalents, function adapter,参数绑定等许多概念,因为我也不太懂,不列了。附一张functor equivalents表:

posted @ 2018-08-27 18:18  life_limbo  阅读(404)  评论(0编辑  收藏  举报