const限定符

有时候我们需要用一个变量存储一个不变的值,例如缓冲区的大小、数组的元素个数等等。好处是我们可以根据需求很容易对其进行调整,并且也能防止程序不小心修改了这个值,此时可以使用关键字const对变量的类型加以限定。

初始化

const对象一旦创建后,其值就无法修改,因此必须初始化。初始值可以是任何复杂的表达式,包括常量和变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
int i = 10; // i是一个已初始化的变量
int j; // j是一个未初始化的变量
const int k = 9; // k是一个常量,必须初始化
cin >> j;
const int a = i;
const int b = j;
const int c = k;
cout << "i = " << i <<endl;
cout << "j = " << j <<endl;
cout << "k = " << k <<endl;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;

输出结果如下:

1
2
3
4
5
6
7
6
i = 10
j = 6
k = 9
a = 10
b = 6
c = 9

当用i、j和k去初始化a、b和c时,根本无须在意是不是用常量初始化,常量特征仅仅在执行改变该对象的操作时才会发生作用。任何试图改变const对象的内容的行为都是非法的。

1
2
a = b; // 错误,不能修改常量的值
const int d; // 错误,常量必须初始化

文件作用域

当以编译时初始化的方式定义一个const对象时,如下

1
const int bufSize = 512;

编译器将在编译过程中把用到该变量的地方都替换成对应的值。为了执行替换,编译器必须知道变量的初始值,如果程序包含多个文件,则每个用了const对象的文件必须能够访问到它的初始值。因此,就必须在每个用到变量的文件中都有对它的定义,为了支持这一用法,同时避免对同一变量的重复定义,默认情况下,const对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量,其实等同于在不同文件中分别定义了独立的变量。
main.cc中声明了func()函数,其定义在a.cc中,初始化bufSize = 512,在main函数中打印它的值,并调用func()。

1
2
3
4
5
6
7
8
9
10
11
12
// main.cc


const int bufSize = 512;
void func(); // define in a.cc

int main()
{
cout<< "main.c: " << bufSize<<endl;
func(); // call func
return 0;
}

a.cc中定义了func()函数,其输出定义在本文件中的const变量bufSize。

1
2
3
4
5
6
7
8
// a.cc

const int bufSize = 1024;

void func()
{
std::cout << "a.cc: " << bufSize << endl;
}

命令行编译并执行:

1
2
3
4
rda@pa ~/Downloads> g++ a.cc main.cc -g -o main
rda@pa ~/Downloads> ./main
main.c: 512
a.cc: 1024

可见,定义在不同文件中的同名const对象是不同的。
若需要在文件之间共享const变量,则对于其声明和定义都添加extern关键字,这样只需定义一次就可以了。
为main.cc中的bufSize添加extern关键字。

1
2
3
4
5
6
7
8
9
10
11
12
// main.cc


extern const int bufSize = 512;
void func(); // define in a.cc

int main()
{
cout<< "main.c: " << bufSize<<endl;
func(); // call func
return 0;
}

为main.cc中的bufSize添加extern关键字,并舍去其初始值。

1
2
3
4
5
6
7
8
// a.cc

extern const int bufSize;

void func()
{
std::cout << "a.cc: " << bufSize << endl;
}

再次编译并执行:

1
2
3
rda@pa ~/Downloads> g++ a.cc main.cc -g -o main && ./main
main.c: 512
a.cc: 512

此时bufSize在两个文件之间共享。

const的引用

可以把引用绑定到const对象上,称之为对常量的引用。对常量的引用无法修改它所绑定的对象:

1
2
3
4
const int ci = 1024;
const int &r1 = ci;
r1 = 42; // 错误,常量引用无法修改绑定的对象
int &r2 = ci; // 错误,非常量引用无法绑定到一个常量对象

一般来说,引用的类型必须与其所引用的对象的类型一致,但当初始化常量引用时允许使用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。允许一个常量引用绑定到非常量的对象、字面值甚至是一般表达式:

1
2
3
4
5
int i = 42;
const int & r1 = i; // 常量引用绑定到非常量对象
const int &r2 = 42; // 常量引用绑定到字面值
const int &r3 = 2 * r1; // 常量引用绑定到表达式
int &r4 = r1 * 3; // 错误,非常量引用无法绑定到一个表达式

当一个常量引用绑定到另一种类型上时,如下:

1
2
double dval = 3.14;
const int &ri = dval;

编译器把上述代码变成如下格式:

1
2
const int temp =dval; // temp的值为3
const int &ri = temp;

这种情况下,ri绑定到了一个临时量(temporary)对象,所谓的临时对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。

指针和const

可以令指针指向常量或非常量。指向常量的指针不能用于改变其所指的对象的值,想要存放常量对象的地址,只能使用指向常量的指针。

1
2
3
4
const double pi = 3.14;
double *ptr = &pi; // 错误,常量对象的地址只能使用指向常量的指针保存
const double *cptr = &pi;
*cptr = 4.2; // 错误,指向常量的指针无法改变其所指对象的值

指针的类型必须与其所指对象的类型一致,但是有两个例外,第一个例外是,允许一个指向常量的指针指向一个非常量对象。

