C++ 操作符重载和继承,虚继承,虚基类

207 阅读6分钟

1、操作符重载

听说过方法重载,还没听说过操作符也能重载。。。这个特性,Kotlin也有,但是我没怎么用过,Kotlin不愧是Kotlin把C++精华都抄了过去。 常用的操作符 + - [] <<>>,在系统中,我们的算数运算符只可以用来两个数相加。但是我们把它们重载了,就可以干一些我们的事情。

1.1 运算符重载

class Point {
private:
    int x = 0;
    int y = 0;
public:
    Point() { }
    Point(int x,int y) :x(x),y(y) {}
    void setX(int x) {
        this->x = x;
    }
    void setY(int y) {
        this->y = y;
    }
    int  getX() {
        return this->x;
    }
    int getY() {
        return this->y;
    }
    //类里边的写法,规范写法
    Point operator + ( Point & point) {
        Point newPoint;
       int x =this->x +point.getX();
       int y= this->y + point.getY();
        newPoint.setX(x);
        newPoint.setY(y);
        return newPoint;
    }
};
//类外边的写法
// Point operator + ( Point & point1, Point & point2) {
//     Point newPoint;
//     int x = point1.getX() +point2.getX();
//     int y =point1.getY() +point2.getY();
//     newPoint.setX(x);
//     newPoint.setY(y);
//     return newPoint;
// }
int main(int argc, char *argv[]) {
    Point point1(100,200);
    Point point2(200,300);
    Point point =point1+point2;
    cout << point.getX()<<"," << point.getY()<<endl;
    return 0;
}

有两种模式,在类外边可以重载操作符,但是我们一般不这样写,规范的写法都是写在类里边的。

Point operator + ( Point & point1, Point & point2) 类外边的写法,操作符重载的两个参数,分别是操作符 + 左边的参数和右边的参数。

Point operator + ( Point & point) 类里边的写法 就一个参数,操作符+ 右边的参数。其他运算符同理。对于++ ,--这类运算符有点特殊。

// ++ 对象
void operator ++() {
    this->x = this->x+1;
    this->y =this->y+1;
}
//对象 ++
void operator ++ (int) {
    this->x = this->x+1;
    this->y =this->y+1;
}

1.2 重载输出运算符<< 实现连续打印自己的对象

friend ostream & operator << (ostream & _START, const Point &  point) {
    //系统的操作符
    _START << point.x <<","<<point.y<<endl;
    return _START;
}

int main(int argc, char *argv[]) {
    Point point1(100,200);
    Point point2(200,300);
    Point point =point1+point2;
    cout << point1 << point2 <<point;
    return 0;
}

输出操作符,在用ostream 时,必须加上friend 这是规定 和其他操作符相比,就是要把第一个参数进行返回。这样就会形成一个闭环。实现连续打印自己的对象。 在很多框架或者Android系统源码里,操作符重载,参数大多都采用 常量引用的方式。这样做的好处,传进去的参数是只读的,保证数据安全,而且是引用,只给对象起了别名,并没有复制在方法入栈的时候,复制一份 Point的副本,这样可以节省很多系统资源,提升程序运行效率。如果传递对象,它会执行类的拷贝构造函数,拷贝一份副本,供方法内使用。

image.png

1.3 操作符重载案例,中括号重载[]

// 数组 [i]  系统已经对这个进行重载了, 相当于 * (arr+i)做了这件事,
//我们也重载一把这个操作符玩一玩
class ArrayClass {
private:
    int size = 0; //c++没有默认值,我们要赋一个默认值
    int *arryValue; //存放数组值
public:
    void add(int value) {
        arryValue[size] =value;//这个中括号 是系统的
        size +=1;
    }
    int operator [] (int index) {
        return this->arryValue[index];//系统的
    }
    int getSize() {
        return this->size;
    }
};

实现的共功能和系统的是一样的。

2、继承,二义性

继承和Java差不多,只说和Java不一样的地方。

2.1 必须执行父类的构造方法

如果一个类继承了某个类,子类的构造方法必须初始化父类有参数的的构造方法。 Base 'Person' is not initialized in this constructor

