“ 运算符的重载及类的多态”
01
—
运算符的重载
C++中对于内置的运算符可以自定义重载,使用关键字operator实现。
运算符的重载方式:
1.通过类内重载
2.通过全局函数重载
class MyPoint{public: MyPoint(double x, double y) :X(x), Y(y) { } double X; double Y; //+运算符重载 MyPoint operator+(const MyPoint &pt) { MyPoint temp(this->X + pt.X, this->Y + pt.Y); return temp; } //++MyPoint重载 MyPoint operator++() { MyPoint temp = *this; this->X++; this->Y++; return temp; } //MyPoint++重载 MyPoint & operator++(int) { this->X++; this->Y++; return *this; } //*运算符重载 MyPoint & operator*(double d) { this->X *= d; this->Y *= d; return *this; } //=运算符重载 MyPoint & operator=(double d) { this->X = d; this->Y = d; return *this; } //=运算符重载 MyPoint & operator=(const MyPoint &pt) { this->X = pt.X; this->Y = pt.Y; return *this; } //仿函数 void operator()(string str) { cout << " 仿函数调用:" << str << endl; }};//全局函数的方式重载运算符<<ostream & operator<out, { out << " X = " << pt.X << "\t" << " Y = " << pt.Y << endl; return out;}
添加测试代码:
int main(){ MyPoint p1(30, 20); MyPoint p2(20, 30); cout << p1 + p2; cout << " ++p1 :" << ++p1 ; cout << " p1++ :" << p1++ ; cout << " p1*10 :" << p1 * 10; p1 = 25; cout << " p1 = 25 :" << p1; p1 = p2; cout << " p1 = p2 :" << p1; p1("TestPrint"); system("pause");}
输出结果:
02
—
类的继承
C++类的继承语法
class ClassA:[继承方式] ClassB
继承方式:public/private/protect(基类private权限的对象子类无法访问)
public:所有继承对象的权限不变
private:所有继承对象的权限改为私有(继续派生时其子类不可访问)
protect:所有继承对象的权限改为protect(类外无法访问,派生的子类可以访问)
一个类可以继承多个基类
class ClassA:[继承方式] ClassB,[继承方式] ClassC...
类继承时的构造及析构函数执行顺序
1.基类构造函数->子类构造函数
2.子类析构函数->基类析构函数
类作为属性时的加载及释放顺序
1.成员对象构造函数->构造函数
2.析构函数->成员对象析构函数
类继承时对于同名成员的访问:添加作用域访问
[子类实例名].[基类型]::[基类型成员]MyClass2 cls;cls.MyClass1::K = 20;cls.MyClass1::Function();
类继承时的对象模型
在类继承时,对子类分配内存空间时也会对基类的private对成员对象分配内存空间,只不过该空间被编译器隐藏,程序无法访问。
菱形继承时的处理 如:
ClassB:public ClassAClassC:public ClassAClassD:public ClassB,public ClassC
这种菱形的继承关系,如果ClassA、ClassB、ClassC均存在属性K,则ClassD在继承属性K的时候会存在多余的继承。针对此种情况,需要用关键字virtual把继承关系改为虚继承:
ClassB:virtual public ClassAClassC:virtual public ClassAClassD:public ClassB,public ClassC
03
—
类的多态
C++类的多态分为静态多态和动态多态。
静态多态:在程序编译阶段就已经确定函数地址。如函数重载、运算符重载。
动态多态:在程序运行期间才能确定函数地址。如派生类和虚函数
class BaseClass{public: int K; virtual void TestFunction(){ }};class ExtendClass :virtual public BaseClass{public: int K;};
多态的对象模型:
查看内存模型的指令:cl /d1 reportSingleClassLayout[类名] [类文件名] 需要用vs开发者命令在类文件夹下执行。
上面例子中ExtendClass的内存模型:
子类继承父类属性时,如果使用了关键字virtual,则从父类继承下来的v(virtual)b(base)ptr(pointer)是一个虚拟基类指针,指向vbtable(虚基类表),表中的内容表达的是继承属性的偏移量,所以从父类继承下来的属性,在内存中只存在一份,不会存在多余的数据。
虚函数在继承时,从父类继承下来的v(virtual)f(function)ptr(pointer)是一个虚函数指针,指向vftable(虚函数表),表中内容表达对应函数的地址(偏移量),当子类重写了虚函数之后,该表中的函数就会被覆盖。
如果在子类中重写了父类的虚函数void TestFunction()
class ExtendClass :virtual public BaseClass{public: int K; void TestFunction(){ }};
此时再查看模型:
发现vftable中的函数地址变成了自身的函数中的地址,这样就达到了函数的重写效果。
虚析构函数,由于多态在使用时采用父类的类型来操作子类数据,所以在释放时会出现子类的析构函数无法调用情况,也就是如果子类在堆区上创建了数据,则会无法释放。对于这种情况可以在父类声明虚析构函数来解决。
virtual ~BaseClass() { }
纯虚函数和抽象类
纯虚函数在定义后该类将会成为抽象类,不允许实例对象。
纯虚函数的语法:
virtual [返回值类类型] [函数名](形参列表) = 0;
纯虚函数定义后子类必须实现,否则子类也会成为抽象类。