C++(详解数组:包含memcpy,round,strlen,冒泡排序等函数)
一维数组
一维数组是存储相同数据类型元素的连续内存空间,是 C++ 中最基础的线性数据结构,核心用法可精简为以下 4 部分:
一、数组的定义(3 种常用方式)
定义需指定「数据类型」和「数组长度」,长度必须是常量表达式(不可用变量)。
| 定义方式 | 示例 | 说明 |
|---|---|---|
| 只指定长度,默认初始化 | int arr[5]; |
全局数组默认初始化为 0,局部数组为随机值 |
| 定义 + 初始化(指定长度) | int arr[5] = {1,2,3}; |
未赋值的元素默认补 0(结果:1,2,3,0,0) |
| 初始化时省略长度 | int arr[] = {1,2,3,4}; |
编译器自动根据初始化元素个数确定长度(此处为 4) |
二、数组的访问(通过下标)
- 下标规则:从
0开始(第一个元素下标为 0,最后一个为「长度 - 1」)。 - 访问方式:
数组名[下标],可读取或修改元素。 - 示例
int arr[] = {10,20,30}; cout << arr[0]; // 读取:输出10 arr[1] = 200; // 修改:arr变为{10,200,30} - 注意:下标不能越界(如长度为 3 的数组,下标 0~2 合法,下标 3 非法),越界会导致内存访问错误。
三、数组的遍历(2 种常用方式)
遍历即逐个访问数组元素,核心是覆盖所有合法下标。
- for 循环遍历(最通用)通过下标控制范围,适合需要修改元素或自定义遍历顺序的场景
int arr[] = {1,2,3,4}; int len = sizeof(arr)/sizeof(arr[0]); // 计算数组长度(总字节数/单个元素字节数) for(int i=0; i<len; i++){ cout << arr[i] << " "; // 输出:1 2 3 4 } - 范围 for 循环(C++11 及以后)自动遍历所有元素,语法简洁,适合仅读取或简单修改元素的场景
int arr[] = {1,2,3,4}; for(int x : arr){ // x依次取arr的每个元素 cout << x << " "; // 输出:1 2 3 4 }
四、数组作为函数参数(2 种传递方式)
数组传参时,不会传递整个数组,仅传递数组首元素的地址(节省内存),因此函数内无法直接通过sizeof获取数组长度,需额外传长度参数。
- 数组名形式传递语法:
函数名(数据类型 数组名[], int 长度void printArr(int arr[], int len){ // arr实际是首元素地址 for(int i=0; i<len; i++){ cout << arr[i] << " "; } } // 调用: int arr[] = {1,2,3}; printArr(arr, 3); // 输出:1 2 3 - 指针形式传递语法:
函数名(数据类型 *指针名, int 长度)(与数组名形式等价,数组名本质是首元素地址void printArr(int *p, int len){ // p接收数组首地址 for(int i=0; i<len; i++){ cout << *(p+i) << " "; // *(p+i) 等价于 arr[i] } } // 调用: int arr[] = {1,2,3}; printArr(arr, 3); // 输出:1 2 3
核心注意点
- 数组长度一旦定义,不可修改(内存空间固定)。
- 局部数组未初始化时,元素值是随机垃圾值,使用前需手动初始化。
- 函数传参时必须额外传递数组长度,不能在函数内用
sizeof(arr)/sizeof(arr[0])计算。
C++ 一维数组常见错误案例及修正表
以下整理了一维数组使用中高频出错的场景,包含错误代码、问题原因和修正方案,帮你快速避坑:
| 错误类型 | 错误代码示例 | 问题原因分析 | 修正后代码示例 |
|---|---|---|---|
| 1. 数组下标越界 | ```cpp | ||
| int arr[3] = {1,2,3}; | |||
| cout <<arr [3]; // 访问下标 3(合法下标 0~2) | |||
| ``` | 数组下标从 0 开始,最后一个元素下标为「长度 - 1」,越界会访问非法内存,导致程序崩溃或乱码。 | ```cpp | |
| int arr[3] = {1,2,3}; | |||
| cout <<arr [2]; // 访问最后一个元素,下标 2 |
|
| 2. 用变量指定数组长度 | ```cpp
int n = 5;
int arr[n]; // 错误:长度必须是常量
``` | C++ 标准中,数组定义时长度需为**常量表达式**(如字面量、const常量),变量值编译时不确定,无法分配固定内存。 | ```cpp
// 方式1:用字面量
int arr[5];
// 方式2:用const常量
const int n = 5;
int arr[n];
``` |
| 3. 函数内用sizeof算长度| ```cpp
void print(int arr[]){
// 错误:arr是指针,sizeof(arr)是4/8字节(指针大小)
int len = sizeof(arr)/sizeof(arr[0]);
for(int i=0; i<len; i++){...}
}
int main(){
int arr[3] = {1,2,3};
print(arr);
}
``` | 数组传参时仅传递首元素地址(arr退化为指针),函数内`sizeof(arr)`计算的是指针大小,而非数组总字节数,导致长度计算错误。 | ```cpp
// 修正:额外传长度参数
void print(int arr[], int len){
for(int i=0; i<len; i++){
cout << arr[i] << " ";
}
}
int main(){
int arr[3] = {1,2,3};
print(arr, 3); // 手动传数组长度3
}
``` |
| 4. 局部数组未初始化 | ```cpp
int main(){
int arr[3]; // 局部数组未初始化
for(int i=0; i<3; i++){
cout << arr[i] << " "; // 输出随机垃圾值
}
}
``` | 局部数组(定义在函数内)未初始化时,元素值是内存中的随机残留值,而非0,使用时会导致逻辑错误。 | ```cpp
int main(){
// 方式1:全部初始化为0
int arr[3] = {0};
// 方式2:逐个赋值初始化
int arr2[3];
arr2[0] = 1; arr2[1] = 2; arr2[2] = 3;
for(int i=0; i<3; i++){
cout << arr[i] << " "; // 输出:0 0 0
}
}
``` |
| 5. 范围for循环修改元素 | ```cpp
int arr[3] = {1,2,3};
for(int x : arr){ // x是元素的副本
x *= 2; // 修改副本,原数组不变
}
for(int x : arr){
cout << x << " "; // 仍输出:1 2 3
}
``` | 普通范围for循环中,`x`是数组元素的**副本**,修改`x`不会影响原数组元素。 | ```cpp
int arr[3] = {1,2,3};
// 加&,x变为元素的引用,修改x即修改原数组
for(int &x : arr){
x *= 2; // 原数组元素翻倍
}
for(int x : arr){
cout << x << " "; // 输出:2 4 6
}
``` |
| 6. 数组赋值错误 | ```cpp
int arr1[3] = {1,2,3};
int arr2[3];
arr2 = arr1; // 错误:数组名是常量地址,不能直接赋值
``` | C++ 不支持数组整体赋值(数组名本质是首元素的常量地址,无法被重新赋值),直接用`=`会编译报错。 | ```cpp
int arr1[3] = {1,2,3};
int arr2[3];
// 方式1:循环逐个赋值
for(int i=0; i<3; i++){
arr2[i] = arr1[i];
}
// 方式2:用memcpy(需包含<cstring>)
#include <cstring>
memcpy(arr2, arr1, sizeof(arr1));
```
memset
1. 函数原型
void* memset(void* ptr, int value, size_t num);
ptr:指向要填充的内存块的指针,可以是数组、结构体等类型的首地址。value:要设置的值,通常是一个字节值(0或255等)。在实际使用中,会将该值转化为无符号字符类型(unsigned char),然后填充到内存块中。num:要填充的字节数,size_t是一种无符号整数类型,通常用于表示内存大小、数组长度等。
2. 功能说明
memset 函数会从指针 ptr 所指向的内存地址开始,将 num 个字节的内存空间设置为 value 所指定的值。它按照字节为单位进行操作,常用于将数组、结构体等数据结构初始化为特定的值,比如将数组初始化为 0 或 -1 等。
3. 使用示例
- 将数组初始化为 0
#include <iostream>
#include <cstring>
int main() {
int arr[5];
memset(arr, 0, sizeof(arr)); // 将arr数组的所有元素初始化为0
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
return 0;
}
- 将字符数组初始化为特定字符
#include <iostream>
#include <cstring>
int main() {
char str[10];
memset(str, '*', sizeof(str)); // 将str数组的每个元素设置为'*'
std::cout << str << std::endl;
return 0;
}
4. 注意事项
- 数据类型和填充值的匹配:由于
memset是按字节填充,对于多字节数据类型(如int、float等),如果要将其初始化为特定值,需要特别注意。例如,想把int类型数组初始化为0是没问题的,但如果想初始化为1,直接使用memset(arr, 1, sizeof(arr));是错误的,因为它会把每个字节都设置为1,而不是将整个int元素设置为1。 - 内存越界:在指定
num时,要确保不超过目标内存块的实际大小,否则会导致内存越界访问,引发程序崩溃或未定义行为。 - 结构体的初始化:对于结构体,如果结构体中包含指针成员,使用
memset初始化可能会导致指针指向无效地址,造成程序错误。一般更建议使用构造函数等方式来初始化结构体。
5. 与其他初始化方式对比
- 与普通循环初始化对比:对于简单地将数组初始化为单一值(如
0),memset的效率通常更高,因为它是直接操作内存,采用了底层的汇编指令来批量填充;而普通循环初始化需要逐个赋值,执行的指令更多。但对于复杂的初始化逻辑,如给数组元素赋予不同的值,循环初始化则更为灵活。 - 与 C++ 的
fill函数对比:<algorithm>头文件中的fill函数适用于初始化STL容器(如vector)或普通数组,它是按照元素为单位进行赋值的,适用于各种数据类型,并且能进行更复杂的初始化操作;而memset是按字节操作,主要用于简单地将内存块填充为单一字节值。
总之,memset 是一个强大且高效的内存初始化工具,但在使用时需要注意数据类型、内存边界等问题,以避免出现错误
memcpy
1. 函数原型
void* memcpy(void* destination, const void* source, size_t num);
destination:指向目标内存块的指针,用于接收复制过来的数据,类型为void*。在使用时,需要根据实际的数据类型进行强制类型转换。source:指向源内存块的指针,数据将从这里被复制,是const void*类型,意味着函数不会修改源内存块的数据。num:要从源内存块复制的字节数,size_t是无符号整数类型,用于表示内存大小等。
2. 功能说明
memcpy 函数会从 source 所指向的内存地址开始,连续复制 num 个字节的数据到 destination 所指向的内存地址。它会原封不动地复制内存中的二进制数据,不关心数据的具体类型和含义,只要目标内存有足够空间容纳复制的数据即可。
3. 使用示例
- 复制数组
#include <iostream>
#include <cstring>
int main() {
int sourceArr[] = {1, 2, 3, 4, 5};
int destinationArr[5];
// 计算要复制的字节数,sizeof(sourceArr)得到整个数组的字节数
memcpy(destinationArr, sourceArr, sizeof(sourceArr));
for (int i = 0; i < 5; ++i) {
std::cout << destinationArr[i] << " ";
}
std::cout << std::endl;
return 0;
}
- 复制结构体
#include <iostream>
#include <cstring>
struct Student {
char name[20];
int age;
};
int main() {
Student sourceStudent = {"Alice", 20};
Student destinationStudent;
// 复制结构体,sizeof(sourceStudent)获取结构体大小
memcpy(&destinationStudent, &sourceStudent, sizeof(sourceStudent));
std::cout << "Name: " << destinationStudent.name << ", Age: " << destinationStudent.age << std::endl;
return 0;
}
4. 注意事项
- 内存重叠问题:如果源内存块和目标内存块存在重叠部分,
memcpy的行为是未定义的。例如,从数组中间位置复制一段数据到数组靠前的位置,且存在重叠,结果不可预测。如果需要处理内存重叠的情况,应使用memmove函数,它可以安全地处理重叠内存的复制。 - 目标内存空间大小:确保目标内存空间足够大,能够容纳要复制的数据。如果目标内存空间不足,会导致缓冲区溢出,引发程序崩溃或其他未定义行为。
- 数据类型与字节数:在确定
num的值时,要根据实际的数据类型准确计算字节数。对于数组和结构体等复合数据类型,常使用sizeof运算符获取其大小,以保证复制的字节数正确。
5. 与其他复制方式对比
- 与赋值运算符对比:对于简单数据类型(如
int、double等),赋值运算符=能完成数据复制;但对于数组、结构体等复杂数据类型,赋值运算符可能无法满足需求,或者需要自定义重载。而memcpy可统一处理各种数据类型的二进制数据复制。 - 与
std::copy对比:<algorithm>头文件中的std::copy常用于STL容器和普通数组的元素复制,它是基于迭代器进行操作,按照元素逐个复制,更具类型安全性。而memcpy是直接操作内存,按字节复制,在处理大量数据且对性能要求高,同时能保证数据类型和内存空间正确的情况下,效率可能更高。
memcpy 是高效复制内存数据的重要工具,在使用时要注意内存管理和数据类型相关的问题,以确保程序的正确性和稳定性。
冒泡排序(Bubble Sort)
一、核心思想
通过重复遍历待排序数组,每次比较相邻的两个元素,若顺序错误则交换它们的位置,直到整个数组有序。
- 每一趟排序会将当前未排序部分的最大(或最小)元素 “冒泡” 到末尾,因此称为 “冒泡排序”。
- 属于稳定排序(相等元素的相对位置不变),原地排序(不需要额外空间)。
二、基本实现(升序)
代码示例
#include <iostream>
#include <vector>
using namespace std;
// 冒泡排序(升序:从小到大)
void bubbleSort(vector<int>& arr) {
int n = arr.size();
// 外层循环:控制排序趟数(最多n-1趟)
for (int i = 0; i < n - 1; i++) {
// 内层循环:每趟比较相邻元素,范围随趟数减少
for (int j = 0; j < n - i - 1; j++) {
// 若前一个元素 > 后一个元素,交换(保证小的往前冒)
if (arr[j] > arr[j + 1]) {
swap(arr[j], arr[j + 1]); // 交换操作
}
}
}
}
int main() {
vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
bubbleSort(arr);
cout << "排序后(升序):";
for (int num : arr) {
cout << num << " ";
}
// 输出:11 12 22 25 34 64 90
return 0;
}
三、关键细节解析
-
外层循环范围(
i < n-1)- 最多需要
n-1趟排序:第i趟排序后,第i大的元素已就位(末尾位置),剩余未排序元素为n-i个。 - 例:
n=5时,4 趟即可完成排序。
- 最多需要
-
内层循环范围(
j < n-i-1)- 每趟排序只需要比较未排序部分的相邻元素,已 “冒泡” 到末尾的元素无需再比较。
- 第
i趟时,末尾i个元素已有序,因此j最大到n-i-2(即j+1最大为n-i-1)。
-
交换条件
- 升序:
if (arr[j] > arr[j+1])(前大后小则交换)。 - 降序:
if (arr[j] < arr[j+1])(前小后大则交换)。
- 升序:
四、优化版本(减少无效循环)
问题:若数组提前有序,仍会执行完所有趟数
优化思路:添加 “是否交换” 的标志,若某趟未交换,说明数组已有序,直接退出
void optimizedBubbleSort(vector<int>& arr) {
int n = arr.size();
bool swapped; // 标记本趟是否发生交换
for (int i = 0; i < n - 1; i++) {
swapped = false; // 初始化为未交换
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr[j], arr[j + 1]);
swapped = true; // 发生交换,标记为true
}
}
// 若本趟未交换,说明数组已有序,直接退出
if (!swapped) {
break;
}
}
}
五、时间复杂度与空间复杂度
| 情况 | 时间复杂度 | 说明 |
|---|---|---|
| 最好情况 | O(n) | 数组已有序(优化版) |
| 最坏情况 | O(n²) | 数组逆序 |
| 平均情况 | O(n²) | 随机无序数组 |
- 空间复杂度:O (1)(仅用常数级额外空间)。
六、适用场景
- 数据量较小时(如
n < 1000),实现简单,易于理解。 - 不适合大数据量场景(效率低于快速排序、归并排序等 O (nlogn) 算法)。
七、常见错误总结
- 数组越界:内层循环条件错误(如写成
j < n-i,导致j+1越界)。 - 输入错误:输入数组元素时索引错误(如误用
arr[n]而非arr[i])。 - 未加判断条件:直接交换相邻元素,导致无论大小都交换(排序逻辑错误)。
- 使用变长数组(VLA):C++ 标准不支持
int arr[n],应使用vector或动态内存分配。
通过以上内容,可以清晰理解冒泡排序的实现逻辑、优化方法及适用场景,避免常见错误。
二维数组
一、二维数组的定义
二维数组可以看作是由多个一维数组组成的数组,它有行和列两个维度。定义二维数组的基本语法为:
数据类型 数组名[行数][列数];
例如:
// 定义一个3行4列的整型二维数组
int arr[3][4];
这里arr是二维数组名,3表示行数,4表示列数,数组中的每个元素都是int类型。
二、二维数组的初始化
1. 全部初始化
可以在定义二维数组时,为所有元素赋初值。
// 全部初始化,按行依次赋值
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
这里第一行的元素是1、2、3,第二行的元素是4、5、6。
2. 部分初始化
如果只对部分元素赋初值,未被赋值的元素会被自动初始化为0(对于数值类型)。
// 部分初始化,第一行前两个元素为1、2,其余为0
int arr[2][3] = {{1, 2}, {4}};
初始化后,数组arr的元素为:第一行:1、2、0;第二行:4、0、0。
3. 省略行数初始化
在初始化二维数组时,可以省略行数,编译器会根据列数和初始化元素的个数自动计算行数。
// 省略行数,编译器会计算出行数为2
int arr[][3] = {{1, 2, 3}, {4, 5, 6}};
三、二维数组的访问
二维数组的元素通过行索引和列索引来访问,索引从0开始。语法为:
数组名[行索引][列索引]
例如,对于前面定义的arr[2][3] = {{1, 2, 3}, {4, 5, 6}};,访问第二行第三列的元素(值为6):
int value = arr[1][2];
四、二维数组的遍历
通常使用嵌套的for循环来遍历二维数组,外层循环控制行,内层循环控制列。
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
cout << arr[i][j] << " ";
}
cout << endl;
}
输出结果:
1 2 3
4 5 6
五、二维数组作为函数参数
当二维数组作为函数参数时,由于数组在传递时会退化为指针,所以需要明确列数(行数可以省略)。
函数定义示例
// 计算二维数组所有元素的和
int sumArray(int arr[][3], int rows) {
int sum = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++) {
sum += arr[i][j];
}
}
return sum;
}
函数调用示例
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
int total = sumArray(arr, 2);
cout << "数组元素和为:" << total << endl; // 输出21
六、动态二维数组(使用指针或容器)
如果在程序运行时才知道二维数组的行数和列数,可以使用动态内存分配或者vector容器来创建二维数组。
1. 使用指针(动态内存分配)
int rows = 2, cols = 3;
// 先分配行指针数组
int** arr = new int*[rows];
for (int i = 0; i < rows; i++) {
// 为每行分配列元素
arr[i] = new int[cols];
}
// 给元素赋值
arr[0][0] = 1;
arr[0][1] = 2;
arr[0][2] = 3;
arr[1][0] = 4;
arr[1][1] = 5;
arr[1][2] = 6;
// 释放内存
for (int i = 0; i < rows; i++) {
delete[] arr[i];
}
delete[] arr;
2. 使用vector容器
vector是 C++ 标准模板库中的容器,使用起来更加方便和安全,不需要手动管理内存。
#include <vector>
int rows = 2, cols = 3;
// 创建二维vector,初始值都为0
vector<vector<int>> arr(rows, vector<int>(cols, 0));
// 赋值
arr[0][0] = 1;
arr[0][1] = 2;
arr[0][2] = 3;
arr[1][0] = 4;
arr[1][1] = 5;
arr[1][2] = 6;
// 遍历
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
cout << arr[i][j] << " ";
}
cout << endl;
}
为什么二维数组在传递到函数中时,行数可以省略,列数不可以省略?
二维数组在内存中的存储方式
二维数组在内存中是按行优先顺序连续存储的。以int arr[3][4];为例,它在内存中的存储顺序是先存储第一行的 4 个元素,接着存储第二行的 4 个元素,最后存储第三行的 4 个元素,本质上是一块连续的内存空间。
编译器解析数组元素的过程
当二维数组作为参数传递给函数时,编译器需要知道如何根据给定的行索引和列索引正确地找到对应的数组元素。这就需要知道每一行有多少个元素,也就是列数。
假设我们有如下函数声明和调用:
// 函数声明,计算二维数组所有元素的和
int sumArray(int arr[][4], int rows);
int main() {
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int result = sumArray(arr, 3);
return 0;
}
在sumArray函数内部,编译器要通过行索引i和列索引j 来计算出每个元素在内存中的位置,计算元素地址的公式大致如下(简化模型,不考虑复杂的内存对齐等因素):元素地址 = 数组首地址 + (i * 列数 + j) * 单个元素大小
其中,i是行索引,j是列索引,单个元素大小是由数组元素类型决定的(比如int类型就是 4 字节)。通过这个公式,编译器就能找到对应的元素进行操作。
行数可以省略的原因
函数在处理二维数组时,行数只是用于控制循环的次数,即要对多少行数据进行操作。我们可以通过额外传递一个参数(比如上面代码中的rows )来告诉函数二维数组有多少行,这样函数就知道需要进行多少轮外层循环去遍历每一行,所以在函数参数声明时可以省略行数。
列数不可以省略的原因
如果省略了列数,编译器就无法通过上述公式计算出每个元素在内存中的准确位置,也就不能正确访问和操作数组元素。例如,当我们写arr[i][j] 时,编译器不知道应该跳过多少个元素才能找到第i行第j列的元素。
所以,在 C++ 中二维数组作为函数参数传递时,为了让编译器能够正确解析和访问数组元素,列数必须明确指定,而行数可以通过其他方式传递给函数,声明时可以省略 。
round函数(四舍五入)
round 函数是 <cmath> 头文件中用于实现四舍五入功能的数学函数。以下是关于它的详细知识点:
1. 函数原型
double round(double x);float round(float x);long double round(long double x);
它接受一个数值参数(可以是 double、float 或者 long double 类型),并返回四舍五入后的结果,返回值类型与传入的参数类型一致 。
2. 功能说明
round 函数的作用是将一个浮点数四舍五入到最接近的整数。例如:
#include <iostream>
#include <cmath>
using namespace std;
int main() {
double num1 = 3.4;
double num2 = 3.6;
cout << round(num1) << endl; // 输出 3
cout << round(num2) << endl; // 输出 4
}
如果小数部分正好是 0.5,则会向距离最近的偶整数取整,即 “四舍六入五成双” 。例如:
#include <iostream>
#include <cmath>
using namespace std;
int main() {
double num1 = 2.5;
double num2 = 3.5;
cout << round(num1) << endl; // 输出 2
cout << round(num2) << endl; // 输出 4
}
3. 使用注意事项
- 头文件包含:使用
round函数前,必须包含<cmath>头文件,否则编译器会提示该函数未定义。 - 数据类型匹配:虽然
round函数有针对不同浮点数类型的重载,但在实际使用中,要确保传入参数的类型与预期的返回值类型相匹配,避免出现精度损失或类型不兼容的问题 。 - 与其他取整函数的区别:
floor函数:double floor(double x);,它返回不大于x的最大整数,即向下取整。例如floor(3.6)的结果是3,floor(-3.6)的结果是-4。ceil函数:double ceil(double x);,它返回不小于x的最小整数,即向上取整。例如ceil(3.4)的结果是4,ceil(-3.4)的结果是-3。
4. 应用场景
- 数值计算:在进行一些财务计算、统计分析等需要精确到整数的数值运算中,
round函数可以帮助将计算结果进行合理的取整 。 - 图形图像处理:如上述的图像模糊处理题目中,用于将计算得到的像素灰度平均值四舍五入到最接近的整数,以满足题目要求和实际显示效果 。
- 数据输出格式化:在输出一些带有小数的数值结果时,如果希望以整数形式展示,并且遵循四舍五入规则,可以使用
round函数对数据进行预处理 。
字符数组
一、字符数组的定义与初始化
字符数组用于存储字符串(以空字符'\0'结尾的字符序列),定义方式如下:
-
基本定义
char arr[10]; // 定义长度为10的字符数组(可存储最多9个有效字符+1个'\0') -
初始化方式
- 逐个字符初始化(需手动添加
'\0')char str1[6] = {'h', 'e', 'l', 'l', 'o', '\0'}; // 正确,长度6(含'\0') - 字符串常量初始化(自动添加
'\0'):char str2[] = "hello"; // 长度自动计算为6('h','e','l','l','o','\0') char str3[10] = "hello"; // 剩余空间自动填充'\0' - 注意:若初始化长度不足且未显式加
'\0',可能导致字符串操作错误:char str4[5] = {'h','e','l','l','o'}; // 错误!无'\0',不是有效字符串
- 逐个字符初始化(需手动添加
二、字符数组的输入与输出
-
输入
cin >>:遇空格 / 回车终止,自动添加'\0',但可能截断长字符串。char str[20]; cin >> str; // 输入"hello world",实际存储"hello"(空格后被截断)cin.getline(数组名, 最大长度):读取一行(含空格),遇回车终止,自动添加'\0'。cin.getline(str, 20); // 输入"hello world",完整存储gets(数组名):C 语言函数,无长度限制(危险,易越界,C++ 不推荐)。
-
输出
cout <<:从首地址开始输出,直到遇见'\0'为止。char str[] = "hello"; cout << str; // 输出"hello"(自动忽略'\0')
3.换位输入输出
想要从数组的第二位开始输入,可以设置成arr+1开始输入
三、字符数组的常用操作(需包含<cstring>)
-
求长度:
strlen(数组名)- 返回有效字符数(不含
'\0'),未加'\0'会导致结果不可控。char str[] = "hello"; cout << strlen(str); // 输出5
- 返回有效字符数(不含
-
字符串复制:
strcpy(目标数组, 源数组)- 将源字符串复制到目标数组,需保证目标数组足够大(否则越界)。
char dest[20], src[] = "hello"; strcpy(dest, src); // dest变为"hello"
- 将源字符串复制到目标数组,需保证目标数组足够大(否则越界)。
-
字符串拼接:
strcat(目标数组, 源数组)- 将源字符串追加到目标字符串末尾(覆盖目标原
'\0'),需保证目标空间足够。char str[20] = "hello"; strcat(str, " world"); // str变为"hello world"
- 将源字符串追加到目标字符串末尾(覆盖目标原
-
字符串比较:
strcmp(数组1, 数组2)- 按 ASCII 码比较,返回:
- 正数:数组 1 > 数组 2
- 0:数组 1 == 数组 2
- 负数:数组 1 < 数组 2
cout << strcmp("apple", "banana"); // 输出负数('a' < 'b')
- 按 ASCII 码比较,返回:
四、注意事项
-
空字符
'\0'的重要性- 字符串必须以
'\0'结尾,否则strlen、cout等操作会越界访问。 - 初始化时若用字符串常量(如
"hello"),编译器自动添加'\0';手动初始化需显式添加。
- 字符串必须以
-
数组越界风险
- 输入 / 复制 / 拼接时,目标数组长度必须大于源字符串长度(含
'\0'),否则可能覆盖其他内存数据。
- 输入 / 复制 / 拼接时,目标数组长度必须大于源字符串长度(含
-
与
string的区别- 字符数组是 C 风格字符串,需手动管理内存和
'\0'; - C++ 的
string类(需包含<string>)是动态字符串,自动管理内存,推荐优先使用。
- 字符数组是 C 风格字符串,需手动管理内存和
-
字符数组的遍历
- 可通过下标遍历,直到遇见
'\0'for (int i = 0; str[i] != '\0'; i++) { cout << str[i]; }
- 可通过下标遍历,直到遇见
五、示例:综合使用
#include <iostream>
#include <cstring>
using namespace std;
int main() {
char str[20] = "hello";
cout << "长度:" << strlen(str) << endl; // 5
strcat(str, " C++");
cout << "拼接后:" << str << endl; // "hello C++"
char another[20];
strcpy(another, str);
cout << "复制后:" << another << endl; // "hello C++"
if (strcmp(str, another) == 0) {
cout << "两个字符串相等" << endl;
}
return 0;
}
以上是字符数组的核心内容,掌握这些可以应对基本的字符串处理场景,实际开发中建议优先使用 C++ 的string类以简化操作。
strlen函数
一、函数原型与头文件
- 头文件:需包含
<cstring>(或 C 语言中的<string.h>,C++ 兼容)。 - 函数原型:
size_t strlen(const char* str);- 参数
str:指向以'\0'结尾的字符数组的指针(字符串首地址)。 - 返回值:
size_t类型(无符号整数),表示字符串中有效字符的个数(不含结尾的'\0')。
- 参数
二、核心功能
计算从字符串首地址开始,到第一个 '\0'(不包含 '\0')之间的字符数量。例如:
char str[] = "hello"; // 存储为 'h','e','l','l','o','\0'
cout << strlen(str); // 输出 5(仅统计前5个有效字符)
三、使用注意事项
-
必须以
'\0'结尾- 若字符串未以
'\0'结尾,strlen会继续向后扫描内存,直到遇到随机的'\0'为止,返回结果不可控,可能导致越界访问char str[5] = {'h','e','l','l','o'}; // 无 '\0',错误! cout << strlen(str); // 结果不确定(危险)
- 若字符串未以
-
参数不能是
nullptr- 若传入空指针(
nullptr),会导致程序崩溃(未定义行为)char* str = nullptr; strlen(str); // 运行时错误
- 若传入空指针(
-
返回值是
size_t(无符号类型)- 不能直接与负数比较,否则可能出现逻辑错误。
if (strlen("a") < -1) { // 错误!size_t 与负数比较会自动转换为正数,导致判断失效 // 永远不会执行 }
- 不能直接与负数比较,否则可能出现逻辑错误。
-
只适用于 C 风格字符串
- 不能直接用于 C++ 的
std::string类(需通过c_str()方法转换):#include <string> string s = "test"; cout << strlen(s.c_str()); // 正确,通过 c_str() 获取C风格字符串指针
- 不能直接用于 C++ 的
四、示例代码
#include <iostream>
#include <cstring> // 包含 strlen
using namespace std;
int main() {
// 1. 正常字符串(带 '\0')
char str1[] = "hello world";
cout << "str1长度:" << strlen(str1) << endl; // 输出 11(不含 '\0')
// 2. 部分初始化的字符数组(剩余空间自动补 '\0')
char str2[20] = "hi";
cout << "str2长度:" << strlen(str2) << endl; // 输出 2
// 3. 手动添加 '\0'
char str3[] = {'a', 'b', 'c', '\0'};
cout << "str3长度:" << strlen(str3) << endl; // 输出 3
// 4. 空字符串(仅含 '\0')
char str4[] = "";
cout << "str4长度:" << strlen(str4) << endl; // 输出 0
return 0;
}
五、与 sizeof 的区别
| 对比项 | strlen(str) |
sizeof(str) |
|---|---|---|
| 功能 | 计算有效字符数(到 '\0' 为止) |
计算变量 / 数组的内存占用大小(字节) |
| 适用对象 | 仅 C 风格字符串(以 '\0' 结尾) |
任意变量 / 数组(包括字符数组) |
| 返回值含义 | 字符个数(无符号整数) | 内存字节数(无符号整数) |
示例:
char str[] = "hello";
cout << strlen(str); // 5(有效字符数)
cout << sizeof(str); // 6(数组总长度:5个字符 + 1个 '\0')
总结
strlen 是处理 C 风格字符串的基础函数,核心是依赖 '\0' 作为结束标志。使用时务必确保字符串正确结尾,避免越界或空指针错误。在 C++ 中,若使用 std::string 类,建议优先使用其成员函数 length() 或 size() 来获取长度,更安全便捷。
关于字符数组遇到空格自动截断的解释
一、问题根源
默认输入函数cin >>的特性:
- 读取时会跳过空白字符(空格、制表符
\t、回车\n等)。 - 遇到第一个空白字符时停止读取,并自动在字符数组末尾添加
'\0'。 - 因此,直接用
cin >>无法读取包含空格的字符串。
示例:
char str[20];
cin >> str; // 输入 "hello world"
cout << str; // 输出 "hello"(空格后内容被截断)
二、正确输入方法(3 种常用方式)
1. 使用cin.getline()(推荐)
- 功能:读取一整行字符串(包含空格),直到遇到回车
\n为止,自动在末尾添加'\0'。 - 函数原型
cin.getline(字符数组名, 最大长度, 终止符); // 终止符默认是'\n' - 参数说明:
- 第一个参数:目标字符数组(存储结果)。
- 第二个参数:最大读取长度(包含
'\0',即最多读取n-1个有效字符)。 - 第三个参数(可选):自定义终止符(遇到该字符停止,默认是回车)。
示例:
char str[20];
cin.getline(str, 20); // 输入 "hello world 123"
cout << str; // 输出 "hello world 123"(完整包含空格)
- 注意:若输入长度超过
最大长度-1,会读取前最大长度-1个字符,剩余内容留在输入缓冲区,并设置错误标志(需用cin.clear()清除)。
2. 使用cin.get()
- 功能:类似
getline(),但需手动处理'\0'(部分用法),或直接读取一行。 - 常用原型
cin.get(字符数组名, 最大长度); // 读取到回车或最大长度,自动加'\0'
示例:
char str[20];
cin.get(str, 20); // 输入 "hello world"
cout << str; // 输出 "hello world"
- 与
getline()的区别:cin.get()会将回车\n留在输入缓冲区,而cin.getline()会丢弃回车。若连续使用cin.get(),需手动清除回车,否则下次读取会直接结束char str1[10], str2[10]; cin.get(str1, 10); // 输入 "abc\n",str1存储"abc",缓冲区残留'\n' cin.ignore(); // 清除缓冲区的'\n'(关键) cin.get(str2, 10); // 才能正确读取下一行
3. 使用 C 语言的gets()(不推荐)
- 功能:读取一整行字符串(包含空格),直到回车为止,自动加
'\0'。 - 缺点:无长度限制,若输入过长会导致数组越界,引发内存安全问题(C++11 已弃用,C11 移除)。
示例:
char str[20];
gets(str); // 输入 "hello world"(危险:若输入超20字符会越界)
cout << str;
三、输入缓冲区问题及解决
-
问题场景:若前面使用过
cin >>读取数据(如整数、单个字符),输入缓冲区会残留回车\n,导致后续getline()/get()直接读取到'\n',返回空字符串。int n; cin >> n; // 输入 "5\n",缓冲区残留'\n' char str[10]; cin.getline(str, 10); // 直接读取到'\n',str为空 -
解决方法:用
cin.ignore()清除缓冲区残留的回车:int n; cin >> n; cin.ignore(); // 清除缓冲区的'\n' char str[10]; cin.getline(str, 10); // 可正常读取
四、总结
| 输入方式 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
cin.getline() |
安全(有长度限制)、自动处理回车 | 需注意输入缓冲区残留问题 | ★★★★★ |
cin.get() |
安全、灵活(可自定义终止符) | 回车会残留缓冲区,需手动清除 | ★★★☆☆ |
gets() |
用法简单 | 无长度限制,易越界,已弃用 | ★☆☆☆☆ |
最佳实践:
- 优先使用
cin.getline(字符数组, 长度)读取带空格的字符串。 - 若前面用了
cin >>,务必用cin.ignore()清除缓冲区的回车。 - 若场景允许,建议使用 C++ 的
std::string配合getline(cin, string),更安全且无需手动管理长度。
这里特别说一下占位符 %s它其实不能简单地等同于字符串。它的规则是,从当前第一个非空白字符开始读起,直到遇到空白字符(即空格、换行符、制表符等)为止。因为 %s 的读取不会包含空白字符,所以无法用来读取多个单词,除非多个 %s 一起使用。这也意味着scanf()不适合读取可能包含空格的字符串,比如书名或歌曲名。另外有一个细节注意一下,scanf()遇到 %s 占位符,会在字符串变量末尾存储一个 \0 字符。
fgets
用法:
- 包含头文件
<cstdio>。 - 调用格式:
fgets(缓冲区地址, 最大读取字符数, 流指针)。- 示例:
char buf[100]; fgets(buf, 100, stdin);(从标准输入读入)。
- 示例:
规范:
- 缓冲区大小需 ≥ 最大读取数(
n),避免越界。 - 处理换行符:若读取到换行符,会保留在缓冲区中,可通过
strcspn(buf, "\n")定位并替换为\0去除。 - 检查返回值:若返回
NULL,需判断是读取失败还是到文件尾(可结合feof或ferror区分)。 - 不适合二进制流:可能因
\0误判字符串结束。 - fgets 也是从第一个字符开始读取,最多读取 num-1 个字符,最后一个位置留给 \8,如果 num 的长度是远大于输入的字符串长度,就会一直读取到\n 停止,并且会读取 \n,将 \n 作为读取到内容的一部分,同时在读取到的内容后自动加上\0。
- 如果要用
fgets得到正确结果,必须手动去除末尾的换行符'\n',否则会导致长度计算错误。而cin >> arr1无需额外处理if (len > 0 && arr1[len - 1] == '\n') { arr1[len - 1] = '\0'; len--; // 修正长度 }
strcpy 和 strcat
一、strcpy 函数(字符串复制)
1. 函数原型
char* strcpy(char* destination, const char* source);
- 参数:
destination:目标字符串的指针(需足够大以容纳源字符串,包括空字符)。source:源字符串的指针(const 修饰,确保源字符串不被修改)。
- 返回值:指向目标字符串
destination的指针。
2. 功能
将源字符串(source)从第一个字符开始,包括空字符 '\0' 在内的所有内容复制到目标字符串(destination)中,覆盖目标字符串原有内容。例如:
#include <cstring>
#include <iostream>
using namespace std;
int main() {
char dest[20] = "Hello";
const char src[] = "World";
strcpy(dest, src); // 将"World"复制到dest,覆盖原有内容
cout << dest; // 输出:World(包含'\0',结束符自动复制)
return 0;
}
3. 注意事项
- 缓冲区溢出风险:若目标字符串
destination的空间小于源字符串source(包括空字符),会导致缓冲区溢出,破坏内存数据,引发程序崩溃或安全问题。例如:char dest[3]; strcpy(dest, "1234");(源字符串长度 4,目标仅 3,溢出)。 - 必须以空字符结束:源字符串
source必须包含空字符'\0',否则函数会一直复制直到遇到内存中的随机空字符,导致不可预期的结果。 - 目标字符串可修改:
destination必须是可修改的字符数组(不能是字符串常量,如char* dest = "test";是只读的,复制会报错)。
二、strcat 函数(字符串拼接)
1. 函数原型
char* strcat(char* destination, const char* source);
- 参数:
destination:目标字符串的指针(需足够大以容纳拼接后的结果,包括原有内容、源字符串和空字符)。source:待拼接的源字符串的指针(const 修饰)。
- 返回值:指向目标字符串
destination的指针。
2. 功能
在目标字符串(destination)的末尾空字符 '\0' 处,拼接源字符串(source)的内容(包括源字符串的空字符),即:
- 先找到目标字符串的空字符位置;
- 从该位置开始复制源字符串的所有字符(包括
'\0')。例如:
#include <cstring>
#include <iostream>
using namespace std;
int main() {
char dest[20] = "Hello";
const char src[] = " World";
strcat(dest, src); // 拼接后:"Hello World"
cout << dest; // 输出:Hello World
return 0;
}
3. 注意事项
- 缓冲区溢出风险:目标字符串
destination的总空间必须大于 “原有长度 + 源字符串长度”,否则会溢出。例如:char dest[10] = "Hi"; strcat(dest, "abcdefgh");(总长度需 1+8+1=10,刚好;若源再长 1 则溢出)。 - 目标和源都需以空字符结束:
- 目标字符串必须包含
'\0',否则函数无法找到拼接的起始位置,会破坏内存。 - 源字符串必须包含
'\0',否则会一直拼接直到遇到随机空字符。
- 目标字符串必须包含
- 避免自身拼接:不能将字符串拼接给自身(如
strcat(dest, dest);),会导致逻辑错误(目标的空字符被覆盖,函数无法终止)。
三、strcpy 与 strcat 的共性与区别
| 对比项 | strcpy(dest, src) |
strcat(dest, src) |
|---|---|---|
| 核心操作 | 覆盖目标字符串,复制源字符串 | 在目标字符串末尾追加源字符串 |
| 目标起始位置 | 从目标字符串的首地址开始 | 从目标字符串的空字符位置开始 |
| 空字符处理 | 复制源字符串的 '\0' 到目标 |
将源字符串的 '\0' 追加到目标末尾 |
| 前提条件 | 目标需足够大,源需以 '\0' 结束 |
目标和源都需以 '\0' 结束,目标需足够大 |
| 风险 | 缓冲区溢出(目标空间不足) | 缓冲区溢出(目标总空间不足) |
四、使用建议
- 安全性问题:
strcpy和strcat均不检查目标缓冲区大小,存在安全风险,C++11 后推荐使用更安全的strncpy和strncat(可指定最大复制 / 拼接长度),或直接使用std::string(自动管理内存,无需手动处理缓冲区)。 - 头文件:必须包含
<cstring>才能使用这两个函数。 - 字符串常量:源字符串可以是字符串常量(如
"test"),但目标字符串必须是可修改的字符数组。
例如,使用 std::string 替代更简单安全:
#include <string>
#include <iostream>
using namespace std;
int main() {
string dest = "Hello";
string src = " World";
dest = src; // 替代strcpy
dest += src; // 替代strcat
cout << dest; // 输出:World World
return 0;
}
tolower 和 islower
一、islower 函数(判断字符是否为小写字母)
1. 函数原型
int islower(int c);
- 参数:
c是需要判断的字符(通常是char类型,会被隐式转换为int)。特殊值:若c为EOF(文件结束标志),函数返回0。 - 返回值:
- 若字符
c是小写字母(a-z),返回非 0 值(通常为1或其他非 0 整数,具体取决于实现); - 否则返回
0(包括大写字母、数字、符号、控制字符等)。
- 若字符
2. 功能
判断输入的字符是否为小写英文字母(a 到 z)。例如:
#include <cctype>
#include <iostream>
using namespace std;
int main() {
cout << islower('a') << endl; // 输出非0(如1),是小写字母
cout << islower('B') << endl; // 输出0,是大写字母
cout << islower('3') << endl; // 输出0,是数字
cout << islower('$') << endl; // 输出0,是符号
return 0;
}
3. 注意事项
- 参数类型:虽然参数是
int,但实际使用时通常传入char类型(会自动提升为int)。若传入超出unsigned char范围的负值(且不是EOF),行为未定义。 - 依赖 ASCII 码:仅对 ASCII 码中的
a-z(ASCII 值 97-122)返回真,对非英文字母的小写字符(如带 accents 的字符é)不识别。
二、tolower 函数(将字符转换为小写字母)
1. 函数原型
int tolower(int c);
- 参数:
c是需要转换的字符(char类型隐式转换为int)。特殊值:若c为EOF,返回EOF。 - 返回值:
- 若字符
c是大写字母(A-Z),返回对应的小写字母(a-z)的 ASCII 码值; - 若
c是小写字母或非字母字符(数字、符号等),返回c本身(不做转换)。
- 若字符
2. 功能
将大写英文字母转换为对应的小写字母,对其他字符不做处理。例如:
#include <cctype>
#include <iostream>
using namespace std;
int main() {
cout << (char)tolower('A') << endl; // 输出 'a'(转换为小写)
cout << (char)tolower('b') << endl; // 输出 'b'(本身是小写,不转换)
cout << (char)tolower('5') << endl; // 输出 '5'(非字母,不转换)
return 0;
}
3. 注意事项
- 转换范围:仅对大写字母
A-Z(ASCII 值 65-90)有效,转换后为a-z(97-122),其他字符保持不变。 - 返回值类型:返回值是
int类型的 ASCII 码,若需直接使用字符,需显式转换为char(如上面示例中的(char)tolower(...))。 - 与
islower的配合:通常先使用islower判断是否为大写字母(或直接转换,因为非大写字母会原样返回),再进行处理。
三、islower 与 tolower 的共性与区别
| 对比项 | islower(c) |
tolower(c) |
|---|---|---|
| 核心功能 | 判断是否为小写字母 | 将大写字母转换为小写 |
| 参数 | 单个字符(int 类型) |
单个字符(int 类型) |
| 返回值 | 非 0(是小写)或 0(否) | 转换后的字符(int 类型) |
| 依赖头文件 | 均需包含 <cctype> |
均需包含 <cctype> |
| 适用场景 | 字符类型判断(如校验输入) | 字符大小写转换(如统一格式) |
四、使用建议
- 头文件:必须包含
<cctype>头文件,否则函数可能未定义。 - 处理字符串:若需对整个字符串进行判断或转换,需结合循环遍历每个字符。例如:
// 将字符串转换为全小写 #include <cctype> #include <iostream> #include <cstring> using namespace std; int main() { char str[] = "Hello World!"; for (int i = 0; i < strlen(str); i++) { str[i] = tolower(str[i]); // 逐个转换为小写 } cout << str; // 输出:hello world! return 0; } - 宽字符处理:对于宽字符(如
wchar_t),需使用对应的iswlower和towlower函数(在<cwctype>头文件中)。
通过这两个函数,可以方便地处理字符的大小写判断和转换,常用于文本处理、输入校验、格式统一等场景
isupper和toupper
一、isupper 函数(判断字符是否为大写字母)
1. 函数原型
int isupper(int c);
- 参数:
c为待判断的字符(通常是char类型,会隐式转换为int)。特殊值:若c为EOF(文件结束标志),返回0。 - 返回值:
- 若字符
c是大写字母(A-Z),返回非 0 值(如1,具体值取决于实现); - 否则返回
0(包括小写字母、数字、符号、控制字符等)。
- 若字符
2. 功能示例
#include <cctype>
#include <iostream>
using namespace std;
int main() {
cout << isupper('A') << endl; // 输出非0(如1),是大写字母
cout << isupper('b') << endl; // 输出0,是小写字母
cout << isupper('5') << endl; // 输出0,是数字
cout << isupper('@') << endl; // 输出0,是符号
return 0;
}
3. 注意事项
- 参数范围:若传入的
c是负值(且不是EOF),行为未定义,通常建议传入unsigned char类型的字符(或其 ASCII 码值)。 - 仅识别 ASCII 大写字母:仅对
A-Z(ASCII 值 65-90)有效,对非英文字母的大写字符(如带符号的É)不识别。
二、toupper 函数(将字符转换为大写字母)
1. 函数原型
int toupper(int c);
- 参数:
c为待转换的字符(char类型隐式转换为int)。特殊值:若c为EOF,返回EOF。 - 返回值:
- 若字符
c是小写字母(a-z),返回对应的大写字母(A-Z)的 ASCII 码值; - 若
c是大写字母或非字母字符(数字、符号等),返回c本身(不转换)。
- 若字符
2. 功能示例
#include <cctype>
#include <iostream>
using namespace std;
int main() {
cout << (char)toupper('a') << endl; // 输出 'A'(转换为大写)
cout << (char)toupper('B') << endl; // 输出 'B'(本身是大写,不转换)
cout << (char)toupper('3') << endl; // 输出 '3'(非字母,不转换)
return 0;
}
3. 注意事项
- 转换范围:仅对小写字母
a-z(ASCII 值 97-122)有效,转换后为A-Z(65-90),其他字符保持不变。 - 返回值类型:返回
int类型的 ASCII 码,需显式转换为char才能直接作为字符使用(如示例中的(char)toupper(...))。
三、isupper 与 toupper 的共性与区别
| 对比项 | isupper(c) |
toupper(c) |
|---|---|---|
| 核心功能 | 判断是否为大写字母 | 将小写字母转换为大写 |
| 参数类型 | 单个字符(int 类型) |
单个字符(int 类型) |
| 返回值 | 非 0(是大写)或 0(否) | 转换后的字符(int 类型) |
| 依赖头文件 | 需包含 <cctype> |
需包含 <cctype> |
| 典型场景 | 校验输入是否为大写字母 | 统一字符串为大写格式 |
四、使用建议
- 处理字符串:对整个字符串操作时,需循环遍历每个字符。例如将字符串转为全大写:
#include <cctype> #include <iostream> #include <cstring> using namespace std; int main() { char str[] = "hello World!"; for (int i = 0; i < strlen(str); i++) { str[i] = toupper(str[i]); // 逐个转换为大写 } cout << str; // 输出:HELLO WORLD! return 0; } - 宽字符支持:对于宽字符(
wchar_t),需使用<cwctype>头文件中的iswupper和towupper函数。 - 与其他函数配合:常与
islower/tolower配合,实现大小写的双向判断和转换(如统一字符串格式)。
这两个函数是字符处理的基础工具,广泛应用于文本格式化、输入验证、大小写敏感的逻辑判断等场景。
strstr( 字符串查找的函数)
1. 函数原型
char* strstr(const char* haystack, const char* needle);
haystack:表示要在其中进行查找的主字符串(也被称为 “大字符串” 或 “源字符串” ),它是一个指向以空字符'\0'结尾的字符数组的指针,并且在函数执行过程中,该字符串的内容不会被修改(因为使用了const修饰)。needle:表示要查找的子字符串(也被称为 “小字符串” ),同样是一个指向以空字符'\0'结尾的字符数组的指针 。
2. 功能描述
strstr 函数的作用是在 haystack 字符串中查找 needle 子字符串第一次出现的位置。查找时区分大小写,并且查找的是连续的字符序列。
3. 返回值
- 如果在
haystack中找到了与needle完全匹配的子字符串,函数将返回一个指针,该指针指向haystack中找到的子字符串的第一个字符 。 - 如果在
haystack中没有找到needle子字符串,函数将返回nullptr(在 C 语言中返回NULL,在 C++ 中NULL会被转换为nullptr,它是一个空指针常量)。
4. 使用示例
#include <iostream>
#include <cstring>
using namespace std;
int main() {
char str1[] = "Hello, world! This is a test string.";
char str2[] = "is";
char* result = strstr(str1, str2);
if (result != nullptr) {
cout << "子字符串 \"" << str2 << "\" 在主字符串中被找到,位置是: " << result - str1 << endl;
} else {
cout << "子字符串 \"" << str2 << "\" 在主字符串中未被找到" << endl;
}
return 0;
}
在上述示例中,首先定义了主字符串 str1 和要查找的子字符串 str2,然后调用 strstr 函数进行查找。如果找到了子字符串,通过计算返回指针与主字符串起始地址的差值,得到子字符串在主字符串中的起始位置并输出;如果没找到,输出相应提示信息。
5. 注意事项
-
传入的字符串必须是以空字符
'\0'结尾的,否则函数可能会访问到非法内存,导致程序崩溃。 - 当
needle指向的字符串为空字符串时,strstr函数会返回haystack的起始地址,因为空字符串被认为是任何字符串的子字符串。 - 函数只能查找普通的以
'\0'结尾的 C 风格字符串,如果要处理std::string类型的字符串,可以先将std::string转换为 C 风格字符串(使用c_str()成员函数 ),再调用strstr;或者直接使用std::string类提供的find成员函数进行查找 ,std::string::find的功能更为丰富,且不需要进行类型转换。例如:
#include <iostream>
#include <string>
using namespace std;
int main() {
string str1 = "Hello, world! This is a test string.";
string str2 = "is";
size_t pos = str1.find(str2);
if (pos != string::npos) {
cout << "子字符串 \"" << str2 << "\" 在主字符串中被找到,位置是: " << pos << endl;
} else {
cout << "子字符串 \"" << str2 << "\" 在主字符串中未被找到" << endl;
}
return 0;
}
std::string::find 函数返回找到的子字符串的起始位置(索引从 0 开始),如果未找到则返回 std::string::npos(表示一个无效的位置,通常是 -1 转换为无符号类型后的最大值 )。
更多推荐


所有评论(0)