类的各种神奇特性

 

类的三种成员

public和private

这两个个个都会,一个是谁都能看,一个是只有自己能看

protected

protected(prəˈtektəd)是protect(prəˈtekt)的过去式和过去分词,意为v.保护;防护;(制定法律)保护;(通过征关税)保护(国内企业)

所以说呢,被保护的成员只能被类自己或者类的子子孙孙派生类看

但是啊……凡事都有例外

friend就是例外

友元函数(Friend function) 是一种不属于类但要在类里面声明的函数,相比别的不属于类的函数,由于它是类的好朋友,于是也可以偷偷光明正大地访问类的privateprotected成员

class FriendClass {  
private:  
    int a;  
public:  
    FriendClass(int v) : a(v) {}  

	friend void showValue(FriendClass &obj);  // 声明
};  
  
void showValue(FriendClass &obj) {  // 实现
    cout << obj.a << endl;  
}

不过要注意友元函数不会被继承

对象的生与死

构造函数

为什么,一定要在类内声明啊

对于一个类,如果要像一个函数一样传入参数然后对其初始化的话,就要用到构造函数,就是说这个类要怎么用传入的参数

但难绷的是,为什么C艹的构造函数的参数必须要写在类里面,而不能像函数或者别的语言那样写在最前面呢

经典原味

下面就是一个构造函数的例子

class MyClass {
public:
    int a, b;
	
//  MyClass(int x, int y) : a(x), b(y) {} 
    MyClass(int x, int y) {
        a = x;
        b = y;
    }
};

列表口味

这种写法和上面等价,这冒号后面一串也叫初始化列表,顾名思义就是一串嗷嗷带初始化的东西

class MyClass {
public:
    int a, b;
	
	MyClass(int x, int y) : a(x), b(y) {} // 这个括号里面是构造函数体
//  MyClass(int x, int y) {
//    a = x;
//    b = y;
//  }
};
    
};

能放列表里的不仅有简单的成员变量,还有各种乱七八糟的东西,包括基类的构造函数(这样就不用重新实现了呢)……

有些东西必须在对象创建伊始就要赋值(那些带const的),这时就需要初始化列表

米家口味

