0.0 类和结构

0.1 struct
// C++ 还保留了 struct
// 功能跟class类似,只是struct默认是pulic, class默认是private  

typedef struct StuStruct {
    int age;
    char *name;

    void info();
} Stus;

void Stus::info() {
    printf("in info, name : %s, age : %d\n", this->name, this->age);
}
0.2 class
class People {
protected:
    char *name;
    int age;

public:
    People();

    People(char *name, int age);
};

People::People() {
    this->name = "def name";
    this->age = 0;
}

People::People(char *name, int age) {
    this->name = name;
    this->age = age;
}

0.3 class 继承
class Student : public People {
protected:
    int score;

public:
    Student();

    Student(char *name, int age, int score);

    void info();
};

Student::Student() {
    this->score = 0;
}

// 冒号前面是派生类构造函数的头部,这和我们以前介绍的构造函数的形式一样,
// 但它的形参列表包括了初始化基类和派生类的成员变量所需的数据;
// 冒号后面是对基类构造函数的调用,这和普通构造函数的参数初始化表非常类似。
Student::Student(char *name1, int age1, int score1) :     People(name1, age1), score(score1) { }

void Student::info() {
    // age 可以不加this
    cout << "name: " << this->name << " \n age :" << age << "\n score : " << this->score << endl;
}

int main() {
    Student stu;
    stu.info();

    Student stu2("HikoQiu", 27, 99);
    stu2.info();
    Student::info();
    std::string();
    cout << "hello world" << endl;

    return 0;
}

实际上,对象(派生类赋值给父类)之间的赋值是成员变量的赋值,成员函数不存在赋值问题。在赋值时,会舍弃派生类自己的成员,也就是”大材小用“.

基类对象和派生类对象之间的赋值仅仅是对应的成员变量的赋值,不会影响成员函数,不会影响 this 指针。

成员变量和成员函数不在同一个内存区域,系统通过 this 指针来访问成员变量,但是却不通过它来访问成员函数。

系统通过对象的类型来访问成员函数。例如 p 的类型是 A,那么不管 p 指向哪个对象,都访问 A 类的成员函数。

0.4 virtual class
class A {
protected:
    int a;
public:
    A(int a) : a(a) { }
};

class B : virtual public A {
protected:
    int b;

public:
    B(int a, int b) : A(a), b(b) { }
};

class C : virtual public A {
protected:
    int c;

public:
    C(int a, int c) : A(a), c(c) { }
};

class D : virtual public B, virtual public C {
protected:
    int d;

public:
    // 现在,由于虚基类在派生类中只有一份成员变量,所以对这份成员变量的初始化必须由派生类直接给出。
    // 如果不由最后的派生类直接对虚基类初始化,而由虚基类的直接派生类(如类B和类C)对虚基类初始化,
    // 就有可能由于在类B和类C的构造函数中对虚基类给出不同的初始化参数而产生矛盾。
    // 所以规定:在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。

    // 可能有疑问:类D的构造函数通过初始化表调了虚基类的构造函数A,
    // 而类B和类C的构造函数也通过初始化表调用了虚基类的构造函数A,
    // 这样虚基类的构造函数岂非被调用了3次?大家不必过虑,C++编译系统只执行最后的派生类对虚基类的构造函数的调用,
    // 而忽略虚基类的其他派生类(如类B和类C)对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。
    D(int a, int b, int c, int d) : A(a), B(a, b), C(a, c), d(d) { }

    // 没加virtual
    // D(int a, int b, int c, int d) : B(a, b), C(a, c), d(d) { }

    void display();
};

void D::display() {
//    cout << "a:" << a << endl; // 如果没有使用virtual :  error: non-static member 'a' found in multiple base-class subobjects of type 'A':
//    cout << "a:" << B::a << endl; // 如果没有使用virtual, 需要这样使用
//    cout << this->B::a<<endl;

    // 加了virtual 虚基类, 就不会由于多重继承 A 而导致 D 中有多个 a 字段, 所以不用像
    // 上面的方式加类名和域解析符(B::a)
    cout << "a : " << this->a << endl;
    cout << "b : " << this->b << endl;
    cout << "c : " << this->c << endl;
    cout << "d : "  << this->d << endl;
}

int main() {

    D d(1, 2, 3, 4);
    d.display();

    return 0;
}

1.0 重载

1.1 操作符重载 (1)

class complex {
private:
    double real, imag;
public:
    complex() : real(0.0), imag(0.0) { }

    complex(double a, double b) : real(a), imag(b) { };

    // 1.1 运算符重载 - 对象方法
    // 返回值类型 operator 运算符名称 (形参表列){}
    // 函数后的const表示这个函数不会修改所处class的member variable
    //
    // 备注: 即使没有定义 const 版本的重载函数,这段代码也是可以正确运行的,但是非 const 成员函数不能处理 const 对象,
    // 所以在编程时通常会提供两个版本的运算符重载函数。
    complex operator+(const complex &a) const;

    // 2.1 运算符重载 - 全局方法
    // 这里不能在方法之后设置const, 是因为该方法不是complex类的函数,而是友元函数.
    friend complex operator*(const complex &a, const complex &b);

    void info();
};

complex complex::operator+(const complex &a) const {
    return complex(real + a.real, imag + a.imag);
}

