一直以为自己的C++水平还可以,但是感觉只停留在理论阶段,实际遇到问题时还不知所措,进而借此机会在好好学习一下。学习理论同时以实践为主,结合自己工作经验将容易出错的地方记录下来,供以后备查。学习计划如下:
a) C++语言设计和演化
b) Accelerated C++
c) C++语言程序设计
d) C++ Primer
e) 深度探索C++对象模型
f) Effective C++中文版
g) More Effective C++中文版
h) C++沉思录
i) C++标准库源码分析
其中前5本比较偏理论一些,后边主要偏实践。由于之前一定C++基础,再次学习关注点主要包括基础理论、应用价值、应用场景并且积累一定量C++代码。本文是针对《C++语言设计和演化》的读书笔记以及自己的感悟。
1. C++演化
学习语言就像是做研究一样,知道问题产生的背景、有什么解决方案、文献如何解决、结论是什么以及后续有什么展望。
C++就是在C语言基础上,参照Simulator进行设计,从带类的C成长为现在标准版C++。
另外学习C++也要像做研究似的,先了解C++的设计和演化,再去运用去解决问题,最后发现问题去改进。一个成功产品的诞生源于一个人的知识面,以及举一反三的能力:
学习历史、哲学、文学都会对研究产生影响,就能决定某个人的倾向;例如追求完美的乔布斯不会因为家里空旷去买不完美的家具;实用主义者不会设计完全完美理想的系统。
这是一本讲述如果从C演化成C++的以及为什么会引进一些机制
C++的演化历程
- l c
- l 带类的C
- l C++
继承关系对象布局模型
- l 派生类的实现是简单的将基类的成员和派生类的成员并列的放在一起。
虚函数关系对象布局模型
- l 实现思想:每个带有虚函数的类都有一个虚函数数组,同时有一个虚指针指向该数组
引用
- l 一个引用变量被初始化之后,就不可能再去引用另外一个变量
一个语言的形成经过不同人的评论和修改,并且不断的进行淬炼,去掉无用的、无意义的冗余的特性,保留干练精华的特性。
学习一门语言包括C++,最根本的是学习编程和设计技术而不是语言的细节。
2. C++特性
第一部分主要介绍了C++的发展史,C-带类的C-C++;C++语言特性上的取舍;C++的发展等。
第二部分介绍引入到C++中的一些特性。
a) 存储控制
- l 有效利用系统资源,能够申请和释放内存
- l new 操作符,可以重载,用于申请一定大小的空间
- l 放置语法:更加有效的进行存储控制,防止不断申请和释放内存带来的开销。new(&addr)X
- l delete操作符用于释放空间,不能重载,其输入就是一个void 指针;后期为释放数组开发了delete[]
- l 垃圾回收机制:好处方便;不足:占用系统空间和时间,导致服务不稳定等
- Ø 实现机制:引用计数等
- Ø 方案:有一个启动开关,用户不启用的时候和正常C++程序一样;
b) 重载
- l 能够有效减少函数个数
- l 机制:利用参数个数或者隐式类型转换来实现
- l C++中对隐式类型转换,提供了一定的优先级别,从高到低分别如下:
- Ø 不转换或者不可避免的转换(数组名到指针、函数名到函数指针、T到const T)
- Ø 使用整数提升的转换:char short int或者float到double
- Ø 使用标准转换:int到double、子类到基类、unsigned到int
- Ø 使用了用户自定义类型
- Ø 命中参数中的省略号
- l 空指针:在C++中用NULL匹配,应该算是一个宏定义
- l 对象的复制控制:主要是控制复制构造函数和赋值操作符
- Ø 控制对象的复制:将复制构造函数和复制操作符定义为私有,但是类内部还可以继续使用(定义但是不实现可以解决)
- Ø 对分配的控制:通过将析构函数定义为私有,则限制对象只能通过new出来,达到不能在栈或者全局范围内定义变量。
- Ø 对分配的控制:重载new函数,实现不能new对象
- Ø 对派生类的控制:原则是将基类的构造函数设定为私有,即派生类不能访问到即可。
- Ø 成员的复制:默认采用浅复制可以通过重载赋值运算符来递归实现深复制
- l 前置++和后置++的重载
- Ø X& operator ++()--前置的重载
- Ø x operator ++(int)--后置的重载
- l bool类型
- Ø 非零值均可以转换为true,0值转换为false
- Ø true转换为1,false转换为0
c) 多重继承
- l 某个类能够继承多个基类,问题时基类可能继承自相同类,例如类B、C分别继承自A,而D继承BC导致D中含有A的对象两次。
- l 解决方案将A声明为虚基类,在实际应用的时候支配关系起到很大的作用
- Ø 如果一个名字支配另外一个,选择的时候选择支配者。例如A中有函数F(),而B中重写了F(),则D中默认会使用B中的F()
- Ø 成员变量也是如此
- l 内存对象布局
- Ø 需要有多个虚表,通过不同基类声明时,指向的虚表位置不一样
- l 可以通过基类名加上::来访问基类函数
- l 多重继承初始化的问题
- l 没有介绍最终采用哪种方式实现
d) 类
- l 抽象类:包含纯虚函数的类,不能定义对象
- l const成员函数的this指针为const不能修改值,mutale修饰的变量可以再const成员函数中改变。
- l 静态成员属于类的属性,可以通过类名访问,可以减少全局变量的污染。函数中的静态成员有类似作用。
- l 到成员的指针
e) 强制
- l RTTI:运行时对象类型转换,主要有三种方式
- l dynamic_cast<T*>B,将基类指针B转换成T,如果转换不成功则返回NULL,也可以适用于引用类型,转换不成功返回bad_cast异常。
- l typeid()运算符可以返回const_cast<T>(e)某类型的标识,返回结果为type_info的引用,可以用于比较类型是否一致等。
- l static_cast<T>(e)将e转换为T,可以理解为隐式类型转换的逆运算。即只要S->T可以隐式转换,则就可以使用它进行T->S的转换。
- Ø 主要是静态类型之间的转换
- l reinterpret_cast<T>(e)主要是对指针的一个重新解释,是不安全的。
- l const_cast<T>(e)在const或者volatile之间进行转换
f) 模版
- l 模版为生成类型提供了一种机制,它本身不是类型,没有运行时的表示,对对象布局没有影响。
- l 类模版
- l 函数模版
- l 模版与专门化
g) 名字空间
- l 解决命名冲突问题
- l 会改变静态变量的作用域
- l 全局作用域中变量的调用:通过“::x”,即::代表全局命名空间
- l 名字空间可以嵌套
h) C预处理器
- l 比较常用的是:
- Ø #define
- Ø #ifndef
- Ø #endif
- 防止多重包含
3. 问题记录
1、一个C++对象可以存在于栈空间、堆上或者静态区域,对于一个应用而言堆、栈和静态空间是多大呢?
在linux系统下new出来的内存使用的是堆空间,其他变量的定义均是存放在栈空间上。系统中有专门参数控制栈空间大小,一般为8M,可以自己改变。
如果设置不当回导致栈溢出(什么情况下会出现呢?留给读者)
2、类的成员函数式inline的吗?可以指定为inline吗?
· 类成员函数默认在类内定义是inline,类外是outline,虚函数例外
· 如果类的成员函数是虚函数,inline关键词不起作用
3、友元函数是怎么回事?
· 友元函数和友元类可以通过friend class A.friend +函数声明,可以用于访问类内的私有成员和保护成员
· 友元函数:整个函数内声明和定义的类变量均可以访问
· 友元性质是需要指定的,没有对称性和传递性
4、每个带有虚函数的类都有一个虚函数表,如何确定该虚函数表的大小呢?
· http://blog.youkuaiyun.com/haoel/article/details/3081328
· 一般虚函数表会放在变量的第一个位置,并且每一个虚函数表都会有一个结束标识符,进行数据的遍历获取虚函数表的大小
· 上面只是对单一继承的情况
· 对于多重继承,每个实例会有多个虚函数表,需要进一步了解对象的内存布局。
5、操作符重载时为什么一般将其定义为友元函数呢?
6、C++的放置语法是怎么回事呢?应用场景是什么呢?
· 一般来说new和delete的耗时比较大,我们可以事先声明一段内存,用于存储对象
· 可以通过c++中的放置语法来分配一块预先定义好的内存
7、函数参数里面省略号(...)使用场景以及方法?
· 首先需要引用 <stdarg.h>头文件,然后利用va_list类型和va_start、va_arg、va_end 3个宏读取传递到函数中的参数值。
· type va_arg( va_list arg_ptr, type );void va_end( va_list arg_ptr );void va_start( va_list arg_ptr, prev_param );
· 【 va_start函数将参数arg_ptr设置为可变参数列表的第一个参数。参数arg_ptr的类型必须为va_list。参数prev_param是在可变参数列表之前的那一个参数。(也就是说在 ANSI C 中,如果一个函数有可变参数,那么在该可变参数前必须有一个明确定义的参数,否则无法调用函数 va_start,例如函数 int add(int i,...)是合法的,而函数 int add(...)是不合法的。)】
· 【 va_arg函数将返回 arg_ptr所指位置的值,并将 arg_ptr指向下一个参数】
·
·
8、虚函数的问题,在构造函数中调用虚函数和其他函数中调用虚函数的区别。
· 构造函数可以调用虚函数,但是只会调用本身的函数定义,不会产生多态
· 原因是:派生类的虚指针还没有被初始化;具体解析
o 构造派生类时,要先初始化基类,此时基类的调用只会找到本身的函数定义
o 然后才会初始化派生类的虚指针
o 产生多态。
9、虚函数表
· 虚函数表是类的属性
· 虚函数表在编译的时候生成,和代码一样存放在只读区域
· 虚函数指针在对象初始化的时候赋值
· 虚函数指针一般位于类的前4个字节
4. 总结
本书主要是介绍c++语言演化过程中,各类特性的产生背景以及简单的实现策略。一旦一个语言产生,后续的维护和优化都要向前兼容保证其可用。在实际做系统时,后期的开发均要保证前期功能的可用,不可能因为要添加一个功能就要去改动之前可用的功能,从另外一个侧面也体现了系统设计的重要性。