[C/C++安全编程]_[中级]_[如何安全使用循环语句]

场景

  1. 在标准C里,使用循环语句也是有可能造成越界或死循环的,那么在现代C++开发里能避免吗?

说明

  1. C++11标准里,新增了一个范围for循环语句,可以快速枚举集合,数组,并且不需要索引操作。 这个是正向枚举的推荐操作,可以避免越界。
cpp 复制代码
for (const auto& one : array_1)
		cout << one << endl;
  1. C++98时可以使用<algorithm>算法库的std::for_each来执行枚举,效果等同于使用迭代器枚举。不涉及到索引值操作,可以安全的。
cpp 复制代码
for (auto ite = array_1.begin(); ite != array_1.end(); ++ite) {
		cout << *ite << endl;
}
  1. 在倒序枚举时,C++也有倒序迭代器可以用,也是安全的。
cpp 复制代码
for (auto ite = array_1.rbegin(); ite != array_1.rend(); ++ite) {
		cout << *ite << endl;
}
  1. 如果非要在倒序时使用索引值,那么要注意循环变量不要声明为无符号常量。比如size_t就是无符号常量,是集合方法.size()的返回类型。因为无符号常量在递减的时候不会出现小于0的情况。
cpp 复制代码
cout << "=== index 1 type ==" << endl;
for (int i = array_1.size() - 1; i >= 0; --i) {  // 使用后置递减,避免 unsigned 类型的问题
	cout << "[" << i << "] => " << array_1[i] << endl;
}
  1. 注意1: 使用goto跳出循环需要跳出全部循环,避免逻辑混乱和死循环。
cpp 复制代码
for (auto i = 0; i < 100; ++i) {
	for (auto j = 0; j < 100; ++j) {
		if ( i && j && (j % 10 == 0)) {
			cout << "i: " << i << " j: " << j << endl;
			goto next;
		}
			
	}
}
next:
  1. 避免修改循环变量,避免逻辑混乱,死循环和越界访问。
cpp 复制代码
for (auto i = 0; i < 100; ++i) {
	if (i && (i % 5) == 0)
		i--;
}
  1. 不要使用浮点型作为循环变量,因为浮点型有精度问题,可能会导致条件不成立。

    • 浮点型的值i可能永远不会达到10.0f导致死循环。

    • 浮点型运算比整型慢的多。

    cpp 复制代码
    for(auto i = 1.0f; i != 10.0f; i+= 0.1f)

例子

cpp 复制代码
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;


void TestDecreaseLoop()
{
	vector<int> array_1 = {4,5,9,2,3,1,0,3,2};
	// 1. 优先选择: 使用反向迭代器; 不涉及到索引的修改,不会出错。
	cout << "=== iterator ==" << endl;
	for (auto ite = array_1.rbegin(); ite != array_1.rend(); ++ite) {
		cout << *ite << endl;
	}
		
	// 2. 如果需要索引,注意使用`int`声明循环变量,如果使用`auto`,那么会是`size_t`类型;
	// -- 而`size_t`是无符号整型,不会出现小于`0`的情况。
	cout << "=== index 1 type ==" << endl;
	for (int i = array_1.size() - 1; i >= 0; --i) {  // 使用后置递减,避免 unsigned 类型的问题
		cout << "[" << i << "] => " << array_1[i] << endl;
    }

	// 错误:i 一直大于等于`0`,死循环。
	//for (auto i = array_1.size() - 1; i >= 0; --i) {
	//	cout << "[" << i << "] => " << array_1[i] << endl;
	//}


	// -- 另一种获取索引的方式
	cout << "=== index 2 type ==" << endl;
	auto iend = array_1.rend();
	for (auto ite = array_1.rbegin(); ite != iend; ++ite) {
		cout << "[" << iend - ite - 1 << "] => " << *ite << endl;
	}
}

void processOne(int& one) 
{
	cout << one << endl;
}

