条款02: 尽量以const, enum, inline替换 #define


由于工作原因,我最近抽空复习了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, 在静态区编译期就可以初始化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值