C++——设计与演化——读书笔记

<<c++设计与演化>>
1.c++的保护模式来自于访问权限许可和转让的概念;
初始化和赋值的区分来自于转让能力的思考;
c++的const概念是从读写保护机制中演化出来.

2.BCPL注释:
CPL(Combined Programming language,组合编程语言):CPL是一种在ALGOL60基础上更接近硬件的一种语言。CPL规模大,实现困难。
BCPL(Basic Combined Programming language,基本的组合编程语言):BCPL是对CPL进行简化后的一种语言。

3.一般地说在世界上,事情并不总是处在很好的状态,要改进它们,有许多事情是可以做的。
c++是为了解决一个问题,而不是想证明一种观点,而它的成长又能够服务于它的使用者.

4.一个类就是一个类型.

5.对象的分配可以有三种方式:
在栈上(作为自动对象),在固定地址(静态对象),或者在自由存储区(在堆,或者说动态存储区)。
与c语言不同的是,带类的c自由存储的分配和释放提供了特定的运算符new和delete.

6.C的意图是采用按名字物价而不是按结构等价.
struct A { int x; int y; };
struct B { int x; int y; };

struct A* pa;
struct B* pb;
pa = pb; /* error : A* expected */
pa = (struct A*) pb; /* OK:explicit conversion */

所以,按名字等价是c++类型系统的基石, 而布局相容性规则保证了可以使用显示转换,以便能提供低级的转换服务.
这样导出了"惟一定义规则":在c++语言中,每个函数,变量,类型,常量等都应该恰好有一个定义.

6.设法保证忽略C++的警告将被看成是一种愚蠢行为.

7.语言设计并不是第一个原理出发的设计,而是一种需要经验,试验和有效工程折衷的艺术.

8.C++对于C多名字空间的解决办法是:
一个名字可以指称一个类,同时也可以指称一个函数,或者一个变量。如果某个名字真的同时指称两种不同的东西,那么这个名字本身的指称的就是那个非类的东西, 除非在前面明显地加上关键字struct, union或者 class.

9.虚函数在时间和空间方面都与常规函数同样有效,即使没有虚函数概念,在带类的C里的派生类也很有用,可以用于从老的构造出新的数据结构,用于将操作与结果关联起来.

10.对象布局模型
把一个类里定义的一集虚函数定义为一个指向函数的指针数组,这样,对虚函数的简单地也就是通过该数组一个间接调用。对每个有虚函数的类都存在一个这样的数组,一般称为虚函数表或者vtbl,这此类的每个对象都包含一个隐式指针,一般称为vprt,指向该对象的类的虚函数表.
class A {
public:
virtual void f();
virtual void g (int);
virtual void h (double);
private:
int a;
};

class B : public A{
public:
int b;
void g(int);
virtual void m(B*);
};

class C : public B{
public:
int c;
void h(double);
virtual void n(C*);
}

类C的一个对象看起来:
+--------------------------+
| a | +----------------------+
| vptr ------>| --> | &A::f |
| b | | &B::g |
| c | | &C::h |
+--------------------------+ | &B::m |
| &C::n |
+----------------------+

对虚函数的调用被编译系统翻译成一个引用.
void f(C* p)
{
p ->g(2);
}
变成:
(*(p ->vptr[1])) (p, 2);

11.用省略号制止类型检查是其中最激烈的也是最不被推荐的东西.

12.变量的作用域:
for (int i = 0; i < 10; ++i)
{
}
i = 0;

在c++语言规范里,i的作用域不应该超过for里{}之外,但在vs2003里可以.
if ,while也一样.

13.面向对象的程序设计是利用继承机制的程序设计;数据抽象是使用用户定义类型的程序设计;面向对象的程序设计将能够而且应支持数据抽象.

14.类型重新定义规则:
typedef char* T;
class Y {
T f() { T a = 0; return a; }
typedef int T;
};

