C++学习笔记8 控制流与错误处理

Cppfront与Cpp2:探索下一代C++语法,提升代码安全性与简洁性 在C++编程中,语法安全性和代码简洁性一直是开发者关注的核心议题。传统C++由于历史包袱,存在未初始化变量、空指针解引用等常见隐患,增加了代码维护的复杂性和错误风险。通过引入更现代的语言设计理念,如默认安全初始化、非空指针类型和统一函数声明语法,可以从根本上消除这些错误源头,提升代码的可靠性和可读性。这些改进不仅符合现代软件工程对安全性和可维护性的要求,也为大型项目开发和团队协作提供了更好的基础。Cppfront作为实验性编译器前端,正是这一理念的实践工具,它通过Cpp2语法探索了如何在不破坏现有生态的前提 阅读详情

8 控制流与错误处理

8.1 控制流语句的类别

类别样例c++中的实现方式
条件条件满足时,才执行对应语句if, else, switch
跳转cpu,从其它地方开始执行goto, break, continue
函数调用跳到其它地方执行,处理完成后返回function calls, return
循环满足条件的情况下,重复执行一段代码0次或多次while, do-while, for, ranged-for
停止停止程序执行std::exit(), std::abort()
异常错误处理的一种特殊形式try, throw, catch

8.2 If语句与代码块

第一类控制流语句是条件语句。条件语句是指定,根据一个判定条件,来确定是否应执行某些关联语句的语句。

8.2.1 If语句

示例:

#include <iostream>

namespace constants
{
    constexpr int minRideHeightCM { 140 };
}

int main()
{
    std::cout << "Enter your height (in cm): ";
    int x{};
    std::cin >> x;

    if (x >= constants::minRideHeightCM)
        std::cout << "You are tall enough to ride.\n";
    else
    { // 这里使用代码块
        std::cout << "You are not tall enough to ride.\n";
        std::cout << "Too bad!\n";
    }

    return 0;
}

8.2.2 If语句的隐式块

如果没有在if else语句里使用代码块,编译器将隐式声明块。因此:

if (条件表达式)
    true_对应的语句;
else
    false_对应的语句;

实际上相当于:

if (条件表达式)
{
    true_对应的语句;
}
else
{
    false_对应的语句;
}

8.3 常见的if语句问题

8.3.1 嵌套的if语句和悬空的else问题

else语句与同一块中最后一个未匹配的if语句成对。

为了在嵌套if语句时避免歧义,最好将内部if语句显式封装在代码块中。这允许我们在if语句中附加else,而不会产生歧义。

8.3.2 展平嵌套if语句

嵌套的if语句通常可以通过重新构造逻辑展平。嵌套较少的代码不太容易出错。

8.3.3 空语句

空语句是仅由分号组成的表达式语句。空语句不执行任何操作。它们通常在语言需要存在语句但程序员不需要时使用。为了可读性,空语句通常放在自己单独的行中。

8.4 Constexpr if语句

通常,if语句的条件在运行时求值。

然而,考虑条件是常量表达式的情况,例如在下面的示例中:

#include <iostream>

int main()
{
	constexpr double gravity{ 9.8 };

	// 提醒: 低精度的浮点数常量可以直接比较是否相等
	if (gravity == 9.8) // 常量表达式,结果永远为true
		std::cout << "Gravity is normal.\n";   // 永远会执行
	else
		std::cout << "We are not on Earth.\n"; // 不会执行到

	return 0;
}

由于gravity是constexpr,并用值9.8初始化,因此条件gravity==9.8的计算结果必须为true。因此,else语句将永远不会执行。

在运行时计算constexpr条件表达式是浪费的(因为结果永远不会变化)。将代码编译为永远无法执行的可执行文件也是浪费的。

8.4.1 Constexpr if语句(C++17)

C++17引入了constexpr if语句,该语句要求条件是常量表达式。将在编译时计算constexpr if语句的条件表达式的结果。

如果constexpr条件的计算结果为true,则整个If-else将替换为true语句。如果constexpr条件的计算结果为false,则整个If-else将替换为false语句(如果存在else)或直接删除(如果没有else)。

要使用if constexpr语句,如下:

#include <iostream>

int main()
{
	constexpr double gravity{ 9.8 };

	if constexpr (gravity == 9.8) // 现在使用 constexpr if
		std::cout << "Gravity is normal.\n";
	else
		std::cout << "We are not on Earth.\n";

	return 0;
}

编译上述代码时,编译器将在编译时计算条件表达式,确认它始终为true,并仅保留单个语句std::cout « “Gravity is normal.\n”;。

换句话说,它将编译以下内容:

int main()
{
	std::cout << "Gravity is normal.\n";

	return 0;
}

当条件表达式是常量表达式时,优先使用if constexpr语句。

