C++ primer plus学习笔记 --- 第5章、循环和关系表达式

 5.1、for循环

主要内容

  • for 循环
  • 表达式和语句
  • 自增和自减操作符:++ 和 –
  • 组合赋值操作符
  • 复合语句(代码块)
  • 逗号操作符
  • 关系操作符:>、>=、==、<=、< 和 !=
  • while 循环
  • typedef 关键字的用法
  • do while 循环
  • get() 字符输入方法
  • 文件结束条件
  • 嵌套循环和二维数组

5.1.1、for循环的组成部分

在 C++ 中,for循环语句用来重复执行一段代码,语法结构:

for (initialization; condition; step) {
    statement(s);
}

for 循环可以省略 initializationcondition 或 step 部分。省略condition 部分,条件默认为 true,无限循环。但是遇到break或return会强制退出。

省略所有三个部分,相当于定义了一个无限循环

5.1.2、回到for循环

在for循环中,可以使用continue语句跳过当前一次循环,进入下一次循环;使用break语句退出循环。另外,在嵌套的for循环中,可以使用goto语句跳转到指定位置,实现跳出多重循环的效果。以下是具体的介绍:

1、continue语句

continue可以跳过for循环剩余可迭代次数,并立即开始下一次迭代。

使用continue语句跳过3和6,只输出奇数:

for (int i = 1; i <= 7; ++i) {
    if (i % 2 == 0) {
        continue;
    }
    std::cout << i << " ";
}

输出结果为:

1 3 5 7

在上述代码中,使用if语句判断i是否为偶数,如果是则使用continue语句跳过。因此,当i为偶数时,不会输出任何内容,而是直接开始下一次循环。

2、break语句

使用break语句可以完全退出for循环。

例如,以下代码使用break语句在i等于4时退出循环:

for (int i = 1; i <= 7; ++i) {
    if (i == 4) {
        break;
    }
    std::cout << i << " ";
}

输出结果为:

1 2 3

在上述代码中,当i等于4时,使用break语句退出循环,因此后面的数值不会输出。

3、goto语句

goto语句可以无条件跳转到指定标签,执行后面的指令。使用goto语句在i等于3时跳转到标签label处:

for (int i = 1; i <= 7; ++i) {
    if (i == 3) {
        goto label;
    }
    std::cout << i << " ";
}
label:
    std::cout << "jump out of loop.";

输出结果为:

1 2 jump out of loop.

i等于3时,使用goto label语句跳转到标签label处,输出"jump out of loop."。因此,在实际应用中,使用goto语句容易使代码结构复杂,不建议频繁使用。

5.1.3、修改步长

在for循环中,可以通过修改循环变量的步长来改变循环的增量或减量。:

1、修改循环变量的步长为2,实现从1到10的偶数输出:

for (int i = 2; i <= 10; i += 2) {
    std::cout << i << " ";
}

输出结果为:

2 4 6 8 10

在上述代码中,将循环变量i的初始值设为2,每次循环增加2,即步长为2,所以循环体内只输出了偶数。

2、修改循环变量的步长为负数,实现从10到1的输出:

for (int i = 10; i >= 1; i--) {
    std::cout << i << " ";
}

输出结果为:

10 9 8 7 6 5 4 3 2 1

在上述代码中,将循环变量的步长设为-1,所以循环体内每次输出从10到1的数据,根据for语句的规则,每次循环结束后i--进行更新,并在循环条件为false时跳出循环。

5.1.4、使用for循环访问字符串

在C++中,字符串是由字符数组来存储的,因此可以使用for循环遍历字符串中的每一个字符。具体实现如下:

#include <iostream>
#include <string>

int main() {
    std::string str = "hello world";
    for (int i = 0; i < str.size(); ++i) {
        std::cout << str[i] << " ";
    }
    return 0;
}

输出结果为:

h e l l o   w o r l d

在上述代码中,使用std::string类定义一个字符串变量str,使用str.size()获取字符串的长度,并通过for循环遍历字符串中的每一个字符。每次循环时,使用str[i]访问字符串中的第i个字符,并在每个字符后面输出一个空格。

需要注意的是,str.size()返回的数据类型是std::size_t,即无符号整型,因此在比较大小时,应该使用无符号整型来避免数据类型不匹配的错误。若希望对字符串进行修改,也可以使用上述方法,但要使用引用类型来修改字符串。例如:

std::string str = "hello";
for (auto& c : str) {
    c = std::toupper(c); // 将字符转换为大写字母
}
std::cout << str; // 输出HELLO

使用for循环遍历字符串中的每一个字符,并通过引用类型auto&修改每一个字符的值。使用std::toupper()函数将每个字符转换为大写字母,并最终输出修改后的字符串。

备注:

在这段代码中,使用了C++11后引入的一种for循环形式,即range-based for循环。它的语法结构为:

for ( declaration : expression ) statement;  // declaration 声明表达式,expression 实际的表达式,statement 循环体

其中,expression通常是一个容器或数组,而declaration则定义一个变量,用于逐一访问expression中的元素。在这个for循环中,每次循环迭代,expression中的下一个元素将被赋值给declaration中定义的变量。process_code 语句执行,直到所有元素都被访问。

