Effective<1>——让自己习惯c++

6 篇文章 1 订阅

<1>让自己习惯C++


条款01:视C++为一个语言联邦

今天的c++已经是个多重范型编程语言,一个同时支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式的语言。主要可以分为四个次语言:
1.C。C++仍是以C为基础。区块(blocks)、语句(statements)、预处理器(perprocessor)、内置数据类型(built-in data types)、数组(arrays)、指针(pointers)等。
2.Object-Oriented C++。C with Classes所诉求的:classes(包括构造函数和析构函数)、封装(encapsulation)、继承(inheritance)、多态(polymorphism)、virtual函数(动态绑定)等。
3.Template C++。泛型编程部分。template相关考虑和设计已经弥漫整个C++。
4.STL。STL是个template程序库。它对容器(containers)、迭代器(iterators)、算法(algorithms)以及函数对象(functionobjects)的规约有极佳的紧密配合与协调。
~C++高效编程守着视状况而变化,取决于你使用C++的哪一部分。

条款02:尽量以const、enum、inline替换#define

常量替换#define有两种特殊情况。
1.定义常量指针(constant pointers),由于常量定义式通常被放在头文件内(以便不同源码含入),因此有必要将指针(非指针指向之物)声明为const。若要定义一个常量的char*-based字符串,则有:
const char* const authorName="Scott Meyers";
或 const std::string authorName("Scott Meyers");
2.class专属常量。为了将常量的作用域(scope)限制与class内,你必须让他成为class的成员(member):
class GamePlayer{
private:
      static const int NumTurns=5;//常量声明式
      int scores[NumTurns];//使用该常量
      ...
};
然而所写的是NumTurns的生明式而非定义式。有些编译器需要另外提供定义式:
const int GamePlayer::NUmTurns;
同时旧式编译器也许不支持上述语法,不允许static成员在其声明式上获得初值。(此外in-class初值设定,也只允许对整数常量进行)则可将初值放入定义式中:
class CostEstimate{
private:
     static const double FudgeFactor;//static class 常量声明
     ...//位于头文件内
};
const double CostEstimate::FudgeFactor=1.35;//static class常量定义,位于实现文件内
当在class编译期间需要class常量值时,可以改用“the enum hack”补偿做法,其理论基础是“一个属于枚举类型(enumerated type)的数值可权充ints被使用:
class gamePlayer{
private:
      enum{NumTurns=5};
      int  scores[NumTurns];
      ...
}; 
请注意,我们无法利用#define创建一个class专属常量。
以及enum hack与#define更相似,获取一个const地址是合法的,而获取enum和#define的地址不合法。但是enum和#define不会导致非必要的内存分配。
最后就是,使用template inline函数的宏可以带来更大的效率:
template <typename T>
inline void callWithMax(const T&a,const T&b)
{
       f(a>b?a:b);
}
有了consts、enums、inlines,我们对预处理器(#define)的需求降低了,但#include仍是必须品,而#ifdef/#ifndef也继续扮演控制编译的角色。
~对于单纯常量,最好以const对象或enums替换#define
~对于形似函数的宏(macros),最好改用inline替换#define

条款03:尽可能使用const

const:允许你指定一个语义约束(一个“不该被改动”的对象),编译器会强制实施者项约束。
指针中,const出现在*左边,表示被指物是常量;出现在*右边,表示指针自身是常量。
void f1(const Widget* pw);//f1、f2是相同的
void f2(Widget const *pw);
STL迭代器,const声明表示,迭代器不得指向不同的东西,但所指的东西可以改变,如果希望他无法改变,就需要使用const_iterator:
std::vector<int> vec;
...
const std::vector<int>::iterator iter=vec.begin();
*iter=10;//没问题,改变的是iter所指之物
++iter;//错误,iter是const
std::vector<int>::const_iterator cIter=vec.begin();
*cIter=10;//错误,*cIter是const
++cIter;//没错,改变cIter
函数返回一个const值,可以防止其被客户改动:
class Rational{...};
const Tarional operator* (const Rational& lhs,const Tarional& rhs);

const函数内调用non-const函数,就改动了曾经做的承诺。所以我们必须使用const_cast将*this身上的const解放掉。
const operator[]完全做掉了non-const版本该做的一切,这种情况可以将const转除:
class Textblock{
public:
...
const char& operator[](std::size_t position)const
{
...
return text[position];
}
char& operator[](std::size_t position)//调用const op[]
{
return 
   const _cast<char&>(//将op[]返回值的const转除
      static_cast<const TextBlock&>(*this)[position]//为*this加上const,调用const op[]
      );
}
...
};
为了避免operator[]递归调用自己,我们必须明确指出调用的是const operator[],所以这里将*this从原始类型TextBlock&转型为const TextBlock&。
这里共有两次转型1.*this 添加const
2.const operator[]返回值中移除const


条款04:确定对象被使用前已先被初始化

通常使用C part of C++而且初始化可能招致运行期成本,那么久不保证发生初始化。一旦进入non-C parts of C++规则就有变化。这就好解释为什么array不保证其内容被初始化,而vector却需要保证。
最佳出来办法就是:永远在使用对象之前将他初始化。
int x=0;//初始化int
const char* text="A C-style string";//初始化指针
double d;
std::cin>>d;//以读取input stream的方式初始化
至于内置类型以外的东西,初始化责任在构造函数上,规则很简单:保证每一个构造函数都将对象的每一个成员初始化。
class PhoneNumber{...}
class ABEntry{
public:
     ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>&phone);
private:
   std::string theName;
   std::string theAddress;
   std::list<PhoneNumber> thePhones;
   int numTinesConsulted;
};
ABEntry::ABEntry(cosnt std::string& name,const std::string& address,const std::list<PhoneNumber>&phones)
{//赋值
     theName=name;
     theAddress=address;
     thePhones=phones;
     numTimesConsulted=0;
}
ABEntry::ABEntry(cosnt std::string& name,const std::string& address,const std::list<PhoneNumber>&phones)
://初始化
     theName(name);
     theAddress(address);
     thePhones(phones);
     numTimesConsulted(0);
{  }
不使用初始化而赋值,会使得成员对象在初始化发生时间更早,发生于这些default构造函数被调用之前,是的资源浪费。
ABEntry::ABEntry()//成员初值表
     :theName(),
      theAddress(),
      thePhones(),
      numTimesConsulted(0)
{}
规定总是在初值列中列出所有成员变量,以免还得记住那些成员变量可以无需初值。
当许多classes拥有多个构造函数时,每个构造函数都会有自己的成员初值列,就会导致很对重复的工作,这种情况下,可以合理的在初值列遗漏那些“赋值表现好的成员”,对他们改用赋值。