出于优化目的,现代编译器通常将具有常量条件的非constexpr-if语句视为constexpr-if语句。然而,这并不意味着一定会这样做。遇到具有常量条件的非constexpr-if语句,编译器可能会发出警告,建议您改用constexpr-if语句。这将确保发生编译时计算(即使禁用编译优化)。

8.5 Switch语句基础

尽管可以将许多if-else语句链接在一起,但这既难以阅读,又效率低下。

根据一组不同的值测试变量或表达式很常见的,因此C++提供了另一种称为switch语句的条件语句,该语句专门用于此目的:

#include <iostream>

void printDigitName(int x)
{
    switch (x)
    {
        case 1:
            std::cout << "One";
            return;
        case 2:
            std::cout << "Two";
            return;
        case 3:
            std::cout << "Three";
            return;
        default:
            std::cout << "Unknown";
            return;
    }
}

int main()
{
    printDigitName(2);
    std::cout << '\n';

    return 0;
}

switch语句背后的思想很简单:计算表达式(有时称为条件)以产生值。如果表达式的值等于任何case标签之后的值,则执行匹配的case标签后面的语句。如果找不到匹配的值并且存在默认标签,则改为执行默认标签之后的语句。

与原始的if语句相比,switch语句的优点是只对表达式求值一次(使其更有效率),switch语句还使读者更清楚地看到,在每个情况下,都有哪些对应的处理语句。

8.5.1 使用switch语句

我们通过使用switch关键字来启动switch语句,后跟括号,其中包含要在其中求值的条件表达式。表达式通常只是单个变量,但它可以是任何有效的表达式。

一个限制是,条件必须求值为整数类型或枚举类型,或者可以转换为一个整数或枚举值。此处不能使用计算为浮点类型、字符串和大多数其他非整型的表达式。

在条件表达式之后,声明了一个代码块。在块内部,我们使用标签来定义要测试相等性的所有值。有两种标签。

8.5.2 case标签

第一种标签是case标签,它使用case关键字声明,后跟常量表达式。常量表达式必须与条件表达式的类型匹配,或者必须可转换为该类型。

如果条件表达式的值等于case标签后的表达式,则从该case标签之后的第一条语句开始执行,然后按顺序继续。

对于可以具有的case标签的数量没有实际限制,但switch中的所有case标签都必须是唯一的。

8.5.3 default标签

第二种标签是default标签(通常称为默认情况),它是使用default关键字声明的。如果条件表达式与任何case标签都不匹配,并且存在default标签,则从default标签之后的第一条语句开始执行。

default标签是可选的,每个switch语句只能有一个default标签。按照惯例,default情况放在switch块中的最后一个。

8.5.4 没有匹配的case标签,也没有default标签

如果条件表达式的值与任何case标签都不匹配,并且没有default标签,则不会执行switch内的任何语句。

8.5.5 使用break关键字

break语句(使用break关键字声明)告诉编译器,我们已经完成了在switch中执行语句的操作,需要执行switch后的下一条语句。这允许我们在不退出整个函数的情况下退出switch语句。

switch语句中标签下的每一组语句都应该以break语句或return语句结尾。

8.6 switch fallthrough机制与作用域

8.6.1 Fallthrough机制

当switch表达式与case标签或可选的默认标签匹配时,执行从匹配标签之后的第一条语句开始。然后继续按顺序执行,直到发生以下终止条件之一:

  1. switch代码块结束
  2. 另一个控制流语句(通常是break或return)导致退出代码块或函数。
  3. 其它打断程序正常控制流的事情(操作系统杀死了对应的进程等其它原因)

请注意,另一个case标签的存在不是这些终止条件之一——因此,如果没有中断或返回,执行将溢出到后续的case情况中。

一旦case或默认标签下的语句开始执行,它们将溢出(fallthrough)到后续的case中。Break或return语句通常用于防止这种情况。

8.6.2 [[fallthrough]]属性

可以通过注释,来告诉其它开发人员,switch的fallthrough行为是有意设计的。虽然这对其他开发人员有效,但编译器和代码分析工具不知道如何解释注释,因此不会消除警告。

为了帮助解决这个问题,C++17添加了一个名为[[fallthrough]]的新属性。

属性是一种现代C++功能,它允许程序员向编译器提供有关代码的一些附加数据。要指定属性,请将属性名称放在双括号之间。属性不是语句——相反,它们几乎可以在上下文相关的任何地方使用。

下面的例子中,[[fallthrough]]属性修改null语句,以指示fallthrough是有意的(不应触发任何警告):

#include <iostream>

int main()
{
    switch (2)
    {
    case 1:
        std::cout << 1 << '\n';
        break;
    case 2:
        std::cout << 2 << '\n'; // 这里开始执行
        [[fallthrough]]; // 有意的进行 fallthrough -- 注意这里的分号代表空语句
    case 3:
        std::cout << 3 << '\n'; // 这里也会执行到
        break;
    }

    return 0;
}

8.6.3 连续case标签

您可以使用逻辑OR运算符将多个测试组合到单个语句中:

