字符串
前言
C语言中对字符和字符串的处理很频繁,但是C语言本身是没有字符串类型的,字符串通常放在 常量字符串中或者字符数组中。字符串常量适用于那些对它不做修改的字符串函数。
对于比赛的时候或者面试的时候,使用这些库函数不仅可以大大减少时间,还可以提高模拟的效率。其实是十分有帮助的。
求字符串长度函数
strlen
size_t strlen(const char* string);
size_t=unsigned int
- 字符串以’\0’作为结束标志,strlen函数返回的是在字符串中’\0’前面出现的字符个数(不包含’\0’)
- 参数指向的字符串必须要以’\0’结束
- 注意函数的返回值为size_t,是无符号数的(易错)
- 学会strlen函数的模拟实现
//1.计数器的方法
//2.递归:不创建临时变量来实现
//3.指针-指针
int my_strlen(const char* str){
int count=0;
assert(str!=NULL);
while(*str!='\0'){
count++;
str++;
}return count;
}
int my_strlen(const char* str){
if(*str=='\0'){
return 0;
}
return 1+my_strlen(str+1);
}
int main(){
int len=my_strlen("abcdef");//后面有'\0'
printf("%d\n",len);
/*错误示范*/
char arr[]={'a','b','c','d','e','f'};
int len=strlen(arr);//随机
/*源码中strlen的返回值是无符号数,3-6溢出了。*/
if( strlen("abc")-strlen("abcdef")>0){
printf("hehe\n"); /*实际输出*/
}
else{
printf("haha\n");
}
}
长度不受限制的字符串函数
strcpy(字符串拷贝)
char* strcpy(char* destination,const char * source);
- 源字符串必须以’\0’结束 (比如char s[]={‘b’,‘i’,‘t’};不是以’\0’结尾)
- 目标空间必须足够大,放得下源字符串
- 目标空间必须可变(常量字符串的空间不可变)
- 学会模拟实现
char* my_strcpy(char* dest, const char* src){
assert(dest!=NULL);
assert(src!=NULL);
char* ret=dest;
//拷贝src指向的字符串到dest指向的空间,包含'\0'
while( *dest++ = *src++ )///精简写法,表达式结果判断是否'\0'
{
;
}
//返回目的空间的起始地址
return ret;
}
int main(){
char arr1[]="abcdefghi";
char arr2[]="bit";
/*错误的示范*/
cbar arr2[]={'b','i','t'};
char *arr1="abcdefgh";/*目标空间不可变*/
my_strcpy(arr1,arr2);
printf("%s\n",arr1);
}
strcat(字符串追加)
char* strcat
- 源字符串必须以’\0’结束
- 目标空间必须有足够的大,能容纳下源字符串的内容
- 目标空间必须可修改
- 字符串自己给自己追加,如何(程序会崩溃)
char* my_strcat(char* dest,const char*src){
char *ret=dest;
assert(dest!=NULL);assert(src!=NULL);
//1.找到目的字符串的'\0'
while(dest!='\0') dest++;
//2.追加(此时就是拷贝)
while(*dest++=*src++){
;
}return ret;
}
int main(){
char arr1[]="hello";
char arr2[]="world";
strcat(arr1,arr2);//error 因为炸了arr1的大小
char arr1[30]="hello\0xxxxx";
char arr2[]="world";
strcat(arr1,arr2);///追加在\0,不是末尾,helloworld\0xxxx
my_strcat()
}
strcmp(字符串比较)
标准规定:
第一个字符串大于第二个字符串返回>0的数字
第一个字符串等于第二个字符串返回=0的数字
第一个字符串小于第二个字符串返回<0的数字
int main(){
char* p1="abcdef";
char* p2="sqwer";
//vs2013
//> 1 ; == 0 ; < -1
//linux gcc
//> >0 ; == 0 ; < <0
int ret=strcmp(p1,p2);
}
int my_strcmp(const char* str1,const char* str2){
assert(str1!=NULL);assert(str2!=NULL);
while(*str1==*str2){
if(*str1=='\0'){
return 0; //相等
}
str1++;
str2++;
}
if(*str1 > *str2){
return 1; //大于
}
else if(*str1<*str2){
return -1; //小于
}
/*linux : return (*str1)-(*str2);*/
}
int main()
{
char* p1="abcdef";
char* p2="abcdef";
int ret=my_strcmp(p1,p2);
}
int main(){
char arr1[5]="abc";
char arr2[]="hello bit";
strcpy(arr1,arr2);
printf("%s\n",arr1);//RE,使用时不安全
}
长度受限制的字符串函数
strncpy
char* strncpy(char* strDest,const char* strSource,size_t count){
}
- 拷贝num个字符从源字符串到目标空间
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后面追加0,直到num个
char* my_strncpy(char* strDest,const char* strSource,int sz){
char* ret=strDest;
while( ( *strDest++=*strSource++) &&sz>0){
sz--;
}
while(sz>0){
*strDest='\0';sz--;
}
return ret;
}
int main(){
char arr1[10]="abcdef";
char arr2[]="hello bit";
strcnpy(arr1,arr2,4);//arr1:hellef
char arr2[]="bit";
strcnpy(arr1,arr2,6);//arr1:bit \0 \0 \0 不够的地方补\0
}
strncat
char* strncat(char* strDest,const char* strSource,size_t count)
- 追加之后主动放一个\0。
- 如果追加的长度超过本身的长度,只追加本身的。
char* strncat(char *strDest,const char* strSource,int sz){
char* ret=strDest;
}
int main(){
char arr1[30]="hello\0xxxxxxxxxxxxxxxxxx";
char arr2[]="world";
strncat(arr1,arr2,3);//追加之后会主动放一个\0进去
strncat(arr1,arr2,8);//个数比我的本身长,只追加本身的
printf("%s\n",arr1);
}
strncmp
int strncmp(const char* string1,const char* string2,size_t count){
}
int main(void){
const char* p1="abcdef";
const char* p2="abcqwer";
int ret=strcmp(p1,p2);
printf("%d\n",ret);
int res=strcmp(p1,p2,3);
printf("%d\n",res);
}
strstr(查找子字符串)
char* strstr(const char* string,const char* strCharset){
}
char* my_strstr(const char* p1,const char* p2 ){
assert(p1!=NULL);assert(p2!=NULL);
if(*p2=='\0'){
return (char*)p1;
}
while(*p1!='\0'){
while( (*p1!=*p2) && (*p1!='\0') ){
p1++;
}
char* cnt=(char*)p1;
if(*cnt=='\0'){
return NULL;
}
char* temp=(char*)p2;
while((*cnt==*temp)&&(temp!='\0')&&(*cnt!='\0')){
cnt++;
temp++;
}
if(*temp=='\0'){
return (char*)p1;
}
p1++;
}
return NULL;
/*
char* cur=p1;
while(*cur){
s1=cur;
s2=p2;
while( (*s1!='\0') &&(*s2!='\0')&&(*s1==*s2)){
s1++;s2++;
}
if(*s2=='\0'){
return cur;
}
cur++;
}
return NULL;
*/
/*简洁版
char* my_strstr(const char* p1,const char* p2 ){
assert(p1!=NULL);assert(p2!=NULL);
if(*p2=='\0') return (char*)p1;
char* now=(char*)p1;
char* cnt=(char*)p2;
while(*now!='\0'){
while((*now!='\0')&&(*cnt!='\0')&&(*now==*cnt)){
now++;
cnt++;
}
if(*cnt=='\0') return (char*)p1;
p1++;
now=(char*)p1;
cnt=(char*)p2;
}
return NULL;
}
*/
}
int main(){
char *p1="abcdefabcdef";
char *p2="def";
char *ret=strstr(p1,p2);
if(ret==NULL){
printf("字串不存在");
}
else{
printf("%s\n",ret);///返回的是第一次出现的起始位置
}
}
strtok(分割字符)
char* strtok(char* str,const char* sep){
}
- sep参数是个字符串,定义了用作分隔符的字符集合
- 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记
- strtok函数找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容且可修改)
- strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回NULL指针
一次只能切出一段,函数会记住’\0’的位置。所以函数内部应该是有静态变量的。出去函数不销毁。
int main()
{
//192.168.32.121
//192 168 31 121
//zpw@bitedu.tech
//zpw bitedu tech
char arr[]="zpw@bitedu.cn";
char* p="@.";
char* ret=NULL;
for(ret=strtok(arr,p);ret!=NULL;ret=strtok(NULL,p)){
printf("%s\n",ret);
}
/*繁琐*/
char buf[1024] ={0};
strcpy(buf,arr);
//切割buf中的字符串
char* ret=strtok(arr,p);
printf("%s\n",ret);
ret=strtok(NULL,p);
printf("%s\n",ret);
}
strerror(返回错误码)
char* strerror(int errnum);
返回错误码----->所对应的错误信息。
#include<errno.h>
int main()
{
//错误码 错误信息
//0- No error
//1 -Operation not permitted
//2 -
//...
char* str =strerror(1);
//真实写代码
//errno是一个全局的错误码变量
//当C语言的库函数在执行过程中,发生了错误,就会把对应的错误码,赋值到error中
char* str=strerror(errno);
printf("%s\n",str);
///打开文件
FILE* pf=fopen("test.txt","r");
if(pf==NULL){
printf("%s\n",strerror(errno));
//perror("测试:") 输出"测试:+错误信息”; ---打印自定义+strerror
}
else{
printf("open file success\n");
}
}
字符分类函数
#include<ctype.h>
iscntrl //控制字符
isspace //空白字符:空格' ',换页'\f',换行'\n',回车'\r'.制符表'\t',垂直制符表'\v'
isdigit;//十进制0~9
islower;//小写字母a~z
isupper;//大写字母A~Z
isalpha;//字母a~z或A-Z
isalnum;//字母或者数字 a~z,A-Z,0~9
ispunct;//标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph;//任何图形字符
isprint;//任何可打印字符,包括图形字符和空白字符
字符转换函数
int tolower(int c);
int toupper(int c);
#include<ctype.h>
int main()
{
char ch=tolower('Q');
putchar(ch);
return 0;
char arr[]="I Am A Student";
int i=0;
while(arr[i])
{
if(isupper(arr[i]))
{
arr[i]=tolower(arr[i]);
}
i++;
}
printf("%s\n",ret);
}
内存函数
以上的strcpy,strcat,strcmp,strmcpy,strncat,strncmp的操作对象是字符串,’\0’
对于整形数组,浮点型数组,结构体数组。
int main()
{
int arr1[]={1,2,3,4,5};
int arr2[5]={0};
strcpy(arr2,arr1)
//小端法
// 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
//拷贝到第一个00就停下来了。
}
memcpy(内存拷贝)
因此有memcpy,只要是放内存中的,都可以拷贝。可以对整型,浮点,结构体,字符。因此对应于回调函数,需要使用void*
void* memcpy(void *destination,const void* source,size_t num)
size_t 的单位是字节
int arr1[]={1,2,3,4,5};
int arr2[5]={0};
memcpy(arr2,arr1,sizeof(arr1));
struct S
{
char name[20];
int age;
};
int main()
{
int arr1[]={1,2,3,4,5};
int arr2[5]={0};
struct S arr3[]={ {"张三",20},{"李四",30} };
struct S arr4[3]={0};
memcpy(arr4,arr3,sizeof(arr3));
}
//
void* my_memcpy(void* dest,const void* src,size_t num)
{
assert(dest&&src);
void* ret=dest;
while(num--)
{
char temp='\0';
temp=*(char*)dest
*(char*)dest=*(char* src);
*(char*)src=temp;
dest=(char*)dest+1;
scr=(char*)src+1;
}
return ret;
}
//模拟实现
void* my_memcpy(void* dest,const void* src,size_t num)
{
assert(dest!=NULL);assert(src!=NULL);
void* ret=dest;
while(num--)
{
*(char*)dest=*(char*)src;
++(char*)dest;
++(char*)src;
dest=(char*) dest+1;
src=(char*) src+1;
}
return ret;
}
void* my_memcpy(void* dest,const void* src,size_t num)
{
assert(dest!=NULL);assert(src!=NULL);
void* ret=dest;
while(num--)
{
*(char*)dest=*(char*)src;
++(char*)dest;
++(char*)src;
}
return ret;
}
int main()
{
int arr[]={1,2,3,4,5,6,7,8,9,10};
int i=0;
my_memcpy(arr+2,arr,20);
for(int i=0;i<10;i++) printf("%d ",arr[i]);
//理想:1 2 1 2 3 4 5 8 9 10
//现实: 1 2 1 2 1 2 1 8 9 10
//因为此时指向的是涉及重叠的一块区域,顺序赋值导致的。然而倒序赋值在一些情况也不行。
//因为要讨论,要分类去做。然而memcpy不是负责这个东西的,负责的是memmove函数(处理内存重叠的情况).
//C语言标准规定memcpy:只要处理不重叠的内存拷贝就可以
//memmove 可以处理重叠内存的拷贝
//但是VS编译器重新实现了memcpy,达到了内存覆盖的处理
}
memmove(处理内存重叠的情况)
void *memmove( void *dest, const void *src, size_t count );
void *my_memove(void* dest,const void* src,size_t num)
{
}
int main()
{
int arr[]={1,2,3,4,5,6,7,8,9,10};
int i=0;
memmove(arr+2,arr,20);
for(int i=0;i<10;i++)
{
printf("%d ",arr[i]);
}
}
#include<stdio.h>
void my_memove(void* dest, const void* source, size_t count)
{
if (dest >= source)//后往前
{
while (count--)
{
*((char*)dest + count) = *((char*)source + count);
}
}
else//前往后
{
while (count--)
{
*(char*)dest = *(char*)source;
dest = (char*)dest + 1;
source = (char*)source + 1;
}
}
}
int main()
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9,10 };
//0 1 2 3 4 5 6 7 8 9 10
//0 1 0 1 2 3 4 7 8 9 10
int sz = sizeof(arr) / sizeof(arr[0]);
my_memove(arr + 2, arr, 5 * sizeof(arr[0]));
for (int i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}printf("\n");
int drr[] = { 0,1,2,3,4,5,6,7,8,9,10 };
//0 1 2 3 4 5 6 7 8 9 10
//2 3 4 5 6 5 6 7 8 9 10
my_memove(drr, drr + 2, 5 * sizeof(drr[0]));
for (int i = 0; i < sz; i++)
{
printf("%d ", drr[i]);
}printf("\n");
return 0;
}
memcmp(内存比较)
按字节比较大小
int memcmp ( const void * ptr1,
const void * ptr2,
size_t num );
/* memcmp example */
#include <stdio.h>
#include <string.h>
int main ()
{
char buffer1[] = "DWgaOtP12df0";
char buffer2[] = "DWGAOTP12DF0";
int n;
n=memcmp ( buffer1, buffer2, sizeof(buffer1) );
if (n>0) printf ("'%s' is greater than '%s'.\n",buffer1,buffer2);
else if (n<0) printf ("'%s' is less than '%s'.\n",buffer1,buffer2);
else printf ("'%s' is the same as '%s'.\n",buffer1,buffer2);
return 0;
}
查了查源码是这样的。但是我略有疑问。
对于 int arr1[]={1,2,3}; int arr2[]={1,2}; 按照arr1的大小来比较3个字节,我觉得应该类似strcmp那样输出greater,但是源码认为是less。
int memcmp2(const void* buffer1, const void* buffer2, int count)
{
if (!count) return(0);
while (--count && *(char*)buffer1 == *(char*)buffer2)
{
buffer1 = (char*)buffer1 + 1;
buffer2 = (char*)buffer2 + 1;
}
return(*((unsigned char*)buffer1) - *((unsigned char*)buffer2));
}
int main()
{
int arr1[]={1,2,3,4,5};
int arr2[]={1,2,3,6,6};
int ret=memcpy(arr1,arr2,12);
printf("%d\n",ret);
}
#include<stdio.h>
#include<string.h>
#include <cassert>
int my_memcmp(const void* s, const void* p, size_t num)//比较从s和p指针开始的num个字节
{
assert(s && p);
char* ed = (char*)p+ num - 1;///p指针的结尾
while (num)
{
if (*(char*)s > * (char*)p) return 1;
else if (*(char*)s < *(char*)p) return -1;
s=(char*)s+1; p=(char*)p+1;
num--;
if (p > ed && num > 0) return 1;//s长度长但是p长度短且前面都相等的情况
}
return 0;
}
int memcmp2(const void* buffer1, const void* buffer2, int count)
{
if (!count)
return(0);
while (--count && *(char*)buffer1 == *(char*)buffer2)
{
buffer1 = (char*)buffer1 + 1;
buffer2 = (char*)buffer2 + 1;
}
return(*((unsigned char*)buffer1) - *((unsigned char*)buffer2));
}
int main()
{
int buffer1[] = { 1,2,3 };
int buffer2[] = { 1,2 };
int n;
n = my_memcmp(buffer1, buffer2, sizeof(buffer1));
if (n > 0) printf("buffer1 is greater than buffer2.\n", buffer1, buffer2);
else if (n < 0) printf("buffer1 is less than buffer2.\n", buffer1, buffer2);
else printf("buffer1 is the same as buffer2.\n", buffer1, buffer2);
n = memcmp2(buffer1, buffer2, sizeof(buffer1));
if (n > 0) printf("buffer1 is greater than buffer2.\n", buffer1, buffer2);
else if (n < 0) printf("buffer1 is less than buffer2.\n", buffer1, buffer2);
else printf("buffer1 is the same as buffer2.\n", buffer1, buffer2);
return 0;
}
memset
注意是按照字节来一个个赋值的。
void *memset( void *dest, int c, size_t count );
count-Number of characters //字节数
int main()
{
int arr[]={1,2,3,4,5,6};
}
模拟实现
1.memmove
#include<stdio.h>
void my_memove(void* dest, const void* source, size_t count)
{
if (dest >= source)//后往前
{
while (count--)
{
*((char*)dest + count) = *((char*)source + count);
}
}
else//前往后
{
while (count--)
{
*(char*)dest = *(char*)source;
dest = (char*)dest + 1;
source = (char*)source + 1;
}
}
}
int main()
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9,10 };
//0 1 2 3 4 5 6 7 8 9 10
//0 1 0 1 2 3 4 7 8 9 10
int sz = sizeof(arr) / sizeof(arr[0]);
my_memove(arr + 2, arr, 5 * sizeof(arr[0]));
for (int i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}printf("\n");
int drr[] = { 0,1,2,3,4,5,6,7,8,9,10 };
//0 1 2 3 4 5 6 7 8 9 10
//2 3 4 5 6 5 6 7 8 9 10
my_memove(drr, drr + 2, 5 * sizeof(drr[0]));
for (int i = 0; i < sz; i++)
{
printf("%d ", drr[i]);
}printf("\n");
return 0;
}
2.memcpy
#include<stdio.h>
void my_memcpy(void* dest, const void* source, size_t count)
{
while (count--)
{
*(char*)dest = *(char*)source;
dest = (char*)dest + 1;
source = (char*)source + 1;
}
}
struct P {
int a, b;
};
int main()
{
P A = { 1,2 };
P B; my_memcpy(&B, &A, 1 * sizeof(struct P));
printf("%d %d\n", B.a, B.b);
return 0;
}
3.strstr
#include<stdio.h>
#include<cassert>
#include<math.h>
#include<iostream>
#include<string.h>
#define max(x,y) ( (x)>(y)?(x):(y) )
const int maxn = 1e5 + 100;
int d[maxn];
char t[maxn], p[maxn];
using namespace std;
//下标从0开始的最长公共前后缀可以直接怼到应该重新开始的匹配的位置
void pre(const char* p)
{
memset(d, 0, sizeof(d));
int len = strlen(p);
int i = 1, j = 0;
while (i < len)
{
if (p[i] == p[j]) d[i++] = ++j; //维护此时的最长公共前后缀
else {
if (j > 0) j = d[j - 1];
else i++;
}
}
}
void my_strstr(const char* t, const char* p)
{
assert(t && p);
int len1 = strlen(t); int len2 = strlen(p);
pre(p);
int i = 0, j = 0;
while (i < len1)
{
if (t[i] == p[j])
{
i++; j++;
if (j == len2)
{
printf("%d\n", i - len2+1);//打印t串中匹配成功的开始位置
j = d[j - 1];
}
}
else
{
if (j > 0) j = d[j - 1];
else i++;//统统失配,新开始
}
}
for (int i = 0; i < len2; i++) cout << d[i] << " ";
}
int main()
{
cin >> t >> p;
my_strstr(t, p);
return 0;
}
4.strcat
#include<stdio.h>
#include <cassert>
void my_strcat(char* dest,const char* source)
{
assert(dest && source);
assert(dest != source);
while (*dest!='\0') dest++;
while (*source != '\0') {
*dest = *source;
dest++;
source++;
}*dest = '\0';
}
int main()
{
char str1[20]="abcd";
char str2[] = "efg";
my_strcat(str1, str2);
printf("%s\n", str1);
return 0;
}
5.strcmp
#include<stdio.h>
#include <cassert>
int my_strcmp(const char* s, const char* p)
{
assert(s && p);
while (*s == *p)
{
if (*s == '\0') return 0;
s++; p++;
}
if (*s > * p) return 1;
else return -1;
}
char str[][4] = { {"ABC"},{"BCD"},{"ACB"},{"AB"},{"AB"} };
int main()
{
for (int i = 0; i < 5; i++) {
for (int j = i + 1; j < 5; j++)
{
printf("%s %s %d\n", str[i], str[j], my_strcmp(str[i], str[j]));
}
}
return 0;
}
6.strcpy
#include<stdio.h>
#include <cassert>
char* my_strcpy(char* s, const char* p)
{
assert(s && p);
char* ret = s;
while (*s++ = *p++);
return ret;
}
int main()
{
char arr1[10] = { };
char arr2[5] = { "efgf" };
printf("%s\n", my_strcpy(arr1, arr2));
return 0;
}
7.strlen
int my_strlen(const char* str)
{
if (*str == 0) return 0;
return 1 + my_strlen(str + 1);
};