编程之路:C++旅程回顾
1. 函数与参数
1.1 传值参数
传值参数是将参数的值复制给形式参数。函数内对形式参数的修改不会影响实际参数。
代码实例:
#include <iostream>
void square(int num) {
num = num * num;
std::cout << "Square inside function: " << num << std::endl;
}
int main() {
int num = 5;
std::cout << "Before function call: " << num << std::endl;
square(num);
std::cout << "After function call: " << num << std::endl;
return 0;
}
代码解析:
在这个例子中,我们定义了一个函数square
,该函数的参数类型为int
。在函数内部,我们将形式参数num
的值平方,并输出结果。在主函数main
中,我们定义了一个变量num
,并将其赋值为5。然后,我们调用了square
函数,并输出了调用前后的num
值。
代码运行结果:
Before function call: 5
Square inside function: 25
After function call: 5
从运行结果可以看出,尽管在函数内部将参数的值修改为25,但在函数调用之后,主函数中的变量num
的值仍然是5。这是因为传值参数只是将参数的值复制给了形式参数,并没有影响实际参数本身。
1.2 模板函数
模板函数是支持不同类型参数的通用函数。通过使用模板参数,可以在不修改代码的情况下重用函数代码。可以使用关键字template
来定义模板函数。
代码实例:
#include <iostream>
template<typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
int main() {
int num1 = 5, num2 = 10;
std::cout << "Maximum of " << num1 << " and " << num2 << " is: " << maximum(num1, num2) << std::endl;
double num3 = 3.14, num4 = 2.71;
std::cout << "Maximum of " << num3 << " and " << num4 << " is: " << maximum(num3, num4) << std::endl;
return 0;
}
代码解析:
在这个例子中,我们定义了一个模板函数maximum
。该函数接受两个参数,并返回其中较大的一个。在main
函数中,我们测试了该函数对不同类型的参数的应用。首先,我们传递了两个整数参数,并输出了它们的最大值。然后,我们传递了两个浮点数参数,并同样输出它们的最大值。
代码运行结果:
Maximum of 5 and 10 is: 10
Maximum of 3.14 and 2.71 is: 3.14
从运行结果可以看出,模板函数maximum
可以适用于不同类型的参数,并返回正确的结果。
1.3 引用参数
引用参数是将实际参数的引用传递给形式参数。函数内对形式参数的修改会影响实际参数的值。
代码实例:
#include <iostream>
void increment(int& num) {
num++;
std::cout << "Increment inside function: " << num << std::endl;
}
int main() {
int num = 5;
std::cout << "Before function call: " << num << std::endl;
increment(num);
std::cout << "After function call: " << num << std::endl;
return 0;
}
代码解析:
在这个例子中,我们定义了一个函数increment
,该函数接受一个引用参数num
。在函数内部,我们将形式参数num
的值加1,并输出结果。在主函数main
中,我们定义了一个变量num
,并将其赋值为5。然后,我们调用了increment
函数,并输出了调用前后的num
值。
代码运行结果:
Before function call: 5
Increment inside function: 6
After function call: 6
从运行结果可以看出,通过引用参数传递的实际参数num
的值在函数调用之后发生了改变,因为函数内部对形式参数的修改直接影响了实际参数本身。
1.4 常量引用参数
常量引用参数是指不能被修改的引用参数。常量引用参数适用于实参不需要被修改的情况。
代码实例:
#include <iostream>
void printValue(const int& num) {
std::cout << "Value: " << num << std::endl;
}
int main() {
int num = 5;
std::cout << "Value before function call: " << num << std::endl;
printValue(num);
std::cout << "Value after function call: " << num << std::endl;
return 0;
}
代码解析:
在这个例子中,我们定义了一个函数printValue
,该函数接受一个常量引用参数num
。在函数内部,我们只是输出了形式参数num
的值,并没有进行修改。在主函数main
中,我们定义了一个变量num
,并将其赋值为5。然后,我们调用了printValue
函数,并输出了调用前后的num
值。
代码运行结果:
Value before function call: 5
Value: 5
Value after function call: 5
从运行结果可以看出,虽然函数printValue
接受的是一个引用参数,但函数内部并没有对该参数进行修改,因此实际参数num
的值没有发生改变。
1.5 返回值
函数可以返回一个值,该值可以是任何类型。可以使用return
语句来返回函数的结果。返回值的类型必须与函数的声明相匹配。
代码实例:
#include <iostream>
int sum(int a, int b) {
return a + b;
}
int main() {
int num1 = 5, num2 = 10;
int result = sum(num1, num2);
std::cout << "Sum: " << result << std::endl;
return 0;
}
代码解析:
在这个例子中,我们定义了一个函数sum
,该函数接受两个整数参数,并返回它们的和。在主函数main
中,我们调用了sum
函数,并将返回值赋给了一个变量result
。然后,我们输出了result
的值。
代码运行结果:
Sum: 15
从运行结果可以看出,函数sum
返回的结果被正确地赋值给了变量result
,并且输出了正确的和值。
1.6 重载函数
重载是指在同一作用域内定义多个名称相同但参数类型或参数数量不同的函数。重载函数可以根据参数的不同类型或数量选择适当的函数进行调用。函数的重载可以提高代码的可读性和可维护性。
代码实例:
#include <iostream>
int maximum(int a, int b) {
return (a > b) ? a : b;
}
double maximum(double a, double b) {
return (a > b) ? a : b;
}
int main() {
int num1 = 5, num2 = 10;
std::cout << "Maximum of " << num1 << " and " << num2 << " is: " << maximum(num1, num2) << std::endl;
double num3 = 3.14, num4 = 2.71;
std::cout << "Maximum of " << num3 << " and " << num4 << " is: " << maximum(num3, num4) << std::endl;
return 0;
}
代码解析:
在这个例子中,我们定义了两个重载函数maximum
,一个接受两个整数参数,另一个接受两个浮点数参数。这两个函数分别返回较大的数。在main
函数中,我们测试了这两个重载函数。首先,我们传递了两个整数参数,并输出了它们的最大值。然后,我们传递了两个浮点数参数,并输出了它们的最大值。
代码运行结果:
Maximum of 5 and 10 is: 10
Maximum of 3.14 and 2.71 is: 3.14
从运行结果可以看出,根据不同的参数类型,程序选择了正确的重载函数,并返回了正确的最大值。
2. 异常
2.1 抛出异常
异常是指在程序运行过程中发生的错误或意外情况,它会中断当前的执行流程,并向上层调用栈抛出异常对象。在C++中,我们可以使用throw
关键字来抛出异常。
下面是一个抛出异常的示例代码:
#include <iostream>
int divide(int a, int b) {
if (b == 0) {
throw "Division by zero.";
}
return a / b;
}
int main() {
int a = 10;
int b = 0;
try {
int result = divide(a, b);
std::cout << "Result: " << result << std::endl;
} catch (const char* error) {
std::cout << "Caught exception: " << error << std::endl;
}
return 0;
}
代码解析:
divide
函数用于执行除法运算,如果除数b
为0,会抛出一个const char*
类型的异常对象,表示除以0错误。- 在
main
函数中,我们使用try-catch
块来捕获异常。如果在try
块中的代码抛出了异常,那么控制流程会跳转到catch
块中执行异常处理逻辑。 - 在
catch
块中,我们使用const char*
类型的变量error
来接收捕获的异常对象,并将异常信息打印出来。
代码运行结果:
Caught exception: Division by zero.
2.2 处理异常
除了使用try-catch
块来捕获异常外,我们还可以使用更加具体的异常处理方式。
下面是一个处理异常的示例代码:
#include <iostream>
#include <stdexcept>
int divide(int a, int b) {
if (b == 0) {
throw std::runtime_error("Division by zero.");
}
return a / b;
}
int main() {
int a = 10;
int b = 0;
try {
int result = divide(a, b);
std::cout << "Result: " << result << std::endl;
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
代码解析:
- 在
divide
函数中,我们使用std::runtime_error
类来抛出异常。它是一个标准库中已定义的异常类,用于表示运行时错误。 - 在
main
函数中,我们使用try-catch
块来捕获异常,并且将异常对象的what()
方法返回的字符串打印出来。这样可以更详细地了解异常的具体信息。
代码运行结果:
Caught exception: Division by zero.
通过以上示例,我们展示了C++中异常的基本用法和处理方式。在实际编程中,我们需要根据具体的场景和需求,选择合适的异常抛出和处理方式来确保程序的稳定性和可靠性。
3. 动态存储空间分配
3.1 操作符new
在C++中,我们可以使用new
操作符来动态地分配内存空间,以满足程序的需求。new
操作符返回一个指向分配内存的指针。
下面是一个使用new
操作符动态分配内存空间的示例代码:
#include <iostream>
int main() {
int* p = new int;
*p = 10;
std::cout << "Value: " << *p << std::endl;
delete p;
return 0;
}
代码解析:
- 在
main
函数中,我们使用new
操作符来动态分配了一个整数类型的内存空间,并将返回的指针赋值给变量p
。 - 通过
*p
,我们可以操作这块动态分配的内存空间,将其值设置为10。 - 在使用完动态分配的内存空间后,我们使用
delete
操作符来释放这块内存空间。
代码运行结果:
Value: 10
3.2 一维数组
除了可以动态分配单个变量的内存空间,我们还可以使用new
操作符动态分配数组的内存空间。
下面是一个使用new
操作符动态分配一维数组的示例代码:
#include <iostream>
int main() {
int size = 5;
int* array = new int[size];
for (int i = 0; i < size; i++) {
array[i] = i * 2;
std::cout << "Element " << i << ": " << array[i] << std::endl;
}
delete[] array;
return 0;
}
代码解析:
- 在
main
函数中,我们使用new
操作符动态分配了一个包含5个整数的数组,并将返回的指针赋值给变量array
。 - 使用循环遍历数组,并给每个元素赋值。
- 在使用完数组后,我们使用
delete[]
操作符来释放这块数组的内存空间。
代码运行结果:
Element 0: 0
Element 1: 2
Element 2: 4
Element 3: 6
Element 4: 8
3.3 异常处理
在动态存储空间分配过程中,可能会遇到一些异常情况,比如内存分配失败。为了处理这些异常,我们可以使用try-catch
块。
下面是一个处理内存分配异常的示例代码:
#include <iostream>
#include <new>
int main() {
int* array;
try {
array = new int[10000000000000];
} catch (const std::bad_alloc& e) {
std::cout << "Failed to allocate memory: " << e.what() << std::endl;
}
return 0;
}
代码解析:
- 在
try
块中,我们尝试动态分配一个非常大的整数数组。 - 如果内存分配失败,将抛出
std::bad_alloc
类型的异常。 - 在
catch
块中,我们捕获并处理这个异常,将异常对象的描述信息打印出来。
代码运行结果:
Failed to allocate memory: std::bad_alloc
3.4 操作符delete
使用new
操作符动态分配的内存空间,在使用完毕后,需要使用delete
操作符来释放这块内存空间。
下面是一个使用new
和delete
操作符的示例代码:
#include <iostream>
int main() {
int* p = new int;
*p = 10;
std::cout << "Value: " << *p << std::endl;
delete p;
std::cout << "Value after delete: " << *p << std::endl;
return 0;
}
代码解析:
- 在使用
delete
操作符后,指针p
所指向的内存空间已被释放。 - 在尝试访问已释放的内存空间时,会导致未定义的行为。
代码运行结果:
Value: 10
Value after delete: 10
请注意,访问已释放的内存空间是一种严重的错误。释放后的内存空间可能已经被其他程序使用,因此应该避免对已释放的内存进行任何操作。
3.5 二维数组
除了一维数组,我们还可以使用new
操作符动态分配二维数组。
下面是一个使用new
操作符动态分配二维数组的示例代码:
#include <iostream>
int main() {
int rows = 3;
int columns = 4;
int** matrix = new int*[rows];
for (int i = 0; i < rows; i++) {
matrix[i] = new int[columns];
}
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
matrix[i][j] = i * j;
std::cout << "Element at (" << i << "," << j << "): " << matrix[i][j] << std::endl;
}
}
for (int i = 0; i < rows; i++) {
delete[] matrix[i];
}
delete[] matrix;
return 0;
}
代码解析:
- 在
main
函数中,我们首先使用new
操作符动态分配了一个包含3个int*
类型指针的数组,即二维数组的行。 - 然后,我们使用循环遍历,对每个指针元素再动态分配一个包含4个
int
类型元素的数组,即二维数组的列。 - 通过两层循环遍历二维数组,并给每个元素赋值。
- 在使用完二维数组后,我们分别使用两个
delete[]
操作符释放二维数组的内存空间。
代码运行结果:
Element at (0,0): 0
Element at (0,1): 0
Element at (0,2): 0
Element at (0,3): 0
Element at (1,0): 0
Element at (1,1): 1
Element at (1,2): 2
Element at (1,3): 3
Element at (2,0): 0
Element at (2,1): 2
Element at (2,2): 4
Element at (2,3): 6
通过以上示例,我们学习了C++中动态存储空间分配的相关操作,包括使用new
操作符分配单个变量和数组的内存空间,以及使用delete
操作符释放内存空间。同时,我们还介绍了如何处理内存分配异常和使用二维数组的方法。在实际应用中,合理地利用动态存储空间分配,可以更灵活地管理内存,提高程序的效率和性能。