void complex::info() {
    cout << "real: " << real << " imag: " << imag << endl;
}

complex operator*(const complex &a, const complex &b) {
    return complex(a.real * b.real, a.imag * b.imag);
}
int main() {

    cout << "Hello operator overload." << endl;

    complex c1(5.4, 6.8);
    complex c2(9.9, 6.6);

    complex c3 = c1 + c2;
    complex c4 = c1 * c2;

    c3.info();
    c4.info();
}
1.2 操作符重载 (2)

class Array {
private:
    int length;
    int *num;

public:
    Array() : length(0), num(NULL) { }

    Array(int n);

    // 即使没有定义 const 版本的重载函数,这段代码也是可以正确运行的,但是非 const 成员函数不能处理 const 对象,所以在编程时通常会提供两个版本的运算符重载函数。

    // 第一个函数最后不带 const,加上 const 意味着该成员函数是常成员函数,如果第一个函数后面也加上了const,则两个函数仅有返回值不同,编译器不能够区分这是函数重载,会报错。
    // 这两个版本的重载函数其实很好理解,第一个能够修改对象,第二个只能访问对象而不能修改对象。
    int &operator[](int i);

    const int &operator[](int i) const;

    int len();

    void free();

    ~Array();
};

Array::~Array() {
    if (*num != NULL) {
        delete[]num;
    } else {
        cout << "num is NULL." << endl;
    }

    cout << "~A() invoked." << endl;
}

Array::Array(int n) {
    num = new int[n];
    length = n;
}

int Array::len() {
    return length;
}

void  Array::free() {
    delete[] num;
}

int &Array::operator[](int i) {
    if (i < 0 || i >= len()) {
        throw string("\nout of bounds.");
    }

    return num[i];
}

const int &Array::operator[](int i) const {
    if (i < 0 || i >= length) {
        throw string("\nout of bounds.");
    }

    return num[i];
}

int main() {

    Array A(5);

    int i = 0;
    try {
        for (i = 0; i < A.len(); i++) {
            A[i] = i;
        }

        for (i = 0; i < 6; i++) {
            cout << "index i = " << i << ", val: " << A[i] << endl;
        }
    } catch (string s) {
        cerr << s << ", i = " << i << endl;
        A.free();
    }

    return 0;
}

/**
 *
 * 其他运算符:
 *
重载赋值运算符时,函数的参数和返回值类型都必须是对象的引用。以 Book 类为例来说,赋值运算符重载函数一般有两种原型:
Book & operator=( Book &b );
Book & operator=( const Book &b );
返回值和参数都是 Book 类对象的引用。下面一种原型则规定在赋值时不能修改原来的对象。

赋值运算符重载函数除了能有对象引用这样的参数之外,也能有其它参数。但是其它参数必须给出默认值。如下所示:
Book & operator=(const Book &b, a = 10);
 */

2.0 模板

2.1 模板 (1)

class Array {
private:
    int length;
    int *num;

public:
    Array() : length(0), num(NULL) { }

    Array(int n);

    // 即使没有定义 const 版本的重载函数,这段代码也是可以正确运行的,但是非 const 成员函数不能处理 const 对象,所以在编程时通常会提供两个版本的运算符重载函数。

    // 第一个函数最后不带 const,加上 const 意味着该成员函数是常成员函数,如果第一个函数后面也加上了const,则两个函数仅有返回值不同,编译器不能够区分这是函数重载,会报错。
    // 这两个版本的重载函数其实很好理解,第一个能够修改对象,第二个只能访问对象而不能修改对象。
    int &operator[](int i);

    const int &operator[](int i) const;

    int len();

    void free();

    ~Array();
};

Array::~Array() {
    if (*num != NULL) {
        delete[]num;
    } else {
        cout << "num is NULL." << endl;
    }

    cout << "~A() invoked." << endl;
}

Array::Array(int n) {
    num = new int[n];
    length = n;
}

int Array::len() {
    return length;
}

void  Array::free() {
    delete[] num;
}

int &Array::operator[](int i) {
    if (i < 0 || i >= len()) {
        throw string("\nout of bounds.");
    }

    return num[i];
}

const int &Array::operator[](int i) const {
    if (i < 0 || i >= length) {
        throw string("\nout of bounds.");
    }

    return num[i];
}

int main() {

    Array A(5);

    int i = 0;
    try {
        for (i = 0; i < A.len(); i++) {
            A[i] = i;
        }

        for (i = 0; i < 6; i++) {
            cout << "index i = " << i << ", val: " << A[i] << endl;
        }
    } catch (string s) {
        cerr << s << ", i = " << i << endl;
        A.free();
    }

    return 0;
}

/**
 * 其他运算符:
重载赋值运算符时,函数的参数和返回值类型都必须是对象的引用。以 Book 类为例来说,赋值运算符重载函数一般有两种原型:
Book & operator=( Book &b );
Book & operator=( const Book &b );
返回值和参数都是 Book 类对象的引用。下面一种原型则规定在赋值时不能修改原来的对象。

赋值运算符重载函数除了能有对象引用这样的参数之外,也能有其它参数。但是其它参数必须给出默认值。如下所示:
Book & operator=(const Book &b, a = 10);
 */