bool isVowel(char c)
{
    return (c=='a' || c=='e' || c=='i' || c=='o' || c=='u' ||
        c=='A' || c=='E' || c=='I' || c=='O' || c=='U');
}

这里c被多次求值。

通过按顺序放置多个case标签,可以使用switch语句执行类似的操作:

bool isVowel(char c)
{
    switch (c)
    {
        case 'a': // if c is 'a'
        case 'e': // or if c is 'e'
        case 'i': // or if c is 'i'
        case 'o': // or if c is 'o'
        case 'u': // or if c is 'u'
        case 'A': // or if c is 'A'
        case 'E': // or if c is 'E'
        case 'I': // or if c is 'I'
        case 'O': // or if c is 'O'
        case 'U': // or if c is 'U'
            return true;
        default:
            return false;
    }
}

请记住,执行从匹配的case标签之后的第一条语句开始。case标签不是语句(它们是标签),因此它们不算数。

上面程序中所有case语句之后的第一个语句都返回true,因此如果任何case标签匹配,函数将返回true。

因此,我们可以“堆叠”case标签,以使所有这些case标签在之后共享相同的语句集。这不被认为是fallthrough行为,因此这里不需要使用注释或[[fallthrough]]。

8.6.4 switch语句中case的作用域

使用if语句,在if条件之后只能有一条语句,并且该语句被认为隐式地位于代码块内。然而,对于switch语句,标签后的语句都作用于switch块。不会创建隐式块。

switch (1)
{
    case 1: // 不会创建隐式块
        foo(); // 在switch的作用域内,而不是case 1内
        break; // 在switch的作用域内,而不是case 1内
    default:
        std::cout << "default case\n";
        break;
}

在上面的示例中,case 1 标签和default标签之间的2条语句的作用域是switch块的一部分,而不是case 1 隐含的代码块。

8.6.5 case语句中的变量声明和初始化

您可以在case标签之前和之后声明或定义(但不能初始化)switch语句内的变量:

switch (1)
{
    int a; // okay: case标签之前可以声明变量
    int b{ 5 }; // 不合法: case 标签之前,不可以初始化变量

    case 1:
        int y; // okay 但不推荐
        y = 4; // okay: 赋值语句可以
        break;

    case 2:
        int z{ 4 }; // 不合法: 后面还有case标签,不允许初始化变量
        y = 5; // okay: y 在上面声明,所以这里可以赋值
        break;

    case 3:
        break;
}

在 case 1 中定义了变量y,在 case 2 中使用了它。switch内的所有语句都被视为同一作用域的一部分。因此,在 case 1 内声明或定义的变量可以在以后使用。

然而,变量的初始化,需要在运行时执行(因为需要将初始值设置给变量)。如果后续还有case标签,则不允许初始化变量(因为初始化可能被跳过,这将使变量未初始化)。在第一个case标签之前不允许初始化,因为这些语句永远不会执行,switch语句无法指定到它们。

如果case标签内需要定义和初始化新变量,最佳实践是在case语句下的显式块内进行定义和初始化:

switch (1)
{
    case 1:
    { // 这里有一个显示的代码块
        int x{ 4 }; // okay, 变量在一个新的代码块内初始化
        std::cout << x;
        break;
    }
    default:
        std::cout << "default case\n";
        break;
}

8.7 goto语句

下一种控制流语句是无条件跳转。无条件跳转会导致执行跳转到代码中的另一个位置。术语“无条件”意味着跳转总是发生(不像if语句或switch语句,跳转仅根据表达式的结果有条件地发生)。

在C++中,无条件跳转是通过goto语句实现的,跳转到的位置是通过使用语句标签来标识的。下面是goto语句和语句标签的示例:

#include <iostream>
#include <cmath> // 引用 sqrt() 函数

int main()
{
    double x{};
tryAgain: // 这是标签语句
    std::cout << "Enter a non-negative number: "; 
    std::cin >> x;

    if (x < 0.0)
        goto tryAgain; // 这是goto语句

    std::cout << "The square root of " << x << " is " << std::sqrt(x) << '\n';
    return 0;
}

在此程序中,要求用户输入一个非负数。如果输入负数,程序将使用goto语句跳回tryAgain标签。然后再次要求用户输入新数字。这样,我们可以不断地要求用户输入,直到输入有效的内容。

8.7.1 标签语句具有函数作用域

在前面,我们讨论了两种作用域:局部(代码块)作用域和文件(全局)作用域。标签语句使用第三种作用域:函数作用域,这意味着标签在整个函数中都是可见的,甚至在其声明点之前也是可见的。goto语句及其对应的标签语句必须出现在同一函数中。

goto有两个主要限制:只能在单个函数的边界内跳转(不能跳出一个函数跳入另一个函数),如果向前跳转,则不能跳过变量的显式初始化。例如:

