程设一些知识点

程设一些知识点

知识点

本文档主要整理了我在复习中遇到比较不熟悉的几个知识点,包括运算符重载、虚函数、模板以及智能指针的使用。

1. 运算符重载

运算符重载允许我们为自定义类型(如类)重新定义或“重载”已有的运算符行为。

1.1. 输入/输出运算符重载 (以 complex 类为例)

在 C++ 中,<<>> 运算符通常分别用于输出和输入。我们可以为自定义类重载这些运算符,以便能像内置类型一样方便地进行输入输出操作。

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
#include <iostream> // 需要包含 iostream 以使用 cin 和 cout

// 为了代码片段能独立编译,我们添加 using namespace std;
// 在大型项目中,更推荐显式使用 std::cin, std::cout 等
using namespace std;

class complex {
public:
double r, i; // 实部和虚部

// 构造函数 (为了示例完整性,添加一个默认构造函数和带参构造函数)
complex(double r_val = 0.0, double i_val = 0.0) : r(r_val), i(i_val) {}

// 拷贝构造函数 (用户提供的版本)
// 注意:通常如果类中没有动态分配的资源,编译器生成的默认拷贝构造函数就足够了。
// 这里的实现是空的,仅为展示。在实际应用中,如果需要自定义,则应正确拷贝成员。
complex(const complex &c) {
r = c.r;
i = c.i;
// cout << "拷贝构造函数被调用" << endl; // 用于观察
}

// 友元函数重载 >> (输入运算符)
// istream& 表示输入流对象的引用,使其可以链式操作 (cin >> a >> b)
// complex& com 表示要读取数据的 complex 对象的引用
friend istream &operator>>(istream &in, complex &com) {
// 提示用户输入
// cout << "请输入复数的实部和虚部 (以空格分隔): "; // 实际应用中可加提示
in >> com.r >> com.i; // 从输入流中读取实部和虚部
return in; // 返回输入流对象,支持链式输入
}

// 友元函数重载 << (输出运算符)
// ostream& 表示输出流对象的引用
// const complex& com 表示要输出的 complex 对象的常量引用 (因为输出操作不应修改对象)
friend ostream &operator<<(ostream &out, const complex &com) {
out << com.r << " + " << com.i << "i"; // 按 "实部 + 虚部i" 格式输出
// 注意:原代码是 cout << com.r << ' ' << com.i; 为了更像复数,这里做了修改
return out; // 返回输出流对象,支持链式输出
}
};

// 示例用法:
// int main() {
// complex c1;
// cout << "请输入一个复数 (实部 虚部): ";
// cin >> c1; // 调用 operator>>(cin, c1)
// cout << "输入的复数是: " << c1 << endl; // 调用 operator<<(cout, c1)
//
// complex c2 = c1; // 调用拷贝构造函数
// cout << "拷贝后的复数是: " << c2 << endl;
// return 0;
// }

解释:

  • friend 关键字:允许非成员函数访问类的 privateprotected 成员。对于 <<>> 重载,通常将它们声明为友元函数,因为它们的左操作数是流对象(istreamostream),而不是类对象。
  • istream &operator>>(istream &in, complex &com)
    • 参数 istream &in: 输入流的引用 (例如 cin)。
    • 参数 complex &com: 要被赋值的 complex 对象的引用。
    • 返回值 istream &: 返回输入流的引用,以支持链式输入 (如 cin >> c1 >> c2;)。
  • ostream &operator<<(ostream &out, const complex &com)
    • 参数 ostream &out: 输出流的引用 (例如 cout)。
    • 参数 const complex &com: 要输出的 complex 对象的常量引用(输出操作不应修改对象)。
    • 返回值 ostream &: 返回输出流的引用,以支持链式输出 (如 cout << c1 << c2;)。

1.2. 前置与后置自增/自减运算符重载

自增 (++) 和自减 (--) 运算符有前置和后置两种形式。它们的重载方式略有不同。

