【学习笔记 Day05】C++基础:面向对象的最后两种技术与思想思路!

类的继承

类的继承,是新的类从已有类那里得到的已有的特性。

派生类的定义:

class 派生类名:继承方式(public、private、protected) 基类名1,继承方式2 基类名2,继承方式n 基类名n
{
      派生类的成员声明;
}

注意:一个派生类可以同时有多个基类,称为多继承。

派生类成员是指除了基类继承来的所有成员外,新增加的数据和函数成员。

单一继承:

class AA
{
      AA(参数列表){};
}

class BB:public AA
{
      BB(参数列表,本类的参数):AA(参数列表),本类的参数{};
}

//解析:BB中的构造函数通过自身的参数列表获取到基类所需要的参数值,并进行传递

多继承:

初始化基类时,顺序为继承的顺序,而不是构造函数赋值的顺序(析构函数的顺序相反);而成员变量的初始化则是按照在类中声明的顺序开始的(析构函数的顺序相反)

访问控制

  • 公有继承(public):当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可直接访问。
  • 私有继承(private):当类的额继承方式为私有继承时,基类的公有和保护成员都会以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可直接访问。
  • 保护继承(protected):保护继承中,基类的公有和保护成员都以保护成员的身份出现在派生类中,而基类的私有成员在派生类中不可直接访问。

注意:当基类和子类中同时有某个变量时,初始化子类,要访问子类中的变量是,使用子类对象名.基类名::变量名 来访问。

作用域分辨符

作用域分辨符就是我们常见到的 :: ,它可以用来限定要访问的成员所在的类的名称。

一般形式:

类名::成员名              //数据成员
类名::成员名(参数列表)     //函数成员

如果派生类中声明了与基类成员函数同名的新函数,即函数的参数列表不同,从基类继承的同名函数的所有重载形式也会被隐藏。

如果某派生类的多个基类拥有同名的成员,同时,派生类又新增这样的同名成员,在这种情况下,派生类成员将隐藏所有基类的同名成员。

例如:

class Base1
{
      int var;
      void fun()
      {
            count << "Base1" << endl;
      }
}

class Base2
{
      int var;
      void fun()
      {
            count << "Base2" << endl;
      }
}

class Derived:public Base1,public Base2
{
      int var;
      void fun()
      {
            count << "Derived" << endl;
      }
}

int main()
{
      Derived d;
      Derived *p = &d;

      d.var = 1;
      d.fun();

      d.Base1::var = 2;
      d.Base1::fun();

      d -> Base2::var = 2;
      d -> Base2::fun();
}

注意:如果希望 d.var 和 d.fun() 的用法不产生二义性,可以使用 using 关键词加以澄清。

class Derived:public Base1,public Base2
{
      using Base1::var;
      using Base1::fun;
}

如果某个派生类的部分或全部直接基类是从另一个共同基类派生而来,在这些直接基类中,从上一级基类继承来的成员就拥有相同的名称,因此派生类中也就会产生同名现象,对这种类型的同名成员也要使用作用域分辨符来唯一标识,而且必须用直接基类来进行限定。

虚基类

将共同基类设置为虚基类,这是从不同的路径继承过来的同名数据成员在内存中就只有一个,同一个函数名也只有一个映射。

语法:

class 派生类名:virtual 继承方式 基类名{}

例如:

class Base0
{
      int var0;
      void fun0()
      {
            count << "Base0" << endl;
      }
}

class Base1:virtual public Base0
{
      int var1;
}

class Base2:virtual public Base0
{
      int var2;
}

class Derived:public Base1,public Base2
{
      int var;
      void fun()
      {
            count << "Derived" << endl;
      }
}

int main()
{
      Derived d;

      d.var0 = 1;
      d.fun0();
}

//结构:
//Devied:
//->Base0::var0:int
//->Base1::var1:int
//->Base2::var2:int
//->var:int
//->Base0::fun0():void
//->fun():void

结论:

  • 如果 Derived 都继承,则按最远端距离来决定 var0 的取值
  • 如果实例化的类是指针类型,则访问其成员时,用 -> 代替

类的多态性

多态从实现的角度来讲可以划分为两类:编译时的多态和运行时的多态。

运算符的重载

运算符的重载是对已有的运算符赋予多重含义,是同一个运算符作用于不同类型的数据时导致不同的行为。

运算符重载的规则:

  • 运算符除了少数几个外,全部都可以重载,而且只能重载 C++ 中已经存在的运算符。
  • 重载之后运算符的优先级和结合性都不会变
  • 运算符的重载是针对新类型数据的实际需要,对原有运算符进行适当的改造
  • 有些运算符是不能重载的,他们是类属关系运算符“.”、成员指针运算符“.*”、作用域分辨符“::”和三目运算符“?:”。

运算符的重载形式有两种,分为重载为类的非静态成员函数和重载为非成员函数。

