C++语言总结(三)

来源:互联网 发布:业务网站源码 编辑:程序博客网 时间:2024/06/06 19:15

C++语言总结(三)

成员变量和函数的存储

在C语言中,变量和函数”分开来声明的”,也就是说,语言本身并没有支持“数据”和“函数”之间的关联性我们吧这种方法称为“程序性的”,由一组“分布在各个以功能为导航的函数中”的算法驱动,他们处理的是共同的外部数据。
在C++中,实现了封装,数据和处理数据的函数是分开储存的。

  • C++中的非静态数据成员直接内含在类对象中,就像C struct一样
  • 成员函数虽然内涵在class声明之内,却不出现在对象中。
  • 每一个非内联成员函数只会诞生一份函数实例。

C++类对象中的变量和函数是分开存储的。
静态数据成员并不保存在类对象中;非静态成员函数不保存在类对象中;静态成员函数也不保存在类对象中。
C++规定,this指针是隐含在对象成员函数内的一种指针。当一个对象被创建后,它的每一个成员函数都含有一个系统自动生成的隐含指针this,用以保存这个对象的地址,也就是说虽然我们没有写上this指针,编译器在编译的时候也是会加上的,因此this也称为”指向本对象的指针”,this指针并不是对象的一部分,不会影响sizeof的结果
this指针永远指向当前对象
静态成员函数内部没有this指针,静态成员函数不能操作非静态成员变量
this指针的使用

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this

用const修饰的成员函数时,const修饰this指针指向的内存区域,成员函数体内不可修改本类的任何普通成员变量
当成员变量类型符前用mutable修饰除外
const修饰的对象被称为常对象

  • 常对象只能调用const的成员函数
  • 常对象可访问const或非const数据成员,不能修改,除非成员用mutable修饰

友元

友元函数是一种特权函数,C++允许这个特权函数访问私有成员。

友元语法

  • friernd关键字只出现在声明处
  • 其他类、类成员函数、全局函数都可声明为友元
  • 友元函数不是类的成员,不带this指针
  • 友元函数可访问对象任意成员属性,包括私有属性

友元类注意

  • 友元关系不能被继承
  • 友元关系是单向的,类A是类B的朋友,但类B不一定是类A的朋友
  • 友元关系不具有传递性,类B是类A的朋友,类C是类B的朋友,但类C不一定是类A的朋友

运算符重载

运算符重载,就是对已有的运算符进行定义,赋予其另一种功能,以适应不同的数据类型。
运算符重载只是一种语法上的方便,也就是它只是另一种函数调用的方式
在C++中,可以定义一个处理类的新运算符,这中定义 很像一个普通的函数定义,只是函数的名字由关键字operator及其紧跟的运算符组成。差别仅此而已,它像任何其他函数一样也是一个函数,当编译器遇到适当的模式时,就会调用这个函数。
定义重载的运算符就像定义函数,只是该函数的名字是operator@,这里的@代表了被重载的运算符,函数的参数个数取决于两个因素

  • 运算符是一元(一个参数)还是二元(两个参数)。
  • 运算符被定义为全局函数(一元是一个参数,对于二元是两个参数)还是成员函数(对于一元没有参数,对于二元是一个参数—此时该类的对象用作左耳参数)

继承

C++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。
派生类中的成员,包含两大部分:

  • 一类是从基类继承过来的,一类是自己增加的成员
  • 从基类继承过来的表现其共性,而新增的成员体现了其个性。

派生类定义

派生类定义格式:
class 派生类名 : 继承方式 基类名{

}
三种继承方式:
public : 公有继承
private : 私有继承
protected : 保护继承

从继承源上分:
单继承:指每个派生类只继承一个基类的特征
多继承:指多个基类派生出一个派生类的继承关系,多继承的派生类直接继承了不止一个基类的特征。
继承中的构造和析构

  • 子类对象在创建时会首先调用父类的构造函数
  • 父类构造函数执行完毕后,才会调用子类的构造函数
  • 当父类构造函数有参数时,需要在子类初始化列表(参数列表)中显示调用父类构造函数。
  • 析构函数调用顺序和构造函数相反

继承中同名成员的处理方法

  • 当子类成员和父类成员同名时,子类依然从父类继承同名成员
  • 如果子类有成员和父类同名,子类访问其成员默认访问子类的成员(本作用域,就近原则)
  • 在子类通过作用域::进行同名成员区分(在派生类中使用基类的同名成员,显示使用类名限定符)

