求字符串长度
长度不受限制的字符串函数
长度受限制的字符串函数介绍
字符串查找
错误信息报告
字符操作
内存操作函数
- 字符串就是一串或多个字符,并且以一个位模式全0的NULL字节结尾。因此我们字符串所包含的字符内部不能出现NULL字节,这个限制很少会引起问题,因为NULL字节并不存在与它相关联的可打印字符,这也是它被选为终止符的原因。NULL字节是字符串的终止符,但它本身并不是字符串的一部分,所以字符串的长度并不句括NULL字节,头文件 string.h 包含了使用字符串函数所需的原型和声明。尽管并非必需,但在程序中包含这个头文件确实是个好主意,因为有了它所包含的原型,编译器可以更好地为你的程序执行错误检查。
size_t strlen(const char*string);
#include
#include
int main()
{
char arr[]={"hello"};
int len = strlen(arr);
printf("%d\n",len);
return 0;
}
char arr[]={"hello"};
解释:
我们在这样初始化时,后面是有\0的。strlen返回的是字符串个数,所以是5
#include
#include
int main()
{
char arr[]={'h','e','l','l','o'};
int len = strlen(arr);
printf("%d\n",len);
return 0;
}
char arr[]={'h','e','l','l','o'};
解释:
我们这样初始化时,arr数组里没有\0,strlen找不到结束标志,所以他一直往后数,所以打印到的是随机值
#include
#include
int main()
{
if(strlen("abcdef")-strlen("abcdefgh")>0)
{
printf(">\0");
}
else
{
printf("<");
}
return 0;
}
解释:
我们发现这条语句和我们预想的不一样,为什么是大于呢?原因是strlen的结果是无符号数,无符号数-无符号数得到的还是无符号数,无符号数不可能是负的,所以它的结果永远都是大于0。
#include
int my_strlen(const char*str)
{
assert(str);
int count=0;
while(*str++)
{
count++;
}
return count;
}
int main()
{
char arr[]={"hello"};
int len = strlen(arr);
printf("%d\n",len);
return 0;
}
解释:
函数循环体判断部分*str++,*str为’\0’时退出循环,因为’\0’的ascii码值为0,为假。str指向字符串第一个字符,我们的想法是当它指向内容不为\0时,计数器++,最后返回count,我们看这个代码:*str++,++操作符的优先级比*高,我们先使用后置++,后置++是先使用后++。然后我们判断是不是\0,不是\0进入循环,计数器加加,要是\0,则退出循环,返回count。
#include
int my_strlen(const char*str)
{
assert(str);
if(*str!='\0')
{
return 1+my_strlen(str+1);
}
else
{
return 0;
}
}
int main()
{
char arr[]={"hello"};
int len = strlen(arr);
printf("%d\n",len);
return 0;
}
刚开始进入函数,str指向’h’,‘h’不等于\0,return 1+my_strlen(str+1),递归调用,str+1指向了’e’,b不等于\0,继续递归调用,str+1指向了’l’,l不等于\0,继续递归调用,str+1指向了’l’,l不等于\0,继续递归调用,str+1指向了’o’,o不等于\0,继续递归调用,str+1指向了’\0’,不会再进行调用,此时就到了归的环节了,归的是调用它的地方,所以依次return 1,return2,return 3,return 4,return 5,最后返回给main函数中调用它的地方。
#include
int my_strlen(char* str)
{
char* start=str;
while(*str!='\0')
{
str++;
}
return str-start;
}
int main()
{
char arr[]="abcdef";
int len=my_strlen(arr);
printf("%d\n",len);//6
return 0;
}
解释:
指针-指针,得到的是指针和指针之间的元素个数
讲到这里,我们求字符串长度函数就到这里了,下面我们看这个代码:
int main()
{
char arr[20]={0};
arr="hello";///error
return 0;
}
这个代码是错误的,arr是数组名,数组名是首元素地址,把hello放入这个(编号)地址上去吗?不行的,我们是要放进arr的内存空间的,那么我们怎么放进去呢?
此时我们就要用到strcpy
char *strcpy(char *destination,const char *source);
我们首先来看一下它的使用:
#include
#include
int main()
{
char arr[20]={0};
strcpy(arr,"hello");//传字符串实际上是将hello字符串的首字符地址h传过去了
printf("%s\n",arr);
return 0;
}
int main()
{
char arr1[20]={0};
char arr2[]={'a','b','c'};
strcpy(arr,arr2);
return 0;
}
arr2能拷贝进arr1吗?
答案是不行的,我们拷贝是要将’\0’也拷贝进去的,源字符串没有’\0’,我们不知道啥时候拷贝停止
#include
#include
int main()
{
char arr[20]={0};
strcpy(arr,"hello");//传字符串实际上是将hello字符串的首字符地址h传过去了
printf("%s\n",arr);
return 0;
}
如果你不相信我们可以调试:
调试后发现确实要将\0也拷贝进去的
#include
#include
int main()
{
char arr1[5]={0};
strcpy(arr1,"hello world");
return 0;
}
目标空间不够时,他会帮你拷贝进去,但是他会出问题
我们进行打印arr1是他会打印出来,但是会报错:
#include
#include
int main()
{
char* p = "************";
char arr[]="hello";
strcpy(p,arr);
return 0;
}
字符指针p指向的字符串是常量字符串,是不能修改的,所以我们的目标空间必须可变
我们调试发现,程序会崩掉:
#include
#include
char* my_strcpy(char* dest, const char* scr)
{
char* temp = dest;//创建一个临时变量存储dest,以便后面返回目标字符串的地址
assert(dest && scr);
while (*dest++ = *scr++)
{
;
}
return temp;
}
int main()
{
char arr1[] = "**************";
char arr2[] = "hello";
printf("%s\n", my_strcpy(arr1, arr2));
return 0;
}
解释:
循环判断条件是*dest++ = *scr++,首先我们要知道,a=b这个表达式的值是a的值,我们将arr1中的每个字符赋值给arr2中时,其实整个表达式的值在每次循环时的值分别为’h’e’l’l’o’\0’字符的ASSIC码值,当我们把\0赋值到arr2中时,同时整个表达式的值也为0了,所以退出循环。
我们必须保证目标字符数组的空间足以容纳需要复制的字符串。若字符串比数组长,多余的字符仍被复制,它们将覆盖原先存储于数组后面的内存空间的值。strcpy无法解决这个问题,它无法判断目标字符数组的长度。
接下来我们看strcat连接字符串
char * strcat ( char * destination, const char * source );
#include
#include
int main()
{
char arr1[20]={"hello "};//假设我们要追加world
char arr2[]="world";
//strcat(arr1,"world");//字符串追加或连接
strcat(arr1,arr2);
printf("%s\n",arr1);
return 0;
}
strcat(arr1,“world”); strcat(arr1,arr2);这两种方式都可以字符串连接。
#include
#include
int main()
{
char arr[]={'h','e','l','l','o'};
char arr2[]="world";
strcat(arr1,arr2);//字符串追加或连接
printf("%s\n",arr1);
return 0;
}
如果不以\0结束,是不能进行连接的,为什么不能连接呢?慢慢往下看,看到strcat函数是如何实现的你就知道啦!
#include
#include
int main()
{
char arr1[8] = { "hello "};
char arr2[] = "world";
strcat(arr1, arr2);//字符串追加或连接
printf("%s\n", arr1);
return 0;
}
如果目标空间不足够大,这里是会发生访问越界的,这里只能拷贝进wo。
#include
#include
int main()
{
char *p = "hello ";
char arr2[] = "world";
strcat(p, arr2);//字符串追加或连接
return 0;
}
字符指针p指向的字符串是常量字符串,是不能修改的,所以我们的目标空间必须可变
#include
#include
int main()
{
char arr[10]={"abcd"};
strcat(arr,arr);
printf("%s\n",arr);
return 0;
}
看下图解释:
我们调试发现:确实进行了死循环的连接,因为没有了连接的结束标志\0,不知道啥时候停止,直到空间满时会报错。
这里将追加的结束标志修改了,程序陷入了死循环,所以strcat函数是不能自己连接自己的
我们这里来看一下strcat是如何实现的,我们首先看下面代码测试:
#include
#include
int main()
{
char arr1[20]={"hello \0*************"};//假设我们要追加world
char arr2[]="world";
strcat(arr1,arr2);//字符串追加或连接
printf("%s\n",arr1);
return 0;
}
strcat函数的实现是目标字符串中的结束标志\0被源字符串的首字符覆盖,然后依次进行追加,直到追加到\0,即追加结束。
arr1中的\0是开始追加的标志,arr2中的\0是追加结束的标志,所以目的字符串和源字符串必须要有\0
#include
void my_strcat(char *dest,const char*src)
{
assert(dest&&src);
//1.找到目标字符串中的\0
while(*dest)
{
dest++;
}
//2.源字符串的追加,包含\0
while(*dest++=*src++)
{
;
}
}
int main()
{
char arr1[20]={"hello "};//假设我们要追加world
char arr2[]="world";
my_strcat(arr1,arr2);//字符串追加或连接
printf("%s\n",arr1);
return 0;
}
关于返回值:
库函数里strcat返回的是目标空间的起始地址,所以我们也将目标空间的起始地址返回,my_strcat函数返回的是指针,那么我们在打印时就可以这样打印:
printf("%s\n",my_strcat(arr1,arr2));
看如下代码:
#include
char* my_strcat(char *dest,const char*src)
{
char *ret=dest;//利用ret变量保存目标空间的起始地址
assert(dest&&src);
//1.找到目标字符串中的\0
while(*dest)
{
dest++;
}
//2.源字符串的追加,包含\0
while(*dest++=*src++)
{
;
}
return ret;//返回起始地址
}
int main()
{
char arr1[20]={"hello "};//假设我们要追加world
char arr2[]="world";
printf("%s\n",my_strcat(arr1,arr2));
return 0;
}
注意:
和前面的strcpy一样,我们必须保证目标字符数组剩余的空间足以保存整个源字符串。但这次并不是简单地把源字符串的长度和目标字符数组的长度进行比较,我们必须考虑目标数组中原先存在的字符串。
在讲strcmp之前我们先来看一下下面的这段代码:
#include
int main()
{
char *p="qwer";
char *q="awerty";
if(p>q)
{
printf(">\n");
}
else
{
printf("<=\n");
}
return 0;
}
经过调试发现:
p,q是字符指针,存放的是字符串首字符的地址,我们想需要的是比较两个字符串,而这里实际上比较的是p,q指针变量的地址,所以肯定不正确。
那么下面的这种写法可行吗?答案是也不行
if("obc">"abcdef")//这里比较的是字符o和字符a的地址
{
}
请看如下动图演示调试:
如果比较的是字符串的话,首字符o是大于首字符a的,应该进入if语句里面,但是我们调试发现这里进入了else语句里面,所以这里比较比较的是首字符o和首字符a的地址,而不是字符串,所以这种比较方式也不可行
这就出现了我们的主角strcmp(字符串比较函数)
int strcmp(const char *string1,const char *string2);
下面代码才能正确的比较两个字符串:
#include
#include
int main()
{
char *p="qwer";
char *q="awerty";
strcmp(p,q);
return 0;
}
知道了这个库函数,那么这个库函数的返回值是什么呢?
我们发现字符串1小于字符串2时,返回的值是小于0,字符串1等于字符串2时,返回的值为等于0,字符串1大于字符串2时,返回的值为大于0。
#include
#include
int main()
{
char *p="qwer";
char *q="awerty";
int ret = strcmp(p,q);
printf("%d\n",ret);
return 0;
}
!!!这里说明一下,博主的编译器是vs2019,vs编译器他将大于返回1,等于返回0,小于返回-1,像上面的代码,只要返回的值大于0就可以了,不需要纠结和我打印的值不一样
实现思路如下图解释:
代码实现如下:
#include
int my_strcmp(const char *s1,const char *s2)
{
assert(s1&&s2);
while(*s1==*s2)
{
if(*s1=='\0')
{
return 0;
}
s1++;
s2++;
}
if(*s1>*s2)
{
return 1;
}
else
{
return -1;
}
//return *s1-*s2;//刚好符合大于返回大于0的数,小于返回小于0的数
}
int main()
{
char *p="abcdef";
char *q="abcc";
int ret = my_strcmp(p,q);
if(ret>0)
{
printf("p > q\n");
}
else if(ret<0)
{
printf("p < q\n");
}
else
{
printf("p == q\n");
}
return 0;
}
注意1:
如果有些人没有关注到strcmp函数的返回值,它们常常会写出这样的表达式:if(strcmp(a,b)),他以为如果两个字符串相等,它的结果将会是真。但是,事实上这个结果却刚刚好相反,因为两个字符串相等时它返回的是0。
注意2:
标准并没有规定当两个字符串不相等时,strcmp函数返回的具体值是多少,它只是说字符串1大于字符串2会返回大于0的数,字符串1小于字符串2会返回小于0的数。
char *strncpy(char *dest,const char *source,size_t count);
我们发现strncpy相比于strcpy增加了一个参数,这个参数类型为size_t,无符号的数,strncpy函数用于将指定长度的字符串复制到字符数组的前(指定长度)个字符中
我们先来看一看它的使用:
#include
#include
int main()
{
char arr1[20]="abcdef";
char arr2[]="qwer";
strncpy(arr1,arr2,2);//相对安全
//strcpy(arr1,arr2);
printf("%s\n",arr1);
return 0;
}
strncpy函数用于将指定长度的字符串复制到字符数组中,他将arr2的前两个字符复制到arr1中。
当传入的长度要比源字符串长能拷贝进去吗?
#include
#include
int main()
{
char arr1[20]="abcdef";
char arr2[]="qwer";
strncpy(arr1,arr2,6);//相对安全
//strcpy(arr1,arr2);
printf("%s\n",arr1);
return 0;
}
当传入的长度要比源字符串长,strncpy函数也是能拷贝进去,而且打印的arr是qwer,为什么不是qweref呢?为什么呢?
我们首先调试一下:
开始时的arr1:
最后的arr1:
我们发现他不仅将qwer拷贝过去了,他还会给你拷贝够6个,只不过后面拷贝过去的是\0。实际上如果strlen(source)的值小于指定的长度,目标数组就用额外的NULL字节填充到指定长度,strlen(source)的值大于或等于指定的长度,那么只有指定长度个字符被复制到目标字符串中。注意!此时并不会将以\0拷贝过去。
当然我们必须需要知道库函数的代码是怎么样的,才能知道结果为什么是这样的,我们首先先来看一下库函数是怎么实现的,下面代码为strncpy在库函数中的实现:
char * __cdecl strncpy (
char * dest,
const char * source,
size_t count
)
{
char *start = dest;
while (count && (*dest++ = *source++) != '\0') /* copy string */
count--;
if (count) /* pad out with zeroes */
while (--count)
*dest++ = '\0';
return(start);
}
当传入的长度要比源字符串长时的解释:
count刚开始是6,每拷贝一次conut–,当字符串qwer拷贝完之后,遇到了\0,while循环终止,此时count是2,然后,count不为0,为真,进入if语句,将目标字符串拷贝进去的串后面的2个字符填充为\0,所以前面的代码打印的是qwer,这就解释了当传入的长度要比源字符串长时的情况。
char *strncat(char *dest,const char *source,size_t count);
strncat与strcat相比于strncpy与strcpy是一样的道理,strncat相比于strcat增加了一个参数,这个参数类型为size_t,无符号的数,strncat函数用于将指定长度的字符串连接到字符数组中
#include
#include
int main()
{
char arr1[20]="hello ";
char arr2[]={"world"};
strncat(arr1,arr2,3);
printf("%s\n",arr1);
return 0;
}
strncat函数用于将指定长度的字符串连接到字符数组中,这里将arr2中的前三个字符连接到arr1中去
那么当指定字符长度要比源字符串长时能连接过去吗?
#include
#include
int main()
{
char arr1[20]="hello ";
char arr2[]={"world"};
strncat(arr1,arr2,10);
printf("%s\n",arr1);
return 0;
}
答案是可以的。
我们来看一看库函数中strncat是怎么实现的:
char * __cdecl strncat (
char * front,
const char * back,
size_t count
)
{
char *start = front;
while (*front++)
;
front--;
while (count--)
if ((*front++ = *back++) == 0)
return(start);
*front = '\0';
return(start);
}
当我们指定字符传10进去时,我们看第13行代码,while(count–)循环里面进行连接,当连接到\0时,count不为0,循环继续,此时也将源字符串中的\0连接上去了,此时if语句里面的表达式成立(这里注意例如:a=b,则整个表达式的结果为a的值),然后return回目的字符串的地址了;当我们指定字符传3时,while循环当count变为0时,不会再进入循环,此时我们还没有将\0连接上去,所以后面*front = ‘\0’;这句代码就将\0连接上去了,最后返回目的字符串的地址。
注意:
和strncpy不同的是,它最多从源字符串中复制源字符串的长度个字符到目标数组的后面,strcat总是在结果字符串后面添加一个\0,它不会像strncpy那样在指定长度大于源字符串长度时对目标数组用\0进行填充。strncat最多向目标数组复制源字符串的长度个字符(加\0),它不会去管目标参数除去原先的字符串还够不够存储连接过来的字符串。
int strncmp( const char *string1, const char *string2, size_t count );
长度受限制的字符串函数与各自对应的长度受限制的字符串函数是十分相似的,strncmp与strcmp相比于前面两个是一样的道理,strncmp相比于strcmp增加了一个参数,这个参数类型为size_t,无符号的数,strncmp函数的作用是:字符串1的前指定长度的字符与字符串2中的前(指定长度)的字符进行比较
字符串1的前指定长度的字符与字符串2中的前(指定长度)的字符相等时,返回0
字符串1的前指定长度的字符小于字符串2中的前(指定长度)的字符时,返回<0的值
字符串1的前指定长度的字符大于字符串2中的前(指定长度)的字符时,返回>0的值
#include
#include
int main()
{
char *p="abcdef";
char *q="abcdqwert";
int ret = strcmp(p,q);
printf("%d\n",ret);//-1
return 0;
}
p所指向的字符串是比q指向的字符串小的,所以返回的是小于0的数,这里我的vs编译器返回的是-1
然后我们再来看看strncmp指定长度比较以下两个字符串的结果:
#include
#include
int main()
{
char *p="abcdef";
char *q="abcdqwert";
int ret = strncmp(p,q,4);
printf("%d\n",ret);
return 0;
}
p所指向字符串的前4个字符和q指向的字符串的前4个字符是相等的,所以返回的是0
- 作用是查找子串并返回字符串中首次出现子串的地址。
char *strstr( const char *string, const char *strCharSet );
#include
#include
int main()
{
char arr1[]="abcdefabcdef";
char arr2[]="bcd";
//在arr1中查找是否包含arr2
char *ret = strstr(arr1,arr2);//找到了就返回第一次出现的地址
if(ret==NULL)
{
printf("没找到\n");
}
else
{
printf("找到了:%s\n",ret);
}
return 0;
}
我们可以看到它返回值存入ret变量中,我们将ret打印,它确实返回的是子串第一次出现的地址
同样的我们可以测试找不到的情况:
可以看到此时ret为NULL,说明返回的是NULL,打印了没找到
#include
char *my_strstr(const char *str1,const char *str2)
{
assert(str1&&str2);
const char *s1=NULL;
const char *s2=NULL;//我们比较时不使用str1和str2,我们创建两个临时变量来移动比较,因为如果我们用str1和str2一直进行移动比较,到最后我们不知道起始的串在哪里,无法找到了。
const char *cp=str1;//指向每次开始比较时起始的第一个字符
if(*str2=='\0')//需要查找的字符串为空字符串的话,返回要浏览的字符串
{
return (char*)str1;//因为函数返回值为char*,所以需要强制类型转换
}
while(*cp)//cp指向不为\0时进入
{
s1=cp;//每次将新的cp赋给s1
s2=str2;//将需要查找的串赋给s2
while(*s1 && *s2 && (*s1==*s2))//s1和s2指向内容相等时s1、s2分别加加指向下一个字符进行比较,直到s1指向内容不等于s2所指向内容时跳出循环,并且s1和s2指向\0时,循环也停止
{
s1++;
s2++;
}
if(*s2=='\0')//当s2指向为\0时,说明比较结束,是子串,返回子串的起始位置
{
return (char*)cp;
}
cp++;//s2不是\0时,说明不是子串,cp指向下一个字符开始比较
}
return NULL;
}
int main()
{
char arr1[]="abcdefabcdef";
char arr2[]="bcd";
//在arr1中查找是否包含arr2
char *ret = my_strstr();//找到了就返回第一次出现的地址
if(ret==NULL)
{
printf("没找到\n");
}
else
{
printf("找到了:%s\n",ret);
}
return 0;
}
char *strtok( char *strToken, const char *strDelimit );
- strtok()用来将字符串分割成一个个片段。参数strToken指向欲分割的字符串,参数strDelimit 则为分割字符的字符串,当strtok()在参数strToken 的字符串中发现到参数strDelimit 的分割字符时则会将该字符改为\0 字符。在第一次调用时,strtok()必需给予参数strToken字符串,往后的调用则将参数strToken设置成NULL。每次调用成功则返回下一个分割后的字符串指针。
注意:
如果strtok函数的第1个参数不是NULL,函数将找到字符串的第1个标记。strtok同时将保存它在字符串中的位置。如果strtok 函数的第1个参数是NULL,函数就在同一个字符串中从这个被保存的位置开始像前面一样查找下一个标记。如果字符串内不存在更多的标记,strtok函数就返回一个NULL 指针。在典型情况下,在第1次调用strtok时,向它传递一个指向字符串的指针。然后,这个函数被重复调用(第1个参数为NULL),直到它返回NULL为止。
#include
#include
int main()
{
char arr[] = "www.baidu.com";
//printf("%s\n",strtok(arr, "."));
//printf("%s\n", strtok(NULL, "."));
//printf("%s\n", strtok(NULL, "."));*/
char* temp;
for (temp = strtok(arr, "."); temp != NULL; temp = strtok(NULL, "."))
{
printf("%s\n", temp);
}
return 0;
}
我们调试可以看到每一步strtok的调用的返回值temp的变化:
库函数strerror函数的原型如下:
char *strerror( int errnum );
我们在使用库函数的时候,调用库函数失败时,都会设置错误码,或者说当你调用一些函数,请求操作系统执行一些功能如打开文件时,如果出现错误,操作系统是通过设置一个外部的整型变量errno进行错误代码报告的。strerror函数把其中一个错误代码作为参数并返回一个指向用于描述错误的字符串的指针。
我们的strerror函数翻译错误码并返回一个指向用于描述对应错误信息的字符串的指针。
#include
#include
int main()
{
printf("%s\n",sterror(0));
printf("%s\n",sterror(1));
printf("%s\n",sterror(2));
printf("%s\n",sterror(3));
printf("%s\n",sterror(4));
printf("%s\n",sterror(5));
FILE* pf=fopen("test.txt","r");
if(pf==NULL)//打开文件失败时,会返回NULL
{
printf("%s\n",strerror(errno));
return 1;
}
fclose(pf);
pf=NULL;
return 0;
}
错误码0,1,2,3,4,5所对应的错误信息如上图打印,当我们打开一个文件失败时,操作系统是通过设置一个外部的整型变量errno进行错误代码报告的。
perror函数的功能是打印错误信息,它和strerror函数不同的是,strerror返回的是字符指针,perror返回的是void,使用strerror函数时,如果需要打印错误信息,需要我们自己写printf进行打印,而perror函数在调用它时,就将错误信息打印了。参数 string 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。
void perror( const char *string );
perror函数实现主要是以下两步:
1、首先把错误码转换为错误信息
2、打印错误信息(包含了自定义信息)
#include
#include
int main()
{
printf("%s\n", strerror(0));
printf("%s\n", strerror(1));
printf("%s\n", strerror(2));
printf("%s\n", strerror(3));
printf("%s\n", strerror(4));
printf("%s\n", strerror(5));
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)//打开文件失败时,会返回NULL
{
//printf("%s\n",strerror(errno));
perror("fopen");
return 1;
}
fclose(pf);
pf = NULL;
return 0;
}
它会直接打印出错误信息:
我们前面讲到的都是操作字符串的,下面我们来讲一些操作字符的函数。
每一个分类函数接收一个包含字符值得整形参数。函数测试这个字符并返回一个整形值,表示真或假。
分类函数如下表:
函数 | 如果它的参数符合下列条件就返回真 |
---|---|
iscntrl | 任何控制字符 |
isspace | 空白字符:空格’’,换页’\f’,换行’\n’,回车’\r’,制表符’\t’或者垂直制表符’\v’ |
isdigit | 十进制数字0~9 |
isxdigit | 十六进制数字,包括所有十进制数字,小写字母a~f,大写字面A~F |
islower | 小写字母a~f |
isupper | 大写字母A~F |
isalpha | 字母a~z或者A~Z |
isalnum | 字母或数字:a~z或者A~Z或0~9 |
ispunct | 标点符号,任何不属于数字或字母的图形字符(可打印字符) |
isgraph | 任何图形字符 |
isprint | 任何可打印字符,包括图形字符和空白字符 |
我们来看两个字符分类函数的测试:
#include
#include
int main()
{
char ch='#';
int ret = isdigit(ch);//是不是数字字符
printf("%d\n",ret);
return 0;
}
如果是数字字符返回非零,如果不是数字字符返回零
#include
#include
int main()
{
char ch='a';
int ret = islower(ch);//是不是小写字母
printf("%d\n",ret);
return 0;
}
如果是小写字母返回非零,如果不是小写字母返回零
- 转换函数把大写字母转换成小写字母或者把小写字母转换为大写字母。
int tolower(int ch);
int toupper(int ch);
- toupper函数返回其参数的对应大写形式,tolower函数返回其参数的对应小写形式。如果函数的参数并不是一个处于适当大小写状态的字符(即toupper的参数不是小写字母或tolower的参数不是大写字母),函数将不修改参数直接返回。
#include
#include
int main()
{
char arr[20]={0};
scanf("%s",arr);
int i=0;
while(arr[i]!='\0')
{
if(isupper(arr[i]))
{
arr[i]=tolower(arr[i]);
}
printf("%c",arr[i]);
i++;
}
return 0;
}
如果函数的参数并不是一个处于适当大小写状态的字符(即toupper的参数不是小写字母或tolower的参数不是大写字母),函数将不修改参数直接返回。
我们在判断一个ch字符是不是一个大写字符时可以这样判断:
if(ch>='A'&&ch<='Z')
- 这样判断是可以判断,但是程序的可移植性不好,可能在其他使用不同的字符集机器上可能是不会成功的,而下面这个语句,无论在哪个机器上,都能够顺利运行的。
if(isupper(ch));
- 我们上面讲的函数都是操作字符串或者字符的,字符串是以NULL字节结尾的,所以字符串内部是不能包含任何NULL字节的,那么对于那些内部包含零值的数据类型该怎么处理呢?我们是无法通过字符串函数来操作这类数据类型的,这时就出现了另外一组相关的函数—内存操作函数
内存操作函数的操作和字符串函数类似,只是它可以处理任意的字节序列,接下来我们首先来看memcpy函数。
void *memcpy( void *dest, const void *src, size_t count );
#include
int main()
{
int arr1[10]={1,2,3,4,5,6,7,8,9,10};
int arr2[20]={0};
memcpy(arr2,arr1,20);
return 0;
}
- 我们调试能够看到memcpy函数将arr1的前20个字节拷贝到了arr2中
memcpy函数是怎么将源数据拷贝到目的地的呢?我们这样进行拷贝可以吗?
void *my_memcpy(void *dest,const void *src,size_t num)
{
assert(dest&&src);
void *ret=dest;
while(num--)
{
*dest++ = *src++;
}
return ret;
}
这样显然是不可以的,因为我们的函数参数是void*的类型,我们是不能对void*类型的参数进行解引用操作的,而且我们解引用也不能明确的知道他会访问几个字节,所以我们要先将dest和src强制类型转换为char*,我们一个字节一个字节的进行拷贝。那么拷贝成功一个字节我们怎么让dest和src指向下一个字节呢?同样的我们需要将dest和src分别强制类型转换为char*然后+1,这样就向后移动一个字节了
所以我们的代码应该这样写:
#include
void *my_memcpy(void *dest,const void *src,size_t num)
{
assert(dest&&src);
void *ret=dest;//保存拷贝目的地的起始地址
while(num--)
{
*(char*)dest = *(char*)src;//一个字节一个字节拷贝
dest = (char*)dest+1;
src = (char*)src+1;
}
return ret;//返回拷贝目的地的起始地址
}
int main()
{
int arr1[10]={1,2,3,4,5,6,7,8,9,10};
int arr2[20]={0};
my_memcpy(arr2,arr1,20);
return 0;
}
完成了memcpy函数的模拟,我们思考这么一个问题:我们将arr1数组的1,2,3,4,5拷贝到3,4,5,6,7所在的内存空间上,可不可以呢?
my_memcpy(arr1+2,arr1,20);
我们结果和我们想的并不一样,我们想的是1,2,1,2,3,4,5,8,9,10,但是结果却是1,2,1,2,1,2,1,8,9,10,为什么呢?看下图解释:
所以memcpy应该拷贝不重叠的内存
这时就有了memmove函数,memmove可以处理内存重叠的情况。
void *memmove( void *dest, const void *src, size_t count );
memmove的参数和返回类型的解释与memcpy函数是一样的,这里就不再进行解释了。
#include
int main()
{
int arr1[10]={1,2,3,4,5,6,7,8,9,10};
memmove(arr1+2,arr1,20);
return 0;
}
我们调试发现,已经进行了拷贝,memmove可以处理内存重叠的情况。
memmove函数该怎么实现呢?请看下图说明:
我们进行拷贝时,可以从前往后拷贝,也可以从后往前拷贝。当我们想要把如图中的1 2 3 4 5拷贝到3 4 5 6 7上去,从前往后显然是不行的,但是我们仔细思考发现从后往前是可以拷贝成功的,数组里面元素地址从前往后是从低到高的。我们得出结论:当src的地址大于dest的地址时,我们可以用从前往后进行拷贝,当src的地址小于dest的地址时,我们可以用从后往前进行拷贝,当dest大于src加指定字节数时,无论是从前往后还是从后往前都可以完成拷贝。
我们也可以将上面三种情况分为两种情况:dest
最终的memmove的模拟实现代码如下:
void *my_memmove(void *my_memmove(void *dest,const void *src,size_t num))
{
assert(dest&&src);
void* ret=dest;
if(dest<src)
{
//前->后
while(num--)
{
*(char*)dest=*(char*)src
dest=(char*)dest+1;
src=(char*)src+1;
}
}
else
{
//后->前
while(num--)
{
*((char*)dest+num)=*((char*)src+num);
}
}
return ret;
}
memmove函数我们的讲解就到这里啦!
下面我们来看一个小问题:
我们在前面中使用自己模拟实现的memcpy函数时,发现是不能拷贝重叠内存的,可能有人会疑惑,为什么我的memcpy库函数是可以实现拷贝重叠内存的呢?是不是我们的模拟实现的memcpy函数错了呢?
如上图,我们的vs编译器中的memcpy函数就可以重叠拷贝。
解释:
我们实现的memcpy当然不是错误的,对于memcpy函数,标准规定只要实现了不重叠拷贝时的情况就可以了,而vs编译器中的实现既可以拷贝不重叠内存,也可以拷贝重叠内存,在vs编译器中,memcpy是可以实现重叠拷贝的,但是在其他编译器环境中,memcpy不一定也能实现重叠拷贝。
函数的作用是比较内存区域buf1和buf2的前count个字节
库函数memcmp函数的原型如下:
int memcmp( const void *buf1, const void *buf2, size_t count );
#include
#include
int main()
{
float arr1[]={1.0,2.0,3.0,4.0};
float arr2[]={1.0,3.0};
int ret=memcmp(arr1,arr2,4);//0
//memcmp - strcmp返回值的设置是一样的
return 0;
}
关于函数返回值:
memcmp函数的返回值和strcmp函数返回值的设置是一样的,这里就不进行描述了,前面我们已经详细说过了。
库函数memset函数的原型如下:
void *memset( void *dest, int c, size_t count );
将指针变量 dest所指向的前 count 字节的内存单元用一个“整数” c 替换,注意 c 是 int 型。dest 是 void 型的指针变量,所以它可以为任何类型的数据进行初始化。*
#include
#include
int main()
{
int arr[10]={0};
memset(arr,1,20);//以字节为单位设置内存的
return 0;
}
将arr中的前20个字节初始化为1
- 字符串是零个或多个字符的序列,该序列以\0为结尾。标准库提供了一些函数处理这些字符串,它们的原型位于头文件string.h中。
- strlen函数用于计算一个字符串的长度,要注意它的返回值是一个无符号的整数,我们将它用于表达式中时要小心。strcpy函数把一个字符串从一个位置复制到另一个位置,而strcat函数把一个字符串的一份拷贝连接到另一个字符串的后面,我们要注意,这两个函数都是假定它们的参数是有效的字符串,如果源字符串和目标字符串出现重叠时,函数的结果是未定义的。strcmp函数对两个字符串进行比较,它的返回值提示第一个字符串是大于、等于、或者小于第二个字符串。
- strncpy、strncat、strncmp都类于它们所对应的长度不受限制的函数,区别在于它们多了一个长度参数,在strncpy函数中,长度指定了多少个字符将被拷贝进目标字符数组中,如果源字符串比指定字符长,将不会拷贝\0;如果源字符串比指定长度短,它会将源字符串全部拷贝进目标字符数组中,然后剩余的长度将以\0填充进目标字符数组。在strncat函数中,长度指定了多少个字符将被连接到目标字符数组后面,当指定长度大于源字符串长度时,它会拷贝整个源字符串过去(加\0拷贝过去),当指定长度小于源字符串长度时,它会将指定长度字符拷贝到目标字符数组中(加\0拷贝过去),所以不管指定长度和源字符串长度的关系大小,它都会将\0拷贝过去。在strncmp函数中,长度指定了字符比较的数目,它的返回值提示第一个字符串的前指定字符是大于、等于、或者小于第二个字符串的前指定字符。
- 字符串查找函数我们这里讲解了两个函数,strstr函数和strtok函数,strstr函数在一个字符串中查找另一个字符串第一次出现的位置,strtok函数把一个字符串分割成几个标记,每次当它被调用时,都返回一个指向字符串中下一个标记位置的指针,当它找不到标记时,会返回空指针;这些标记由一个指定字符集的一个或多个字符分隔。
- strerror把一个错误代码作为它的参数,它返回一个指向描述错误信息字符串的指针。perror函数和strerror函数不同的是,我们在调用perror时,它会打印我们传入的信息,然后后面跟着错误信息,而strerror函数则需要我们自己打印错误信息。
- 标准库还提供了各种用于测试字符和转换字符的函数,我们使用这些函数的程序比那些自己执行字符测试和字符转换的程序更具移植性。toupper函数把一个小写字母字符转换成大写字母字符,而tolower函数则把一个大写字母转换成小写字符。测试字符大家可以认真看字符分类那一块讲解的那一个表格。它们的原型位于头文件ctype.h中
- memxxx类的函数提供了类型字符串函数的功能,但它们可以处理包括空字节在内的任意字节。这些函数都接受一个长度参数,memcpy将源参数向目标参数复制有长度参数指定的字节数,memmove与memcpy功能相同,只是它可以处理源参数和目标参数内存重叠的情况,memcmp函数比较两个参数的指定字节,memset函数把一个序列的指定字节初始化为特定的值。
有关字符串和字符函数的讲解就到这里,由于博主水平有限,如有错误之处,还望指正,欢迎大家学习交流!
大家如果觉得文章不错的话别忘了点赞评论加收藏哦!