c++ day3

发布时间 2023-07-07 21:45:25作者: 芜湖大厨师

今天的复习感觉会挺轻松。

#ifndef 是 C++ 中的一个预处理指令,用于条件编译。它通常与 #define#endif 一起使用,用于包含或排除某个代码块,以防止重复包含头文件。

当使用 #include 指令将头文件包含到源文件中时,存在一种潜在的问题,即多个源文件可能都包含了同一个头文件。这种情况下,如果没有适当的预防措施,可能会导致重复定义的错误。

为了解决这个问题,C++ 提供了条件编译指令 #ifndef(也称为条件预处理指令)。它允许我们根据一个标识符的定义状态来决定是否编译某段代码。常见的用法是在头文件中使用 #ifndef 配合 #define#endif

下面是详细的解释和用法示例:

  1. #ifndef:这是条件编译指令的开始部分。它接受一个标识符作为参数,通常是一个宏定义的名称。在预处理阶段,如果该标识符未定义,条件就成立,代码块将会被编译。

  2. #define:这是条件编译指令的一部分。它用于定义标识符,以表示某个代码块正在被编译。在通常的用法中,我们使用与 #ifndef 相同的标识符作为参数。这样,当头文件第一次被包含时,该标识符将被定义。

  3. #endif:这是条件编译指令的结束部分。它标志着条件编译代码块的结束。

通过将这三个指令结合使用,可以确保头文件只被编译一次,即使它在多个源文件中被包含。

下面是一个示例:

假设有一个名为 header.h 的头文件,其中包含一些函数的声明和定义:

1 // header.h
2 
3 #ifndef HEADER_H
4 #define HEADER_H
5 
6 void foo();
7 void bar();
8 
9 #endif

在上述代码中,HEADER_H 是我们自定义的标识符。如果在包含 header.h 之前该标识符未定义,那么就会执行 #ifndef#endif 之间的代码。

现在,假设有两个源文件 source1.cppsource2.cpp 都需要使用 header.h 中的函数。

1 // source1.cpp
2 
3 #include "header.h"
4 
5 int main() {
6     foo();
7     return 0;
8 }
1 // source2.cpp
2 
3 #include "header.h"
4 
5 void bar() {
6     // 函数实现
7 }

当编译 source1.cppsource2.cpp 时,它们都会包含 header.h。由于 header.h 中的 #ifndef#define 保护,头文件只会被编译一次。这样,foobar 的定义只会出现一次,并且不会导致重复定义的错误。

总结一下:

  • #ifndef 用于检查一个标识符是否未定义。
  • #define 用于定义标识符,表示代码块正在被编译。
  • #endif 标志着条件编译代码块的结束。

通过合理使用 #ifndef,可以避免头文件的重复包含和重复定义的问题,确保代码的正确编译和执行。

异常类

异常类在 C++ 中用于处理异常情况。它们是从 std::exception 类派生而来的,可以通过自定义异常类来表示特定的异常类型。下面是异常类的定义和使用方式:

 1 #include <exception>
 2 
 3 class MyException : public std::exception {
 4 public:
 5     // 构造函数,通常接受一个字符串作为异常信息
 6     MyException(const char* message) : m_message(message) {}
 7 
 8     // 重写基类的 what() 函数,返回异常信息
 9     virtual const char* what() const noexcept {
10         return m_message;
11     }
12 
13 private:
14     const char* m_message;
15 };

在上述代码中,我们定义了一个名为 MyException 的自定义异常类,它继承自 std::exception。异常类通常具有一个构造函数,用于接收异常信息,并将其存储在成员变量中。我们还重写了 what() 函数,该函数是基类 std::exception 的成员函数,用于返回异常信息。

使用自定义异常类时,可以通过抛出异常的方式来表示异常情况,并在异常处理代码中捕获并处理它们。下面是一个示例:

 1 #include <iostream>
 2 
 3 void foo() {
 4     throw MyException("Something went wrong!");
 5 }
 6 
 7 int main() {
 8     try {
 9         foo();
10     } catch (const MyException& e) {
11         std::cout << "Exception caught: " << e.what() << std::endl;
12     }
13 
14     return 0;
15 }

在上述代码中,foo() 函数抛出了一个 MyException 异常对象,携带了异常信息。在 main() 函数中,我们使用 try-catch 块来捕获并处理异常。当异常被抛出时,程序会跳转到 catch 块中,并执行相应的异常处理代码。

在上面的示例中,我们捕获了 MyException 类型的异常,并通过调用 e.what() 来获取异常信息并输出。

自定义异常类使得我们能够更好地组织和处理不同类型的异常,以便在程序执行过程中捕获和处理特定的异常情况。

 

终于学习到递归函数了

首先来理解下什么是递归,大家都听说一个故事吧。

从前有座山,山里有座庙,庙里有个老和尚和小和尚,老和尚对小和尚说,从前有座山,山里有座庙,庙里有个老和尚和小和尚,老和尚对小和尚说,从前有座山......