1.2.1. 成员函数方式

  • 前置运算符 (++obj--obj):

    • 声明: T& operator++();T& operator--();
    • 返回类型通常是被操作对象的引用 (T&)。
    • 实现时,先修改对象的值,然后返回修改后的对象。
  • 后置运算符 (obj++obj--):

    • 声明: T operator++(int);T operator--(int);
    • 参数列表中的 int 是一个哑元(dummy argument),仅用于区分前置和后置版本,调用时不需要传递实参。
    • 返回类型通常是被操作对象的值 (T),而不是引用。这是因为后置操作符需要返回对象在自增/自减前的状态。
    • 实现时,先保存对象的当前状态(创建一个临时副本),然后修改原始对象的值,最后返回保存的临时副本。

1.2.2. 全局 (友元) 函数方式

  • 前置运算符 (++obj--obj):

    • 声明: T1& operator++(T2& obj);T1& operator--(T2& obj); (其中 T1 通常是 T2 或其引用)
    • 第一个参数是被操作对象的引用。
  • 后置运算符 (obj++obj--):

    • 声明: T1 operator++(T2& obj, int);T1 operator--(T2& obj, int); (其中 T1 通常是 T2)
    • 第一个参数是被操作对象的引用,第二个参数是哑元 int

1.2.3. Counter 类示例 (全局函数方式)

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
// 假设 Counter 类的定义
class Counter {
public:
int value;

Counter(int v = 0) : value(v) {}

// 声明友元函数以便它们可以访问 Counter 的私有成员 (如果 value 是 private)
// 如果 value 是 public,则友元声明不是严格必需的,但通常是良好实践,
// 因为运算符重载通常与类的接口紧密相关。
friend Counter& operator++(Counter& c); // 前置++
friend Counter operator++(Counter& c, int); // 后置++

// 为了方便演示,添加一个输出方法
void print() const {
cout << "Counter value: " << value << endl;
}
};

// 全局函数实现前置 ++
Counter& operator++(Counter& c) {
++c.value; // 修改对象的值
return c; // 返回修改后的对象
}

// 全局函数实现后置 ++
Counter operator++(Counter& c, int) {
Counter temp = c; // 保存原始值的副本
++c.value; // 修改原始对象的值 (也可以调用前置版本: ++c;)
return temp; // 返回原始值的副本
}

// 示例用法:
// int main() {
// Counter c1(5);
// cout << "初始 "; c1.print();
//
// Counter c2 = ++c1; // 前置自增
// cout << "++c1 后: " << endl;
// cout << "c1: "; c1.print();
// cout << "c2: "; c2.print();
//
// Counter c3(10);
// cout << "初始 "; c3.print();
// Counter c4 = c3++; // 后置自增
// cout << "c3++ 后: " << endl;
// cout << "c3: "; c3.print();
// cout << "c4: "; c4.print();
//
// return 0;
// }

要点:

  • 后置版本通过一个未命名的 int 参数与前置版本区分。
  • 前置版本通常返回对象的引用 (T&),而后置版本通常按值返回 (T),因为它返回的是操作前的对象状态。

2. 虚函数 (Virtual Functions)

虚函数是实现 C++ 运行时多态性的关键机制。当通过基类指针或引用调用派生类中的虚函数时,会根据指针或引用实际指向的对象类型来决定调用哪个版本的函数。

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
class Base {
public:
// 虚析构函数
// 如果一个类要作为基类,并且可能会通过基类指针删除派生类对象,
// 那么它的析构函数应该声明为 virtual。
// 这样可以确保在删除派生类对象时,先调用派生类的析构函数,然后再调用基类的析构函数。
// = default; 表示使用编译器生成的默认实现。
virtual ~Base() = default;

// 纯虚函数 (Pure Virtual Function)
// 纯虚函数在基类中没有实现,它强制派生类必须提供该函数的具体实现。
// 包含纯虚函数的类是抽象类,不能被实例化。
// const 表示该成员函数不会修改类的成员变量。
// = 0; 表明这是一个纯虚函数。
virtual int function(int x) const = 0;

// 普通虚函数 (示例)
virtual void printType() {
cout << "This is Base class" << endl;
}
};

