C++基础与深度解析 | 语句 | 分支语句 | 循环语句 | 达夫设备

2024-05-15 06:44

本文主要是介绍C++基础与深度解析 | 语句 | 分支语句 | 循环语句 | 达夫设备,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、语句基础
  • 二、分支语句
    • 1.分支语句--if
    • 2.分支语句--switch
  • 三、循环语句
    • 1.循环语句--while
    • 2.循环语句--do-while
    • 3.循环语句--for
    • 4.循环语句--基于范围的for循环
    • 5.break / continue语句
    • 四、语句的综合应用--达夫设备

一、语句基础

语句的常见类别

  • 表达式语句:表达式后加分号,对表达式求值后丢弃,可能产生副作用
  • 空语句:仅包含一个分号的语句,可能与循环一起工作
  • 复合语句(语句体):由大括号组成,无需在结尾加分号(分号标识语句的结束),形成独立的域(语句域)。语句域可以更加精确地控制对象的生命周期,在复合语句结束后对象消亡。复合语句可以包含多条子语句。
  • if语句

顺序语句与非顺序语句

  • 顺序语句

    • 从语义上按照先后顺序执行

    • 实际的执行顺序可能产生变化(编译器优化、硬件乱序执行)

      编译器优化–在不改变语义的情况下,对执行顺序执行调整

    • 顺序语句执行时,通过编译器优化、硬件乱序执行,与硬件流水线紧密结合,执行效率较高

  • 非顺序语句

    • 非顺序语句可分为分支语句与循环语句
    • 在执行过程中引入跳转,从而产生复杂的变化
    • 分支预测错误可能导致执行性能降低

    最基本的非顺序语句: goto

    • 通过标签指定跳转到的位置
    • 具有若干限制
      • 不能跨函数跳转
      • 向前跳转时不能越过对象初始化语句
      • 向后跳转可能会导致对象销毁与重新初始化
    • goto 本质上对应了汇编语言中的跳转指令:如jne,但有如下缺陷
      • 缺乏结构性的含义
      • 容易造成逻辑混乱
      • 除特殊情况外,应避免使用
    #include <iostream>int main()
    {int x = 3;if(x) goto label;x = x + 1;label:return 0;
    }
    

    image-20240511005059360

二、分支语句

1.分支语句–if

  if语句的语法可参考if 语句。当需要在分支语句中引入复杂的逻辑,需要使用语句块表示复杂的分支逻辑。if语句基于条件的真假来执行不同的分支。

基本形式

if (condition) {// 当condition为真时执行的代码
}

if-else结构:二分支

if (condition) {// 当condition为真时执行的代码
} else {// 当condition为假时执行的代码
}

if-else if-else结构:三分支以及多分支

if (condition1) {// 当condition1为真时执行的代码
} else if (condition2) {// 当condition1不为真且condition2为真时执行的代码
} else {// 当所有条件都不为真时执行的代码
}

使用逻辑运算符

C++中的逻辑运算符包括:

  • &&(逻辑与)
  • ||(逻辑或)
  • !(逻辑非)
if (condition1 && condition2) {// 当condition1和condition2都为真时执行的代码
} else if (condition1 || condition2) {// 当condition1或condition2为真时执行的代码
} else {// 当condition1和condition2都不为真时执行的代码
}

示例

#include <iostream>int main() {int score = 75;//根据score的值,程序会输出对应的评价等级if (score >= 90) {std::cout << "优秀" << std::endl;} else if (score >= 80) {std::cout << "良好" << std::endl;} else if (score >= 70) {std::cout << "中等" << std::endl;} else if (score >= 60) {std::cout << "及格" << std::endl;} else {std::cout << "不及格" << std::endl;}return 0;
}

