C++ Primer中文版(第四版)-读书笔记【PartB】

 

上接PartA

 

p387 初始化列表
构造函数具有名字、形参表和函数体,例如
Sales_item::Sales_item(const string &book):isbn(book),units_sold(0),revenue(0.0){}
其中,省略初始化列表并在构造函数内对数据成员赋值是合法的,例如可以修改为:

Sales_item::Sales_item(const string &book)
{
 isbn=book;
 units_sold=0;
 revenue=0.0;
} 


 

p388 构造函数执行两个阶段
初始化阶段(类类型的数据成员总是在初始化阶段初始化)
普通的计算阶段(计算阶段由构造函数函数体重所有语句组成)

p389
初始化const或引用类型数据成员的唯一机会实在构造函数初始化列表中,如果ci为const,则
ConstRef::ConstRef(int ii):i(ii),ci(i),ri(ii){}
必须对任何const或引用类型成员以及没有默认构造函数的类类型的任何成员使用初始化式

p410 禁止复制
为了防止复制,类必须显示声明其复制构造函数为private,类的友元和成员仍然可以进行复制,如果要完全禁止,则声明复制构造函数而不对其定义

p413 三法则
如果类需要析构函数,则它也需要赋值操作符和复制构造函数,这是一个有用的经验法则,俗称三法则

p431 重载操作符必须具有一个类类型操作数
int operator+(int,int); //wrong,内置的整型加号操作符不能重定义
Sales_item operator+(const Sales_item&, const Sales_item&);//right,重载操作符
不能为任何内置类型定义额外的新的操作符,例如,不能定义接受两个数组类型操作数的operator+

p443 成员访问操作符
箭头操作符必须定义类成员函数,解引用操作符不要求定义为成员,但将它作为成员一般也是正确的。

p447 区别操作符前缀与后缀
后缀式操作符函数接受一个额外的int型形参,使用后缀式操作符时,编译器提供0作为这个形参的实参。那个形参不是后缀式操作符的正常工作所需要的,它的唯一目的是使后缀函数与前缀函数区别开来。

p450 函数对象定义
定义了调用操作符的类,其对象常称为函数对象,即他们是行为类似函数的对象,例如
absInt absObj;
unsigned int ui=absObj(i); //调用absInt::operator(int)
函数调用操作必须声明为成员函数,一个类可以定义函数调用操作符的多个版本,由形参的数目或类型加以区别

class GT_cls
{
 public:
 GT_cls(size_t val=0):bound(val){}
 bool operator()(const string &s){return s.size()>=bound;}
 private:
 std::string::size_type bound;
};



使用时直接调用GT_cls类型的对象,查找单词数多于6个的情况
cout<<count_if(words.begin(),words.end(),GT_cls(6))<<"words 6 characters or longer"<<endl;


p455 转换操作符
转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空,例如

class SmallInt
{
 public:
 ...//构造函数
 operator int() const{return val;} //转换操作符,必须放在类中,放在类外部为错
 private:
 std::size_t val;
};



同时转换操作符通过应定义为const,因为该函数一般不改变被转换的对象

p461 函数重载三步
1、确定候选函数集合:这些是与被调用函数同名的函数
2、选择可行函数:这些是形参数目和类型与函数调用的实参相匹配的候选函数。选择可行函数时,如果有转换操作,编译器还要确定哪个转换操作来匹配每个形参
3、选择最佳匹配函数。对将实参转换为对应形参所需的类型转换进行分类,对于类类型的实参和形参,可能的转换的集合包括类类型转换

p462-465 函数中的二义性问题

避免二义性的最好方法是。保证最低只有一种途径将一个类型转换为另一个类型。最好的方法是限制转换操作符的数目,尤其是到一种内置类型应该只有一个转换。

多个转换

class SmallInt
{
 public:
 operator int() const {return val;}
 operator double() const{return val;}
 private:
 std::size_type val;
};
void compute(int);
void compute(double);
void compute(long double);
SmallInt si;
compute(si);//出现二义性,可以用int,也可以通过转换为double,可修改为compute(static_cast<int>(si));


 

多个转换构造函数

class SmallInt
{
 public:
 SmallInt(int=0);
};
class Integral
{
 public:
 Integral(int =0);
};
void manip(const Integral&);
void manip(const SmallInt&);
manip(10);//二义性,任意一个类都可以与manip的一个版本匹配,修改为manip(SmallInt(10));


 

操作符二义性

class SmallInt
{
 SmallInt(int=0);
 operator int() const{return val;}
 friend SmallInt operator+ (const SmallInt&, const SmallInt&);
 private:
 std::size_t val;
};
SmallInt s1,s2;
SmallInt s3=s1+s2; //使用+操作
int i=s3+0; //二义性,可以将0转换为SmallInt类型,也可以将s3转换为int类型使用int内置加操作符


 

p475 protected
派生类只能通过派生类对象访问其基类的protected成员,派生类对其基类类型对象的protected成员没有特殊的访问权限

