继承

访问控制

使用:进行继承,有三种继承的方式public,protected,private,被继承的类叫做基类,继承基类的类叫做派生类

publicprotectedprivate 是三种访问控制修饰符,用于控制成员在类的内部和外部的可见性和可访问性,public在类的内外都可以访问,protected在类的外部不可访问,在子类中可以直接访问,但子类中不能通过对象名对protected进行访问,private在子类中也不可访问

  • public:基类的public成员在派生类中仍为public,积累的protected成员在派生类中仍是protected,基类的private成员为private
  • protected:基类的public成员在派生类中变成protected,积累的protected成员在派生类中仍为protected,积累的private成员为private
  • private:基类的public和protected成员在派生类中都变成private

继承中的构造函数和析构函数

创建子类会先调用父类的构造函数

子类不能继承父类的构造函数,但可以显式调用父类的构造函数,如果没有调用基类的构造函数,基类的默认构造函数会在子类的构造函数之前自动调用,如果基类没有默认构造函数,子类必须通过:BaseClass()的方式显示调用基类的构造函数

销毁子类会先调用子类的析构函数

当对象被销毁时,子类的析构函数会首先被调用,然后基类的析构函数会被调用,析构函数的调用顺序和构造函数的调用顺序相反

多态和虚函数

通过使用虚函数(virtual),可以在基类中定义一个接口,并在派生类中重写该接口的实现,这样,基类指针或引用可以指向派生类对象,并调用派生类的重写方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Base {
public:
virtual void show() {
std::cout << "Base class show function" << std::endl;
}
};

class Derived : public Base {
public:
void show() override {
std::cout << "Derived class show function" << std::endl;
}
};

int main() {
Base* basePtr = new Derived();
basePtr->show(); // 输出:Derived class show function
delete basePtr;
return 0;
}

对于虚函数,在基类中有了实现,那么子类不一定必须进行重写,如果没有重写,就会调用父类中的虚函数。而基类中没有实现的虚函数叫做纯虚函数,使用=0表示该函数时虚函数,纯虚函数强制派生类必须提供该函数的实现,含有纯虚函数的类时抽象类,无法实例化

多重继承

c++支持多重继承,即一个类可以从多个基类继承。

在多重继承中,如果基类中有相同名称的函数,派生类需要显示指定调用的哪一个基类的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Base1 {
public:
void show() {
std::cout << "Base1 class show function" << std::endl;
}
};

class Base2 {
public:
void show() {
std::cout << "Base2 class show function" << std::endl;
}
};

class Derived : public Base1, public Base2 {
};

int main() {
Derived obj;
obj.Base1::show(); // 调用 Base1 的 show()
obj.Base2::show(); // 调用 Base2 的 show()
return 0;
}

虚继承

当多重继承中多个基类继承自同一个祖先类时,可能会发生“菱形继承”——祖先类的成员在派生类中出现多次,为了解决这个问题,C++提供了虚继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A {
public:
int value;
};

class B : virtual public A {
};

class C : virtual public A {
};

class D : public B, public C {
};

int main() {
D obj;
obj.value = 10; // 通过虚继承,`value` 只继承一次
return 0;
}

操作符重载

所有重载的操作符本质上都是一个函数,操作符对应的操作子是该操作符函数的参数。任意一个操作符都能被重载为自由函数或成员函数。定义重载函数时要保证每一个操作子都有对应的参数位置,

我们可以在类的内部声明友元函数,然后在类的外部实现友元函数,这样能更加方便的对类内的成员进行访问。如果对同一个操作符,在多个类上进行不同的操作符重载,那么每个被重载的类都有自己对应的操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <iostream>
#include <string>
using namespace std;

// 定义 Person 类
class Person {
private:
string name_;
int age_;

public:
Person(const string& name, int age) : name_(name), age_(age) {}

// 重载 << 操作符,用于输出 Person 对象
friend ostream& operator<<(ostream& os, const Person& person) {
os << "Person(Name: " << person.name_ << ", Age: " << person.age_ << ")";
return os;
}
};

// 定义 Car 类
class Car {
private:
string brand_;
int year_;

public:
Car(const string& brand, int year) : brand_(brand), year_(year) {}

// 重载 << 操作符,用于输出 Car 对象
friend ostream& operator<<(ostream& os, const Car& car) {
os << "Car(Brand: " << car.brand_ << ", Year: " << car.year_ << ")";
return os;
}
};

int main() {
// 创建 Person 和 Car 对象
Person p1("John Doe", 30);
Car c1("Toyota", 2020);

// 分别调用重载的 << 操作符
cout << p1 << endl; // 调用 Person 类的 << 重载
cout << c1 << endl; // 调用 Car 类的 << 重载

return 0;
}

流操作符返回值为流是为了实现链式操作。

隐式类型转换

当为用户自定义的类型进行操作符重载时,如果要对基本类型进行操作,要么对一个操作符进行多次重载,要么显式地写出转换

除了C++的standard conversion,C++还允许用户自定义隐式转换规则,只要有对应地user-defined conversion

user-defined conversion 有两种:

转换构造函数(converting constructor)

用户定义的转换函数(user-defined conversion constructor)

转换构造函数是所有构造函数的一种性质,凡是没有explicit说明符的构造函数都是转换构造函数

例如我们定义以下

1
2
3
4
5
6
7
8
class complex{
public:
complex(doubole d);
friend complex operator+(complex c,complex d);
}
complex a;
double d;
complex z = a+d;

那么在执行complex z = a+d;的时候,d会进行隐式类型转换变为complex,但是如果operator+是成员函数,在参数传递的时候就会失去对称性,所以一般将=操作符设置为成员函数,=作为成员函数重载还能保证第一个参数不会被改变。

如果构造函数被explicit修饰,那么这个构造函数就不是converting constructor,不能用作隐式类型转换,只能用作显式类型转换

用户定义的转换函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Complex {
double r, i;
public:
Complex(double r) : r(r), i(0) {};
Complex(double r, double i) : r(r), i(i) {};
operator string() const {
cout << "operator string" << endl;
return to_string(r) + " + " + to_string(i) + 'i';
}
explicit operator double() const {
cout << "operator double" << endl;
return r;
}
explicit operator bool() const {
cout << "operator bool" << endl;
return r != 0 || i != 0;
}
};

void foo(double);

int main() {
Complex c = 3; // implicit conversion, calls Complex(3)
string str = c; // implicit conversion, calls Complex::string()

foo(double(c)); // OK, explicit conversion
foo((double)c); // OK, explicit conversion
// foo(c); // Error: no matching call to 'foo', because no
// implicit conversion from Complex to double

// bool b = c; // Error: no implicit conversion from Complex to bool
if (c) { // OK, this context considers explicit operator bool
cout << str;
}
return 0;
}

string(),explicit double(),explicit bool()都是用户定义的转换函数,其中double加上了explicit只能显式调用,bool虽然加上了explicit,但是在上下文包括if,while,for的条件语句!,||,&&,以及三元操作符?:时,即使加上explicitbool()的隐式转换也会执行

线程