语法:

//重载为类的成员函数:
返回类型 类名::operator 运算符(形参表)
{
      函数体;
}

//重载为非成员函数:
返回类型 operator 运算符(形参表)
{
      函数体;
}

例如(运算符重载为成员函数):

class Complex
{
public:
      Complex(double r = 0.0,double i = 0.0):real(r),image(i){}
      Complex operate+(const Complex &c) const;
      Complex operate-(const Complex &c) const;
      void display() const;
private:
      double real;
      double image;
}

Complex Complex::operator+(const Complex &c) const
{
      return Complex(real + c.real,image + c.image);
}

Complex Complex::operator-(const Complex &c) const
{
      return Complex(real - c.real,image - c.image);
}

void Complex::display() const
{
      cout << "Complex" << endl;
}

void main()
{
      Complex c1(5,4),c2(2,10),c3;
      c3 = c1 - c2;
      c3 = c1 + c2;
      c3.display();
}

例如(运算符重载为非成员函数):

class Complex
{
public:
      Complex(double r = 0.0,double i = 0.0):real(r),image(i){}
      friend Complex operate+(const Complex &c1,const Complex &c2);
      friend Complex operate-(const Complex &c1,const Complex &c2);
      friend ostream & operator<<(ostream &out,const Complex &c);
private:
      double real;
      double image;
}

Complex Complex::operator+(const Complex &c1,const Complex &c2);
{
      return Complex(c1.real + c2.real,c1.image + c2.image);
}

Complex Complex::operator-(const Complex &c1,const Complex &c2);
{
      return Complex(c1.real - c2.real,c1.image - c2.image);
}

ostream & operator<<(ostream &out,const Complex &c)
{
      out << "Complex's real is:" << c.real << endl;
      return out;
}

void main()
{
      Complex c1(5,4),c2(2,10),c3;
      c3 = c1 - c2;
      c3 = c1 + c2;
      cout << c3 << endl;
}

虚函数

语法:

virtual 函数类型 函数名(形参列表);

注意:虚函数声明只能出现在类定义中的函数原型声明中,而不能出现在成员函数实现的时候。

运行时的多态需要满足的条件:

  • 类之间满足赋值兼容规则
  • 声明虚函数
  • 由成员函数来调用或者通过指针、引用来访问虚函数

例如:

class Base1
{
public:
      virtual void display() const;
}

void Base1::display() const
{
      cout << "Base1" << endl;
}

class Base2:public Base1
{
public:
      void display() const;
}

void Base2::display() const
{
      cout << "Base2" << endl;
}

class Derived:public Base2
{
public:
      void display() const;
}

void Derived::display() const
{
      cout << "Derived" << endl;
}

void fun(Base1 *p)
{
      p -> display();
}

void main()
{
      Base1 b1;
      Base2 b2;
      Derived d;
      
      fun(&b1);
      fun(&b2);
      fun(&d);
}

final 和 override 说明符

  • override 关键字:派生类和基类中的函数名同名时,可以使用该关键字将基类中的函数继续覆盖。
  • final 关键字:指定基类中的函数不能被覆盖。

虚析构函数

在C++中不能声明虚构造函数,但是可以声明虚析构函数。

语法:

virtual ~类名();

析构函数设置为虚函数后,在使用指针引用时可以动态绑定,实现运行时的多态,保证使用基类类型的指针就能够调用适当的析构函数针对不同的对象进行清理工作。

class Base1
{
public:
      ~Base1();
}

Base1::~Base1()
{
      cout << "Base1" << endl;
}

class Derived:public Base1
{
public:
      Derived();
      ~Derived();
private:
      int *p;
}

Derived::Derived()
{
      p = int(0);
}

Derived::~Derived()
{
      cout << "Derived" << endl;
}

void fun(Base1 *p)
{
      delete p;
}

void main()
{
      Base1 *b1 = new Derived();
      
      fun(b1);//输出:Base1
}

纯虚函数

纯虚函数是一个在基类中声明的虚函数,他在该基类中没有定义具体的操作内容。

语法:

virtual 函数类型 函数名(参数列表) = 0;

注意:声明为纯虚函数之后,基类中就可以不在给出函数的实现部分。

抽象类

带纯虚函数的类是抽象类,抽象类是不能实例化。

模板与群体数据

  • 群体分为:线性群体和非线性群体
  • 线性群体中的元素按位置排列有序。
  • 非线性群体不用位置顺序来标识元素。

函数模板与类模板

所谓参数化多态性,就是将程序处理的对象的类型参数化,使得一段程序可以用于处理多种不同类型的对象。

函数模板

例如:

int abs(int x)
{
      return x < 0 ? -x : x;
}

double abs(double x)
{
      return x < 0 ? -x : x;
}

这两个函数只有参数类型不同,功能完全一样。使用函数模板就能简化这种方式。