int main()
{
    goto skip;   // error: 这个跳转是非法的...
    int x { 5 }; // 因为这里的显式初始化会被跳过
skip:
    x += 3;      // 这里的x的初始值设置的是多少?
    return 0;
}

请注意,可以向后跳转,当执行接下来的初始化语句时,变量将被重新初始化。

8.7.2 避免使用goto

几乎任何使用goto语句编写的代码都可以使用C++中的其它控制流(如if语句和循环)更清楚地编写。一个值得注意的例外是,当您需要退出嵌套循环而不是整个函数时——在这种情况下,使用goto转到循环后面可能是最干净的解决方案。

避免goto语句(除非替代方案对代码可读性的影响明显)。

8.8 循环和while语句

8.8.1 循环

循环允许一段代码重复执行,直到满足某个条件退出。循环为编程添加了大量的灵活性。

8.8.2 While语句

while语句(也称为while循环)是C++提供的三种循环类型中最简单的一种,它的定义与if语句的定义非常相似:

while (条件表达式)
    语句;

使用while关键字声明while语句。执行while语句时,将计算条件表达式。如果条件的计算结果为true,则执行关联的语句。

然而,与if语句不同,一旦语句完成执行,控制权就会返回到while语句的顶部,并且重复该过程。这意味着只要条件继续计算为true,while语句就会保持循环。

8.8.3 初始条件计算为false的语句

如果条件最初的计算结果为false,则关联的语句将根本不会执行。

8.8.4 死循环(无限循环)

如果表达式的计算结果总是为true,则while循环将永远执行。这称为死循环。

8.8.5 有意的死循环

可以这样声明有意的死循环:

while (true)
{
  // 这个循环将无限执行
}

退出无限循环的唯一方法是通过return语句、break语句、exit语句、goto语句、引发异常或用户终止程序。

8.8.6 循环变量的命名

循环变量是用于控制循环执行次数的变量。例如,给定while ( count <= 10 ),count是一个循环变量。虽然大多数循环变量的类型都是int,但您偶尔会看到其他类型(例如char)。

循环变量通常被赋予简单的名称,其中i、j和k是最常见的。

然而,如果您想知道在程序中的何处使用循环变量,并且在i、j或k上使用搜索函数,则搜索函数将返回程序中大半的行!由于这个原因,一些开发人员更喜欢独特的循环变量名,如iii、jjj或kkk。由于这些名称更独特,这使得搜索循环变量变得更容易,并帮助它们作为循环变量脱颖而出。一个更好的想法是使用“真实”的变量名,例如count、index,或者提供有关计数内容的更多细节的名称(例如userCount)。

最常见的循环变量类型称为计数器,它是一个循环变量,用于统计循环已执行的次数。

8.8.7 整形循环变量应该有符号

整形循环变量几乎总是有符号的,因为无符号整数可能会导致意外的问题。

8.8.8 每N次迭代执行一次操作

每次执行循环时,它被称为一次迭代。

通常,我们希望在第2、3或4次迭代中执行某些操作,例如打印换行。通过在计数器上使用余数运算符,可以轻松完成此操作。

8.8.9 嵌套循环

还可以将循环嵌套在其他循环的内部。

8.9 Do while语句

C++提供了do-while语句:

do
    语句; // 可以是单条语句,或代码块
while (条件表达式);

do-while语句是一个循环语句,其工作方式类似于while循环,但该语句至少执行一次。执行语句后,do-while循环检查条件。如果条件的计算结果为true,则执行路径跳回到do-while循环的顶部,并再次执行它。

下面是使用do-while的示例:

#include <iostream>

int main()
{
    // 选项需要在循环外定义, 以便稍后使用
    int selection{};

    do
    {
        std::cout << "Please make a selection: \n";
        std::cout << "1) Addition\n";
        std::cout << "2) Subtraction\n";
        std::cout << "3) Multiplication\n";
        std::cout << "4) Division\n";
        std::cin >> selection;
    }
    while (selection != 1 && selection != 2 &&
        selection != 3 && selection != 4);

    // 处理用户的选择
    // 例如可以使用switch语句

    std::cout << "You selected option #" << selection << '\n';

    return 0;
}

通过这种方式,我们避免了魔数和额外的变量。

8.10 for语句

for语句在定义上看起来相当简单:

for (初始化语句; 条件表达式; 递增/递减表达式)
   语句;

8.10.1 for语句的计算

for语句分为3阶段执行:

首先,执行初始化语句。这仅在循环启动时执行一次。初始化语句通常用于变量定义和初始化。定义的变量具有“循环作用域”,这实际上只是块作用域的一种形式,这些变量从定义点一直存在到循环语句的末尾。在等价的while循环中,可以看到初始化语句位于等价的代码块内,当整体结束时,初始化语句中定义的变量超出作用域。

其次,对于每次循环迭代,先计算条件表达式的结果。如果结果为true,则执行对应的语句。如果为false,则循环终止,并执行循环之外的下一条语句。