image.png
正确的代码

class Student :Person {
    Student(char * name,int age):Person(name,age) {

    }
};

2.2 C++子类访问父类成员的权限

在C++中,即使继承了父类,父类的公开成员属性在类外边也不能访问。

image.png

想要访问怎么办呢,在继承Person的时候,声明公开继承。

class Person {
public:
    char * name;
    int age=0;
public:
    Person(char * name,int age):name(name),age(age){}
};
class Student : public Person {
    Student(char * name,int age):Person(name,age) {

    }
    void printName() {
        this->name  ="aaa";
    }

};

int main(int argc, char *argv[]) {
    Student student("虾米",99);
    student.name ="xiami";
}

如果父类的成员属性是私有的,不好意思,不仅类外访问不了,类内也不能访问。这下能理解友元函数的比喻了吧,其关系超越了亲情和爱情。友元函数是能访问私有变量的。

父类属性权限修饰词子类继承时权限修饰词子类本身对父类属性的访问权限子类外边对父类属性的访问权限
privateprivate不能访问不能访问
publicprivate能访问不能访问
privatepublic不能访问不能访问
publicpublic能访问能访问

2.3 多继承,

在Java中是不允许多继承的,但是可以多实现,严格避免了二义性问题。但在C++中是可以多继承的。子类是可以有多个爸爸的。而且多继承,在编写代码的时候,会遇到有歧义的地方,也就是二义性。导致编译不通过,需要我们多注意一下。

image.png

class BaseActivity1 {
public:
    void onCreate() {
        cout << "BaseActivity1 onCreate"<<endl;
    }
    void onStart() {
        cout << "BaseActivity1 onStart"<<endl;
    }
};
class BaseActivity2{
public:
    void onCreate() {
        cout << "BaseActivity2 onCreate"<<endl;
    }
    void onStart() {
        cout << "BaseActivity2 onStart"<<endl;
    }
};
class MainActivity : public BaseActivity1 , public BaseActivity2{
public:
    void onCreate() {
        cout << "MainActivity onCreate"<<endl;
    }
};
int main(int argc, char *argv[]) {
    MainActivity mainActivity;
    //此时还没产生二义性,
    //优先匹配的子类的方法,重写了父类的方法  打印结果 MainActivity onCreate
    mainActivity.onCreate();
    //产生了二义性 Ambiguous function call. 调用方法有歧义
    //我的两个爹都有 onStart方法,你让我调谁。。。。
    //mainActivity.onStart();
    //解决办法一,和onCreate一样,在子类重写父类方法。你们两个的我都不调,我调用我自己的
    //解决办法二 指明调用那个爹的函数
    mainActivity.BaseActivity1::onStart();
}

现在我们把关系搞得更复杂一些,子类不仅有两个爸爸,它还有一个爷爷。三层继承关系。

ps:我们在开发过程中,要严格避免出现二义性的,代码中的例子是我们认为制造出来的二义性,在真实开发过程中要避免这样干。

我们写一个类,Object 里面有个公开的成员变量number,BaseActivity1,和BaseActivity2 都公开继承 Object,MainActivity 公开继承 BaseActivity1,BaseActivity2,这时我们使用MainActivity的实例,去访问number,会产生二义性,原理和上边的是一样的。也是不知道去找那个父类去调用number。 此时我们的解决方案,由原来的两种变成了三种,将BaseActivity1.和BaseActivity2都变成虚基类。这样我们就可以访问了。

class Object {
public:
    int number;
};
class BaseActivity1 : virtual public Object{};
class BaseActivity2 : virtual public Object{};
class MainActivity : public BaseActivity1 , public BaseActivity2{};

int main(int argc, char *argv[]) {
    MainActivity mainActivity;
    //会产生二义性,原理是一样的,因为BaseActivity1 BaseActivty2 都继承了Object;
    mainActivity.number;
}

BaseActivity1 虚继承 Object ,BaseActivity2 虚继承 Object, BaseActivity1,BaseActivity2被称为虚基类。多个虚基类只拥有一份父类的成员变量,解决了二义性问题。忽然感觉Java和Kotlin好香。