c++ const 限定符

来源:互联网 发布:拼接控制器软件 编辑:程序博客网 时间:2024/06/05 12:48

c++ const 限定符

1.const引用与非const引用

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 3std::cout<<"dVal:"<<dVal<<std::endl;//print 3.14return 0;}



这里ri绑定到dVal并不影响dVal本身,只是编译器构造了临时变量,即实际效果等价于代码:

Int tmp = dVal;//cast to int

const int &ri = tmp;//bind to temporary variable

 

2.指针和const限定符

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

3.函数参数和const

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来实现函数的重载。

4.类的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 );   // 写入函数,不能为constprivate:   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}

Point 2 : 在非静态成员函数中,隐含传递的this指针是一个常量指针,它保存调用这个函数的当前对象的地址.

对于一个类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

5.const成员初始化

  初始化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];}



 

0 0