前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《Effective C++》读书笔记(1):让自己习惯C++

《Effective C++》读书笔记(1):让自己习惯C++

作者头像
C语言与CPP编程
发布2023-08-10 08:23:54
1640
发布2023-08-10 08:23:54
举报
文章被收录于专栏:c语言与cpp编程

条款1、视C++为一个语言联邦

最初,C++只是C加上一些面向对象特性。但随着它逐渐成熟,今天的C++已经是个多重范型语言。

可以将C++视为一个由4个次语言组成的联邦而非单一语言:1、C,说到底C++仍是以C为基础。2、object-oriented C++,包括封装、继承、多态等面向对象设计。3、template C++,泛型编程,衍生出模板元编程(在各个新标准中逐步完善)。4、STL,包括容器、迭代器、算法与函数对象。

每个次语言都有自己的规约,而C++高效编程守则视情况而变化。


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

#define可以用来定义一些变量、函数,但它只是一方面单纯的文本替换,并且没有任何类型检查,导致容易引起莫名其妙的问题,另一方面预处理后已经消失,编译链接过程中没有其符号信息,出问题时无法定位到它。

代码语言:javascript
复制
#define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))

int a=5,b=0;
CALL_WITH_MAX(++a,b);
CALL_WITH_MAX(++a,b+10);

即使上文的宏已经仔细地为所有参数添加小括号,仍然出现了问题:第一次调用中a被累加两次,第二次调用中a被累加一次。

更加可预测并且类型安全的写法是,对于定义常量,使用const对象(对于一系列常量,使用枚举或枚举类,而不是一系列#define),对于定义函数,使用模板内联函数。

代码语言:javascript
复制
template<typename T>
inline void call_with_max(const T& a, const T& b){
  f( (a>b)? a : b);
}
#不过inline目前主要指多重定义而非内联

条款3、尽可能使用const

const可被施加于各种作用域中的各种对象,告诉编译器某值应该不变。

代码语言:javascript
复制
char greeting[] = "Hello";
char* p1 = greeting;
const char* p2 = greeting;       //被指物不可修改
char* const p3 = greeting        //指针不可修改
const char* const p4 = greeting; //皆不可修改

真正威力强大的用法是面对函数声明时,const可以和函数返回值、各参数、成员函数自身产生关联。例如令函数返回const,往往可以降低因用户错误而造成的意外,又不至于放弃安全性和高效性。

代码语言:javascript
复制
class Rational{...};
const Rational operator*(const Rational& lhs,const Rational& rhs);
//上述写法可以避免用户写出 a*b = c

对于成员函数自身的const,编译器强制实施bitwise const,即强制不能修改任何成员变量。但实际上很多情况下我们需要的是logical const,即const成员函数也应该可以修改某些客户不可见的数据,这时可以用mutable成员变量来绕过const成员函数的限制。

例如对于一个文本块的对象而言,其内部很可能存在高速缓存;对于查询文本块长度这样的const操作,仍然需要更新高速缓存:

代码语言:javascript
复制
class TextBlock{
public:
  std::size_t length() const;
private:
  char *pText;
  mutable std::size_t text_length;
  mutable bool length_is_valid;
};

std::size_t TextBlock::length() const{
  if(!length_is_valid){
    text_length = std::strlen(pText);
    length_is_valid = true;
  }
  return text_length;
};

C++中两个函数如果只是常量性不同,也可以重载。当const成员函数与非const成员函数有着实质等价的实现时,为了避免冗余,可以令non-const版本调用const版本:

代码语言:javascript
复制
class TextBlock{
public:
  const char& operator[](std::size_t position) const{
    ...
  }
  char& operator[](std::size_t position){
    return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
  }
};
//后者首先将自身转换为const对象
//随后调用const成员函数,返回const引用
//最后转换为non-const引用

条款4、确定对象被使用前已先被初始化

C++中变量并非一定会进行初始化。最佳处理办法是:对于内置类型必须手动初始化,而对于用户定义的对象,在使用对象前将其初始化(责任落在构造函数上)。

构造函数包含成员初值列与函数体。1、最好使用成员初始列的初始化而非函数体内的赋值,否则对象会在成员初始列的步骤中进行默认初始化,再在赋值的过程中进行拷贝,成本增高。2、成员初始列的排列顺序应与在类中的声明次序一致,因为成员初始化顺序只与后者有关,前者若与后者不一致的话可能导致误解。

只剩最后一个难点:函数内的静态变量称为local静态变量,其他的都是non-local;而不同编译单元(一个编译单元指产出单一目标文件的源码们)内定义的non-local静态对象的初始化顺序并未规定。倘若存在这样的两个变量a和b,且b的初始化需要使用a,如果a尚未初始化就被b使用了,显然程序会出错。

解决方法也很简单:将每个non-local静态变量移到自己的专属函数内,这些函数返回该静态变量的引用,用户使用这些函数而非直接使用变量(类似单例模式)。至此,non-local静态变量被local静态变量取代。

代码语言:javascript
复制
class FileSystem{...};
FileSystem& tfs(){
  static FileSystem fs;
  return fs;
}
class Directory{...};
Directory::Directory(...){
    ...
   std::size_t disks = tfs().num_disks();
   ...
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-06-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 C语言与CPP编程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档