EffectiveC++ 第1章 让自己习惯C++
我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的“可能比较准确”的「翻译」。
Chapter 1 让自己习惯C++
条款 1 :视 C++为一个语言联邦 (P41 )
c++其实可以视为有四个部分:
- C (C++实际上以C为基础)
- Object-Oriented C++ (C++面向对象)
- Template C++ (C++泛型编程)
- STL (template程序库)
条款 2:尽量以 const ,enum , inline 替换 #define ( P43 )
标题可翻译为“宁可以编译器替换预处理器”
首先我们强调#define有时会带来很多麻烦。
当你做出这样的事情: #define VAR 1.653
记号名称VAR或许从未被编译器看见,或许在编译期开始处理源码之前它就被预处理器移走了。于是VAR记号可能没有进入记号表(symbol table)内。重点来了,当你运用此「常量」但是却获得了一个编译错误信息,可能会有这样的困惑:这个错误信息也许仅提及VAR的值1.63而没有关于“VAR”的任何提示。一旦这个VAR宏被定义在一个你不知道的头文件内,你将对“1.63是哪来的”毫无概念,于是会为追踪它而浪费时间。
解决之道是以一个真正的常量替换上述的宏(#define)
const double Var = 1.63; //习惯上const变量名开头字母大写
作为语言常量,Var肯定会被编译器看到,从而进入记号表内。
考虑常量时,有一种情况值得提一下。
常量指针(const pointers)
常量定义式通常放在头文件内(以便被不同源码用到),因此有必要将指针声明为const:
注意,我们平常说的
const xx* = xx
是指xx*指针不能对所指物xx进行变动。而这里所说的“将指针声明为const”是指此指针本身的地址值不能有变动
例如在header里定义一个常量char*字符串,必须写“const”两次:
const char* const authorName = "Shit"; //后面的条款会提到为啥这么写
Specially: class里的常量很多时候声明为static const xxx (至多有一份实体) 然而这只是个声明
对于class专属static常量,只有声明而没有定义式,要想取此常量地址的话,则必须有定义式,可以这么做:
假设你是在头文件的class Num里定义了静态常量static const int Num = 5,则可以在实现文件中提供此常量的定义:
const int Num::Num; 因为在头文件定声明时已经赋了初值,所以定义时不用赋值
值得注意的是,有些编译器不支持声明期间赋值,你可以在定义式中赋初值 (事实上,比较鼓励这种做法)
如果你的编译器不允许声明期间赋初值,而class里又有声明数组大小这种操作,那么可以用enum枚举类型代替:
Class GamePlayer{
private:
enum { NumTurns = 5 };
int scores[NumTurns];
……
}
事实上,enum成员不能被取到地址
另外,会有长得像函数的#define宏,它们甚至有参数:比如比较两个数哪个大。然而很多时候它们被展开后会带来很多料想不到的麻烦,这时最好采用template inline函数:
template <typename T>
inline void callWithMax(const T& a,const T& b)
{
f(a>b?a:b);
}
条款 3: 尽可能使用 const
const语法变化多端,但并不莫测高深。
char greeting[] = "hello";
char* p = greeting; //non-const pointer,non-const data
const char* p = greeting; //non-const pointer,const data
char* const p = greeting; //const pointer,non-const data
const char* const p = greeting; //const pointer,const data
如果const出现在星号左侧,表示被指物是常量,在右边表示指针自身是常量。若在左侧就不能改变被指物的值,在右侧就不能改变指针指向地址的值(比如将一个const数组指针指向的首地址自增1是不可能的)
函数声明式返回值为const的话,你可以很好应对一种情况:
if(返回值 = xxx)
在这里,你可能将==
写成了=
。
由于你的失误将比较操作变成了赋值操作,这对于一个const返回值是会报错的!所以这很好地在编译期阻止了错误。
在class成员函数结尾加上const表明不允许此函数改动任何成员变量
如果某些成员函数即使在const成员函数内也可能会因为具体实现被修改,可以使用mutable关键字
public:
…
std::size_t length() const;
private:
char* pText;
mutable std::size_t textLength;
mutable bool lengthIsValid;
std::size_t CTextBlock::length() const
{
if(!lengthIsValid){
textLength = std::strlen(pText); //现在你可以在const函数里修改成员值了
lengthIsValid = true; //现在你可以在const函数里修改成员值了
}
return textLength;
}
条款 4: 确定对象被使用前已被初始化
记住,在c++中,读取未初始化的值会导致不明确的行为,最佳操作是:在使用对象之前将它初始化。
对于内置类型,最好手工初始化:
例如: int x = 0;
const char* text = “A C-style string.”;
double d;
std::cin>>d; 以读取input stream的方式初始化
对于内置类型以外的东西,责任交给构造函数身上:将每个成员初始化。
注意,假设有一个构造函数是这样的:
XXX(string name, int age){
this->name = name;
this->age = age;
}
//这种叫做赋值,不叫初始化,不是最佳做法
初始化是指成员变量初始化动作发生在进入构造函数本体之前,时间更早(比进入默认构造函数时间还要早)
所以一种较佳写法是,使用成员初始化列表来初始化:
XXX(string name, int age):name(name),age(age){……}
(这样似乎效率更高)
用成员初始化列表效率更高的原因是,「赋值」的写法会让构造函数先赋变量以默认值,然后再赋用户指定的值。而成员初始化列表是在进入构造函数之前就将你指定的值赋给了变量
我们可以规定成员初始化都用初始化列表
Important:
若有成员变量是const或引用(references)的话,它们就一定需要列表初始化,而不能被赋值
为了避免忘记这种特殊情况,最好的做法是都用成员初始化列表,它往往比赋值更高效
但是有时这种操作会使代码看起来不太美观,你就可以偷偷将一些“赋值表现得和初始化一样好”的变量改用赋值操作
Specially:
初始化次序取决于成员变量声明次序,也就是说,即使你在初始化列表里打乱了顺序,依旧按照声明顺序进行初始化
现在来关心一下static类型:
一般来说,在函数内部的static对象都叫做”local-static”,以外的叫做”non-local-static”
Local-static对象只会在函数调用之后才会被构造出来
而non…是在main后构造出来
现假设有两个cpp源文件,各自的类里有一个non-local-static对象,其中一个的初始化动作会使用到另一个static,但是被使用的对象可能还未初始化,此时会发生错误
此时可以用一个小设计:将两个类各自的non-local-static搬到属于自己的函数里,将它们变成local-static,函数会返回对象的引用。此操作保证了调用函数时,此函数包含的static对象一定会被初始化。很棒的是,只要你还未调用此函数,就不会有构造与析构成本
For example:
class FileSystem{
……
FileSystem& tfs(){
static FIleSystem fs;
return fs;
}
……
};
//这种函数很简单,通常会成为inlining候选人
OVER
EffectiveC++ 第1章 让自己习惯C++的更多相关文章
- 《Effective C++》第1章 让自己习惯C++-读书笔记
章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...
- EffectiveC++ 第7章 模板与泛型编程
我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的"可能比较准确"的「翻译」. Chapter 7 模版与泛型编程 Templates and Gen ...
- Effective c++ 第一章 让自己习惯C++
条款 01:c++是一个语言联邦而不是一种单一的语言, 它包括: 1.C语言:没有模版.没有异常.没有重载…… 2.Object-Oriented C++:class.析构函数.构造函数.封装.继承. ...
- EffectiveC++ 第2章 构造/析构/赋值运算
我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的"可能比较准确"的「翻译」. Chapter 2 构造 / 析构 / 赋值 条款 05:了解C++ ...
- EffectiveC++ 第3章 资源管理
我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的"可能比较准确"的「翻译」. Chapter 3 资源管理 条款13: 以对象管理资源 有时即使你顺 ...
- EffectiveC++ 第4章 设计与声明
我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的"可能比较准确"的「翻译」. Chapter4 设计与声明 Designs and Declarat ...
- EffectiveC++ 第5章 实现
我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的"可能比较准确"的「翻译」. Chapter 5 实现 Implementations 适当提出属于 ...
- EffectiveC++ 第6章 继承与面向对象设计
我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的"可能比较准确"的「翻译」. Chapter 6 继承与面向对象设计 Inheritance and ...
- 《Effective C++》第5章 实现-读书笔记
章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...
随机推荐
- React Router路由传参方式总结
首先我们要知道一个前提,路由传递的参数我们可以通过props里面的属性来获取.只要组件是被<Router>组件的<component>定义和指派的,这个组件自然就有了props ...
- [LeetCode] 10. 正则表达式匹配
题目链接:https://leetcode-cn.com/problems/regular-expression-matching/ 题目描述: 给定一个字符串 (s) 和一个字符模式 (p).实现支 ...
- java遍历复杂json字符串获取想要的数据
https://blog.csdn.net/qq_34309663/article/details/80508125 java如何解析复杂的json数据关于json处理的包有好几个,比如jackson ...
- monkey日志管理
日志管理作用 Monkey日志管理是Monkey测试中非常重要的一个环节,通过日志管理分析,可以获取当前测试对象在测试过程中是否会发生异常,以及发生的概率,同时还可以获取对应的错误信息,帮助开发定位和 ...
- Mysql——Navicat 连接MySQL 8.0.11 出现2059错误
原因 mysql8 之前的版本中加密规则是mysql_native_password,而在mysql8之后,加密规则是caching_sha2_password 解决 更改加密规则: mysql -u ...
- Spring MVC 使用介绍(十一)—— 跨域与静态资源访问
一.跨域 服务端须在响应中添加相应响应头,从而允许跨域,具体可通过 public class CorsFilter extends OncePerRequestFilter { @Override p ...
- P4783 【模板】矩阵求逆
原题链接 https://www.luogu.org/problemnew/show/P4783 一道模板题,更重要的省选难度..... 题目要求的是一个n*n的逆矩阵,还要对大数取膜. 普通高中生: ...
- jzoj6101. 【GDOI2019模拟2019.4.2】Path
题目链接:https://jzoj.net/senior/#main/show/6101 记\(f_i\)为从\(i\)号点走到\(n\)号点所花天数的期望 那么根据\(m\)条边等可能的出现一条和一 ...
- ueditor 插件集成到 xadmin 中的相关操作
安装 点击这里下载源码包 在相关的虚拟环境下安装源码方式安装 切入解压后路径进行 python setup.py install 注册 安装成功按照普通app一般注册在 django 程序的app 中 ...
- Python【第二篇】运算符及优先级、数据类型及常用操作、深浅拷贝
一.运算符及优先级 Python 运算符(算术运算.比较运算.赋值运算.逻辑运算.成员运算) 1.算数运算符 运算符 描述 实例,a=20,b=10 + 加 a+b输出结果30 - 减 a-b输出结果 ...