C++,有着十分固定的“成员初始化次序”(不会报错),base classes(基类)先于derived classes(派生类)。

在,问题涉及多个源码文件时,会发生使用某一non-local static对象的初始化使用了另一编译单元内的某个non-local static对象,他有可能尚未被初始化。
这时我们需要做的便是:将每个non-local static对象搬到自己的专属函数中(该对象在此函数内被声明为static)。喊句话说,non-local static 被local static替换了。
例:
class FileSystem{//来着你的程序库
public:
     ...
    std::size_t numDisks()const;//成员函数
     ...
};
extern Fileststem tfs;//预备给客户使用的对象

class Directory{//由程序库客户建立
public:
    directory(params);
    ...
};
Directory::Directory(params)
{
    ...
    std::size_t disks=tfs.numDisks();//使用tfs对象
    ...
}
Directory tempDir(params);//为临时文件做目录
除非tfs在tempDir之前初始化,否则tempDir会使用尚未出生的tfs。
改动:
class FileStstem{...}
FileSystem& tfs()//替换tfs对象
{//FileSystem class 中可能是个static.
    static FileSystem fs;//定义初始化一个local static对象
    return fs;//返回一个reference上述对象
}
class Directory{...};
Directory::Directory(params)
{
    ...
    std::size_t disks=tfs.numDisks();//使用tfs对象
    ...
}
Dierctory& tempDir()//替换tempDir对象
{
     static Directory td;
     return td;
}

~为内置型对象手工初始化,C++ 不保证他们初始化。
~构造函数最好使用成员初值列,而不要再构造函数本体内使用赋值。
~为免除"跨编译单元的初始化次序"问题,用local static对象替换non-local static。



















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值