C++ Primer学习总结 第15章 面向对象程序设计

1、 构造基类和派生类

其中A类是基类,B类是派生类。
继承关系中,每个类必须控制它自己的成员初始化过程。即B类必须使用A的构造函数来初始化基类部分。也就是B的构造函数必须是重写过的(不是继承A的)。

派生类可以覆盖基类的虚函数,但是也可以不覆盖(而使用基类的)。

基类的静态成员:基类定义一个静态成员,那么基类与所有派生类都共同拥有这仅有的一个静态成员。

#include<bits/stdc++.h>
using namespace std;
class A{
public:	
	A(int v):v(v){}
	virtual void print(){
		cout << v << endl;
	}


	virtual void print1(){
		cout << v << endl;
	}
	int v;
	static int x;
};
int  A::x=1;

class B:public A
{
public:	
	B(int v):A(v){}//调用A的构造函数



	void print1()override{
		cout << v*10 << endl;
	}


};

int main() 
{
	B b(10);
	b.print1();//100
	b.print();//10,并没有重写覆盖。
	A *p = &b;//p是A类指针,动态绑定。虽然静态类型是A,但是指向b的
	p->print1();//100
	cout << b.x<<endl;//B类可以直接使用基类的静态成员
    return 0;
}






2、深入理解动态绑定。

静态绑定:对象在声明时采用的类型,是在编译期确定的。
动态绑定:目前所指对象的类型。是在运行期决定的。对象的动态类型可以更改,但是静态类型不可以更改。

#include<bits/stdc++.h>
using namespace std;

class A{
public:	
	A(int v):v(v){}
	void print(){
		cout << v << endl;
	}
	virtual void print1(){
		cout << v << endl;
	}
	int v;
};


class B:public A
{
public:	
	B(int v):A(v){}//调用A的构造函数
	void print(){//子类重新定义了no—virture 函数,这是一个不好的设计,会导致名称遮掩。这里只是解释动态与静态绑定。
		cout << v * 10 << endl;

	}

	void print1()override{
		cout << v*10 << endl;
	}

};

int main() 
{
	
	B *pb = new B(10);
	A *pa = pb;
	pb->print();//100    静态  
	pa->print();//10

	pa->print1();//100   动态
	pb->print1();//100
}

print函数不是虚函数,所以pb调用时对应的是B类的,pa调用的是A类的,也就是静态绑定。
而print1函数是虚函数,所以两个调用都是同一个函数(B类的),也就是动态绑定。
PS:绝不要重写no——virture 函数(违背is-a,“不变性凌驾于特异性之上”)
一句话总结:只有虚函数才是用动态绑定,其他的全部都是静态绑定。






3、虚函数与默认实参

虚函数是动态绑定的,但是为了执行效率,缺省参数是静态绑定的。

如果缺省值也是动态绑定的,那么编译期就必须要有办法在运行期间为virtue函数决定适当的缺省值,如果是这样的话,就要比目前的在编译期决定的机制更慢而且更复杂。

话说每次派生类都提供缺省值好麻烦,并且修改不容易。怎么解决呢?
NVI,non-virtual绝对不会被重新改写。

#include<bits/stdc++.h>
using namespace std;

class A{
public:	
	A(int v):v(v){}
	
	virtual void print(int x = 100){
		cout << x << endl;
	}	

	int v;
};


class B:public A
{
public:	
	B(int v):A(v){}//调用A的构造函数
	
	void print(int x = 10)override{
		cout << x + 111 << endl;
	}

};

int main() 
{
	B b(10);
	A &p = b;
	p.print();//100 + 111 = 211,缺省参数静态绑定。
}







4、三种访问权限与三种继承方式。

三种访问权限:
public:对本类,子类,外部(调用方) 均可见
protect:对外部调用不可见
private:对外部、子类不可见

三种继承方式:
不影响子类对父类的访问权限,子类对父类只看父类的访问控制权。
继承方式为了控制子类调用方对父类的访问权限。
相当于把父类的public的权限在子类中变成了相应的权限。






5、派生类可以访问基类中成员的访问权限。
但是仅限于那些可访问的基类成员(基类访问权限是private不可以)。使用using声明即可。


class A{
public:
	A(int v):v(v){}
protected://如果此时是private的,子类也不可用using改变了(子类本身不能访问)
	int v;
};

class B:private A{//private派生
public:
	using A::v;//A::v变成public了(取决于using在哪个访问权限下面) B的用户可使用v了

	B(int v):A(v){}//调用基类的构造函数
};

int main(){
	B b(10);
	cout << b.v << endl;//B的用户可访问了
	return 0;
}





6、只有在派生类中才可以通过访问派生类对象访问基类的protected成员。而基类对象不可以。


class A{
protected:
	void f(){cout << 1 << endl;}
};

class B:public A{
public:
	void get(){
		f();	
		B b;
		b.f();//派生类对象为什么可以访问基类protected?
		// A a;
		// a.f();//基类对象不可以访问protected
	}
};

int main(){
	B b;
	b.get();
}





7、除了重新定义虚函数外,派生类的成员一定不要和基类的成员同名。且一定要在派生类重新定义的虚函数面前加上virtual关键字(或者override)。
如果基类有序函数virtual void p();的话,那么派生类如果定义一个函数void p();由于该函数与基类的虚函数同名且调用形式相同,所以void p()也是虚函数。
如果派生类中的函数是 void p(int x = 1)的话,那么这个函数不是虚函数并且掩盖了基类虚函数名称p()。

