条例一:视c++为一个语言联邦
将c++视为一个由相关语言组成的联邦
c:c++语言包容c的语法以c为基础,在很多时候,c++是c的高级解法。
面向对象:c++内包含封装、继承、多态、虚函数等。
模板:c++的泛型编程部分。
STL:stl是标准模板库。
将c++视为由上述四个次语言构成的联邦。
条例二:尽量用inline、enum、const替换#define
本条款的含义是尽量使用编译器来替换预处理器。
#define ASPECT 1.68
在其出错时,它可能会出现1.68而不是ASPECT,这会导致调试的困难。因为该名称可能未进入信号表。应该改为常量表示。
const double Aspect=1.68
两种特殊情况
在宏定义一个字符串时,我们不能只简单的将define替换为const
#define MYCHARARR helloworld
const char * Mychararr="helloworld";//错误,常量指针的指向可以改变
const char const * Mychararr="helloworld"; //正确,但不完美
const string Mychararr="helloworld"; //完美
第二种情况是在定义一个类的专属常量时
class myclass
{
private: static const int Num=5;//声明式
int arr[Num];
};
//如果不取地址,上面声明式即可,若取得一个专属常量的地址,则需定义
const int myclass::Num; //不赋值 定义式
不能用#define定义一个专属的class常量,因为#define一旦定义,在编译过程有效,而非仅仅属于一个类,不存在private:#define的语法。然而也有许多编译器不支持这种写法。
于是引入enum类型。
class myclass
{
private: enum{Num=5};
int arr[Num];
};
enum比较像#define,例如取const类型是合法的,取#define和enum类型是不合法的。enum类型不会导致非必要的类型分配。
另一个#define的误用情况是以它来实现宏,这种方式不会有函数调用的额外开销
#define MAX(a,b) f((a)>(b)?(a):(b))
int a=5,b=0;
MAX(++a,b);//递增一次
MAX(++a,b+10);//递增两次
该方式有很多问题,首先要为宏的所有实参加上小括号,还有很多问题。一般我们可以使用函数模板和inline函数来实现相同的结果。
对于单纯常量,最好以const或enum来替换#define
形似函数的宏,尽量使用内联函数来替代。
条款三:尽可能使用const
const 指定一个不被改动的对象
char p[]="helloworld" //字符串数组
char* p="helloworld" //等于上行
const char * p="helloworld" //指向可以改变,值不能改变
char* const p="helloworld" //指向不能改变,内容可以改变
const char * const p="helloworld" //值和指向都不能改变
下面两种const代表含义均相同
void fun(const Widget* pw)
void fun(const Widget *pw)
在STL中,迭代器是根据指针塑膜出来的,因此声明迭代器为const就和声明指针为const有相似的问题。
const vector<int>::iterator //表明迭代器不可变,指向的内容可以改变
vector<int>::const_iterator //迭代器可变,指向的内容不可变
在函数声明时,const可以和函数返回值、函数参数、函数自身产生关联
const Rational operator*(const Rational& lhs,const Rational& rhs)
在上述函数声明中,返回值为const,这可以避免a*b=c这种不合法的操作。
const成员函数
将const用于成员函数的目的是为了确认该成员函数可作用于const对象身上。
这一类成员函数之所以重要,
理由一:可以得知那个函数可以改变对象内容而哪个不行。
理由二:使操作const对象成为可能。
const不同,函数可重载。
class TextBlock{
public: const char & operator[](size_t position)
{
return text[position];
}
char & operator[](size_t position)
{
return text[position];
}
};
TextBlock tb("hello");
cout<<tb[0]; //非const
const TextBlock ctb("hello");
cout<<tb[0]; //const
tb[0]='x' //正确
ctb[0]='x' //错误
另外,返回类型必须为引用,否则tb[0]=‘x' 无法通过编译。因为如果返回值类型为一个内置类型,改动函数的返回值不合法,因为其是一个右值。
即使合法,改动的也是其副本,而非其本身。
bitwise const 成员函数只有在不更改的任何成员变量(static除外)才能被称作const
char& opertator[](size_t position) const
{ return pText[position];
}
//bitwise声明,但其实不合适
const CTextBlock cctb("hello");
char *pc=&cctb[0];
*pc='p';//更改成功
还是改变了其内的值。(若不想改变,需要给返回值也加上const)
所以引出
logical const 一个const成员函数可以修改可以修改他所处理对象的某些bits,但只有在客户端侦测不出来的情况下才可如此。
class CTextBlock{
public:
size_t length()const;
private:
bool lengthisvalid;
size_t textlenth;
char * pText;
};
size_t CTextBlock::length()const
{
if(!lenthisvalid)
{textlenth=strlen(pText);
lenthisvaild=true;
}
return textlenth;
}
如上述代码,lenthisvaild和textlenth都有可能被修改,不是bitwise,但是编译不会通过。
一个解决方法是将该两个变量声明为mutable。
class CTextBlock{
public:
size_t length()const;
private:
mutable bool lengthisvalid;
mutable size_t textlenth;
char * pText;
};
size_t CTextBlock::length()const
{
if(!lenthisvalid)
{textlenth=strlen(pText);
lenthisvaild=true;
}
return textlenth;
}
该声明含义是即使在const函数中,其也是可修改的。
另外,如果在operator[]中做了许多事情,如边界检测、记录访问信息等等操作,使用const或non-const成员函数可能会造成大量的代码重复,如下:
class TextBlock{
public:
const char & operator[](size_t position)const
{
...//边界检测
...//数据记录
...
return text[position];
}
char & operator[](size_t position)
{
...//边界检测
...//数据记录
...
return text[position];
}
};
就会带来代码膨胀、编译时间等等各种问题。
正确的解决方式是实现operator[]一次,并使用其两次,如下
class TextBlock{
public:
const char & operator[](size_t position)const
{
...//边界检测
...//数据记录
...
return text[position];
}
char & operator[](size_t position)
{
return
const_cast<char&>( //去掉返回的const属性
static_cast<const TextBlock&>(*this)[position]) ; //加上const,调用上述函数
}
};
总结:
- 某些东西声明为const可以帮助编译器侦测错误。const可被用于任何作用域内的对象、函数参数 、函数返回值类型、成员函数本体。
- 编译器强制实行bitwise,,但在我们使用时应该使用logical const。
- const 和non-const有着等价操作时,可以用non-const函数调用const,避免重复。