c++语言规则: 如果一个类型已在某个类中使用过,就不能在那里重新定义.
<上面T,应该是重定义,但在vs2003里错误提示: 无法从Y::T转为T,因为T f()返回的是最上面的T,而函数体内的是类里定义的T,所以,此处错误有分歧,到底是不能返回不同的类型还是后面那个T是重定义?>
<此处应按c++标准,不能进行类型的重定义>

15.重写规则:对于以在线(online)方式定义的成员函数进行分析时,就像它们是在类声明之后定义那样去做.
如:
class TT
{
A f();
void g() { A a; }
typedef int A;
};
A f(); 是错误的,因为A还没有声明,而void g(){ A a; }是正确的,因为符合上面写的规则.

16.基于编译器出错:
class T{}; 如果用T做为类名,在vs2003里出现内部编译器出错信息.

17.c++定义规则(1993):
(1).在一个类里一个被声明的名字的作用域不仅包括这个名字声明之后的正文,还包括所有的函数体,默认参数,以及在这个类里的构造函数初始化(包含嵌套的类和这些部份),但不包括这个名字本身的声明式.
(2).在类S里使用过的一个名字,在它的上下文中或在S的完整作用域中重新求值的时候引用的都必须是同一个声明,S的完整作用域由类S本身,S的基类以及所有包含着S的类组成。这通常称为"重新考虑规则".
(3).如果在一个类里重新排列了成员的声明,产生的是另一个符合1和2的合法程序,那么该程序是无定义,这通常被称为"重新排序规则".

18.临时变量
void f (string s1, string s2)
{
printf ("%s", (s1+ s2).c_str());
const char* p = (s1 + s2).c_str();
printf ("%s", p);
}

const char* p = (s1 + s2).c_str();此句具体的操作:
(1).生成一个string对象
(2).调用拷贝构造
(3).调用重载+=
(4).析构函数

所以p 指针在此语名结束后,根本不能获取临时字符串对象的地址<临时对象的生命期不会到此函数体的结束处>,因为临时对象的生命期结束.
此外,string库里的+操作是返回一个对象的值,不是引用.

19.放置:
前提: (1).需要一种机制把对象安放到特定的地址上(把一个表示进程的对象放到特定的硬件地址上);
(2).需要一种机制在某种特定区域里分配对象(在一个多处理器系统的共享存储中,或由某个特定管理器控制的区域中分配对象)。
解决方案:
重载new运算符,又c++规则里不能对delete进行多样的重载.
new的重载如下:
class X
{
public:
void* operator new (size, void* p) { return p; }
};

void* buf = (void*)0xF00F; //取特定的地址
X* p1 = new(buf)X; // 这里就是在特定的地址上分配对象
X* p2 = new(buf)X; // 此时p1和p2对象的地址都为0xF00F;

new 的第一个参数是对象的长度,由编译器递给new,buf是放置对象的地址,p对内存分配函数或对象的引用.

// 以上格式为放置语法格式
放置机制使new不再只是一种简单的存储分配机制,因为可以给特定的存储位置关联上任意的逻辑性质,使得new起一种通用资源管理的作用.
分配区的一种重要应用是提供特定的存储管理定义.

释放问题:
不能期望用户有关对象是如何分配的,用户就根本不要去释放此对象,这是特殊分配场地的 一种用途.
但是可以用p1 ->X::~X();语句释放<这是一个特殊的调用格式>
所以上面类x也不要重载new运算符(那只是观察对象是怎样分配内存区).

20.C++区分了5种“匹配”:
(1).匹配中不转换或者只使用不可避免的转换(例如,从数组到指针,函数名到函数指针,以及T到const T);
(2).使用了整数提升的匹配(像ANSI标准所定义的。也就是说从char到int,short到int以及对应的unsiged类型)以及float到double;
(3).使用了标准转换的匹配(例如从int到double,derived*到base*,unsigned int 到int);
(4).使用了用户定义转换的匹配(包括通过建构造函数和转换操作);
(5).使用了在函数声明里的省略号...的匹配.
以上可以在ARM里有更好的匹配规则

