说明
看《C++ Primer Plus》时整理的学习笔记,部分内容完全摘抄自《C++ Primer Plus》(第6版)中文版,Stephen Prata 著,张海龙 袁国忠译,人民邮电出版社。只做学习记录用途。
- 说明
-
5.1 for 循环
- 5.1.1 for 循环格式
- 5.1.2 递增运算符(++)和递减运算符(--)
- 5.1.3 递增/递减运算符和解除引用运算符
- 5.1.4 组合赋值运算符
- 5.1.5 逗号运算符
- 5.1.6 关系表达式
- 5.1.7 字符串的比较
-
5.2 while 循环
- 5.2.1 while 循环格式
- 5.2.2 编写延时循环
- 5.2.3 类型别名
- 5.3 do while 循环
- 5.4 基于范围的 for 循环(C++11)
-
5.5 嵌套循环和二维数组
- 5.5.1 初始化二维数组
- 5.5.2 使用 new 创建动态二维数组
- 5.5.3 嵌套循环
本章介绍循环和关系表达式。
5.1 for 循环
for
循环是入口条件循环,也就是在每轮循环之前,都将计算测试表达式的值。
5.1.1 for 循环格式
for
循环的基本格式如下:
for (initialization; test-expression; update-expression)
{
statements;
}
initialization
在循环开始时被执行,且整个循环过程中只被执行一次,它可以使用任意表达式,通常在这一部分中声明并初始化变量,但这种变量只存在于for
语句中,当程序离开循环后,这种变量将消失。(对于部分老式实现,initialization
部分内声明的变量将被视为在循环之前声明的,因此在循环结束后仍可使用。)
test-expression
(测试表达式)决定循环体是否被执行,它也可以使用任意表达式,C++ 将把结果强制转换为 bool
类型,若值为false
,将导致循环结束,若值为true
,循环将继续进行,这一部分通常使用关系表达式。当省略测试表达式时,测试条件默认为true
。
update-expression
(更新表达式)在每轮循环结束时执行,它也可以使用任意表达式,但通常被用来对跟踪循环轮次的变量的值进行增减。
//以下循环将一直运行,除非在statements里跳出
for (;;)
{
statements;
}
5.1.2 递增运算符(++)和递减运算符(--)
递增运算符(++)和递减运算符(--)执行两种极其常见的循环操作:将循环计数加 1 或减 1。这两个运算符都有两种变体:前缀版本位于操作数前面,如++x
;后缀版本位于操作数后面,如x++
;两个版本对操作数的影响是一样的,但是影响的时间不同。后缀版本表示先使用操作数的值,然后再将操作数的值加 1;前缀版本表示先将操作数的值加 1,然后再使用操作数的值。
//前缀版本,全部执行完毕后x=6, y=6.
int x = 5;
int y = ++x;
//后缀版本,全部执行完毕后x=6, y=5.
int x = 5;
int y = x++;
此外,前缀格式与后缀格式的执行速度会有细微的差别:后缀版本首先会复制一个副本,将其加 1,然后将复制的副本返回,而前缀版本不会进行额外的复制操作。对于内置类型而言,这种差异微乎其微,但对于用户定义的类型,前缀版本的效率比后缀版本高。
5.1.3 递增/递减运算符和解除引用运算符
将递增(++)/递减(--)运算符和解除引用运算符(*)同时用于指针时,将依据运算符的位置以及优先级来进行运算。前缀递增、前缀递减、解除引用运算符的优先级相同,都以从右到左的方式进行结合;后缀递增、后缀递减的优先级相同,但比前缀运算符的优先级高,且都以从左到右的方式进行结合。
//执行完毕后x=32.8, pt指向arr[1], arr元素无变化
double arr[3] = {21.1, 32.8, 23.4};
double *pt = arr;
double x = *++pt;
//执行完毕后x=22.1, pt指向arr[0], arr元素无变化
double arr[3] = {21.1, 32.8, 23.4};
double *pt = arr;
double x = ++*pt;
//执行完毕后x=21.1, pt指向arr[1], arr元素无变化
double arr[3] = {21.1, 32.8, 23.4};
double *pt = arr;
double x = *pt++;
//执行完毕后x=21.1, pt指向arr[0], arr[0]=22.1
double arr[3] = {21.1, 32.8, 23.4};
double *pt = arr;
double x = (*pt)++;
5.1.4 组合赋值运算符
每个算术运算符都有其对应的组合赋值运算符:
操作数 | 作用(L为左操作数,R为右操作数) |
---|---|
+= |
将L+R 赋给L
|
-= |
将L-R 赋给L
|
*= |
将L*R 赋给L
|
/= |
将L/R 赋给L
|
%= |
将L%R 赋给L
|
5.1.5 逗号运算符
用两个花括号可以构造一条复合语句(代码块),代码块被视为一条语句,这种做法允许把两条或更多语句放到按 C++ 语法只能放一条语句的地方。逗号运算符对表达式完成同样的任务,可以将两个或多个表达式合并为一个,但在声明语句中,逗号只做为分隔符,而不是运算符。逗号运算符是一个顺序点,它确保先计算第一个表达式,再计算第二个表达式,C++ 规定,逗号表达式的值是第二部分的值,在所有运算符中,逗号运算符的优先级是最低的。
//声明语句中,逗号用做分隔符
int i = 0, j = 0;
//逗号用做运算符
i = 0, j = 0;
//逗号运算符的优先级最低,此时i=1
i = 1,2,3,4,5,6;
//逗号表达式的值,此时i=6
i = (1,2,3,4,5,6);
5.1.6 关系表达式
C++ 提供了 6 种关系运算符来对数字进行比较,由于字符用其 ASCII 码表示,因此也可将这些运算符用于字符。不能将它们用于 C-风格字符串,但可用于 string
类对象。对所有关系表达式,若比较结果为真,则其值为true
,否则为false
。关系运算符的优先级比算术运算符低。
操作符 | 含义 |
---|---|
< |
小于 |
<= |
小于或等于 |
== |
等于 |
> |
大于 |
>= |
大于或等于 |
!= |
不等于 |
5.1.7 字符串的比较
C-风格字符串应使用strcmp()
函数来比较,该函数接受两个字符串地址作为参数,参数可以是指针、字符串常量或字符数组名。如果两个字符串相同,该函数将返回零;如果第一个字符串按字母顺序排在第二个字符串之前,该函数将返回一个负值;如果第一个字符串按字母顺序排在第二个字符串之后,该函数将返回一个正值。
//比较C-风格字符串是否相等
strcmp(str1,str2) == 0
//比较C-风格字符串是否不等
strcmp(str1,str2) != 0
strcmp(str1,str2)
//比较C-风格字符串str1是否在str2前面
strcmp(str1,str2) < 0
//比较C-风格字符串str1是否在str2后面
strcmp(str1,str2) > 0
string
类函数重载了关系运算符,因此其对象可直接使用关系运算符进行比较。
5.2 while 循环
while
循环也是入口条件循环,也就是在每轮循环之前,都将计算测试表达式的值。
5.2.1 while 循环格式
while
循环的基本格式如下:
while (test-expression)
{
statements;
}
它可以转换为for
循环:
for (;test-expression;)
{
statements;
}
同样地,for
循环基本格式也可以转换为while
循环:
initialization;
while (test-expression)
{
statements;
update-expression;
}
通常,使用for
循环来为循环计数,在无法事先知道循环将执行的次数时,一般使用while
循环。设计循环时,有以下几条指导原则:
- 指定循环终止条件。
- 在首次测试之前初始化条件。
- 在条件被再次测试之前更新条件。
5.2.2 编写延时循环
头文件ctime
定义了一个符号常量CLOCKS_PER_SEC
,该常量等于每秒钟包含的系统时间单位数,将系统时间除以这个值,可以得到秒数。或者将秒数乘以CLOCKS_PER_SEC
,可以得到以系统时间单位为单位的时间。以下程序使用了头文件ctime
来创建延时 5 秒的循环:
#include <ctime>
int main()
{
//接下来的4行总耗时约5秒
float secs = 5;
clock_t delay = secs * CLOCKS_PER_SEC;
clock_t start = clock();
while (clock() - start < delay);
return 0;
}
5.2.3 类型别名
C++ 为类型建立别名的方式有两种。一种是使用预处理器:
//使用预处理器创建别名(通用格式)
#define aliasName typeName
//使用预处理器创建别名(例子)
#define BYTE char
#define FLOAT_POINTER float *
第二种方法是使用关键字typedef
来创建别名:
//使用关键字typedef来创建别名(通用格式)
typedef typeName aliasName;
//使用关键字typedef来创建别名(例子)
typedef char BYTE;
typedef float * FLOAT_POINTER;
typedef
不会创建新类型,只是为已有类型建立一个新名称,相比于使用#define
,它能处理更复杂的类型别名,因此,使用typedef
是一种更佳的选择。
5.3 do while 循环
do while
循环是出口条件循环,这意味着这种循环将首先执行循环体,然后再判定测试表达式,决定是否应继续执行循环。这样的循环通常至少执行一次。do while
循环的基本格式如下(注意最后的分号):
do
{
statements;
} while (test-expression);
5.4 基于范围的 for 循环(C++11)
C++11 新增了一种循环:基于范围的for
循环,这简化了一种常见的循环任务:对数组或容器类的每个元素执行相同的操作:
//循环遍历数组的值
double prices[5] = {4.99, 10.99, 6.87, 7.99, 8.49};
for (double x : prices)
{
std::cout << x << std::endl;
}
//循环遍历并修改数组的值(引用变量)
double prices[5] = {4.99, 10.99, 6.87, 7.99, 8.49};
for (double &x : prices)
{
x = x * 0.80;
}
//基于范围的for循环和初始化列表
for (double x : {3, 5, 2, 8, 6})
{
std::cout << x << std::endl;
}
这种循环多用于模板容器类。
5.5 嵌套循环和二维数组
5.5.1 初始化二维数组
C++ 没有提供二维数组的类型,但用户可以创建每个元素本身都是数组的数组,二维数组在概念上是二维的,但在内存中是连续存放的,在 C++ 中,二维数组是按行存储的,也就是先存放a[0]
行,再存放a[1]
行,接着存放a[2]
行,以此类推直到元素放完,每行中元素也是依次存放。二维数组的初始化与一维数组类似:
//初始化方式一:全元素初始化
int a[4][5] =
{
{96, 100, 87, 101, 105},
{96, 98, 91, 107, 104},
{96, 101, 93, 108, 107},
{96, 103, 95, 109, 108}
};
//初始化方式二:全元素初始化时,可省略内部括号
int a[4][5] =
{
96, 100, 87, 101, 105,
96, 98, 91, 107, 104,
96, 101, 93, 108, 107,
96, 103, 95, 109, 108
};
//初始化方式三:全元素初始化时,第一维大小可省略
int a[][5] =
{
96, 100, 87, 101, 105,
96, 98, 91, 107, 104,
96, 101, 93, 108, 107,
96, 103, 95, 109, 108
};
//初始化方式四:全元素初始化为0
int a[4][5] = {0};
//初始化方式五:初始化部分元素,剩余元素默认为0
int a[4][5] =
{
{96},
{96, 98, 91},
{96, 101},
{96, 103, 95, 109}
};
//初始化方式六:省略第一维大小且只初始化部分元素
int a[][5] =
{
{},
{96, 98, 91},
{96, 101},
{96, 103, 95, 109}
};
5.5.2 使用 new 创建动态二维数组
动态二维数组的创建以及释放如下所示:
//分配动态二维数组内存的通用格式
typeName ** pointer_name = new typeName *[rowSize];
for (int i = 0; i < rowSize; i++)
{
pointer_name[i] = new typeName[columnSize];
}
//释放动态二维数组内存
for (int i = 0; i < rowSize; i++)
delete[] pointer_name[i];
delete[] pointer_name;
在 C++11 中,使用 new
创建动态二维数组的同时还可对其进行初始化,采用动态一维数组的初始化方法,在for
循环中对动态二维数组的每行进行初始化即可。
5.5.3 嵌套循环
嵌套循环是循环中的循环,外层循环以及内层循环可以是for
、while
、do while
中的任意一种。由于 CPU 使用了分支预测技术,将大循环做为内层循环,小循环做为外层循环可以提升嵌套循环的运行效率,因此嵌套循环一般将循环次数最多循环的放在最内层。由于二维数组是按行存储的,使用嵌套循环遍历二维数组时,按行遍历可提升运行效率,因此当嵌套循环仅用于遍历二维数组时,一般将列循环放在内层[测试例]。