欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

c++ primer plus第五版读书笔记

程序员文章站 2022-10-30 20:30:31
第一章 c++简介: 1.是基于过程的,自上向下的语言 先分析大的解决方向,逐渐细化。 c++是面向对象的oop,自下而上的编程语言 先设计类,然后将他们串联起来 优点是:保护数据,代码重用,可移植...

第一章 c++简介:

1.是基于过程的,自上向下的语言

先分析大的解决方向,逐渐细化。

c++是面向对象的oop,自下而上的编程语言

先设计类,然后将他们串联起来

优点是:保护数据,代码重用,可移植性强

2.c++的oop与通用编程

都是强调代码重用

不过oop强调的是数据方面,而通用编程强调的是算法方面

3.oop提供了高级抽象,c语言则提供了低级硬件访问

4.c++融合了oop 通用编程和传统c的过程性方法。

5.程序可移植方面存在两个障碍:

一个是:硬件障碍,有些程序直接控制固定的硬件设备则不能移植

二个是:语言障碍,不同的语言环境不一样

6.程序创建流程: 编码-》编译-》链接-》执行

源代码->通过编译器->目标代码->通过链接代码(链接启动代码和库代码)->可执行代码

7.在unix系统下,c程序实现代码使用.c后缀名,而c++则使用.c(因为unix系统区分大小写)

第二章 开始学习c++

1. c++ 对大小写敏感

2. c++在输出的末尾加上\n和endl都可以换行

第三章 数据类型

1. oop的本质是设计并拓展自己的数据类型。设计自己的数据类型就是让类型和数据匹配。

2. c++内置数据类型分两种:基本类型和复合类型。基本类型分为整数和浮点数;复合类型包括

数组,字符串,指针和结构体。

3. 变量名命名规则:

1)只能使用字母字符,数字和下划线

2)名称的第一个字符不能为数字

3)名称区分大小写

4)不能使用c++的关键字

5)c++对于名称的长度没有限制(但是c99中只是保证前63个字符有意义)

4. c++的基本整型从小到大为:char、short、int和long 还有他们的又符合和无符号类型,一共8种

5. #include中定义了符号常量(int_max,long_max,shrt_max等)

6. 关键字sizeof 对类型名(如int等)使用时,应将名称放到放到括号中,而对变量名使用时,可以不使用括号

7. short a = shrt_max(32767); a+1 == -32768 (结果为true;因为越界)

unsigned short b = shrt_max(32767); b+1 == 32768 (true,没有越界,因为其最大值为65535)

b = 0; b-1 == 65536 (true;因为最小值为0,越界)

8. 在要使用cout输出8位或者16位的整数时,可以先使用cout << hex;(则可在接下来的整数中显示16进制)

cout << oct; (则可以在接下来的整数在输出中使用8进制显示)

9. cout.put(char);可以显示一个单字符。如:cout.put('a');

10. '\b'是转义字符,表示退格。'\a'表示振铃;

11. c++特殊字符集使用 \u和\u来表示。用它们来实现通用字符名(表示一些特殊的字符)

12. 定义符号常量最好使用const而不是使用#define,因为const能指定类型,并且可以将作用域限定在

某个函数或者文件中,其三const还可以用于更复杂的类型

13. 计算机吧浮点数分成两部分存储,一部分是存储值,另一部分存储缩放比例

14. 浮点数float的表示方法有标准小数点表示法(1.23)和e表示法(2.53e+8)

15. e表示法不区分大小写,指数必须为整数,指数可以是正数、负数或者省略。但是,数字中不能有空格(2.32 e5就是错误的)

16. c++的浮点类型有三种:float double和long double,是按照有效位和允许的指数的最小范围来描述的

17. 浮点常量在默认情况下都为double类型,要使其为float,则需要手动在常量末尾加上f,比如1e7f则表示float类型,1e7则表示double

18. c++算术类型分为两种:一种是整型,一种是浮点型

19. c++会自动进行类型转换的有:

1)将一种算术类型转化为另一种算术类型

2)表达式中包含不同的类型时

3)将参数传给函数时

20. 转换时因为精度降低导致的问题:

1)double转float

2)浮点数转化为整数

3)将较大的整数转化为较小的整数

第四章 复合类型

1. 数组中的元素数目必须是整数常量或者(const值),也可以是常量表达式(8*sizeof(int))

