#CS225
模板本质上是一种参数化的类型或函数。 你可以把类型或函数的一部分(通常是类型)作为参数传递给模板,编译器会根据你提供的参数生成特定的代码。
C++ 中有两种主要的模板:
2.1 函数模板
函数模板允许你创建可以处理多种数据类型的函数。
语法:
1
2
3
4
template <typename TypeParameter1, typename TypeParameter2, ...>
return_type function_name(parameter_list) {
// 函数体
}
template: 关键字,表示这是一个模板定义。typename: 关键字,用来声明类型参数。 也可以使用 class 关键字来代替 typename,但 typename 更清晰地表达了参数是类型。TypeParameter1, TypeParameter2, ...: 类型参数列表,用逗号分隔。 这些类型参数可以在函数定义中使用。return_type: 函数的返回类型。function_name: 函数名。parameter_list: 函数的参数列表。例子:交换两个变量的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template <typename T>
void swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
int main() {
int x = 5, y = 10;
swap(x, y);
std::cout << "x = " << x << ", y = " << y << std::endl; // 输出 x = 10, y = 5
double p = 3.14, q = 2.71;
swap(p, q);
std::cout << "p = " << p << ", q = " << q << std::endl; // 输出 p = 2.71, q = 3.14
return 0;
}
2.2 类模板
类模板允许你创建可以处理多种数据类型的类。 这在容器类(例如 std::vector,std::list)的实现中非常有用。
语法:
1
2
3
4
template <typename TypeParameter1, typename TypeParameter2, ...>
class ClassName {
// 类定义 (成员变量、成员函数等)
};
template: 关键字,表示这是一个模板定义。typename: 关键字,用来声明类型参数。 也可以使用 class 关键字来代替 typename。TypeParameter1, TypeParameter2, ...: 类型参数列表,用逗号分隔。 这些类型参数可以在类定义中使用。ClassName: 类名。例子:一个简单的动态数组类
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
48
49
50
51
52
53
54
55
56
57
58
59
60
template <typename T>
class DynamicArray {
private:
T* data;
int size;
int capacity;
public:
DynamicArray(int initialCapacity = 10) : size(0), capacity(initialCapacity) {
data = new T[capacity];
}
~DynamicArray() {
delete[] data;
}
void push_back(const T& value) {
if (size == capacity) {
// 扩容
capacity *= 2;
T* newData = new T[capacity];
for (int i = 0; i < size; ++i) {
newData[i] = data[i];
}
delete[] data;
data = newData;
}
data[size++] = value;
}
T& operator[](int index) {
if (index < 0 || index >= size) {
throw std::out_of_range("Index out of bounds");
}
return data[index];
}
int getSize() const {
return size;
}
};
int main() {
DynamicArray<int> intArray;
intArray.push_back(1);
intArray.push_back(2);
intArray.push_back(3);
std::cout << "Int Array Size: " << intArray.getSize() << std::endl; // 输出 Int Array Size: 3
std::cout << "Int Array[0]: " << intArray[0] << std::endl; // 输出 Int Array[0]: 1
DynamicArray<double> doubleArray;
doubleArray.push_back(3.14);
doubleArray.push_back(2.71);
std::cout << "Double Array Size: " << doubleArray.getSize() << std::endl; // 输出 Double Array Size: 2
std::cout << "Double Array[1]: " << doubleArray[1] << std::endl; // 输出 Double Array[1]: 2.71
return 0;
}
在这个例子中,DynamicArray 是一个类模板。 typename T 表示 T 是一个类型参数,可以用来指定数组中存储的数据类型。 DynamicArray<int> 创建一个存储整数的动态数组,而 DynamicArray<double> 创建一个存储双精度浮点数的动态数组。
模板参数可以是:
typename 或 class 声明,例如 typename T。template <int N>,这里 N 就是一个非类型参数。3.1 非类型模板参数
非类型模板参数允许你在编译时指定一个常量值。
例子:固定大小的数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template <typename T, int N>
class FixedSizeArray {
private:
T data[N];
public:
T& operator[](int index) {
if (index < 0 || index >= N) {
throw std::out_of_range("Index out of bounds");
}
return data[index];
}
};
int main() {
FixedSizeArray<int, 5> intArray;
intArray[0] = 10;
std::cout << intArray[0] << std::endl; // 输出 10
return 0;
}
在这个例子中,N 是一个非类型模板参数,它指定了数组的大小。 FixedSizeArray<int, 5> 创建一个可以存储 5 个整数的固定大小的数组。
3.2 模板参数作为模板参数
模板参数本身也可以是另一个模板,这允许你创建更复杂和灵活的数据结构。
例子:存储一个 std::vector 的 std::list
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
#include <vector>
#include <list>
#include <iostream>
template <typename T>
void printList(const std::list<T>& lst) {
for (const auto& element : lst) {
std::cout << element << " ";
}
std::cout << std::endl;
}
int main() {
// Create a list of vectors of integers
std::list<std::vector<int>> listOfVectors;
// Add some vectors to the list
listOfVectors.push_back({1, 2, 3});
listOfVectors.push_back({4, 5, 6, 7});
listOfVectors.push_back({8, 9});
// Iterate through the list and print each vector
for (const auto& vec : listOfVectors) {
std::cout << "Vector elements: ";
for (int element : vec) {
std::cout << element << " ";
}
std::cout << std::endl;
}
std::list<int> myList = {10, 20, 30, 40, 50};
printList(myList);
return 0;
}
模板本身不是真正的类或函数。 只有当你使用具体的类型参数来实例化模板时,编译器才会生成实际的代码。 模板实例化可以分为两种:
max(x, y) 会隐式地实例化 max<int>(int a, int b)。template int max<int>(int a, int b); 会显式地实例化 max<int>(int a, int b)。显式实例化例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template <typename T>
T min(T a, T b) {
return (a < b) ? a : b;
}
// 显式实例化 min<double>(double a, double b)
template double min<double>(double a, double b);
int main() {
// 你仍然可以使用隐式实例化
std::cout << min(5, 10) << std::endl; // 输出 5 (int)
// 使用显式实例化
double p = 3.14, q = 2.71;
std::cout << min<double>(p, q) << std::endl; // 输出 2.71 (double)
return 0;
}
模板特化允许你为特定的类型参数组合提供不同的模板实现。 这在你需要为某些类型提供优化或特殊处理时非常有用。
5.1 函数模板特化
你可以为特定的类型提供不同的函数模板实现。
例子:为 char* 类型的 max 函数提供特殊处理
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <cstring> // for strcmp
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
// 函数模板特化:为 char* 类型提供特殊处理
template<>
const char* max<const char*>(const char* a, const char* b) {
return (std::strcmp(a, b) > 0) ? a : b;
}
1. struct(结构体)
定义: struct 是一种用户自定义的数据类型,它可以将多个不同类型的变量组合在一起,形成一个复合数据类型。struct 中的成员变量可以是基本数据类型(如 int、float、char),也可以是其他用户自定义类型(如 class、struct)。
语法:
1
2
3
4
5
struct 结构体名 {
成员变量类型 成员变量名1;
成员变量类型 成员变量名2;
// ...
}; // 注意分号
例如:
1
2
3
4
struct Point {
int x;
int y;
};
使用:
创建结构体变量:
1
Point p1; // 创建一个 Point 类型的变量 p1
访问结构体成员: 使用 . 运算符访问结构体成员。
1
2
3
p1.x = 10; // 设置 p1 的 x 坐标为 10
p1.y = 20; // 设置 p1 的 y 坐标为 20
cout << "x = " << p1.x << ", y = " << p1.y << endl; // 输出 p1 的 x 和 y 坐标
初始化:
int 类型默认为 0)。列表初始化:
1
Point p2 = {30, 40}; // 使用列表初始化 p2
指定成员初始化(C++20):
1
Point p3{.x = 50, .y = 60}; // 使用指定成员初始化 p3
2. struct 与 class 的区别
struct:成员变量的默认访问权限是 public(公共的)。class:成员变量的默认访问权限是 private(私有的)。struct:默认是 public 继承。class:默认是 private 继承。其他方面:
struct 默认不包含 Member Function1. Copy Constructor vs Assignment Operator
| 对比项 | 拷贝构造函数 | 赋值运算符 |
|---|---|---|
| 作用 | 用于创建新对象并复制 | 用于已有对象的赋值 |
| 调用时机 | 初始化对象时 | 赋值操作时 |
| 默认实现 | 逐成员拷贝 | 逐成员赋值 |
| 是否释放旧资源 | 否(对象是新创建的) | 是(必须释放旧资源) |
拷贝构造函数(Copy Constructor)
定义:
拷贝构造函数用于创建新对象并将另一个对象的值复制到新对象中。
语法:
1
ClassName(const ClassName& other);
调用时机:
ClassName obj2 = obj1; 进行对象初始化时。ClassName obj2(obj1); 进行对象初始化时。示例:
1
2
3
4
5
6
7
8
9
class Example {
public:
int* data;
// 拷贝构造函数
Example(const Example& other) {
data = new int(*other.data);
}
};
2. 赋值运算符(Assignment Operator)
定义: 赋值运算符用于将一个已经存在的对象的值赋给另一个已经存在的对象。
语法:
1
ClassName& operator=(const ClassName& other);
调用时机:
obj1 = obj2;(obj1 已经存在,给它赋值)
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Example {
public:
int* data;
// 赋值运算符
Example& operator=(const Example& other) {
if (this != &other) { // 防止自赋值,常见的代码流程
delete data; // 释放旧资源!!重要
data = new int(*other.data);
}
return *this;
}
};
2. Rule of Three(三法则)
三法则(Rule of Three) 指的是如果一个类需要显式定义以下三者之一,则很可能需要显式定义另外两个:
为什么需要三法则?
当类涉及动态内存分配或其他资源管理(如文件句柄、互斥锁),默认的拷贝构造、赋值运算符只会浅拷贝,导致资源重复释放(如 delete 两次)或悬挂指针(dangling pointer)。
示例:违反三法则的类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class BadExample {
public:
int* data;
BadExample(int value) { data = new int(value); }
~BadExample() { delete data; } // 需要释放资源
// 但是没有提供拷贝构造和赋值运算符,导致浅拷贝问题
};
void test() {
BadExample obj1(10);
BadExample obj2 = obj1; // 这里调用默认拷贝构造,data 指针被浅拷贝!
obj2 = obj1; // 这里调用默认赋值运算符,data 指针被浅拷贝!
} // obj1 和 obj2 都会调用析构函数,导致 delete 两次,程序崩溃!
正确实现(遵循三法则)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class GoodExample {
public:
int* data;
// 构造函数
GoodExample(int value) { data = new int(value); }
// 拷贝构造函数
GoodExample(const GoodExample& other) {
data = new int(*other.data);
}
// 赋值运算符
GoodExample& operator=(const GoodExample& other) {
if (this != &other) {
delete data; // 释放旧资源
data = new int(*other.data);
}
return *this;
}
// 析构函数
~GoodExample() { delete data; }
};
[!tip] Pass by Value, Pass by Reference, Pass by Pointers
1. 概念对比
| 特性 | 值传递 (Pass by Value) | 引用传递 (Pass by Reference) | 指针传递 (Pass by Pointer) |
|---|---|---|---|
| 传递内容 | 实参的副本 | 实参的别名 | 实参的地址 |
| 修改实参 | 否 | 是 | 是 |
| 空值 | 不可能 | 不可能 | 可能 (空指针) |
| 开销 | 较大 (复制对象) | 较小 (无复制) | 较小 (复制地址) |
| 安全性 | 高 (不修改实参) | 中 | 低 (可能空指针) |
| Copy Constructor | 调用,构造创建对象的副本 | 不调用,因为传递的是对象的别名 | 不调用,因为传递的是对象的地址 |
2. 详细解释
值传递 (Pass by Value)
引用传递 (Pass by Reference)
NULL。指针传递 (Pass by Pointer)
3. 代码示例
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>
using namespace std;
// 值传递
void modifyValue(int x) {
x = 10; // 修改的是 x 的副本,不会影响实参
cout << "Inside modifyValue: x = " << x << endl;
}
// 引用传递
void modifyReference(int &x) {
x = 20; // 修改的是 x 的别名,会影响实参
cout << "Inside modifyReference: x = " << x << endl;
}
// 指针传递
void modifyPointer(int *x) {
if (x != nullptr) { // 检查指针是否为空
*x = 30; // 通过解引用指针,修改实参
cout << "Inside modifyPointer: *x = " << *x << endl;
} else {
cout << "Error: Null pointer!" << endl;
}
}
int main() {
int a = 1;
cout << "Before modifyValue: a = " << a << endl;
modifyValue(a);
cout << "After modifyValue: a = " << a << endl;
cout << "Before modifyReference: a = " << a << endl;
modifyReference(a);
cout << "After modifyReference: a = " << a << endl;
cout << "Before modifyPointer: a = " << a << endl;
modifyPointer(&a);
cout << "After modifyPointer: a = " << a << endl;
// 指针传递空指针示例
int *ptr = nullptr;
modifyPointer(ptr);
return 0;
}
[!tip] Const 关键字
1. const 成员函数 (Const Member Functions)
const 成员函数是指在类中声明为 const 的成员函数。它承诺不会修改调用它的对象的状态,即不会修改对象的任何非 mutable 成员变量。
声明方式:
1
2
3
4
class MyClass {
public:
returnType functionName(parameterList) const;
};
const 关键字放在函数声明的末尾,在参数列表之后。在 .cpp .h 文件中均需要声明标注,在 .cpp 文件中写在函数体前
限制:
const 成员函数不能修改对象的任何非 mutable 成员变量。试图修改会导致编译错误。const 成员函数只能调用其他 const 成员函数(或非成员的 const 正确的函数)。它们不能调用非 const 成员函数,因为非 const 成员函数可能会修改对象的状态。2. 哪些函数可以访问 const 变量 (Const Objects’ Member Variables)
const 对象只能调用 const 成员函数。const 对象可以调用任何成员函数(包括 const 和非 const 函数)。const 成员函数可以访问任何对象的成员变量,但不能修改它们(除非使用 mutable 关键字)。3. 代码示例
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
#include <iostream>
class MyClass {
private:
int value;
mutable int accessCount; // mutable 成员变量可以在 const 成员函数中修改
public:
MyClass(int val) : value(val), accessCount(0) {}
// const 成员函数,用于获取 value 的值
int getValue() const {
accessCount++; // 允许修改 mutable 成员变量
std::cout << "getValue() const called. Access count: " << accessCount << std::endl;
return value;
}
// 非 const 成员函数,用于设置 value 的值
void setValue(int val) {
value = val;
std::cout << "setValue() called." << std::endl;
}
void print() const {
std::cout << "Value: " << value << ", Access Count: " << accessCount << std::endl;
}
};
int main() {
MyClass obj(10); // 非 const 对象
std::cout << "Initial value: " << obj.getValue() << std::endl; // 调用 const 成员函数
obj.setValue(20); // 调用非 const 成员函数
std::cout << "New value: " << obj.getValue() << std::endl; // 再次调用 const 成员函数
obj.print();
const MyClass constObj(30); // const 对象
std::cout << "Const object value: " << constObj.getValue() << std::endl; // 调用 const 成员函数
//constObj.setValue(40); // 错误:const 对象不能调用非 const 成员函数
constObj.print();
return 0;
}
[!tip] Return by Value, Reference, Pointer
1. 返回值传递 (return by value):
2. 返回引用传递 (return by reference):
3. 返回指针传递 (return by pointer):
There shall be no references to references, no arrays of references, and no pointers to references. References are not objects. They do not occupy memory. — Declaring an array of nothing
1. Virtual Function(虚函数)
定义:Virtual Function 是在基类(Base Class)中声明的函数,使用 virtual 关键字修饰。它允许在派生类(Derived Class)中重写(Override)该函数,实现运行时多态性。
作用:Virtual Function 的主要作用是允许通过基类指针或引用调用派生类中重写的函数。
重载调用时机
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
#include <iostream>
class Base {
public:
virtual void print() {
std::cout << "Base class print" << std::endl;
}
};
class Derived : public Base {
public:
void print() override { // 重写基类的 virtual function
std::cout << "Derived class print" << std::endl;
}
};
int main() {
Base* basePtr = new Base();
Base* derivedPtr = new Derived();
Derived* derivedPtr2 = new Derived();
// 1. 使用基类指针指向基类对象
basePtr->print(); // 输出 "Base class print"
// 2. 使用基类指针指向派生类对象
derivedPtr->print(); // 输出 "Derived class print" (多态)
// 3. 使用派生类指针指向派生类对象
derivedPtr2->print(); // 输出 "Derived class print"
delete basePtr;
delete derivedPtr;
delete derivedPtr2;
return 0;
}
2. Pure Virtual Function(纯虚函数)
定义:Pure Virtual Function 是一种特殊的 Virtual Function,在基类中声明时被赋值为 0。包含 Pure Virtual Function 的类被称为 Abstract Class(抽象类)。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Shape {
public:
virtual double getArea() = 0; // 纯虚函数
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double getArea() override {
return 3.14159 * radius * radius;
}
};
int main() {
// Shape shape; // 错误:不能实例化抽象类
Circle circle(5.0);
std::cout << "Area of circle: " << circle.getArea() << std::endl; // 输出圆的面积
return 0;
}
3. Why Constructor Cannot Be Virtual Function(为什么构造函数不能是虚函数)
调用基类构造函数的方法:
主要有两种方式:隐式调用和显式调用。
1. 隐式调用(Implicit Call):
条件: 基类有 无参构造函数(默认构造函数)。
行为: 如果派生类构造函数没有在初始化列表中显式调用基类构造函数,编译器会自动在派生类构造函数体执行之前调用基类的 无参构造函数。
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Base {
public:
Base() {
data = 0; // 默认初始化
cout << "Base constructor called" << endl;
}
private:
int data;
};
class Derived : public Base {
public:
Derived() { // 没有显式调用 Base 构造函数
extraData = 10;
cout << "Derived constructor called" << endl;
}
private:
int extraData;
};
int main() {
Derived d; // 先输出 "Base constructor called",再输出 "Derived constructor called"
}
缺点: 如果基类 没有无参构造函数,则会发生编译错误。
2. 显式调用(Explicit Call):
方法: 在派生类构造函数的初始化列表中调用基类构造函数。
语法:
1
2
3
Derived(参数列表) : Base(基类构造函数参数), 派生类成员初始化 {
// 派生类构造函数体
}
代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Base {
public:
Base(int value) : data(value) { // 有参构造函数
cout << "Base constructor called with value: " << value << endl;
}
private:
int data;
};
class Derived : public Base {
public:
Derived(int value, int extra) : Base(value), extraData(extra) { // 显式调用 Base(value)
cout << "Derived constructor called with extra: " << extra << endl;
}
private:
int extraData;
};
int main() {
Derived d(5, 10); // 输出 "Base constructor called with value: 5",再输出 "Derived constructor called with extra: 10"
}
优点:
注意事项:
初始化列表的顺序: 初始化列表的执行顺序与成员变量声明的顺序有关,而不是与初始化列表的顺序有关。基类的初始化总是先于派生类的初始化。
构造函数调用链: 如果存在多层继承(例如,A -> B -> C),则构造函数会形成一个调用链,从最顶层的基类开始,逐级向下初始化。
总结表:
| Inheritance Type | Base Class public
|
Base Class protected
|
Base Class private
|
Outside the Derived Class |
|---|---|---|---|---|
public |
public |
protected |
Inaccessible | Accessible |
protected |
protected |
protected |
Inaccessible | Inaccessible |
private |
private |
private |
Inaccessible | Inaccessible |
重要提示:
private 成员始终无法在派生类中直接访问。private 继承 (对于 class 关键字定义的类) 或 public 继承 (对于 struct 关键字定义的结构体)。1. Public Inheritance (公有继承):
class Derived : public Base { ... };
public 成员在派生类中仍然是 public。protected 成员在派生类中仍然是 protected。private 成员在派生类中不可访问 (在派生类内部,包括成员函数,都无法直接访问基类的 private 成员)。private 成员)。 派生类的对象可以访问从基类继承的 public 成员。例子:
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
class Base {
public:
int publicVar = 1;
protected:
int protectedVar = 2;
private:
int privateVar = 3;
};
class Derived : public Base {
public:
void accessBaseMembers() {
cout << "Public: " << publicVar << endl; // OK
cout << "Protected: " << protectedVar << endl; // OK
// cout << "Private: " << privateVar << endl; // Error: cannot access private member
}
};
int main() {
Derived d;
cout << "Public from object: " << d.publicVar << endl; // OK
// cout << "Protected from object: " << d.protectedVar << endl; // Error: protected member
// cout << "Private from object: " << d.privateVar << endl; // Error: private member
return 0;
}
2. Protected Inheritance (保护继承):
class Derived : protected Base { ... };
public 成员在派生类中变为 protected。protected 成员在派生类中仍然是 protected。private 成员在派生类中不可访问。public 成员在派生类中被降级为 protected。 派生类的对象不能直接访问从基类继承的成员(即使是 public 成员)。 protected 继承通常用于实现更严格的封装,隐藏基类的接口。例子:
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
class Base {
public:
int publicVar = 1;
protected:
int protectedVar = 2;
private:
int privateVar = 3;
};
class Derived : protected Base {
public:
void accessBaseMembers() {
cout << "Public: " << publicVar << endl; // OK (在派生类内部可以访问)
cout << "Protected: " << protectedVar << endl; // OK
// cout << "Private: " << privateVar << endl; // Error: cannot access private member
}
};
int main() {
Derived d;
// cout << "Public from object: " << d.publicVar << endl; // Error: publicVar is protected
// cout << "Protected from object: " << d.protectedVar << endl; // Error: protected member
// cout << "Private from object: " << d.privateVar << endl; // Error: private member
return 0;
}
3. Private Inheritance (私有继承):
class Derived : private Base { ... };
public 成员在派生类中变为 private。protected 成员在派生类中变为 private。private 成员在派生类中不可访问。private 成员)在派生类中都变为 private。 派生类的对象不能直接访问从基类继承的成员。 派生类可以访问基类的 public 和 protected 成员,但是这种访问仅限于派生类的内部。 私有继承通常用于实现组合 (composition) 关系,而不是 “is-a” 关系。 派生类仅仅是利用基类的实现,而不想暴露基类的接口。例子:
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
class Base {
public:
int publicVar = 1;
protected:
int protectedVar = 2;
private:
int privateVar = 3;
};
class Derived : private Base {
public:
void accessBaseMembers() {
cout << "Public: " << publicVar << endl; // OK (在派生类内部可以访问,但现在是 private)
cout << "Protected: " << protectedVar << endl; // OK (在派生类内部可以访问,但现在是 private)
// cout << "Private: " << privateVar << endl; // Error: cannot access private member
}
};
int main() {
Derived d;
// cout << "Public from object: " << d.publicVar << endl; // Error: publicVar is private
// cout << "Protected from object: " << d.protectedVar << endl; // Error: protected member
// cout << "Private from object: " << d.privateVar << endl; // Error: private member
return 0;
}
运算符重载允许你为自定义类型重新定义标准运算符。 这使得自定义类型的使用更加直观,像内置类型一样。 本质上,运算符重载就是一种多态性,因为同一个运算符可以对不同类型的数据进行操作。
Syntax
运算符重载本质上就是定义一个名为 operator<operator> 的成员函数或友元函数。 <operator> 代表你要重载的运算符符号。
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
#include <iostream>
class Complex {
public:
double real;
double imag;
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
Complex operator-(const Complex& other) const {
return Complex(real - other.real, imag - other.imag);
}
void print() const {
std::cout << real << " + " << imag << "i\n";
}
};
int main() {
Complex c1(2, 3);
Complex c2(4, -1);
Complex c3 = c1 + c2; // 使用 + 运算符
Complex c4 = c1 - c2; // 使用 - 运算符
c3.print(); // Output: 6 + 2i
c4.print(); // Output: -2 + 4i
return 0;
}
这里,我们重载了 + 和 - 运算符,使它们能够对 Complex 对象进行加减运算。 operator+ 和 operator- 函数的实现定义了运算符的行为。
总结:
C++ 的多态性使得代码更具可重用性、可扩展性和可维护性。通过继承和虚函数,你可以用统一的接口操作不同类型的对象,从而简化代码并降低耦合度。 运算符重载是多态性的一个特例,它使自定义类型更加易于使用,并提高了代码的可读性。 记住,虚函数是实现运行时多态性的关键,而运算符重载则增强了自定义类型的表达能力。 需要谨慎设计,避免重载运算符导致代码难以理解。