??xml version="1.0" encoding="utf-8" standalone="yes"?>
······条款16 C80-20准则
大约20%的代码用了80%的资源,E序的整体性能是由该程序的一部分代码所军_的~
可行的办法是使用E序分析器(profilerQ来扑ֈD性能瓉的拿20%的程序~
而且要针寚w成瓉的资源来使用相应的分析器~
······条款17 考虑使用延迟计算
延迟计算Q?也就是说知道E序要求l出l果的时候才q行q算~ 很好理解Q和操作pȝ中的cow copy on write 一个原理~
四个使用场景Q?/font>
~1~ 引用计数 Q?/font>
class String{…};
String s1 = “hello”;
String s2 = s1 ; //call string Copy ctor
通常情况下,s2赋值后会有一个hello的拷贝,者通常需要用new操作W分配内存,之后strcpys1
的数据给他,但如果下面的操作如下的话Q?/font>
cout << s1 ;
cout << s1 + s2;
q种情况下如果只增加s1的引用计敎ͼ而s2只是׃ns1的值就可以了。只有在需要对s2q行修改或者s1q行修改Ӟ才需要真正拷贝给s2一个副本,引用计数的实现在29条款
~2~区分d操作
如: String s = “homer’s all”;
cout<< s[3];
s[3] = ‘x’;
在进行读操作Ӟ使用引用计数是开销很小的,然而写操作必须生成新的拯。通过条款30的代理类我们可以把判断读写操作推q到我们能够军_哪个是正操作的时?/font>
~3~延迟d
假设E序使用了包含许多数据成员的大对象,q些对象必须在每ơ程序运行的时候保留下来,因此存进了数据库。某些时候从database中取出所有数据是没有必要的,比如他只讉K该对象中的一个数据成员。此Ӟ应该对对象进行处理,只有对象内部某一个特定的数据成员被访问的时候才把他取出来。类gos中的按需换页~
class LargeObject{
LargeObject(ObjectID id);
const string& field1() const;
int field2() const;
double field3() const;
const string& field4() const;
private:
ObjectID id;
mutable string* field1value;
mutable int * fieldValue;
};
LargeObject::LargeObject(ObjectID id):oid(id),fieldValue(0),…{}
const string& LargeObject::field1()const{
if(fieldValue == 0){
//read the data for field 1 from database and make field1 point to it
}
return *field1Value;
}
实施lazy fetching M成员函数都需要初始化I指针以指向有效数据。但是const成员函数中,试图修改数据~译器会报错。所以声明字D|针ؓ mutable Q表CZQ何函数都可以修改,即便在const成员函数中也可以~ 条款28中的指针可以让这一Ҏ更灵z?/font>
~3~延迟表达式求?/font>
数D领域,也在使用延迟计算。例?/font>
matrix<int> m1(1000,1000);
matrix<int> m2(1000,1000);
matrix<int> m3 = m1 + m2;
如果此时计算出m3的话q算量非怹大~
但是如果此时E序为:
m3 = m4*m1;
那么刚才的计就没必要了
如果cout<< m3[4];
我们只需要计m3[4]可以了Q其他的值等到确实需要他们的时候才予以计算~如果q气够好的话永远不需要计~
ȝQgq计只有当软g在某U程度下可以被避免时候才有意义~只有延迟计算得到的好处大于设计它与实现它p的精力时才有意义~
·······条款18Q?分期摊还预期的计开销
提前计算~ over-eager evaluation 在系l要求你做某些事情之前就做了他~
例如Q大量数据的集合
template<class NumericalType>
class DataCollection}{
public:
NumericalType min() const;
NumericalType max() const;
NumericalType avg() const;
};
使用提前计算Q我们随时跟t目前集合的最大最^均|q样 min max avg被调用时候,我们可以不用计算立刻q回正确的数值~~
提前计算的思想便是Q如果预计某个计会被频J调用,你可以通过设计你的数据l构以更高效的办法处理请求,q样可以降低每次h的^均开销~
最单的做法?~存已经计算qƈ且很可能不需要重新计的那些值~
例如在数据库中存有很多办公室的电话号码,E序在每ơ查询电话时先查询本地的~存如果没找到再去访问数据库Qƈ且更新缓存,q样使用~存q_讉K旉要大大减?/font>
预处理也是一U策略?/font>
例如设计动态数l的时候,当烦引下标大于已有最大范围时候,需要new出新的空_如果甌两倍于索引的大的话就可以避免频繁的申h作~~~
········条款 19 Q?了解临时对象的来?/font>
如果一个对象被创徏Q不是在堆上Q没有名字,那么q个对象是临时对象?/font>
通常产生于: Z使函数调用能够成功而进行的隐式转换Q或者函数返回对象是q行的隐式{换。由于构造和析构他们带来的开销可以l你的程序带来显著的影响Q因此有必要了解他们~
~1首先考虑Z函数调用能通过产生的时对象的情况
传给某个函数的对象的cd和这个函数所l定的参数类型不一致的情况下会出现q种情况?/font>
例如Q?/font>
size_t count(const string& str,char ch);
函数定义str中ch的数?/font>
char buffer[100];
cout<<count(buffer,‘c’);
传入的是一个char数组Q此时编译器会调用str的构造函敎ͼ利用buffer来创Z个时对象?/font>
在调用完countChar语句后这个时对象就被自动销毁了~
仅当传值或者const引用的时候才会发生这Lcd转换~当传递一个非帔R引用的时候,不会发生?/font>
void uppercasify(string& str); //change all chars in str to upper case;
在这个例子中使用char数组׃会成功~
因ؓE序作者声明非帔R引用也就是想让对引用的修改反映在他引用的对象w上Q但是如果此时生成了临时对象Q那么这些修改只是作用在临时对象w上Q也׃是作者的本意了。所以c++止非常量引用生时对象?/font>
~2 函数q回对象时候会产生临时对象
例如Q?const Number operator + ( const Number& lhs,const Number& rhs);
q个函数q回一个时对象,因ؓ他没有名字,只是函数的返回倹{?/font>
条款20?Q会介绍让编译器对已l超出生存周期的临时对象q行优化
········条款20Q?协助~译器实现返回g?/font>
q回g化:q回带有参数的构造函数?/font>
cosnt Rational operator * (cosnt Rational& lhs,const Rational& rhs){
return Rational(lhs.numerator()*rhs.numerator(),lhs.denomiator()*rhs.denominator()};
c++允许~译器针对超出生命周期的临时对象q行优化。因此如果调用Rational c=a*bQ?/font>
c++允许~译器消除operator*内部的时变量以及operator*q回的时变量,~译器可以把return表达式所定义的返回对象构造在分配lc的内存上。如果这样做的话那么调用operator*所产生的时对象所带来的开销是0~ 我们可以把operator 声明为内联函数而去除调用构造函数带来的开销~
#include <iostream>
#include <string>
#include "time.h"
using namespace std;
char buffer[100];
class number{
public:
const friend number operator * (const number& rhs,const number lhs);
number(){}
number(int b):a(b){}
number(const number& rhs){
a = rhs.a;
}
int a;
};
const number operator*(const number& rhs,const number lhs){
number res;
res.a = rhs.a * lhs.a;
return res;
/*return number(rhs.a*lhs.a);*/
}
//CLOCKS_PER_SEC
int main()
{
clock_t start = clock();
number A(5);number B(6);
for(int i=0;i<100000000;i++)
number C = A*B;
clock_t end = clock();
cout<<double(end-start)/CLOCKS_PER_SEC<<endl;
}
通过上面的程序运?如果没有q回g?q行旉 15.9s 优化后是 10.1s
q是很显著的?快了33% Q如果这U情况出现在E序的热点处~效果很好了
·········条款21 Q?通过函数重蝲避免隐式cd转换
例子Q?/font>
class upint{
public:
upint();
upint(int value);
};
cosnt upint operator+(const upint&lhs,const upint&rhs);
upint up1,up2;
upint up3 = up1+up2;
upi3 = up1 +10;
upi4 = 10+ upi2;
q些语句也可以通过Q因为创Z临时对象Q通过带有int的构造函C生了临时的upint对象Q如果我们不愿意些时对象的产生与析构付ZP我们需要做什么:
我们声明 cosnt upint operator+(cosnt upint&lhs,int rhs);
cosnt upint operator+(int lhs,const upint& rhs);
可以去除时对象生了~
但是如果我们写了 const upint operator+(int lhs,int rhs); // 错了~
c++规定Q每一个被重蝲的运符必须臛_有一个参数属于用戯定义cdQintq不是自定义cd所以上面的不对?/font>
同样的如果希望string char* 作ؓ参数的函敎ͼ都有理由q行重蝲而避免隐形类型{换(仅仅在有必要的时候,也就是说他们可以对程序效率v到很大帮助的时候~Q?/font>
··········条款Q?考虑使用 op = 来取?单独?opq算W?/font>
class Rational{
public:
Rational& operator+=(const Rational& rhs);
Rational& operator-=(const Rational& rhs);
}
const Rational operator+(cosnt Rational& lhs,const Rational & rhs){
return Rational(lhs)+=rhs;
}
利用+= -=来实? -可以保证q算W的赋值Ş式与单独使用q算W之间存在正常的关系?/font>
Rational a,b,c,d,result;
result = a+ b+c+d; // 可能要用?个时对?/font>
result +=a;result+=b;result+=c; //没有临时对象
前者书写维护都更容易,而且一般来说效率不存在问题Q但是特D情况下后者效率更高更可取
注意Q?/font>
如果+的实现是q样的:
const T operator+ (constT& lhs,const T&rhs){
T result(lhs);
return result += rhs;
}
q个模版中包含有名字对象resultQ这个对象有名字意味着q回g化不可用~~~~~~~~·
·······条款23 Q?考虑使用其他{h的程序库
LQ?/font>
提供cM功能的程序库通常在性能问题上采取不同的权衡措施Q比如iostream和stdioQ所以通过分析E序扑ֈ软g瓉之后Q可以考虑是否通过替换E序库来消除瓉~~~~
······条款24 : 理解虚函敎ͼ多重l承Q虚基类以及 RTTI 带来的开销
虚函数表Qvtabs 指向虚函数表的指?vptrs
E序中每个声明了或者承了的虚函数的类都具有自q虚函数表。表中的各个就是指向虚函数具体实现的指针?/font>
class c1{
c1();
virtual ~c1();
virtual void f1();
virtual int f2(char c)const;
virtual void f3(const string& s);
};
c1 的虚函数表包括: c1::~c1 c1::f1 c1::f2 c1::f3
class c2:public c1{
c2();
virtual ~c2();
virtual void f1();
virtual void f5(char *str);
};
它的虚函数表入口指向的是那些由c1声明但是c2没有重定义的虚函数指针:
c2::~c2 c2::f1 c1::f2 c1::f3 c2::f5
所以开销上: 必须为包含虚函数的类腑և额外的空间来存放虚函数表。一个类的虚函数表的大小取决于它的虚函数的个敎ͼ虽然每一个类只要有一个虚函数表,但是如果有很多类或者每个类h很多个虚函数Q虚函数表也会占据很大的I间Q这也是mfc没有采用虚函数实现消息机制的一个原因?/font>
׃每一个类只需要一个vtbl的拷贝,把它攑֜哪里是一个问题:
一U:为每一个需要vtbl的目标文件生成拷贝,然后q接时取出重复拷?/font>
或者:更常见的是采用试探性算法决定哪一个目标文件应该包含类的vtbl。试探:一个类的vtbl通常产生在包含该cȝ一个非内联Q非U虚函数定义的目标文仉。所以上面c1cȝvtbl放在c1::~c1 定义的目标文仉。如果所有虚函数都声明ؓ内联Q试探性算法就会失败,在每一个目标文件就会有vtbl。所以一般忽略虚函数的inline指o?/font>
如果一个类h虚函敎ͼ那么q个cȝ每一个对象都会具有指向这个虚函数表的指针Q这是一个隐藏数据成员vptr~被编译器加在某一个位|?/font>
此处W二个开销Q你必须在每一个对象中存放一个额外的指针~
如果对象很小q个开销十分显著~~因ؓ比例大~
此时 void makeCall(c1* pc1){
pc1->f1();
}
译?(*pc1->vptr[i])(pc1);
Ҏvptr扑ֈvtbl q很单,
在vtbl扑ֈ调用函数对应的函数指针,q个步骤也很单,因ؓ~译器ؓ虚函数表里的每一个函数设|了唯一的烦?/font>
然后调用指针所指向的函数~
q样看来Q调用虚函数与普通函数调用的效率相差无几Q只多出几个指o?/font>
虚函数真正的开销与内联函数有关~Q在实际应用中,虚函C应该被内联,因ؓ内联意味着在编译时ȝ被调用函数的函数体来代替被调用函数。但是虚函数意味着q行时刻军_调用哪个一函数Qso~~~虚函C出的W三个代价啊Q~不能内联Q通过对象调用虚函数的时候,q些虚函数可以内联,但是大多数虚函数通过指针或者以用来调用的)?/font>
~多重l承的情?/font>
多重l承一般要求虚基类。没有虚基类Q如果一个派生类h多个通向基类的承\径,基类的数据成员会被复制到每一个承类对象里,l承cM基类间的每一条\径都有一个拷贝?/font>
有了虚基c,通常使用指向虚基cȝ指针作ؓ避免重复的手D,q样需要在对象内部嵌入一个或者多个指针~也带来了一定的开销~
例如菱Şl承 Q?
class A{};
class B:virtual public A{};
class C:virtual public A{};
class D:public B,public C{};
q里A是一个虚基类Q因为B和C虚拟l承了他?/font>
对象 D 的布局Q?/font>
B data
vptr
pointer to virtual base class
C data
vptr
pointer to virtual base class
D data members
A data members
vptr
上面四个c,只有三个vptrQ因为B和D可以׃n一个vptr Qؓ啥?Q?/font>
现在我们已经看到虚函数如何对象变得更大Q以及ؓ何不能把它内联了~
下面我们看看RTTI的开销 runtime type identifycation 所需要的开销
通过rtti我们可以知道对象和类的有关信息,所以肯定在某个地方存储了这些供我们查询的信息,q些信息被存储在type_info cd的对象里Q你可以通过typeidq算W访问一个类的type_info对象?/font>
每个cM仅需要一个RTTI的拷贝,规范上只保证提供哪些臛_有一个虚函数的对象的准确的动态类型信息~
whyQ和虚函数有啥关p~ 因ؓrtti设计在vtbl?/font>
vtbl的下?包含指向type_info对象的指针。所以用这U实现方法,消费的空间是vtbl中占用一个额外的单元再加上存储type_info对象所需要的I间?/font>
------------------------|恶的结束线 OVER~------------------------------------------
学习一下断a吧:
·······什么是断言
在某处判断某一个表辑ּ的gؓ真或者假Q如果假则输出错误消息ƈ停止E序的执行~
assert是宏Q而不是函敎ͼ只在debug版本中有效,因此无需在release版本删除?/font>
·······哪几U断a
MFC
ASSERT
void foo(char* p,int size)
{
ASSERT(p != 0); // 验证~冲区指?br>ASSERT((size >= 100); // 认~冲区大至ؓ100字节
// foo 函数的其它计过E?br>}
如果没有定义_DEBUG预处理符Q则该语句不会真正生成代码。Visual C++会在调试模式~译时自动定义_DEBUGQ而在发行模式下,该预处理W是不存在的。如果定义了_DEBUGQ则上述两个断言生成的代码类如:
//ASSERT(p != 0);
do
{
if(!(p != 0) && AfxAssertFailedLine(__FILE__, __LINE__))
AfxDebugBreak();
} while(0);
//ASSERT((size >= 100);
do
{
if(!(size >= 100) && AfxAssertFailedLine(__FILE__,__LINE__))
AfxDebugBreak();
}while(0);
ASSERT_KINDOF(classname,pObject); ASSERT_KINDOF(CDocument,pDocument);
验pObject指向的对象是classnamecȝ一个对象或者其zcȝ对象
ASSERT_VALID(pObject); pObject 必须是一个派生于CObjectcȝcd象,会调用其重写的AssertValid函数 Q例?/font>
如果使用应用向导或类向导生成ZMFC的类Q通常会得到AssertValid()的骨Ӟ最好改写这些骨架代码以增加最基本的完整性检查。下面是一个典型的例子Q类Sample从CObjectl承Q假定它含有职员名字及其薪水Q?
class Sample : public CObject
{
protected:
CString m_Name; // 职员名字
double m_Salary; // 薪水
public:
Sample(LPCTSTR name,double salary) : m_Name(name), m_Salary(salary) {}
#ifdef _DEBUG
virtual void AssertValid() const;
#endif
};
#ifdef _DEBUG
void Sample::AssertValid() const
{
CObject::AssertValid(); // 验证基类
ASSERT(!m_Name.IsEmpty()); // 验证职员名字
ASSERT(m_Salary > 0); // 验证薪水
}
#endif
CRT assertion
_ASSERT ?nbsp; _ASSERTE 后一个会在出错时同时打印出条件判断句
ANSI
assert()
注意Qassert用于非法的输入Q但是合法的输入q不一定是正确的,例如Q?/font>
int pB = (int*)malloc(sizeof(int)*1000);
assert(pB!=NULL) //错误的用assert 他会在release版本失效~也就是说assert不应该对E序产生副作?/font>
正确的做法:
int pB = (int*) malloc(sizeof(int)*1000);
if(pB == NULL)
{
//错误处理
}
else{
}
另一个例子:
void draw(){
CFigure* pF = getCF();
assert(pf!=NULL);
if(pf == NULL){}
else{
}
}
此处Q对于getCF来说q回gؓNULL是非法的Q如果他的返回值可能ؓnull没必要加上assert语句?/font>
而下面的if语句则是Z防止release版本出现null指针的情c?
VERIFY()
׃ASSERT仅在E序的调试版起作用,试表达式L被动的。也是_它们不能包含赋倹{增量、减量等真正改变数据的操作。但有时候我们需要验证一个主动表辑ּQ比如赋D句。这时可以用VERIFY代替ASSERT。下面是一个例子:
void foo(char* p,int size)
{
char* q; // 指针的副?br>VERIFY(q = p); // 拯指针q执行验?br>ASSERT((size >= 100); // 保~冲区大至ؓ100字节
// 执行 foo 的其它操?br>}
在调试模式下ASSERT和VERIFY是相同的。但在release模式下,VERIFY能够l箋对表辑ּ求|但不再进行断a验)Q而ASSERT语句在效果上如同已l删除了一栗?
管在MFC源代码中可以扑ֈ一些应用VERIFY的例子,但ASSERT用得更ؓ普遍。一些程序员L完全避免使用VERIFYQ因Z们已l习惯于使用被动断言。请CQ如果在ASSERT语句中用了d表达式,~译器不会发ZQ何警告。在发行模式下编译时该表辑ּ会被直接删除Q从而导致程序运行的错误。由于发行版E序不含调试信息Q这U类型的错误是很难找到原因的?
E序可以使用基类的指?/font>或者引用来索这些指针或引用所指对象的实际zcd
标准库提供了两个操作W:
1. typeid 他可以返回指针或者引用所指向对象的实际类?/font>
2. dynamic_cast 基cȝ型的指针或引用安全的转ؓzcȝ指针或者引?nbsp;
对于不带虚函数的cd~译时期执行Q否则在q行期间执行
使用时机Q?/font>
基类指针调用zcL员函数的话,一般应使用虚函敎ͼq样~译器会Ҏ对象的实际类型来选择正确的函数。但是某些情况下无法使用虚函敎ͼ那么?/font>
时如果需要用基cL针调用派生类成员函数侉K要用RTTI强制转换Q而且必须查{换是否成?/font>?/font>
(一) Dynamic_cast
dynamic_cast 如果转换到指针失败则q回 0 如果转换到引用类型失败则抛出 bad_cast 异常
Ҏ针操作:
if(Derived *derivedPtr = dynamic_cast<Derived*> (basePtr)){
//basePtr point to a derived object;
}else{
//basePtr point to a base object;
}
?if 语句中进行检?1.条g代码内部知道 derivedPtr 的类?2.不可能在试代码和用代码中加入代码Q也是说不会在没有试前就使用derivedPtr 3.如果p|外部不会使用未绑定的指针derivedPtr
对引用操作:
因ؓ不存在空引用所以{换失败抛出异?/font>
void f(const Base& b){
try{
const Derived &d = dynamic_cast<const Derived&> (b);
}catch(bad_cast){
}
}
(? typeid
typeid(e) e是Q意的表达式或者类型名
Base *bp;
Derived *dp;
//compare type at run time of two objects
if(typeid(*bp)==typeid(*dp)){
//bp dp point to objects of the same type
}
if(typeid(*bp)==typeid(Derived)){
//bp actually point to a Derived
}
注意typeid 试的是 *bp 对象
//test always fails: The type of bp if pointer to Base
if(typeid(bp)==typeid(Derived)){
}
Base* 永q和Derivedcd不同
只有typeid 的操作数是带虚函数的cȝ对象的时候,才返回动态类型信息,试指针q回指针的静态的Q编译时cd
Q三 Qtype_info c?/p>
作ؓtypeid的返回?提供
t1==t2 t1!=t2 t.name() 的操?/p>
作ؓ应用例子Q可以实C个类型敏感的相等操作W?/p>
friend bool operator == (const base& lhs, const base& rhs){
return typeid(lhs)==typeid(rhs) && lhs.equal(rhs);
}
q样可以处理基类子类混合的相{判断了
一个对象的行ؓ取决于他的状态,而且它必dq行时根据状态改变他的行为。常规实CQ一个操作含有庞大的多分支的条g语句Q且q些分支依赖于该对象的状态。这个状态通常使用一个或者多个枚丑ָ量表C。STate模式把这些状态时候的对象看做一个独立的对象Q也是不同状态时的行为分散到相应的状态类中。要辑ֈq样的效果,需要contextQ也是状态的持有者,卛_先的c;抽象状态类Q他装了与context交互的接口;具体状态类Q也是一个个的具体状态。context中保存一个抽象状态类对象为成员,qdelegate对象行ؓl他Q从而相应状态下的行Z码生效。如果状态改变的准则不是固定的则state状态类同时应该重写changestatecM控制状态的改变Q否则可以在context中实现?/font>
具体到我们的目Q?/font>
每一个device即ؓcontextQ他拥有一个state对象Qdevice中的函数processMsg(){state->processMSg();} ׃状态改变的规则依赖于收到的消息Q也是说一个状态可能{换到多个状态device的每个状态需要重写statechangeҎQstateChange(){state->stateChange(thisQmsg);} q样Q不同的状态下的行为实现在具体状态的cMQ比原先的版本清晰明了,可读性更强?/font>
主要内容 FIND-UNION ALGORITHM
是利用q查集来考察q通性的法 。条件N个节点,M对节点,输入每一对节Ҏ候,如果已经q通,则忽略,否则输出接点q更?
主要介绍三种法Q第一U,QUCK-FIND 利用数组记录每一个联通子图,同一个联通子囄节点数组变量是相同的。因此每d一对就需要遍历N个数l变量考察是否需要更斎ͼ最坏时间MNQ实际上每个子图为高度ؓ2的树;W二U,QUICK-UNION 联通子N要union?仅仅需要将两个root union 因此每个联通子N度变高,所有find root 会消耗一定时_当然无需遍历N?Q最坏时_也就是每ơ均需要从叶子节点d溯求根(q里书中丑־例子好像有问题)也是MNQ第三种也就?QUICK WEIGHT UNION q种Ҏ在两个子树合q的时候,不是单的指定合ƈ{略Q而是l过判断的,把size(相应高度也小Q的合ƈ到size大的里面Q实际上很容易理解就是尽量减树的高度来q求quick-find的效率;
q而作者写道\径压~,也是q种思想。或者实Cؓ全部压羃Q也是说在q行union操作的时候,所有check的节炚w直接链接到union后的新root下面Q或者half union Q容易实C性能好)每次check时候,如果没有停止loop单的?id[i]=id[id[i]];卛_辑ֈ减少到根距离的效果?
half union 的主要代码:
int i,j; for(i=p;id[i]!=i;i=id[i]) id[i]=id[id[i]]; for(j=p;id[j]!=j;j=id[j]) id[j]=id[id[j]]; if(i==j)continue; if(size[i]>size[j]) id[j]=i,size[i]+=size[j]; else id[i]=j,size[j]+=size[i];
W一ơ用windows live writer 试试效果哦~~
sign | exponent | mantissa | |
single precision | 1 | 8 | 23 |
double precision | 1 | 11 | 52 |