最后,计算递增/递减表达式。通常,该表达式用于递增或递减初始化语句中定义的循环变量。然后,会返回到第二步(并再次判断条件表达式)。

8.10.2 省略的表达式

for语句中的任何部分都可省略。值得注意的是,下面的示例生成了无限循环:

for (;;)
    语句;

8.11 break与continue

8.11.1 break

break语句会导致while循环、do-while环、for循环或switch语句结束,并执行循环或者switch后的下一个语句。

8.11.2 continue

continue语句提供了一种方法,可以在不终止整个循环的情况下结束循环的当前迭代。

8.11.3 提前返回

不是函数中最后一个语句的return语句称为提前返回(early return)。许多程序员认为应该避免提前返回。return语句只在函数的最底部,这具有简单性——函数将接受参数,执行任何逻辑,并返回结果。有额外的返回会使逻辑复杂化。

但是,使用提前返回允许函数在完成后立即退出,这减少了对不必要逻辑的判断执行,并最大限度地减少了对条件嵌套的需要,这使得代码更具可读性。

一些开发人员采取中间立场,只在函数顶部使用提前返回来进行参数验证(捕获传入的错误参数)。

我们的立场是,提前返回更有帮助,而不是有害。

8.12 提前退出程序

退出,代表程序终止执行。在C++中,这被实现为函数(而不是关键字),因此退出程序需要函数调用。

当main() 函数返回时(到达函数末尾或通过return语句返回),会发生许多事情。

首先,离开main函数,所以所有局部变量和函数参数都会被销毁(通常情况)。

接下来,调用一个名为std::exit()的特殊函数,并将main()函数的返回值作为参数传入。那么什么是std::exit()?

8.12.1 std::exit()函数

exit()是一个导致程序正常终止的函数。正常终止意味着程序以预期的方式退出。请注意,术语正常终止并不意味着程序执行符合预期。例如,假设您正在编写一个程序,希望用户输入要处理的文件名。如果用户键入了无效的文件名,则程序可能会返回非零的状态码来指示故障状态,但它仍然是正常的终止。

exit()执行许多清理函数。首先,销毁具有静态存储期的对象。然后,如果使用了任何文件,则执行一些文件清理动作。最后,用传递给std::exit()的参数用作状态码,将控制返回给操作系统。

8.12.2 显式调用std::exit()

尽管在函数main()结束时隐式调用std::exit(),但也可以显式调用std::exit。当以这种方式调用std::exit()时,需要引用cstdlib头文件。

下面是显式使用std::exit()的示例:

#include <cstdlib> // 引入 std::exit()
#include <iostream>

void cleanup()
{
    // 这里来做一些清理工作
    std::cout << "cleanup!\n";
}

int main()
{
    std::cout << 1 << '\n';
    cleanup();

    std::exit(0); // 结束程序执行,并将0返回给操作系统

    // 下面的代码不会执行
    std::cout << 2 << '\n';

    return 0;
}

请注意,std::exit()后续的语句将不会执行,因为程序已经终止。

在上面的程序中,从函数main()中调用std::exit(),但可以从任何函数中调用std::exit()来终止程序。

显式调用std::exit(),不会清理任何局部变量(当前函数中,以及在调用堆栈上的函数中)。因此,通常最好避免调用std::exit()。

std::exit()函数不清理当前函数以及调用堆栈中的局部变量。

8.12.3 std::atexit

由于std::exit()立即终止程序,因此您可能希望在终止之前手动进行一些清理。在这种情况下,清理意味着关闭数据库或网络连接、释放分配的任何内存、将信息写入日志文件等…

上面的示例调用了函数cleanup()来处理清理任务。然而,在每次调用exit()之前手动调用清理函数会给我们增加大量的负担。

为了帮助实现这一点,C++提供了std::atexit()函数,它允许您指定一个函数,该函数将在程序终止时通过std::exit()自动调用。

在多线程程序中,调用std::exit()可能会导致程序崩溃(因为调用std::exit()的线程将清理可能仍然被其他线程访问的静态对象)。由于这个原因,C++引入了另一对函数,它们的工作方式类似于std::exit()和std::atexit(),称为std::quick_exit()和std:∶at_quick_exit()。std::quick_exit()会终止程序,但不会清理静态对象,并且不一定执行其他类型的清理。对于以std::quick_exit()终止的程序,std::at_quick_exit()扮演与std:∶atexit()相同的角色。

8.12.4 std::abort和std::terminate

C++包含另外两个与退出程序相关的函数。

std::abort()导致程序异常终止。异常终止意味着程序出现某种异常运行时错误,程序无法继续运行。例如,尝试除以0将导致异常终止。std::abort()不执行任何清理。

std::terminate()函数通常与异常一起使用。尽管可以显式调用std::terminate,但在处理异常时(以及在其他一些与异常相关的情况下),它经常被隐式调用。默认情况下,std::terminate()会调用std::abort()。

8.12.5 什么时候需要手动退出程序

