C++ Primer Plus 第13章

来源:互联网 发布:如何通过ip找到域名 编辑:程序博客网 时间:2024/05/16 13:04
//类继承
从一个类派送出另一个类时,原始类称为父类,继承类称为子类,或以父类/子类称呼
class TableTennisPlayer
{
//...
};
class RatedPlayer : public TableTennisPlay
{
//...
};
子类的特征:
 1 子类对象存储了父类的数据成员
 2 子类对象可以使用父类的方法
子类需要添加的东西:
 1 子类需要自己的构造函数
 2 子类根据需要添加额外的数据成员和成员函数
 
//子类的构造函数
RatedPlayer::RatedPlayer(unsigned in r, const char * fn, const char *ln, bool ht):TabeTennisPlayer(fn, ln, ht);
由于父类对象必须首先被创建,所以如果省略成员初始化列表,那么程序将使用默认的父类构造函数


//有关子类构造函数的要点为
 1 父类对象首先被创建
 2 子类构造函数应通过成员初始化列表将父类信息传递给父类构造函数
 3 子类构造函数应初始化子类新增的数据成员
 4 析构函数执行顺序相反,即先析构子类,再析构父类
 
//子类与父类之间的关系
 1 子类对象可以使用父类的方法,条件是方法不是私有的
 2 父类的指针可以在不进行显式类型转换的情况下指向子类对象
 3 父类的引用可以在不进行显式类型转换的情况下引用派生类对象
 4 但是,不可以将子类的指针或引用指向父类对象
 5 可以使用通过子类利用 复制构造函数 与 赋值操作符函数,来初始化父类
5.1 RatedPlayer rp;
   TableTennisPlayer(rp);//将利用TableTennisPlayer(const TableTennisPlayer &) 复制构造函数(因为2,3)
5.2 RatedPlayer rp;
   TableTennisPlayer tp;
tp = rp; //将利用operator=(const TableTennisPlayer &) 赋值构造函数(因为2,3)


//多态公有继承
 1 在子类中重新定义父类的方法
 2 使用虚方法
 ex:
 class Brass
 {
virtual void ViewAcct() const;
virtual ~Brass(){};
 };
 class BrassPlus : public Brass
 {
virtual void ViewAcct() const;
virtual ~BrassPlus(){};
 };
 1 如果是 virtual 方法,那么C++将根据指针指向的对象决定调用哪个方法(这个指针类本身类的定义需要 virtual 方法,其子类则不需要,见3)
 2 如果是 普通    方法,那么C++将根据指针的类型决定调用哪个方法
 3 在父类中将子类会重新定义的方法声明为虚方法,方法在子类中将自动成为虚方法
 4 一般都将基类的析构函数声明为 virtual 的 
 5 在子类中要调用覆写过的父类方法,需要使用域名解析操作符 
void BrassPlus::ViewAcct() const
{
Brass::ViewAcct();
//....
}
 
//为什么析构函数要被声明为 virtual 的
 如果析构函数不是 virtual 的,那么则只调用对应于指针类型析构函数,而不是指针真正指向的对象。那么就相当于只调用了父类的析构函数,而没有调用子类的析构函数
 那么子类 new 出的内存将会溢出,如果将析构函数声明为 virtual 的,那么将会调用指针真正指向对象的析构函数。


//子类与父类的转换
父类指针 指向 子类对象 //可行的
子类指针 指向 父类对象 //需要强制转换 子类中新添加的成员会以默认的方式初始化


//虚函数的原理
通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针。被称为虚函数表。
NOTE:不管有多少虚函数,虚函数表都自有一个,只是大小不同,虚函数表中虚函数的地址与虚函数在类中的声明顺序有关


//关于虚函数的注意事项
1 在父类方法的声明中使用关键字 virtual 可以使该方法在基类以及所有的子类(包括子类的子类)中是 virtual 的
2 如果使用指向对象的引用或指针来调用 virtual 方法,程序将使用实际的对象类型的对应 virtual 方法
3 如果定义的类将被用作父类,则应将那些要在子类中重新定义的类方法声明为 virtual 的(不是强制的?)


&&


1 构造函数不能是 virtual 的,因为没有意义,其必须调用子类的构造函数完成类的创建
2 析构函数应该是 virtual 的,析构函数应该是虚函数(请一定给父类的析构函数声明为虚拟的)
3 友元不能是 virtual 的,因为友元不是类成员
4 如果子类没有重新定义 virtual 方法,则将使用该函数的最近父类版本


5 隐藏:如果在子类中重新定义 virtual 方法(方法名相同,而不管参数特征),子类的方法会隐藏父类的 virtual 方法,即是覆盖,而不是重载
6 但是返回值如果是引用或者指针则可以修改(返回类型协变)
7 如果父类的方法是重载的,子类应该实现所有的虚方法,复制父类的未实现虚方法将会被隐藏