若是想在类外实现也未尝不可(别的方法也可以哦

class MyClass {
public:
    int a, b;
    
    MyClass(int x, int y);
};

MyClass::Myclass(int x, int y) {
    a = x; b = y;
}

可部分不自选口味

对于某些神奇的类,有的参数是不一定要传入的,就可以实现多种选择方案

class MyClass { 
public:
    int a, b;
    
    MyClass(int x, int y) : a(x), b(y) {}
    
    Myclass(int x) : Myclass(x, 0) {} 

};

本质上还是初始化列表

class MyClass { // 另一个在别的语言更多见的写法
public:
    int a, b;
    
    MyClass(int x, int y = 0) : a(x), b(y) {}

};

这时构造函数里面的y就默认为0了,当然也可以改

工厂源头口味

静态工厂方法(Static Factory Method)是一种创建对象的设计模式,它通过一个静态方法创建并返回类的实例,而不是直接调用类的构造函数。这种方法提供了更灵活的对象创建方式,适合复杂对象初始化的场景。

这多少有点太复杂了……

析构函数

谁起的这名字,望文生义不了一点

有生便有灭,虽然我不知道为什么构造和析构能对得上

析构函数就是在对象销毁时自动执行的函数,用来释放内存之类的资源防止电脑爆掉

和构造函数不同的地方就是会多一个~,比如~MyClass()

请看

class MyClass {
public:
    int a, b, *arr;

    MyClass(int x, int y) {
        arr = new int[x];
        a = x; b = y;
    }
    
    ~Myclass() {
        delete[] arr;
    }
};

当这个类的对象没用了的时候,被new分配来的内存就会被delete掉,真是太棒了

类的继承

普通的?多的?虚的?

还挺复杂

一般的继承

C艹里面继承用的是冒号:,也就是派生类 : 基类,非常的简单(

不过基类前面可能经常要加上诸如publicprotected之类的,不然class的默认继承会把基类所有的成员变成private哦 (相对的,struct的默认继承会把能public的都public了)

当然,本来就不给看的就是不给看哦

一次买够

和某些面向对象语言不同的是,C艹可以干出多继承这种比较反直觉的操作

class MyClass0 {  
public:  
    int a, b;  
	MyClass0(int x = 5, int y = 6) : a(x), b(y) {}  
};

class MyClass1 {  
public:  
    int c, d;  
	MyClass1(int x = 3, int y = 4) : c(x), d(y) {}  
};

class HisClass : public MyClass0, public MyClass1 {  
public:  
    HisClass(int x = 3, int y = 4, int z = 5, int n = 6) { // 这里不能用初始化列表  
        a = x; b = y; c = z; d = n;  
    }  
	void output() {  // HisClass a(1, 2, 3) 在这里会输出1 2 3 6
		printf("%d %d %d %d\n", a, b, c, d);  
    }  
};

就是一次性把两个类继承了,不过这样可能会出现一些比较混乱的问题……

虚伪的多面函数

虚函数是一种可以被重写(override) 的函数成员,也就是说,如果你觉得前人写得太烂了,就可以自己来操刀

好吧其实这个特性是更多地用来实现多态

没那么虚的虚函数

虚函数就是函数成员前面加上一个virtual

class MyClass {  
public:  
    virtual void sayHello() {  
        cout << "Hello" << endl;  
    }  
};

非常的简单,如果要重写的话可以

class ChineseClass : public MyClass {  
public:  
    void sayHello() override {  // override不是必须的,但还是加上比较好
        cout << "你好" << endl;  
    }  
};

但是如果你只是想在前人的基础上加点自己的东西而不是想推倒一切重来的话呢?也可以像这样

class YuzusoftCLass : public ChineseClass {  
public:  
    void sayHello() override final {  // 加上final即使被继承也再也不能被重写了
        ChineseClass :: sayHello();  // 调用上一级的同名虚函数
        cout << "Ciallo~" << endl;  
    }  
};
更加虚的纯虚函数

纯虚函数就是写了个函数名和返回值类型的玩意,意在告诉后人要写这玩意,自己就不干了

纯虚函数与虚函数相比真的啥也不是啊

class EmptyClass {  
public:  
    virtual void sayHello() = 0;  
};

这种强制要求派生类实现某个方法的基类又叫做抽象类,因为确实很抽象没有明确的父类实现

抽象类是不能有实例的,换句话说就是抽象不会有对象(

还要给它写个表

虚函数表(vtable) 是C艹中多态实现的基础,每个有虚函数或继承了虚函数的类都有一个,里面装的是这个类里面所有虚函数的指针

对的对的,其实C里面函数是可以通过函数指针调用的,事实上每个函数都有个指向它的指针,可以通过指针来调用它 声明函数指针的语法是 返回类型 (*指针名)(参数类型列表);

也许大概可以理解为每个对象都有一个数组来存指针,如果函数继承后没有重写就直接引用基类的指针,重写与新增都会换个指针

论构造和析构的继承

构造

一般而言,构造函数是不会自动继承的,除非在C++11及以后使用using强调你要继承

比如

class MyClass {
public:
    int a, b;

	MyClass(int x, int y) : a(x), b(y) {} 
};

class YourClass : public Myclass {
public:
	int c;

	using MyClass::MyClass; // 哇还有命名空间
}

这好吗?至少能这么做也是不错的

当然,如果想改造一下也未尝不可……

class YourClass : public MyClass {  
    int c = 0;  
public:  
    YourClass(int x) : MyClass(x) {  // 其实啊,这是初始化列表
        cout << "Ciallo~" << endl;  // 加上各种你喜欢的东西
    }  
};

析构

析构函数就不用想那么多了,因为无论怎么样对象死掉的时候都会调用派生类和基类的析构函数,所以压根不用继承

对象的运算

当然就是运算符重载啦

运算符重载(Operator Overloading) 就是让你自己的对象也能用=-*/<<>>之类的运算符的,很多运算符都能重载,除了. .* :: ?: sizeof alignof typeid这些不行

而且啊,运算符的优先级、运算对象数量都不能变,不然就太不符合直觉了

有的运算符(像 =[]()->)都只能作为成员函数重载