只有在没有安全的方法从main函数正常返回时才手动退出程序。

尽管应该尽量减少手动的退出程序,但程序可以通过许多其他方式意外关闭。例如:

  1. 应用程序可能由于错误而崩溃(在这种情况下,操作系统将关闭它)。
  2. 用户可能会以各种方式终止应用程序。
  3. 用户可能会关闭其计算机的电源。
  4. 太阳可能会变成超新星,并在一个巨大的火球中吞噬地球。

一个设计良好的程序应该能够在任何时候被关闭,并且影响很小。

一个常见例子,现代游戏通常定期自动保存游戏状态和用户设置,以便如果游戏意外关闭而未保存,则用户可以稍后继续(使用先前的自动保存的设置),而不会损失太多进度。

C++异常与Rust Result:错误处理机制的设计哲学与性能权衡 错误处理是编程语言设计的核心问题,涉及程序健壮性与开发效率的平衡。从原理上看,错误处理机制可分为基于控制流跳转的异常模型和基于类型系统的值模型。C++异常采用栈展开和异常表机制实现非本地控制流转移,虽能分离关注点但带来运行时开销和二进制体积增大;Rust的Result类型将错误编码为普通值,通过编译期检查强制处理所有错误路径,实现零成本抽象。在技术价值层面,C++异常提供了灵活的抽象能力但牺牲了确定性性能,而Rust的错误值模型确保了内存安全和执行路径可预测性。应用场景上,高性能计算和嵌入式系统常禁用异常以 阅读详情

相关推荐

为何 C++ 异常强大却性能不佳,Rust 错误处理安全却代码繁琐?

C++异常与Rust Result在错误处理上各有优劣,通过、异常安全边界和零开销错误码映射的融合,我们可以在性能与表达力之间找到平衡。这些优化方案不仅提升了代码的健壮性和效率,也为C++和Rust的混合开发开辟了新的可能性。希望本文的案例和见解能为你的错误处理实践提供启发。

weixin_45715405的博客 393

C++控制流语句深度解析:break、continue与goto的正确使用

控制流语句是编程语言中用于改变程序执行顺序的基础概念,其核心原理在于通过条件判断和跳转指令,使程序能够根据不同情况执行不同代码路径。从技术价值看,合理运用控制流能显著提升代码执行效率、增强逻辑清晰度,并优化资源管理。在应用场景上,break常用于提前终止循环或switch分支,continue用于跳过当前循环迭代,而goto则在特定场景如多层嵌套错误处理中发挥作用。本文聚焦于C++中的break、continue和goto语句,深入探讨其语法细节、在嵌套循环中的行为差异,以及如何避免常见陷阱。通过分析bre

weixin_33701294的博客 441

深入C++编程:控制流程与算法实现

本篇博客将带领读者深入探索C++编程中的控制流程语句,包括条件判断和循环控制。通过具体的代码示例和逻辑分析,我们将了解如何实现基本的数学运算、输入输出以及如何在程序中作出决策。同时,回顾计算机语言的发展历程,从最初的手动编程到现代复杂的编程语言。...

weixin_42502089的博客 300

C++异常处理完全指南:CPP-Crash-Course中的错误处理最佳实践

C++异常处理是现代C++开发中不可或缺的错误处理机制,本指南将从CPP-Crash-Course项目出发,详细介绍异常处理的核心概念、使用方法和最佳实践,帮助开发者构建更健壮的C++应用程序。 ## 为什么异常处理对C++开发至关重要 在C++程序开发过程中,错误处理是保证程序稳定性和可靠性的关键环节。传统的错误码返回方式存在诸多局限,而异常处理机制提供了一种更优雅、更灵活的错误处理方案。C

gitblog_00521的博客 798

初学者的C++扑克牌游戏源代码项目

本文还有配套的精品资源,点击获取 简介:本压缩包包含一个适合初学者理解的C++扑克牌游戏源代码,涉及C++基础语法、数据结构设计和算法实现等。游戏模拟21点计数规则,初学者可以通过源代码学习类与对象、函数使用、控制流、错误处理、输入输出和编程风格。实践这一项目有助于提升编程技能,深化对C++的理解。 1. C++基础语法 C++是一门通用的编程语言,它既可以...

weixin_30765637的博客 1488

掌握SQL控制流与CASE表达式

本文深入探讨了SQL Server中的控制流语句,特别是WAITFOR语句、RETURN语句、TRY...CATCH错误处理以及CASE表达式。我们将通过实例和详细解释来理解这些语句如何在数据库编程中实现复杂逻辑和错误处理,以及如何利用CASE表达式进行灵活的数据转换和报告。

weixin_33557333的博客 293

如何快速上手Outcome:C++错误处理的终极指南