注意事项

  • 条件表达式必须用圆括号()括起来。

  • 条件表达式的结果必须是布尔值(truefalse)。

  • 虽然C++允许省略圆括号,但为了提高可读性,建议总是使用圆括号。

  • 实现多重分支–在分支语句中,再继续使用if-else分支语句

  • else 会与最近的 if 匹配(如果没有使用大括号的话)

    #include <iostream>int main()
    {int grade = 65;if (grade > 60)if (grade > 80)std::cout << "Excellent\n";elsestd::cout << "Bad\n";
    }//输出结果为Bad,但想要的是什么都不输出
    

    使用大括号改变匹配规则后,不能与大括号外面的else语句形成匹配

    #include <iostream>int main()
    {int grade = 65;if (grade > 60) {if (grade > 80)std::cout << "Excellent\n";}elsestd::cout << "Bad\n";
    }
    //不输出任何东西
    
  • 使用大括号改变匹配规则

if V.S. constexpr if–C++17引入constexpr if

  • constexpr if的条件是常量表达式
  • constexpr是在编译期确定的,因此,在编译期就可以确定执行哪个分支,屏蔽掉其他分支(为编译器优化引入更多的可能性)
#include <iostream>int main()
{constexpr int grade = 65;//在编译期就可以优化成如下// if constexpr (grade > 70) {//     if (grade > 80)//         std::cout << "Excellent\n";// }// elsestd::cout << "Bad\n";
}

带初始化语句的 if:(从C++17开始)

  从C++17开始可以将初始化语句放入括号里面。如果希望变量只是为了if分支语句而引入的,将可以使用带初始化的if语句。

#include <iostream>int main()
{int x = 3;if(int y = x +3; y > 100){} else {}int y = 4;
}

2.分支语句–switch

  switch语句的语法可参考switch语句。

switch语句的基本语法:

switch (expression) {case constant-expression1:// 当expression的值与constant-expression1相等时执行的代码break;case constant-expression2:// 当expression的值与constant-expression2相等时执行的代码break;// ...default:// 如果expression的值与所有case都不匹配时执行的代码break;
}
  • expression:可以是任意表达式,但表达式的值必须具有整型或枚举类型,或者可隐式转换到整型或枚举类型的类类型。可以包含初始化的语句(C++17起)

    switch ( 初始化语句 (可选) 条件 ) 语句		
    等价于
    {
    初始化语句
    switch ( 条件 ) 语句
    }
    
  • constant-expression:是与case标签相关联的常量表达式,它必须与switch表达式的类型兼容。

  • break:用于终止switch语句中当前case的执行,防止代码继续执行到下一个case。如果省略break,程序将执行当前case之后的所有case,直到遇到一个breakswitch语句结束,这称为“fall through”。

  • case/default 标签

    • case 后面跟常量表达式 , 用于匹配 switch 中的条件,匹配时执行后续的代码

    • 可以使用 break 跳出当前的 switch 执行

    • default 用于定义缺省情况下的逻辑

    • 在 case/default 中定义对象要加大括号

      在标签下不能直接定义对象,会有编译错误。在标签下定义对象默认对象的作用域是整个switch语句,如果该标签后面还有下一个标签,则在执行下一个标签时就会跳过对象的初始化。举例如下:

      #include <iostream>int main() {int x = 1;switch (x){case 1:int x = 0; // 初始化std::cout << x << '\n';break;default:// 编译错误:跳到 default: 会在尚未初始化 'x' 的情况下进入它的作用域std::cout << "default\n";break;}
      }
      

      定义对象要加大括号,将对象的生命周期由switch语句变为由大括号定义的域

      #include <iostream>int main() {int x = 1;switch (1){case 1:{int x = 0;std::cout << x << '\n';break;} // 'x' 的作用域在此结束default:std::cout << "default\n"; // 无错误break;}
      }
      
  • C++17标准引入了[[fallthrough]];属性,允许程序员明确指出两个case之间的意图是连续的

     #include <iostream>int main()
    {const int i = 2;switch (i){case 1:std::cout << "1";case 2:              // 从这个 case 标号开始执行std::cout << "2";case 3:std::cout << "3";[[fallthrough]];  C++17 属性,用以关闭对直落的警告case 5:std::cout << "45";break;           // 语句的顺序执行到此终止case 6:std::cout << "6";default:break;}
    }
    

switch语句的简单示例

#include <iostream>int main() {int day = 4;switch (day) {case 1:std::cout << "Monday" << std::endl;break;case 2:std::cout << "Tuesday" << std::endl;break;case 3:std::cout << "Wednesday" << std::endl;break;case 4:std::cout << "Thursday" << std::endl;break;case 5:std::cout << "Friday" << std::endl;break;default:std::cout << "Invalid day" << std::endl;}return 0;
}

