欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C++ const 引用 指针

程序员文章站 2022-07-02 13:02:32
先简单回忆一下常量的性质: 初始化时: 当用一个对象去初始化另外一个对象,他们是不是const就无关紧要 ci是整形常量,但ci的常量特征仅仅在执行 改变ci 的操作时才会发挥作用 const和引用 对常量的引用 把引用绑定到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是一个普通的非常量引用

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

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绑定了一个临时量对象,这下你可看懂上面的代码发生了什么了吧

你可以想象以下,如果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;
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
const double homo2 = 5.14;
cptr = &homo2;  
cout << "cptr = " << *cptr <<endl;
//允许一个 指向常量的指针 指向 一个非常量对象

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

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关键字之前,用以说明指针是一个常量(即不变的是指针本身的值)

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上