1 函数调用方式
1.1 普通成员函数调用方式
我们或许会认为调用类成员函数的开销会大于调用普通函数,但是其实不是这样的,调用普通成员函数和全局函数开销差不多,我们可以在VS中调试,查看反汇编代码。普通成员函数在调用的时候编译器会在传递一个对象的this指针
1.2 虚函数的调用方式
EG:
#include<iostream>
using namespace std;
class A
{
public:
int a;
virtual void func()
{
}
void test1()
{
func(); //由于是用this指针调用func函数,所以会查询虚函数表
}
void test()
{
A::func(); //这种方式调用func函数不会查询虚函数表
}
};
int main()
{
A a = A();
a.func(); //不会查询虚函数表
a.test(); //test函数内部调用func函数不会查询虚函数表
a.test1(); //test1函数内部调用func函数会查询虚函数表
return 0;
}
1.3 静态成员函数的调用
EG:
#include<iostream>
using namespace std;
class A
{
public:
int a;
static void func()
{
cout << "静态成员函数" << endl;
}
void func1()
{
cout << "非静态成员函数" << endl;
}
void func2()
{
a = 3;
}
};
int main()
{
A *a = nullptr;
a->func();
//这里有疑问,为什莫a为空也可以调用func函数呢?
//因为,静态成员函数不需要编译器传入this指针,所以ok
a->func1();
//既然a为空指针,为什莫也可以调用非静态成员函数呢?
//因为即使编译器传入了一个this指针,但是在func1中并没有使用this指针,所以即使this指针传入的是空指针也没事
//a->func2(); //执行这行代码程序异常,由于func2中使用this指针为成员变量a赋值,但是this是一个空指针
return 0;
}
2 C++的静态与动态
2.1 静态类型与动态类型
静态类型:对象定义时的类型,在编译期间被确定
动态类型:对象目标所指的类型,在运行时决定(伴随着引用和多态)
EG:
#include<iostream>
using namespace std;
class A
{
};
class B : public A
{
};
int main()
{
A a; //a的静态类型是A,没有动态类型,因为是直接定义没有使用指针或者引用
B b; //b的静态类型是B,没有动态类型
A* a1; //a1的静态类型是A,目前没有动态类型,因为a1没有指向任何对象
A* a2 = new B(); //a2的静态类型是A,动态类型是B
return 0;
}
2.2 静态绑定与动态绑定
静态绑定:绑定的是静态类型,所对应的函数或者属性依赖于对象的静态类型,发生在编译期
动态绑定:绑定的是动态类型,所对一个的函数或者属性依赖于对象的动态类型,发生在运行期
2.3 非虚函数的静态绑定
EG:
#include<iostream>
using namespace std;
class A
{
public:
void func()
{
cout << "A::func" << endl;
}
};
class B : public A
{
public:
void func()
{
cout << "B::func" << endl;
}
};
int main()
{
B b;
B* b1 = &b;
b1->func(); //func是普通成员函数,发生静态绑定,b1的静态类型是B,所以调用B::func
A* a1 = &b;
a1->func(); //func是普通成员函数,发生静态绑定,b1的静态类型是A,所以调用A::func
return 0;
}
2.4 虚函数的动态绑定
EG:
#include<iostream>
using namespace std;
class A
{
public:
virtual void func()
{
cout << "A::func" << endl;
}
};
class B : public A
{
public:
virtual void func()
{
cout << "B::func" << endl;
}
};
int main()
{
B b;
B* b1 = &b;
b1->func(); //func是虚函数,发生动态绑定,b1的动态类型是B,所以调用B::func
A* a1 = &b;
a1->func(); //func是虚函数,发生动态绑定,b1的动态类型是B,所以调用B::func
return 0;
}
2.5 虚函数中缺省参数坑
虚函数中如果有缺省参数,那么这个缺省参数的值是静态绑定
EG:
#include<iostream>
using namespace std;
class A
{
public:
virtual void func(int i = 0)
{
cout << "A::func i:" << i << endl;
}
};
class B : public A
{
public:
virtual void func(int i = 1)
{
cout << "B::func i:" << i << endl;
}
};
int main()
{
B b;
B* b1 = &b;
b1->func(); //调用B::func,打印的i的值是1
A* a1 = &b;
a1->func(); //调用B::func,打印的i的值是0(注意)
return 0;
}
3 多继承中虚函数探究
3.1 虚基类继承中析构函数的调用
EG:
#include<iostream>
using namespace std;
class A
{
public:
~A()
{
cout << "A::~A" << endl;
}
};
class B :public A
{
public:
~B()
{
cout << "B::~B" << endl;
}
};
class A1
{
public:
~A1()
{
cout << "A1::~A1" << endl;
}
};
class B1 :public A1
{
public:
virtual ~B1()
{
cout << "B1::~B1" << endl;
}
};
class A2
{
public:
virtual ~A2()
{
cout << "A2::~A2" << endl;
}
};
class B2 :public A2
{
public:
~B2() //由于继承的A2有一个虚析构函数,所以本类中的析构函数也是虚函数
{
cout << "B2::~B2" << endl;
}
};
int main()
{
//A* a = new B();
//delete a; //因为析构函数不是虚函数,所以发生静态绑定,只会调用A的析构函数,但是a的动态类型是B
//A1* a1 = new B1();
//delete a1; //程序会报错
A2* a2 = new B2();
delete a2; //程序正常执行,符合预期,所以在发生有虚函数的继承时,应当给基类添加虚析构函数
return 0;
}
继承中,只要调用了子类的析构函数,那么就算是对内存释放成功,因为编译器会在子类的析构函数中去调用基类的析构函数,就像递归一样,直到顶层基类的析构函数被调用才会逐层返回
3.2 释放无虚析构函数第二基类引发的错误
EG:
#include<iostream>
using namespace std;
class A1{};
class A2{};
class B :public A1, public A2{};
int main()
{
A2 * a2 = new B();
delete a2; //由于返回的是B类实例偏移后的指针,所以不是对象首地址指针,调用delete会发生异常
//解决办法1
//将a2转换为B类
//解决办法2
//给A2类添加一个虚析构函数
return 0;
}
来源:CSDN
作者:浅若如初
链接:https://blog.csdn.net/qq_40794602/article/details/103836567