c++编程提升(一)

最近在阅读<<effective C++>>这本书, 这本书在我刚开始学习编程的时候, 我就听过这本书的大名。 不管在任何论坛和前辈的推荐中, 关于进阶C++, 总会有这本书的影子。

本章浅谈下条款1到条款4。
很好奇为什么这本书要以条款为标题。但是确实新颖, 分层很清晰。也算是这本书一大特色,挺喜欢的。

条款1到条款4重点是在介绍const的重要性, 从各个方面引入,比较, 说const各大优点,甚至在设计的使用的优点等。

1、explicit关键字

我们来看一个例子:

#include<iostream>
using namespace std;

class A {

public:	
	A(int a = 0, int b = 0) 
		:_a(a)
		,_b(b)
	{

	}

	void Print() {
		cout << _a << ' ' << _b << endl;
	}
private:
	int _a;
	int _b;
};

void fun(A object) {
	object.Print();
}

int main() {

	A a;

	fun(10);

	return 0;
}

执行结果: 10 0

fun函数的形参是一个A类, 但fun(10)确实可以执行的, 这是因为编译器优化存在隐式类型转化。 这种结果是无法预料的, 我想我们对我们的程序每一步的执行流程都是想了解的。 因此我们可以加关键字explict, 它的作用是来防止隐式类型转化带来一些问题。

	explicit A(int a = 0, int b = 0) 
		:_a(a)
		,_b(b)
	{

	}

当我们在构造函数前面加explicit, 我们发现刚才fun(10)出现问题了。

下面借鉴原话:explicit进制编译器执行非预期(往往不被期望)的类型转化。除非我有一个好理由允许构造函数被用于隐式类型转化,否则我会把它声明为explicit。我鼓励你遵循相同的政策。

2、使用const, enum, inline 替换 #define

在C语言中,我们定义一个常量,我们会用到#define, 对于define定义的常量我们都知道, 在预处理阶段进行替换。它存在很大的问题。
1、没有类型检查。
2、开发中不易修改程序。

书中强烈建议我们使用const, enmu来替换掉define。

1、const定义的常变量是有类型检查的, 而且使用和常量一样。enmu枚举同样!

2、class的专属常量
define定义的常量, 它是全局性的, 不会归于一个类私用的常量的。
但是const 和 enmu就不一样了。

class A {
public:
 	A() {}
private:
	static const int numbers = 5;
	int arr[numbers];
};

[]里面只能为常量, 上面的写法是成立的, 而且numbers这个常量是所有A类私有的。

class A {

public:	
	 

private:
	enum { numbers = 5 };
	int arr[numbers];
};

上述的写法也是可以的, 枚举定义的常量numbers, 而且是这个类私有的。

对于enmu来说,相对于const定义的常量, 它更像define, 定义的常量没有地址。
如果你不想让别人知道const定义的常变量的地址, 就可以使用enmu。

3、inline 替换 宏函数
define也是可以定义宏函数, 但是也是替换,它存在很多缺点:
1、没有类型检查, 语法分析。
2、对于参数需要加括号, 不然很容易出现问题。(这点大家有目共睹)

inline内联函数, 他会给我们编译器一个建议, 最终决定权在编译器手里面,
如果展开后,效率高,会在调用出展开, 如果效果不高, 就不会展开。
(这个主要适合代码量小, 减少函数栈的开销从而提升效率!)
由于会在调用出展开原代码,因此它会在编译阶段对于变量进行类型检查 和 语法检查。 具有安全可靠性。

总结一下: 对于定义常量cosnt, enmu 代替 define。
对于定义函数inline 代替 define。

3、const其他用法

1、这也是面试常考的点。

int a = 10;
const int * p = &a;
int * const p1 = &a;
int const * p2 = &a;
const int * const p3 = &a;

指针p的指向可以改变, 但是指向的内容不可以改变。
指针p1的指向不可以改变, 但是指向的内容可以改变。
指针p2的指向可以改变, 但是指向的内容不可以改变。
指针p3的指向和指向的内容都不可以改变。

2、STL容器中的迭代器

std::vector<int> vec;
std::vector<int>::iterator iter = vec.begin();
std::vector<int>::const_iterator citer = vec.cbegin(); 

iter迭代器对指向的内容发生改变, citer迭代器对指向的内容不能发生改变。
关键在于cbegin()函数返回的const引用。

3、类中const成员函数

class A {

public:

	const char& operator[](size_t pos) const {
		return text[pos];
	}

	char& operator[](size_t pos) {
		return text[pos];
	}
private:
	char text[10];
};

上面有两个operaot[]函数, 是为不同类型对象准备。
对于const A的对象, 它的调用是不会修改类里面的test数组内容的。
因此我们在它的返回值返回const char&。
这是一种规范,如果返回得是char&, 那么就乱套了。

4、const和non-const函数代码复用

class A {

public:

	const char& operator[](size_t pos) const {
		//操作1
		//操作2
		//操作3
		return text[pos];
	}

	char& operator[](size_t pos) {
		//操作1
		//操作2
		//操作3
		return text[pos];
	}
private:
	char text[10];
};

const和non-const函数的实现是一样的, 只是返回值不同而已, 如果两个都写,就相当于写了两份代码。如果函数的操作非常非常多呢?

我们采用代码复用, non-const复用const函数的代码

class A {

public:

	const char& operator[](size_t pos) const {
		//操作1
		//操作2
		//操作3
		return text[pos];
	}

	char& operator[](size_t pos) {

		return 
		   const_cast<char&>(
			static_cast<const A&>(*this)
			 [pos]
		);
	}
private:
	char text[10];
};

我们只需要在non-const函数中写一个返回值就行了。

*this是对象本身, 用static_cast将对象本身添加为const属性, 然后调用eperator[], 它会自动调用const的eperator[]函数, 然后返回一个const char&类型, 我们再用const_cast去掉常量性质把它变成变量,然后返回。

从而达到代码复用的情况!

5、初始化问题

1、 对于变量声明一定要初始化,
不管是什么类型,自定义好,内置类型也罢,在c++编程中鼓励变量声明要初始化。(尤其是指针!)(因为你不知道不初始化的变量是指向什么鬼地方。)

2、对于类中成员变量的初始化
强力建议使用初始化列表进行初始化, 不管内置类型还是自定义类型。
对于内置类型:初始化列表没有什么区别。
对于部分自定类类型: 初始化可以提高效率。

举例:

class A {

public:
	A(const string& name, const string& addrs, const string& phones,
		const int id)
	{
		_name = name;
		_addrs = addrs;
		_phones = phones;
		_id = id;
	}
	
private:
	string _name;
	string _addrs;
	string _phones;
	int _id;
};

对于_name、 _addrs、 _phones string对象上述的赋值调用构造函数 和 赋值运算符重载函数 两步 操作完成。

在构造函数时候,首先会自动调用成员对象类的构造函数。
当构造函数完毕后,进行赋值, 会调用类的赋值运算符重载函数。

class A {

public:
	A(const string& name, const string& addrs, const string& phones,
		const int id)
		:_name(name)
		,_addrs(addrs)
		,_phones(phones)
		,_id(id)
	{

	}
	
private:
	string _name;
	string _addrs;
	string _phones;
	int _id;
};

当我们使用初始化列表, 对于类成员变量只会调用一个拷贝构造函数就完成初始化了。 效率提升了很多。

但对于_id内置类型, 好像没有什么提升, 但是为了统一规范, 还是建议一起使用初始化解决初始化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值