目录
1. for语句简介
2. for语句实例
3. 顺序点和副作用
4. 递增递减操作符应用于指针
1. for语句简介
for语句包含控制体和循环体,循环体跟在控制体的后面。
如下所示,控制体为for括号中的几个表达式,循环体为花括号中的几条语句。
for (initialization; test expression; update-expression)
{语句1;语句2;
}
for语句的执行顺序如下:
- 设置初始值;
- 执行测试,看看循环是否应当继续进行;
- 执行循环操作;
- 更新用于测试的值;
初始化,测试和更新操作这三个部分构成了for语句的控制部分,这些操作由括号括起来,其中每一部分都是一个表达式,且彼此由分号隔开。控制体后面的语句叫循环体,只要测试表达式为true,它便执行。
几个注意事项:
- 控制体只执行一次循环变量的初始化;
- 循环体中可以包含一条或多条语句;
- test expression(测试表达式)决定循环体是否被执行,通常,这个表达式是关系表达式,即对两个值进行比较。
2. for语句实例
下面通过一个opencv中图像二值化的实例来更深入的理解for循环,这里主要使用上篇博文《OpenCV中数组和指针运用实例》中的代码,对循环部分稍做了些修改。
#include <iostream>
#include <cstring>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{Mat srcImage = imread("test.jpg");Mat srcGray;cvtColor(srcImage, srcGray, COLOR_BGR2GRAY);Mat outputImage = srcGray.clone();int rows = outputImage.rows;int cols = outputImage.cols*outputImage.channels();int threshold = 50;if (outputImage.isContinuous() == false){cout << "图像数据非连续存储" << endl;return 0;}else{cout << "图像数据为连续存储" << endl;}uchar* data = outputImage.data;int pixels = rows * cols;for (int i = 0; i < pixels; i++){if (*data < threshold){*data++ = 255;}else{*data++ = 0;}}imshow("src", srcImage);imshow("binary", outputImage);waitKey();return 0;
}
对于上面代码中的循环体,前面说过,控制体中i只初始化一次,此处初始化为0。for语句执行流程如下:
上面图像二值化的代码中难点是for语句的循环体代码的理解,下面我们来逐步分析。
3. 顺序点和副作用
上面流程图中的每一步都是一个完整的表达式,所以更新测试用值这个表达式i++也可以替换成++i,效果是一样的,因为此处的i++和++i都是一个完整表达式,完整表达式末尾有一个顺序点,C++确保副作用在进入循环体之前完成。
副作用定义:副作用(side effect)指的是在计算表达式时对某些东西(如存储在变量中的值)进行了修改。
顺序点定义:顺序点是程序执行过程中的一个点,在这里,在进入下一步之前将确保对所有的副作用都进行了评估。
C++的顺序点有以下两种:
- 语句中的分号是一个顺序点,即程序执行下一条语句前,赋值操作符、递增操作符、递减操作符执行的所有修改都必须完成。
- 任何完整表达式末尾都有一个顺序点。何为完整表达式?它是一个表达式,但不是另一个更大表达式的子表达式。比如:表达式语句中的表达式部分,for语句控制体中更新测试用值部分,while语句中的检测条件的表达式,if语句的关系表达式部分。
关于表达式的补充知识:
- 对任何表达式加上分号都可以成为一个语句;
- C++表达式是一个值,或值与操作符的组合,每个C++表达式都有值;
如10是一个表达式,10+10也是一个表达式,x=10也是一个表达式,它由两个值和一个赋值操作符组成(C++将赋值表达式的值定义为左侧成员的值,所以该表达式的值为20)。
4. 递增递减操作符应用于指针
递增操作符(++)定义:
a++表示使用a的当前值计算表达式,然后将a的值加1;
++a表示先将a的值加1,然后使用新值来计算法表达式;
将递增操作符应用于指针时,将把指针的值增加其指向的数据类型占用的字节数。
int arr[5] = { 21,31,41,51,61 };
int *pt = arr;///使pt指向第一个元素21
++pt;///该语句执行后,pt指向数据第二个元素31
将递增操作符(++)和解除引用操作符(*)结合可以修改指针指向的值。在使用前,需要先了解递增操作符(++)和解除引用操作符(*)的优先级和结合顺序。
前缀递增操作符(即将递增操作符使用在变量前,如++pt)和解除引用操作符的优先级相同,以从右向左的方式进行结合。后缀递增操作符(即将递增操作符使用在变量后,如pt++)的优先级比前缀递增操作符高,即也比解除引用操作符(*)高,并且以从左到右的方式进行结合。
所以以下表达式的含义可以分别解释为:
- (*++pt):前缀递增操作符同解除引用操作符相同,结合方向为从右向左,所以该表达式的含义为先将++与pt结合,然后将(*)应用于被递增后的pt。
- (++*pt):表示先将(*)与pt结合,然后将pt指向的变量的值加1;
- (*pt)++ :表示先对指针解除引用,然后将解除引用后的变量的值加1;
- *pt++ :因为后缀递增操作符的优先级高于解除引用操作符,所以先将pt与递增操作符(++)结合而不是将(*pt)与递增操作符结合,所以是对指针递增,而不是对指针所指向的变量递增。需要特别注意的是:该语句在执行的时候,是对pt原来的地址解除引用,而不是递增后的新地址解除引用,只有当该语句执行完后pt的值才会指向下一个元素的地址。
上面的话术可能听的云里雾里,结合下面实例看下吧。
#include <iostream>
#include <cstring>
using namespace std;
int main()
{int arr[5] = { 21,31,41,51,61 };int *pt1 = arr;///使pt1指向第一个元素21cout << (*++pt1) << endl;cout << (*pt1) << endl;int *pt2 = arr;cout << (*pt2++) << endl;cout << (*pt2) << endl;return 0;
}
以下是输出结果。
为啥pt1和pt2的输出结果不一致。就是由于递增操作符使用在变量前和变量后的区别,递增操作符使用在指针变量前意味着先使指针变量加1个单位,即先使pt1指向&arr[1],然后再解除引用。递增操作符使用在指针变量后意味着对pt2先解除引用,然后再使pt2指向&arr[1],所以pt2的第一条输出语句输出是21,当该语句执行完后会使pt2指向&arr[1],pt2的第二条输出语句输出值是31。
再回到opencv的二值化实例中的for语句:
for (int i = 0; i < pixels; i++){if (*data < threshold){*data++ = 255;}else{*data++ = 0;}}
现在再来看这条语句*data++ = 255;就好理解了,就是先把data指针原来指向的变量的值赋值为255,然后再使data指针指向下一个元素。如果这条语句变成*++data = 255;那就完全错了,就变成if语句中判断的是当前指向的像素值,而赋值却赋的是下一个像素的值。