语法:

template<模板参数列表>
类型名 函数名(参数列表)
{
      函数体的定义;
}

模板参数表由用逗号分隔的模板参数构成,可以包括以下内容:

  • class(或 typename)标识符,指明可以接受一个类型参数。
  • 类型说明符 标识符,指明可以接受一个由“类型说明符”所规定类型的常量作为参数
  • template<参数列表>class 标识符,指明可以接收一个类模板名作为参数。

例如:

template<typename T>

T abs(T x)
{
      return x < 0 ? -x : x;
}

int main()
{
      int n = -5;
      double d = 5.5;
      cout << abs(n) << endl;
      cout << abs(d) << endl;
}

类模板

使用类模板使用户可以为类定义一种模式,使得类中的某些数据成员、某些成员函数的参数、返回值或局部变量能取不同类型。

语法:

template<模板参数表>

class 类名
{
      类成员声明;
}

如果需要在类模板以外定义其成员函数,则采用以下形式:

template<模板参数列表>

类型名 类名<模板参数标识符列表>::函数名(参数列表)

一个类模板声明自身并不是一个类,只有当被其他代码引用时,模板才会根据引用的需要生成具体的类。

使用一个模板类来建立对象时,具体声明语法:

模板名<模板参数表> 对象名1,...,对象名n;

例如:

struct Student
{
      int id;
      float gpa;
}

//类模板:实现对任意类型数据进行存取
template<class T>
class Store
{
private:
      T item;
      bool haveValue;
public:
      Store();
      T &getElem();
      void putElem(const T &x);
}

//实现各个成员函数
template<class T>
Store<T>::Store():haveValue(false){}

template<class T>
T &Store<T>::getElem()
{
      if(!haveValue)
      {
            cout << haveValue << endl;
            exit(1);                          //是程序完全退出,参数可以用来表示程序终止的原因,可以被操作系统接收
      }
      return item
}

template<class T>
void Store<T>::putElem(const T &x)
{
      haveValue = true;
      item = x;
}

void main()
{
      Store<int> s1, s2;
      s1.putElem(3);
      s2.putElem(-7);
      
      Student s = {1000,23};
      Store<Student> s3;
      s3.putElem(s);

}

线性群体

对于可直接访问的线性群体,可以直接访问群体中的任何一个元素;对顺序访问的线性群体,只能按元素的排列顺序从头开始依次访问各个元素。

泛型程序设计

泛型程序设计的主要思想就是将算法从特定的数据结构中抽象出来,使算法成为通用的数据结构。

异常处理

程序运行中的有些错误可以预料但是不可避免的,例如内存不足、硬盘上的文件被移动等等由系统运行环境造成的错误。

异常处理的语法:

//throw 表达式语法:
throw 表达式;

//try块语法:
try
{
      复合语句
}
catch(异常声明)
{
      符合语句
}
catch(异常声明)
{
      符合语句
}
catch(异常声明)
{
      符合语句
}
......

例如:

int divide(int x,int y)
{
      if(y == 0)
      {
            throw x;
      }
      return x/y;
}

void main()
{
      try
      {
            cout << divide(5,2) << endl;
            cout << divide(5,0) << endl;
      } catch (int e)
      {
            cout << "Error is" << e << ednl;
      }
}

异常接口的声明

为了加强程序的可读性,使函数的用户能够方便的知道所使用的函数会抛掷那些异常,可以在函数的声明中列出这个函数可能抛掷的所有异常类型,如:

void fun() throw(A,B,C,D);

如果在函数声明中没有包括异常接口声明,则此函数可以抛出任何类型的异常。

void fun();

标准库的异常处理

常见的异常:

异常类头文件异常的含义
bad_allocexception用 new 动态分配空间失败
bad_castnew执行 dynamic_cast 失败
bad_typeidtypeinfo对某个空指针 p 执行 typeid(*p)
bad_exceptiontypeinfo当某个函数 fun() 因在执行过程中抛出异常声明所不允许的异常而调用 unexpected() 函数时,若 unexpected() 函数又一次抛出 fun() 的异常声明所不允许的异常,且 fun() 的异常声明列表中有 bad_exception ,则会有一个 bad_exception 异常在 fun() 的调用点被抛出
ios_base::failureios用来表示C++的输入输出流执行过程中发生的错误
underflow_errorstdexcept算术运算时向下溢出
overflow_errorstdexcept算术运算时向上溢出
range_errorstdexcept内部计算时发生作用域的错误
out_of_rangestdexcept表示一个参数值不允许的范围内
length_errorstdexcept尝试创建一个长度超过最大允许值的对象
invalid_argumentstdexcept表示向函数传入无效参数
domain_errorstdexcept执行一段程序所需要的先决条件不满足

© 版权声明
THE END
喜欢就支持一下吧
点赞11 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容