const

有时我们希望定义这样一种变量,他的值不能被改变。
const声明语句从右向左读

指针和const

const double pi = 3.14;		// pi是个常量,它的值不能改变
double *ptr = π			// 错误:ptr是个普通指针
const double *cptr = π	// 正确:cptr可以指向一个双精度常量
*cptr = 42;					// 错误:不能给*cptr赋值

指针的类型必须与其所指对象的类型一致,但是有两个例外。

  • 允许令一个指向常量的指针指向一个非常量对象。
double dval = 3.14;			// dval的值可以改变
cptr = &dval;				// 正确:但是不能通过cptr改变dval的值

所谓指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变。不过是指针或引用“自以为是”罢了

  • const 指针

指针是对象而引用不是,允许把指针本身定为常量。常量指针(const pointer) 必须初始化,而且一旦初始化完成,则他的值(也就是存放在指针中的那个地址)就不能再改变了。把* 放在const关键字之前用以说明指针是一个常量,这样的书写方式隐含着一层意味,即不变的是指针本身的值而非指向的那个值。

int errNumb = 0;
int *const curErr = &errNumb;	// curErr将一直指向errNumb
const double pi = 3.14159;
const double *const pip = π	// pip是一个指向常量对象的常量指针

想要弄清楚这些声明的含义最行之有效的办法是从右向左阅读。 此例中,离curErr最近的符号是const,意味着curErr本身是一个常量对象,对象的类型由声明符的其余部分确定。声明符的下一个符号是*,意识是curErr是一个常量指针。最后,该声明语句的基本数据类型部分确定了常量指针指向的是一个int对象。
指针本身是一个常量并不意味着不能通过指针修改其所指对象的值,能否这样做完全依赖于所指对象的类型。例如,pip是一个指向常量的常量指针,则不论是pip所指的对象值还是pip自己存储的那个地址都不能改变。相反的,curErr指向的是一个一般的非常量整数,那么就完全可以用curErr去修改errNumb的值。

顶层const

如前所述,指针本身是一个对象,它又可以指向另外一个对象。因此,指针本身是不是常量以及指针所指的是不是一个常量就是两个独立的问题。
用名词顶层const(top-level const)表示指针本身是个常量,而用名词底层const(low-level const)表示指针所指的对象是一个常量。
更一般的,顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用,如算术类型、类、指针等。底层const则与指针和引用等复合类型的基本类型部分有关。比较特殊的是,指针类型既可以是顶层const也可以是底层const,这一点和其他类型区别明显。

int i = 0;
int *const p1 = &i;				// 不能改变p1的值,这是一个顶层const
const int ci = 42;				// 不能改变ci的值,这是一个顶层const
const int* p2 = &ci;			// 允许改变p2的值,这是一个底层const
const int *const p3 = p2;		// 靠右的const是顶层const,靠左的是底层const
const int &r = ci;				// 用于声明引用的const都是底层const

当执行对象的拷贝操作时,常量时顶层const还是底层const区别明显。其中,顶层const不受什么影响。

i = ci;						// 正确:拷贝ci的值,ci是一个顶层const,对此操作无影响
p2 = p3;					// 正确:p2和p3指向的对象类型相同,p3顶层const的部分不影响

执行拷贝操作并不会改变被拷贝对象的值,因此,拷入和拷出的对象是否是常量都没什么影响。
另一方面,底层const的限制却不能忽视。当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之则不行:

int *p = p3;				// 错误:p3包含底层const的定义,而p没有
p2 = p3;					// 正确:p2和p3都时底层const
p2 = &i;					// 正确:int*能转换成const int*
int &r = ci;				// 错误:普通的int&不能绑定到int常量上
const int &r2 = i;			// 正确:const int& 可以绑定到一个普通int上

constexpr 和常量表达式

常量表达式(const expression)是指不会改变并且在编译过程就能得到计算结果的表达式。显然,字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。
一个对象(或表达式)是不是常量表达式)由它的数据类型和初始值共同决定,例如:

const int max_files = 20;			// max_files 是常量表达式
const int limit = max_files + 1;	// limit 是常量表达式
int staff_size = 27;				// staff_size 不是常量表达式
const int sz = get_size();			// sz 不是常量表达式

尽管staff_size的初始值是个字面值常量,但由于它的数据类型只是一个普通int而非const int,所以它不属于常量表达式。另一方面,尽管sz本身是一个常量,但它的具体值直到运行时才能获取到,所以也不是常量表达式。

constexpr 变量

C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化:

constexpr int mf = 20;			// 20是常量表达式
constexpr int limit = mf + 1;	// mf + 1是常量表达式
constexpr int sz = size();		// 只有当size是一个constexpr函数时才是一条正确的语句。

一般来说,如果认定变量是一个常量表达式,那就把它声明成constexpr类型

字面值类型

常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制。因为这些类型一边比较简单,值也显而易见、容易得到,就把它们称为字面值类型(literal type)
算术类型、引用和指针都属于字面值类型。自定义类,IO库,string类型,STL则不属于字面值类型,也就不能被定义成constexpr。
尽管指针和引用都能定义成constexpr,但它们的初始值却受到严格限制。一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。
函数体内定义的对象一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量。相反的,定义于所有函数体之外的对象其地址固定不变,能用来初始化constexpr指针。

指针和constexpr

必须明确,在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关:

const int *p = nullptr;			// p是一个指向整型常量的指针
constexpr int *q = nullptr;		// q是一个指向整数的常量指针

p和q的类型相差甚远,p是一个指向常量的指针,而q是一个常量指针,其中的关键在于constexpr把它定义的对象置为了顶层const。
与其他常量指针类似,constexpr指针既可以指向常量也可以指向一个非常量。

constexpr int *np = nullptr;	// np是一个指向整型的常量指针,其值为空
int j = 0;
constexpr int i = 42;			// i的类型是整型常量
// i 和 j 都必须定义在函数体之外
constexpr const int *p = &i;	// p是常量指针,指向整型常量i
constexpr int *p1 = &j;			// p1是常量指针,指向整数j

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值