书摘
若要成为一个不平庸的人,在平常的事情中就应当表现出不平常的看法和做法来,即使是最平常的事,亦能凸显出人格的伟大。
前言
如果对于类的基础概念还有什么疑惑可以看这篇文章:类的基础概念
类的继承和组合为面向对象的程序设计提供了代码复用的可能性,奉行拿来主义。
类的组合
简单来说,类的组合是指:一个类中的某个数据成员是另一个类。
如下图:
现在有两个类,一个是发动机,一个是汽车。那么在已经有发动机类的情况下,我们在抽象汽车类的时候直接把发动机类包含进来就可以了,而不用再把发动机的成员一个个抽象到汽车类中,达到代码复用的目的。
代码框架
接下来用代码来实现组合,重点在于构造函数的书写。
class Motor //发动机类
{
/*省略不必要成员*/
public:
//构造函数
Motor(float wei):weight(wei){}
private:
int weight; //发动机的重量
};
class Car //汽车类
{
public:
/*演示一下怎么写构造函数*/
Car(Motor mo, float he):motor(mo), height(he){}
private:
float heght;
Motor motor; //发动机类作为私有成员
/*省略其他成员*/
};
总结
类的组合较为简单,掌握了如何初始化那么应用就可以得心应手的使用了。
类的继承
类的继承相对来说较为困难,需要仔细体会,反复琢磨。不断的发现问题,解决问题,逐渐完善出一套自己的代码风格。
插播知识点
@基类和派生类:派生类包含基类的所有属性,如下图:
汽车和火车都属于交通工具的一种,那么基类是交通工具,派生类是汽车和火车;
@继承:派生类拥有基类的所有属性和操作,这就是继承。
@类的成员分为三种,公有成员(public),私有成员(private),保护成员(protected)。
在类的继承时,也分这三种,公有继承,私有继承,保护继承。
基类成员的属性和继承方式的不同组合使基类相对于派生类有不同的属性。
emm还是逐个说明一下吧:(以下3行说的属性是蓝色框中的,不包括框外面的)
属性为public代表派生类和外界都可以访问基类成员;
属性为protected代表派生类可以访问但是外界不可以访问;
属性为private代表派生类和外界都不可以访问。
代码框架
class Vehicle //交通工具类
{
public:
//构造函数
Vehicle(int da):data(da){}
/*省略非必要成员*/
private:
int data;
};
class Car:public Vehicle
{
public:
//在派生类中调用基类的构造函数
Car(int da, int he):Vehicle(da), height(he){}
private:
int height;
};
书上说派生类的构造总是由基类的初始化开始的,也就是说,派生类在生成对象的时候先调用基类的构造函数,然后调用派生类的构造函数,也就是说先执行Vehicle()再执行Car()。
根据先构造的后析构,后构造的先析构,那么首先调用的析构函数是~Car(),再调用函数 ~Vehicle()。
多态性(滞后联编)
先来看一段例子:
class Vehicle //交通工具类
{
public:
float speed() //计算速度的函数
{
return 30.0;
}
/*省略非必要成员*/
};
class Car:public Vehicle
{
public:
float speed() //计算速度函数
{
return 40.0;
}
};
/*
*又是一个小细节:
*函数功能说明:
*传入的x是Vehicle对象,调用的是Vehicle::speed()
*传入的x是Car对象,调用的是Car::speed();
*原因:Car是Vehicle的派生类,传入参数是Car对象也没有问题。但是反过来不行:如果函数的参数表是Car对象
*那么不能传入Vehicle对象。
*/
float Sp(Vehicle& x)
{
float m = x.speed();
}
int main()
{
cin>>m;
if(m == 1)
{
Vehicle x;
}
else
{
Car x;
}
Sp(x);
}
看上面的代码:在程序的编译期间是不能够确定Sp函数究竟调用Vehicle::Speed()还是Car::speed()函数,只有在程序运行完if-else语句才能确定调用哪个函数。此时,编译器在编译期间暂时不确定调用哪个函数,而是在程序运行完if-else语句后确定调用哪个函数,编译器的这个行为叫做滞后联编。这是C++多态性的体现。
虚函数
那么如何告诉编译器什么时候使用滞后联编什么时候不用滞后联编呢?关键字virtual,把需要滞后联编的函数告诉告诉编译器。而这些被virtual关键字修饰的函数叫做虚函数。现在重写上面的代码:
class Vehicle //交通工具类
{
public:
virtual float speed() //计算速度的函数
{
return 30.0;
}
/*省略非必要成员*/
};
class Car:public Vehicle
{
public:
//此处的virtual可以省略!
virtual float speed() //计算速度函数
{
return 40.0;
}
};
float Sp(Vehicle& x)
{
float m = x.speed();
}
int main()
{
cin>>m;
if(m == 1)
{
Vehicle x;
}
else
{
Car x;
}
Sp(x);
}
注意:如果说基类和派生类中的某个函数名字相同,但是函数的参数表不同或者是返回值不同,那么是不会被编译器看作虚函数的(即使你声明为virtual),因为编译器可以在编译期间根据参数列表的差异或是返回值差异确定调用哪个函数。
但是有一个例外情况:如果基类中函数的返回值是一个基类的指针,派生类函数的返回值是派生类的指针,那么会启用滞后联编。
例如:下面的例子会对speed函数启用滞后联编。
class Vehicle //交通工具类
{
public:
virtual Vehicle* speed() //计算速度的函数
{
/*省略函数体*/
}
/*省略非必要成员*/
};
class Car:public Vehicle
{
public:
//此处的virtual可以省略!
virtual Car* speed() //计算速度函数
{
/*省略函数体*/
}
};
注:虚函数会增加系统资源的开销,但这并不意味着它不好。
纯虚函数和抽象类
@什么是抽象类
比如说,现在有一个类是中国人,而你现在想要定义一个类是美国人,中国人和美国人有许多共同的属性,如果你用中国人作为基类派生出美国人,这并不合适,因为我们有许多他们没有的品格和文化底蕴,但是,许多的共同属性又吸引着你这么做来减少代码的编辑量。C++为了免去我们这种困扰,衍生出了抽象类,把中国人和美国人的共同特征拿出来作为他们共同的基类,在这个类中至少有一个纯虚函数来标识这个基类只是作为继承使用,抽象类不能被实例化。
@什么样的虚函数可以是纯虚函数
性质一样但是函数内部操作不同,比如说,我们都会讲话,但是讲话的方式不同(中文和English),那么讲话这个函数就可以作为纯虚函数,只在抽象类中声明,不在抽象类中实现。
@如何声明纯虚函数呢
在虚函数的后面加上一个 ”= 0“。
举个例子:
/*中国人和美国人共有的特征*/
class Human
{
public:
/*抽象类不需要重载构造函数*/
/*纯虚函数,*/ /*注意此处的 = 0,不可忽略!!*/
virtual void speaking(string s) = 0;
private:
int age; //年龄
string name;//名字
};
class Chinese : public Human
{
public:
virtual void speaking(string purpose)
{
/*具体实现*/
cout<<"我想要"<<purpose<<endl;
}
};
class American : public Human
{
public:
virtual void speaking(string purpose)
{
cout<<"I want to"<<purpose<<endl;
}
};
多重继承
简单来说,多重继承就是一个类有多个基类。
虚拟继承
上面说了中国人和美国人的问题,那么接下来聊聊混血产生的歧义。
现在一个中国人和美国人生了一个小孩,这个小孩既有美国人的特征又有中国人的特征,这是毋庸置疑的,如果只有中国人的特征,emmm,事情变得有趣了。也就是说,这个小孩child类,继承了Chinese和Ameerican的特性,在代码的书写上,可能是这样:
/*一个类*/
class children: public Chinese, public American
{
/*省略*/
}
这个时候,会出现一个问题:
这是他们的继承关系:
在Children类中的状态:
那么此时出现的问题就是这个children类中有两个Human类,但是我们只需要一个就够了,C++也考虑到了这个问题,提供了解决方案。虚拟继承,如果American和Chinese是虚拟继承Human,那么在Children类中只会存在一个Human类。
如下:
那么如何声明虚拟继承呢?virtual关键字。
/*这个virtual起到了虚拟继承的声明作用*/
class Chinese : virtual public Human
{
public:
virtual void speaking(string purpose)
{
/*具体实现*/
cout<<"我想要"<<purpose<<endl;
}
};
class American : virtual public Human
{
public:
virtual void speaking(string purpose)
{
cout<<"I want to"<<purpose<<endl;
}
};
class children: public Chinese, public American
{
/*省略*/
}
总结
类的继承主要是插播知识点那一块比较抽象,建议多体会。
对于看到这里的人表示深深的感谢,如果觉得不错的话,给个小小的赞吧。