21.ARM对指针描述:
空指针并不一定用与整数0同样的二进制式表示.
C++具有足够强的类型语言,一个像空指针这样的概念完全可以采用实现者选定的任何方式表示.
所以p = 0,给指针p赋了空指针值,但并不表示必然和整数0完全一样.

22.怎样实现只能在堆上分配一个对象或怎样只能在全局或栈上分配一个对象
(1).只能在堆上分配
class X {
public:
X() {}
static void free(X* p) { delete p; }

private:
~X(){}
};
X x; //错误
X* p = new X(); //OK
X::free(p); //析构

它也能阻止派生一个新类;

(2).只能在全局或栈上分配一个对象
class Y{
public:
Y() {}
private:
class D {};
void* operator new(size_t, D){}
};

class Y
{
public:
Y() {}
private:
void* operator new(size_t){}
};
X x; //OK
X* p = new X(); //错误

23.怎样阻止一个类派生一个子类
class usable;

class unable_lock{
friend usable; // 变成友元可以访问私有构造函数
private:
unable_lock() {}
};

class usable : public virtual { /*这里必须要virtual关键字*/ unable_lock
public:
usable() { }
};

class DD : public usable{};

usable ua; //OK
DD dd; //错误,不能实例化

虚拟继承后改变了构造的顺序:unable_lock -> usable ->DD;
采用非虚拟继承时顺序:usable ->调用usable的父类unable_lock的构造函数;
所以虚拟继承时,DD类不能调用unable_lock里的构造函数,构造函数为私有.

假如:
class X1{
public:
X1() {}
};

class X2_1 : public virtual X1{
public:
X2_1() {}
};

class X2_2 : public virtual X1{
public:
X2_2(){}
};

class X3 : public X2_1, public X2_2{
public:
X3(){}
};

X3 xs;
虚拟继承的顺序:X1 -> X2_1 -> X2_2 ->X3;
非虚拟继承的顺序:X2_1 ->X1 -> X2_2 ->X1 -> X;
采用虚拟继承时有两个作用:(1).改变顺序;(2).只有X1的一份拷贝,或者说只构造一次.

24.多重继承:
class AW : public W{};
class BW : public W{};
class CW : public AW, public BW{};
这叫独立的多重继承,
结构:
/----------AW --------- W
CW
\----------BW ----------W

虚继承:
class AW: public virtual W{};
class BW : public virtual W{};
class CW : public AW, public BW{};
结构:
/-----------AW----------\
CW W
\-----------BW----------/
类W的"虚"是AW和BW描述的那些派生的一种性质,而不是W自身的一种性质,每个virtual基类总表示一个对象.

25.一个正常的基类在一个给定派生类的每个对象的位置都是固定的.
一个虚基类的对象并不位于某个固定位置,必须通过一个间接才能访问.基类被定义一个无名成员.如果允许有虚数据成员,那么虚基类就该是虚数据成员的一个例子.

26.派生类的函数常常是基于同一函数在基类中的版本综合出来的,这一般称作方法组合.

27.抽象类概念的重要性在于它能使人对于用户和实现者做更清晰的划分,降低用户和实现者之间的偶合度。

28.const成员函数:
const成员函数可能用于const对象或非const对象,非const成员函数只能用于非const对象,
假如有对象X,此时就是让X的非const成员函数里的this指针指向X,而让其const成员函数里的this指针指向const X.

29.由于static成员函数并不关联于任何特定对象,因而不需要用特定成员函数的语法进行调用,在某些情况下,类被简单地娄做一种作用域使用,把不放进就是全局的名字放入其中,作用为它的static成员,以便使这些名字不会污染全局的名字,这是名字空间概念的一个起源.

30.指向数据成员的指针是一种表达C++的类布局方式的与实现无关的方式.
指向成员的针指实际上比一个偏移量(标识对象中一个成员的值)还要多一些东西.

31.RTTI(run-time type infomation).