1
2
double dval = 3.14;
const double *cptr = &dval;

所谓指向常量的指针或引用,仅仅是自身无法修改所指向的对象或所绑定的对象,而没有规定那个对象的值无法修改。
指针是对象,而引用不是,因此允许把指针本身定义为常量。和其他常量对象相同,常量指针必须初始化,且初始化后不能改变指针的值。把*放在const前说明指针本身是一个常量。

1
2
3
4
5
6
7
int errNum = 0;
int i;
int *const curErr = &errNum; // curErr是一个常量指针,将一直指向errNum
*curErr = 4; // 可以修改指向的对象,因为该对象不是常量
curErr = &i; // 错误 无法修改该指针的值
const double pi = 3.14;
const double *const pip = &pi; // pip是一个指向常量对象的常量指针

指针本身是一个常量并不意味着无法通过指针修改其指向对象的值,能否这样做取决于其指向的对象是否为常量。

顶层const和底层const

指针本身是一个对象,因此指针本身是否为常量和其指向的对象是否为常量是相互独立的。名词顶层const表示指针本身是常量,名词底层const表示指向的对象是一个常量。

更一般的,顶层const可以表示任何对象是常量,而底层const则与指针和引用等复合类型的基本类型部分有关。

1
2
3
4
5
6
int i = 0;
int * const p1 = &i; // 顶层const,不能改变p1的值
const int ci = 42; // 顶层const,不能改变ci的值
const int *p2 = &ci; // 底层const,可以改变p2的值但不能通过p2改变其指向对象的值
const int *const p3 = p2; // 左边的是底层const,修饰其指向的对象;右边的是顶层const,修饰其本身
const int &r = ci; // 引用的const都是底层const

执行对象拷贝操作时,常量是顶层const还是底层const有明显区别,顶层const不受影响:

1
2
i = ci;
p2 = p3; // p2和p3指向的对象类型相同

底层const具有限制作用,拷入和拷出的对象必须具备相同的底层const资格,或者两个对象的数据类型必须能够转换,一般来说,非常量可以转换成常量,反之则不行:

1
2
3
4
5
6
7
8
9
10
int *p = p3;  // 错误,p3包含底层const
p2 = p3;
p2 = &i;
int &r = ci; // 错误,普通int&不能绑定到常量
const int &r2 = r;
const int &cr = i;
p2 = &cr;
p = &cr; // 错误,const int *无法转换为int *
int &ri = i;
p2= &ri;

const形参和实参

VS C

c语言中也有const关键字,c++虽然与c保持兼容,但还是存在一定差别,而const关键字上表现出的不同特性就是其一。

本身含义

严格来说,c语言中const修饰的变量并不是常量,只不过其值不允许被改变,因此称为只读变量更合适;因此如下代码是可以通过编译的:

1
2
3
4
5
6
7
8
9
10
11
12
> cat a.c
#include <stdio.h>

const int global;
int main()
{
const int local;
printf("global: %d; local: %d\n", global, local);
return 0;
}⏎
> gcc a.c -o main && ./main
global: 0; local: 22013

可以不初始化const变量的值。

c++中的const修饰的是真正的常量,在compiler-time或者run-time必须被初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> cat a.cc
#include <iostream>
using namespace std;

const int global;
int main()
{
const int local;
cout << "global: " << global << "; local: " << local << endl;
return 0;
}⏎
> g++ a.cc b.cc -g -o main && ./main
a.cc:4:11: error: uninitialized ‘const global’ [-fpermissive]
4 | const int global;
| ^~~~~~
a.cc: In function ‘int main()’:
a.cc:7:15: error: uninitialized ‘const local’ [-fpermissive]
7 | const int local;
| ^~~~~

文件作用域

c++中const修饰的变量文件作用域为本文件内,即internal linkage,因此两个不同的文件中相同的全局const变量是不相互影响的,通过上述描述也可以看出;而c语言中const修饰的变量具有外部链接,即external linkage。分别准备两个c++源文件(a.cc和b.cc)和两个c文件(a.c和b.c),功能类似:

a.cc

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;

const int a = 12;
void func();
int main()
{
cout << "a.cc: " << a << endl;
func();
return 0;
}

b.cc

1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;
const int a = 0;

void func()
{
cout << "b.cc: " << a << endl;
}

a.c

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

const int a = 12;
void func();
int main()
{
printf("a.cc: %d\n", a);
func();
return 0;
}

b.c

1
2
3
4
5
6
7
#include <stdio.h>
const int a = 12;

void func()
{
printf("b.cc: %d\n", a);
}

编译运行c++程序,如下:

1
2
3
4
> g++ a.cc b.cc -g -o main
> ./main
a.cc: 12
b.cc: 0

分别打印出各自源文件内定义的const变量的值。

编译运行c程序,如下:

1
2
3
> gcc a.c b.c -g -o main
/usr/bin/ld: /tmp/ccA7931K.o:/home/rda/Templates/b.c:2: multiple definition of `a'; /tmp/ccWlwmJW.o:/home/rda/Templates/a.c:3: first defined here
collect2: error: ld returned 1 exit status

提示链接错误:重复定义变量。