2262 字
11 分钟
C++杂记 - 2(三):const 重载、函数签名与 final
2022-07-20

关于const函数重载。修饰(成员)函数的const算作函数签名的一部分#

const修饰函数参数一般不作为函数签名的一部分。除非修饰的参数是指针或引用。并且需要是修饰的是底层。#

  • const修饰的函数参数是值的时候不可以进行重载。如下代码编译错误。
class myobj{
public:
myobj(int x):cal(x){}
int cal;
};
void func1(myobj a){
cout << "non const" << endl;
}
void func1(const myobj a){
cout <<"const" << endl;
}
  • const修饰的函数参数是指针或引用的时候,并且修饰的是底层的时候可以进行重载。

  • volatile也适用于此规则。

  • 以上例外const必须修饰的是指针指向的内容, 即ponter-to-const;

    • 如果是指针本身是const的,则不能重载
  • 原因是函数签名不包括形参列表参数的顶层CV限定

    • 顶层const表示指针本身是个常量。顶层修饰的是对象本身。

      • myobj* const b = new myobj(20); const指针b指向myobj
    • 底层const表示指针所指向的对象是个常量。

      • const myobj* b = new myobj(20);指针b指向const myobj
//指针
void func(myobj* a){
cout << "non const" << endl;
}
void func(const myobj* a){
cout <<"const" << endl;
}
//引用
void func1(myobj& a){
cout << "non const" << endl;
}
void func1(const myobj& a){
cout <<"const" << endl;
}
int main(){
myobj* a = new myobj(10);
const myobj* b = new myobj(20);
func(a); //输出non const
func(b); //输出const
myobj obj(10);
myobj& refa = obj;
const myobj& refb = obj;
func1(refa); //输出 non const
func1(refb); //输出 const
return 0;
}
  • const引用可以绑定到非const对象,但是非const引用不可绑定const对象。因为不能保证其不被修改。
string t = "234"; //非const对象
const string& y = t; //可以 const引用
const string s = "234"; //const对象
string& b = s; //不可以 非const引用
  • 所以函数入参为引用的时候,非const对象可以传入const引用参数。也就是const引用绑定到非const对象。比如作用是防止修改。但是const对象不可传入非const引用参数。也就是非const引用不可以绑定到const对象。
void test(const string& a){
cout << a << endl;
}
void test2(string& a){
cout << a << endl;
}
int main(){
const string str1 = "234";
string str2 = "234";
test(str1);
test2(str1); //不可以。const对象不能传入非const参数
test(str2);
test2(str2);
}
  • 值传递不受影响

const如果放在函数后面(修饰成员函数)只能放在类成员函数后面#

先看代码

class myclass{
public:
int vala;
int valb;
myclass(int x, int y):vala(x), valb(y){}
void changethis(myclass& obj)const {
cout <<"const" << endl;
}
// void changethis(const myclass* const this, myclass& obj) const{
// cout <<"non const" << endl;
// }
void changethis(myclass& obj) {
cout <<"non const" << endl;
}
// void changethis(myclass* const this, myclass& obj) {
// cout <<"non const" << endl;
// }
};
int main(){
myclass obj(10,20);
const myclass obj1(10,20);
obj.changethis(obj); //调用不带const的
obj1.changethis(obj); //调用带const的
return 0;
}

const放在类成员函数后面的意义是,不可以修改当前调用对象的数据成员的值。切记注意,入参是否可以被修改是不归函数管的。函数加const只是负责禁止修改调用方。在这里也就是禁止修改调用方obj1的值。

const修饰成员函数修饰的是什么?是this指针#