2. c语言中的string类的相关函数操作放在cstring这个头文件中。

3.友元函数

class girl;

class boy

{

public:

void disp(girl &);

};

void boy::disp(girl &x) //函数disp()为类boy的成员函数,也是类girl的友元函数

{

cout<<"girl's name is:"< }

class girl

{

private:

char *name;

int age;

friend boy; //声明类boy是类girl的友元

};

4. 共用体的用途:

当数据项使用两种或两种以上类型的时候,可以节省空间。例如:假设管理一个商品目录,商品的id可能是数字,也可能是字符,

这个时候就可以使用union。

struct thing

{

char name[20];

int type;

union id

{

int id_num;

char id_ch[20];

}

}

4.使用new如果内存分配失败,则会返回空指针,还可能引发bad_alloc异常

5.只能使用delete来释放new分配的内存,而不能用来释放指向变量的指针,对空指针使用delete是安全的

6.使用new构建动态数组的时候,int * psome = new int[10];其中psome是指向数组的第一个元素的地址。

使用new时带上了[]创建数组,则在释放时也应该加上[],delete [] psome;反之亦然。

7.如果使用new []为一个实体分配内存,则应该使用delete(没有[])来释放

8.位数组分配内存的通用格式是:type_name pointer = new type_name[num];

9.指针和数组名的区别是:

1)指针可以加减,而数组名不行,因为指针是变量,数组名是常量,

2)sizeof对数组名来说计算的是整个数组长度

10.数组: arr[3] <==> *(arr + 3);

11.在c++中,char数组名,char*,以及使用引号括起来的字符串常量都被解释为 字符串的第一个字符的地址

12.使用在初始化字符数组时可以使用=为其直接赋值,

但是不是初始化的时候,则必须strcpy或者strncpy

13.c++有三种管理数据内存的方式:自动存储,静态存储和动态存储

1)函数内部定义的常规变量使用自动存储空间

2)静态存储:函数外部定义的变量或者使用关键字:static

3)动态存储:从内存池中使用new动态分配的方式

14.

第五章 关系表达式

1.逗号运算符 - 它的优先级是在所有操作符中最低的,如:

arrno = 17,240;

会被解释为: (arrno=17),240; ==> arrno = 17;

arrno = (17,240); ==》 arrno = 240

***********************************组合字符串的好方法**********************************************************

std_string reg_line;

auto at_detail = m_plat_info->get_register()->get_reg_status();

for ( auto it = at_detail.begin() ; it != at_detail.end() ; it++ )

{

if ( reg_line.size() != 0 )

{

reg_line += "|";

}

reg_line += it->c_str();

}

第七章 函数

1.函数原型不需要提供变量名,有类型列表就够了

2.数据名被解释为其第一个元素的地址,除了数组声明使用数组名来标记存储位置 和 对数组使用sizeof计算的是整个数组的长度

3.当int * arr 和 int arr[]用于函数头和声明中的时候,他们的含义是一致的

4.const int *pt = num;表示不能通过*pt来修改num变量的值,但是可以通过直接修改num变量来改变值

5.const float g_earth = 1.1;

const float * pe = &g_earth;这种方法是可行的,g_earth和指针pe都不能修改值

const float g_earth = 1.1;

float * pe = &g_earth;这种方法是不可行的,因为指针可以修改其值,但是g_earth是的const是禁止的,显得冲突了

结论: c++禁止将const的地址 赋值给非const的指针;如果非要这样做-》可以使用const_cast强制转换

6.尽量使用const,这样可以避免无意间修改数据而导致的编译错误,使用const可以处理const和非const实参

7.回调函数:回调函数是在编写者不知道函数什么时候会被调用的情况下编写的,一般用于动态库函数的接口使用。

8.内联函数-》是为了提高程序运行速度的一个方式,在运行的时候直接替换代码

9.如果使用宏执行了类似函数的功能,则应该考虑将他们转换成内联函数。

10.宏和内联函数的区别:

用内联取代宏:

1.内联函数在运行时可调试,而宏定义不可以;

2.编译器会对内联函数的参数类型做安全检查或自动类型转换(同普通函数),而宏定义则不会;

3.内联函数可以访问类的成员变量,宏定义则不能;

4.在类中声明同时定义的成员函数,自动转化为内联函数。

5.在内联函数中如果有复杂操作将不被内联。如:循环和递归调用。

