C++const详解

相信看了这篇博客,你会对c++的const有更深的了解

const的最初动机是取代#define来进行值替代。从这以后它曾被用于指针、函数变量、返回类型、类对象以及成员函数。

先来说说#define
预处理命令#define可以不受限制地建立宏并用它来替代值,并且只存在于预处理期间。预处理器只做一些文本替代,既没有类型检查概念,也没有类型检查功能,在实际编程中,预处理器盲目的替代会产生一些意料之外的问题。
假如有一下语句:

#define LENGTH 100
const int length=100;
char buf_L[LENGTH];
char buf_l[length];

LENGTH是一个名字,它只是在预处理期间存在,也许它从未被编译器看见,在编译器开始处理源码之前它就被预处理器移走了,于是LENGTH这个名字并没有进入符号表,因此它不占用存储空间。
所以在编译阶段,编译器看到的就直接是char buf_L[100];
那么#define建立的宏相对于const常量有什么缺点呢?

1、const常量作为语言常量,肯定会被编译器看到,会进入到符号表中,而#define定义的宏则不会。而由于预处理器会将所有的宏都替换为相应的文本,所以可能会使得编译后的目标代码出现多份的替换的文本,导致代码量增加。

2、带参数的宏定义使得代码晦涩难懂,并且可能带来边缘效应,即使你足够仔细为每个参数都带上了括号,也还是可能会发生一些你意料之外的事情。

看看下面两个例子:

#define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))
int a=5,b=0;
CALL_WITH_MAX(++a,b);//a被累加两次
CALL_WITH_MAX(++a,b+10);//a被累加一次

在这里,调用f之前,a的递增次数取决于它被拿来和谁比较。

主角:const

1、const变量

如果想让一个值不变,就应该使之成为const,编译器会保证const的值不能被改变。

通常C++编译器并不为const创建存储空间,相反它把这个定义保存在它的符号表里,除非用extern修饰const变量,extern意味着要使用外部链接,这样就会为const变量分配存储空间,还有一种,如果对const变量取地址,编译器也会为const变量分配存储空间。

来看看以下代码:

const int i=100;
const int j=i+10;
long address=(long)&j;
char buf[j+10];

const变量i是一个编译期间的const,编译器把它保存在符号表中,并不为它分配存储空间。const变量j是根据i计算出来的,同样是编译期间的const,也被编译器放到符号表中。下面一行对const变量j做了取地址操作,这就迫使编译器为const变量j分配存储空间。下面一行定义一个buf数组,数组大小为j+10。编译器需要预先知道j的值,j的值存储在两个地方,一个在符号表中,一个在内存中,但在编译期间,编译器无法知道运行时内存中的值,所以只能拿符号表中保存的j去计算j+10的值并为buf数组分配空间。

但如果编译器不知道const变量在编译期间的值会怎样呢?

int main()
{
   cout<<"type a character & CR:";
   const char c=cin.get();
   const char c2=c+'a';
   cout<<c2;
}

const变量c的值在编译期间是不知道的,编译器必须为其分配存储空间。这时候,const意味着不能改变的一块存储空间,其初始化必须在定义点进行,而且定义的时候必须初始化,一旦初始化后,其值就不能改变。同样的,const变量c2的值是由c的值计算而来的,在编译期间同样不知道c2的值,编译器也必须为c2分配存储空间。


2、与const有关的指针

先记住一点:如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量。如果出现在星号两边,表示被指物和指针两者都是常量。

1)指向const的指针

const int* u;
int const* v;

两个指向int类型的指针u,v都用了const修饰,而且const都出现在星号左边,这意味着两个指针都是指向const int类型的指针,也就是它们的所指物是常量,不可修改,而指针本身不是常量,可以修改。

但一般来说,为了使程序更具有可读性,应坚持用第一种形式。

2)const指针

int d=1;
int* const w=&d;

指向int类型的指针w在定义时用了const修饰,而且const出现在星号右边,这意味着w是个const指针,也就是w的所指物不是常量,可以修改,而w本身是常量,不可修改。所以const指针w定义时必须要初始化,指向int类型变量d。

3)指向const类型的const指针

const int a=10;
const int* const b=&a;

指向int类型的指针b在定义时用了const修饰,而且const既出现在星号左边,又出现在星号右边,这意味着b是个const指针,而且b的所指物也是const类型的。所以const指针b定义时必须要初始化,指向const int类型变量a。

记住:可以把一个非const对象的地址赋值给一个const指针,因为也许有时不想改变某些可以改变的东西。然而,不能把一个const对象的地址赋值给一个非const指针,因为这样做可能通过被赋值的指针改变这个对象的值。


3、与const有关的函数参数和返回值

1)传递const值

如果函数参数是按值传递,则可用指定参数是const的,如:

void f1(const int i)
{
   i++;//不合法
}

在函数里,const有这样的意义:参数不能被改变。所以它是为函数创建者服务的,与函数调用者无关,因为按值传递的参数只是原变量的副本,在函数内对参数的改动并不会影响原变量的值。

2)返回const值

对于内置类型来说,按值返回的是否是一个const,是无关紧要的,所以按值返回一个内置类型时,应该去掉const,以免引起混淆。

当按值返回一个内置类型时,const不起作用的原因是:编译器已经不让它成为一个左值(因为它总是一个值而不是一个变量)。


4、与const有关的类

1)类里的const

在一个类里建立一个普通的const时,不能给它初值。因为对于这个常量来讲,每个不同的对象可以含有一个不同的值。这个初始化工作必须在构造函数里进行。例如:

class Fred
{
   const int size;
public:
   Fred(int sz):size(sz){}
};

2)const对象和成员函数

如果声明一个成员函数为const,则等于告诉编译器该成员函数可以为一个const对象所调用。一个没有被明确声明为const的成员函数被看成是将要修改对象中数据成员的函数,而且编译器不允许它为一个const对象所调用。

关键字const必须用同样的方式重复出现在const函数的声明和定义中,否则编译器把它们看成两个不同的函数。

class X
{
   int i;
public:
   X(int ii);
   int f() const;
   int d();
};

X::X(int ii):i(ii){}
int X::f() const 
{
   return i;
}
int X::d()
{
   i++;
}

int main()
{
   X x1(10);
   const X x2(20);
   x1.d();//非const对象可以调用const成员函数
   x1.f();
   x2.d();//错误!const对象不能调用非const成员函数
   x2.f();
}

不修改数据成员的任何函数都应该把它们声明为const,这样它可以和const对象一起使用。


猜你喜欢

转载自blog.csdn.net/fordreamyue/article/details/78074703