1、视C++为语言联邦

C++集成自C,本身又有面向对象的特征,而且还可以使用Template,除此之外,还有很Diao的STL库,把C++视为四种语言的集合。

C

Object-Oriented C++

Template C++

STL

C++模板

作用:模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。

简而言之,通过共用代码,减小码字数量。

Code:

#include<iostream>
using namespace std;
 
const int MAXSIZE = 1000;
template<class T>
class Seqlist{
public:
	Seqlist() { length = 0; }
	Seqlist(const T a[], int n);
	int locate(T x);
	T get(int i);//获取第i个位置元素
private:
	T data[MAXSIZE];
	int length;
};

template<class T>
Seqlist<T>::Seqlist(const T a[], int n){
	if (n > MAXSIZE)throw"数组长度超过最大长度";
	for (int i = 0; i < n; i++)
	{
		data[i] = a[i];
	}
	length = n;
}

template<class T>
T Seqlist<T>::get(int i){
	if (i<1 || i>length)throw"位置非法";
	return data[i - 1];
}

template<class T>
int Seqlist<T>::locate(const T x){
	for (int i = 0; i < length; i++)
		if (x == data[i])
			return i + 1;
	return 0;//查找失败
}

int main(){
	int a[7] = { 1,2,3,4,5,6,7 };
	Seqlist<int>list(a, 7);
	int v = list.locate(5);
	cout << v << endl;
	int d = list.get(5);
	cout << d << endl;
}

内部实现:

《Effective C++》笔记——(1)让自己习惯C++

参考:https://blog.csdn.net/lianhunqianr1/article/details/79966911

2、尽量使用const、enum、inline替代#define

原因:预处理器不靠谱,使用编译器替代预处理器

1)const替代#define

好处:预处理器可能会有乱七八糟的操作,使用const编译器,不容易出错;const有作用域的限制

2)enum(这个似乎不怎么重要)

就的编译器不支持static成员在声明时获得初值,使用enum解决

3)inline

#define调用宏会出现问题

//define误用举例

//#define中要为所有实参加上小括号
#define MAX(a, b) f((a) > (b) ? (a) : (b))

int a = 5, b = 0;
MAX(++a, b) //a++调用2次
MAX(++a, b+10) //a++调用一次

解决

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

4)C++预处理器

定义:C++的预处理(Preprocess),继承自C语言,是指在C++程序源代码被编译之前,由预处理器(Preprocessor)对C++程序源代码进行的处理。

这个过程并不对程序的源代码进行解析,但它把源代分割或处理成为特定的符号用来支持宏调用。

常见操作:

1. 文件包含:#include 是一种最为常见的预处理,主要是做为文件的引用组合源程序正文。

2. 条件编译:#if,#ifndef,#ifdef,#endif,#undef等也是比较常见的预处理,主要是进,行编译时进行有选择的挑选,注释掉一些指定的代码,以达到版本控制、防止对文件重复包含的功能。

3. 布局控制:#progma,这也是我们应用预处理的一个重要方面,主要功能是为编译程序提供非常规的控制流信息。

4. 宏替换: #define,这是最常见的用法,它可以定义符号常量、函数功能、重新命名、字符串的拼接等各种功能。

参考:https://www.cnblogs.com/pmars/archive/2012/10/17/2727626.html

3、尽可能使用const

const是个很迷的东西……书上讲的有比较乱,这里按照const的使用场景,总结下

1)const修饰非成员变量

const修饰全局变量,不能改变

#include <iostream>
using namespace std;
 
const int a = 7;
int main()
{
	//const volatile int a = 7;
	int *p = (int *)(&a);
	*p = 8;
	cout << "a=" << a << endl;
	cout << "*p=" << *p;
	system("pause");
	return 0;
}


//运行报错,cost int不能修改

const修饰局部变量,运行结果显示const局部变量被修改了,但是在使用变量名输出时,编译器会出现一种类似宏定义的功能一样的行为,将变量名替换为初始值。可见,const局部变量并不能做到真正的不变,而是编译器对其进行了一些优化行为,这导致了const局部变量与真实值产生了不一致。那么,如果想获取修改后的const局部变量真实值,该怎么办呢?答案是使用volatile关键字。

#include <iostream>
using namespace std;
 
int main()
{
	//const volatile int a = 7;
	const int a = 7;
	int *p = (int *)(&a);
	*p = 8;
	cout << "a=" << a << endl;
	cout << "*p=" << *p;
	system("pause");
	return 0;
}

//a=7
//*p=8

2)const修饰指针

// 顶层const(top-level const): 指针本身是个常量
// 底层const(low-level const): 指针指向对象是一个常量

int a = 1;
int b = 2;
const int* p1 = &a;
int* const p2 = &a;

//p1的对象是一个const
//p2指针是一个const

3)const修饰函数参数

防止函数内部修改参数的原始对象,主要有三种情况:

1. 函数参数为值传递:值传递(pass-by-value)是传递一份参数的拷贝给函数,因此不论函数体代码如何运行,也只会修改拷贝而无法修改原始对象,这种情况不需要将参数声明为const。
2. 函数参数为指针:指针传递(pass-by-pointer)只会进行浅拷贝,拷贝一份指针给函数,而不会拷贝一份原始对象。因此,给指针参数加上顶层const可以防止指针指向被篡改,加上底层const可以防止指向对象被篡改。
3. 函数参数为引用:引用传递(pass-by-reference)有一个很重要的作用,由于引用就是对象的一个别名,因此不需要拷贝对象,减小了开销。这同时也导致可以通过修改引用直接修改原始对象(毕竟引用和原始对象其实是同一个东西),因此,大多数时候,推荐函数参数设置为pass-by-reference-to-const。给引用加上底层const,既可以减小拷贝开销,又可以防止修改底层所引用的对象。

4)const修饰函数返回值

有效防止因用户错误或者奇特的操作造成的意外

// 声明和定义
class Rational{...};
const Rational operator* (const Rational& lhs, const Rational&  rhs);

// 调用
Rational a,b,c;
(a*b)=c;
a=b=c;

5)const修饰成员函数

防止成员函数修改对象的内容

const_reference operator[]( size_type pos ) const;

上图是STL string的成员函数,可以看出,在函数返回值,函数参数,函数是否可以修改类对象三个地方都做出了const限定。

如果const成员函数想修改成员变量值,可以用mutable修饰目标成员变量。

如果一个类对象为const 对象,语义上说明该对象的值不可改变,因此该const对象只能调用const成员函数,因为非const成员函数不能保证不修改对象值,编译器会禁止这种会造成隐患的行为。

C++ const更加详细的介绍可以参考:https://blog.csdn.net/u011333734/article/details/81294043

4、确定对象使用前初始化

1)使用任何对象之前都要初始化;

2)成员变量的初始化发生在构造函数调用之前,如果在构造函数中初始化,其实是赋值操作,而非初始化,正确的初始化方式是使用初始化列表,注意变量的顺序;

ABEntry::ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>& phones)
    :theName(name),
    theAddress(address),
    thePhones(phones),
    numTimesConsulted(0)
{}

3)为避免“不同编译单元内定义的 non-local static 对象的初始化次序”,使用local static替代non-local static,具体操作是把static对象放在一个函数中

FileSystem & tfs(){
    Static FileSystem fs;
    Return fd;
}

不同编译单元内定义的 non-local static 对象的初始化次序(本来就很拗口,翻译得更加难受了):

《Effective C++》笔记——(1)让自己习惯C++

简而言之:有两个源代码,一个代码non-local static对象调用了另外一个的non-local static对象,不能保证对方已经初始化。

 

相关文章: