一 、 字符数组
1.1 介绍
数组的元素如果是字符类型 , 这种数组就是字符数组 , 字符数组可以是一维数组 , 可以是二维数组 (多维数组)。
接下来主要讨论一维的字符数组 :
char arr1[5] //一维数组
char arr2[3][5] // 二维数组
C语言 中使用 双引号 括起来一串字符表示字符串 , 这种方式虽然在C++中也是支持的(C++提供了string) ,但我们一般会将这种字符串称为 C语言风格的字符串 。如果需要将一个C语言风格的字符串存储起来 , 就可以使用字符数组 。
1.2 初始化
1.字符数组的创建: char a[10];
2 . 字符数组的创建同一维数组的创建一样 , 就不再赘述 , 字符串数的初始化有2种方式:
#include <iostream>
#include <cstdio>
using namespce std;
int main()
{
//1.直接使用字符串初始化
char ch1[10] = "abcdef";//后面的默认为0
char ch2[] = "abcdef";//数组大小根据初始化进行调整
//2.用字符进行初始化
char ch3[10] = {'a','b','c','d','e','f'};//后面默认为0
char ch4[] = {'a','b','c','d','e','f'};
return 0;
}

如果调试看⼀下 ch2 和 ch4 数组的内容,我们会明显的发现,数组 ch2 中多⼀个 '\0' 字符,这是因为字符串的末尾其实隐藏⼀个 '\0' 字符,这个 '\0' 是字符串的结束标志,在打印字符串的时候遇到 '\0' ,打印结束。

注意 ,字符数组的打印和整型数组的打印一定要区分开来 !!!
1.3 字符串长度 - strlen
问题引入:我们看下面的ch1 , 数组长度是10 , 但是数组并没有被字符填满 , 字符串的长度是6,这时候要怎么计算 ?

C/C++中有一个库函数 : strlen , 可以求字符串长度 , 其实统计的就是字符串中 \0 之前的字符个数 。strlen 需要的头文件是<cstring>
size_t strlen ( const char * str );
//str - 指针,存放的是字符串的起始地址,从这个地址开始计算字符串的长度
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char ch1[10] = "abcdef";
int n = strlen(ch1);
cout << n << endl;
int sz = sizeof(ch1)/sizeof(ch1[0]);
cout << sz << endl;
return 0;
}

字符数组的元素个数 , 大小是用sizeof求 ; 字符数组的长度是用strlen来求!
1.4 字符数组的输入
输入没有空格字符串
1)使用scanf函数和字符数组来实现:(这里推荐使用VS,调试观察)
数组首元素就是数组的地址
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char ch[20] = { 0 };
//输入
scanf("%s", ch);
//输出
printf("%s", ch);
return 0;
}
我们发现会报错 , 不用担心 , 按照下面添加语句即可 :



#define _CRT_SECURE_NO_WARNINGS 1
继续运行 :

按下F10 进行调试 , 可以看到没有初始化的字符元素默认为'\0'

2)使用cin 和 字符数组来实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char ch[20] = { 0 };
//输入
cin >> ch;
//输出
cout << ch << endl;
return 0;
}

3) 上面两个代码是将字符串读取后从数组的其实位置开始存放的 , 当然也可以指定位置存放 。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char ch[20] = { 0 };
//输入
//+2的意思就是跳过两个元素 , 就是第三个元素
cin >> ch + 2;
//输出
cout << ch + 2<< endl;
return 0;
}

那么从第n个元素开始存放 , 就应该是 cin >> arr + n ; 使用scanf 也是一样的 。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char ch[20] = { 0 };
//输入
scanf("%s", ch + 2);
//输出
printf("%s", ch + 2);
return 0;
}
输入有空格的字符串
1)发现问题:
前面我们讲解了scanf 和 cin 读取不含空格的字符串 , 一切正常 , 那如果我们输入的字符串中带有空格 , 实际上是无法正常打印出我们想要的结果 , 而是读到空白字符 , 编译器默认读完 。
2)scanf的方式 :
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char ch[20] = { 0 };
//输入
scanf("%s", ch );
//输出
printf("%s", ch );
return 0;
}

当输入"abc def"的时候 , 实际上scanf 只读取了abc就结束了 , 也就是相当于遇到空格就结束了。
这里特别说一下占位符 %s 。 **它其实不能简单地等同于字符串。**它的规则是,从当前第⼀个
非空白字符开始读起,直到遇到空白字符(即空格、换行符、制表符等)为止 。%s 的读取不会包含空白字符,所以无法用来读取多个单词,除非多个 %s ⼀起使用。
这也意味着, scanf() 不适合读取可能包含空格的字符串,比如书名或歌曲名。 另外有⼀
个细节注意⼀下, scanf() 遇到 %s 占位符,会在字符串变量末尾存储⼀个 \0 字符 。
同时scanf() , 将字符串读入字符数组的时候 , 不会检测字符串是否超过了数组的长度 。

为了防止这种情况 , 使用%s 占位符的时候 , 可以指定读入字符串的最长长度 , 即写成**%[m]s , 其中的[m]是一个整数 , 表示读取字符串的最长长度 , 后面的字符将被丢弃**。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char ch[20] = { 0 };
//输入
scanf("%5s", ch);
//输出
printf("%s", ch);
return 0;
}

上面的%5s表示最多读取用户输入的10个字符 ,后面的字符将被丢弃 , 这样就不会有数组溢出的风险了。
3)cin的方式:

结果都一样~~~~, 没有任何区别!!!
其实cin在读取一个字符串的时候 ,在遇到空白字符的时候 , 就认为字符串就结束了 ,往后读取剩余的字符,同时将已经读取到的字符串末尾加上\0 , 直接存储起来 。
4)解决问题:
4-1)gets 和 fgets
使用gets函数的方式 , 这种方式能解决问题 , 但是 因为gets 存在 安全性问题 , 在C++11中取消了gets , 随后给出了更加安全的方案:fgets 。
char * gets ( char * str );
char * fgets ( char * str, int num, FILE * stream );
| | |
地址 最多存放的大小 流
gets 原理 : gets 是从第一个字符开始读取的 , 一致读取到\n停止 , 但是不会读取\n , 也就是读取到的内容中不包含\n , 但是会在读取到的内容后面自动加上 \0 。 (读到\n,但不读取\n,会自动加\0)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char arr[10] = {0};
gets(arr);
printf("%s",arr);
return 0;
}

小提示 : Dec - C++中使用gets函数 , 没有报错 , 但是在其他IDE上 , 比如VS , 就会报错。所以慎用gets !!!!

4-2)fgets
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char arr[10] = {0};
fgets(arr,sizeof(arr),stdin);
printf("%s",arr);
return 0;
}

fgets 原理 : fgets 也是从第一个字符开始读取,最多读取 num-1 个字符,最后⼀个位置留给 \0 ,如果 num 的长度是远大于输入的字符串长度,就会⼀直读取到 \n 停止,并且会读取 \n ,将 \n 作 为读取到内容的⼀部分,同时在读取到的内容后自动加上 \0 。(最多读num-1个,读取\n ,然后加\0) --> 这时候需要注意遍历数组的时候,需要考虑换行问题。

用gets 和 fgets 同时输入"abc def" , 两者差异如下:

4-3) scanf
当然C语言中使用 scanf 函数其实也能做到读取带有空格的字符串,只是不常见。
将 "%s" 改成 "%[^\n]s" , 其中在 % 和 s 之间加上了 [^\n] ,意思是⼀直读取,直到遇到
\n ,这样即使遇到空格也就不会结束了。 这种方式读取,不会将 \n 读取进来,但是在读取到的字符串末尾加上 \0 。(不读取\n , 自动加\0)
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char arr[10] = { 0 };
scanf("%[^\n]s", arr);
printf("%s", arr);
return 0;
}

4-4)getchar
使用getchar 逐个字符的读取 , 也是可以读取一个字符串的。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char arr[20];
int ch = 0;
int i = 0;
while ((ch = getchar()) != '\n')
{
arr[i++] = ch;
}
printf("%s\n", arr);
return 0;
}

1.5 字符数组的输出
1 . C语言中可以在printf 函数中使用 %s 占位符的方式 , 打印字符数组中的字符串。
2 . C++中使用 cout , 可以直接打印字符数组中的字符串内容 。
3 . 也可以采用循环的方式逐个字符打印字符串内容 。
---> 字符串的结束标志是\0
----> strlen
4 . 如果没有\0的字符数组 , 计算数组的长度 , for循环
方法一:printf 、cout
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char arr[] = "Hello Wrold!";
printf("%s\n",arr);
cout << arr << endl;
return 0;
}
方法二:循环
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char arr[] = "Hello Wrold!";
int i = 0;
while(arr[i] != '\0')
{
cout << arr[i];
i++;
}
cout << endl;
return 0;
}
方法三:单个字符打印,根据字符长度来逐个打印 (strlen 可以用来计算字符串的长度,不包含\0,记住需要包含头文件<cstring>
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char arr[] = "Hello Wrold!";
for(int i = 0; i< strlen(arr);i++)
{
cout << arr[i];
}
cout << endl;
return 0;
}
方法四:当我们遇到没有\0的字符数组的时候 , 我们可以计算数组的长度来打印字符数组
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char arr[] = {'a','b','c','d'};
int sz = sizeof(arr)/sizeof(arr[0]);
for(int i = 0; i< sz;i++)
{
cout << arr[i];
}
cout << endl;
return 0;
}
方法很多 , 根据需求来选择!
1.6 strcpy
使用字符数组可以存放字符串 , 但是字符数组能否直接赋值呢?
比如:
char arr1[] = "abcdef";
char arr2[20] = {0};
**arr2 = arr1;//这样赋值可以吗?
不可以 , 常量不可改
其实C/C++中有一个库函数 --> strcpy , 可以拷贝字符串

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char arr1[] = "abcdef";
char arr2[20] = {0};
strcpy(arr2,arr1);
cout << arr2 << endl;
return 0;
}
1.7 strcat
在一个字符的末尾追加一个字符串 , 那字符串能直接追加吗?
char arr1[20] = "hello ";
char arr2[] = "world";
**arr1 += arr2;//这样也是不行的
其实C/C++中有一个库函数strcat , 可以实现该功能。
char * strcat ( char * destination, const char * source );

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int main()
{
char arr1[20] = "Hello ";
char arr2[] = "World!";
strcat(arr1,arr2);
cout << arr1 << endl;
return 0;
}
注意:追加到的数组需要空间大小足够,否组会越界
1.8 练习
练习一:自动修正


#include <iostream>
#include <cstdio>
using namespace std;
const int N = 110;
char arr[N];
int main()
{
cin >> arr;
//cin输入字符串后,会在末尾存放\0
//调整字符串
int i = 0;
while(arr[i] != '\0')
{
if(arr[i] >= 'a' && arr[i] <= 'z')
{
arr[i] -= 32;
//小写转大写
}
i++;
}
cout << arr << endl;
return 0;
}
除了借助'\0' , 还可以借助strlen 计算字符数组的长度 :
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 110;
char arr[N];
int main()
{
cin >> arr;
int len = strlen(arr);
int i = 0;
for(int i = 0;i < len ; i++)
{
if(arr[i] >= 'a' && arr[i] <= 'z')
{
arr[i] -= 32;
//小写转大写
}
}
cout << arr << endl;
return 0;
}
这里再给大家介绍两组函数:islowe 和 tolower , 需要的头文件是<cctype>
字符分类函数和字符转换函数:<cctype> (ctype.h) - C++ Reference
**int islower ( int c ); //判断字符是否是小写字母
**int tolower ( int c ); //转换成小写字母
1 . islower 是C/C++中提供的一个判断字符是否是小写字符的一个函数 ,如果c是小写字母 , 函数返回一个非0的数字 , 如果不是小写字母 , 函数返回 0, 还有一个函数是 issupper ,是判断是否大写字母的 。

2 . tolower 是C/C++中提供的一个将参数C 从大写字母转化成小写字母的函数 ,如果C是小写字母将什么也不发生。
有了以上的函数,我们可以对这道题目的代码进行修改。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
using namespace std;
const int N = 110;
char arr[N];
int main()
{
cin >> arr;
for(int i = 0;arr[i] != '\0';i++)
{
if(islower(arr[i]))
arr[i] = toupper(arr[i]);
}
cout << arr << endl;
return 0;
}
练习二:统计数字字符个数

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 265;
char arr[N];
int main()
{
//使用fgets读取带空格的字符串时候,会读取\n,并将\n也读取到arr中,然后末尾加\0
fgets(arr,265,stdin);
int i = 0;
int c = 0;
while(arr[i] != '\n')
{
if(arr[i] >= '0' && arr[i] <= '9')
c++;
i++;
}
cout << c << endl;
return 0;
}
判断一个字符是否是数字字符, 有一个函数是isdigit , 可以直接使用
int isdigit ( int c );
如果参数c是数字字符 , 则返回非0的数值 , 如果不是数字字符 , 则返回0
需要包含头文件<cctype>
#include <iostream>
#include <cstdio>
#include <cctype>
using namespace std;
const int N = 265;
char arr[N];
int main()
{
//使用fgets读取带空格的字符串时候,会读取\n,并将\n也读取到arr中,然后末尾加\0
fgets(arr,265,stdin);
int i = 0;
int c = 0;
while(arr[i] != '\n')
{
if(isdigit(arr[i]))
c++;
i++;
}
cout << c << endl;
return 0;
}
练习三:整理药名