switch与if比较

  • 分支描述能力较弱

  • 在一些情况下编译期能引入更好的优化(运行期节省时间)

    从数据结构算法来讲,if接近于线性,而switch可利用跳表或二分查找

三、循环语句

1.循环语句–while

  while语句的语法可查看while语句。while循环是一种基本的循环结构,它允许代码在给定的布尔条件为true时重复执行。

while循环的基本语法

while (condition) {// 循环体:只要条件为真,就执行这里的代码
}
  • condition是任何能转换为bool的表达式,或带花括号或等号初始化式的单个变量的声明。

    如果条件是T t = x 这样的声明,那么被声明的变量仅在循环体内处于作用域中,而且在每次重复中销毁并重新创建

    #include <iostream>int main()
    {// 带单语句的 while 循环int i = 0;while (i < 10)i++;std::cout << i << '\n';// 带复合语句的 while 循环int j = 2;while (j < 9){std::cout << j << ' ';j += 2;}std::cout << '\n';// 带声明条件的 while 循环char cstr[] = "Hello";int k = 0;while (char c = cstr[k++])std::cout << c;std::cout << '\n';
    }
    

    运行结果为:

    10
    2 4 6 8 
    Hello
    
  • 语句:任何语句,是循环体

  • 处理逻辑:

    1. 判断条件是否满足(是否为true),如果不满足则跳出循环
    2. 如果条件满足则执行循环体
    3. 执行完循环体后转向步骤 1
  • 注意:在 while 的条件部分不包含额外的初始化内容

下面是一个使用while循环的示例,该示例展示了如何使用while循环来计算从1到10的整数之和:

#include <iostream>int main() {int sum = 0;int i = 1;while (i <= 10) {sum += i;  // 将i加到sum上i++;       // 增加i的值}std::cout << "The sum of numbers from 1 to 10 is: " << sum << std::endl;return 0;
}

2.循环语句–do-while

  do-while循环语句的语法可查看do-while循环。C++中的do-while语句是一种后测试循环,这意味着循环体内的代码至少会执行一次,之后才会判断循环条件。如果条件为真,则再次执行循环体,这个过程会一直重复,直到条件为假。

do-while循环的基本语法如下

do {// 循环体:这段代码至少执行一次
} while (condition);
  • condition是任意能转换成bool的表达式。如果condition为true,则循环体再次执行。这个过程会一直重复,直到condition为false。

  • 处理逻辑

    1. 执行循环体
    2. 判断条件是否满足,如果不满足则跳出循环
    3. 如果条件满足则转向步骤 1
  • do-while循环常用于至少需要执行一次的场合,例如用户输入验证、至少一次的尝试等。

  • 注意结尾处要有分号,表示一条语句的结束

    do-while循环的一个特点是循环体后面的while部分有一个分号;,这与while循环不同,while循环的条件后面不应该有分号。

下面是一个使用do-while循环的示例,该示例展示了如何使用do-while循环来提示用户输入一个正数:

#include <iostream>int main() {int number;do {std::cout << "Please enter a positive number: ";std::cin >> number;if (number <= 0) {std::cout << "The number is not positive. Try again." << std::endl;}} while (number <= 0);  // 循环继续,直到输入的number大于0std::cout << "Thank you for entering a positive number: " << number << std::endl;return 0;
}