对于本例中的代码,首先定义了一个string类型的变量str并将其初始化为"hello"。之后,使用range-based for循环来遍历这个字符串中的每一个字符。在循环体内部,使用引用类型auto&声明了一个变量c,将其指向当前循环所访问的字符。然后使用toupper函数将字符转换为大写形式,最后将大写形式的字符重新赋值给原字符串中的该位置。

因此,这段代码的执行结果是将字符串中的所有字母大写化后输出,即输出"HELLO"。

5.1.5、递增运算符(++)和递减运算符(--)

递增运算符(++)和递减运算符(–)是常用的运算符,它们可以对数值类型、指针类型、迭代器进行自增或自减操作。

1、递增运算符
递增运算符(++)可以将数值类型、指针类型或迭代器的值加1,并返回自增后的值。

下面是数值类型的例子:

int x = 1;
x++;
std::cout << x; // 输出2

下面是指针类型和迭代器类型的例子:

int arr[] = {1, 2, 3};
int* ptr = arr;
std::cout << *ptr << " "; // 输出1
ptr++;
std::cout << *ptr << " "; // 输出2

在上面的例子中,定义了一个整型数组arr和一个指向arr的指针ptr。使用*ptr可以访问该指针指向的元素,使用ptr++可以将指针指向下一个元素。

2、递减运算符
递减运算符(–)可以将数值类型、指针类型或迭代器的值减1,并返回自减后的值。

下面是数值类型的例子:

int x = 2;
x--;
std::cout << x; // 输出1

下面是指针类型和迭代器类型的例子:

int arr[] = {1, 2, 3};
int* ptr = arr + 2; // 指向最后一个元素
std::cout << *ptr << " "; // 输出3
ptr--;
std::cout << *ptr << " "; // 输出2

在上面的例子中,定义了一个整型数组arr和一个指向arr最后一个元素的指针ptr。使用*ptr可以访问该指针指向的元素,使用ptr--可以将指针指向上一个元素。

需要注意的是,递增运算符和递减运算符可以作为前缀(prefix)或后缀(suffix)使用,这将影响运算符的优先级和运算顺序。例如,++i表示将i加1后,返回自增后的值;i++表示返回i的原始值,然后将i加1。

5.1.6、副作用和顺序点

副作用指对变量进行的操作,会影响到该变量在之后的代码中的值。例如,赋值操作将新的值赋给变量后,该变量的值就发生了改变。递增和递减运算符也有副作用,因为它们会改变变量的值。

顺序点是指在程序执行过程中,保证某些操作的顺序。例如,赋值运算符的两个操作数之间就有一个顺序点,可以保证左侧的操作数先于右侧的操作数执行。由于递增和递减运算符也有副作用,所以它们也需要顺序点来保证正确的执行顺序。

下面是一个简单的例子,说明递增运算符中顺序点的作用:

int x = 1;
std::cout << x++ << " "; // 输出1
std::cout << ++x << " "; // 输出3

在上面的例子中,使用递增运算符对x进行自增操作,并通过std::cout输出自增后的值。第一行使用后缀形式的递增运算符x++,输出的结果为1。在这种情况下,递增运算符返回的是变量的原始值,但同时会修改变量的值。因此,在第二行使用前缀形式的递增运算符++x,此时x的值已经变成2了,再对其进行自增操作,输出的结果为3。

正确理解顺序点和副作用的概念,可以帮助我们更好地编写C++代码,并避免一些不可预测的行为。

5.1.7、前缀格式和后缀格式

前缀格式和后缀格式是递增和递减运算符的两种常见形式。

前缀格式是指在运算符前面加上一个++--,表示先将变量的值加1或减1,然后再对变量进行操作。例如:

int a = 1;
int b = ++a; // 先将a加1,再把结果赋给b
std::cout << a << " " << b; // 输出2 2

在上面的例子中,先使用前缀格式的递增运算符将a加1,然后将新的值(即2)赋给b,输出结果为2 2。

后缀格式是指在运算符后面加上一个++--,表示先对变量进行操作,然后再将变量的值加1或减1。例如:

int a = 1;
int b = a++; // 先将a的值赋给b,再将a加1
std::cout << a << " " << b; // 输出2 1

在上面的例子中,使用后缀格式的递增运算符先将a的值(即1)赋给b,然后再将a的值加1,输出结果为2 1。

需要注意的是,虽然前缀格式和后缀格式都是递增或递减运算符,但它们的执行顺序是不同的。在同一个表达式中,前缀格式的运算符会先于表达式中的其他运算进行求值,而后缀格式的运算符则会在表达式中的其他运算之后求值。因此,在使用递增或递减运算符时,应该根据具体需求选择合适的格式。

5.1.8、递增/递减运算符和指针

递增运算符和递减运算符也可以用于指针类型。对于指针类型变量ptr,使用前缀递增运算符++ptr可以将指针指向下一个相邻的内存单元,使用前缀递减运算符--ptr可以将指针指向上一个相邻的内存单元。

下面是一个简单的例子,展示如何使用递增/递减运算符操作指针类型变量:

int arr[] = {1, 2, 3};
int* ptr = arr; // 指向arr的第一个元素
std::cout << *ptr << " "; // 输出1
ptr++; // 指向arr的第二个元素
std::cout << *ptr << " "; // 输出2
ptr--; // 再次指向arr的第一个元素
std::cout << *ptr << " "; // 输出1

定义了一个整型数组arr和一个指针类型变量ptr,并让指针ptr指向数组的第一个元素。通过*ptr可以访问该指针指向的元素,然后使用递增和递减运算符操作指针ptr,让其分别指向下一个和上一个相邻的内存单元,并输出指针指向的元素的值。

需要注意的是,对于指针类型,递增/递减运算符的“加”和“减”的大小为指针所指向数据类型的大小。例如,对于整型指针,递增/递减运算符的“加”和“减”都是4,因为int的大小为4字节;对于字符指针,递增/递减运算符的“加”和“减”都是1,因为字符的大小为1字节。

5.1.9、组合赋值运算符

组合赋值运算符简化了语法 ,将赋值运算符和算术运算符组合在一起。例如+=运算符可以表示将变量的值加上一个常量,并将改值重新赋值给该变量。

例子:

int a = 1;
a += 5; // 等价于 a = a + 5;
std::cout << a << std::endl; // 输出6

int b = 10;
b -= 3; // 等价于 b = b - 3;
std::cout << b << std::endl; // 输出7

int c = 2;
c *= 4; // 等价于 c = c * 4;
std::cout << c << std::endl; // 输出8

int d = 10;
d /= 3; // 等价于 d = d / 3;
std::cout << d << std::endl; // 输出3

int e = 5;
e %= 2; // 等价于 e = e % 2;
std::cout << e << std::endl; // 输出1

在上面的例子中,分别使用了加等于(+=)、减等于(-=)、乘等于(*=)、除等于(/=)、取模等于(%=)等组合赋值运算符,简化了代码的书写。

需要注意的是,组合赋值运算符的优先级比普通赋值运算符低,但比算术运算符高。也就是说,如果表达式中既有组合赋值运算符,又有算术运算符,则组合赋值运算符会先执行。例如,a = b + (a += 5)会先执行a += 5,再执行b + a,最后将结果赋值给a

5.1.10、复合语句(语句块)

复合语句指用花括号{}括起来的一组语句。语句块中的语句被视为一个整体,可以作为单个语句在程序中使用。在C++程序中,语句块用于控制语句的作用范围、限制变量的生命周期和作用范围、以及代码的结构化、清晰。

下面是一个简单的例子,展示如何使用语句块限制变量的作用域:

#include <iostream>

int main() {
  int a = 1;

  std::cout << "Before block, a = " << a << std::endl;

  {
    int a = 2; // 声明一个新的变量a,限制其作用范围在语句块内
    std::cout << "Inside block, a = " << a << std::endl;
  }

  std::cout << "After block, a = " << a << std::endl;
  
  return 0;
}

在上面的例子中,我们在函数main()中定义了一个整型变量a,并初始化为1。接着,使用花括号{}定义一个语句块,在语句块内部定义一个新的变量a,初始化为2,限制其作用范围在语句块内部。在语句块内部,输出新变量a的值,然后在语句块外部再次输出原始变量a的值。由于语句块内部的变量a和语句块外部的变量a不是同一个变量,因此输出结果为:

Before block, a = 1
Inside block, a = 2
After block, a = 1

需要注意的是,语句块中定义的变量的作用域仅限于该语句块内。当程序执行完该语句块后,语句块内部定义的变量就会被销毁。另外,语句块中的语句可以包含任意数量的语句,可以嵌套多层语句块,可以将语句块作为单个语句在程序中使用。

5.1.11、其他语法技巧 --- 逗号运算符

逗号运算符,用于链接多个表达式,将它们看做一个整体,在程序中作为一个语句单元出现,也可以用于在函数调用、初始化列表中分隔多个参数。

下面是逗号运算符的几个常见用途:

1、将多个表达式作为一个整体在程序中执行。例如:

int a = 1, b = 2, c = 3;
int d = (a++, b++, c++); // 逗号运算符将三个递增表达式链接起来
std::cout << d << std::endl; // 输出3

使用逗号运算符将三个递增表达式链接起来,将它们看做一个整体,在程序中作为一个语句单元出现。在执行完最后一个表达式c++后,变量d的值为3。

2、在函数调用、初始化列表中分隔多个参数。例如:

void func(int a, int b, int c) {
  std::cout << a << " " << b << " " << c << std::endl;
}

int main() {
  int a = 1, b = 2, c = 3;
  func(a, (a++, b++, c++), c); // 使用逗号运算符分隔三个参数
  return 0;
}

在上面的例子中,调用函数func时,使用逗号运算符分隔三个参数,其中第二个参数是一个逗号运算符表达式,将三个递增表达式链接在一起。在执行完整个表达式后,变量a的值为2,变量b的值为3,变量c的值为4。