class Derived : public Base {
public:
// 覆盖 (override) 基类的虚析构函数
// ~Derived() override { cout << "Derived destructor called" << endl; } // C++11 override 关键字
~Derived() { /* cout << "Derived destructor called" << endl; */ } // 也可以不加 override

// 实现基类中的纯虚函数
// C++11 中推荐使用 override 关键字明确表示这是对基类虚函数的覆盖
int function(int x) const override {
return x * x;
}

// 覆盖基类的普通虚函数
void printType() override {
cout << "This is Derived class" << endl;
}
};

// 示例用法:
// int main() {
// // Base b; // 错误!Base 是抽象类,不能实例化
//
// Derived d;
// cout << "d.function(5): " << d.function(5) << endl; // 输出 25
//
// Base* ptr_b = &d; // 基类指针指向派生类对象
// cout << "ptr_b->function(10): " << ptr_b->function(10) << endl; // 输出 100 (调用 Derived::function)
// ptr_b->printType(); // 输出 "This is Derived class" (调用 Derived::printType)
//
// // 如果 Base 的析构函数不是 virtual,通过基类指针 delete 派生类对象可能导致资源泄漏
// // Base* ptr_heap = new Derived();
// // delete ptr_heap; // 正确调用 Derived 和 Base 的析构函数
//
// return 0;
// }

关键点:

  • virtual ~Base() = default;: 虚析构函数。如果类可能被继承,并且可能通过基类指针删除派生类对象,则析构函数应声明为 virtual。这确保了派生类的析构函数会被正确调用,防止资源泄漏。= default 表示使用编译器提供的默认实现。
  • virtual int function(int x) const = 0;: 纯虚函数。
    • virtual: 表明它是一个虚函数。
    • const: 表明该函数不会修改类的成员变量(对于 const 对象或通过 const 引用/指针调用)。
    • = 0: 将其声明为纯虚函数。纯虚函数在声明它的类中没有定义(实现)。
    • 包含一个或多个纯虚函数的类称为抽象类。抽象类不能被实例化(不能创建其对象)。
    • 派生类必须实现(覆盖)基类中的所有纯虚函数,否则派生类也将成为抽象类。

3. 类模板 (Class Templates)

类模板允许我们定义一个通用的类蓝图,其中的数据类型或某些值可以作为参数在实例化时指定。

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
61
62
63
64
65
66
67
68
69
#include <stdexcept> // 为了使用 std::out_of_range

template <typename T, int i> // T 是类型参数,i 是非类型参数 (整数常量)
class TestClass {
public:
T buffer[i]; // T 类型的数组,其大小由非类型参数 i 决定

// 构造函数 (示例)
TestClass() {
// 可以在这里初始化 buffer,例如:
// for(int k = 0; k < i; ++k) {
// buffer[k] = T(); // 使用 T 类型的默认构造函数初始化
// }
}

// 成员函数声明
T getData(int j);

// 另一个成员函数示例
void setData(int j, T value) {
if (j >= 0 && j < i) {
buffer[j] = value;
} else {
// 处理越界,例如抛出异常
throw std::out_of_range("Index out of bounds in setData");
}
}

int getSize() const {
return i;
}
};

// 类模板的成员函数定义在类外时,需要再次声明模板参数列表
template <typename T, int i>
T TestClass<T, i>::getData(int j) {
if (j >= 0 && j < i) {
return *(buffer + j); // 等价于 buffer[j]
} else {
// 处理越界,例如抛出异常或返回默认值
throw std::out_of_range("Index out of bounds in getData");
// 或者 return T(); // 返回 T 类型的默认构造值
}
}