3.循环语句–for

  for循环语句的语法可查看for循环。C++中的for循环是一种基本的迭代结构,它允许代码在给定的条件下重复执行。for循环通常用于当你知道循环需要执行的确切次数时。

for循环的基本语法

for (initialization; condition; increment/decrement) {// 循环体:只要条件为真,就执行这里的代码
}
  • 初始化语句initialization:循环开始前的初始化步骤,通常用于声明和初始化循环控制变量。

  • 条件condition:在每次循环迭代开始前判断的布尔表达式。如果条件为真,则执行循环体。如果条件为假,则循环结束。

  • 迭代表达式increment/decrement:循环体执行完毕后执行的更新步骤,通常用于更新循环控制变量。这一步可以是增加(如i++)或减少(如i--)循环变量。

  • 处理逻辑

    1. 初始化语句会被首先执行(初始化语句声明与定义的变量的作用域是在for循环中)
    2. 条件部分会被执行,执行结果如果为 false ,则终止循环
    3. 否则执行循环体
    4. 循环体执行完后会对迭代表达式进行求值,之后转向 2
  • 在初始化语句中声明多个名字,只要它们可以使用相同的声明说明符序列(相同的基础类型)

    在变量生命与定义时,语法上允许声明多个变量,但是不建议这么做,因为容易引起歧义。如:int* p, q;,p是int指针,q是int型。

    #include <iostream>int main()
    {//初始化语句可以声明多个名字,只要它们可以使用相同的声明说明符序列for (int i = 0, *p = &i; i < 9; i += 2){std::cout << i << ':' << *p << ' ';}
    }
    
  • 初始化语句、条件、迭代表达式可以为空,可以省略初始化、条件或更新步骤中的任何一个

    #include <iostream>int main()
    {for (; i < 10; ) {// 循环体i++;  // 循环控制变量的更新在循环体内部进行}for ( ; ; );
    }
    

下面是一个使用for循环的示例,该示例展示了如何使用for循环来计算从1到10的整数之和:

#include <iostream>int main() {int sum = 0;for (int i = 1; i <= 10; i++) {sum += i;  // 将i加到sum上}std::cout << "The sum of numbers from 1 to 10 is: " << sum << std::endl;return 0;
}

for更多示例

#include <iostream>
#include <vector>int main()
{std::cout << "1) 典型的以单语句作为循环体的循环:\n";for (int i = 0; i < 10; ++i)std::cout << i << ' ';std::cout << "\n\n" "2) 初始化语句可以声明多个名字,\n""只要它们可以使用相同的声明说明符序列:\n";for (int i = 0, *p = &i; i < 9; i += 2)std::cout << i << ':' << *p << ' ';std::cout << "\n\n" "3) (循环)条件可以是声明:\n";char cstr[] = "Hello";for (int n = 0; char c = cstr[n]; ++n)std::cout << c;std::cout << "\n\n" "4) 初始化语句可以使用 auto 类型说明符:\n";std::vector<int> v = {3, 1, 4, 1, 5, 9};for (auto iter = v.begin(); iter != v.end(); ++iter)std::cout << *iter << ' ';//方便理解for循环的执行逻辑std::cout << "\n\n" "5) 初始化语句可以是表达式:\n";int n = 0;for (std::cout << "循环开始\n";std::cout << "循环测试\n";		//<<运算符的结果为std::cout,隐式转换为truestd::cout << "迭代 " << ++n << '\n'){if (n > 1)break;}std::cout << "\n" "6) 每次迭代时均会调用循环体中创建的对象的构造函数和析构函数:\n";struct S{S(int x, int y) { std::cout << "S::S(" << x << ", " << y << "); "; }~S() { std::cout << "S::~S()\n"; }};for (int i{0}, j{5}; i < j; ++i, --j)S s{i, j};std::cout << "\n" "7) 初始化语句可以使用结构化绑定:\n";long arr[]{1, 3, 7};for (auto [i, j, k] = arr; i + j < k; ++i)std::cout << i + j << ' ';std::cout << '\n';
}

运行结果:

1) 典型的以单语句作为循环体的循环:
0 1 2 3 4 5 6 7 8 9 2) 初始化语句可以声明多个名字,
只要它们可以使用相同的声明说明符序列:
0:0 2:2 4:4 6:6 8:8 3) (循环)条件可以是声明:
Hello4) 初始化语句可以使用 auto 类型说明符:
3 1 4 1 5 9 5) 初始化语句可以是表达式:
循环开始
循环测试
迭代 1
循环测试
迭代 2
循环测试6) 每次迭代时均会调用循环体中创建的对象的构造函数和析构函数:
S::S(0, 5); S::~S()
S::S(1, 4); S::~S()
S::S(2, 3); S::~S()7) 初始化语句可以使用结构化绑定:
4 5 6

4.循环语句–基于范围的for循环

  基于范围的for循环的语法可查看基于范围的for循环。for循环的一个变体是范围基for循环(C++11及以后版本引入),它允许你直接迭代容器(如数组、向量、字符串等)中的元素,而无需显式管理索引变量。

范围基for循环的语法

for (const auto& element : container) {// 循环体:对container中的每个元素执行操作
}
  • container可以是任何支持迭代的容器

  • element是容器中当前迭代到的元素

  • 本质:语法糖,编译器会自动转换为 for 循环的调用方式

    image-20240512182551654

  • 转换形式的衍化: C++11 / C++17 / C++20(编译器对范围表达式的推导)

    image-20240512183521741

  • 使用常量左值引用读元素;使用 万能引用修改元素

    常量左值引用读元素

    #include <iostream>
    #include <vector>int main()
    {std::vector<std::string> strs{"h","e","l","l","o"};for (const auto& str : strs) //不加const为非常量左值引用,可以修改元素,但并不安全std::cout << str << "\n";
    }
    

    万能引用修改元素

    #include <iostream>
    #include <vector>int main()
    {std::vector<std::string> strs{"h","e","l","l","o"};//万能引用修改元素for (auto&& str : strs)str += "-";for (const auto& str : strs)std::cout << str << "\n";
    }
    

5.break / continue语句

  在C++中,breakcontinue是控制循环流程的两个关键字,它们在循环结构(如forwhiledo-while循环)中起到关键作用。

break

  break关键字用于立即终止其所在的循环体。当break被执行时,它会跳出最内层的循环,并继续执行循环后面的代码。这适用于任何类型的循环结构。

