[C++] const限定符
在定义变量时,如果希望该变量是一个只读的常量,则在变量前加const
限定符即可。
1.用于基本类型
对于基本类型变量,定义只读常量有两种方式:#define
预处理器编译指令和const
限定符,前者是C中的语法,后者是C++中特有。
#define和const的区别
#define
和#include
一样,是一个预处理器编译指令,如:
#define SIZE 21
该指令告诉预处理器,在程序中查找SIZE,并将所有的SIZE替换成21。
因此,#define工作方式和文本处理器中的全局搜索并替换命令相似。
在C++中,可以使用const
来创建符号常量:
const int SIZE = 20;
//或者
#define SIZE 20
然而,如果有些头文件需要在C和C++中都可用的话,那么必须使用#define
。
应在变量声明时就对const进行初始化,否则将编译失败:
const int s;//s未提供值,因此编译器将提供一个不确定值
s = 20; //s 已经被编译器赋值,因此将失败
2.用于指针
有两种方式可以将const用于指针,且这两种方式之间有微妙的变化:
- 1.使用const让指针指向一个常量对象,防止该指针修改所指向的值。
- 2.使用const让指针成为一个常量指针,防止指针改变指向位置。
先来看第一种方式:
int i = 24;
const int* p1 = &i;
//*p1 = 12;//NOT ALLOWED
i = 30;//OK
这里表示,指针p1指向一个const int类型的数据,对于p来说,*p的值为const int,因此,不能通过p1修改值。
这种方式并不意味着p1指向的值实际上就是一个常量,而是对于p1而言,这个值是一个常量。
在来看第二种方式:
int i = 24;
int j = 50;
int * const p2 = &i;
// p2 = &j;//NOT ALLOWED
*p2 = 29;//OK ,i = 29
这里表示,指针cosnt p2指向一个int类型的数据,因此,p2再不能改变它的指向位置,但是其指向位置的值依然可以改变。
其他性质
- 1.常规变量的地址可以赋给指向const的指针:
double score = 23;
const double * ps = &score;
- 2.const变量的地址可以赋给指向const的指针:
const double score = 23;
const double * ps = &score;
- 3.const变量的地址不可以赋给常规指针:
const double score = 23;
// double * ps = &score;//NOT ALLOWED
score被const限定,说明不能修改它的值,如果将score地址赋给指针ps,则可以使用ps来修改score,这将使得const无意义。
所以,当char 指针指向字面量值时,一定是const char*
而不是char*
:
const char * p = "Hello world";
3.用于数组名
在数组作为参数的函数中,可以使用const保护数组,避免数组被修改,比如如下示例:
#include <iostream>
using namespace std;
int fill_arr(double arr[],int limit);
void show_arr(const double arr[],int n);
void revalue_arr(double arr[],double r, int n);
int main()
{
double arr[5];
int length = fill_arr(arr,5);
show_arr(arr,length);
revalue_arr(arr,2.2,length);
show_arr(arr,length);
return 0;
}
void revalue_arr(double arr[],double r,int n)
{
for(int i = 0;i<n;i++)
{
arr[i] *= r;
}
}
void show_arr(const double arr[],int n)
{
for(int i=0;i < n;i++)
{
cout << "arr["<<i << "]:" << arr[i] << endl;
}
}
int fill_arr(double arr[],int limit)
{
int i = 0;
double temp;
for(; i < limit;i++)
{
cout << "Enter value #" << (i+1) << endl;
cin >> temp;
if(!cin)
{
cin.clear();
while(cin.get() != '\n')
{
continue;
}
cout << "Bad input;break";
break;
}else if (temp < 0)
{
break;
}
arr[i] = temp;
}
return i;
}
在这个示例中,三个函数都有一个参数是数组,因为数组作为参数时,传递的实际上是它的地址,因此,在fill_arr()
和revalue_arr()
函数中,对形参修改,实际上修改的就是原始数组,而在show_arr()
函数中,避免数组被修改,将形参用const修饰,而常规变量赋给const变量是允许的。
由于数组和指针在大多数情况下是一样的,因此,在数组和指针用于形参时,因尽可能地将他们声明为const,一是可以避免数据的无意识修改,二是使用const,函数可以处理const和非const数据,否则只能使用非const数据。
4.用于引用
将const用于引用时,说明该引用是一个常量引用,如果将引用作为函数参数时,应尽可能地使用const限定,这是因为,如果函数实参和引用形参不匹配时,只有当引用参数为const的情况下,C++会在必要时生成临时变量。一般有如下两种情况:
- 1.实参类型正确,但不是左值;
- 2.实参类型不正确,但可以转换为正确的类型。
如:
#include <iostream>
int add( int& i, int& j);
int main()
{
using namespace std;
int i = 45;
int j = 200;
int sum = add(i,j);
cout << "sum=" << sum << endl;
long h = 12;
long k = 400;
//sum = add(h, k);//无法用long类型的值初始化int&类型的引用
//sum = add(3,6);//非常量引用的初始值必须为左值
system("pause");
return 0;
}
int add(int& i, int& j) {
return i + j;
}
在上例中,add()
函数中使用long类型的数据将编译失败,这是因为,无法用long类型的值初始化int&类型的引用。同时无法使用add(3,6)
,但是如果将函数引用参数声明为const:
int add(const int& i,const int& j);
则以上两种情况皆可以编译。由于const的声明,当发现实参和引用参数不匹配,编译器会生成一个临时变量,然后让引用参数i,j指向这些临时变量,这些临时变量只会在函数运行期间存在。
将引用参数声明为const引用的理由有三:
- 1.使用const可以避免无意的修改数据;
- 2.使用const可以使函数能够处理const类型和非const类型数据,否则只能接受非const数据;
- 3.使用const引用可以使函数能够正确生成并使用临时变量。