《C++ Primer》(第五版)P208
一个拥有顶层const(const修饰变量自身)的形参无法和另一个没有顶层const的形参区分开来:
Record lookup(Phone);
Record lookup(const Phone); //编译错,重复声明了Record lookup(Phone)
变量或常量的实参都可以值拷贝给变量或常量的形参,因此无法判断调用上面哪个函数,C++干脆禁止形参列表只分是否是顶层const的重载。顶层const的指针也是如此:
Record lookup(Phone*);
Record lookup(Phone* const); //顶层const指针,重复声明了Record lookup(Phone*)
上述两组函数声明中,每一组的第二个声明和第一个声明是等价的。验证:
void overrideFc2(int){ //与下面函数共同存在时编译报错重overrideFc2定义
//值拷贝,实参可以是左值变量或左值常量,或右值
std::cout<<"calling overrideFc(int)\n";
}
// void overrideFc2(const int){ //与上面函数共同存在时编译报错重overrideFc2定义
// //值拷贝,实参可以是左值变量或左值常量,或右值
// std::cout<<"calling overrideFc(const int)\n";
// }
void func8(){
int num(9);
const int cnum(10);
overrideFc2(num); //overrideFc2(int)与overrideFc2(const int)定义了哪个就调用哪个
overrideFc2(cnum); //overrideFc2(int)与overrideFc2(const int)定义了哪个就调用哪个
overrideFc2(10); //overrideFc2(int)与overrideFc2(const int)定义了哪个就调用哪个
}
另一方面,如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的:
void overrideFc(int &){ //引用绑定,实参必须是左值变量
std::cout<<"calling overrideFc(int &)\n";
}
void overrideFc(const int &){ //引用绑定,实参可以是左值常量或左值变量,或右值
std::cout<<"calling overrideFc(const int &)\n";
}
void overrideFc(int *){ //值拷贝,实参必须是指向变量的指针左值,或变量的地址(右值)
std::cout<<"calling overrideFc(int *)\n";
}
void overrideFc(const int *){ //值拷贝,实参可以是指向变量的指针或指向常量的指针,或常量的地址(右值)
std::cout<<"calling overrideFc(const int *)\n";
}
void func7(){
int num(7);
overrideFc(num); //如果只有overrideFc(const int &)则调用之,否则优先调用overrideFc(int&)
const int cnum(8);
overrideFc(cnum); //只能调用overrideFc(const int &)
overrideFc(9); //calling overrideFc(const int &)
int *pnum(&num);
overrideFc(pnum); //如果只有overrideFc(const int *)则调用之,否则优先调用overrideFc(int*)
overrideFc(&num); //变量的地址(右值),如果只有overrideFc(const int *)则调用值,否则优先调用overrideFc(int*)
int const *cpnum(&num); //底层const
overrideFc(cpnum); //只能调用overrideFc(const int *)
overrideFc(&cnum); //常量的地址(右值),只能调用overrideFc(const int *)
}
变量实参可以传引用给变量引用,也可以传引用给常量引用,而常量实参只能传引用给常量引用。虽然也具有“歧义”,但相比实参变量或常量都可以值拷贝给形参变量或常量,歧义要少。
因而对变量实参,当只定义有常量引用的形参版本时则调用之,否则优先调用变量引用的形参版本。指向变量的指针也是一样,当只定义有指向常量的形参版本时就调用之,否则优先调用指向变量的形参版本。
特别地,常量引用const int &可以绑定到左值常量或左值变量,或右值。(因为常量引用表明了不会修改所绑定的对象,那么绑定一个右值也不是不行)。但指向常量的指针const int *,可以被变量的地址(右值)进行值拷贝,也可以被常量的地址(右值)进行值拷贝,对变量的地址值(右值),如果只定义有指向常量的指针的形参版本则调用之,否则优先调用指向变量的指针的形参版本。而常量的地址(右值),只能调用指向常量的指针的形参版本。
《C++ Primer》中更精炼的解释:因为const不能转换成其他类型,所以我们只能把const对象(或指向const的指针)传递给const形参。相反的,因为非常量可以转换成const,所以上面的4个函数都能作用域非常量对象或者指向非常量对象的指针。不过,当我们传递一个非常量对象或者指向非常量对象的指针时,编译器会优先选用非常量版本的函数。