// 示例用法:
// int main() {
// TestClass<int, 10> intTest; // T 为 int, i 为 10
// intTest.setData(0, 100);
// intTest.setData(5, 500);
//
// cout << "intTest.getData(0): " << intTest.getData(0) << endl; // 输出 100
// cout << "intTest.getData(5): " << intTest.getData(5) << endl; // 输出 500
// cout << "Size of intTest buffer: " << intTest.getSize() << endl; // 输出 10
//
// TestClass<double, 5> doubleTest; // T 为 double, i 为 5
// doubleTest.setData(2, 3.14);
// cout << "doubleTest.getData(2): " << doubleTest.getData(2) << endl; // 输出 3.14
//
// // TestClass<int, 0> zeroSizeTest; // 通常 i 必须大于 0,否则 buffer[0] 是非法的
//
// try {
// cout << intTest.getData(10) << endl; // 会抛出异常
// } catch (const std::out_of_range& e) {
// cerr << "Exception: " << e.what() << endl;
// }
//
// return 0;
// }

解释:

  • template <typename T, int i>: 模板声明。
    • typename T: T 是一个类型参数,代表任何数据类型(如 int, double, std::string, 自定义类等)。class 也可以用来代替 typename
    • int i: i 是一个非类型模板参数。它必须是一个编译时常量(如整型、指针、引用,或枚举值)。在这里,它决定了 buffer 数组的大小。
  • T buffer[i];: 类成员,一个类型为 T,大小为 i 的数组。
  • T TestClass<T,i>::getData(int j): 当在类模板外部定义成员函数时,必须重复模板参数列表,并使用 TestClass<T,i> 来限定函数所属的类。

4. 函数模板与智能指针

函数模板允许我们编写一个通用的函数,它可以处理不同类型的数据。智能指针是管理动态分配内存的类,有助于防止内存泄漏。

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
#include <memory> // 为了使用 std::unique_ptr

// 假设 GtLever 函数的声明 (具体实现未知,仅为示例)
// 它接收一个 T 类型的数组,数组长度 n,以及一个 T 类型的 lever 值
template <class T>
void GtLever(const T* arr, int n, T lever) {
cout << "GtLever called with lever: " << lever << " and array: ";
for (int i = 0; i < n; ++i) {
cout << arr[i] << (i == n - 1 ? "" : ", ");
}
cout << endl;
// 实际的 GtLever 逻辑会在这里
}


// 处理输入和调用 GtLever 的函数模板
template <class T> // T 是类型参数
void processInput(int length, int n_for_gtlever) { // 修改了第二个参数名以示区分
// 使用智能指针 std::unique_ptr 管理动态分配的数组内存
// std::unique_ptr<T[]> arr(new T[length]);
// C++14 及以后版本推荐使用 std::make_unique
std::unique_ptr<T[]> arr = std::make_unique<T[]>(length);

cout << "Enter " << length << " elements of type " << typeid(T).name() << ":" << endl;
for (int i = 0; i < length; i++) {
cin >> arr[i];
}

T lever;
cout << "Enter the lever value of type " << typeid(T).name() << ":" << endl;
cin >> lever;

// 调用 GtLever 函数
// arr.get() 返回指向 unique_ptr所管理数组的原始指针
GtLever(arr.get(), n_for_gtlever, lever); // 假设 GtLever 需要的长度是 n_for_gtlever
// 如果 GtLever 处理整个 arr,则应传递 length
}

// 示例用法 (构造方法调用):
// int main() {
// int length, n;
//
// cout << "Processing char input:" << endl;
// cout << "Enter array length: ";
// cin >> length;
// cout << "Enter n for GtLever: ";
// cin >> n;
// processInput<char>(length, n); // 实例化函数模板,T 为 char
//
// cout << "\nProcessing int input:" << endl;
// cout << "Enter array length: ";
// cin >> length;
// cout << "Enter n for GtLever: ";
// cin >> n;
// processInput<int>(length, n); // 实例化函数模板,T 为 int
//
// return 0;
// }