6.参数的传递方式有:按值传递,按引用传递,按指针传递

7.返回引用时需要注意:

不能返回临时的内存单元如:

const struct_test & foo()

{

struct_test stt;

return stt;

}

为了避免返回一个临时变量,一般我们都返回一个 作为参数传递给函数的引用,因为这也他的作用域还不会消失

还有一种办法就是使用new来为变量分配内存空间,并返回指向该内存空间的指针。只是这样要记得释放

8.不能返回零时变量的引用。

9.什么时候应该使用引用,什么时候应该使用指针,什么时候应该按值传递

=》如果数据对象较小,如内置类型和小型的结构,则按值传递

=》如果对象是数组,则使用指针,这是唯一的选择,并将指针声明为指向const的指针

=》如果数据对象是较大的结构,则使用const指针或者const引用,以提高程序的效率,节省空间开支

=》如果数据对象是类,则使用const引用。类设计的语义常常要求使用引用。

10.对于带参数列表的函数,必须从右向左添加默认值,也就是说如果要为某个参数设置默认值,则要么将这个参数放在

最右边,要么为它有点的所有参数都设置默认值。

11.函数的默认参数

函数的默认参数只能从右向左,依次声明,定义函数体的时候函数列表中不需要添加默认参数

12.函数重载

参数列表不同(参数顺序,参数个数,参数类型不同)的同名函数可以构成函数重载

1)一般只是用于做相近的函数功能的时候使用。

2)当参数列表只是个数不同的时候,一般情况下可以使用默认参数,而不需要使用重载

3)const和非const的参数可以构成函数重载

int foo(const char* p); 与 int foo (char* p)可以构成

int foo(int)const; 与 int foo(int)也可以构成

第九章 内存模型和名称空间

1.一般情况下,不要将函数的定义或变量声明放在头文件中。因为如果这个头文件在被其他两个文件中包含,

那么这个函数就会被定义两次。除非函数是内联的,否则就会出错(类的成员函数默认是内联的)。

2.register int a;这种声明方式为 寄存器变量,只要使用了register,就不能获取a的地址,因为寄存器中是没有地址的,

这个时候不论编译器到底有没有使用寄存器来存储这个变量。 auto int a 《==》 int a;前后等价,都存储于堆栈中。

使用register的好处是,他存储的变量如果是被频繁使用,则可以大大提高访问速度。

3.如果在文件2中试图定义和文件1中一样的全局变量,那么程序将会出错。

正确的方法是:使用extern关键

如果是定义的static静态全局变量和其他文件中的全局变量想通,那么静态变量将隐藏常规的外部变量

static定义的全局变量,作用域只在于他本文件之中,不能被引用到其他文件中。

4.mutable关键字是为了突破const的限制而设置的,变量将用于处于可变的状态,即使是在一个const函数中

struct data

{

char ch[20];

mutable int a;

}

const data veep={"love", 1};

strcpy(veep.ch, "like");//error

veep.a = 10; //ok

5.布局new操作符和常规new操作符???

6.using声明使一个名称可用,而using编译指令,也就是using namespace使整个命名空间的内容都可用

第十章 对象和类

1.当且仅当没有定义任何的构造函数时,编译器才会提供默认构造函数。而只要我们定义了构造函数后,

我们就必须为它提供默认构造函数,如果我们只是提供了非默认构造函数(如:stock(char* p)),

但没有提供默认构造函数,则下面声明:stock stock1;将出错。

2.定义默认构造函数的方法有两种:一是给已有的构造函数的所有参数都提供默认值(如:stock(char* p=“hello”)),

另一种则是通过重载来定义另外一个构造函数——没有参数的构造函数(stock();)

由于只能有一个默认构造函数,所以不要同时采用这两种方法。

3.隐式的调用构造函数时,不要使用括号,不要就成了一个返回对象的函数(stock stock1();)

4.析构函数没有参数,所以不能实现重载

5.const stock stock1("hello");

stock1.fun(); 如果:fun()函数不是声明为const参数,则程序不能通过,因为编译器无法确定fun()里面的代码不会修改

stock1的值,在这儿因为fun函数没有参数,所以只能这样声明(void stock::fun()const{ ... }const成员函数 )

6.只要类的定义不修改调用对象,则我们应将其声明为const

7.如果一个函数的参数是const,如果函数要返回这个参数,则返回值也必须是const

