C++代码优化(1):条款1~4

 "不要烟火不要星光,只要问问你内心的想法。"


        本栏仅仅 由对《Effictive C++》其中的一系列条款,掺杂着自己一些愚钝的理解而写的。

---前言

 条款01:

尽量以const、enum、inline 替换 #define

在谈及上述好几个关键字 与define宏定义的关系,我们不得不谈谈他们各自 在编码时的使用情况。

(1)小试牛刀地回顾

①const 与 enum 

#include<iostream>
using namespace std;

enum Color
{
	Red,
	Blow,
	Black
};

int main()
{
	const int a = 10;
	cout << "a:" << a << endl;
	cout << "Red:" << Red << endl;

	//Red = 3;
	//a = 3;

	return 0;
}

 ②inline 与 define

上述的enum、const 或许我们都在C中经常看到。 inline是个什么东西呢?

inline是一种“用于实现”的关键字,而不是一种“用于声明”的关键字。

想想一个场景:

        一个函数其代码量短小,但是很多地方都会调用,也就是会被反复调用的函数,必然带来的是 main函数为其 反复开辟、销毁的栈帧损耗。 那么如何避免开辟栈帧了? 

        在C中提供了一种宏函数调用的方式!

int Add(int x, int y)
{
	return x + y;
}

#define ADD(x,y) ((x)+(y))

int main()
{
	int x = 4;
	int y = 5;
	cout << "普通函数加法的总和为:" << Add(1, 2) << endl;
	cout << "宏函数加法的总和为:" << ADD(1, 2) << endl;
	return 0;
}

它们的结果完完全全一样的,但是宏函数可以避免 栈空间反复的 开辟、销毁。 

 那么 宏函数就可以肆无忌惮地乱使用了吗? 答案是肯定不是! 否则也不会有inline

宏函数的缺陷 

1.没有检查类型

2.不支持调试

3.使用复杂 

当然前两个也无需多说。宏函数不支持调试的根本原因就在于,宏函数在预处理完之后已经完成了文本替换。

其次,你也可以明显察觉到 当上述代码定义宏函数的时候 需要 加上很多括号。

举个简单的例子:

也许C++正是看到了define宏函数存在的缺点,因此设计了一个新的关键字 inline;

并且还 不如像使用宏函数那么拘谨! 就当做一个普通的函数即可!

 (2)开胃菜

①错误信息与数据冗余

上面的一节,仅仅做了一些要理解本条款的预备知识。

书上也说,老师也说,预处理阶段要经历的几个步骤; 但是没什么感性的认识!我们怎么知道这些是什么个东东???

去注释、宏替换、条件编译、头文件展开。

  

尽管const、enum、宏定义 都作为一个语言常量! 都保障其数值的 常量性。

但:

 ①如果 这个宏定义的数值(DEBUG) 在程序运行的 获得一个错误信息却得到一个"2"! 而非const 定义下的 DEBUG。如果这份代码不是你写的! 你肯定对这个数字"2"的来源无从下手! 因为其根本没有进入 记号表。

 ②此外,编译器看到 #define DEBUG 只会盲目地进行替换(它可不会太智能)。从而导致一份代码里 出现多份"2".相反 如果改用const int DEBUG 则只需要有一份记录即可。

②封装性

C++引入类这个概念,对封装的要求 一定比C语言高得多。

#define 与 const 

注:

枚举与const:

 

上述不懂? 那就记住!

1. 对于单纯常量,最好以const对象 或者 enums替换 define。

2.形似函数的宏,最好用inline 替代。


条款03:

尽可能使用const 

(1)小试牛刀地回顾

const是一个非常神奇 且用途十分广泛的关键字。它既可以告诉 编译器,你是会否应该让某个变量保持不变,也是告诉程序员,哪个变量由const 修饰 是不变的!

恐怕各位初学const的时候 尤其是到指针那章节! 一定会被这个const 弄得晕头转向。

char greetring[] = "Hello";             //non-const pointer,non-const data
char* p1 = greetring;                   //non-const pointer,const data
const char* p2 = greetring;             //const pointer,non-const data
char* const p3 = greetring;             //non-const pointer,const data
const char* const p4 = greetring;       //const pointer,const data
                                                                ---样例取自《Effective C++》

分清 const 到底是修饰的是 指针自身 还是指针所指向的物

        就是区分 const是在 " * " 的左边还是右边;

 (2)开胃菜

①const 与 迭代器 

 在C++中,STL大量使用迭代器(类似于指针一样,但不是真的指针)。

可能接触过STL容器的,一定更为头疼里面const的 “漫天使用”。