Outcome是一个专为C++14设计的轻量级函数失败报告和处理库,为那些不适合直接使用C++异常处理的场景提供了完美的替代方案。无论你是需要更明确的错误处理路径,还是要在禁用异常的编译环境中工作,Outcome都能为你提供强大而灵活的工具。 ## 为什么选择Outcome?🤔 Outcome项目提供了极其轻量级的`outcome<T>`和`result<T>`类型,它们专门解决了以下痛点:

gitblog_00704的博客 598

用废旧汽车电池DIY家用UPS:手把手教你低成本搭建Wi-Fi不断电系统

本文详细介绍了如何利用废旧汽车电池DIY低成本家用UPS系统,确保Wi-Fi设备在断电时持续供电。通过精选材料、电路搭建和安全防护,实现高效稳定的电源转换与智能切换,成本仅为市售UPS的三分之一。特别适合家庭办公和远程工作者提升网络供电可靠性。

weixin_29200485的博客 464

C++中的错误处理机制

虽然自定义异常可以提供更具体的错误类型,但过多的自定义异常类型可能会使代码变得复杂和难以理解。在定义新的异常类型之前,请考虑是否可以使用现有的标准异常类。

shiming8879的博客 1185

本地部署 pyvideotrans(视频翻译配音工具)

语音识别选择faster-whisper(本地),模型选择预先下载并保存的medium,然后选择好要处理的视频以及保存到的位置,这里我选择了一个名为why not的英文演讲视频,保存在桌面_vedio_out文件下。将附件中faster-medium.7z 和 faster-small.7z解压到项目中的models目录下, 解压后,目录中应该存在 models--Systran--faster-whisper-small。en.m4a|.wav = 原始视频中的音频文件(包含所有背景音和人声)

西唯兵欧泡的博客 2341

验证(uvm常见问题)--子类和父类的句柄和对象问题

sv中的子类和父类 在sv中,有如下两个类,child继承于parent。然后在test module中,正确转换的方法如下面所示。其中c1,c2,p1 都指向了同一个子类的对象c1。总结如下: 一个子类的对象是包括子类的成员变量,方法和父类的成员变量,方法。 子类的句柄c1可以直接赋值给父类的句柄p1,此时p1只可以访问父类的成员变量和方法,或者子类的虚方法,比如下面调用p1.display是...

q876507447的博客 7631

异常控制流概述与异常

异常控制流一、异常控制流的概述1、控制流是什么2、异常控制流(ECF)的诞生3、ECF的重要性4、从硬件到软件,从底层到顶层的学习逻辑二、异常1、异常的定义2、异常的剖析:3、异常的处理4、异常与过程调用的区别5、异常的类别1)中断(异步发生)2)陷阱和系统调用(同步发生)3)故障4)终止三、收获、问题与思考(有些问题待解决)收获问题思考 一、异常控制流的概述 1、控制流是什么 从给处理器加电开始,直到你断电为止,PC假设一个值的序列a0,a1,……,an-1。其中,每个ak是某个相应的指令Ik的地址。每次

qq_45579563的博客 924

保姆级教程:在Windows/Mac上搞定SAM 3的权重和BPE文件下载与配置

本文提供了一份详细的SAM 3权重和BPE文件下载与配置指南,特别针对国内开发者遇到的下载缓慢和配置错误问题。通过国内镜像源快速获取文件,并详细介绍了环境检查、文件下载、路径配置及常见问题解决方案,帮助开发者高效完成SAM 3的部署与使用。

weixin_42547431的博客 548

C++异常处理的根本缺陷:隐式传播的性能陷阱与控制流断裂

C++异常处理机制存在本质缺陷:1)表面优雅的控制流非局部跳转破坏了调用栈连续性,导致资源管理复杂化;2)违背"零开销"原则,带来编译时和运行时性能的不确定性,特别是栈展开的隐藏成本随调用深度呈指数增长;3)严重阻碍编译器优化,影响循环向量化和内联等关键优化;4)实际性能测试显示异常处理比错误码方式慢25%-648%,且导致代码显著膨胀。异常机制还引发架构问题,使模块边界模糊,增加系统复杂度。这些缺陷使C++异常处理成为看似美好实则问题重重的机制。

weixin_42108533的博客 598

【主题建模】一种基于深度学习的主题建模方法:BERTopic(实战篇)

BERTopic 是基于深度学习的一种主题建模方法。2018 年底,Devlin et al. 提出了 Bidirectional Encoder Representations from Transformers (BERT)。BERT 是一种用于 NLP 的预训练策略,它成功地利用了句子的深层语义信息。

Data · Model · Agent · Repeat 2万+

某联达OA任意用户登录漏洞复现

由于`/WebService/Lk6SyncService/DirectToOthers/GetSSOStamp.asm`j接口未作限制,导致可以通过该接口调用`admin`的`cookie`值,通过对`LoginCredence`和`LoginTimestamp`的参数修改,进而实现任意登录进去系统.

千寻的博客 1589

C++核心指导原则: 错误处理

本文介绍C++ Core Guidelines性能部分.

一些C++相关的技术笔记或者教程. 854

