C++类中支持三种类型的成员函数
- 静态函数(static)、非静态函数(non-static)和虚函数(virtual)
静态函数不能声明为const,更不能操作非静态变量
原因:静态成员函数没有this指针
非静态成员函数
- 成员函数在编译时内化为非成员函数
- 添加
this
指针 - 非静态变量前加
this->
- 函数名改编(mangling)
- 添加
double Point3d::normalize() const {
return sqrt(x * x + y * y + z * z);
}
// c++伪代码描述非成员化过程,使用了NRV优化
double normalize_9Point3dFv(register const Point3d const *this) {
return sqrt(this->x * this->x + this->y * this->y + this->z * this->z);
}
如果函数是const
隐含的this指针加const,表示这个this指向的东西是const的,也就是说这个函数中无法改动数据成员了,相当于只读操作
double Point3d::normalize() const; // 扩张后: double Point3d::normalize(const Point3d* const this); // 非const函数 double Point3d::normalize(Point3d* const this);
名字改编(name mangling)
- 起初为了解决函数重载问题
- 重载函数应该在参数个数或参数类型上有所区别,否则编译器将无法确定调用哪一个重载版本,即使是返回类型不同,也无法区分。例如:虽然这两个函数的返回值类型不同,但是函数的参数个数和类型完全相同,编译器将无法区分这两个函数。
int mul(int x,int y); double mul(int x,int y);
- 编译器通过函数名和其参数类型识别重载函数。为了保证类型安全的链接,编译器用参数个数和参数类型对每一个> 函数标识符进行专门编码,这个过程有时称为“名字改编”。类型安全的连接使得程序能够调用合适的重载函数并保证了参数传递的一致性。编译器能够检测到并报告连接错误。
虚成员函数
- 通过
指针
调用虚成员函数时:
ptr->normalize();
// 内化为
(*ptr->vptr[1])(ptr);
- 如果在
成员函数内部
调用另一个虚函数
,为了提高效率可以直接显示的调用虚函数,这样可以压制由于虚拟机制而产生成本,他的决议方式和非成员函数一样,同时还可以inline虚函数提高效率
double point = Point3d::normalize();
// 内化为
double point = normalize_9point3ddFv(this);
- 通过
对象.
的方式直接调用虚函数
,也能实现非成员函数的内化方式,此时虚函数也可以inline化提高效率。
obj.normalize();
// 看似:
(*obj.vptr[1])( &obj );
// 实则:
normalize_9point3ddFv( &obj );
虚函数被声明为inline时,是否真正的被inline了?
虚函数可以被声明为inline,这是毫无疑问的,因为inline同register一样,只是对编译器的建议。
主流思想:virtual的意思是等到运行时再决定调用哪个函数,inline的意思是在编译期间将调用之处用被调函数来代替",
- 其实上面的观点是不正确的,如果virtual可以在编译期决定调用什么函数,那么就可以被inline!
- 例如:用一个类对象通过成员选择符
.
调用虚函数,如obj.vf()这时虚函数vf()就可以被优化内联展开。这样调用等于告诉编译器你要调用的具体函数,在函数有inline修饰或是体内定义的情况下就会被内联展开。- 当然还有其他情况可以被inline如果编译器在编译的时候就可以确定该虚函数的决议,则编译器以inline方式静态决议该虚函数。如果编译器在编译的时候不能决定,则必须在运行时决议虚函数,此时虚函数不能以inline函数的方式调用。
静态成员函数
static成员函数的来源:
如果没有任何一个成员变量被直接操作,事实上就不需要this指针,因此也就没必要通过一个对象来调用一个成员函数。
- 独立于对象之外的操作,为了解决在没有对象存在的情况下依然能调用一个成员函数:将0强制转换为一个类指针,但是函数内并不会使用它。
normalize((Point3d*) 0 ); // 解决之道提供一个空指针
- 静态成员函数主要特性
没有this指针
,因此导致:- 不能直接读写类中的非静态成员变量
- 不能被声明为
const
volatile
virtual
- 只能通过类作用域调用
- 由于缺乏this指针,因此差不多等同于非成员函数
- 如果一个静态变量被
private
化,那么必须提供一个static函数
才能读写它(例如单例模式)