需要注意的是,在程序中使用逗号运算符时,应当注意运算符的优先级。逗号运算符的优先级是最低的,因此在表达式中使用逗号运算符时,应当使用小括号明确运算的优先级和顺序。

5.1.12、关系表达式

关系表达式是指一类二元运算表达式,用于比较两个值的大小关系。C++中常用的关系运算符有六个:大于(>)、小于(<)、大于等于(>=)、小于等于(<=)、等于(==)、不等于(!=)。这些关系表达式的结果为布尔值,即truefalse

下面是使用关系运算符的一些例子:

int a = 10, b = 5;

std::cout << (a > b) << std::endl; // 输出1,即true
std::cout << (a < b) << std::endl; // 输出0,即false
std::cout << (a >= b) << std::endl; // 输出1,即true
std::cout << (a <= b) << std::endl; // 输出0,即false
std::cout << (a == b) << std::endl; // 输出0,即false
std::cout << (a != b) << std::endl; // 输出1,即true

可以使用关系表达式来控制程序的流程,例如,可以将关系表达式的结果作为条件,结合if语句、while语句等控制语句,实现程序的分支和循环功能。

需要注意的是,关系表达式的结果是一个布尔类型的值,即非0表示true,0表示false。在程序中,可以使用truefalse关键字表示真和假。在使用关系表达式时,应当注意数据类型的匹配和数据精度等问题。如果比较的两个操作数的数据类型不一致,则需要进行类型转换后再进行比较。此外,还需要注意使用浮点数进行比较可能存在精度问题。

5.1.13、赋值、比较和可能犯的错误

赋值运算符、关系运算符是C++中常见的运算符之一,但是使用时也容易出现一些错误。接下来列举一些可能犯的错误并给出相应的注意事项:

1、将赋值运算符(=)误写成比较运算符(==)。这种错误很常见,导致程序逻辑出现错误。

2、混用不同类型的变量进行比较。在C++中,不同类型的变量之间是不能直接进行比较的,需要进行类型转换。

3、对浮点数进行相等比较。由于浮点数在计算机内部表示时存在精度问题,因此对浮点数进行相等比较可能会出现误差。在进行浮点数的比较时,应当使用一个很小的数作为比较的精度阈值。

4、连续使用多个赋值运算符(=)。在C++中,连续使用多个赋值运算符是一种非法的行为,并且它没有任何实际意义。例如,a = b = c = 0是错误的。

5、在比较运算符的两侧使用不同的数据类型。C++中使用不同类型的数据进行比较时,需要进行类型转换。如果类型无法匹配,则会通过隐式类型转换将其中一个操作数转换为另一个操作数的类型。

6、不注意运算符优先级。在C++中,不同的运算符具有不同的优先级,有时候可能会导致预期之外的结果,因此在使用这些运算符时需要非常注意它们的优先级。

7、没有使用大括号来隔离语句块。在C++中,使用大括号可以将多条语句组成一个语句块,它可以作为一个整体来使用。如果没有使用大括号,则可能会导致程序出现意外的错误。

在编写C++程序时,应该尽量避免这些错误,并且为了保持代码的可读性和可维护性,在编写代码时应该注重代码格式,遵守一些良好的编码习惯。

5.1.14、C风格字符串比较

C风格字符串比较是指对两个以null为结尾的字符数组进行比较。C++标准库提供了许多用于字符串操作的函数,例如std::string类和各种算法,但是在一些场景下,仍然需要使用C风格字符串进行比较。

C风格字符串比较可以使用标准库中的strcmp()函数,该函数接受两个参数,分别是要比较的两个字符串。函数返回一个整型值,表示比较结果。如果两个字符串相等,则返回0;如果第一个字符串小于第二个字符串,则返回负整数;如果第一个字符串大于第二个字符串,则返回正整数。

下面是一个使用strcmp()函数比较两个字符串的例子:

#include <iostream>
#include <cstring>

int main() {
  char str1[] = "hello";
  char str2[] = "world";
  
  if (std::strcmp(str1, str2) == 0) {
    std::cout << "str1 and str2 are equal." << std::endl;
  } else if (std::strcmp(str1, str2) < 0) {
    std::cout << "str1 is less than str2." << std::endl;
  } else {
    std::cout << "str1 is greater than str2." << std::endl;
  }
  
  return 0;
}

在上面的例子中,定义了两个字符数组str1str2,分别初始化为helloworld。使用std::strcmp()函数比较这两个字符串,根据返回值,输出比较结果。由于str1小于str2,因此输出结果为str1 is less than str2.

需要注意的是,当使用strcmp()函数比较两个字符串时,应当确保这两个字符串都以null字符结尾,并且它们的长度相同。如果两个字符串长度不同,则可能会出现越界访问的错误。此外,在进行字符串比较时,应当了解字符数组的特点,并且避免出现一些常见的错误,例如忘记在字符数组末尾添加null字符。

5.1.15、比较string类字符串

C风格字符串比较是指对两个以null为结尾的字符数组进行比较。C++标准库提供了许多用于字符串操作的函数,例如std::string类和各种算法,但是在一些场景下,仍然需要使用C风格字符串进行比较。

C风格字符串比较可以使用标准库中的strcmp()函数,该函数接受两个参数,分别是要比较的两个字符串。函数返回一个整型值,表示比较结果。如果两个字符串相等,则返回0;如果第一个字符串小于第二个字符串,则返回负整数;如果第一个字符串大于第二个字符串,则返回正整数。

下面是一个使用strcmp()函数比较两个字符串的例子:

#include <iostream>
#include <cstring>

int main() {
  char str1[] = "hello";
  char str2[] = "world";
  
  if (std::strcmp(str1, str2) == 0) {
    std::cout << "str1 and str2 are equal." << std::endl;
  } else if (std::strcmp(str1, str2) < 0) {
    std::cout << "str1 is less than str2." << std::endl;
  } else {
    std::cout << "str1 is greater than str2." << std::endl;
  }
  
  return 0;
}

在上面的例子中,定义了两个字符数组str1str2,分别初始化为helloworld。使用std::strcmp()函数比较这两个字符串,根据返回值,输出比较结果。由于str1小于str2,因此输出结果为str1 is less than str2.

需要注意的是,当使用strcmp()函数比较两个字符串时,应当确保这两个字符串都以null字符结尾,并且它们的长度相同。如果两个字符串长度不同,则可能会出现越界访问的错误。此外,在进行字符串比较时,应当了解字符数组的特点,并且避免出现一些常见的错误,例如忘记在字符数组末尾添加null字符。

5.2、while循环

5.2.1、for与while

while循环和for循环是C++中两种常见的循环类型,它们都可以用于重复执行一些语句块,直到指定的条件不再满足。

for循环不同的是,while循环没有计数器的概念,它更加通用,适用于各种循环场景。while循环的语法格式如下:

while (condition) {
  // 循环体语句
}

其中,condition是一个表达式,表示循环条件。只有当conditiontrue时,while循环才会执行循环体中的语句。如果conditionfalse,则直接跳过循环体,执行循环后面的语句。

需要注意的是,循环体中应当包含能够改变循环条件的语句,例如循环计数器的自增或自减。如果循环体中没有这样的语句,循环条件一旦满足,循环就变成了死循环,程序将陷入无限循环状态,直到强行中止程序。

下面是一个使用while循环计算自然数和的例子:

#include <iostream>

int main() {
  int sum = 0;
  int i = 1;
  
  while (i <= 100) {
    sum += i;
    ++i;
  }
  
  std::cout << "The sum of 1 to 100 is: " << sum << std::endl;
  
  return 0;
}

在上面的例子中,使用while循环计算了1到100这些自然数的和。循环变量i从1开始,循环条件为i <= 100,每次循环体执行完后将i自增,最终计算出所有自然数的和并输出。

5.2.2、等待一段时间:编写演示循环

要等待一段时间可以使用C++标准库中的<chrono>头文件,该头文件中提供了一些与时间相关的类和函数。其中,std::chrono::milliseconds表示毫秒,可以用来指定需要等待的时间。使用std::this_thread::sleep_for()函数可以让当前线程等待指定的时间。

下面是一个使用while循环等待一段时间的例子:

#include <iostream>
#include <chrono>
#include <thread>

int main() {
  std::cout << "Starting countdown..." << std::endl;
  
  int count = 10;
  while (count > 0) {
    std::cout << count << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    --count;
  }
  
  std::cout << "Blast off!" << std::endl;
  
  return 0;
}

在上面的例子中,使用while循环实现了从10开始的倒计时,并在每次循环体中输出当前的倒计时数字。使用std::this_thread::sleep_for()函数让程序等待1秒钟,以实现每秒钟输出一次倒计时数字的效果。循环体中的代码被执行了10次后,最终输出Blast off!表示倒计时结束。

需要注意的是,std::this_thread::sleep_for()函数可能会抛出一些异常,因此在实际使用时需要进行异常处理。同时,在使用while循环等待一段时间时,循环体中也应当包含能够终止循环的条件,例如用户输入某个终止指令。否则,程序也可能会陷入无限循环状态。

5.3、do while循环

do while循环是一种与while循环类似的循环结构,它在判断循环条件之前首先执行一次循环体中的语句,然后根据指定的条件是否满足判断是否继续执行循环。相比之下,while循环在进入循环之前会判断循环条件是否满足。

do while循环的语法格式如下:

do {
  // 循环体语句
} while (condition);

其中,condition是一个表达式,表示循环条件。首先执行循环体中的语句,然后根据condition的值决定是否继续执行循环体。如果conditiontrue,则继续执行循环体;否则,跳出循环,执行循环后面的语句。

需要注意的是,与while循环类似,do while循环中的循环体应当包含能够改变循环条件的语句,以确保循环能够正常地退出。如果循环条件一开始就不满足,循环体中的语句也会被执行一次,但不会再次进行循环。

下面是一个使用do while循环计算自然数和的例子:

#include <iostream>

int main() {
  int sum = 0;
  int i = 1;
  
  do {
    sum += i;
    ++i;
  } while (i <= 100);
  
  std::cout << "The sum of 1 to 100 is: " << sum << std::endl;
  
  return 0;
}

在上面的例子中,使用do while循环计算了1到100这些自然数的和。循环变量i从1开始,循环条件为i <= 100,每次循环体执行完后将i自增,最终计算出所有自然数的和并输出。因为循环条件一开始就满足,所以循环体中的语句会被执行一次。

5.4、基于范围类的for循环(C++11)

 基于范围类的for循环(range-based for loop)是C++11新增的一种循环方式,可以用来遍历某个范围内的元素。它适用于所有支持C++11新特性的容器、元组、数组等数据结构。

这种循环的语法形式如下:

for (element : range) {
  // 循环体语句
}

其中,element表示容器中的元素,也可以是数组的元素、或者是元组的成员等等;range是可以被遍历的范围,比如STL中的容器、数组的名字等等。循环开始时,对于range中的每个元素,都会执行一次循环体中的语句,而且可以使用element来访问遍历的元素。

下面是一个基于链表遍历的例子:

#include <iostream>
#include <list>

int main() {
  std::list<int> lst = {1, 2, 3, 4, 5};
  
  for (int i : lst) {
    std::cout << i << " ";
  }
  std::cout << std::endl;
  
  return 0;
}

在上面的例子中,使用C++标准库中的list来存储一些整数,然后使用基于范围类的for循环遍历整个链表,输出链表中的每个元素。

需要注意的是,在使用基于范围类的for循环时,range必须是一个支持范围遍历的数据结构,比如容器、数组、元组等,否则程序会报错。另外,在循环过程中,不允许修改range中的元素,否则会产生不可预料的结果。如果需要修改数据结构中的元素,可以使用常规的for循环来实现。

5.5、循环和文本输入

要读取文件内容,可以使用C++标准库中提供的文件输入流(ifstream)类。具体步骤如下:

  1. 包含文件输入流所在的头文件 #include <fstream>
  2. 创建一个文件输入对象,调用它的构造函数并传入文件路径名参数。
  3. 判断文件的打开是否成功,如果失败,需要进行错误处理。
  4. 使用文件输入对象读取文件内容,可以使用 >> 运算符连续读取文件中的内容。
  5. 关闭文件输入对象,释放资源。

下面是一个简单的例子,演示了如何读取文件中的内容:

#include <iostream>
#include <fstream>

int main() {
  // 创建文件输入对象并打开文件
  std::ifstream infile("input.txt");
  
  // 判断文件是否成功打开
  if (!infile.is_open()) {
    std::cerr << "Failed to open file." << std::endl;
    return 1;
  }
  
  // 读取文件中的内容
  int num;
  while (infile >> num) {
    std::cout << num << " ";
  }
  
  // 关闭文件输入对象
  infile.close();
  
  return 0;
}

在上面的代码中,先创建了一个文件输入对象 infile,并通过构造函数传入文件路径名参数,这里假设文件名为 input.txt。然后使用 is_open() 函数判断文件是否成功打开。如果打开失败,通过 cerr 输出错误信息并返回 1 表示程序执行失败。如果打开成功,就通过 >> 运算符连续读取文件中的内 容,并输出到标准输出流中。最后通过 close() 函数关闭文件输入对象,释放资源。

需要注意的是,在读取文件时需要注意文件的编码方式。如果文件采用了非 ASCII 码的编码方式,例如 UTF-8,需要在打开文件输入对象时指定该编码方式。可以使用 infile.open("input.txt", std::ios_base::in | std::ios_base::binary); 来打开一个二进制文件进行操作。

5.5.1、使用原始的cin进行输入

在使用 C++ 进行输入时,最常用的方式是使用 std::cin 标准输入流对象。下面是一个简单的输入示例:

#include <iostream>

int main() {
  int num;
  std::cout << "Please enter an integer: ";
  std::cin >> num;
  std::cout << "You entered: " << num << std::endl;
  return 0;
}

在上面的代码中,通过 std::cin 输入一个整数,并将用户输入的值存储到变量 num 中,然后通过 std::cout 输出所输入的整数。

需要注意的是,使用 std::cin 输入时,用户必须按下回车键才能结束输入。如果用户输入的内容超过了变量类型所能表示的范围,或者输入的内容无法转换为变量所需的类型,那么将会导致输入的失败。

此外,如果在输入时出现了流错误(如遇到了文件结尾或者输入格式错误),可以调用 std::cin 的 clear() 函数来清除错误标志并继续输入,例如:

#include <iostream>

int main() {
  int num;
  std::cout << "Please enter an integer: ";
  while (!(std::cin >> num)) {
    std::cout << "Input error! Please try again." << std::endl;
    std::cin.clear(); // 清除错误标志
    // 忽略缓冲区中的所有字符
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); 
  }
  std::cout << "You entered: " << num << std::endl;
  return 0;
}

在上面的代码中,如果输入失败,将输出一条错误信息,并清除失败的标志,在缓冲区中忽略了所有非整数的字符,然后重新进行输入。

5.5.2、使用cin.get(char)进行补救

除了使用 std::cin 输入数据外,还可以使用 std::cin.get() 方法逐个读取字符,这种方式可以更加灵活地处理输入数据。

例如,如果需要读取一行字符串,但又不知道字符串的长度,可以使用 std::cin.get() 逐个读取字符,直到遇到换行符为止。下面是一个简单的例子:

#include <iostream>

int main() {
  char str[100]; // 字符串缓冲区
  std::cout << "Please enter a string: ";
  std::cin.get(str, 100, '\n'); // 读取字符串,遇到换行符(\n)结束
  
  std::cout << "You entered: " << str << std::endl;
  return 0;
}

在上面的代码中,先定义一个长度为 100 的字符缓冲区 str。然后使用 std::cin.get() 方法,从标准输入流中一次读取一个字符,将其存储到缓冲区中,如果读取到换行符则停止读取。最后,将读取到的字符串输出到标准输出流中。

需要注意的是,使用 std::cin.get() 方法时,需要指定要读取的最大字符数以及停止读取的字符。同时,也需要处理输入错误和缓冲区清空的情况。

5.5.3、使用哪一个cin.get()

在 C++ 中,std::cin.get() 这个方法有多个重载版本,可以根据实际需求选择合适的重载版本。

以下是常用的 std::cin.get() 重载版本:

  1. get(char& ch):从标准输入流中读取并返回一个字符,存储在 ch 变量中。不会忽略空格和换行符。

  2. get(char* str, std::streamsize count, char delim):从标准输入流中读取 count-1 个字符(最多读取 count-1 个字符),把它们存储在 str 中,直到遇到指定的定界符 delim。函数会在读取完 count-1 个字符后结束,添加 ‘\0’ 到 str 的末尾,以便将其转换成 C 风格的字符串。不会忽略空格和换行符。

  3. get():从标准输入流中读取并返回一个字符,不存储在变量中。也不会忽略空格和换行符。

  4. getline(char* str, std::streamsize count):从标准输入流中读取字符,直到遇到换行符或文件结尾或读取了 count-1 个字符为止,把这些字符存储在 null-terminated 的数组 str 中。会忽略换行符。

  5. getline(char* str, std::streamsize count, char delim)从标准输入流中读取字符,直到遇到指定的定界符 delim,或读取了 count-1 个字符为止,把这些字符存储在 null-terminated 的数组 str 中。会忽略定界符 delim。

需要根据实际需求,选择合适的 std::cin.get() 重载版本。默认情况下,std::cin.get() 会读取并保留空白符(包括空格、制表符和换行符)。如果需要忽略空白符可以使用该方法的重载版本。

5.5.4、文件尾条件

在 C++ 中,可以使用 std::fstream 操作文件,并通过判断文件尾条件来实现文件的遍历和读取。当读取文件到达文件的末尾时,std::fstream 对象会返回一个错误标志,用于表示文件读取到了结束位置。

通过 std::fstream 操作文件需要注意以下几点:

  1. 打开文件时,需要指定文件打开模式,使用 std::ios::in 表示以只读模式打开文件。
  2. 读取文件时,需要使用文件输入流对象 std::ifstream,并使用 eof() 函数判断是否到达文件尾。
  3. 在读取文件时,如果读取操作失败,需要使用 fail() 函数和 clear() 函数清除错误标志,以保证程序能够继续正常运行。

下面是一个简单的使用 std::fstream 操作文件的例子,演示了如何读取文件内容并判断是否到达了文件结尾:

#include <iostream>
#include <fstream>

int main() {
  std::ifstream infile("input.txt", std::ios::in);
  if (!infile) {
    std::cout << "Open file failed!" << std::endl;
    return -1;
  }

  std::string str;
  while (!infile.eof()) {
    std::getline(infile, str);
    if (!str.empty()) {
      std::cout << str << std::endl;
    }
  }

  infile.close();
  return 0;
}

在上面的代码中,先打开了一个文件输入流对象 infile,并指定了文件打开模式为只读模式。如果文件打开失败,输出一条错误信息并返回 -1。然后,使用一个 while 循环读取文件的每一行,如果当前行不为空,则将其输出到标准输出流中。在读取时,使用 std::getline() 函数读取一行,并通过 eof() 函数判断是否到达了文件尾。最后,关闭文件输入流对象,释放资源。

需要注意的是,在判断是否到达文件尾时,不能只使用 eof() 函数,因为在读取文件时如果出现错误(如遇到非法数据),也会将 eof() 函数返回值设置为 true,导致无法正确判断是否到达了文件尾。因此,需要根据实际情况同时判断 eof() 函数和读取结果是否为空才能正确判断文件是否读取到了结尾。

5.5.5、另一个cin.get()版本

除了上面提到的重载版本外,std::cin.get() 还有一种常见的用法是读取一行输入,然后将其存储在字符串对象中。这种形式的 std::cin.get() 使用形式如下:

#include <iostream>
#include <string>

int main() {
    std::string line;
    std::cout << "Please enter a line of text: ";
    std::getline(std::cin, line);

    std::cout << "You entered: " << line << std::endl;
    return 0;
}

在上面的代码中,通过 std::getline() 函数从 std::cin 中读取一行输入,将其存储在字符串对象 line 中。如果输入过程中遇到了换行符,则停止读取。最后将所读取的字符串输出到标准输出流中。

需要注意,使用 std::getline() 操作时,需要将第二个参数传入,以告诉 std::getline() 在哪个定界符处停止读取。如果不提供第二个参数,则默认定界符为换行符。另外,std::getline() 函数可以与任何输入流对象一起使用,而不仅仅是 std::cin

5.6、嵌套循环和二维数组

嵌套循环和二维数组是 C++ 程序中常见的概念。在使用二维数组时,嵌套循环很常见,因为需要使用两个循环来访问二维数组中的每个元素。

在 C++ 中,可以通过两种方式定义二维数组:静态定义和动态定义。静态定义的二维数组在程序编译时就已经分配了固定的内存空间,而动态定义的二维数组则在程序运行时动态分配内存。以下是示例代码:

// 静态定义 2x3 的二维数组
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};

// 动态定义 2x3 的二维数组
int **arr = new int*[2];
for (int i = 0; i < 2; ++i) {
  arr[i] = new int[3];
}

在使用二维数组时,往往需要使用嵌套循环来遍历每个元素。以下是一个简单的例子:

#include <iostream>

int main() {
  int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};

  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      std::cout << arr[i][j] << " ";
    }
    std::cout << std::endl;
  }

  return 0;
}

在上面的代码中,定义了一个 2x3 的二维整型数组 arr,使用嵌套循环遍历每个元素,并将其输出到标准输出流中。其中外部循环用于遍历每一行,内部循环用于遍历行中的每一列。

需要注意的是,使用嵌套循环时,内部循环的大小通常要与数组相应维度大小匹配。如果内部循环越界,可能会导致程序崩溃或未定义行为发生。

5.6.1、初始化二维数组

在 C++ 中,可以通过初始化列表的方式对二维数组进行初始化。以下是一个简单的例子:

int arr[2][3] = {
  {1, 2, 3},
  {4, 5, 6}
};

在上面的代码中,定义了一个 2x3 的二维整型数组 arr,使用初始化列表的方式对其进行了初始化。其中,大括号内的每一行表示对应行的初始化列表,大括号内的每一个数值表示数组元素的值。

需要注意,在进行初始化时,初始化列表中的元素数量必须与二维数组的大小相匹配,否则会导致编译错误。此外,如果没有提供初始化列表,则数组中的元素将默认初始化为对应类型的默认值(例如 int 类型的数组元素默认为 0)。

还可以使用嵌套循环来初始化二维数组,具体代码如下:

int arr[2][3];
for (int i = 0; i < 2; ++i) {
  for (int j = 0; j < 3; ++j) {
    arr[i][j] = i + j;
  }
}

在上面的代码中,使用两层嵌套循环遍历二维数组,并将数组元素的值设置为 i+j。需要注意,进行循环初始化时,需要确保数组的每个元素都被正确初始化,否则可能会导致未定义行为或者阻碍程序正常运行。

5.6.2、使用二维数组

使用二维数组时,可以通过索引访问数组中的元素。在 C++ 中,二维数组的索引由两个下标组成,第一个下标表示数组中的行数,第二个下标表示列数。例如,arr[i][j] 表示二维数组中第 i 行第 j 列的元素。

以下是一个使用二维数组的例子:

#include <iostream>

int main() {
  int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};

  std::cout << "arr[0][0] = " << arr[0][0] << std::endl; // 输出 arr[0][0] 的值
  arr[1][2] = 7; // 修改 arr[1][2] 的值
  std::cout << "arr[1][2] = " << arr[1][2] << std::endl; // 输出 arr[1][2] 的值

  return 0;
}

在上面的代码中,使用二维数组 arr 中第一个下标为 0,第二个下标为 1 访问数组元素,并将其输出到标准输出流中。然后,修改了 arr 中第二行第三列的元素值,并再次输出到标准输出流中。

需要注意的是,访问数组元素时,需要确保所引用的元素存在。如果超出了数组边界范围,则可能会导致程序崩溃或未定义行为发生。此外,在使用二维数组时,嵌套循环也是常见的操作方式,可以用来遍历数组中的每一个元素。

5.7、总结

本文介绍了 C++ 中循环结构的不同类型及其用法,包括 for 循环、while 循环、do-while 循环和范围-for 循环。其中 for 循环是最常见的循环结构,可以使用多种方式进行重载。while 循环和 do-while 循环则适用于需要根据特定条件重复执行的情况。范围-for 循环则是 C++11 引入的新特性,用于简化迭代器和指针的使用。

此外,本文还介绍了嵌套循环和二维数组的概念。使用二维数组时,常采用嵌套循环的方式遍历每个元素。二维数组可以静态或动态定义,并可以使用初始化列表或嵌套循环进行初始化。访问二维数组的元素时,需要确保索引不超出数组边界。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值