C++进阶 ——const关键字

01.尽量以const,enum,inline代替#define

也可以这么说“宁可以编译器替换预处理器”。

首先这里有个符号表的概念:在计算机科学中,符号表是一种用于语言翻译器(例如编译器和解释器)中的数据结构。在符号表中,程序源代码中的每个标识符都和它的声明或使用信息绑定在一起,比如其数据类型、作用域以及内存地址。
那么当你使用#define定义一个记号名称时,如果此记号从未被编译器所看见,或者在编译器开始处理源码之前就被预处理器移走了,那么此记号名称就有可能没有进入符号表,就会发生编译错误,且如果此#define语句所在的头文件还不是你写的,那么你将要去追踪它而浪费时间。

解决方法1:使用const常量代替宏
 const int a = 10;

作为一个语法常量,a肯定能被编译器看到并被收入近符号表中。此外预处理器会盲目地将宏名称替换,导致内存中出现多份目标值,而const绝不会出现相同情况。

  • 还有就是const修饰指针的两种情况:

    • 1.常量指针,const修饰的是指针,表示指针不能再次指向其他位置,但是被指物可以被修改
      int* const p = a; //const pointer,non-const data
    
    • 2.指针常量,const修饰的是指针所指向的物,表示此物不能修改,但是指针可以指向其他位置
    const int* p = a;// const data,non-const pointer
    int const *p = a;// ... 
    
解决方法2:使用枚举enum

一个属于枚举类型的数值可权充ints被使用

class Student
{
private:
  enum {LENGTH = 10 };
  int grades[LENGTH];
  ...
};

注意:1.enum的某些行为像#define,例如取const的地址是合法的,但取一个enum的地址是不合法的,取#define的地址通常也是不合法的。如果你不想让别人使用指针或引用指向你的某个整数常量,enum可以帮你实现这个约束。

解决方法3:使用inline

对于形似函数的宏,最好使用inline函数代替#define,特别是涉及到++/–问题以及乘除大小比较算法的时候,光是括号的补充就够头疼了。对于这些规模较小但调用次数频繁的代码,记得要使用内联函数

template <typename T>
inline int MAX_WHO(const T& a,const T& b)
{
    return a>b?a:b;
}

总结:有了const,enum,inline,我们对预处理器的需求降低了,但#include,以及#ifndef/#ifdef依然还是有相当重要的地位的。


02.尽可能使用const

关键字const多才多艺,你可以用它在class外部修饰全局或者namespace作用域中的常量,或修饰文件、函数、或区块作用域中被声明为static的对象。你也可以用它修饰class内部的成员变量。
对于const的指针两种用法,在01有说明,STL迭代器系其实也是以指针为根据建立的,所以迭代器的作用就像个T* pointer:
1.声明迭代器为const就像声明指针为const一样(即声明一个T* const iterator),表示这个迭代器不得指向不同的东西。
2.如果你希望迭代器所指向的东西不可被改动,你需要的是const_iterator

std::vector<int> vec;
...
const std::vector<int>::iterator p = vec.begin(); //== T* const p 
*p = 10; // 正确√ 
++p; //错误× 

std::vector<int>::const_iterator cp = vec.begin(); // == const T* p
*p = 10;//错误× 
++p;//正确√

const成员函数

许多人漠视了一个事实:两个成员函数末尾是否加const可以构成重载。
将const修饰于成员函数的目的,是为了保证该成员函数可作用于const对象上,只有const对象才可以调用const成员函数。
那么如果要在const成员函数中修改类的变量,切记是不能直接修改的。解决方法很简单:利用mutable关键字修饰需要改动的类成员变量。

在const和non-const成员函数中避免重复

例如当实现一个operator函数时,难道需要复制粘贴一份给const修饰的吗,代码重复会让人头疼。我们真正要做的是仅一次实现这个函数的功能并使用它两次,也就是我们必须要让其中一个调用另外一个。
如下:

class TextStation
{
public:
const char& operator[] (std::size_t position) const
{
	... //一些检验与访问
	return text[position];
}
char& operator[] (std::size_t position)
{
	return const_cast<char&>(
		static_cast<const TextStation&>(*this)//non-const成员函数的*this属性为non-const TextStation,需要进行类型转换,转换成const TextStation类型,来调用const成员函数operator[]
		[position]);							 //调用后对其进行去const转换
}
private:
std::string text;
};

这里用了两次转换,第一次将non-const对象转换为const对象,从而成功调用const operator[ ],之后对其进行去常操作,这样减少了代码重复。
但是记住:“运用const成员函数实现出其non-const孪生兄弟”的语法是可以好好学习的。 但是,其反向做法,让const成员函数调用non-const是万万不可取的,const成员函数承诺不改变对象的逻辑状态,但是non-const不承诺,因此调用non-const本身就会有很大的风险。

总结:

  • 1.对于单纯常量,最好以const对象或enum替换#define
  • 2.对于形式函数的宏,用inline替换#define
  • 3.const可被施加于任何作用域内的对象、函数参数、返回值、成员函数本体
  • 4.当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复

你可能感兴趣的:(C++,C++)