void TestIncreaseLoop()
{
	vector<int> array_1 = {4,5,9,2,3,1,0,3,2};
	// 1. 优先使用范围`for`循环,避免使用索引
	cout << "=== range for ==" << endl;
	for (const auto& one : array_1)
		cout << one << endl;

	// 2. 如果是`C++98`
	cout << "=== for_each ==" << endl;
	for_each(array_1.begin(), array_1.end(), processOne);

	// -- 效果等同于使用`iterator`
	cout << "=== iterator ==" << endl;
	for (auto ite = array_1.begin(); ite != array_1.end(); ++ite) {
		cout << *ite << endl;
	}

	// 3. 最后的选择: 需要用到索引时;
	cout << "=== index ==" << endl;
	auto size = array_1.size();
	for (auto i = 0; i < size; ++i) {
		cout << "[" << i << "] => " << array_1[i] << endl;
	}
}

void TestOtherLoop()
{
	// 1. 使用`goto`跳出循环需要跳出全部循环,避免逻辑混乱和死循环。
	for (auto i = 0; i < 100; ++i) {
		for (auto j = 0; j < 100; ++j) {
			if ( i && j && (j % 10 == 0)) {
				cout << "i: " << i << " j: " << j << endl;
				goto next;
			}
				
		}
	}

next:
	cout << "loop finish" << endl;

	// 2. 避免修改循环变量,避免逻辑混乱,死循环和越界访问。
	//for (auto i = 0; i < 100; ++i) {
	//	if (i && (i % 5) == 0)
	//		i--;
	//}

	// 3. 不要使用浮点型作为循环变量,因为浮点型有精度问题,可能会导致条件不成立。
	// -- 浮点型的值`i`可能永远不会达到10.0f导致死循环。
	// -- 浮点型运算比整型慢的多。
	// for(auto i = 1.0f; i != 10.0f; i+= 0.1f)
}

int main()
{
    std::cout << "Hello World!\n";
	cout << "========== TestDecreaseLoop =========" << endl;
	TestDecreaseLoop();

	cout << "========== TestIncreaseLoop =========" << endl;
	TestIncreaseLoop();

	cout << "========== TestOtherLoop =========" << endl;
	TestOtherLoop();
}

输出

cpp 复制代码
Hello World!
========== TestDecreaseLoop =========
=== iterator ==
2
3
0
1
3
2
9
5
4
=== index 1 type ==
[8] => 2
[7] => 3
[6] => 0
[5] => 1
[4] => 3
[3] => 2
[2] => 9
[1] => 5
[0] => 4
=== index 2 type ==
[8] => 2
[7] => 3
[6] => 0
[5] => 1
[4] => 3
[3] => 2
[2] => 9
[1] => 5
[0] => 4
========== TestIncreaseLoop =========
=== range for ==
4
5
9
2
3
1
0
3
2
=== for_each ==
4
5
9
2
3
1
0
3
2
=== iterator ==
4
5
9
2
3
1
0
3
2
=== index ==
[0] => 4
[1] => 5
[2] => 9
[3] => 2
[4] => 3
[5] => 1
[6] => 0
[7] => 3
[8] => 2
========== TestOtherLoop =========
i: 1 j: 10
loop finish

参考

  1. C++ Core Guidelines

  2. C++11语言特性和标准库

相关推荐
晴天的雨.9924 分钟前
[C++算法]盛最多水的容器(双指针算法)
开发语言·c++·算法
布莱克60522 分钟前
理解内存泄漏:成因、发现方法与解决策略(C++ 举例)
c语言·c++·内存泄漏
此生决int1 小时前
深入理解C++系列(19)——C++11(上)
开发语言·c++
郝学胜-神的一滴1 小时前
C++20模板元编程 05:吃透变参模板,解锁编译期万能参数能力
服务器·开发语言·c++·windows·vscode
C++ 老炮儿的技术栈11 小时前
我们在设计tcp协议时,要传一个字符串过去,报文:头十长度十内容,是否要把‘\0‘也填入,长度是否包含‘\0‘
开发语言·数据结构·c++·mfc·c
I_belong_to_jesus16 小时前
std::unique_ptr成员函数用法
c++
Tairitsu_H17 小时前
[C++] 拷贝构造还是赋值重载?类默认成员函数细节详解
开发语言·c++·类和对象
汉克老师18 小时前
GESP2026年9月认证C++四级( 第一部分选择题(1~7题)精讲
c++·gesp·小学生·学c++编程
geats人山人海18 小时前
现代c++第二章 字符串
c++
idealzouhu18 小时前
现代 CMake 依赖管理详解:从构建树、Target 封装到 find_package 工作原理
c++·cmake