前言
本文讲解 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 arr[5])
- 原理:静态数组的类型是「
元素类型 [长度]」(比如int[5]),编译器能通过数组类型直接知道元素个数,从而生成begin(arr)(数组首地址)和end(arr)(数组首地址 + 长度)的迭代器。
示例:
int arr[5]={1,2,3,4,5};
for(auto num : arr){// 编译器知道 arr 是 int[5],自动遍历 5 个元素
cout << num << " ";
}
2. 静态二维数组
(如 int arr[3][4])
- 原理:静态二维数组的'行'是「一维数组类型」(比如
int[4]),外层用auto& row(引用每行的一维数组),编译器能识别每行的长度,进而内层遍历元素。
示例:
int arr[3][4]={{1,2},{3,4},{5,6}};
for(auto& row : arr){// row 是 int[4] 的引用,编译器知道每行长度
for(auto num : row){
cout << num << " ";
}
}
3. STL 容器(先作了解)
(如 vector、string、list)
- 原理:STL 容器内置了
begin()和end()成员函数,编译器直接调用这两个函数获取迭代器,无需额外信息。
示例:
vector<string> vec ={"a","b","c"};
for(auto& s : vec){// 调用 vec.begin()/vec.end()
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));
// for (auto num : arr) { ... } // 报错:无法确定 arr 的遍历边界
2. 动态二维指针数组
(如 int** arr)
- 原理:动态二维数组的'行'是「指针(如
int*)」,指针同样没有长度信息,内层范围 for 无法识别要遍历多少个元素。
错误示例(编译报错):
int** arr =(int**)malloc(2*sizeof(int*));
arr[0]=(int*)malloc(3*sizeof(int));
// for (auto& row : arr) { for (auto num : row) { ... } } // 报错:row 是 int*,无长度信息
(6)补充:让'不能用的场景'支持范围 for 的方法(可忽略)
注意:这里仅做了解,在我们学完包装指针后再来看。
如果想让动态数组支持范围 for,可通过包装指针 + 长度的方式,告诉编译器遍历边界(C++20+ 推荐用 std::span):
#include<span>// C++20 及以上
int* arr =(int*)malloc(5*sizeof(int));
// 用 span 包装'指针 + 长度',告诉编译器要遍历 5 个元素
for(auto num : std::span<int>(arr,5)){
cout << num << " ";
}
(7)总结
范围 for 的核心是'依赖编译器自动识别遍历边界'——能识别(静态数组、STL 容器)就可以直接用;不能识别(裸指针、动态指针数组)就无法直接用。
但是对于范围 for 要慎重使用!范围 for 是对数组中所有元素进行遍历的,但是我们实际在做题的过程中,可能只需要遍历数组中指定个数的元素,这样范围 for 就不合适了。
3、编译器配置 C++11
上面讲解的范围 for 是在 C++11 这个标准中引入的,如果你使用的编译器默认不支持 C++11,可能需要配置才能使用。
勾选【编译时加入一下命令】,然后在下方的编译框中加入:
-std=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)头文件
#include<cstring>
(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;
}
这里就把数组 arr 内容设置为全为 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(){
// -------------------------- 1. 一维数组设为 0 --------------------------
int a1[5]={1,2,3,4,5};// a1=array 1(一维数组)
memset(a1,0,sizeof(a1));// 内存连续,直接整体填充
cout <<"1. 一维数组设为 0 结果:";
for(int num : a1) cout << num << " ";
cout <<"\n"<< endl;
// -------------------------- 2. 二维静态数组设为 0 --------------------------
int a2[3][4]={// a2=array 2(二维静态数组)
{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;
// -------------------------- 3. 二维动态指针数组设为 0 --------------------------
const int rows =3, cols =4;
int* ad[rows];// ad=array dynamic(二维动态数组)
// 为每行分配内存并逐行填充
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;
// -------------------------- 4. 字符数组设为 'w' --------------------------
char ac[10]="hello";// ac=array char(字符数组)
memset(ac,'w',sizeof(ac));// char 占 1 字节,直接填充任意字符
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 );
//destination -- 目标空间的起始地址
//source -- 源数据空间的起始地址
//num -- 拷贝的数据的字节个数
memcpy 其实是用来做内存块的拷贝的,当然用来做数组内容的拷贝也是没问题的。 和 memset 注意点一样,这里就不做说明了。
(1)头文件
#include<cstring>
(2)功能实现
这里直接展示对所有类型数组进行操作的代码,便于理解。
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
int main(){
// -------------------------- 1. 一维数组拷贝 --------------------------
int s1[5]={10,20,30,40,50};// 源:s1=source 1(一维)
int d1[5];// 目标:d1=dest 1(一维)
memcpy(d1, s1,sizeof(s1));
cout <<"1. 一维数组拷贝结果:";
for(int num : d1) cout << num << " ";
cout <<"\n"<< endl;
// -------------------------- 2. 二维静态数组拷贝 --------------------------
int s2[3][4]={// 源:s2=source 2(二维静态)
{1,2,3,4},
{5,6,7,8},
{9,10,11,12}
};
int d2[3][4];// 目标:d2=dest 2(二维静态)
memcpy(d2, s2,sizeof(s2));
cout <<"2. 二维静态数组拷贝结果:"<< endl;
for(auto& row : d2){
for(int num : row) cout << num << " ";
cout << endl;
}
cout << endl;
// -------------------------- 3. 二维动态指针数组拷贝 --------------------------
const int rows =3, cols =4;
// 源:ds=dynamic source(二维动态)
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;
}
}
// 目标:dd=dynamic dest(二维动态)
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;
// -------------------------- 4. 字符数组拷贝 --------------------------
char cs[]="Hello, memcpy!";// 源:cs=char source(字符源)
char cd[20];// 目标:cd=char dest(字符目标)
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。
实现
方案 1
#include<cstdio>//方案 1
int main(){
char arr[10]={0};
gets(arr);
printf("%s\n", arr);
return 0;
}
替代方案 - 方法 2
//替代方案 - 方法 2
#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 );
// 注释说明
// destination - 是目标空间的地址
// source - 是源头空间的地址
// 需要的头文件 <cstring>
代码实现
#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 );
// 注释说明
// destination - 是目标空间的地址
// source - 是源头空间的地址
// 需要的头文件 <cstring>
代码实现
#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 其中前三中的内容即可,后面学到相关知识后再来继续理解!