这就是一种递归

在递归函数中,可以存在两种类型的递归:直接递归和间接递归。

  1. 直接递归(Direct Recursion):直接递归是指递归函数在自身的定义体内直接调用自身。换句话说,函数直接调用自身作为一部分。这种递归形式是最常见的。

下面是一个示例,展示了直接递归的情况:

1 void directRecursion() {
2     // 其他代码...
3 
4     // 直接调用自身
5     directRecursion();
6 
7     // 其他代码...
8 }

在上述代码中,directRecursion() 函数在其定义体内直接调用了自身。

  1. 间接递归(Indirect Recursion):间接递归是指递归函数通过一系列函数调用,最终又回到自身。换句话说,多个函数之间形成了一个环状调用,最终导致递归。

下面是一个示例,展示了间接递归的情况:

 1 void functionB(); // 函数的声明
 2 
 3 void functionA() {
 4     // 其他代码...
 5 
 6     // 调用函数B
 7     functionB();
 8 
 9     // 其他代码...
10 }
11 
12 void functionB() {
13     // 其他代码...
14 
15     // 调用函数A
16     functionA();
17 
18     // 其他代码...
19 }

在上述代码中,functionA()functionB() 两个函数通过相互调用形成了一个间接递归。

无论是直接递归还是间接递归,递归函数都需要正确设置终止条件,以确保递归能够在某个条件下结束,避免无限递归。在递归过程中,终止条件的判断非常重要,以确保递归能够正确结束。

递归的选择取决于问题的性质和逻辑结构。有时候,直接递归更直观和自然,而在其他情况下,间接递归可能更适合解决特定的问题。

递归的数学函数

归在数学函数中有广泛的应用,以下是一些常见的递归数学函数的示例:

  1. 阶乘(Factorial):阶乘是一个经典的递归函数示例。n 的阶乘(表示为 n!)定义为从 1 到 n 的所有正整数的乘积。阶乘函数可以使用递归来计算。
1 int factorial(int n) {
2     if (n == 0)
3         return 1;
4     return n * factorial(n - 1);
5 }
  1. 斐波那契数列(Fibonacci Sequence):斐波那契数列是一个递归定义的数列,其中每个数字都是前两个数字的和。数列的前两个数字通常定义为 0 和 1。
1 int fibonacci(int n) {
2     if (n <= 1)
3         return n;
4     return fibonacci(n - 1) + fibonacci(n - 2);
5 }
  1. 幂函数(Power Function):幂函数用于计算一个数的指定次幂。可以使用递归来实现幂函数。
1 double power(double base, int exponent) {
2     if (exponent == 0)
3         return 1;
4     if (exponent > 0)
5         return base * power(base, exponent - 1);
6     else
7         return 1 / (base * power(base, -exponent - 1));
8 }

 

  1. 最大公约数(Greatest Common Divisor):最大公约数是两个或多个整数的最大公约数。可以使用递归的欧几里得算法来计算最大公约数。
1 int gcd(int a, int b) {
2     if (b == 0)
3         return a;
4     return gcd(b, a % b);
5 }

 

这些是一些常见的数学函数,它们使用递归来解决具有递归性质的问题。在实际使用中,需要根据具体问题选择适当的递归方法,并确保正确设置递归的终止条件,以避免无限递归和其他潜在的问题。

 说到递归就要谈到归纳了

递归的归纳证明是数学和计算机科学中常用的证明技巧之一。它用于证明递归定义的数学对象或递归算法的性质。

下面是递归的归纳证明的基本步骤:

  1. 基本情况(Base Case):首先证明递归的基本情况。即证明当满足最小的输入条件时,性质成立。通常这是一个简单且显而易见的情况。

  2. 归纳假设(Inductive Hypothesis):假设递归定义或算法对于某个规模较小的输入是正确的。这就是归纳假设的核心,它假设了性质在规模较小的情况下成立。

  3. 归纳步骤(Inductive Step):证明在给定归纳假设下,当输入规模增加时,性质仍然成立。这一步骤通常包括两个关键要素:首先,需要展示对于某个规模的输入,性质成立;其次,需要展示在此基础上,性质也成立于规模更大的输入。

  4. 归纳结论(Inductive Conclusion):通过基本情况、归纳假设和归纳步骤,得出结论:根据递归定义或算法的性质,性质对于所有可能的输入都成立。

这个证明过程与一般的归纳证明相似,但由于递归的特殊性质,需要特别关注基本情况、归纳假设和归纳步骤的设计。递归的归纳证明能够展示出递归定义或算法的正确性,并加深对递归过程的理解。

需要注意的是,递归的归纳证明并不总是适用于所有的递归定义或算法。有时,其他的证明技巧可能更合适。此外,在进行递归的归纳证明时,正确定义和选择递归的终止条件也非常重要。