32.运行时类型信息包括3个部份:
(1).一个运算符dynamic_cast,给它一个指向某对象的基类指针,它能够得到一个到这个对象的派生类指针,只有在被指对象确实属于所指明的派生类时,运算符dynmaic_cast才给出这个指针,否则返回0;
(2).一个运算符typeid,它对一个给定的基类指针识别被指对象的确切类型;
(3).一个结构type_info,作为与有关类型的更多运行时类型住处的挂接点(hook).

33.模板:
template <class T> class vector{
public:
vector(int);
T& operator[] (int);
T& elem(int i) { return v[i]; }
};

解释:
template 申请模板关键字,同时为模板类和模板函数提供了一种共有语法形式;
<>是为了强调模板参数具有不同的性质(将在编译时求值)
class T 用于指明类型参数的类型部份

模板是为生成类型提供的一种机制,本身并不是类型,也没有运行时的表示形式,所以对于对象的布局没有任何影响.
除了类型参数外,C++也允许非类型的模板参数,这种机制基本上被看着是容器类提供大小和界限所需的信息。
tempate<class T, int i> class buffer{
public:
buffer():sz(i){}
private:
T v[i];
int sz;
};
在那些运行时效率和紧凑性非常要紧的地方,传递大小信息允许实现者不使用自由空间.如果不能使用非类型的参数,用户就必须把关于大小的信息编码用到数组类型里.
template<class T, class A> class buffer{
public:
buffer():sz(sizeof(A) / sizeof(T)){}
private:
A v;
int sz;
};
buffer<int, int[700]> b;

在模板设计中,不允许名字空间和模板作为模板参数.
类型检测是在模板实例化的时刻进行.
模板实现是一种基于用户所描述的需要去生成类型的机制.

34.类模板可以有默认参数,模板函数则不可以
template<class T = int> class xyz{}; //OK
template<class T = int> T add(T& t){ return ++t; } //error
因为函数模板的模板参数是在调用的时候编译器根据实参的类型来确定的

35.就C++语言规则而论,由同一个类模板生成的两个类之间没有任何关系.
class x{};
class xx : public x{};
template<class T> class a{};
a<x> x1;
a<xx>x2;
x1 = x2; //error
x2 = x1; //error

36.成员模板函数不能是虚的
class shape
{
public:
template<class T> virtual bool get() const { return true; } //error be can‘t virtual
};
因为如果能用virtual合法化时,类的虚函数表会每次因使用者传递的类型不同,而添加新项.这意味着连接程度才能去构造虚函数表,并在表中设置有关的函数.

时间: 2024-12-17 21:52:04

C++——设计与演化——读书笔记的相关文章

Linux内核设计与实现 读书笔记 转

Linux内核设计与实现  读书笔记: http://www.cnblogs.com/wang_yb/tag/linux-kernel/ <深入理解LINUX内存管理> http://blog.csdn.net/yrj/article/category/718110 Linux内存管理和性能学习笔记(一) :内存测量与堆内存 第一篇 内存的测量 2.1. 系统当前可用内存 # cat /proc/meminfoMemTotal:        8063544 kBMemFree:       

Linux内核设计与实现读书笔记——第三章

Linux内核设计与实现读书笔记——第三章 进程管理 20135111李光豫 3.1进程 1.进程即处于执行期的程序,并不局限于一个可执行的代码,是处于执行期程序以及其相关资源的总称. 2.Linux系统中,对于进程和线程并没有明显的区分,线程是一种特殊的进程. 3.Linux系统中,常用fork()进程创建子进程.调用fork()进程的成之为其子进程的父进程. 4.fork()继承实际上由clone()系统调用实现.最后通过exit()退出执行. 3.2任务描述符及任务结构 1.任务队列实质上

《点石成金-访客至上的web和移动可用性设计秘籍》读书笔记

