C++是一个多重泛型编程语言,我们可以认为其由4个子语言组成:
- C: C++刚开始时是C with class
- Object-Oriented C++: C++面向对象的特性部分。如封装、继承、多态
- Template C++: 泛型编模板元编程
- STL:算法、容器、迭代器、函数对象等
- 对于常量,应该以const对象或者enum来代替#define
- 对于宏函数,最好使用inline来替代#define
#define是预处理,并不被当作语言的一部分,而是直接做文本替换,可能会带来意料外的错误。
如 #define printf cout会改变函数定义
形如
#define PI = 3.1415926 const double Pi = 3.1415926;
Pi会进入编译器的符号表,从而方便后续操作。
常量指针
定义常量指针,一般都是放在头文件的,因此最好声明为
const char* const name = "Tom";
// 更适用于string
const std::string name("Tom");
类中静态常量
struct A{
static const int Number = 0;
// 使用该常量
int scores[Number];
};
一般编译器都支持默认类型可以在类内声明时赋值,只要不对其进行取地址操作,就可以直接使用. 否则的话需要单独定义.
struct A{
static const int Number = 0;
// 使用该常量
int scores[Number];
};
/// in xx.cc文件
// 声明时赋过初值了.不可以再次赋值,否则会报错
const int A::Number;
enum hack
可以使用enum类型来定义常量,但是其特点是:
- 不可以取地址. 所以通过enum hack,不可以获得指向某个整数常量的指针.
用来解决某些编译器不允许类内初始化的问题
struct A{
enum {Number = 6;}
int score[Number];
};
define与inline
一个例子.define可能会造成意外结果
#define MAX(a, b) (a) > (b) ? (a) : (b); templateinline T findMax(const T &a, const T &b) { return a > b ? a : b; } int main() { int a = 1; int b = 1; int c = MAX(++a, b); cout << a << " " << b << " " << c << endl; a = 1, b = 1; c = findMax(++a, b); cout << a << " " << b << " " << c << endl; }
使用宏函数的例子中,++a被执行了两次
尽可能使用const- 将某些需要的类型声明为const可以帮助编译器检查错误用法。 const可以修饰作用域内的任意成员函数、对象类型、函数参数、返回类型。
- 编译器强制实行bitwise constness(不能在const函数内改变任何对象的指),但是不保证logical constness(可以修改指针指向的非对象内的指)。
- 当const重载的函数内容一致时,可以通过非const函数调用const版来精简代码。
迭代器是以指针为根据塑造出来的,其作用类似于一个T*的指针,因此直接作用于stl迭代器的const,类似于一个const指针,可以改变值但是不能改变指向. 想要一个指向const的迭代器,应该是const_iterator
int main() {
vector vec{1, 2, 3, 4};
const auto it1 = vec.begin();
(*it1) = 0;
// it1++;
vector::const_iterator it = vec.begin();
// (*it) = 1;
it++;
}
函数返回const–减少意外赋值错误
可以减少if(a + b = c)这样将==意外打成=的错误
其中a b是重载的类型
const Type operator*(const Type& a, const Type& b){
// ...
return a;
}
这样就无法对其返回类型赋值,减少意外操作.
const成员函数- 使成员函数可以作用于const对象
- 使接口便于理解
const可以重载成员函数
struct A {
string s;
A(string v) : s(v) {}
// 返回类型必须是引用,因为内置返回类型无法被修改
char &operator[](int i) {
return s[i];
}
const char &operator[](int i) const {
return s[i];
}
};
int main() {
A a("hello");
a[0] = 'H'; // 调用非const函数
cout << a.s << endl;
const A b("hello"); // 调用const函数
cout << b[0];
}
指针绕过const
const成员函数不可以修改对象内的任何non-static内容,但是如果const函数内,我们只修改了一个指针指向的值,而只有指针属于这个对象,其所指内容不属于对象,那么就可以通过const检查.但是不符合bitwise constness概念.如:
struct A {
char *s;
static int a;
A(char *v) : s(v) {}
void func() const {
s[1] = 'L';
a++; // 合法,是静态成员
}
};
int A::a = 0;
int main() {
char *c = new char[10];
memset(c, ' ', 10);
strcpy(c, "AAAA");
A a(c);
a.func();
cout << a.s;
// ALAA
}
mutable关键字可以使non-static对象改变
struct A {
char *s;
mutable int len;
int getL() const{
len = strlen(s);
return len;
}
};
在const和非const之间避免重复代码
利用const函数构造其非const的孪生版本
如上文,通过const可以重载函数,有时候两个函数之间代码完全一致,但是通过const重载会造成代码冗余,进而导致编译时间、维护难度等上升。
struct A {
string s;
A(string v) : s(v) {}
// 返回类型必须是引用,因为内置返回类型无法被修改
char &operator[](int i) {
// 可能的边界检查代码
// 可能的数据访问代码
// 可能的数据完整性校验
return s[i];
}
const char &operator[](int i) const {
// 可能的边界检查代码
// 可能的数据访问代码
// 可能的数据完整性校验
return s[i];
}
};
我们可以通过const_cast和static_cast来完成代码复用。
struct A {
string text;
A(string s) : text(s) {}
const char &operator[](int i) const {
// 可能的边界检查代码
// 可能的数据访问代码
// 可能的数据完整性校验
return text[i];
}
char &operator[](int i) {
return const_cast(static_cast(*this)[i]);
}
};
如上,我们通过static_cast
注意:在非const函数内调用const函数是安全的,在const函数内调用非const函数是不安全的!