8.要创建对象数组,则这个类必须有默认构造函数,因为对象数组的初始化是先使用默认构造函数来创建数组元素

9.c++类中定义常量的方式有两种:一是使用枚举,这样相当于宏;二是使用static关键字,在声明的地方直接可以初始化

如:static const int a=30;

10.bool在大多数平台只占用一个字节,而bool占用4个字节

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

第十一章 使用类

1.操作符重载的规则:

1)重载后的操作符至少有一个操作数是用户定义的类型,这是为了防止用户为标准类型重载操作符,例如不能将-重载为两个double的+

2)操作符不能违反原来的句法原则,例如不能将%重载为只是使用一个操作数(原本为两个)

3)不能修改操作符的优先级,操作符重载后将保持原来的优先级

4)不能定义新的操作符,例如不能使用operator @()来表示求幂;

5)sizeof . .* :: ?: typeid 以及4个强制转化符 不能被重载

2.如果一个函数为类的友元函数,那么就赋予了该函数和类一样的访问权限。

3.为什么需要非成员重载操作符函数?

因为:如果是类的成员操作符函数,那么在对于重载二元操作符函数来说:第一个参数已经默认为this指针了,第二个参数才是我们自己定义的另外一个参数;

这时候当我们使用重载操作符的时候只能是 this->operator+(type);

例如: time operator * (double m); 我们在使用的时候,只能使用 time tm = time*2.5;而不能使用成time tm = 2.5*time;

这时候我们使用非成员重载操作符则可以自己定义参数列表顺序,time operator *(double m, const time& t);这样就可以使用上述方法调用了;

=》但是这儿出现了另外一个问题,就是非成员函数不能访问类的私有变量,这样就不能实现操作符重载;这个时候就需要把函数声明为友元函数就解决了;

=》最好的例子: friend void operator<< (ostream & os, const time& t);第一个参数必须是cout;

4.类的友元函数需要在类中声明前加上关键字friend;友元函数不是类的成员函数,所以不能用成员操作符来调用。(定义中不需要friend关键字了)

5.只有类声明中可以决定哪一个函数为友元函数,因此类声明依然控制了哪些函数可以访问私有数据,这样不会破坏oop原则。

6.ostream & operator << (ostream& os, const time& t)

{

os << t.hours << " " << t.minutes;

return os;

}

返回ostream的引用是为了,能在函数外部能正常使用cout ; cout << time << " ";

7.c operator+(const b & b)const; 和 friend c operator+(const c& c, const b& b);不能同时存在,参数列表相同,调用时会产生二义性

应当c operator+(const b & b)const;与 friend c operator+(const b& b, const c& c);这样参数顺序不一致即可

8."-" 操作符 在c++中可以表示一元的负数,或者二元的减号;这时候重载"-"的时候,可以重载为一元或二元都可以

9.当构造函数只接受一个参数的时候,在初始化类对象的时候可以直接用等号进行初始化: time time = 8; time::time(int day);

这种等价于 time time(8); 或者 time time = time(8);

10.可以为类定义类型转化函数:

time::operator int()const

{

return hour;

}

注意这里不能有返回值,不能有参数。因为已经确定了返回类型为int,而参数为类对象本身。

这个时候就可以使用 int hour = int(time(342));这样的类型转换了

11.如果一个类里面定义两种类型转化函数 (int 和 double),这个时候我们如果要进行long的类型转化,这时候会出现二义性

12.应当谨慎的使用隐式转换,因为这样往往对导致很多麻烦。

13.可以声明一个全局的对象,这样可以在main函数之前,通过全局对象的构造函数来做一些事情。常见于mfc的theapp对象技术。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

第十二章 类的动态内存分配

1.使用动态内存分配最主要的目的是:有时候不知道分配多少内存合适,如果使用数组预估一个值分配,则有可能会过大;但是过小又不能存储

这个时候就可以在构造函数中,先对传递进来的参数进行内存占用计算,然后在分配相应大小的内存,这样就不会浪费空间了。

(new将在运行时确定分配空间的大小,而数组是在编译时确定分配内存的大小。)

2.类的成员不能在声明的地方进行初始化:因为声明的时候只是指定了成员变量的格式和使用方法,但是没有为其分配内存空间。

3.静态类成员如果加上了const,则可以在声明中进行初始化。

4.新建一个对象,并将其初始化为同类现有对象的时候,将调用拷贝构造函数。

