按情形的不同,将C++的对象在构造的过程中,将 “=” 运算符左边的操作数的类型分为既不是指针也不是引用,是指针或引用这两种情况。函数调用时的形实结合也对应左边操作数和右边操作数。
引用的本质还是指针,强调一下引用与指针的区别:a、引用不能为空 b、引用一定要初始化 c、引用自带const属性,即初始化之后不能解绑定,引用一旦绑定便不能改变绑定。d、表达式的引用类型会发生退化,指针的则不会。C++的语义使得引用总是表示其绑定的对象,引用只是其绑定的对象的一个别名。所以引用出现的地方,实际相当于用引用所绑定的对象来替换。对于指针T *p, *p同样是p所指向的对象的一个别名。
箭头的方向表示一种类型的表达式可以自动隐式转化为另外一种类型
一个指针左值变量 P1 对另一个指针左值变量 P2 赋值时,P1 便是 P2的一个副本,一个拷贝。但是创建一个引用变量 r = obj,只是为 obj 多创建了一个别名,不存在生成副本的过程。
今天写了一段代码如下:
#include <iostream>
using namespace std;
class Base
{
public:
virtual Base* copy() const= 0;
};
class Derived: public Base
{
public:
virtual Base* copy() const
{
return new Derived;
}
};
void gfunc(const Base& arg)
{
arg.copy();
}
int main()
{
//Derived obj;
gfunc(Derived()); //为了这里能使用临时变量 所以gfunc 的参数类型为定为const &
//但是对于const & 类型对象, 只能调用类的const成员函数 所以这里将Derived 函数定义为 const 成员函数,否则编译错误
return 0;
}
在代码中,为了函数gfunc() 能够接受临时对象作为实参,将 gfunc() 函数的参数类型定义为const &,但是最初 Derived 类中的 copy() 成员函数没有定义为const,在mingw下报错为 passing 'const Base' as 'this' argument discards qualifiers,在VS下的报错可能为Error C2662, cannot convert ‘this’ pointer from ‘const BASE ’ to ‘BASE &’就是说,错误原因为将常量对象的地址传给了 this 指针。
类型T的非const 成员函数的this指针的类型为 T* const, 类型T的const 对象的指针类型为 const T * , 或者说是 T const*,下边详细说明为啥后一类型的数据不能直接赋给前一类型的数据。
第一种情况,左边操作数的类型既不是指针也不是引用,右边操作数的const(这里的const是限定词,不是中心词(innermost 最内部的那个类型说明符))限定符,在传递过程中,可能会丢失。
即当T不是引用和指针类型时,
const T lhs = const T rhs 或者 T lhs = const T rhs 或者 const T lhs = T rhs 都可以。
注意:此时 const T lhs 同 T const lhs 等价(const T rhs 同 T const rhs同样等价)。左边本来就是源处数据的一个拷贝,副本!
第二中情况,左边操作数的类型为指针或引用,右边操作数的const 限定符必须得到保持。
即当T是引用和指针时,
必须是 const Type& lhs = const Type rhs. 或者 const Type& lhs = Type rhs 也可以Type& lhs = Type rhs. 当T是Type*时,结果一样。简单的说,传指针或者引用时,右边操作数的const在左边必须得到保留。另一个角度来说,传引用或者指针时,说明const 限定符能够增强左边操作数引用或指针接收能力。
原因很简单,当T 是引用(innermost 最内部的那个类型),左边操作数绑定到右边操作数,是右边操作数的一个别名,代表了右边操作数。同样,当T是指针时,左边操作数 和右边操作数指向同一个对象。
所以回到最初的那个问题,函数 void gfunc(const Base& arg) 的参数arg的类型是 const Base&, 则arg调用成员方法 arg.copy() 传入的this指针的类型为const Base* (C++的语义使得引用只是一个别名,即arg代表的是其所绑定的 const Base 对象), 但是该方法本身的this 指针的形参类型是 Base*const , 仅从this指针类型上来看,成员方法接受的对象指针类型为 Base*const 或者 Base*, 而const Base*不行,所以报错。于是将成员方法定义为void gfunc(const Base& arg) const 即可,此时的成员方法的this指针行参为 const Base* const, 能够接受&arg的类型(const Base*)。
对于const Base* const, 即 Base const * const, 分为两个部分, [Base const *] [const] , 前一部分是个const 指针,传递时const 得以保留;后一部分是个const,传递时const 既可以保留,也可以丢掉。总结成一结论:1、const常量只能被const& 绑定;2、const* 只能传递给const*;其它地方的const不受控制。
所以说,常量对象只能调用常量成员方法,而不能调用非常量成员方法。换句话说,const常量成员方法,能被const 常量对象和非const 常量对象调用,但是非const成员方法,只能被非const常量对象调用。
对于引用来说,T const& 能够绑定到临时变量上,非const 常量的引用,只能绑定到普通的非临时变量上。
VS2008中,居然支持非const 引用绑定到临时变量,WTF. SB扩展!!!
C++11编译器能够完整的区分左值和右值,引用也分成左值引用和右值引用。右值优先绑定到右值引用上,左值优先绑定到左值引用上,加上const限定符,绑定能力进一步提升:各种类型的引用的绑定能力如下:
非const右值引用 | 非const右值 | ------------- | ------------- | ------------- |
const右值引用 | 非const右值 | const右值 | ------------- | ------------- |
非const左值引用 | ------------- | ------------- | 非const左值 | const左值 |
const左值引用 | 非const右值 | const右值 | 非const左值 | const左值 |
有名的 -> 左值;反过来的逆否命题,右值 ->无名的。
C++11中引入左值和右值,类型推倒过程中(也是传递时要进行的编译过程),需要分为三种情况:1、既非引用也非指针 2、普通的引用和指针 3、万能引用。在万能引用中引入了引用折叠折叠规则,使得万能引用既能表示左值引用,也能表示右值引用。
由于函数的形参总是具名的,即形参总是个左值。当外层函数需要将实参传递给内部函数时,若保证传递过程中的左右值属性不变,则达到了完美转发。
在C++11的类型推导时,总是优先按照传值进行推导,当然指针类型传递必须推断为指针类型,要使参数的类型为引用必须显示的指出来,写出是普通引用& 或万能引用&& 。
传值得到的是一个副本,const 和非 const 随意,毕竟是两份不相关的数据; const_cast<> 一般用来去掉表达式的const,当然也可以用来添加 const,添加const 本身可以隐式进行,不必写出const_cast. const_cast 也只能对指针变量和引用变量进行操作。
template <typename T>
void f(const T& x) //实际上是T const& x
{
std::cout << "My arg is a reference";
}
template <typename T>
void f(const T* x) //实际上是 T const* x
{
std::cout << " My arg is a pointer";
}
double p = 0;
f(&p); //显然&p 的类型为 double*
如果匹配第一个版本,推导出T的类型为double*, 形参的类型为 double* const& ;如果匹配第二个版本,推到出的T的类型为double, 形参的类型为 double const* ,形参将是实参的一个副本,形实结合所以会发生* 到const* 的转换,所以会比第一个版本多了转换的操作( *到 *const是自动的),所以最终匹配的将是一个特化版本。
当模板函数的函数参数为 const T x时,实际上是T const x,因为这里T可能是指针,所以后一种写法更好。const T& x 实际上是T const& x.
当传值时,const 和 非const之间没有相关性,如果既有const 版本,又有const版本,会编译出错。
template <typename T>
void f(const T x) //这里实际上是T const x
{
std::cout << "version 1";
}
template <typename T>
void f(T x)
{
std::cout << "version 2";
}
int main()
{
double p = 0;
f(&p);
return 0;
}
编译出错,因为虽然传的是指针,一种是 double* const, 一种匹配出来 double*, 结果就是无法匹配。
template <typename T>
void f(const T* x) //实际上是T const* x
{
std::cout << "version 1";
}
template <typename T>
void f(T* x)
{
std::cout << "version 2";
}
此时最终会匹配第二个,第一个* 到 const* 多了const 转化,都能匹配,简单的优先。
对于引用,也一样。 在能匹配的前提下,& 优先于 const& 得到匹配。
template <typename T>
void f(const T&& x)
{
std::cout << "version 1";
}
template <typename T>
void f(T&& x)
{
std::cout << "version 2";
}
int main()
{
double p = 0;
f(&p);
return 0;
}
第二个得到匹配,上边模板中函数的参数是万能引用,需要说明的是这里实参是个右值,所以最终推导出的函数的函数类型为右值引用。
template <typename T>
void f(const T& x)
{
std::cout << "version 1 ";
}
template <typename T>
void f(T& x)
{
std::cout << "version 2 ";
}
int main()
{
double p = 0;
double* ptr = nullptr;
f(&p); //第一个版本得到匹配
f(ptr); //第二个版本得到匹配
return 0;
}
当函数的参数类型是普通的引用时,注意右值只能绑定到const 左值引用上,所以 f(&p) 匹配的只能是第一个版本;f(ptr) 则第二个版本优先匹配。
所以在能匹配的前提下,匹配的优先顺序为 T&& > T const && > T& > T const&. 右值引用 > 常量右值引用 > 左值引用 > 常量左值引用。当在模板中,T&&只能表示万能引用,所以实际上只有 T&& > T const&&.