解释:

  • template <class T>: processInput 是一个函数模板,T 是其类型参数。
  • std::unique_ptr<T[]> arr(new T[length]); (或 std::make_unique<T[]>(length);):
    • std::unique_ptr 是一种智能指针,它拥有其指向的对象。当 unique_ptr 对象本身被销毁时(例如离开作用域),它会自动释放所管理的内存。这有助于防止内存泄漏。
    • T[] 表示 unique_ptr 管理的是一个 T 类型的数组。
    • new T[length] 动态分配一个包含 lengthT 类型元素的数组。
    • std::make_unique<T[]>(length) (C++14+) 是创建 unique_ptr 的更安全、更简洁的方式。
  • arr.get(): unique_ptrget() 成员函数返回一个指向其所管理内存的原始指针。这在需要将管理的内存传递给不接受 unique_ptr 的旧式 C API 或函数时很有用。
  • processInput<char>(length, n);: 这是函数模板的显式实例化。编译器会根据指定的类型参数 char 生成一个特定版本的 processInput 函数。

5. 构造函数中使用智能指针初始化成员

在类的构造函数中初始化 std::unique_ptr 成员是常见的做法,以确保资源在对象创建时被正确获取,并在对象销毁时自动释放。

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
// 假设 T 是一个已定义的类型或模板参数
// template <typename T> // 如果 C1 本身是模板类
class C1 {
public:
int num;
std::unique_ptr<int[]> a; // 使用 int[] 作为示例,原代码是 T[]

// 构造函数
// 使用成员初始化列表来初始化 num 和智能指针 a
// std::make_unique<T[]>(n) (C++14+) 是推荐的初始化方式
C1(int n) : num(n), a(std::make_unique<int[]>(n)) {
cout << "C1 constructor called. num = " << num << ". Array of size " << n << " allocated." << endl;
// 可以在这里初始化数组 a 的内容
for (int i = 0; i < n; ++i) {
a[i] = i * 10; // 示例初始化
}
}

// 析构函数 (由 unique_ptr 自动管理内存,通常不需要显式 delete)
~C1() {
cout << "C1 destructor called. Memory for array will be automatically deallocated by unique_ptr." << endl;
}

void printArray() const {
if (num > 0 && a) {
cout << "Array contents: ";
for (int i = 0; i < num; ++i) {
cout << a[i] << (i == num - 1 ? "" : ", ");
}
cout << endl;
} else {
cout << "Array is empty or not allocated." << endl;
}
}
};

// 示例用法:
// int main() {
// C1 obj1(5);
// obj1.printArray();
//
// // 当 obj1 离开作用域时,其析构函数被调用,
// // unique_ptr a 会自动释放其管理的动态数组内存。
//
// return 0;
// }

解释:

  • std::unique_ptr<T[]> a;: 类 C1 有一个名为 a 的成员,它是一个指向 T 类型数组的 unique_ptr
  • C1(int n) : num(n), a(std::make_unique<T[]>(n)) {}:
    • 这是构造函数。
    • : num(n), a(std::make_unique<T[]>(n)) 是成员初始化列表。这是初始化类成员(尤其是 const 成员、引用成员和需要构造函数参数的成员对象)的首选方式。
    • a(std::make_unique<T[]>(n)) 初始化 unique_ptr 成员 a,使其管理一个新分配的大小为 nT 类型数组。
  • C1 类型的对象被销毁时,其成员 a (即 unique_ptr) 也会被销毁。unique_ptr 的析构函数会自动 delete[] 它所管理的数组,从而防止内存泄漏。

程设一些知识点
https://kyc001.github.io/2025/05/24/程设一些知识点/
作者
kyc
发布于
2025年5月24日
许可协议