简介 作者Steve Krug,惯例先去了解一下本书的作者,发现书中介绍的并不多,百度一下后发现这本书比作者出名.好吧,百度就是这样子,作者自称web可用性咨询师,手上这本书是第三版再版,第一版2000年出版,2013年出版第三版,最大的变化大概是综合啦,原来凉拌是网站的可用性,这次是web和mobile.作者书中说,互联网时代,13年就像100年那么久,想想我们的cpu和手机这3年的更替,何况现在都16年啦.我想这本书之所以称为互联网必读书之一大概是因为它定义啦许多“本该如此”的原则问题,就是

《css设计指南》 读书笔记 一

<css设计指南>这本书是一个大神同学介绍给我的,据说覆盖了几乎所有前端面试的有关css的知识点,所以赶紧买来看看.( ps:这本书貌似绝版了,可以上淘宝买复印本,也可直接看电子书. ) 闭合标签,自闭合标签. html5中,将忽略所有自闭合标签最后的 / .可是建议在最后仍然加上  空格加/ 以规范格式. 所有img标签都需要加上alt属性. ps:视障用户使用的屏幕阅读器会读出图片的alt属性. 行内.块级标签.  块级: h1~h6, p, ol/ul, li, blockquote .

Redis设计与实现读书笔记(一) SDS

作为redis最基础的底层数据结构之一,SDS提供了许多C风格字符串所不具备的功能,为之后redis内存管理提供了许多方便.它们分别是: 二进制安全 减少字符串长度获取时间复杂度 杜绝字符串溢出 减少内存分配次数 兼容部分C语言函数 下面将简要阐述SDS基础结构,并介绍这些功能相应的实现细节. SDS字符类型定义非常简单,以redis3.0.7为例: typedef char *sds; struct sdshdr { unsigned int len; //定义当前字符串长度(不包含'\0')

《设计心理学》读书笔记

<设计心理学>,英文原名为<The design of everyday things>,尽管书中确实提到了一些认知心理学和行为心理学中的概念,并且其作者--诺曼博士--也从事过心理学研究,但是对于书名是否应该被翻译为设计心理学,却依然是一个备受争议的话题. 回归书的内容本身,作者强调的是,日常物品的设计,应以易于使用为重.一款产品设计出来,最终是要落到用户手里的,其与用户的交互过程是否顺畅,直接决定了这款产品设计的成功与否.而针对目前设计界过分崇尚外观美学的现实,书中则多次用&q

Redis 设计与实现读书笔记一 Redis字符串

1 Redis 是C语言实现的 2 C字符串是 /0 结束的字符数组 3 Redis具体的动态字符串实现 /* * 保存字符串对象的结构 */ struct sdshdr { // buf 中已占用空间的长度 int len; // buf 中剩余可用空间的长度 int free; // 数据空间 char buf[]; }; sdshdr free 0 len 5 buf ---> 'R' 'e' 'd' 'i' 's' '/0' 4 感觉更像 Java 中的 StringBuffer 的设计

《恰如其分的软件架构:风险驱动的设计方法》——读书笔记

个人觉得本书概念太多,软件的架构和开发不是概念拼成的,一些经验性的东西用合适的词描述就行. 所谓风险驱动,其实就根据项目情况选择合适的设计力度,避免过度设计.而对于复杂的软件系统,精心设计还是非常重要的,在开发前和开发中都会有设计的问题. 另外,对于一个软件工程师,对一些架构模式需要有些了解,在脑海中构建所谓的“概念模型”,而每个模型都是一定的抽象和受到一些约束. 还介绍了领域模型,设计模型,代码模型,封装和分割,模型元素(组件),模型关系. 架构风格,就是所谓的概念模型. 第二部分值得一看.

Linux内核设计与实现 读书笔记

第三章 进程管理 1. fork系统调用从内核返回两次: 一次返回到子进程,一次返回到父进程 2. task_struct结构是用slab分配器分配的,2.6以前的是放在内核栈的栈底的:所有进程的task_struct连在一起组成了一个双向链表 3. 2.6内核的内核栈底放的是thread_info结构,其中有指向task_struct的指针: 4. current宏可以找到当前进程的task_struct:X86是通过先找到thread_info结构,而PPC是有专门的寄存器存当前task_s