#include <iostream>
#include <cstdio>
#include <cctype>
using namespace std;
const int N = 25;
char arr[N];
int main()
{
int n;
cin >> n;
while(n--)
{
//输入药名
cin >> arr;
//处理第一个字符
if(islower(arr[0]))
arr[0] = toupper(arr[0]);
int j = 1;
while(arr[j] != '\0')
{
if(isupper(arr[j]))
arr[j] = tolower(arr[j]);
j++;
}
//输出
cout << arr << endl;
}
return 0;
}
练习四:基因相关性

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 510;
char a1[N];
char a2[N];
int main()
{
double n = 0;
cin >> n;
cin >> a1;
cin >> a2;
int len = strlen(a1);
int c = 0;
for(int i = 0; i < len ; i++)
{
if(a1[i] == a2[i])
c++;
}
if(c*1.0 / len >= n)
cout << "yes" << endl;
else
cout << "no" << endl;
return 0;
}
练习五:输出亲朋字符串


#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 110;
char arr[N];
int main()
{
cin >> arr;
//n-1
int i = 0;
while(arr[i+1] != '\0')
{
char tmp = arr[i] + arr[i+1];
cout << tmp;
i++;
}
//n
cout << (char)(arr[i] + arr[0]) << endl;
return 0;
}
方法二 : 我们发现 (最后一个元素的下标) % 元素个数 == 第一个元素的下标 , 借助这一个特点 , 我们可以统一用一个式子来求亲朋数 , 需要注意的是 , % 运算符 的优先级比 + 高 , 所以需要加上小括号
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 110;
char arr[N];
int main()
{
cin >> arr;
int i = 0;
int len = strlen(arr);
while(arr[i] != '\0')
{
//% 的优先级比 + 的高
char tmp = arr[i] + arr[(i+1)%len];
cout << tmp;
i++;
}
return 0;
}
练习六:验证子串

介绍一个函数 , 可以用来判断字串 : strstr
strstr - C++ Reference
const char * strstr ( const char * str1, const char * str2 ) ;
这个函数本质上是用来查找子字符串的 。在str1 字符串中查找str2 字符串第一次出现的位置,如果找到了,就返回第一次出现的地址 ; 如果没有找到 , 就返回NULL(0)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 25;
char a1[N];
char a2[N];
int main()
{
cin >> a1;
cin >> a2;
if(strstr(a1,a2))
cout << a2 << " is substring of " << a1 << endl;
else if(strstr(a2,a1))
cout << a1 << " is substring of " << a2 << endl;
else
cout << "No substring" << endl;
return 0;
}
练习七:找到第一个只出现一次的字符


#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
//方法一:暴力求解
const int N = 1110;
char arr[N];
int main()
{
cin >> arr;
int i = 0;
int flag = 0;//表示没有出现仅一个的字符
while(arr[i])
{
int j = 0;
int c = 0;
while(arr[j])
{
if(arr[i] == arr[j])
{
c++;
}
j++;
}
if(c == 1)
{
flag = 1;
cout << arr[i] << endl;
break;
}
i++;
}
if(flag == 0)
cout << "no" << endl;
return 0;
}
方法二:题目说字符串中只有小写字母,**小写字母的ASCII值的范围是:97~122,在C和C++中每个字符都有ASCII值,**标准的ASCII码表中有128个字符,ASCII值的范围是0~127.
所以我们创建⼀个128元素的整型数组 ,下标分别是0~127,下标正好和字符的ASCII值范围对应,那么整型的数组的⼀个元素就为⼀个字符计数就可以。每读取⼀个字符,根据字符的ASCII值将数组中下标为字符ASCII值的元素值+1,相当于统计这个字符出现的次数。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1110;
char arr[N];
int num[128] = {0};
int main()
{
int i = 0;
while((arr[i] = getchar())!='\n')
{
num[arr[i]]++;
i++;
}
i = 0;
int flag = 0;
while(arr[i])
{
if(num[arr[i]] == 1)
{
flag = 1;
cout << arr[i] << endl;
break;
}
i++;
}
if(flag == 0)
cout << "no" << endl;
return 0;
}