每当程序生产了对象副本时,编译器都将使用复制构造函数。

如:

time tm(0);//

time t = time(tm);

time t(tm);

time t = tm;

time * t = new time(tm);

5.成员复制也称为浅复制,默认的拷贝构造只是复制成员的值,而不会拷贝内存.

6.在一个类的成员函数里面,可以访问该类另一个对象的私有成员. 例如time(const time & tm);

在这个拷贝构造函数中,可以直接使用tm的私有成员hour。

7.复制操作符 time & operator=(const time& tm); 这儿返回time&是为了连续赋值(tm2=tm1=tm;)

相当于:tm2.operator=(tm1.operator=(tm));

8.赋值操作符只能使用成员函数重载

9.如果一个类有多个构造函数,其中每个构造函数new出内存空间的方式都应该一致,

要么都是用new 要么都是用new[],因为只有一个析构函数,只能有一种方式释放

10.类的转换函数:

1)将其他类型转换为类,可以使用类的构造函数;time(type_name _value);的方式

2)将类转换为其他类型,则需要使用类成员函数:operator type_name();来实现,

其中type_name则是要转换成的类型,函数没有声明返回类型,但是必须返回对应类型的值。

11.const int num; num是const类型的成员变量,必须在执行到构造函数之前(即创建对象时)对其进行初始化,

这时候就需要使用初始化列表。当然初始化列表不只是可以初始化const修饰的成员变量,适用于所有的变量初始化。

注:声明为引用的初始化成员变量,也必须使用初始化列表对其进行初始化。

注:如果是类对象的成员变量,使用初始化列表的效率更高。

注:数据成员被初始化的顺序与初始化列表中的排列顺序无关,和他们声明的顺序想同。

12.类成员初始化的方法也可以用于普通常量的初始化中,如:int a=1; <=> int a(1);

13.如果想你的对象不能被复制或者调用默认构造函数可以这样:

time (int hour){}

time & operator=(const time tm){ return *this;}

把这两个函数定位为private:这样就不能通过复制和初始化的方法使用这个对象了。

14. 12.2队列例子理解清楚????? 440页

第十三章 类继承

1.如果父类没有默认的构造函数,则子类在继承父类后,子类的构造函数必须完成对父类的构造。

如: a(char *p){}; class b :public a{}; 则b(char* p): a(p){};

否则将调用父类的默认构造函数,如果父类没有定义默认构造函数,则会出错。

2.派生类构造函数注意要点:

1)基类对象首先被创建

2)派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数

3)派生类构造函数应该初始化派生类新添加的数据成员。

3.派生类要添加的东西:

1)新的构造函数

2)额外的数据成员和成员函数

4.如果基类的构造函数有参数,则派生类的构造函数必须先初始化基类的构造函数,否则会调用基类的默认构造函数。

5.除虚拟基类外,类的初始化列表只能将值传递给相邻的基类,后者也可以使用相同的机制将信息再传递给他的基类

如果没有在成员初始化列表提供基类的构造函数,则程序使用默认的基类无参构造函数。

成员初始化列表只能出现在构造函数中,主要用于初始化基类和const成员。

6.继承只能是is-a,而不能是is-like-a,is-kind-a,uses-a的关系

7.基类声明为虚析构函数,是为了确保对象在释放的时候,先释放派生类,再释放基类。如果不是虚拟析构,则只会调用基类析构

8.如果要在派生类中重新定义基类的方法,通常应该将基类方法声明为虚拟的,这样程序能根据对象类型来选择方法版本,

同时这时候为基类声明为虚拟析构函数也是一种惯例。

9.静态联编:在编译过程中联编的(宏,内联),编译器对非虚拟方法使用静态联编。(效率高)

动态联编:在运行时中联编的(多态)()

10.将派生类引用或指针转换为基类引用或指被称为向上强制转换,这使得公有继承不需要进行显示类型转换。

向上强制转换是可传递的,比如a派生出b,b派生出c,则a的指针和引用可以引用a、b、c对象。

11.虚函数工作原理:

编译器为每个有虚函数的对象添加一个隐藏成员(一个保存了指向函数地址数组的指针,这个数组称为虚函数表),

如果派生类提供了某个虚函数的新定义,则虚函数表将保存新函数的地址,在使用时则根据指针去寻找对应的函数地址

12.虚函数疑点:

(1)内联函数不能声明为虚函数

