effective C++笔记(一)

发布时间:2014-10-23 23:29:22
来源:分享查询网

条款2: 一.对于单纯常量,最好使用const对象或者enum代替#define 二.对于形似函数的宏,最好采用inline替换#define 这样的预定义宏:#define PI 3.14 这样的定义会在编译之前进行替换,这就导致在编译器的符号列表中并没有PI这样的名字,只有3.14这样的数值,尤其是当你的宏定义在与实现文件不同的头文件中时,当编译器发生编译错误时,我们无法跟踪这个变量(PI),因为它压根没在符号列表中。 解决方法:用const代替:const double pi=3.14 此时的const会记录到符号列表中,这样就并不是简单的宏替换,而是要在编译阶段按照语法进行类型检查等。 特殊情况:如果在类内需要一个常量用于初始化数组时,有两种方法: 一.静态常量定义,如下: "gameplayer.h" class GamePlayer { public: static const int NumTurns=9; GamePlayer(){} private: int scores[NumTurns]; }; #include "stdafx.h" #include"gameplayer.h" #include<iostream> using namespace std; //int GamePlayer::Num=9; int _tmain(int argc, _TCHAR* argv[]) { GamePlayer obj; cout<<obj.NumTurns<<endl; return 0; } 如上在NumTurns是一个常量,但这里注意必须是static的,原因是该变量在类内定义,此时没有对象给予它初始化,所以必须是类初始化对象,即static变量。const是为了给予数组初始化,从语法上讲这里可以是非const的,但是注意必须是在类外定义。总之要想像如上在类内定义必须是static const类型的。 除此之外的变量必须是在构造函数内初始化,即使用对象初始化 二.enum进行常量初始化 这种方式是C++最佳方式,前面的常量初始化有些晦涩 class GamePlayer { public: enum{NumTurns=9}; GamePlayer(){} private: int scores[NumTurns]; }; 上述阐述了条款中第一点的含义,下面介绍第二点的内涵: #define CALL_WITH_MAX(a,b) f((a)>(b):(a)?(b)) 如上要注意所有的参数都应该加上小括号,才能够避免替换的问题,但是就上述宏的本义仍会出现bug: int a=5,b=0; int c=CALL_WITH_MAX(a++,b); 此时会得到c的值为7 而如果这样调用呢? int c=CALL_WITH_MAX(a++,b+10); 此时c的值为10 这种不稳定性让我们很无语,解决的方法是使用inline函数 template<typename T> void CALL_WITH_MAX(T& a,T& b) { f(a>b?a:b); }条款3: 尽可能使用const: 一.const char* const这些用法含义无需细说,很简单 这里说一下const iterator和const_iterator的区别: const vector<int>::iterator iter表示iter是一个T* const含义的常量,即迭代器的指向不能修改。 vector<int>::const_iterator iter表示这是一个const T*含义的迭代器,即指向的内容不可以改变 二.函数返回const类型 class Type{...}; const Type operator*(const Type& lhs,const Type& rhs); 重点:为什么要返回一个const类型的值? 假设可以返回非const的数值: Type a,b,c; ... (a*b)=c;成立 但是如果我们定义为const返回类型则该式是不允许的 总结:const标记是避免用于左值操作数的 返回值作为引用的作用是:可以作为左值,即可以改变真正意义上的对象的值 如下: #include<iostream> #include<cstring> using namespace std; class type { private: string text; public: type(char* s):text(s){}; const char& operator[](size_t position) const { cout<<"const function is called."<<endl; return text[position]; } char& operator[](size_t position) { cout<<"Nonconst function is called."<<endl; return text[position]; } }; int print(const type& obj) { cout<<obj[1]<<endl; return 0; } int main() { type obj("hello"); print(obj); obj[1]='x'; print(obj); system("pause"); return 0; } 上述程序打印结果如下: 当调用const type obj时意味着const运算符[]被调用(在print函数中的参数),注意返回的都是引用,如果不是引用obj[1]='x'这类的赋值代码将变得不合法,且在obj[1]='x'时调用的是Nonconst函数 三.const成员函数的实质 一种观点是:const类不允许改变类中的数据成员。static类型的除外 但这种观点并不能保证开发者原本的含义: #include<iostream> #include<cstring> using namespace std; class type { private: char* pText; public: char& operator[](size_t position) const { return pText[position]; } type(char* s) { if(strlen(s)==0) s[0]='\0'; else { pText=new char[strlen(s)+1]; strcpy(pText,s); } } void print() { cout<<pText<<endl; } }; int main() { type obj("hello"); obj.print(); obj[0]='k'; obj.print(); system("pause"); return 0; } 如上运算符[]是const函数,但是我们在客户端程序仍然可以改变他。 注意如果我在const函数中也想改变普通数据成员时,我们应将该数据成员修饰为mutable 使用non-const函数调用const函数的代码: #include<iostream> using namespace std; class test { private: char* text; public: test(char* str) { size_t len=strlen(str); if(len==0) text[0]='\0'; else { text=new char[len+1]; strcpy(text,str); } } const char& operator[](size_t position) const { //.... cout<<"const function is called."<<endl; return text[position]; } /* char& operator[](size_t position) { //.... return text[position]; } */ char& operator[](size_t position) { cout<<"non-const function is called."<<endl; return const_cast<char&>(static_cast<const test&>(*this)[position]); } void print() { cout<<text<<endl; } }; int main() { test a("hi,zhangxin"); a.print(); cout<<"-----------"<<endl; a[0]='k'; cout<<"-----------"<<endl; a.print(); system("pause"); return 0; } 上述代码的结果是(DEV C++测试结果): 如上可以看到当non-const函数和const函数是相同的代码时,可以采用在non-const函数中调用const函数代码,但注意反之不成立(const函数中调用non-const函数代码) 下面补一点C++的基础知识: const_cast: 用法:const_cast<type_id> (expression) 该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。 一、常量指针被转化成非常量的指针,并且仍然指向原来的对象; 二、常量引用被转换成非常量的引用,并且仍然指向原来的对象; 注意该强制转化符只用于引用和指针 #include<iostream> using namespace std; class B { public: B() { } public: int m_iNum; }; void foo() { const B b1; //b1.m_iNum = 100; //compile error // 可以做如下转换,体现出转换为指针类型 B *b2 = const_cast<B*>(&b1); // 或者左侧也可以用引用类型,如果对b2或b3的数据成员做改变,就是对b1的值在做改变 B &b3 = const_cast<B&>(b1); b2->m_iNum = 200; //fine cout<<b2->m_iNum<<endl; b3.m_iNum = 300; //fine cout<<b2->m_iNum<<endl; cout<<b3.m_iNum<<endl; b2->m_iNum=500; cout<<b2->m_iNum<<endl; cout<<b3.m_iNum<<endl; } int main( int argc, char * argv[] ) { foo(); system("pause"); return 0; } 上述的结果是(DEV C++环境): 如上可以表明引用和指针互相影响,因为两者的改变都是指向b1的,即改变的是b1本身的成员值 使用const_cast时注意常量折叠问题: #include<iostream> using namespace std; int main() {     const int i=2;     int* j=const_cast<int*>(&i);     *j=1;     cout<<&i<<" "<<j<<" "<<i<<" "<<*j<<endl;     system("pause");     return 0; } 结果如下(DEV C++环境): 如上可以显示出来i的值并没有改变,为什么呢? 因为在编译阶段cout<<i中的i已经被替换成了2.即cout<<&i<<j<<2<<i 但是这里也说明了i和j都是有地址的,这一点和#define不同 static_cast: 代替C语言中的强制类型转化,static_cast<type-id>(expression) 一.常量指针转化为非常量的 二.父类和子类指针之间的转化 三.任何类型的指针转化为void 四,与三相反 如下实例: int i; float f=16.76; i=static_cast<int>(f); cout<<i<<endl; 回到一开始的non-const函数中调用const函数代码的程序中: const_cast<char&>(static_cast<const type&>(*this)[position]) 先通过static_cast转化,此时会调用const函数,之后再除去const含义(使用const_cast) 注意:在const的函数调用non-const代码是不提倡的,因为non-const不能承诺其const特性。

返回顶部
查看电脑版