关于函数重载实际上我理解不是很深入,直接导致const成员函数这块出大问题,在阅读《effective C++》后才恍然大悟,做如下学习记录
再看函数重载
关于函数重载的概念我就不多说了,这里我要讨论的是一种我们常常忽视的函数重载:
下面这两个函数构成函数重载吗?
void fun(int a)
{
cout << "int a" << endl;
}
void fun(const int a)
{
cout << "const int a" << endl;
}
答案是不构成!这种写法是错误的,编译器会报错对于fun函数的重定义!
那下面两个函数构成函数重载吗
void fun(int *a)
{
cout << "int *a" << endl;
}
void fun(const int *a)
{
cout << "const int* a" << endl;
}
int main()
{
int a = 1;
const int b = 2;
fun(&a);
fun(&b);
return 0;
}
答案是构成函数重载😅,当我知道这个特性的时候人都麻了,上面的程序的运行成果如下图:
那么这个特性实际上又会引出另一个特性:const成员函数重载
作为上面两个用例的引申,请判断一下下面两个 类C 的成员函数是否构成重载?
class C
{
public:
void fun()
{
cout << "i am not const" << endl;
}
void fun() const
{
cout << "i am const" << endl;
}
};
int main()
{
C x;
const C y;
x.fun();
y.fun();
}
答案是肯定的,问题就处在C++类的this
指针上,const
成员函数的const
修饰的是被类成员函数的参数列表省略的this
指针,而非const
this指针就和const
this指针就构成了重载。这种重载是相当隐晦,很长时间我都没有理解明白其中原理。
const成员函数
接下来我们将对const成员函数的特性进行一系列的讨论
众所周知const成员函数是:const对象才能调用的函数,非const类对象只能调用非const类对象
而const 代表的是不被修改——换句话说就是const修饰对象会被保护?
首先我们先复习一下指针的const特性,下面两种定义方式的区别是什么?
const int* a = new int;
int* const a = new int;
const
修饰的是指针变量a所存储的地址所对应的内存const
修饰的是指针变量a
保安(const)能保护所有人(类成员)吗?
struct A
{
//真的能保护成员变量吗
void func1() const
{
a = 10; //错误的 ,不能修改
cout << a << endl;
}
const int* func2()const //注意返回值类型
{
return &a;
}
const int& func3()const //注意返回值类型
{
return a;
}
int a=1;
};
int main()
{
const A a;
}
这时你定义了一个const对象a,他在调用成员函数时那个隐藏的this指针显式的写出来为const A* this
,这里的const修饰的指针变量存储地址所对应的内存,所以你无法修改对象a里面任何的字节!
但是我们可以看一下下面的情况
class A
{
public:
A()
{
p = new char[100];
strcpy(p, "hello world");
}
void func() const
{
p[5] = '$';
}
char* p;
};
int main()
{
const A a;
a.func();
cout << a.p << endl;
}
在这个Demo中我们定义了一个类A的常量对象,并在常量成员函数func
中对成员函数进行修改,结果就是字符串的内容被修改成功了。常量对象的成员变量被修改了,逻辑上违背了const的定义,所以这时一个很大的坑,但是语法上是没有问题的,解释如下👇
虽然对象a是一个const对象,但是只能代表指针变量这个变量的值是不变的,并不能保证指针变量所指向内存的不变。所以字符串“hello world”还是可以修改的。
这样就会让我们在编写代码的时候,逻辑上认为自己定义了一个“const的对象“,但是可能在使用时对数据发生了改变。
所以认为const能保护所有的成员对象的想法其实并不靠谱
mutable修饰类成员
对于某些情况我们想要修改部分const对象的成员变量值,但是const这个属性是对所有成员变量所施加的,这样会导致一些很麻烦的问题
在一个类A中我们希望部分一个const对象的a和b可以修改,但是c不能修改
class A
{
public:
void func() const
{
a = 10; //错误的行为
b = 20; //错误的行为
}
int a;
int b;
int c;
};
int main()
{
const A a;
a.func();
}
上面的代码中定义了一个const对象,这个const属性也继承到了三个成员变量身上,所以func中修改a和b的值是非法的。
如果我们想要部分的const成员变量可以修改,可以加上mutable
关键字来修饰。
class A
{
public:
void func() const
{
a = 10;
b = 20;
}
mutable int a;
mutable int b;
int c;
};
int main()
{
const A a;
a.func();
}
加上mutable
之后,上面的代码就正确了
const/非const成员函数的复用
对于const和非const成员函数两者的功能是类似的,在一定情况下是可以代码复用的,示例如下:
我们在模拟实现stl的string 类型时对于const char& operator[](int pos) const
和char& operator[](int pos)
两个函数就可以使用强制类型转化进行代码复用。
class String
{
const char& operator[](int pos) const
{
//省略边界检查等一系列操作
return p[pos];
}
char& operator[](int pos)
{
return const_cast<char&>(static_cast<const String>(*this)[pos]);
}
char p[];
};
但是注意你可以用const成员函数去复用非const成员函数,但是反过来的复用(即用非const成员函数去复用const成员函数)就是一种非常不安全的做法!