for (int i = 0; i < 10; ++i) {if (i == 5) {break; // 当i等于5时,立即退出循环}std::cout << i << " ";
}
// 输出: 0 1 2 3 4

continue

  continue关键字则用于跳过当前循环的剩余部分,并立即开始下一次循环迭代。当continue被执行时,它会跳过循环体中剩余的代码,并根据循环条件继续执行下一次迭代。

for (int i = 0; i < 10; ++i) {if (i % 2 == 0) {continue; // 跳过偶数,只打印奇数}std::cout << i << " ";
}
// 输出: 1 3 5 7 9
  • breakcontinue都只影响最内层的循环。如果你在嵌套循环中使用它们,它们只控制最内层的循环结构。
  • 在使用breakcontinue时,应当小心谨慎,因为过度使用或不当使用可能会使代码逻辑变得难以理解和维护。
  • 在使用switch语句时,break也用于终止switch中的一个case,防止代码继续执行到下一个case(除非下一个case被明确地指定)。

四、语句的综合应用–达夫设备

  达夫设备(Duff’s Device)是一种在C/C++编程中提高循环效率的技巧,它利用了C语言中switch语句的“fal through”特性来实现循环展开,从而减少循环控制的开销。这种方法最早由Tom Duff在1983年提出,目的是为了优化循环,尤其是在循环体内执行的操作非常快速时,循环的测试条件会占用相当一部分时间。

  达夫设备的核心思想是减少循环迭代次数,通过预先计算循环的迭代次数并将循环体的一部分操作放入switch语句的case分支中,从而减少循环控制的开销。当循环次数不能被展开的固定次数整除时,达夫设备使用switch来处理剩余的迭代次数。

  • 达夫设备使用循环展开提升系统性能
  • 处理无法整除的情形
    • 额外增加一个循环语句
    • 将 switch 与循环结合

以下是达夫设备的一个典型示例,该示例展示了如何将数据从from数组复制到to数组:

register short *to, *from;
register count; //count不能整除
{register n = (count + 7) / 8;  /* 假设count > 0 */switch (count % 8) {case 0: do { *to = *from++;case 7:      *to = *from++;case 6:      *to = *from++;case 5:      *to = *from++;case 4:      *to = *from++;case 3:      *to = *from++;case 2:      *to = *from++;case 1:      *to = *from++;} while (--n > 0);}
}

在这个例子中,count表示需要复制的元素数量。循环被展开为8次迭代,因为这样可以减少循环控制的开销。switch语句根据count除以8的余数选择从哪个case开始执行,而case分支中的代码会“跌落”到下一个case,直到遇到do-while循环的结束条件。
  达夫设备虽然可以提高效率,但它牺牲了代码的可读性和可维护性。现代编译器通常能够自动进行循环展开优化,因此达夫设备在现代编程中较少使用。然而,了解这种技术对于理解编译器优化和程序性能优化仍然有其价值。

这篇关于C++基础与深度解析 | 语句 | 分支语句 | 循环语句 | 达夫设备的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/991094

相关文章

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

Android Mainline基础简介

《AndroidMainline基础简介》AndroidMainline是通过模块化更新Android核心组件的框架,可能提高安全性,本文给大家介绍AndroidMainline基础简介,感兴趣的朋... 目录关键要点什么是 android Mainline?Android Mainline 的工作原理关键

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

Java字符串处理全解析(String、StringBuilder与StringBuffer)

《Java字符串处理全解析(String、StringBuilder与StringBuffer)》:本文主要介绍Java字符串处理全解析(String、StringBuilder与StringBu... 目录Java字符串处理全解析:String、StringBuilder与StringBuffer一、St

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

C#中async await异步关键字用法和异步的底层原理全解析

《C#中asyncawait异步关键字用法和异步的底层原理全解析》:本文主要介绍C#中asyncawait异步关键字用法和异步的底层原理全解析,本文给大家介绍的非常详细,对大家的学习或工作具有一... 目录C#异步编程一、异步编程基础二、异步方法的工作原理三、代码示例四、编译后的底层实现五、总结C#异步编程

C++中初始化二维数组的几种常见方法

《C++中初始化二维数组的几种常见方法》本文详细介绍了在C++中初始化二维数组的不同方式,包括静态初始化、循环、全部为零、部分初始化、std::array和std::vector,以及std::vec... 目录1. 静态初始化2. 使用循环初始化3. 全部初始化为零4. 部分初始化5. 使用 std::a

MySQL 中的 LIMIT 语句及基本用法

《MySQL中的LIMIT语句及基本用法》LIMIT语句用于限制查询返回的行数,常用于分页查询或取部分数据,提高查询效率,:本文主要介绍MySQL中的LIMIT语句,需要的朋友可以参考下... 目录mysql 中的 LIMIT 语句1. LIMIT 语法2. LIMIT 基本用法(1) 获取前 N 行数据(

MySQL中动态生成SQL语句去掉所有字段的空格的操作方法

《MySQL中动态生成SQL语句去掉所有字段的空格的操作方法》在数据库管理过程中,我们常常会遇到需要对表中字段进行清洗和整理的情况,本文将详细介绍如何在MySQL中动态生成SQL语句来去掉所有字段的空... 目录在mysql中动态生成SQL语句去掉所有字段的空格准备工作原理分析动态生成SQL语句在MySQL