我们在上面代码段看到了两段被注释掉的函数,里面写出了如果显式this指针会是什么样。

  • 注意,由于this指针不能改变指向,所以this指针是一个常量指针。也就是myclass* const this。

  • 如果我们不想修改一个参数的值,办法就是让这个参数被const修饰。在这里就是把this指针也用const修饰。则会变成const myclass* const this。这也说明了为什么const只能修饰成员函数,因为非成员函数没有this指针。

  • 又由于this指针会被编译器根据对象来隐式传入。所以const对象的this指针必然是也是常量指针。所以自然const对象无法调用非const函数。因为this指针的不匹配。const可以被隐式(函数调用)加上但是不能被去除。也就是权限的缩紧是可以的,放松是不可以的。

  • 简单理解就是因为const成员无法进行修改。如果const成员变量调用了非const函数,函数不能保证不修改const成员,如果修改了会报错,所以直接加以限制。当然了非const成员因为可以修改也可以不修改,都不会报错。所以自然可以调用const函数也可以调用非const函数。

  • const成员函数不能调用非const成员函数。因为在const成员函数里面的this指针被const修饰,在const成员函数里面调用非const成员函数时,相当于将const修饰的this指针传给非const成员函数。

    • 也可以理解为权限的扩张是禁止的。不可以修改(整体)-> 可以修改(局部)是不可以的。
  • 非const成员函数可以调用const成员函数的原因是:权限的缩紧是可以的。

    • 也就是:可以修改(整体)->不可以修改(局部)这样是可以的

无意间发生的const与非const冲突#

class myclass{
public:
int vala;
int valb;
myclass(int x, int y):vala(x), valb(y){}
void func(){
//一些内容
}
void changethis(const myclass& obj){
obj.func(); //错误。const对象(引用)不能调用非const函数
}
};
  • 我们看到了。经常性的我们会使用常量左值引用做为函数入参签名,一是防止拷贝二是防止修改。但是我们可能需要通过这个对象调用其内部的一些函数。此时就不可以通过const引用调用非const函数

注意 this指针并不是对象本身的一部分。#

注意 this指针并不是对象本身的一部分。也不会影响sizeof的结果。它的作用域是在类的内部。this指针只不过会在调用非静态函数的时候被编译器隐式加入。

unknown

关于const函数重载。const算作函数签名的一部分#

我们在上面代码可以看到,我们有一个函数重载。因为const算作函数签名的一部分。

但是编译器如何判断我们调用的是哪个函数呢?虽然表面参数一样,但其实隐藏的this形参是不同的。

  • 所以如果我们调用方是const对象,则会调用const版本的函数。如果是非const对象就调用非const版本的函数。

unknown (1)

为什么返回值类型不能做为函数重载的依据?也就是为什么仅返回值不同的函数不可重载?#

  • 理论上来说就是函数的返回值不是普通函数签名的一部分。
  • 实际上来说是有二义性。比如
int func();
string func();
int main(){
func(); //调用哪个func?
return 0;
}

如上面代码,调用func的时候编译器无法确定到底调用哪个func。

虽然返回值不是普通函数签名一部分,但是在链接期,匹配的不仅是函数的签名,还包括其类型。所以返回值依旧要匹配(个人理解)

函数签名总结#

  • 普通函数的函数签名包括:
    • 函数名(非限定名)或者 生成该函数的函数模板名称
    • 参数类型和个数(参数列表)。如果函数是从函数模板中生成的,那么指的是替换前的模板参数
    • 函数名称所属的类或命名空间作用域;如果函数名称拥有内部链接,还包括该名称声明所在的编译单元。
    • 如果是类成员函数则还包括函数的:
      • const限定符
      • volatile限定符
      • ref限定符
  • 函数模板的签名除了上面的以外还包括:
    • 返回值类型
    • 模板参数和模板实参
    • 格外注意,函数模板的签名包括返回值类型导致的表面说“函数模板的重载“其实是一种重载决议。被不同类型实例化的函数模板是不同的函数。
  • 函数参数的const一般不算做函数签名。除非该参数为引用或指针
    • 普通参数的const不算做函数签名是为了兼容C。
    • C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。

普通函数的函数签名不包括返回值类型。也就是无法仅通过返回值类型的不同进行函数重载

关键字 final#

  • final关键字的意义是阻止子类重写自身(父类)的虚函数或禁止继承。
  • 一般不用在父类,因为没有意义。
  • 一般用在某个子类防止子类的子类重写子类的虚函数 或者是 禁止继承。
C++杂记 - 2(三):const 重载、函数签名与 final
https://sterben-01.github.io/posts/杂记2-3/
作者
Sterben_01
发布于
2022-07-20
许可协议
CC BY-NC-SA 4.0