class A{
public:
	virtual void f(){cout << 1 << endl;}
};

class B:public A{
public:
	void f(int x = 1){ cout << 2 << endl;}
};

int main(){
	A a;
	B b;
	A *pa = &b;
	pa->f();//1,

	pa = &a;
	pa->f();//1


	b.f();//2
	a.f();//1
}

分析:第一个pa->f(); 此时pa = &b,因为pa是一个A类指针,所以编译器在类A中寻找f函数,但是它发现f函数是virtual的,所以动态绑定B类的virtual函数,但是B类的虚函数f是A的默认实现,所以最终还是调用A::f();

第二个pa->f()类似第一个,绑定A类直接调用。
第三个第四个都是静态绑定。






8、多态基类的析构函数应该为虚函数。

class A{
public:
	A(){cout << "A constructor" << endl;}
	~A(){cout << "A destructor" << endl;}
};

class B:public A{
public:
	B(){cout << "B constructor" << endl;}
	~B(){cout << "B destructor" << endl;}

};

int main(){
	A *p = new B();
	delete p;

    
}

// A constructor
// B constructor
// A destructor

可以看到:当一个基类指针是非虚的,用它去删除一个派生类对象时,c++将不会调用整个析构函数链,结果是未定义的。只调用了A的析构函数,对象的派生部分并没有被销毁。“局部销毁”。造成资源泄露。


class A{
public:
	A(){cout << "A constructor" << endl;}
	virtual ~A(){cout << "A destructor" << endl;}
};

class B:public A{
public:
	B(){cout << "B constructor" << endl;}
	virtual ~B(){cout << "B destructor" << endl;}

};

int main(){
	A *p = new B();
	delete p;


}

// A constructor
// B constructor
// B destructor
// A destructor





9、关于虚函数表:

当我们创建一个对象b,&b是这个对象的地址。
(int *)&b 强制转化成int *,为了取对象地址的前4个字节,前四个字节是虚表指针。
*(int *)&b 虚表地址
*((int )(int *)&b) 第一个虚函数地址
*((int )(int *)(&b) + 1) )第二个虚函数地址

#include <iostream>  
using namespace std;  
   
class Base1 {  
public:  
            virtual void f() { cout << "Base1::f" << endl; }  
            virtual void g() { cout << "Base1::g" << endl; }  
            virtual void h() { cout << "Base1::h" << endl; }  
   
};  
class Base2 {  
public:  
            virtual void f() { cout << "Base2::f" << endl; }  
            virtual void g() { cout << "Base2::g" << endl; }  
            virtual void h() { cout << "Base2::h" << endl; }  
};  
class Base3 {  
public:  
            virtual void f() { cout << "Base3::f" << endl; }  
            virtual void g() { cout << "Base3::g" << endl; }  
            virtual void h() { cout << "Base3::h" << endl; }  
};  
class Derive : public Base1, public Base2, public Base3 {  
public:  
            virtual void f() { cout << "Derive::f" << endl; }  
            virtual void g1() { cout << "Derive::g1" << endl; }  
};  
   
   
typedef void(*Fun)(void);  
   
int main()  
{  
            Fun pFun = NULL;  
   
            Derive d;  
            int** pVtab = (int**)&d;  
   
            //Base1's vtable  
            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);  
            pFun = (Fun)pVtab[0][0];  
            pFun();  
   
            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);  
            pFun = (Fun)pVtab[0][1];  
            pFun();  
   
            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);  
            pFun = (Fun)pVtab[0][2];  
            pFun();  
   
            //Derive's vtable  
            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);  
            pFun = (Fun)pVtab[0][3];  
            pFun();  
   
            //The tail of the vtable  
            pFun = (Fun)pVtab[0][4];  
            cout<<pFun<<endl;  
   
   
            //Base2's vtable  
            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);  
            pFun = (Fun)pVtab[1][0];  
            pFun();  
   
            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);  
            pFun = (Fun)pVtab[1][1];  
            pFun();  
   
            pFun = (Fun)pVtab[1][2];  
            pFun();  
   
            //The tail of the vtable  
            pFun = (Fun)pVtab[1][3];  
            cout<<pFun<<endl;  
   
   
   
            //Base3's vtable  
            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);  
            pFun = (Fun)pVtab[2][0];  
            pFun();  
   
            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);  
            pFun = (Fun)pVtab[2][1];  
            pFun();  
   
            pFun = (Fun)pVtab[2][2];  
            pFun();  
   
            //The tail of the vtable  
            pFun = (Fun)pVtab[2][3];  
            cout<<pFun<<endl;  
            return 0;  
}  

// Derive::f
// Base1::g
// Base1::h
// Derive::g1
// 1
// Derive::f
// Base2::g
// Base2::h
// 1
// Derive::f
// Base3::g
// Base3::h
// 0






哪些函数不能使虚函数?

1、构造函数不可以。
构造函数用来初始化对象,假如子类可以继承基类构造函数(使用虚函数),那么基类不知道子类有哪些成员。

从存储:对象未创建,vtable不存在,构造函数如果是虚的,那么需要vtable来调用,对象都还没实例化,怎么构造?

2、普通函数,友元函数
3、内联函数:编译时展开
4、静态成员函数:编译时确定

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值