工作已经进入到了11年的工龄了。在我这个年龄大部分人都去做管理岗位了。
对于我来说, 还是忠于编程,编程就是我的兴趣爱好。干一行就爱一行。
开始学习C++的基础知识。在看《C++程序设计语言》的时候,看到引用这章节的介绍时,里面有这么一段代码:
template<class T>
class vector {
T* elem;
// ...
public:
T& operator[](int i) { return elem[i]; }
const T& operator[](int i) const { return elem[i]; }
void push_back(const T& a);
//...
};
void f(const vector<double>& v)
{
double d1 = v[1];
v[2] = 7; //set 7 to v.operator[](2) indirect double
v.push_back(d1);
}
在f函数里面, v[2]被设置成了值7,这里就很困惑。vector不是被设定成了常量吗?怎么可以改变引用对象的值呢?颠覆了我的三观。
看来需要重新复习常量的基础知识了。
一:const修饰成员变量
从基础介绍中了解:const大致意思是“我承诺不改变这个值”。主要用于说明接口,这样在把变量传入函数时就不必担心变量会在函数内被改变了。编译器负责确认并执行const的承诺。一旦我们把某物声明成const,就确保它的值在其作用域内不会发生改变。
对于指针这个类型定义的常量来看。我们要了解指针的一些信息。一个指针牵扯到两个对象,指针本身以及指针所指的对象。所以存在两种指针类型的常量。
1,在指针的声明语句中“前置”const关键字将令所指的对象而非指针本身成为常量。
2,在指针的声明语句中“后置”const关键字将令指针本身成为常量。
如下:
#include<iostream>
using namespace std;
int main(int arc, char **argv){
int a1=3; ///non-const data
const int a2=a1; ///const data
int * a3 = &a1; ///non-const data,non-const pointer
const int * a4 = &a1; ///const data,non-const pointer
int * const a5 = &a1; ///non-const data,const pointer
int const * const a6 = &a1; ///const data,const pointer
const int * const a7 = &a1; ///const data,const pointer
return 0;
}
这是一些有关常用指针的sample。声明运算符 *const的作用是令指针本身成为常量。C++允许把非const变量的地址赋给指向常量的指针,不允许把常量的地址赋给某个不受限的指针。
介绍到这里,我们返回到刚开始写的常量引用是怎么回事?那么还是需要首先理解引用的本质。
引用是一种C++的语言机制,和指针类似,作为对象的别名存放对象的机器地址。与指针相比,引用不会带来额外的开销。
二者之间的区别:
- 访问引用与访问对象本身从语法形式上看是一样的。
- 引用所引的永远是一开始初始化的那个对象
- 不存在“空引用”,我们可以认为引用一定对应着某个对象
引用最重要的用途是作为函数的实参或者返回值。
存在三种形式的引用:
左值引用(lvalue reference):引用那些我们希望改变值的对象。
const引用(const reference):引用那些我们不希望改变值的对象(比如常量)
右值引用(rvalue reference):所引用对象的值在我们使用之后就无须保留了(比如临时变量)
这三种形式统称为引用,其中前两种形式都是左值引用。
引用的实现方式类似于常量指针,每次使用引用实际上是对该指针执行解引用操作。(这种只是辅助理解,引用不是对象,指针式一种对象)。
回到了引发这边博文的核心问题点,const类型的引用。《C++程序设计语言》【第四版】中的第165中有这么一段话:
const T& 的初始值不一定非得是左值,甚至可以不是T类型的。此时:
- 首先,如果必要的话先执行目标为T的隐式类型转换
- 然后,所得的值置于一个T类型的临时变量中
- 最后,把这个临时变量作为初始值
事咧:
double & d1 = 1; //错误:此处需要左值
const double& cdr {1}; //OK
const的这条语句的初始化过程可以理解为:
double temp = double{1}; //首先用给定的值创建一个临时变量
const double& cdr {temp}; //然后用这个临时变量作为cdr的初始值
用于存放引用初始值的临时变量的生命周期从它创建之处开始,到它的引用作用域结束为止。
我写了一段测试代码如下:
#include <iostream>
using namespace std;
void display(int const &ref) {
ref = 7; //对常量引用ref进行重新赋值
cout << ref << '\n';
}
int main() {
int i = 1;
display(i);
int const anotheri = 2;
display(anotheri);
display(2);
display(1 + 2);
display(static_cast<int>(3.14159));
}
编译结果如下:
medeadeMacBook-Pro:C++ medea$ g++ const_test.c -o const_test
clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated [-Wdeprecated]
const_test.c:5:9: error: cannot assign to variable 'ref' with const-qualified type 'const int &'
ref = 7;
~~~ ^
const_test.c:4:25: note: variable 'ref' declared const here
void display(int const &ref) {
~~~~~~~~~~~^~~
1 error generated.
可以看到常量引用是不能赋值的。那么回到最初代码进行再次分析:
T& operator[](int i) { return elem[i]; }
const T& operator[](int i) const { return elem[i]; }
以上可以看到其实对操作符[],有两个重载函数。调用的代码如下:
void f(const vector<double>& v)
{
double d1 = v[1];
v[2] = 7; //set 7 to v.operator[](2) indirect double
v.push_back(d1);
}
对于v[2] = 7,首先执行的是是操作符号[]。那么到底匹配那个重载函数呢?我们用事实来证明,参照下面的代码:
#include <iostream>
template<class T>
class vector {
T* elem;
// ...
public:
T& operator[](int i) { return elem[i]; }
const T& operator[](int i) const { return elem[i]; }
};
void f(const vector<double>& v)
{
double d1 = v[1];
v[2] = 7; //set 7 to v.operator[](2) indirect double
}
int main(int argc, char **argv)
{
vector<double> test;
f(test);
return 0;
}
编译结果如下:
medeadeMacBook-Pro:C++ medea$ g++ const_vector.c -o const_vector
clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated [-Wdeprecated]
const_vector.c:16:10: error: cannot assign to return value because function 'operator[]' returns a const value
v[2] = 7; //set 7 to v.operator[](2) indirect double
~~~~ ^
const_vector.c:9:11: note: function 'operator[]' which returns const-qualified type 'const double &' declared here
const T& operator[](int i) const { return elem[i]; }
^~
1 error generated.
也是编译不过。 难道不能重载。
把const T& operator[](int i) const { return elem[i]; }注释掉后,继续编译结果如下:
medeadeMacBook-Pro:C++ medea$ g++ const_vector.c -o const_vector
clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated [-Wdeprecated]
const_vector.c:15:18: error: no viable overloaded operator[] for type 'const vector<double>'
double d1 = v[1];
~^~
const_vector.c:8:8: note: candidate function not viable: 'this' argument has type 'const vector<double>', but method is not marked const
T& operator[](int i) { return elem[i]; }
^
const_vector.c:16:6: error: no viable overloaded operator[] for type 'const vector<double>'
v[2] = 7; //set 7 to v.operator[](2) indirect double
~^~
const_vector.c:8:8: note: candidate function not viable: 'this' argument has type 'const vector<double>', but method is not marked const
T& operator[](int i) { return elem[i]; }
^
2 errors generated.
通过上面两次实验,可以知道。首先:
1,main函数中调用f函数,将test对象复制给常量引用const vector<double>& v。
2,由于形参是常量引用,所以调用的函数也必须是const类型。当调用操作符[]的时候,需要匹配const T& operator[](int i) const 函数。
综上所诉,这里应该是书本上写错了。至少是翻译的中文版本是写错了。英文版本有时间在去看看。
到这里const引用的语言机制(编译器行为)已经比较清晰。但是又引发出来了一个新的问题:
引用类似于常量指针,那么const引用从字面上面理解应该代表的是引用住对象的不变性。如果按照上面的语言机制,const引用是可以写入值的,使用一个临时变量保存备份,有什么特殊意义?
这个问题需要答案,期待阅读这篇文章的大牛们帮忙解惑。