该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
3 字符串:字符串原理
字符串不只是用来读取的:
C中的字符串其实就是char数组;那字符串能用来干嘛?
该String.h出场了:
String.h是C标准库中的一员;负责处理字符串;如果想要连接、比较或复制字符串,String.h中的函数就可以派上用处;
先看一个目标:
-创建字符串数组,并使用strstr()函数搜索字符串;
创建数组的数组:
字符串本身是字符数组,所以用数据记录多个字符串,就是说需要创建数组的数组;
如:
char tracks[][12] = {"abc","123"};
第一对方括号用来访问有所有字符串组成的数组;(编译器可以识别有两个字符串,所以不用指定长度)
第二对方括号用来访问每个单独的字符串;(假设每个字符串最大长度为12)
这就是一个数组的数组;
可以像这样访问:
tracks[1] -> 读取某个字符串;
tracks[1][2] -> 读取某个字符串的某个字符;
(Code3_1)
/*
* 字符串数组
*/
#include <stdio.h>
int main() {
char tracks[][12] = {
"I love you1",
"I love you2",
"I love you3",
"I love you4",
"I love you5",
"you love I1",
"you love I2",
"you love I3",
"you love I4",
"you love I5",
};
printf("Tracks number:%lu\n",sizeof(tracks));
for (int i = 0 ; i < sizeof(tracks)/sizeof(tracks[0]); i++) {
printf("Tracks[%i] %s number:%lu\n",i,tracks[i],sizeof(tracks[i]));
for (int j = 0; j < sizeof(tracks[i]); j++) {
// printf("%c",tracks[i][j]);
}
}
return 0;
}
log:
Tracks number:120
Tracks[0] I love you1 number:12
Tracks[1] I love you2 number:12
Tracks[2] I love you3 number:12
Tracks[3] I love you4 number:12
Tracks[4] I love you5 number:12
Tracks[5] you love I1 number:12
Tracks[6] you love I2 number:12
Tracks[7] you love I3 number:12
Tracks[8] you love I4 number:12
Tracks[9] you love I5 number:12
这里实践了访问数组的方式,还有如何计算数组长度;
找到包含搜索文本的字符串:
输入指定字符串,然后遍历数组,包含指定字符串的话,就将遍历到的字符串打印出来;
那么如何判断字符串中包含某段文本呢?
使用string.h:
安装C编译器时可以免费得到一批很有用的代码——C标准库;
可以做很多事:打开文件、做算术、管理存储器;标准库分了好几个部分,每个部分都有一个头文件,列出这部分标准库中的所有函数;
目前用到的stdio.h,提供了标准输入/输出函数,如printf和scanf;
string.h就是标准库中处理字符串的代码;稳定可靠,速度也很快;比如 比较、复制、搜索、切割字符串等;
首先你需要,#include <string.h>
再看看常用的一些函数:
-strchr() -> 在字符串中查找字符
-strcmp() -> 比较字符串
-strstr() -> 在字符串中查找字符
-strcpy() -> 复制字符串
-strlen() -> 返回字符串长度
-strcat() -> 连接字符串
使用strstr()函数:
例如:strstr("abcdef","abc");
strstr()函数会在第一个字符串中查找第二个字符串;
如果找到,返回第二个字符串在存储器中的位置;
如果找不到,就会返回0;(C中0相当于假)
(Code3_2)
/*
* String.h
*/
#include <stdio.h>
#include <string.h>
char tracks[][12] = {
"I love you1",
"I love you2",
"I hate you3",
"I love you4",
"I love you5",
"you love I1",
"you love I2",
"you love I3",
"you love I4",
"you love I5",
};
void track_str(char * str){
for (int i = 0; i < sizeof(tracks) / sizeof(tracks[0]); i++) {
if (strstr(tracks[i],str)) {
printf("在数组第%i的位置的%s中找到了%s。\n",i,tracks[i],str);
}
}
}
int main() {
char * str1 = "abcdefg";
printf("字符串长度-%lu\n",strlen(str1));
char * str2 = "de";
printf("str2 子串 在 str1中,从 %p 位置开始\n",strstr(str1,str2));
if(strstr(str1,str2)){
printf("在str1中找到了str2字符串!\n");
}
char cur[12];
puts("有了上面的测试,接下来让我们输入一个字符串在给定的字符串数组中进行查找:");
fgets(cur,12,stdin);
printf("字符串-%s\n",cur);
for (int i = 0 ; i < strlen(cur); i++) {
printf("字符-%c\n",cur[i]);
}
printf("字符串长度-%lu\n",strlen(cur));
printf("设置字符串最后一位为\\0");
cur[strlen(cur) - 1] = '\0';
printf("字符串--%s\n",cur);
for (int i = 0 ; i < strlen(cur); i++) {
printf("字符--%c\n",cur[i]);
}
printf("字符串长度--%lu\n",strlen(cur));
track_str(cur);
return 0;
}
log:
str2 子串 在 str1中,从 0x1022f5e62 位置开始
在str1中找到了str2字符串!
有了上面的测试,接下来让我们输入一个字符串在给定的字符串数组中进行查找:
hate
字符串-hate
字符-h
字符-a
字符-t
字符-e
字符-
字符串长度-5
设置字符串最后一位为\0字符串--hate
字符--h
字符--a
字符--t
字符--e
字符串长度--4
在数组第2的位置的I hate you3中找到了hate。
这是一个完整的例子;其中有很多值得注意的地方:
1)关于fgets:
指定的是可接受字符串的最大长度,包括了存储\0的位置,之所以cur[strlen(cur) - 1] = '\0';这样做是为了为fgets接收的字符串指定哨兵字符,以标示字符串的结尾;(哨兵字符并不占字符串的长度,但却需要占字符串对应的字符数组的一个位置);否则的话fgets直接接收的字符串会比录入的多一个长度,是给\0预留的(由于没有\0的录入,可能会影响字符串,因为你无法标示这个字符串何时结束);
scanf()不会出现这种情况,这里举个例子说明下:
(1)char ex[20]; scanf("%19s",ex); printf("%lu",strlen(ex));
log:
fff
3
(2)char ex[20]; fgets(ex,20,stdin); printf("%lu",strlen(ex));
fff
4
2)代码的排列顺序:
顶部包含头文件,这样编译器就能在编译代码之前把所有的函数都准备好;
在开始写函数之前,定义tracks,这叫把tracks数组放在全局区;全局变量位于任何函数之外,所有函数都可以调用它们;
最后是两个函数:track_str()和main();前者必须赶在main()函数调用之前出现;
在Mac或Linux的计算机,可以通过手册查看string.h中每个函数的介绍,如man strstr;
为什么不写成track[][];//每个字符串都不一样长的话,为了放下最长的,编译器需要分配足够大的空间;
如果是track[m][n]的话,track数组一共占了m*n个字符;
计算机总会先运行main()函数,之所以把track_str()函数放在前面,是因为在调用函数前,编译器需要知道函数接收什么参数以及函数的返回类型;
要点:
-可以用char strings[...][...]来创建数组的数组;
-第一组方括号用来访问外层数组;
-第二组方括号用来访问每个内层数组中的元素;
-有了string.h头文件,就可以使用C标准库中的字符串处理函数;
-一个C程序可以有多个函数,但是计算机总是先运行main();
再看一个示例:反转字符串中的字符,输出并返回;
(Code3_1)
/*
* reverse string
*/
#include <stdio.h>
#include <string.h>
char * reverse(char * s){
size_t len = strlen(s);//size_t相当于整数,用来保存字符串的长度
char chars[len];
char * t = s + len - 1;
while (t >= s) {
printf("%c",*t);
chars[len - 1 - (t-s)] = *t;
t = t - 1;
}
puts("\n");
return chars;
}
int main() {
char * chars = "flower";
char * charreverse = reverse(chars);
printf("%s\n",charreverse);
return 0;
}
log:
rewolf
rewolf
数组的数组 和 指针的数组:
我们已经见过如何用数组的数组保存多个字符串;还有一种方法是只用指针的数组;
指针的数组就是保存存储器地址的数组;
如果想要快速创建字符串字面值列表,指针的数组很有用:
char * names[] = {"ab","cd","ef"};//一个字符串字面值配一个指针
我们可以像访问数组的数组那样访问指针的数组;
(Code3_2)
/*
* 填字游戏举例
*/
#include <stdio.h>
#include <string.h>
void reverse(char * s){
size_t len = strlen(s);//size_t相当于整数,用来保存字符串的长度
char chars[len];//按照字符串长度声明一个字符数组
char * t = s + len - 1;//获取指向字符数组最后一位字符的指针
while (t >= s) {
printf("%c",*t);//打印指针存储的字符
chars[len - 1 - (t-s)] = *t;//将指针指向的字符逐个赋到数组的指定位置(地址)
t = t - 1;//进行指针的运算
}
puts("\n");
}
int main() {
//横
puts("横");
char * juices[] = {
"dragonfruit","waterberry","sharonfruit","uglifruit",
"rumberry","kiwifruit","mulberry","strawberry",
"blueberry","blackberry","starfruit"
};
char * a;
puts(juices[6]);
reverse(juices[7]);
a = juices[2];
juices[2] = juices[8];
juices[8] = a;
puts(juices[8]);
reverse(juices[(18+7)/5]);
//纵
puts("纵");
puts(juices[2]);
reverse(juices[9]);
juices[1] = juices[3];
puts(juices[10]);
reverse(juices[1]);
return 0;
}
log:
横
mulberry
yrrebwarts
sharonfruit
tiurfiwik
纵
blueberry
yrrebkcalb
starfruit
tiurfilgu
这段代码其实在对指针的数组的操作上其实没做什么,和我们之前用的差不多,但在字符串反转的处理上却很有意思(仔细看下注释);
工具箱:
-string.h头文件包含了有用的字符串处理函数;
-strstr(a,b)可以返回字符串b在字符串a中的地址;
-strcpy()可以复制字符串;
-strcmp()可以比较字符串;
-strcat()可以连接字符串;
-字符串数组是数组的数组;
-可以用char strings[...][...]创建数组的数组;
-strchr()用来在字符串中查找某个字符的位置;
-strlen()可以得到字符串的长度;