#include<vector>
#include<iostream>
using namespace std;
int main()
{
	vector<int> v1;
	v1.push_back(1);
	vector<int> v2;
	v1.push_back(1);

	//1.如果你希望得到一个迭代器
	//指向的对象是 可以被修改 但是自己不能指向不同的东西
	//你就需要一个 T* const
	vector<int>::iterator it = v1.begin();
	cout << ++(*it) << endl;

	//2.如果你希望得到一个迭代器
	// 指向的对象是 不能被修改的!
	//你就需要 const T*
	vector<int>::const_iterator cit = v1.cbegin(); //v1.begin()
	cout << ++(*cit) << endl;

	return 0;
}

注:

const迭代器 并不是给 迭代器+const!

②const 与 函数返回值

有些函数 的声明 会让函数 就返回一个const 对象! 但是可能 有人会觉得多此一举。 

多的也不用多说了。 很多时候 我们或许将“ == ” 写成了 " = " 很明显,如果你对该对象施加了const 那么很容易 编译器就会给我们立马 找出错误点! 而不是让我们盯着眼花缭乱的 代码胡乱翻找。 

③const对象、非const对象 与 成员函数 

我们先来看看以下代码;

#include<string>
#include<iostream>
using namepace std;
class TextBlock
{
public:
	TextBlock(const char* str)
		:_text(str)
	{}
	//...
	const char& operator[](size_t pos)const  //这两个是operator 函数重载 
	{
		return _text[pos];
	}

	char& operator[](size_t pos)
	{
		return _text[pos];
	}
private:
	string _text;
};

int main()
{
	TextBlock tb("Hello");
	cout << tb[1] << endl;


	const TextBlock ctb("World");
	cout << ctb[1] << endl;

	return 0;
}

        上述的境况 仅仅是operator[] 的返回类型以致,"由const 版 之 operator[] 返回的" const char& 进行赋值动作。

        同样,对于non-const成员 可以调用const成员函数 ,这显然是可以进容忍的! 上述问题的根本在于,const成员 去调用了 non-const成员函数。因此,const成员函数 

当然! operator[]的返回值 一定是char& (reference to char) 而非 'char' (这样就变成了右值),否则 也无法通过编译。

④如何定义成员函数 const属性? 

        1.对于成员变量的const属性 那即是 —— 不能修改const修饰的成员变量,反之 non-const的成员变量 可以轻易地更改。

        2.因此一派(bitwise const)认为: 成员函数的const属性 也和成员变量的const属性一样。

不能对 const成员函数里的 任何成员变量做任何修改! 显然,这很符合 毒地const常良性的定义。

但事实是这样吗? 我们看看下面代码;

#include<iostream>
using namespace std;
class TextBlock
{
public:
	TextBlock(char* str)
		:_ptext(str)
	{}
	//...
	char& operator[](size_t pos) const
	{
		return _ptext[pos];
	}
	void Print()const
	{
		cout << _ptext << endl;
	}
private:
	char* _ptext;
};

int main()
{
	char ch[] = "hello";
	const TextBlock ctb(ch);
	char* pc = &ctb[0];
	cout << "更改前-----------" << endl;
	ctb.Print();
	*pc = 'W';
	cout << "更改后-----------" << endl;
	ctb.Print();
	return 0;
}

不仅如此,难道const成员函数里的 成员变量任何改变都不能容忍吗?

logical constness就拥护:

一个const成员函数可以修改它 所处理对象内的某些bit!但 这种情况 只允许出现在 检测不出来才得以如此。

mutable(C++11 后引入的新宠)

class TextBlock
{
public:
	TextBlock(char* str)
				:_ptext(str)
			{}
	//...
	size_t lenth()const
	{
		if (!lenghValid)
		{
			_textlength = strlen(_ptext);
			lenghValid = true;
		}

		return _textlength;
	}
private:
	char* _ptext;
	mutable size_t _textlength; //释放 bitwise constness
	mutable bool lenghValid;
};

⑤const 和 non-const避免重复 

上面对于bitwise constness 一刀切的问题,mutable 可以解决。 但不能解决所有问题。

回到最初的问题;

C++11提供了一个新的方法,即:常量性转除(cast away constness);

然而事实上,使用 转型(casting) 是一个糟糕的想法! 

但是本例 中,仅仅是为了解决 代码冗余 给人带来的不快。

class TextBlocK
{
public:
	const char& operator[](size_t pos)const
	{
		//..

		return text[pos];
	}

	char& operator[](size_t pos)
	{
		return  //最外层是 去掉const属性
			const_cast<char&>(
				//加上const 属性 去调用 const版本的 operator[] 更加明确!
				static_cast<const TextBlocK&>(*this)[pos] //调用[]
				);
	}
private:
	string text;
};

        忽然,你拍一脑袋! 为什么不能让const const operator[] 版本去 复用 非const operator[]?

        显然! 你肯定不能被容忍这样干! 因为const成员函数 承诺不会改变 其所处理对象的任何状态,非const成员函数 则不会保证这样。

        同样,如果你非要这样干, 那不妨给const对象 const_cast 给它释放掉const 的属性。