现在你已经知道递归了 让我们来处理一个普通的数学问题吧。
我们知道n个元素的排列个数是n!
如果给我们了一个数组 怎么样输出所有的排列呢?递归就上场了。
以下是一个使用递归实现的排列算法的示例。该算法接受一个数组和数组长度作为输入,并生成数组元素的所有排列。
 1 #include <iostream>
 2 #include <vector>
 3 
 4 // 交换数组中两个位置的元素
 5 void swap(int& a, int& b) {
 6     int temp = a;
 7     a = b;
 8     b = temp;
 9 }
10 
11 // 递归生成数组的排列
12 void generatePermutations(std::vector<int>& arr, int start, int end) {
13     if (start == end) {
14         // 输出排列结果
15         for (int i = 0; i <= end; i++) {
16             std::cout << arr[i] << " ";
17         }
18         std::cout << std::endl;
19     } else {
20         // 递归生成排列
21         for (int i = start; i <= end; i++) {
22             swap(arr[start], arr[i]);  // 交换元素
23             generatePermutations(arr, start + 1, end);  // 递归生成子排列
24             swap(arr[start], arr[i]);  // 恢复元素的原始顺序
25         }
26     }
27 }
28 
29 int main() {
30     std::vector<int> arr = {1, 2, 3};  // 输入数组
31     int n = arr.size();
32 
33     generatePermutations(arr, 0, n - 1);
34 
35     return 0;
36 }

在上述代码中,generatePermutations 函数是一个递归函数,用于生成数组的排列。它采用数组、起始索引和结束索引作为参数。当起始索引等于结束索引时,表示已经生成了一个完整的排列,将其输出。否则,它遍历从起始索引到结束索引的范围,每次选择一个元素作为当前位置,并递归生成子排列。

main 函数中,我们定义了一个包含元素 {1, 2, 3} 的数组,并调用 generatePermutations 函数来生成该数组的所有排列。

运行程序,将输出以下结果:

1 1 2 3 
2 1 3 2 
3 2 1 3 
4 2 3 1 
5 3 2 1 
6 3 1 2 

方法还有很多

如迭代的方法,以下是一个使用迭代的排列算法示例:

 1 #include <iostream>
 2 #include <vector>
 3 #include <algorithm>
 4 
 5 void generatePermutations(std::vector<int>& arr) {
 6     int n = arr.size();
 7 
 8     // 用于存储当前排列的索引数组
 9     std::vector<int> indices(n);
10     for (int i = 0; i < n; i++) {
11         indices[i] = i;
12     }
13 
14     while (true) {
15         // 输出当前排列
16         for (int i = 0; i < n; i++) {
17             std::cout << arr[indices[i]] << " ";
18         }
19         std::cout << std::endl;
20 
21         // 查找下一个排列
22         int i = n - 2;
23         while (i >= 0 && indices[i] > indices[i + 1]) {
24             i--;
25         }
26         if (i < 0) {
27             break;  // 已经是最后一个排列,退出循环
28         }
29 
30         int j = n - 1;
31         while (indices[j] < indices[i]) {
32             j--;
33         }
34 
35         // 交换元素
36         std::swap(indices[i], indices[j]);
37 
38         // 反转后续元素
39         std::reverse(indices.begin() + i + 1, indices.end());
40     }
41 }
42 
43 int main() {
44     std::vector<int> arr = {1, 2, 3};  // 输入数组
45 
46     generatePermutations(arr);
47 
48     return 0;
49 }

更简便的还有字典序算法生成排列的示例。这种方法通过按照字典序生成排列,而不是通过递归或迭代的方式。

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <vector>
 4 
 5 void generatePermutations(std::vector<int>& arr) {
 6     std::sort(arr.begin(), arr.end());  // 将数组按升序排列
 7 
 8     do {
 9         // 输出当前排列
10         for (int num : arr) {
11             std::cout << num << " ";
12         }
13         std::cout << std::endl;
14     } while (std::next_permutation(arr.begin(), arr.end()));  // 生成下一个排列,直到达到最后一个排列
15 }
16 
17 int main() {
18     std::vector<int> arr = {1, 2, 3};  // 输入数组
19 
20     generatePermutations(arr);
21 
22     return 0;
23 }

包括可以使用标准库中的 std::next_permutation 函数来生成数组的所有排列

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <vector>
 4 
 5 void generatePermutations(std::vector<int>& arr) {
 6     std::sort(arr.begin(), arr.end());  // 将数组按升序排列
 7 
 8     do {
 9         // 输出当前排列
10         for (int num : arr) {
11             std::cout << num << " ";
12         }
13         std::cout << std::endl;
14     } while (std::next_permutation(arr.begin(), arr.end()));
15 }
16 
17 int main() {
18     std::vector<int> arr = {1, 2, 3};  // 输入数组
19 
20     generatePermutations(arr);
21 
22     return 0;
23 }

方法还有许多许多

今天就到这里吧 润!