p480 多态性
引用和指针的静态类型和动态类型可以不同,这是C++用以支持多态性的基石
通过基类引用或指针调用基类中定义的函数时,并不知道执行函数的对象的确切类型,执行函数的对象可能是基类类型的,也可能是派生类型的
如果调用非虚函数,则无论实际对象什么类型,都执行基类类型所定义的函数,如果调用虚函数,则直到运行时才能确定调用哪个函数

p488 引用转换不同于转换对象
可以将派生类型的对象传给希望接受基类引用的函数,但在讲对象传给函数之前,引用直接绑定到该对象,实际上实参是该对象的引用,对象本身并未复制,该对象仍是派生类型对象。
将派生类对象传给希望接受基类类型对象(不是引用)的函数时,形参的类型是固定的,在编译和运行时形参都是基类类型对象,如果派生类型调用这样的函数,则该派生类对象的基类部分被复制到形参
一个是将派生类对象转换为基类类型引用,一个是用派生类对象对基类对象进行初始化或赋值

p494 复制控制
如果派生类定义了自己的复制构造函数,则该复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分

class Base{/*...*/};
class Derived:public Base
{
 Derived(const Derived& d):Base(d) /*...*/{/*...*/};
};



如果没有Base(d),效果是运行Base的默认构造函数初始化对象的基类部分,导致派生类中基类的部分是默认值,而派生成员是另一个对象副本

p497 构造函数虚函数
如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本

p499 作用域
在派生类作用域中派生类成员将屏蔽基类成员,即使函数原型不同,基类成员也会屏蔽

struct Base
{
 int menfcn();
};
struct Derived: Base
{
 int menfcn(int);
};
Derived d;
Base b;
b.menfcn(); //调用Base
d.menfcn(10); //调用Derived
d.menfcn(); //错误,没有相对应匹配,编译器一旦找到名字相同就不再查找
d.Base::menfcn();//调用Base 



派生类中定义的函数也不重载基类中定义的函数,通过派生类对象调用函数时,实参必须与派生类中定义的版本匹配,只有派生类根本没有定义该函数时,才考虑基类函数。

p501 基类调用虚函数
通过基类类型的引用或指针调用函数

struct Base
{
 public:
 virtual int fcn();
};
class D1:public Base
{
 public:
 int fcn(int);
};
//D1中有两个函数,一个Base中的虚函数,一个自己定义的函数
class D2:public D1
{
 public:
 int fcn(int);
 int fcn();
};
//D2重定义它继承的两个函数
Base bobj;
D1 d1obj;
D2 d2obj;
Base *bp1=&bobj, *bp2=&d1obj,*bp3=&d2obj;
bp1->fcn();//调用Base
bp2->fcn();//调用Base
bp3->fcn();//调用D2


p528 inline函数模板
template <typename T> inline T min(const T&,const T&);

p533 非形参模板类型
在调用函数时非形参将用值代替,值的类型在模板形参表中指定,例如

template <class T,size_t N> void array_init(T(&parm)[N])
{
 for(size_t i=0;i!=N;++i)
 {
  parm[i]=0;
 }
}
int x[42];
double y[10];
array_init(x);//转换为 array_init(int(&)[42]);
array_init(y);//转换为array_init(double(&)[10]);



使用非类型形参指定数组的长度

p538 类类型转换受限
一般而言,不会转换实参以匹配已有的实例化,相反,会产生新的实例。除了产生新的实例以为,编译器只会执行两种转换
1、const转换:接受const引用或const指针的函数可以分别用非const对象的引用或指针来调用,无须产生新的实例化。如果函数接受非引用类型,形参和实参都忽略const,无论传递const或非const对象给接受非引用类型的函数,都使用相同的实例化
2、数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针

p552 类模板中友元
三种友元声明
1、普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数

template <class Type> class Bar
{
 friend class FooBar;
 friend void fcn();
};



2、类模板或函数模板的友元声明,授予对友元所有实例的访问权

template <class Type> class Bar
{
 template <class T> friend class Foo1;
 template <class T> friend void temp1_fcn1(const T&);
};


 


3、只授予对类模板或函数模板的特定实例的访问权的友元声明

template <class T> class Foo2;
template <class T> void temp1_fcn2(const &T);
template <class T> class Bar
{
 friend class Foo2<char*>;
 friend void temp1_fcn2<char*>(char * const &);
};


 


p556 成员模板
在类内部定义

template <class Type> class Queue
{
 public:
 template <class Iter> void assign(Iter,Iter);
};


在类外部定义

template <class T> template <class Iter>
void Queue<T>::assign(Iter beg,Iter end)
{
 //...
}



在类外部定义时,必须包含类模板形参以及自己的模板形参,第一个模板形参表是类模板的,第二个模板形参表是成员模板的


p565 函数模板特化
该定义中一个或多个模板形参的实际类型或实际值是指定的

template <>
int compare<const char*>(const char* const &v1,const char* const &v2)
{
 //...
}



当调用compare版本时,传给它两个字符指针,编辑器将调用特化版本,为其他实参类型调用泛型版本

const char *cp1="word",*cp2="hi";
int i1,i2;
compare(cp1,cp2); //调用特化版本
compare(i1,i2);//调用泛型版本


 

