如果在class中想要定义常量,直观上是想用这样的方式
class A
{
public:
const int length = 10;
char buffer[length];
};
上面这个逻辑在class外是行得通的,也是最常用的。
但是在class内部,上面的代码编译时会报错:error: invalid use of non-static data member 'A::length’
vscode中的报错信息为:非静态成员引用必须与特定对象相对(这个翻译真的是迷,但我没有找不到这句话的英文版本),大致意思是想说:必须使用一个实例化的对象来操作非static的成员。
问题可以这样理解
上面代码的class声明只是表述了:「length
是int类型的;在实例化之后是不可修改的;该class实例化后,如果没有用初始化列表给length
提供新的初值,则length
的初值为10」。
注意,= 10
这一部分是在class实例化的时候才有意义的,相当于是初始化列表中的默认一项。在class没有实例化之前,这个length
是不存在的,自然也就不能用length
去指定数组的长度(根据C++语法,数组的长度必须是在编译时就已知的)。
换个角度看
const的作用是限制了代码不许对length
进行修改,这个限制是C++语言层面上的,实际上在汇编层面没有什么办法能够阻止你去修改这些数据。所以说声明了const int length
和声明普通的int length
对于底层来说没什么区别。换句话说,const是留给编译器来看的。
而且,尽管是const的,但是在构造函数的初始化列表中还是可以修改length
的值,毕竟在执行初始化列表阶段,这个对象相当于还不存在。初始化列表执行完,const限制即生效,自此之后length
的值就不可变了。所以说C++的class里的const数据,在初始化列表之后才会展现出它的不可变属性。
回顾一下C语言中的const
C中的const变量要求在定义时就完成初始化,之后不允许修改。所以说如果C中某个地方出现了const int length = 10;
则length
的值将永远都为10,这样在任何用到length
的地方,都可以在编译阶段就确定这个值,所以是可以用来指定数组长度的。
形式上来说,C种的const类似于一种加强版的#define
那怎么办
在类中将成员数据指定为static const
类型,即可实现类似C语言的const常量的作用。
class A
{
public:
static const int length = 10;
char buffer[length];
};
相对于第1版错误的代码,这里只是在const int length = 10;
前面加了static
限定符。
这里的static表明了:「这个数据是独立存在的;它是被各个实例所共享的;不管是否有实例对象创建,它都存在」,再结合之前已有的const限定符,length
的属性可以概括为:独立存在且不会改变
综上,length
在class A没有进行任何实例化之前,就已经存在了,你可以用A::b来使用它(用sizeof去量一下A,其值为10,也就是说length
并没有存储在这个class里)。所以这样的length
在编译阶段就是已知的常量了,可以用来作为buffer
的长度指定。
使用static成员需要注意的
static成员的使用没什么特别的,当作像C语言中的static变量那样用就是了,大部分情况下static成员都被用来在一些C++的demo中统计class被构造/析构的次数,或者是在笔试题中增加难度。
static成员最麻烦的地方是初始化工作。
根据前面分析的static限定符的含义可知,static成员在class A的第1个实例创建之前就已经存在(且初始化了),所以构造函数的初始化列表中不允许初始化static数据成员,但是,static数据成员是必须要有初值的,C++不会给你个默认值的。具体地:
1、任何形式的static成员都可以在类外进行初始化,类外初始化式不用带着static
2、如果static成员是const整数类型或const枚举类型,则可以选择在类声明中初始化
ps:非static成员是不能在类外初始化的,必须在类声明中进行初始化(相当给所有的构造函数提供了默认的初始化列表项目)
回到最初的问题上
我们只是想能有个集中的位置来控制数组的长度,之后可能还会有新的static const常量加入这个团体,成为牵一发而动全身的trigger,也有可能会有某一部分数据依赖已有的常量,总之这种类似#define的操作确实促进了代码的规范化、模块化。
但是,一个比较轻微的问题是,static成员终究是要占用内存的,使用#define的形式定义常量虽然不占用内存,但又会脱离class作用域的限制,如何能够结合这两者的优势呢。
目前能想到的一个较优的方案是——使用「类内枚举」(唤起了被湖南籍室友支配的恐惧)
class A
{
public:
enum /* 匿名的 */
{
LENGTH = 10
};
char buffer[LENGTH];
};
这样既限制了LENGTH
只能在class A内部使用,同时还不占用内存。
另外,由于这里并不需要声明具体的枚举变量,所以枚举类型可以是匿名的。