任何时候重新定义基类的任何一个函数,子类中这种函数的任何版本都会被隐藏(非覆盖,可通过类作用域运算符调用)

不能自动继承的函数

  • 基类的析构函数和构造函数用来处理对象的创建和析构操作,必须为每一个特定的派生类分别创建
  • operator=不能被继承,因为它完成类似构造函数的行为。

在继承过程中,如果没有创建这些函数,编译器会自动生成他们

多继承

我们可以从一个类继承,我们也可以同时从多个类继承,这就是多继承。但是由于多继承是非常受争议的,从多个类继承可能会导致函数、变量等同名导致较多的歧义。
多继承带来的一些二义性的问题,可以使用显示指定那个基类的版本。
菱形继承和虚继承
两个派生类继承同一个基类而又有某个类同时继承者两个派生类,这种继承被称为菱形继承,或者叫做钻石继承。
这种继承会带来一些问题:比如产生二义性,重复继承相同的函数和数据。
对于这种问题所带来的两个问题,C++为我们提供了一种方式,采用虚基类。
采用虚继承的时候,编译器做了一些幕后工作,使得派生类中增加了一个vbptr,vbptr指向了一张表,这张表保存了当前的虚指针相对于虚基类的首地址的偏移量
当使用虚继承的时候,虚基类是被共享的,也就是在继承体系中无论被继承了多少次,对象内存模型中均只会出现一个虚基类的子对象。即使共享虚基类,但是必须要有一个类来完成基类的初始化,同时还不能够重复进行初始化。C++标准中选择在每一次继承子类中都必须书写初始化语句,但是虚基类的初始化是由最后的子类完成,其他的初始化语句都不会调用。

多态

多态是面向对象程序设计语言中数据抽象和继承之外的第三个基本特征。
多态提供接口与具体实现之间的另一层隔离,从而将“what”和“how”分离开来。多态性改善了代码的可读性和组织性,同时也使创建的程序具有可扩展性,项目不仅在最初创建时期可以扩展,最后当项目在需要有新的功能时也能扩展。

向上类型转换及问题

对象可以作为自己的类或者作为它的基类的对象来使用,还能通过基类的地址来操作它,取一个对象的地址(指针或引用),并将其作为基类的地址来处理,这种称为向上类型转换。
也就是说:父类引用或指针可以指向子类对象,通过父类指针或引用来操作子类对象。
C++动态多态性是通过虚函数来实现的,虚函数允许子类重新定义父类成员函数,而子类重新定义父类虚函数的做法称为覆盖,或者称为重写。
对于特定的函数进行动态绑定,C++要求在基类中声明这个函数的时候使用virtual关键字,动态绑定也就是对virtual函数起作用。

  • 为创建一个需要动态绑定的虚成员函数,可以简单在这个函数声明前面加上virtual关键字,定义时候不需要。
  • 如果一个函数在基类被声明为virtual,那么在所有的派生类中它都是virtual的
  • 在派生类中virtual函数的重定义成为重写
  • virtual关键字只能修饰成员函数
  • 构造函数不能为虚函数

C++如何实现动态绑定
当编译器发现我们的类中有虚函数的时候,编译器会创建一张虚函数表,把虚函数的函数入口地址放到虚函数表中,并且在类中秘密增加一个指针,这个指针就是vpointer,这个指针指向对象的虚函数表。在多态调用的时候,更具vptr指针,找到虚函数表来实现动态绑定。

多态成立的条件

  • 有继承
  • 子类重写父类虚函数函数
    (1)返回值,函数名字,函数参数,必须和父类完全一致
    (2)子类中virtual关键字可写可不写,建议写
  • 类型兼容,父类指针,父类引用 指向 子类对象

纯虚函数和抽象基类

在基类中加入至少一个纯虚函数,使得基类称为抽象类

  • 纯虚函数使用关键字virtual,并在起后面加上=0。如果试图去实例化一个抽象类,编译器则会阻止这种操作
  • 当继承一个抽象类的时候,必须实现所有的纯虚函数,否则由抽象类派生的类也是一个抽象类
  • virtual void fun() = 0; 告诉编译器vtable中卫函数保留一个位置,但在这个特定位置不放地址。