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;
}
内部实现:
参考: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 对象的初始化次序(本来就很拗口,翻译得更加难受了):
简而言之:有两个源代码,一个代码non-local static对象调用了另外一个的non-local static对象,不能保证对方已经初始化。