跳到主要内容C++条件判断、循环与数组详解 | 极客日志C++算法
C++条件判断、循环与数组详解
C++条件判断、循环结构及数组操作详解。涵盖 auto 关键字、范围 for 遍历语法及其适用场景(静态数组与动态指针的区别)、内存函数 memset 与 memcpy 的使用规范、字符数组输入输出及常用字符串函数。重点解析编译器配置 C++11 标准的方法,以及不同数组类型在初始化、拷贝时的注意事项。
前言
本文讲解 C++ 的条件判断、循环及数组相关内容。
内容注意
范围 for 的原理涉及后续内容,此处只需理解其核心功能及可直接使用的场景即可。
一、条件判断与循环
在 C++ 的条件判断与循环中包括:if-else 语句、关系操作符、条件操作符、逻辑操作符、switch 语句、while 循环语句、for 循环语句、do-while 循环语句、break 和 continue 语句、循环嵌套。
除了 C++ for 循环比 C 多了个范围 for,其他与 C 语言完全相同,这里重点讲解范围 for 的相关知识。
范围 for 和数组相联系,放在数组讲解的板块进行讲解。
二、一维和二维数组
数组的创建和初始化,元素访问及元素打印与 C 一样,这里只进行 auto 关键字、范围 for 和 memset 设置数组内容、memcpy 拷贝数组内容相关知识。
1、auto 关键字
功能:auto 的主要用途是让编译器自动推导出变量的类型。
#include<iostream>
using namespace std;
int main(){
auto a = 3.14;
auto b = 100;
auto c = 'x';
return 0;
}
2、范围 for
我们在遍历数组元素时,通常用 for 循环及 while 等等,除了这些方法外,还有一个更方便的方式——使用范围 for。
注意
我们在这个函数中只需学会直接可以用范围 for 的场景,以及了解不能直接用范围 for 的场景即可。
(1)范围 for 的核心功能
范围 for 是 C++11 及以上版本引入的遍历语法,核心作用是:
- 自动遍历「可迭代对象」的所有元素,无需手动写索引、计算长度;
- 简化遍历代码,减少'索引越界'等错误;
- 配合
auto 关键字可自动推导元素类型,进一步简化代码。
相当于将数组中取到的元素依次存到元素变量中进行遍历。
(2)范围 for 的语法格式
基础语法:
for(元素类型 元素变量 : 可迭代对象){
}
常用简化写法(用 auto 自动推导元素类型):
for(auto 元素变量 : 可迭代对象){...}
(& 元素变量 : 可迭代对象){...}
for
auto
(3)简单直观了解功能
#include<iostream>
using namespace std;
int main(){
int arr[10]={1,2,3,4,5,6,7,8,9,10};
for(int e : arr){
cout << e << " ";
}
return 0;
}
上面代码中的 for 就是 范围 for,代码的意思是将 arr 数组中的元素,依次放在 e 变量中,然后打印 e,直到遍历完整个数组的元素。这里的 e 是一个单独的变量,不是数组的元素,所以对 e 的修改,不会影响数组。
(4)能直接用范围 for 的场景(及背后原理)
范围 for 能工作的核心前提是:编译器能自动获取「可迭代对象」的遍历起点(begin)和终点(end)。(这里 begin 表示数组起始位置,end 表示数组结束位置的下一个位置)
1. 静态一维数组
- 原理:静态数组的类型是「
元素类型 [长度]」(比如 int[5]),编译器能通过数组类型直接知道元素个数,从而生成 begin(arr)(数组首地址)和 end(arr)(数组首地址 + 长度)的迭代器。
int arr[5]={1,2,3,4,5};
for(auto num : arr){
cout << num << " ";
}
2. 静态二维数组
- 原理:静态二维数组的'行'是「一维数组类型」(比如
int[4]),外层用 auto& row(引用每行的一维数组),编译器能识别每行的长度,进而内层遍历元素。
int arr[3][4]={{1,2},{3,4},{5,6}};
for(auto& row : arr){
for(auto num : row){
cout << num << " ";
}
}
3. STL 容器(先作了解)
- 原理:STL 容器内置了
begin() 和 end() 成员函数,编译器直接调用这两个函数获取迭代器,无需额外信息。
vector<string> vec ={"a","b","c"};
for(auto& s : vec){
s += "!";
}
这里可以先不看,后面会有几篇会进行讲解,可以先把这个跳过去,不影响,学完容器后再来看也可以。
(5)不能直接用范围 for 的场景(及背后原理)
以下场景中,编译器无法自动获取遍历的起点/终点,因此范围 for 无法直接使用:
1. malloc 动态一维数组
(如 int* arr = (int*)malloc(5*sizeof(int)))
- 原理:malloc 返回的是「裸指针(如
int*)」,指针仅记录内存起始地址,不包含任何长度信息,编译器不知道要遍历多少个元素,无法生成 begin(arr) 和 end(arr)。
int* arr =(int*)malloc(5*sizeof(int));
2. 动态二维指针数组
- 原理:动态二维数组的'行'是「指针(如
int*)」,指针同样没有长度信息,内层范围 for 无法识别要遍历多少个元素。
int** arr =(int**)malloc(2*sizeof(int*));
arr[0]=(int*)malloc(3*sizeof(int));
(6)补充:让'不能用的场景'支持范围 for 的方法(可忽略)
注意:这里仅做了解,在我们学完包装指针后再来看。
如果想让动态数组支持范围 for,可通过包装指针 + 长度的方式,告诉编译器遍历边界(C++20+ 推荐用 std::span):
#include<span>
int* arr =(int*)malloc(5*sizeof(int));
for(auto num : std::span<int>(arr,5)){
cout << num << " ";
}
(7)总结
范围 for 的核心是'依赖编译器自动识别遍历边界'——能识别(静态数组、STL 容器)就可以直接用;不能识别(裸指针、动态指针数组)就无法直接用。
但是对于范围 for 要慎重使用!范围 for 是对数组中所有元素进行遍历的,但是我们实际在做题的过程中,可能只需要遍历数组中指定个数的元素,这样范围 for 就不合适了。
3、编译器配置 C++11
上面讲解的范围 for 是在 C++11 这个标准中引入的,如果你使用的编译器默认不支持 C++11,可能需要配置才能使用。
勾选【编译时加入一下命令】,然后在下方的编译框中加入:
点击确定即可。
要想支持其他 C++ 的标准也是一样的方法。
-std=c++14
-std=c++17
-std=c++20 等
4、memset 设置数组内容
void*memset(void* ptr,int value, size_t num );
- ptr – 指针:指向了要设置的内存块的起始位置
- value – 要设置的值
- num – 设置的字节个数
(1)功能
memset 是用来设置内存的,将内存中的值以字节为单位设置成想要的内容,需要头文件 <cstring>。
(2)头文件
(3)设置数组功能实现
#include<iostream>
#include<cstring>
using namespace std;
int main(){
char str[]="hello world";
memset(str,'x',6);
cout << str << endl;
int arr[]={1,2,3,4,5};
memset(arr,0,sizeof(arr));
for(auto i : arr){
cout << i << " ";
}
cout << endl;
return 0;
}
注意
这是关于 memset 使用的关键注意事项,核心是基于其'按字节填充内存'的特性,明确不同数组类型的使用限制,可拆解为以下两点并补充解读:
- 按字节填充的类型限制
memset 以字节为单位设置内存内容,因此:
- 普通数组(如
int/double 数组):仅能设置为 0。因为这类数组的元素是多字节类型(如 int 占 4 字节),若设置非 0 值,每个字节会被填充为目标值,最终元素值会是多个字节拼接的结果(如设置 1 会得到 0x01010101,而非预期的 1);只有 0 的每个字节都是 0,填充后元素值符合预期。
- 字符数组:可任意设置值。因为字符类型(
char)本身占 1 字节,按字节填充的结果与预期一致。
- 一维数组、二维静态数组:可直接用
memset 设置。这类数组的内存是连续的整块存储,memset 能一次性覆盖所有元素对应的内存区域。
- 二维动态指针数组:需逐行调用
memset。这类数组的内存是分散的(每个行指针指向独立的内存块),直接用 memset 无法覆盖所有行的内容,因此要对每个行指针指向的内存块单独执行 memset 操作。
(4)用代码实现所有类型数组设置
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
int main(){
int a1[5]={1,2,3,4,5};
memset(a1,0,sizeof(a1));
cout <<"1. 一维数组设为 0 结果:";
for(int num : a1) cout << num << " ";
cout <<"\n"<< endl;
int a2[3][4]={
{1,2,3,4},
{5,6,7,8},
{9,10,11,12}
};
memset(a2,0,sizeof(a2));
cout <<"2. 二维静态数组设为 0 结果:"<< endl;
for(auto& row : a2){
for(int num : row) cout << num << " ";
cout << endl;
}
cout << endl;
const int rows =3, cols =4;
int* ad[rows];
for(int i =0; i < rows; i++){
ad[i]=(int*)malloc(cols *sizeof(int));
memset(ad[i],0, cols *sizeof(int));
}
cout <<"3. 二维动态指针数组设为 0 结果:"<< endl;
for(auto rowPtr : ad){
for(int j =0; j < cols; j++) cout << rowPtr[j]<<" ";
cout << endl;
}
for(int i =0; i < rows; i++)free(ad[i]);
cout << endl;
char ac[10]="hello";
memset(ac,'w',sizeof(ac));
cout <<"4. 字符数组设为 'w' 结果:"<< ac << endl;
return 0;
}
5、memcpy 拷贝数组内容
在使用数组的时候,有时候我们需要将数组的内容给数组 b,比如:
int a[10]={1,2,3,4,5,6,7,8,9,10};
int b[10]={0};
void*memcpy(void* destination,const void* source,size_t num );
memcpy 其实是用来做内存块的拷贝的,当然用来做数组内容的拷贝也是没问题的。
和 memset 注意点一样,这里就不做说明了。
(1)头文件
(2)功能实现
这里直接展示对所有类型数组进行操作的代码,便于理解。
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
int main(){
int s1[5]={10,20,30,40,50};
int d1[5];
memcpy(d1, s1,sizeof(s1));
cout <<"1. 一维数组拷贝结果:";
for(int num : d1) cout << num << " ";
cout <<"\n"<< endl;
int s2[3][4]={
{1,2,3,4},
{5,6,7,8},
{9,10,11,12}
};
int d2[3][4];
memcpy(d2, s2,sizeof(s2));
cout <<"2. 二维静态数组拷贝结果:"<< endl;
for(auto& row : d2){
for(int num : row) cout << num << " ";
cout << endl;
}
cout << endl;
const int rows =3, cols =4;
int* ds[rows];
for(int i =0; i < rows; i++){
ds[i]=(int*)malloc(cols *sizeof(int));
for(int j =0; j < cols; j++){
ds[i][j]= i * cols + j +1;
}
}
int* dd[rows];
for(int i =0; i < rows; i++){
dd[i]=(int*)malloc(cols *sizeof(int));
memcpy(dd[i], ds[i], cols *sizeof(int));
}
cout <<"3. 二维动态指针数组拷贝结果:"<< endl;
for(auto rowPtr : dd){
for(int j =0; j < cols; j++) cout << rowPtr[j]<<" ";
cout << endl;
}
for(int i =0; i < rows; i++){
free(ds[i]);
free(dd[i]);
}
cout << endl;
char cs[]="Hello, memcpy!";
char cd[20];
memcpy(cd, cs,strlen(cs)+1);
cout <<"4. 字符数组拷贝结果:"<< cd << endl;
return 0;
}
6、对范围 for,memset,memcpy 总结
范围 for——遍历数组
memset——初始化普通数组或设置字符数组内为特定字符
memcpy——拷贝数组内容
- 范围 for 是无需索引的自动遍历语法,仅支持编译器可获取边界的可迭代对象(如静态数组、STL 容器),不支持裸指针和动态指针数组(因无长度信息);
- memset 按字节填充连续内存(常用填 0),memcpy 按字节拷贝连续内存,二者均需手动指定字节数、依赖内存连续,不支持动态指针数组等分散内存,且 memset 对多字节类型(如 int)不能填非全字节值(如 1),memcpy 需避免内存重叠和浅拷贝;
三者对静态数组均适用,核心差异在于范围 for 侧重遍历便捷,memset/memcpy 侧重底层内存操作。
三、字符数组
字符数组的初始化,strlen,输入输出都和 C 语言一致,我们在这再进行字符数组输入中 gets 和 fgets 两个函数,以及简单回顾一下 strcpy 和 strcat 这两个函数。
1、读取带有空格的字符串方法
我们在读取字符串时直接用 scanf 和 cin 到空格就停止了,无法继续读取,如果需要继续读取我们怎么办呢?
(1)gets 和 fgets
char*gets(char* str );
char*fgets(char* str,int num, FILE * stream );
使用 gets 函数的方式,这种方式能解决问题,但是因为 gets 存在安全性问题,在 C+11 中取消了 gets,给出了更加安全的方案:fgets。
#include<cstdio>
int main(){
char arr[10]={0};
gets(arr);
printf("%s\n", arr);
return 0;
}
#include<cstdio>
int main(){
char arr[10]={0};
fgets(arr,sizeof(arr),stdin);
printf("%s\n", arr);
return 0;
}
gets 读取直到换行符,但不保留换行符;fgets 读取直到换行符或达到指定数量,会保留换行符。
(2)改变 scanf 读取方式
C 语言中用 scanf 也能读取带空格的字符串,只需将格式符 "%s" 改为 "%[^ ]s":其中 [^ ] 表示一直读取直到遇到 ,遇到空格不会结束读取。
这种方式不会将 读入,但会在读取的字符串末尾自动加上 \0。
实现
#include<cstdio>
int main(){
char arr[10]="xxxxxxxx";
scanf("%[^
]s", arr);
printf("%s\n", arr);
return 0;
}
(3)getchar 逐个字符的读取
其实我们上一篇已经顺便介绍了 getchar 逐个字符读取,这里直接实现。
#include<cstdio>
int main(){
char arr[10]={0};
int ch =0;
int i =0;
while((ch =getchar())!='\n'){
arr[i++]= ch;
}
printf("%s\n", arr);
return 0;
}
2、strcpy
strcpy 这个属于字符数组的复制。
函数链接:strcpy
函数原型
char*strcpy(char* destination,constchar* source );
#include<cstdio>
#include<cstring>
int main(){
char arr1[]="abcdef";
char arr2[20]={0};
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
3、strcpy 和 memcpy 对比
我们发现这两个函数功能相似,我们来分析一下他们的区别。
核心区别:strcpy 是「字符串专用拷贝」,memcpy 是「通用内存拷贝」(本质差异)。
两者的所有差异都源于此定位,以下是关键对比(简洁版):
| 对比维度 | strcpy | memcpy |
|---|
| 核心用途 | 仅拷贝字符串(char 数组/指针) | 拷贝任意类型数据(int、struct、字符串等) |
| 拷贝依据 | 以字符串结束符 \0 为终止信号 | 以用户指定的「字节数」为终止依据 |
| 长度指定 | 无需手动指定(自动识别 \0) | 必须手动指定拷贝的总字节数 |
| 适用数据类型 | 仅支持 char 类型(字符串) | 支持所有数据类型(通用) |
| 安全性 | 无长度检查,目标空间不足会溢出 | 无长度检查,但需手动确保字节数≤目标空间 |
| 头文件 | <cstring>(C/C++) | <cstring>(C/C++) |
- strcpy 特点:
- 自动拷贝到
\0 为止(包括 \0 本身,保证字符串完整);
- 风险:若源字符串无
\0 或目标空间太小,会越界溢出(未定义行为);
- 示例:
strcpy(dest, "abc") → 自动拷贝 'a'/'b'/'c'/\0 共 4 字节。
- memcpy 特点:
- 不关心数据内容,仅按字节「机械拷贝」,完全依赖字节数控制;
- 通用性强:可拷贝 int 数组、结构体等非字符串数据;
- 示例:
memcpy(dest, src, 4*sizeof(int)) → 拷贝 4 个 int(共 16 字节)。
一句话总结
strcpy 是「字符串专属工具」,靠 \0 自动终止;memcpy 是「底层内存工具」,靠字节数精准控制,适用于所有数据类型。
4、strcat
有时候我们需要在一个字符的末尾再追加一个字符串,需要用到 strcat。
函数链接:strcat
函数原型
char*strcat(char* destination,constchar* source );
#include<cstdio>
#include<cstring>
int main(){
char arr1[20]="hello ";
char arr2[]="world";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
5、其余字符串函数
除了上面的两个字符串相关函数外,其实 C/C++ 中还提供了一些其他的函数,有兴趣的可以拓展学习。
链接:其他函数
本篇函数头文件
主要涉及 <iostream>, <cstring>, <cstdio>, <cstdlib> 等。
结束语
本篇到这里就结束啦!主要讲解了条件判断与循环及数组的相关内容,本篇中范围 for 讲的有点超纲,大家可以先学会范围 for 其中前三中的内容即可,后面学到相关知识后再来继续理解!
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online