Trae+雪球MCP,化身专业的股票分析师(玩转100个MCP系列第二弹)

大家好呀,我是程序员夏叶,深度AI编程使用者,专注于分享AI编程方面的使用技巧、经验以及前沿资讯,有兴趣的可以关注我的公众号,一起学习,共同进步。第二弹,使用Trae+雪球MCP,获取股票实时行情,分析股票走向等,写代码间隙看看股票价格,研究研究行情,哈哈。真的很不错呀,最关键的是,足够隐蔽,同事以为你在写代码,实际你在浏览股票行情,哈哈,感兴趣的可以试一下哦。说实话,沧州明珠这只股我算是砸手里了,不过还好买的不多,哈哈。

cpp加油站的专栏 518

C++26来了!为什么你的旧错误处理方式将在2025被淘汰?

掌握C++26错误处理新标准,解决跨平台兼容难题。2025全球C++及系统软件技术大会揭晓统一异常机制与零成本抽象设计,适用于嵌入式、分布式系统。提升代码健壮性与移植效率,开发者的进阶利器,值得收藏。

Algorhythm的博客 807

深度解析:Android开发工程师(嵌入式系统方向)职位 - 技术需求、面试准备与发展路径

本文深入分析了嵌入式Android开发工程师的岗位要求和技术内涵。该职位需要掌握C/C++、Linux系统编程、硬件接口等核心技术,能够进行Android系统底层定制开发,包括HAL层实现、Framework修改、性能优化等工作。文章详细解读了岗位职责,梳理了必备技术栈,提供了针对性的面试问题与参考答案,并探讨了职业发展路径。这类岗位要求开发者兼具软件工程能力和硬件基础知识,适合在智能电视、机顶盒、车载系统等嵌入式领域发展的技术人员。

郑伟强dev的专栏 134

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

异常控制流

现代操作系统必须能够对系统状态的变化做出反应,这些系统状态不是被内部程序变量捕获的,而且也不一定要和程序的执行相关。比如,一个硬件定时器定期产生信号,这个事件必须得到处理。包到达网络适配器后,必须存放在内存中。程序向磁盘请求数据,然后休眠,直到被通知说数据已就绪。当子进程终止时,创造这些子进程的父进程必须得到通知。 现代系统通过使控制流发生突变来对这些情况做出反应。一般而言,我们把这些突变称为异常控制流(Exceptional Control Flow,ECF)。 异常控制流发生在计算机系统的各个层次。

EJoft的博客 464

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

从Result到std::expected,现代C++错误处理演进之路,你跟上了吗?

掌握C++23新特性std::expected在错误处理中的最佳实践,提升代码健壮性与可读性。适用于函数返回多状态场景,替代异常与传统Result模式,实现零成本错误传递。结合模式匹配与链式调用,简化错误处理逻辑,值得收藏。

InstrWander的博客 965

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

C++错误处理最佳实践:从异常到结果类型的演进

C++错误处理演进与最佳实践 本文系统梳理了C++错误处理机制的演进历程,对比分析了各种处理策略的优缺点。从早期的返回码/errno模式,到C++98引入的异常机制,再到C++17/23的optional/expected等类型安全方案,错误处理逐步向更可靠、更显式的方向发展。 文章详细对比了不同错误处理机制的性能、类型安全性和适用场景,重点解析了异常处理的原理、安全性保证级别和最佳实践。同时介绍了现代C++引入的optional和expected等新特性,以及函数式错误处理风格的应用。 最后,文章提供了跨

ALex_zry的博客 414

【系统级编程新纪元】:C++26跨平台错误处理的7种高效模式

掌握C++26跨平台错误处理核心方法,解决系统级编程兼容难题。基于2025全球C++及系统软件技术大会:C++26错误处理机制的跨平台兼容方案,详解7种高效模式在嵌入式、分布式系统的应用,提升异常安全与性能一致性。模式设计兼顾标准化与可扩展性,值得收藏。

InstrGap的博客 1062

【C++26错误处理革命】:深度解析跨平台兼容新范式与实战方案

解决C++跨平台错误处理难题,提供C++26新机制实战方案。深度解读2025全球C++及系统软件技术大会最新成果,涵盖异构系统兼容策略、异常与预期对象协同设计,提升系统鲁棒性与可移植性,值得收藏。

LogicWander的博客 1026

从Result到std::expected,现代C++错误处理演进,你跟上了吗?

掌握C++23新特性std::expected在错误处理中的最佳实践,提升代码健壮性与可读性。适用于函数返回值异常处理,替代传统错误码或异常机制,实现类型安全的预期结果管理。结合模式匹配与链式调用,显著增强逻辑清晰度,现代C++错误处理演进必学方案,值得收藏。

AlgoInk的博客 875
上一篇: C++学习笔记7 作用域与编译链接
下一篇: C++学习笔记9 错误检查与处理
一条铁憨憨
博客等级 码龄6年 151粉丝 15原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值