前排提示:此文并不适合初学者阅读
如果各位对编译原理熟悉,应该知道什么是符号表,如果不懂,可以去翻翻。
我们都知道#define在预编译阶段就会被处理(通常情况下),因此在程序编译过程中,如果出现错误,我们看到的报错往往是#define后面的内容,本身符号不会显示在错误提示栏中,这样很容易造成困惑。因此我们经常用const替换define;有两个特殊情况:
const char* const p="I love wqm" or const std::string p("I love wqm")
class test { static const num=5; int nums[num] } const int test::num;
在旧式编译器中,如果不支持内部定义值,我们可以用enum代替,它与const有两点区别:
1.其实enum更像define,因为不可以取地址,如果不希望有指针指向这个对象,推荐用enum,
2.不会多分配空间,不够优秀的编译器可能会给const分配额外的空间
class test { enum{num=5}; int nums[num] }
宏定义的一个很大的有点就是可以定义简单的函数,但是不会招致函数调用带来的额外开销,比如额外的堆栈调用,比如这个:
#define MY_MAX(a,b) f((a)>(b)?(a):(b))
但是其实这种写法很傻,因为符号优先级我们必须对所有实参加上小括号,否则运行的时候可能会与宏左右的运算符提前运算导致达不到理想的结果。再比如说这种调用方式:
int a=5,b=0; MY_MAX(++a,b); My_MAX(++a,b+10);
如果传入的本身就是一个带操作符的数,很可能结果会与我们想法事与愿违。
我们可以用template inline函数替换简单的函数
template<typename T> inline void myMax(const T& a,const T& b) { f(a>b?a:b); }
这种方式就不用担心多次求值,另一个优点是可以在类内实现,是有作用域的,而define无法定义作用域。
c++14版本进一步扩充了constexpr的使用,要求为传入和输出都是const,这两者是有区别的。const可以表示字面值常量也可以表示只读,而constexpr只能表示字面值常量,如果用constexpr作为函数前缀,编译器就能大胆地优化。
需要额外强调的是,常量表达式和非常量表达式的计算时机不同,非常量表达式只能在程序运行阶段计算出结果,但是常量表达式的计算往往发生在程序的编译阶段,这可以极大提高程序的执行效率,因为表达式只需要在编译阶段计算一次,节省了每次程序运行时都需要计算一次的时间。
但是值得注意的是,并不是所有的函数加上都能被当作字面值常量,如果传入的参数并不是字面值常量而是只是代表只读,那么还是会被当做普通函数处理。此时constexpr只是推荐编译器优化,但是并不一定会优化,比如下面这个,还是运行时编译的。
using namespace std; constexpr int f(const int x) { return x+10; } int main() { int i=cin.get(); cout<<f(i)<<endl; }
const在普通函数上一个作用是在返回值是const的时候表示只读,因此如果有比如类似赋值之类的操作是无意义的,可以避免bug的出现。即使返回的是值,也是一个副本而不是值,这类返回会被编译器报错,比如这样:
class num { public: int num = 0; int get() { return this->num; } }; int main() { num n1; n1.num = 1; n1.get() = 2; system("pause"); }
如果类内部对一个函数的返回值做const重载,会有不同的效果:
using namespace std; class nums { public: int num = 0; nums(int number) :num(number) {}; int get() { cout << "not const" << endl; return this->num; } const int get() const { cout << "const" << endl; return this->num; } }; int main() { nums n1(1); const nums n2(2); n1.get(); n2.get(); system("pause"); }
输出结果如下,因此我们可以根据是否是const制定不同的函数。
如果我们不写const版本,有const版本的编译无法通过
如果俩内容一样,那么呢简单的解决方法是这样,反之并不推荐
using namespace std; class nums { public: int num = 0; nums(int number) :num(number) {}; int get() { auto&& temp = static_cast<const nums>(*this).get(); return const_cast<int&>(temp); } const int get() const { cout << "const" << endl; return this->num; } }; int main() { nums n1(1); const nums n2(2); n1.get(); n2.get(); system("pause"); }
还有值得指出的是如果希望针对一个const成员函数希望改变部分成员对象,定义的时候可以加上mutable消除掉对这个变量的束缚,比如这样。
using namespace std; class nums { public: int num = 0; mutable int num2 = 0; nums(int number) :num(number) {}; int get() { auto&& temp = static_cast<const nums>(*this).get(); return const_cast<int&>(temp); } const int get() const { cout << "const" << endl; num2 += 1; return this->num; } }; int main() { nums n1(1); const nums n2(2); n1.get(); n2.get(); system("pause"); }
首先先说明这个顺序是无解的,比如我们在文件a里面定义了一个classA,然后文件b里面定义了另一个classB,我们的类B需要类A的一个static参数来初始化,这种参数叫做no_local static param,而编译器本身没办法保证这个东西是初始化在我们定义类之前的,因此我们需要用一个额函数做一层封装:
A& getA() { static A a; return a; }
如果涉及多线程,我们需要考虑重排问题,就需要加fence防止汇编重排,从而生成多个对象。有点像设计模式种的单例模式了。具体解决方案可以看我设计模式部分的博客。