所谓内联就是告诉编译器将代码嵌入到调用者的代码中,以增加执行码长度来换取执行的速度,而虚函数是执行器动态转换的,则编译器不知道要替换哪部分代码进去,故不能内联

(2)静态成员函数不能声明为虚函数

而类的普通成员函数(包括虚函数)在编译时加入this指针,通过这种方式可以与对象捆绑,而静态函数编译时不加this,因为静态函数是给所有类对象公用的,所以没有在编译时加this,所以无法与对象捆绑,而虚函数就是靠着与对象捆绑加上虚函数列表才实现了动态捆绑。所以没有this指针虚函数无从谈起。

(3)构造函数不能声明为虚函数的原因是:

构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象 的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类。无法确定。。。

虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初始化,将无法进行。

(4)析构函数设为虚函数的作用:

在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。

(5)友元函数不能为虚函数:

友元不是类成员,只有类成员才能是虚函数。

13.如果在基类中定义了虚函数 virtual foo(){};而派生类中定义了 virtual foo(int a){};这个时候派生类不会出现两个函数重载的版本,而是隐藏了基类版本,只是保留派生类中的版本。(重新定义基类的方法并不是重载,而是隐藏同名的基类方法)

(所以如果重新定义了基类方法,则应确保和原来的参数列表一致,包括返回值)

14.如果基类重载了某个函数的多种方法,则应该在派生类中实现所有的重载版本。因为如果派生类如果只定义了一个版本,

则会把基类重载的其他版本全部隐藏而导致不能使用。(如果其他重载版本不需要修改,可以在实现中调用基类版本即可)

15.对于类外部来说,保护成员和私有成员相似,而对于派生类来说,保护成员和共有成员相似。

16.单例模式:

class theone

{

public:

static theone* getoneinstance();

protected:

theone(){ }

};

theone* theone::getoneinstance()

{

static theone onlyone;

return &onlyone;

}

解析:getoneinstance()方法仅在第一次被调用时,会创建theone类的一个实例,用这种方式构造的静态对象一直有效,知道程序终止。

因为静态变量在函数调用结束后仍保存在内存中,所有后面再调用getoneinstance()时,将返回同一个静态对象的地址。

17.对于派生类中有使用new申请变量的构造函数时,必须为基类也要调用派生类的引用。hasclass(const hasclass& cls):baseclass(cls){ ... }

18.对于使用了new的派生类,赋值操作符使用方法:hasclass& operator=(const hasclass & cls){... baseclass::operator=(cls); ...}

19.当基类和派生类均使用了动态内存分配的时候,派生类的析构函数,赋值操作符和拷贝构造函数都需要使用相应的基类方法来处理基类元素。如上17和18条。

20.函数返回对象和返回对象的引用,最主要的目的是返回引用可以方便减少内存的操作,不能返回临时变量的引用。

21.void mycls::show()const{} 这儿的const可以确保方法不修改调用它的对象。

22.基类的构造函数和析构函数都不能被继承,赋值操作符也不能被继承=》因为基类和派生类的赋值的时候参数不一样(特征标不一样)

23.派生类的友元函数可以通过强制类型转换dynamic_cast,将派生类的引用或指针转化成基类的引用或指针,

然后就可以通过这种方式访问基类的友元函数等了。

第十四章:c++中的代码重用

1.如果一个成员类没有在构造函数的初始化列表中构造,那么c++将调用这个类的默认构造函数进行构造;而这样就不能传递参数了。

2.如果一个类的初始化列表包含多个项目时,这些项目的初始化顺序是按照他们的声明顺序来初始化的,而不是初始化列表中的顺序。

3.如果私有继承的时候想使用基类的方法,则可以使用作用域限定符::后面跟方法名来使用相应的方法。(500页)

如果是共有继承则可以使用对象调用相应的方法《=》私有继承使用父类名加上作用域限定符来调用相应的方法。

如果要使用基类对象本身,则使用强制类型转换。(const string& ) *this;

4.私有继承的好处:私有继承下 基类的保护成员 在派生类中可以直接使用,而如果是把类作为类的成员变量,则保护成员不可访问

通常情况下,我们使用包含来建立has-a关系,但是如果新类需要访问原有类的保护成员,则应该使用私有继承

5.要想在使用私有继承的派生类中使用基类的私有方法,可以使用using;比如using string::size;

