• C++ const 引用 指针


    C++ const 引用 指针

    先简单回忆一下常量的性质:

    int main()
    {
    	const int buffSize = 512;
        buffsize = 512;	//× buffSize是常量
    }
    

    初始化时:

    const int i = get_val();	//√	运行时初始化
    const int j = 42;			//√	编译时初始化
    const int k;				//× k未经初始化
    

    当用一个对象去初始化另外一个对象,他们是不是const就无关紧要

    int i = 42;
    const int ci = i;
    int j = ci;
    

    ci是整形常量,但ci的常量特征仅仅在执行 改变ci 的操作时才会发挥作用

    const和引用

    对常量的引用

    把引用绑定到const对象上,称之为对常量的引用

    对常量的引用不能用作修改它所绑定的对象,也就是说:引用 及其 引用的对象 都是常量

    const int ci = 1024;
    const int &r1 = ci;
    

    需要注意的是,非常量引用不能引用常量对象

    const int ci = 1024;
    const int &r1 = ci;
    r1 = 42;		//× r1是对常量的引用
    int &r2 = ci;	//× r2是一个非常量引用,ci是一个常量对象
    

    因为不允许把ci用作修改它所绑定的对象,所以也不能通过引用去改变ci(假设第四句合法,那我们就可以通过r2去改变ci了,显然是不对的)

    以下两句同理

    int &r3 = r1;		//×
    const int &r4 = r1; //√
    

    我们口头所说的常量引用其实是对const的引用,严格来说是不存在常量引用的,因为和指针不一样,引用不是对象,我们没有办法让引用本身很定不变

    (P.S:由于C++不允许随意改变引用所绑定的对象,所以也可以理解为,所有的引用都是常量,当然了,引用的对象是否是常量,会决定其所能参与的操作,但无论如何也不会影响到引用和对象的绑定关系)

    初始化对常量的引用

    我们知道引用的类型必须要和所引用的对象类型一致,但涉及初始化常量的引用会出现第二种例外(第一种:初始化常量引用是允许用任意表达式作为初始值,只要该表达式能转换成引用的类型)

    int i = 42;
    const int &r1 = i;		//√ 允许const int绑定到一个普通int对象上
    const int &r2 = 42;		//√ r2是一个常量引用
    const int &r3 = r1 * 2;	//√ r3是一个常量引用
    int &r4 = r1 * 2;		//× r4是一个普通的非常量引用,非常量引用初始值必须为左值
    int &r5 = 2;			//× 非常量引用初始值必须为左值
    

    我们知道,非常量引用的初始值必须为左值,常量引用的初始值可以为左值、右值,再看看下面的情况

    int i =2;
    double &r =i;		//编译报错
    const double &r =i; //编译通过
    //难道这个i不是左值?
    
    double i =2;
    double &r =i;		//编译通过
    //难道这里的i又是左值了?
    

    为什么会出现这种情况?先来看一个简单的例子

    double dval = 0.114514;
    const int &ri = dval;
    cout << "ri = " << ri <<endl;
    

    运行输出

    ri=0

    在这个过程中,由于类型不匹配,编译器把代码改成了:

    double dval = 0.114514;
    const int temp = dval;
    const int &ri = temp;
    cout << "ri = " << ri <<endl;
    

    这种情况下,ri绑定了一个临时量对象,临时变量都是const,所以没有const的引用会失败

    这下你可看懂上面的代码发生了什么了吧

    你可以想象以下,如果ri不是常量时,执行了上述初始化过程会带来怎样的后果:如果ri不是常量。就允许对ri赋值,这样就会改变ri所引用对象的值(此时绑定的是临时量而非dval),所以C++也把以下这种行为归为非法

    double dval = 0.114514;
    int &ri = dval;//编译报错
    cout << "ri = " << ri <<endl;
    

    同时注意,对const的引用可能引用一个并非const的对象

    对const的引用仅对引用可参与的操作做出了限定,对于引用对象本身是否是一个常量没有做出限定,因此对象也可能是个非常量,允许通过其他途径改变它的值

    int i = 42;
    int &r1 = i;
    const int &r2 = i;
    //r2 = 0; //× r2是一个常量引用
    cout << "r2 = " << r2 <<endl;
    r2 = 0;	//不可通过编译
    i = 0;	//可通过编译
    cout << "r2 = " << r2 <<endl;
    

    该程序输出如下:

    r2 = 42
    r2 = 0

    const和指针

    指向常量的指针

    类似于对常量的引用指向常量的指针不能用于改变其所指对象的值

    同时,想要存放常量对象的地址,只能使用指向常量的指针:

    const double homo = 1.14;
    double *ptr = &homo;			//× ptr是一个普通指针
    const double *cptr = &homo;		//√
    cptr = 5.14;					//× 不能给*cptr赋值
    //不允许 指向常量的指针 用于改变其所指对象的值
    

    不同于引用,我们能改变指向常量的指针所指向的对象

    const double homo = 1.14;
    const double *cptr = &homo;
    cout << "cptr = " << *cptr <<endl;
    cout << "*cptr = " << cptr <<endl;
    const double homo2 = 5.14;
    cptr = &homo2;	
    cout << "cptr = " << *cptr <<endl;
    cout << "*cptr = " << cptr <<endl;
    cptr++; //合法
    //允许一个 指向常量的指针 指向 一个非常量对象
    

    注意,与引用类似,虽然我们说指针的类型必须与所指对象一致,但是这里有第一种例外:允许一个指向常量的指针指向一个非常量对象,但是不允许通过 指向常量的指针 修改非常量对象的值

    const double homo = 1.14;
    const double *cptr = &homo;
    double dval = 3.14;
    cptr = &dval;	//允许一个 指向常量的指针 指向 一个非常量对象
    *cptr = 0.0		//但是不允许通过 指向常量的指针 修改非常量对象的值
    

    所以,指向常量的指针也没有规定其所指的对象必须是一个常量,所谓指向常量仅仅要求不能通过该指针修改所指向对象的值,而没有规定所指对象的值不能通过其他途径改变

    const double homo = 1.14;
    const double *cptr = &homo;
    cout << "cptr = " << *cptr <<endl
    
    double dval = 5.14;
    cptr = &dval;	//允许一个 指向常量的指针 指向 一个非常量对象
    //*cptr = 0.0	//但是不允许通过 指向常量的指针 修改非常量对象的值
    cout << "cptr = " << *cptr <<endl;
    
    dval = 0.0		//所指对象的值可以通过其他途径改变
    cout << "cptr = " << *cptr <<endl;
    

    现在我们输出就变成了:

    cptr = 1.14
    cptr = 5.14
    cptr = 0

    const指针

    和引用不同,指针本身是对象,所以允许把指针本身定为常量,也就是常量指针,常量指针必须被初始化,并且初始化完成后值(存放在指针对象里的地址)不能改变

    *放const关键字之后,用以说明指针是指向常量的指针(不能通过该指针修改所指向的对象)

    *放const关键字之前,用以说明指针是一个常量(即指针本身的值——存储的地址不变)

    int errorNumb = 0;
    int* const curErr = &errorNumb; //curErr是一个常量指针,一直指向errNumb
    const double pi = 3.1415;
    const double* const pip = &pi;	//pip是一个 指向常量对象 的 常量指针
    

    以下两种写法区别很大:

    int* const curErr = &errorNumb; //curErr一直指向errNumb
    *curErr = 1;					//可以修改所指变量的值
    
    const int* curErr = &errorNumb; //curErr是一个 指向常量的指针
    *curErr = 1;					//× 不能通过curErr修改所指对象的值
    

    顶层const和底层const

    由于指针本身是一个对象,它又可以指向另外一个对象,因此指针本身是不是常量指针所指的对象是不是常量就是两个互相独立的问题,顶层const表示指针本身是个常量,底层const表示指针所指的对象是个常量

    顶层const其实可以表示任意的对象(自身)是常量,指针式比较特殊的,因为它既可以是顶层也可以是底层const

    int i = 0;
    int* const p1 = &i;			//p1本身是常量,顶层const
    const int ci = 42;			//ci本身是常量,顶层const
    const int* p2 = &ci;		//*在const之后,p2是指向常量的指针,底层const
    const int* const p3 = p2;	//先看左边是顶层,再看右边是底层,p3是指向常量的常量指针
    const int& r = ci;			//声明引用的const都是底层const,r是一个对常量的引用
    

    拷贝操作不会影响被拷贝对象的值,顶层const不受影响

    i = ci;
    p2 = p3;
    

    但是底层const就会产生限制:

    拷贝操作时,拷入和拷出的对象必须有相同的底层const资格,活着两个对象数据类型能转换(非常量能转成常量,反之不行)

    int* p = p3;		//× p3包含底层const,p没有
    const int* p = p3;	//√ p和p3都是底层const
    p2 = p3;			//√ p2和p3都是底层const
    p2 = &i;			//√ int能转换成const int*
    int& r = ci;		//×	普通int&不能绑到const int&上
    const int& r2 = i;	//√ const int&可以绑到一个普通int上
    

    常量对象成员

    class Corrdinate	//坐标
    {
    public:
    	Corrdinate(int x,int y);
    private:
    	const int m_iX;
    	const int m_iY;
    };
    

    可以看到两个整形成员变量都是常量,我们也类似指针和数组,称之为常量成员

    由于是常量,初始化肯定就会受到限制:

    //m_iXh,m_iY是常量成员,以下写法是错误的
    Corrdinate::Corrdinate(int x, int y) {
    	m_iX = x;
    	m_iY = y;
    }
    

    正确的方法应该是使用初始化列表

    Corrdinate::Corrdinate(int x, int y):m_iX(x), m_iY(y) {
    
    }
    

    那如果类的成员也是对象呢?

    我们再看一个线段类

    class Line 
    {	
    public:
    	Line(int x1, int y1, int x2, int y2);
    private:
    	const Corrdinate m_corrA;
    	const Corrdinate m_corrB;
    };
    

    我们需要让一个线段定义后不能修改,于是我们把线段的两个端点定义为const,类似地称之为常量对象

    定义完后,想通过构造函数初始化,也是使用初始化列表

    Line::Line(int x1, int y1, int x2, int y2) :m_corrA(x1, y1), m_corrB(x2, y2) {
    
    }
    

    常量成员函数

    我们还可以用cosnt修饰成员函数,这样的成员函数叫常量成员函数

    class Corrdinate	//坐标
    {
    public:
    	Corrdinate(int x,int y);
    	void changeX() const;	//常量成员函数
    	void changeX();
    private:
    	int m_iX;
    	int m_iY;
    };
    

    然后你就会发现

    void Corrdinate::changeX() const {	//×
    	m_iX = 10;
    }
    
    void Corrdinate::changeX() {		//√
    	m_iX = 20;
    }
    

    常量成员函数中不能改变数据成员的值,为什么呢?

    这个函数只是看似没有参数

    void Corrdinate::changeX() {
    	m_iX = 20;
    }
    

    实际在编译时会变成

    void changeX(Corrdinate *this) {
    	this->m_iX = 20;
    }
    

    当我们定义了常量成员函数时:

    void Corrdinate::changeX() const {
    	m_iX = 20;
    }
    

    实际在编译时会变成

    void changeX(const Corrdinate *this) {
    	this->m_iX = 20;
    }
    

    此时的this指针是个常量指针,不能用于改变其所指对象的值

    还要注意的是,由于我们定义了两个版本的changeX函数,你还要弄明白什么时候会调用哪个changeX

    int main()
    {
    	Corrdinate cor1(3, 5);
    	cor1.changeX();			//此时调用的是void changeX()
        const Corrdinate cor2(3, 5);	//常量对象
    	cor1.changeX();			//此时调用的是void changeX() const
    }
    

    当成员函数的 const 和 non-const 版本同时存在时:

    const object 只能调用 const 版本,non-const object 只能调用 non-const 版本

    const object(data members不得变动) non-const objectdata members可变动)
    const member function(保证不更改data members)
    non-const member function(不保证) 不可
  • 相关阅读:
    3.node.js AssertionError: false == true错误解决
    9.获取当前时区时间和utc时间的工具方法
    2.Express封装mysq方法
    1.Express初识
    poj 3617
    前缀和
    pop 反序列化
    Reverse前两个题
    前两个Web题
    Misc
  • 原文地址:https://www.cnblogs.com/zhxmdefj/p/11524403.html
Copyright © 2020-2023  润新知