一、对于基本声明
const int X = 100;
const加在int X的前面,用来定义一个不可被改变的整型常量X,这个X必须在声明时赋值。这种用法用来替代传统的
#define
如
#define PI 3.14
改为
const float PI = 3.14;
总结:C++代码中应该使用const替换#define,const常量必须在声明时初始化
二、对于指针
如下几种指针
int x = 100;
int* p = &x; // 1. 非const指针,非const数据
const int* p = &x; // 2. 非const指针,const数据
int const *p = &x; // 3. 非const指针,const数据
int* const p = &x; // 4. const指针,非const数据
const int* const p = &x; // 5. const指针,const数据
可以看出2和3的功能是完全一样的,只是两种不同的写法,这种指针表示指针本身可以修改,但是指向的数据不能通过
这个指针修改。即p可以指向其他的变量,但是不能通过*p修改x的值。
对于4,允许通过指针修改数据,但是不能修改指针本身。即p只能指向x,可以通过*p修改x的值。
5的功能是前两种的叠加,即指针不能修改,数据也不能修改
总结:对于指针来说,如果const在*的左边,说明数据是const的,如果const在*的右面,说明指针是const的,如果是
指针是const的话,必须在声明时为指针初始化。
三、对于函数的参数
以下几种形式
void method(const int x); // 1. 普通变量的const
void method(const int& x); // 2. 普通变量引用的const
void method(const int* p); // 3. 普通变量指针的const
void method(const MyClass x); // 4. 类的const
void method(const MyClass& x); // 5. 类引用的const
void method(const MyClass* x); // 6. 类指针的const
对于1和2来说,这两种形式完全没有意义,这里就不多说了。
对于3来说,如果传递数组的首地址是有意义的,否则同1和2一样,也无意义例如
int a[10] = {……};
method(a);
那么a中所有的元素都不能被修改。通常为char*类型的参数加上const修饰,保证字符串不会在函数内被修改,即void
method(const char* str);
第4种用法也不应该出现,因为默认情况下C++的函数参数都是值传递。例:
MyClass c;
method(c); // 声明void method(const MyClass x);
这是C++会为c产生一个临时变量,也就是说传递到method中的是c的副本,这样增大了系统开销。所以传递类时一般要使
用5和6的方法。
对比5和6,没有本质区别,只是由于指针和引用的不同,指针可能为空值,函数进入时要检查参数的合法性。所以通常
使用第5种方法。
总结:对于一般内置类型来说(int, char等)不管引用还是指针,都没有必要传递加const修饰的参数,数组首地址除外
。对于类应该传递const引用,这样能提高运行时效率。(但是对于STL迭代器等少部分类除外,参考Effective C++
Item 20)
四,对于函数返回值
函数返回值加const修饰,对于内置类型来说(int, char等)指的是函数的返回值的初值不能被修改,同函数的参数一样
,函数的返回值也是值传递,所以对于内置类型来说,返回值前面加const基本上没有意义。
即,下面两种const返回值是不必要的
const int method(void);
const int& method(void);
下面要特别说明一下返回指针时加const修饰的情况
const int* method();
请参考下面两种实现方式
const int* method() // 1
{
int x = 100;
return &x; // 函数结束后x已经离开作用域,返回的是野指针
}
const int* method() // 2
{
int* x = new int(100);
return x; // 要记得在调用method后delete
}
对于返回对象时加const修饰的情况,和内置类型的情况基本相同,值传递时没有必要加const修饰,更不可返回类对象
的引用(类的operator =除外,下面章节会介绍)(参考Effective C++ Item10 & Item21),如果返回一个局部变量的
地址,或在堆上创建对象的地址时要小心,对于类的成员函数,需要返回内部数据时,应该返回const的指针,如下
class MyClass
{
char* m_str;
const char* GetString()
{
return m_str; // 返回const,防止外部修改对象内部数据
}
}
总结:返回值前面加const基本上没有意义,非类的成员函数返回const指针时要小心野指针和内存泄露的问题,不要返
回类对象的引用(类的operator =除外)
五、对于成员变量
class MyClass
{
const int x = 100;
}
这种用法会造成空间的浪费,每个MyClass对象都要用4字节保存一个永远不变的常量。如果为某个类定义一个常量,应
该使用static const,如下
class MyClass
{
static const int x = 100;
}
还有一种用法,可以在运行时对const成员变量初始化,使每个对象的const成员变量拥有不同的数值,如下:
class MyClass
{
public:
MyClass ( int sz ) : size( sz ) {};
const int size;
};
这个const成员变量只能在成员初始化列表中初始化。
总结:要使同一类型不同的对象拥有相同数值的const成员变量,使用static const,要使同一类型的不同对象拥有不同
数值的const成员变量,直接使用const,但是这个const成员变量应在成员初始化列表中初始化。
六、对于成员函数
函数的参数和返回值已经介绍过了,这里介绍const成员函数,如下 :
class MyClass
{
void method() const
{
}
}
这个const写在成员函数的后面,区别于对返回值的修饰。在const成员函数中,不能修改任何一个非静态成员变量,也
不能调用其他非const成员函数修改成员变量。如下
class MyClass
{
int a;
static int s_a;
void method() const
{
other_method(); // 错误
a = 100; // 错误
s_a = 100; // OK
}
void other_method()
{
a = 100; // OK
}
}
另外要注意的是如果是const的对象,这个对象只能调用const成员函数,否则编译会出错,如下
void F(const MyClass& myclass)
{
myclass. method(); // OK
myclass. other_method (); // 错误,因为对象是const的,但是other_method可能会
// 改变对象的成员变量
}
还有一个例外,用C++关键字mutable标识的成员变量可以在const成员函数中修改。如下:
class MyClass
{
mutable int a;
void method() const
{
a = 100; // OK
}
}
总结:const写在成员函数后面可以定义const成员函数,const成员函数不能修改任何非静态成员变量(静态变量可以修
改),用mutable标识的成员变量例外,可以在const成员函数中修改,const的对象只能调用const成员函数。
七、对于类的operator =
类的operator应该返回const的引用,如下:
class MyClass
{
public:
const MyClass& operator = (const MyClass& rhs)
{
……
return *this;
}
}
参考Effective C++ Item 10。
八、默认的const
下面这种情况,声明char* 同时初始化字符串常量是,运行时会初始化char*为const:
char* str = "Hello";
str[0] = 'a'; // 运行时错误
因为编译时不会出错,只有运行时才报错,所以这里使用时要注意。