p571 函数匹配与函数模板
确定函数中既有普通函数又有函数模板,确定函数调用步骤如下
1、为这个函数名建立后选函数集合,包括
与被调用的函数名字相同的任意普通函数
任意函数模板实例化,模板实参推断发现了与调用中所用的函数实参相匹配的模板实参
2、确定哪些普通函数是可行的。候选集合中的每个模板实例都是可行的
3、如果需要转换来进行调用,根据转换的种类排列可行函数,调用模板函数实例所允许的转换是有限的
如果只有一个函数可选,就使用它
如果调用有二义性,从可行函数集合中去掉所有函数模板实例
4、重新排列去点函数模板实例的可行函数
如果只有一个函数可选,就使用它
否则调用有二义性

p584 异常匹配
在查找匹配的catch期间,找到的catch不必是与异常最匹配的那个catch,相反,将选中第一个找到的可以处理异常的catch。除了以下几种情况外,异常的类型与catch的说明符必须完全匹配
允许从非const到const的转换,非const对象的throw可以与指定接受const引用的catch匹配
允许从派生类型到基类类型的转换
将数组转换为指向数组类型的指针,将函数转换为指向函数类型的适当指针

p585 重新抛出
catch可以通过重新抛出将异常传递给调用链更上层的函数
throw;
被抛出的异常是原来的异常对象,而不是catch自己的形参。当catch形参是基类类型的时候,抛出的实际类型取决于异常对象的动态类型,而不是catch形参的静态类型。一般而言catch可以改变它的形参,在改变它的形参以后,如果重新抛出异常,只有当异常说明符是引用的时候,才会传播那些改变

catch (my_error &eobj)
{
 eobj.status=severErr;
 throw; //抛出的severErr
}
catch (my_error eobj)
{
 eobj.status=severErr;
 throw; //抛出的没有改变
}


 

p611 实参相关的查找

std::string s;
getline(std::cin ,s);//调用std::getline(std::istream&, const std::string &)



接受类类型形参的函数,以及与类本身定义在同一命名空间中的函数,在用类类型对象作为实参时是可见的
调用getline在当前作用域中,包含调用的作用域以及cin的类型和string类型的命名空间中查找匹配的函数

p623 虚继承
虚继承是一种机制,类通过虚继承指出它希望共享器虚基类的状态,在虚继承下,对给定虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象。共享的基类子对象称为虚基类。

class istream:: public virtual ios{...};
class ostream:: virtual public ios{...};
class iostream:: public istream,public ostream{...};


 

p624 虚基类二义性
假定通过多个派生路径继承名为x的成员
1、如果在每个路径中x表示同一虚基类成员,没有二义性,因为共享该成员的单个实例
2、如果在某个路径中x是虚基类的成员,而在另一路径中x是后代派生类的成员,也没有二义性,特定派生类实例的优先级高于共享虚基类实例
3、如果沿每个继承路径x表示后代派生类的不同成员,则该成员的直接访问是二义性的

 

p625 虚基类初始化
在虚派生中,由最低层的派生类的构造函数初始化虚基类。任何直接或间接继承虚基类的类一般也必须为该基类提供自己的初始化式,只要可以创建虚基类派生类类型的独立对象,该类就必须初始化自己的虚基类。


p647
可以使用dynamic_cast操作符将基类类型对象的引用或指针转换为同一继承层次中其他类型的引用或指针,如果转换到指针类型失败,则结果是0,转换到引用类型失败,抛出异常

p649
typeid问一个表达式是什么类型

typeid(e)//e是任意表达式或类型名
if(typeid(*bp)==typeid(*dp))
{...}


 

p653 类成员指针

class Screen
{
...
 private:
 std::string contents;
};



contents的完全类型是Screen类的成员,其类型是std::string,可以修改为

string Screen::*ps_Screen; //定义指向Screen类的string成员的指针
string Screen::*ps_Screen=&Screen::contents; //将contents的地址初始化ps_Screen


 

p671 extern"C"函数指针
extern "C" void (*pf)(int);
通过pf调用函数时,假定调用的是一个C函数调用而编译该函数
C函数的指针与C++函数的指针具有不同的类型,不能将C函数的指针初始化或赋值为C++函数的指针,反之亦然

void (*pf1)(int); //C++
extern "C" void (*pf2)(int);//C
pf1=pf2;//错误


 

一点感想:陆陆续续花了半个月的时间把这本C++经典入门教材看完了,怎么说呢,感觉阅读之前还是最好要对C++有点基础,因为这本书并没有花太大笔墨来唠叨继承和多态性,而是直接把面向对象编程和泛型编程结合起来讲。我想第四部分应该是本书最重要也是最难的地方,肯定也是C++的精华所在,当前只是看过一遍,有点了解罢了,以后肯定还要再看,通过实践加深印象才行。至于第五部分用处其实不是很多,除了异常外几乎不怎么用。不过了解一下肯定也是好的。既然看完这本了,下一本书应该是《C++程序设计语言》了,看看那本书能提升到什么样的层次,期待吧。

你可能感兴趣的:(C++,String,struct,读书,Class,编译器)