C++函数语义学

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,此时因为B的this指针和A的this指针指向同一地方,在VS中不会报错,但是没有调用派生类析构函数终究是不严谨的

	//A1* a1 = new B1();
	//delete a1;  //程序在VS中会报错,因为A1的this指针和B1的this指针不指向同一位置

	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;
}
发布了123 篇原创文章 · 获赞 31 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/qq_40794602/article/details/103836567
今日推荐