60,dms(02)

/*
达内学习  C/C++FAQs 02 day58 2013-11-21
C/C++ 的一些常见问题
*/
一,构造和析构函数中调用虚函数
多态的前提:虚函数 + 指针/引用
基类的构造和析构函数中调用虚函数不表现多态,都是调用的父类。
class A{
virtual void foo()
};
class B:public A{
public:
   b.foo();

};
B b;
b.foo();
B继承A,当A的构造函数被B的构造函数调用时,b对象尚不能说是B类型的(未构造B特有部分),他只能表现出A类型的外观和行为,因此当虚函数被调用时,他实际被绑定到A::foo()
当A的析构函数被B的析构函数调用时候,b对象已经不在是B类型(B特有部分已经被析构),他同样表现出A类型的外观,因此当虚函数foo被调用时,他实际被绑定到A::foo().
在构造和析构函数中调用纯虚函数,其结果是未定义的。this 指向被构造或调用的对象。
总之在构造和析构函数中调用析构函数绝不可能表现出多态.实际调用的都是基类的。
在构造和析构函数中通过调用完备子类对象调用虚函数,其多态性不受任何影响。
********《《《覆盖虚函数和访问权限无关》》》
基类虚函数为 public,子类中覆盖成 private,通过A类型的引用 多态调用子类的函数仍然有权限,编译器检测父类型通过,运行时和防问权限无关。
二,重载 、覆盖 、隐藏
>>>>重载(overload):在C++语言中的重载,就是为不同的函数使用同一个标识符。并且这些函数都位于同一个作用域且参数列表不同。
C++语言中,实际函数名由其声明名和参数表类型信息组合而成。
继承不能改变作用域,子类和父类的函数不构成重载,子类和基类各自拥有独立的作用域,除非通过using 声明,把基类中的同名函数引入子类作用域。
子类中的和基类中的函数名相同,子类隐藏基类。
当编译器在子类中查找和父类中相同的函数时,父类中函数隐藏看不到,若和子类的参数类型不一致直接报错。
>>>>覆盖(重写 override):只能在基类中存在虚函数时才能发生,返回值和参数表都要相同,函数原型(const控制,返回类型,参数表)要一致。
和父类虚函数之间  只有返回类型不同 报错 不是隐藏也不是覆盖  ->非法覆盖
const 函数控制不同只能构成隐藏
访控属性对重载 隐藏 覆盖的判断不构成影响
子类覆盖基类的虚函数,子类中写不写 virtual 都是一样的,而且只是形成覆盖子类中的版本也是虚函数,子类可以隔代覆盖,甚至该虚函数在继承链中被隐藏,也可以覆盖。
一般,若子类覆盖了基类中的虚函数,则他们必须具有相同的返回类型(为了让编译器知道用什么类型的匿名变量存放返回值)虚函数调用固然在运行时候动态绑定,但是传参和接受返回值的堆栈操作,确实在编译阶段生成,编译器必须按照统一的接口(基类中的函数原型)规格,生成这些指令。只要保证子类的覆盖版本和基类具有相同的原型, 就可以保证无论调用哪个虚函数,其类型都是安全的。
/*
唯一的例外,协变返回类型,如果基类的虚函数返回类型的指针或引用,则子类的覆盖版本可以返回相应类型的子类。 */
>>>>隐藏:子类和父类,函数名相同。
三、深入理解虚函数和覆盖的实现机制
基类
class B
{
 public:
 virtual int f1();
 virtual void  f2();
 virtual int f3();
};
编译器会为每个包含虚函数的类生成一张虚函数表(vtbl),即存放每个虚函数的地址的函数指针数组,简称虚表,每个虚函数对应一个虚函数表中的索引号,(编译阶段建立表)
0 - B::f1
1 - B::f2
2 - B::f3
与此同时,编译器还会为该类增加一个隐式的成员变量(通常作为第一个成员),用于在实例化对象以后存放虚函数表的起始地址。改变量成为虚函数表指针,简称虚指针(vptr)。
对于B* pb = new B;
代码
pb->f3(12)
被编译成
pb->vptr[2](pb,12);
传递给成员函数的第一个参数,是调用对象的地址,this 指针
虚表一个类一张,不是一个对象一张。同一个类的对象通过各自的虚指针共享同一张虚表。
class D:public B
{
public:
 int f1(void); // 覆盖
 int f3(int); // 覆盖
 virtual void f4(void);
};
覆盖了f1 和 f3,继承了f2 增加了f4。
编译器同样为子类生成一张专属于他的虚表,
0 - D::f1
1 - B::f2
2 - B::f3
3 - B::f4
指向该虚表的虚指针就放在子类对象的基类字对象中(通常起始位置); //每个子类对象里面都一个父类对象
对于 B* pb = new D;
  pb->f3(12);
被编译为 pb->vptr[2](pb,12);  // 调用D里的 D::f3
这就是所谓多态的本质
所谓虚函数覆盖,其实就是编译器为子类构造虚表时,用子类中的虚函数地址覆盖基类相应的虚函数地址。

多继承:
子类对象中的多个基类子对象,按照继承表的顺序从低地址到高地址依次排开,因此子类对象中会同时存在多个虚指针,

同时继承了B1和B2的f2,覆盖了B2的f3,继承了B1的f1和B2的f4,增加了f5,编译器会为子类生成两张专属于他虚表。
D对象中B1子对象的vptr指向的虚表:
0 - B1::f1
1 - D::f2
2 - D::f3
3 - D::f5
D对象中B2子对象的vptr指向的虚表
0 - D::f2
1 - D::f3
2 - D::f4
第二个虚表里面不需要子类增加的函数。
多个基类中同型的虚函数被子类全部覆盖,尽管D中新增加了虚函数f5,直接放到第一个基类虚表中。
四、友元与运算符
某些运算符既可以被重载成成员函数,又可以被重载成友元函数
友元函数形式的运算符允许在其操作数上应用隐式自定义类型转换,但是成员函数形式的运算符其调用对象的操作数不能隐式转换为所需要的类型。
五、流控制
cout<<setw(12)<<1234;
cout<<hex<<1234;
可以自己定义格式函数
 ostream& datetime(ostream& os);

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值