C++的const用法真NND复杂,花时间梳理一下,记录下来,记备后用。若有误解,欢迎拍砖!
1. const变量
const int val = 512;
常量在定义后不能被修改,所以定义时必须初始化(相信大家都知道).
const对象默认为文件的局部变量,即其作用域是定义该对象的文件,不能被其它文件访问(定义在所有函数外的名字具有全局作用域,即可在程序的任何地方访问).
通过指定const变量为extern,就可以在整个程序中访问const对象:
extern const int val = 512;
2. const引用
const引用是指向const对象的引用!
const int ival = 1024;
const int &refVal = ival; // ok: both reference and object are const
int &ref = ival; // error: nonconst reference to a const object
const引用可以初始化为不同类型的对象或者初始化为右值,如字面常量:
int i = 42;
//legal for const reference only
const int &r = 42;
const int &r2 = r+i;
然而对非const引用却是不合法的,会导致编译错误:
int &refVal = 10 // error: initializer must be an object
观察下述代码:
double dval = 3.14;
const int &ri = dval;
编译器会把这些代码转换成以下代码:
int temp = dval; // create temporary int from the double
const int &ri = temp; // bind ri to that temporary
这样给ri赋值并不会修改dval,而只是修改了temp,因为const引用是只读的!
非const引用只能绑定到与该引用相同类型的对象。
const引用则可以绑定到不同但相关的类型的对象或绑定到右值。
*3. 指针和const
3.1 指向const对象的指针
不允许用指针来改变所指的const值。C++强制要求指针也必须有const特性(类似于上边的const引用):
const double *cptr; // cptr may point to a double that is const
cptr本身并不是const,可以修改,只是指向了一个const对象。定义时的初始化是非必须的(因为它实质并非一个const对象)。
若该指针指向的是一个非const对象,则可以直接修改对象值。指向const的指针不能保证所指向的值一定不可修改。
3.2 const指针
指针本身的值不可修改,且必须在定义时初始化:
int num = 0;
int *const cptr = #
可以用指针修改对象的值。
3.3 指向const对象的const指针
即不能修改指针所指向对象的值,也不允许修改该指针的指向(即指针值):
const double pi = 3.14;
const double *const pi_ptr = π
想必都清楚,第一个const修饰指针所指的对象,第二个const修饰指针本身。
*4. 函数与const
4.1 参数const
若函数是值传递的,const参数限定就失去了意义,因为值传递本身不会改变被传入的值,而只是传递了变量或对象的一个副本。
然而对于引用或指针传递,由于其会直接改变传入的值,这时使用const就显得格外重要。
PS:在<<Effective C++>>一书中讲到:对于内置类型,用值传递会比引用传递效率来得高,而对于对象或用户定义类型,使用引用(并且是const引用)效率更高,并且可以免去切割问题。
4.2 返回值与const
对于内部数据类型来说,返回值是否const没有意义。处理用户定义的类型时,如果返回类对象的值,则const是非常重要的,因为返回值不能为左值(即不能被赋值,不能被修改)。
返回值为的址时,是否用const修饰它,取决于用户想要做什么。
PS:对于临时变量作为参数传递,有点意思:一个总是常量的临时变量(编译器使然),它的地址可以被传递给一个函数。临时变量通过引用被传递给一个函数时,这个函数的参数一定是常量引用。下面的例子说明了这一点:
class X {};
X f() {return X();} //return by value
void g1(X &) {} // Pass by non-const reference
void g2(const X&) {} // pass by const reference
main(){
g1(f()); // Error: const temporary created by f()
g2(f()); // OK
}
5. 类与const
5.1 类里的const成员
这里有两方面意思:
(1) 在一个对象生命周期内,这是一个常量。需用初始化表达式表初始化,如下例:
class Fred {
const size; //just declaration, can't initialization
public:
Fred();
Fred(int i);
};
Fred::Fred() : size(100){};
Fred::Fred(int i) : size(i){};
(2) 对整个类来说,这是一个常量(一个编译时常量)。需要用一个不带实例的无标记的enum,如下例:
class Bunch {
enum {size = 100}; //枚举常量在编译时被求值
int i[size];
};
5.2 const对象和成员函数
一个类对象为const对象,即其数据成员在对象生命周期内不会被改变。
要保证数据成员不被改变,就该const成员函数出场了!
声明const成员函数,等于告诉编译器可以为一个const对象调用这个函数。一个非const成员函数被看成是将要修改对象中的数据成员,编译器不允许一个const成员调用非const成员函数。
const成员函数不允许修改成员变量或者调用非const成员函数。将不会修改成员变量的成员函数声明为const,以便const对象调用。
const成员函数的声明语法不再赘述,另外要说明一下,const成员不仅仅需要声明,定义时也需在函数参数列表后const关键字。
其实说到底,const成员函数也是可以修改成员变量的,需理解按位与按成员const的概念,以及关键字mutable,困了不说了......