const引用的含义是指向const对象的引用,非const引用表示指向非const类型的引用。
非const引用不可以绑定到const对象,例如:
const int iSize = 1024;
int &iRef = iSize;//error
将提示错误:
prog4.cpp(8) : error C2440: “初始化”: 无法从“const int”转换为“int &”
转换丢失限定符
非const引用只能绑定到同类型的非const对象(因为非const引用可以修改绑定的对象,如果绑定到const对象,那么又不能通过这个引用改变这个对象,这不是自相矛盾,所以不能绑定到const对象),const引用则可以绑定到不同但相关的类型的对象或者右值。
绑定到不同但相关类型对象,例如:
#include <iostream> //const引用绑定到不同但相关类型的对象 int main() { double dVal = 3.14; const int &ri = dVal; //int &ref = dVal;//error,无法从“double”转换为“int &” std::cout<<"ri:"<<ri<<std::endl;//print 3 std::cout<<"dVal:"<<dVal<<std::endl;//print 3.14 return 0; }
这里ri绑定到dVal并不影响dVal本身,只是编译器构造了临时变量,即实际效果等价于代码:
Int tmp = dVal;//cast to int
const int &ri = tmp;//bind to temporary variable
1)指向const对象的指针——不能用来修改其指向的对象的值
一般使用的指针,允许我们通过指针修改其指向对象的值。如果指针指向的是const对象,则不允许用指针来修改其所指对象的值,这就需要将指针定义为指向const对象的指针。指向const对象的指针确保不能用来修改基础对象。
注意,指向const对象的指针,不允许修改其指向的对象的值,但是允许给指针重新赋值从而指向另一个const对象。同时注意,不能使用void*保存const对象的地址,必须使用const void*保存。
在实际应用中,指向const的指针常用作函数的形参,以确保传递给函数的实际对象在函数中不因为形参而被修改。
2)const指针——本身的值不可修改
const指针的本身的值不可修改,但是并没有说明能否使用该指针修改它指向对象的值。指针所指对象的值能否修改完全取决于该对象的类型。
3)指向const对象的const指针——本身和所指向对象的值均不能改变
这种类型的指针,一经定义就不能做任何更改,既不能改变指向,也不能改变其指向对象的值。例如:
const double pi = 3.14;
const double* const pi_ptr = π
从右至左阅读,解释为pi_ptr首先是一个const指针,指向double类型的const对象。当然,这里确实非常混淆。
const限定符和指针,可参见下例:
#include <iostream> //指向const对象的指针和const指针 int main() { const double pi = 3.14; double exp = 2.718; const double *cptr;//指向const对象的指针,在定义时不强制初始化 cptr = π std::cout<<*cptr<<std::endl; cptr = &exp;//允许重新绑定到另一个const对象 std::cout<<*cptr<<std::endl; //double *ptr = π//错误,无法从“const double *”转换为“double *” //void *pv = π//错误,无法从“const double *”转换为“void *” const void *cpv = π//使用const void*保存const对象的地址 std::cout<<*((double*)cpv)<<std::endl; double *const cpd =&exp;//const指针 *cpd = 2.7;//通过cosnt指针修改原对象的值 std::cout<<exp<<std::endl; //cpd = &exp;//错误,不能给常量赋值 const double *const cptr_cd = π//指向const对象的const指针 std::cout<<*cptr_cd<<std::endl; return 0; }
输出:
3.14
2.718
3.14
2.7
3.14
1) const引用形参。
如果使用引用形参的唯一目的在于避免复制实参,即不修改相应的实参,则应该将其定义为const引用,否则const实参、字面值常量将无法调用此函数,因为非const引用形参只能与完全同类型的非const对象关联(若与const对象关联,那么通过非const引用形参去修改实参时会发生错误)。
例如:
#include <iostream> #include <string> using std::cout; using std::endl; using std::string; //const引用形参举例 //非const引用形参只能与完全同类型的非const对象关联 std::size_t find_char(string &s,char c) { string::size_type i = 0; while(i != s.size() && s[i] != c) ++i; if(i == s.size()) return string::npos; else return i; } int main(int argc, char *argv[]) { //字面值常量为const对象,调用出错 if(find_char("Hello, world.",'.') != string::npos) { cout<<"a sentence."<<endl; } return 0; }
提示错误:
error C2664: “find_char”: 不能将参数 1 从“const char [14]”转换为“std::string &
应该将finc_char函数形参定义为const引用。
2) 函数返回const引用。
返回引用的函数返回左值,这样的函数可以用于任何要求使用左值的地方,这一点确实令人迷惑,需要注意,例如下面的代码可能违背你的原意:
#include <iostream> #include <string> using std::cout; using std::endl; using std::string; //返回引用的函数返回左值需要注意 class CStu { public: CStu(){} CStu(string sname,int iage) :name(sname),age(iage){} string & getName(){return name;}//应声明返回const引用 private: string name; int age; }; int main(int argc, char *argv[]) { CStu stu("Tom",22); cout<<"name: "<<stu.getName()<<endl; stu.getName() = "Jack";//修改了私有成员,违背原意 cout<<"name: "<<stu.getName()<<endl; return 0; }
使用getName函数返回的引用竟然修改了私有成员,这里违背了封装性设计原则。如果不希望引用返回值被修改,应该将其声明为const引用。这里需要将getName()函数返回值声明为const引用,需要修改其name时通过setName()修改。
3) 重载和const形参
仅当形参是引用或者指针时,形参是否为const才能实现函数的重载。可以基于指针指向const对象还是非const对象实现函数重载,编译器可以判断,如果实参是const对象,则调用带有const*类型形参的函数,否则如果实参不是const对象则调用带有普通指针形参的函数。注意,不能基于指针本身是否为const来实现函数的重载。
关于const成员函数的要点有以下要点:
Point 1: const成员函数的作用在于约定,这个成员函数不会去修改调用它的对象.const成员函数,不能修改非静态的成员变量,也不能调用任何非const成员函数. 如果一个函数声明为const而更改了成员,则编译器会报错.
例如下面的例子(来自:Constant Member Functions):
class Date { public: Date( int mn, int dy, int yr ); int getMonth() const; // 只读成员方法 void setMonth( int mn ); // 写入函数,不能为const private: int month; }; int Date::getMonth() const { return month; // 不会修改任何内容 } void Date::setMonth( int mn ) { month = mn; // 修改成员变量 } int main() { Date MyDate( 7, 4, 1998 ); const Date BirthDate( 1, 18, 1953 ); MyDate.setMonth( 4 ); // Okay BirthDate.getMonth(); // Okay BirthDate.setMonth( 4 ); // C2662 Error }
对于一个类X来说,其非const成员中,隐含的this指针类型为'X* const',而const成员函数中,this类型为:'const X *const'.
所以在const成员函数中,无法通过this指针修改对象的成员变量和调用非const成员函数.
Point 3: const对象,指向const对象的指针或者引用只能用于调用其const成员函数,如果尝试用它们来调用非const成员函数则是错误的.C++ 允许基于成员函数是否为const进行重载.const对象调用const成员函数版本,而非const对象调用非const版本.
例如(来自: Function overloading and const keyword):
#include<iostream> using namespace std; class Test { protected: int x; public: Test (int i):x(i) { } void fun() const { cout << "fun() const called " << endl; } void fun() { cout << "fun() called " << endl; } }; int main() { Test t1 (10); const Test t2 (20); t1.fun(); // call non-const version t2.fun(); // call const version return 0; }
fun() called
fun() const called
初始化const类型的成员必须在构造函数初始化列表中进行初始化。例如如下代码:
//const成员初始化 class ConstInit { public: ConstInit(int i,int j) { ival = i; cival = j; rival = ival; } private: int ival; const int cival; int &rival; }; int main(int argc, char *argv[]) { ConstInit ci; }
将提示错误:
error C2758: “ConstInit::cival”: 必须在构造函数基/成员初始值设定项列表中初始化
prog28.cpp(12) : 参见“ConstInit::cival”的声明
解决办法就是在初始化列表中初始化特殊类成员,如下所示代码:
//const成员初始化 #include <iostream> using std::cout; class ConstInit { public: ConstInit(int i=0):ival(i),cival(i),rival(i){} private: int ival; const int cival; int &rival; //只要初始化表达式是一个常量,可以再定义体中进行初始化 static const int period = 30; public: static const unsigned int ARRAY[3];//静态常量数组 }; const unsigned int ConstInit::ARRAY[3] = {1,3,5}; int main(int argc, char *argv[]) { ConstInit ci; cout<<ConstInit::ARRAY[1]; }