当然 这等于 “脱了裤子 ,打屁”。

        想说的也就是,非const对象 可以 去调用非const成员函数 也可以 去调用 const成员函数,因为 非const对象可以对自己的 状态 选择不做任何修改。

上述不懂?那就记住!

 1.将某些变量声明为const 可以让编译器帮助你 检测错误。 其次 const可以施加于 作用域的任何对象,函数参数、函数返回的类型、成员函数自己身上。

 2.编译器多半强制实施 bitwise constness,mutable有时可为你提供一种解决方法。

 3.当const 与 non-const成员函数 有着实质性的等价实现时,令non-const成员函数 调用 const成员函数,未尝不是一个值得考虑的选择。


条款4:

确定对象被使用前 已先被初始化。 

(1)小试牛刀地回顾

class Point
{
	int x, y;
	//.....
};

int main()
{
	Point p;
	return 0;
}

因此,处理这些的最佳办法就是: 永远在使用这些对象、使用这些内置类型之前 进行初始化。 

(2)开胃菜 

①区分 初始化 和 赋值 

这里提个问题:类的构造函数 是赋值 还是 初始化?

         C++规定,对象的成员变量初始化的动作发生在,进入构造函数本体之前。所以在ABEntry进入构造函数之前 其中等待Name、Address(自定义类型)已经 初始化好了。构造函数调用时,已经是对ABEntry 进行赋值 动作了。  但可以看到, 构造函数对Age(内置类型)就并没有那么友好, 里面是随机值!并没对其进行初始化! 

        对上述ABEntry的最佳的写法时,利用初始化列表(member initialzation list);从而替换 赋值的那些 琐碎的操作。 

   

        显然,第二个版本避免了 由default对它进行重新赋值的多余动作。而那些实参 都被初始化列表拿去作为 实参 进行构造拷贝了。 当然Name \ Address 分别为 name\address的 初值copy。

        当然,也有无参的构造函数(默认构造)。都受便 于编译器对于自定义类型的处理方式:

②初始化列表 与 const、reference 

如果没有缺省参数,我们对于const 或者 reference 应该怎么初始化?  难道任让它们各自飞翔?

        对于内置类型,可能赋值、初始化的 代价不是很大! 但是 例如const 、 reference 它们一定就需要初值,而不是赋值!

         最简单的做法就是,所有成员变量的初始化 都交由初始化列表! "这样做有时候绝对必要,且往往比赋值要高 "

        但是,也有classes 拥有多个构造函数和多个成员变量,那样每个构造函数都 写初始化列表,未免有些 重复、无聊。 因此 ,对于 “初始化 与 赋值等价”的成员而言,也可以 给它们的赋值操作 抽象成一个 函数(通常为 private),往往使得 代码更加美观。 

       构造函数 无非只是一种 “伪初始化”,而不是初始化列表的“真初始化”那样 更可取。

③初始化列表 与  static

(这里讨论的问题,更加倾向于 工程性的情况,可以进行选读)

static声明的对象,其寿命是随程序的!

我们往往称 一个 在函数内的static 对象为 local static 其他static对象为 non-local  static 

 如何解决 “定义于不同编译单元内的 non-local static对象 无明确定义次序”?

简单来说,就是两个源码文件,各自内部都有一个 non-local static 的对象。 如果其中一个源码文件中的 non-local static的初始化 需要借助另外一个文件里的 non-local static对象, 但事实是,你根本不知道 要借助的该对象 是否已经初始化完毕了!  毕竟C++并没有该 明确的次序定义!

我们来看看下面代码;

class FileSystem
{
public:
	//...
	size_t numDisks() const; //统计Disks众多成员
	//...
};

extern FileSystem tfs; //这是一个全局变量 预备给客户使用的!


class Directory
{
public:
	Directory()
	{
		size_t disks = tfs.numDisks();
	}
};

Directory tempDir;

 这样不管是 tempDir依赖tfs  还是之后tempDir被其他依赖。 都会在用其被调用的地方,首先被初始化。并且,不会再经历、调用non-local static 对象的 “仿真函数”,也绝不会引发 构造、析构带来的性能损耗。

上述不懂?那就记住:
 1.为内置类型对对象进行手工初始化,因为C++不保证初始化他们。

 2.任何类的 成员变量初始化。最好都使用初始化列表。而不要在构造函数内就 进行赋值。并且,初始化列表里的排序,最好与变量声明的次序一致。

 3.为免除"跨编译单元之初始化次序"问题,请以local static 对象 替代 non-local static 对象。


本篇也就到此为止。 此外,“每个条款的最后模块都也是该本书最后的总结

希望对读者能有所帮助。感谢你的阅读~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值