400 likes | 632 Views
程序设计实习 第三讲 字符串处理. 内容提要. 字符串的存储、字符串处理函数 将数组传递给函数 例题: ai2767 Caesar 密码 (P115) 例题:单词排序 例题: ai2744 子串 (P112) 例题: POJ1936 All in All. 字符串. 每个字符串是一个特殊的数组,满足两个条件 元素的类型为 char 最后一个元素的值为‘ ’ ,Ascii 码就是 0 以字符型数组存储 从 0 号元素开始存储 最大可以存储长度为 N-1 的字符串, N 是数组的大小。
E N D
程序设计实习 第三讲 字符串处理
内容提要 • 字符串的存储、字符串处理函数 • 将数组传递给函数 • 例题:ai2767 Caesar密码 (P115) • 例题:单词排序 • 例题:ai2744 子串 (P112) • 例题:POJ1936 All in All
字符串 • 每个字符串是一个特殊的数组,满足两个条件 • 元素的类型为char • 最后一个元素的值为‘\0’ ,Ascii码就是 0 • 以字符型数组存储 • 从0号元素开始存储 • 最大可以存储长度为N-1的字符串,N是数组的大小。 • 字符串“hello”在长度为10的字符串数组中的存储 h e l l o \0
字符串处理函数 • 将格式化数据写入字符串:sprintf • 字符串长度查询函数: strlen • 字符串复制函数:strcpy、strncpy • 字符串连接函数: strcat • 字符串比较函数: strcmp、strncmp、stricmp、strnicmp • 字符串搜索函数: strcspn、strspn、strstr、strtok、strchr • 字符串大小写转换函数: strlwr、strupr 这些函数都要求 #include <string.h>
字符串拷贝和求字符串长度 char *strcpy(char *dest, const char *src); int strlen(const char *s); 查询str1中字符串的长度 #include <stdio.h> #include <string.h> void main() { char str1[10]="hello", str2[12]; strcpy(str2,"hello world"); printf("length:%d(str1); %d(str2)\n", strlen(str1), strlen(str2)); strcpy(str1, str2); printf("length:%d(str1); %d(str2)\n", strlen(str1), strlen(str2)); printf("%s\n", str1); return; } 把”hello world”复制到str2 把str2复制到str1
输出结果: length:5(str1); 11(str2) length:11(str1); 11(str2) hello world • str1存储了11个非’\0’字符? • strcpy在复制字符串str2到str1时,不检查str2是否超出了str1的存储容量,而是直接将str2中存储的字符串复制到从str1开始的一段连续区域 • 在程序中要特别注意这种情况所引发的程序运行 不确定性
main() str1 h e l l o \0 str2 strcpy(str2,“hello world"); str1 h e l l o \0 str2 h e l l o w o r l d \0 strcpy(str1,str2); str1 h e l l o w o r l d \0 str2 h e l l o w o r l d \0
用strlen时常犯的错误 int MyStrchr(char * s, char c) //看s中是否包含 c { for( int i = 0; i < strlen(s) -1 ; i ++ ) if( s[i] == c) return 1; return 0; } 哪里不好?这个函数执行时间和 s 的长度是什么关系? strlen 是一个o(N)的函数,每次判断 i < strlen(s) – 1 都要执行,太浪费时间了
字符串添加 strcat char *strcat(char *dest, const char *src); 把src内容加到dest后面,同样不会考虑 dest是否够长 #include <stdio.h> #include <string.h> void main() { char str1[100]="hello", str2[10]="^_^"; strcat(str1," world "); printf("%s\n", str1); strcat(str1, str2); printf("%s\n", str1); return; } 把”world”添加到str1中原字符串的末尾 把str2中的字符串添加到str1中原字符串的末尾 输出: hello world hello world ^_^
字符串比较函数strcmp(分大小写)、stricmp(不分大小写)字符串比较函数strcmp(分大小写)、stricmp(不分大小写) int strcmp(const char *s1, const char *s2); Return Value If s1 is... return value is... less than s2 < 0 the same as s2 == 0 greater than s2 > 0 int stricmp(const char *s1, const char *s2); Compares one string to another, without case sensitivity. Return Value If s1 is... return value is... less than s2 < 0 the same as s2 == 0 greater than s2 > 0
关于 stricmp 的注意事项stricmp 不是严格意义上的C++标准库函数,所以不同编译器的实现有所不同。比如在POJ上交题,编译器选C++时,该函数应写为:_stricmp不同编译器的 _stricmp 库函数执行过程也可能不一样,有的是把小写转换成大写后比较,有的是把大写转换成小写后比较,所以,在待比较字符串里面有非字母字符,比如标点符号时,不同编译器的 _stricmp执行的结果有可能不同
字符串比较函数strcmp、stricmp #include <string.h> #include <stdio.h> char string1[] = "The quick brown dog jumps over the lazy fox"; char string2[] = "The QUICK brown dog jumps over the lazy fox"; void main( void ) { int result; printf( "Compare strings:\n\t%s\n\t%s\n\n", string1, string2 ); result = strcmp( string1, string2 ); printf( "strcmp: result=%d\n", result); result = stricmp( string1, string2 ); printf( "stricmp: result=%d\n", result); return; }
输出: Compare strings: The quick brown dog jumps over the lazy fox The QUICK brown dog jumps over the lazy fox strcmp: result=1 stricmp: result=0
查找子串strstr char *strstr(char *s1, char *s2); Scans a string for the occurrence of a given substring. 查找给定字符串在字符串中第一次出现的位置,返回位置指针 strstr scans s1 for the first occurrence of the substring s2. Return Value strstr returns a pointer to the element in s1, where s2 begins (points to s2 in s1). If s2 does not occur in s1, strstr returns null. 如果找到,返回指针,指向s1中第一次出现s2的位置 如果找不到,返回 NULL
查找子串strstr #include <string.h> #include <stdio.h> char str[] = "lazy"; char string[] = "The quick brown dog jumps over the lazy fox"; void main( void ) { char *pdest; int result; pdest = strstr( string, str ); result = pdest - string + 1; if( pdest != NULL ) printf( "%s found at position %d\n\n", str, result ); else printf( "%s not found\n", str ); } 输出: lazy found at position 36 在string中搜索str,返回str在string中第一次出现的位置
在字符串中查找字符 strchr Scans a string for the first occurrence of a given character. 查找给定字符在字符串中第一次出现的位置,返回位置指针 strchr scans a string in the forward direction, looking for a specific character. strchr finds the first occurrence of the character c in the string s. Return Value strchr returns a pointer to the first occurrence of the character c in s; if c does not occur in s, strchr returns null. char *strchr(char *s, int c);
在字符串中查找字符 strchr #include <string.h> #include <stdio.h> int ch = 'r'; char string[] = "The quick brown dog jumps over the lazy fox"; void main( void ) { char *pdest; int result; pdest = strchr( string, ch ); result = pdest - string + 1; if( pdest != NULL ) printf( "Result:\tfirst %c found at position %d\n\n", ch, result ); else printf( "Result:\t%c not found\n" ); • } 输出: Result: first r found at position 12 在string中搜索ch,返回str在string中第一次出现的位置
字符串部分拷贝 strncpy char *strncpy(char *dest, char *src, int maxlen); 将前 maxlen 个字符从src拷贝到dest 1)如果src中字符不足 maxlen 个,则连’\0’一起拷贝,’\0’后面的不拷贝 2) 如果src中字符大于等于maxlen个,则拷贝 maxlen个字符
字符串部分拷贝 strncpy #include <iostream> using namespace std; #include <stdio.h> int main(void) { char s1[20] = "1234567890"; char s2[] = "abcd" ; strncpy( s1,s2,5); cout << s1 << endl; strcpy( s1,"1234567890"); strncpy( s1,s2,4); cout << s1 << endl; return ; } 输出: abcd abcd567890
数组作为函数的参数 #include <iostream> using namespace std; char str1[200] = "Hello,World"; char str2[100] = "Computer"; void swap( char s1[ ], char * s2) //交换两个字符串的内容 { char c; for( int i = 0; s1[i] || s2[i]; i ++ ){ // ‘\0’的Ascii 码就是 0 c = s2[i]; s2[i] = s1[i]; s1[i] = c; } } int main() { swap(str1,str2); cout << str1 << endl << str2; return 0; } 输出: Computer Hello,World
例题:ai2767 Caesar密码(P115) • 问题描述 • Julius Caesar 生活在充满危险和阴谋的年代。为了生存,他首次发明了密码,用于军队的消息传递 • 假设你是Caesar 军团中的一名军官,需要把Caesar 发送的消息破译出来、并提供给你的将军。消息加密的办法是:对消息原文中的每个字母,分别用该字母之后的第5个字母替换(例如:消息原文中的每个字母A都分别替换成字母F,V替换成A,W替换成B…),其他字符不 变,并且消息原文的所有字母都是大写的。
密码字母:A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 原文字母:V W X Y Z A B C D E F G H I J K L M N O P Q R S T U • 输入 • 最多不超过100个数据集组成。每个数据集由3部分组成 • 起始行:START • 密码消息:由1到200个字符组成一行,表示Caesar发出的一条消息 • 结束行:END • 在最后一个数据集之后,是另一行:ENDOFINPUT • 输出 • 每个数据集对应一行,是Caesar的原始消息。
样例输入 START NS BFW, JAJSYX TK NRUTWYFSHJ FWJ YMJ WJXZQY TK YWNANFQ HFZXJX END START N BTZQI WFYMJW GJ KNWXY NS F QNYYQJ NGJWNFS ANQQFLJ YMFS XJHTSI NS WTRJ END START IFSLJW PSTBX KZQQ BJQQ YMFY HFJXFW NX RTWJ IFSLJWTZX YMFS MJ END ENDOFINPUT • 样例输出 IN WAR, EVENTS OF IMPORTANCE ARE THE RESULT OF TRIVIAL CAUSES I WOULD RATHER BE FIRST IN A LITTLE IBERIAN VILLAGE THAN SECOND IN ROME DANGER KNOWS FULL WELL THAT CAESAR IS MORE DANGEROUS THAN HE
#include <iostream> #include <string.h> using namespace std; int main() { char szLine[300]; while( cin.getline(szLine,210) ) { //可用此方式判断数据是否读完 /*cin.getline 读取一行,第一个参数是缓冲区地址;第二个参数是缓冲区大小,为了防止越界用的。缓冲区不够大,就自动截断。它会自动往缓冲区末尾添加 ‘\0’。*/ if( strcmp( szLine,"ENDOFINPUT") == 0) break; cin.getline(szLine,210); //读取密文 for( int i = 0; szLine[i]; i ++ ) if( szLine[i] >= 'A' && szLine[i] <= 'Z' ) { szLine[i] -= 5; if( szLine[i] < 'A' ) szLine[i] = 'Z' - ('A' - szLine[i]) + 1; } cout << szLine; cout << endl; cin.getline(szLine,210); //读取 END } return 0; }
例题:单词排序 输入若干行单词(不含空格),请按字典序排序输出。大小写有区别。单词一共不超过100行,每个单词不超过20字符 Sample input: What man Tell About back Sample output: About Tell What back man
例题:单词排序 用什么保存多个单词? 答:用二维字符数组 char Word[100][30]; 则表达式 Word[i] 的类型就是 char * Word[i] 就是数组中的一行,就是一个字符串 Word[i][0] 就是Word[i] 这个字符串的头一个字符 二维字符数组也能初始化,例如: char week[7][10]={"Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"};
例题:单词排序 #include <stdlib.h> #include <stdio.h> #include <string.h> char Word[100][30]; int MyCompare( const void * e1, const void * e2 ) { return strcmp( (char * ) e1, (char * ) e2 ); } int main() { int n = 0; //单词个数 while(scanf("%s",Word[n]) != EOF && Word[n][0]) n ++; qsort(Word, n,sizeof(Word[0]),MyCompare); for( int i = 0; i < n; i ++ ) printf(“%s\n”,Word[i]); // ‘\n’表示换行 return 0; } 为了对付有可能最后一行读入的是空行
例题:ai2744子串 (P112) • 问题描述:给出一些由英文字符组成的大小写敏感的字符串的集合s,请找到一个最长的字符串x,使得对于s中任意字符串y,x或者是y的子串,或者x中的字符反序之后得到的新字符串是y的子串。 • 输入:输入的第一行是一个整数t (1 <= t <= 10),t表示测试数据的数目。对于每一组测试数据,第一行是一个整数n (1 <= n <= 100),表示已经给出n个字符串。接下来n行,每行给出一个长度在1和100之间的字符串。
例题:ai2744子串 (P112) • 输出:对于每一组测试数据,输出一行,给出题目中要求的字符串x的长度。 Sample Input: 2 3 ABCD BCDFF BRCD 2 rose orchid Sample Output2 2
例题:ai2744子串 (P112) 思路: 随便拿出输入数据中的一个字符串,从长到短找出它的所有子串,直到找到否符合题目要求的。 改进: 不要随便拿,要拿输入数据中最短的那个。从长到短找出它的所有子串,直到找到否符合题目要求的。
#include <stdio.h> #include <string.h> int t, n; char str[100][101]; int searchMaxSubString(char* source); int main() { int i, minStrLen, subStrLen; char minStr[101]; scanf("%d", &t); while(t--) { scanf("%d", &n); minStrLen = 100; //记录输入数据中最短字符串的长度 for (i = 0; i < n; i++) {//输入一组字符串 scanf("%s", str[i]); if ( strlen(str[i]) < minStrLen ) {//找其中最短字符串 strcpy(minStr, str[i]); minStrLen = strlen(minStr); } } subStrLen = searchMaxSubString(minStr);//找答案 printf("%d\n", subStrLen); } return 0; }
int searchMaxSubString(char* source) { int subStrLen = strlen(source), sourceStrLen = strlen(source); int i, j; bool foundMaxSubStr; char subStr[101], revSubStr[101]; while ( subStrLen > 0 ) {//搜索不同长度的子串,从最长的子串开始搜索 for (i = 0; i <= sourceStrLen - subStrLen; i++) { //搜索长度为subStrLen的全部子串 strncpy(subStr, source+i, subStrLen); strncpy(revSubStr, source+i, subStrLen); subStr[subStrLen] = revSubStr[subStrLen] = '\0'; _strrev(revSubStr);foundMaxSubStr = true; for( j = 0; j < n; j++) //遍历所有输入的字符串 if ( strstr(str[j], subStr) == NULL && strstr(str[j], revSubStr) == NULL ) { foundMaxSubStr = false; break; } if (foundMaxSubStr) return subStrLen; } subStrLen--; } return(0); } _strrev (char * s) 将字符串 s 颠倒
例题:POJ1936 All in All • 问题描述:给定两个字符串s和t,请判断s是否是t的子序列。即从t中删除一些字符,将剩余的字符连接起来,即可获得s。 • 输入:包括若干个测试数据。每个测试数据由两个ASCII码的数字和字母串s和t组成, s和t的长度不超过100000。 • 输出:对每个测试数据,如果s是t的子序列则输出“Yes”,否则输出“No”。
例题:POJ1936 All in All • 样例输入 sequence subsequence person compression VERDI vivaVittorioEmanueleReDiItalia caseDoesMatter CaseDoesMatter • 样例输出 Yes No Yes No
例题:POJ1936 All in All 思路: 关键是看s中的每个字符是否在t中出现,而且顺序一致。 设两个指针,分别指向s和t的开头,然后, 如果t的指针指向的字符和s的指针指向的字符相同,则将s的指针向前移动一个字符。 t的指针则不停向前每次移动一个字符, 直到s指针指向末尾的 ‘\0’ 说明Yes找到 或 t 的指针指向末尾的 ‘\0’,说明No
#include <stdio.h> char s[100010]; char t[100010]; int main() { int i,j; while (scanf( "%s%s",s,t) > 0 ) { i = 0; for(j = 0 ; s[i] && t[j]; j ++ ) { if( t[j] == s[i] ) i ++; } if ( s[i] == 0) // ‘\0’ 的Ascii 码就是 0 printf("Yes\n"); else printf("No\n"); } return 0; }
ai2804 词典作业提示: 为加快查找速度,可能会用到折半查找库函数: 该函数与 qsort有类似之处 void *bsearch(const void *key, const void *base, size_t nelem, size_t width, int ( *fcmp)(const void *, const void *)); 该函数在一个排过序的数组base里查找值和 * key 一样大(未必是严格相等,只是按 fcmp指定的比较规则是一样大的而已)的元素。找到的话,返回第一个该种元素的地址,找不到的话,返回 NULL 复杂度:O(lgN) 要求比较大小的规则和排序的规则一样
ai2804 词典作业提示: 怎么判断读到空行? char s[100]; 可以用 gets(s)一次读取一行,然后再用 sscanf 从s中分出英文和外文 gets(s)读到空行时,s的长度为 0,即 s[0] = 0 读到文件尾巴时,gets返回 NULL scanf 返回 EOF也可以说明到了文件尾巴 注意,说不定文件尾有空行
ai2804 词典作业提示: 数据量很大,注意用 cin, cout说不定会超时
作业 • ai2734 十进制 -> 八进制 • ai2797 最短前缀 • ai2804 词典