文章目录
由于工作原因,我最近抽空复习了effective c++, 以前在看第二条的时候,遇到过enum hack的说法,当时压根也没留意,现在看看感觉还挺有意思的。
基础知识
C++ 代码分为:
- 编译期
在编译期可以用的东西叫做
constant expression
(常量表达式),判断一个数值是不是常量表达式可以用下面的函数模板验证,下面代码中的global, monday, member, local,num都属于常量表达式,为了使用方便,C++还贴心的提供了constexpr
关键字,凡事被修饰的值是指:值不会改变并且在编译过程就能得到计算结果的表达式。
#include <iostream>
using namespace std;
const static int global=0;
enum WEEK
{
monday = 7
};
class Test
{
public:
const static int member = 8;
};
template<int N>
int show()
{
cout<<N<<endl;
}
int main()
{
const int local = 9;
constexpr int num = 10;
//global, monday, member, local, num
show<Test::member>();
return 0;
}
- 运行期
运行期肯定所有的数值都可以用了,不用在此纠结。
static
下面这种是正确的使用demo, 根据这个代码我们可以思考几个问题:
###demo
#include <iostream>
class Game
{
public:
static const int has_const = 1;
};
int main()
{
std::cout<<Game::has_const<<std::endl;
}
non-const static member能不能这样用?
####non_const
#include <iostream>
class Game
{
public:
static int non_const = 1;
};
int main()
{
std::cout<<Game::non_const<<std::endl;
}
g++ 7.5版本编译期会报错如下:说的是普通的静态成员变量不能在类中初始化,所以const static member就可以初始化,就像上面demo代码一样, 但是当我尝试下面的代码后又会报错:
####get_address
#include <iostream>
class Game
{
public:
static const int has_const = 1;
};
int main()
{
std::cout<<&(Game::has_const)<<std::endl;
}
这个报错很奇怪啊,为啥初始化了拿不到地址???
其实这里在class Game在申明的时候,当编译的时候压根没有所谓初始化has_const一说,压根也不会生成给has_const分配内存的代码,因为只有申明没有定义,那为什么demo中的代码可以运行呢?因为在编译的时候,直接把Game::has_const替换为1了,这个发生在编译期,所以编译没报错,运行也正常。下面是验证:
可以看到在编译的时候压根就没有分配内存,当涉及到提取地址信息的时候,那肯定就无法找到,所以编译就会报错。
那么如何处理这种情况呢,那就是需要程序员自己直接了当的定义,具体如下:
可以看到当我们搞一个定义的时候就可以了。所以有时候需要看看编译器到底干了什么,不然使用起来总是稀里糊涂的。不过上面这种有个缺陷就是,只支持int, 不支持其他类型比如double, 所以为了统一规范,我建议大家都这样写代码:
#include <iostream>
class Game
{
public:
static const double Num;//这里double可以换成int,float,...
};
const double Game::Num = 0.1;//这里double可以换成int,float,...
int main()
{
std::cout<<Game::Num;
std::cout<<&(Game::Num);
}
enum hack
可是有时候,我们会见到enum,enum之所以存在是部分老编译不支持上面的int申明类时候初始化,所以只能用下面的方式,因为我们知道enum也是在编译阶段会被替换的,所以可以编译通过。我觉得现在还这样用,本质就是提升逼格。
class Game
{
public:
enum{Num=100};
int socres[Num];
};
static, const, enum
- 如果静态变量在函数中,假如在定义时候就初始化了,那么编译时候这个就是可见的。如果没有初始化就会在运行时初始化,有且只会初始化一次(这也是为什么函数局部静态变量可以实现单例模式)。
- 如果这个静态变量在全局或者类中声明,
必须要提供初始化
,如果是int的话可以在类中初始化(其实根据编译器的结果可以看到并没有实际分配内存初始化,只是编译器替换了而已),其他必须
单独提供初始化语句,编译的时候编译器就会在内存处写如对应的值。
根据上面的知识和下面的代码汇编结果,enum hack 和const 的区别很明显,enum在编译阶段会被替换,是一个左值,用法和理解有点像static 成员,唯一的区别是enum不会分配内存。而const 其实就是一个普通的成员变量,有地址, 必须通过对象来调用。
为什么在类中不能声明constexpr成员变量,只能static constexpr
因为非static的成员变量只能在类实例化后,也就是有具体对象的时候才能初始化,而constexpr是在编译期使用的数值,为此想要在类中定义constexpr 变量,就必须加一个static, 在静态区编译期就可以初始化