Effective C++阅读笔记(一)

本文介绍了C++编程的几个核心原则,包括将其视为语言联邦、使用const和inline替代宏定义、充分利用const特性以及确保对象初始化等方面的最佳实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

01:C++是一个语言联邦

可以将C++视为四种主要的此语言:

  • C语言
  • Objedct-Oriented C++:继承,封装,堕胎,虚函数(动态绑定)
  • Template C++:泛型编程
  • STL:容器、迭代器、算法、函数对象进行规约与协调

守则依据使用C++的哪一部分来进行变化。

02:尽量使用const、enum、inline替换#define

  • 单纯常量,使用const对象或enum对象替换#defines
  • 形似函数的宏,使用inline来替换#define

使用#define的不足:

常数问题

#define PI 3.14

记号 PI 可能被预处理器给删除,导致编译器没能将其送入符号表;同时,报错时可能会提到3.14而不是PI。

专属常量

class Test {
	private:
	#define T 21213;
}

这时,我们仍可以在其他地方使用T,而不是我们希望的那样,称为一个类专属的数值。

函数

#define CALL_MAX(a,b) f((a)>(b)?(a):(b))

首先,变量必须加括号;其次如果使用CALL_MAX(a++,b),那么a增加的次数和b有关,因为它会将表达式中的a全部替换成a++。
可以使用template inline,并通过这种方式确保类型安全。

template<typename T>
inline void callMax(const T&a, const T& b) { f(a>b?a:b); }

通过引用,防止传入a++的情况,也不用担心赋值多次;同时可以用作一个private函数。

特殊情况

  1. 常量指针
    因为常量定义常被放在头文件中,所有有必要讲指针(而不是其所指向的东西)声明成const
  2. class
    为了保证常量只有一份实体,需要声明为static成员
    in-class初值设定只允许对整数常量进行,如果编译器不支持或初始化其他变量,那么就将定义放在实现文件内。
  3. 编译期间需要常量值(enum hack)
    通过enum,让NumTurns成为5的一个记号名称
    class GamePlay {
       private:
       	enum {NumTurns = 5};
       	int scores[NumTurns];
       	...
    }
    
    enum hack更像#define,取enum地址不合法;无法让指针指向某个整数常量;不会导致没有必要的内存分配。

03:尽可能使用const

  • 将参数、返回类型、成员函数加上cosnt可以帮助编译器识别错误的用法
  • 编译器使用bitwise constness,但编写程序时要使用conceptual constness
  • 使用non-const版本表用const版本可以避免代码重复

const用法

  • 放在星号左边,表示指向的对象是常量;放在星号右边,表示指针式常量。(可以讲将int*理解成一个类型)。
  • const可以放在类型之前也可以放在类型之后,下面两个语句等价。
    int const * p;
    const int *p;
    
  • 当与迭代器一同使用的时候,const iterator表示迭代器指向的东西不可改变,例如不能使用自增操作;而如果想表示指向的对象的内容不可改变,则需要使用const_iterator
  • const可以与函数返回值,各个参数,函数自身(如果是成员函数)产生关联

比如对乘法操作进行重载,如果返回的不是一个const类型,那么可能会出现:a * b = c的情况,这是是a* b返回的那个临时对象赋值成c。

const成员函数

const作用于成员函数是为了确认成员函数可以作用于const对象。这样做有两个好处:

  • 使class接口已于理解,可以得知哪个函数可以改动对象内容而哪个函数不行。
  • 使“操作const”对象成为可能。

两个函数常量性(constness)不同,那么可以重载。比如对于[]操作,可以返回一个常量引用,也可以返回一个普通引用。
这是为了解决类似array[i] = obj;的操作,如果不返回引用,则实际上是对临时变量修改;如果返回常量引用,则无法对其修改。

成员函数是const有两种理解:

  • bitwise constness:不改变对象任何成员变量,不改变其中任何一个bit。这也是c++对constness的定义。所以const成员函数不能改变任何non-const成员变量。(当然也不是完全不能修改,比如利用指针)

  • logical constness:成员函数可以处理对象内某些bits,但只有用户侦测不出的情况下才可以。比如对privte的变量进行修改。这时候要将需要修改的变量前加上mutable,表示这些变量总是可以被更改,即使在const成员函数内部。

    class A {
    	private:
    		mutable bool isEmpty;
    		int length;
    	public:
    		bool is_empty() const {
    			if (!length) {
    				isEmpty = true;
    			} else {
    				isEmpty = false;
    			}
    			return isEmpty;
    		}
    };
    

在const 和 non-const 成员函数中避免重复

比如对[]重载,可能返回一个正常引用,也可能返回一个常量引用。
但其中要进行一些边界检查,进行数据访问日志的记录,检验数据完整性等。如果两个函数都要进行同样的操作,就造成了代码重复,所以考虑一个调用另一个。

一般是使用non-const成员函数调用const成员函数。这里先将this转换成常量,于是会调用常量版本的[],随后去掉const。

反过来是不行的,const保证不改变成员的逻辑状态,但是如果const调用non-const,non-const函数可能会改变成员的逻辑状态。

const T& operator[] (size_t pos) const {
	...
	...
	return array[pos];
}
T& operator[] (size_t pos) {
	return const_cast<T&> {
		static_cast<const T&>(*this)[pos];
	)
}

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

  • 对内置对象类型(int,char等)手工初始化,C++不保证初始化他们
  • 构造函数最好使用成员初始值列(member initialization list),而不要在构造函数中使用赋值操作。构造函数过多,可以抽出来共同的、影响较小的变量进行赋值。初始值成员变量排列次序应该与在class中的声明次序相同。
  • 为了避免“跨编译单元初始化”,使用local static对象替换non-local static对象

static对象指的对象的寿命从被构造出来开始到程序结束为止,包括global对象,定义域namespace的对象,classes内、函数内、及file作用域内被声明为static的对象。

函数内的static对象成为local static对象,其他static对象称为non-local static对象。

编译单元指的产出单一的目标文件的源码,时单一源码文件加上其所含的头文件。

两个源码中文件中,每一个都喊一个non-local static对象,假设为A和B。如果B的初始化中使用了A,那么可能会出现问题,因为C++并没有对non-local static对象的初始化顺序做出规定,并且也无法做出规定。

可以将non-static对象搬到函数内,因为函数中的static对象会在函数被调用期间,首次遇到该对象定义式的时候被初始化。所以如果用“函数调用”替换“直接访问non-local static”对象,那么可以保证reference指向一个经过初始化的变量。

class A {...};
A& getA() {
	static A a;
	return a;
}
class B {
	static A a;
	B() {
		a = getA();
	}
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值