这样就可以把size()函数作为共有方法一样使用啦

6.虚继承:当b、c继承自a,而d同时继承自b、c时,这时候会出现菱形继承关系,d同时会拥有两份a基类数据的拷贝,访问时将会出错;

为避免这种情况发生,则在b、c继承a的时候 使用virtual关键字来实现虚继承。class b :virtual public a {};

1)在使用虚继承进行构造d类的时候,会在d的初始化列表中同时调用a、b、c的构造函数,如:

d(int d, int b, int c, int a):b(b,a),c(c,a),a(a){ m_d = d};

注意:非虚继承的情况下,d不能直接调用a的构造函数,而虚继承的情况下,a如果不希望调用默认构造函数,则必须这样做。

2)如果d同时继承b、c都存在的foo()函数时,d在使用foo()函数的时候会存在二义性,这时候需要使用作用域限定符,d.b::foo();

7.在基类和派生类中定义了相同的函数名时,派生类的定义优先于基类的定义。

8.虚拟二义性规则与访问规则无关,也就是说,c继承自a,b,而a、b都有foo()函数,不管foo在ab中是私有还是共有,在c中调用foo的时候都会出现二义性。

要避免出现二义性,则需要使用基类的类限定符,a::foo()、b::foo();同名成员变量亦如此。

9.类模板

类模板的特性:可以为类型参数提供默认值: template 这样如果在使用时省略t2,则使用默认类型

注意:不能为函数模板提供默认值。

10.疑问:类模板和函数模板的具体化?!!

第十五章 友元、异常和其他

1.友元类的所有方法都可以访问原始类的私有成员和保护成员。

2.友元类的声明friend class b;可以放在在共有、私有和保护部分

3.对类的嵌套通常是为了实现另外一个类,并且避免名称冲突。

4.异常类型通常定义为一个类:

class bad_error

{

public:

bad_error(char* p):str(p){}

void err_info(){cout << "err=" << str << endl;}

private:

string str;

};

在使用的时候:

void foo(int a) throw(bad_error) //如果throw后面的括号为空就表示该函数不会抛出异常

{

if(a==0)

throw bad_error("参数为0");

else

return a++;

}

6.异常捕获中,catch块的排列顺序应该与派生顺序相反,不然它将可能捕获不了所有的异常。

当你不知道异常类型的时候,可以使用省略号来表示异常类型,这样可以捕获任何异常。

当你知道这部分程序可能会出现一部分异常的时候,可以先在catch后面列出这些异常,然后在最后使用"..."来捕获其他所有。

在catch中使用基类对象时,将捕获所有子对象。

7.c++中定义了 exception异常类,我们可以把我们的异常类自他继承下来,

8.如果程序抛出了异常,但是我们没有捕获,那么程序将会调用myquit()退出程序。

9.rtti 运行阶段类型识别:

=>c++有3个支持rtti的元素

1)dynamic_case操作符:它可以安全的将对象的地址贵指定类的指针。可以将派生类转化为基类。转化成功,返回对象地址,转化失败返回空指针

2)typeid操作符返回一个type_info对象,可以对两个typeid的返回值进行比较。比如 typeid(string) == typeid(int);typeid操作符返回一个对type_info对象的引用。

3)type_info结构存储了有关特定类型的信息。

10.4种类型转化操作符

=》dynamic_cast(动态转换),可以将类进行类型转换,只有当转换为自己的基类类型时,才会成功。用途:确保安全的调用虚函数。

=》const_cast (const转换),用于改变值为 const和volatile的类型,类型的其他地方必须一致,如果不一致将会出错,一般用于取消const关键字

high bar; const high *pbar = &bar; high *pb = const_cast(pbar);正确(用于大多数时候是常量,但有时候是可以修改的值)

=》static_cast(静态转换),可以将两个有关系的类互相转化,比如基类转换为派生类,或者派生类转换为基类。

只要一个类型可以从一个方向转换到另外一个类型,比如枚举可以转换为整数,那么使用这种类型转换则可以从整数转化到枚举。

=》reinterpret_cast,用于天生危险的类型转换

第十六章: string类和标准模板库

1.auto_ptr类 ---》用于管理动态内存分配的用法

2.只能对new分配出来的内存使用auto_ptr,而不要new[]分配的或通过自动变量声明的内存来使用它

3.基类成员一般都为抽象类,具体类一般处于继承关系的最后一级。