//C++的线程安全单例
 class SingletonStatic
 {
 private:
     static const SingletonStatic* m_instance;
     SingletonStatic(){}
 public:
     static const SingletonStatic* getInstance()
     {
         return m_instance;
     }
 }; 
 //外部初始化 before invoke main,在main函数之前就进行了初始化
 const SingletonStatic* SingletonStatic::m_instance = new SingletonStatic;
 
//抽象基类-有纯虚函数的类
纯虚函数,以=0结尾
void Move(int nx, int ny ) = 0;
纯虚函数也可以有定义
void BaseEllipse::Move(int nx, int ny ){x += nx; y += ny;}


//继承与new
1 子类不用new
则可以不为子类定义显示析构函数,复制构造函数和赋值操作符

2 子类使用new
必须为子类定义显示析构函数,复制构造函数和赋值操作符
  ex:
class baseDMA
{
};
class hasDMA:public baseDMA
{
};
(1)析构函数
(2)复制构造函数 hasDMA只能访问自己的成员,所以必须显示调用baseDMA的复制构造函数
hasDMA::hasDMA(const hasDMA &hs):baseDMA(hs)
{
//...
}
(3)赋值操作符 hasDMA只能访问自己的成员,所以必须显示调用baseDMA的赋值操作符
hasDMA& hasDMA::operator = (const hasDMA &hs)
{
if(this == &hs)
return *this;
baseDMA::operator = (hs);
//...
return *this
}

//类设计回顾
编译器自动生成的成员函数
 1 默认构造函数
 2 复制构造函数
 3 赋值操作符
 4 地址操作符


其他类方法
 1 构造函数
   成员初始化列表(const 与 引用 要在成员初始化列表中初始化)
 2 析构函数
   对于基类,需要一个虚析构函数,即时它不需要析构函数
 3 转换
   使用一个参数就可以调用的构造函数可以进行转换
   Star(const char *); //  Start star = "polaris"
   Star(const Spectral&, int members = 1); //  Start star = spectral
   使用 explicit 可以禁止隐式转换,但可以显式转换
   class Star
   {
//...
explicit Star(const char *);//  Start star = "polaris" (wrong)
//...
   }
   转换的二义性:
   class vector
   {   
  vector(double i);
  operator int();
  vector& operator+(vector & v);
   }
   则 
   vector ius(6.0);
   vector iux = ius + 20.2; //编译器可以将ius转换成double使用double加法,也可以将20.2转换成vector使用vector加法
//除了指出二义性以外,编译器什么也不做
  4 按值传递与按引用,指针传递
  作为参数时,使用引用,指针传递
  
  5 返回对象与返回引用,指针
  作为返回值时,使用值传递
  
  6 cosnt
  使用const可以保证方法不会修改修饰对象
  
 返回值不能改                   参数不能改   方法不会修改类
  const Stock & Stock::topVa;(const Stock & s) const
  {
//...
  }
 
//使用继承时的注意事项
1 is-a关系

2 什么不能被继承
构造函数不能被继承,你必须显式的首先调用基类的构造函数,否则,C++会调用默认构造函数
析构函数也不能被继承,不过释放对象是,程序讲首先调用子类的析构函数,然后才是父类的析构函数
 
3 赋值操作符
赋值操作符也不能被继承
Brass brass;
sBrassPlus brassPlus;
将子类赋值给父类对象时:  
brass = brassPlus; -> brass.operator=(brassPlus); //赋值操作符将只处理父类成员,子类成员则不进行处理(其值为默认的,即有可能为随机的)
将父类赋值给子类对象时:
brassPlus = brass; -> brassPlus.operator=(brass);  //子类对象不能自动转换为父类对象,所以这个操作不可行,
  //除非有个显式的复制构造函数 BrassPlus(const Brass &)
  //或者定义一个专用的复制操作符 BrassPlus::operator=(const Brass &)
 
4 私有成员与保护成员

5 虚方法

6 析构函数
基类的析构函数应该是 virtual 的

7 友元函数
友元函数不是类成员,因此不能继承

8 一些使用基类方法的说明
以 public 方法继承的类的对象可以通过多种方式来使用父类的方法
1 子类对象自动使用继承而来的基类方法,如果子类没有重新定义该方法
2 子类的构造函数自动调用父类的构造函数
3 子类的构造函数自动调用父类的默认构造函数,如果没有在成员初始化列表中指定其他构造函数
4 子类的构造函数显式调用父类指定的父类构造函数
5 子类的方法可以使用 父类名::父类方法 调用父类 public 或 protected 的方法
6 子类的友元可以通过强制类型转换,将子类对象指针或引用转换为父类指针或引用,;哎使用父类的友元函数

父类的析构函数要是 virtual 的(因为很重要,所以说很多次!)
0 0
原创粉丝点击