重点介绍处理字符和字符串的库函数的使用和注意事项
1.求字符串长度 strlen 2.长度不受限制的字符串函数 strcpy strcat strcmp 3.长度受限制的字符串函数介绍 strncpy strncat strncmp 4.字符串查找 strstr strtok 5.错误信息报告 strerror 6.字符操作 7.内存操作函数 memcpy memmove memset memcmp
size_t strlen(const char* str);
字符串已经’\0’作为结束标志,strlen函数返回的是在字符串中’\0’前面出现的字符个数(不包含’\0’) 注: 1)参数指向的字符串必须要以’\0’结束。 2)注意函数的返回值为size_t,是无符号的。(易错) 3)学会strlen函数的模拟实现。
#include <stdio.h>
int main()
{
const char* str1 = "abcdef";
const char* str2 = "bbb";
if (strlen(str2) - strlen(str1) > 0)
{
printf("str2>str1\n");
}
else
{
printf("srt1>str2\n");//打印
}
return 0;
}
char* strcpy(char * destination, const char * source );
将source的字符全部复制给destination,返回值为复制之后的destination的地址 注: 1)原字符串必须以’\0’结束。 2)会将source中的0拷贝到destination的空间。 3)目标空间必须足够大,以确保能存放原字符串。 4)目标空间必须可变。 5)学会模拟实现。
char* strcat(char* destination,const char* source);
将源字符串的副本追加到目标字符串。终止空字符在destination中被源的第一个字符覆盖,并且包含一个空字符在新字符串的末尾,将两者连接在destination中形成,返回值为连接之后的destination的首地址。 注: 1)源字符串必须以’\0’结束 2)目标空间必须足够大,即destination必须放得下二者之和 3)目标空间必须可修改。 4)字符串自己给自己追加,如何? 答 4):会陷入死循环,在模拟实现中解释
int strcmp(const char* str1, const char* str2);
这个函数开始比较每个字符串的第一个字符。如果它们相等,则继续向后比较,直到字符不同或终止null字符。注:(比较的是ascii码值的大小) 标准规定: 1)第一个字符串大于第二个字符串,则返回大于0的数字 2)第一个字符串等于第二个字符串,则返回0 3)第一个字符串小于第二个字符串,则返回小于0的数字 4)那么如何判断两个字符串?一个字母一个字母比较ascii码值的大小,不相等就截止
char* strncpy(char* destination, const char* source ,size_t num);
比strcpy多了一个长度上的限制,即将source的前num个字符拷贝到destination中。 注: 1)拷贝num个字符从源字符串到目标空间。 2)如果源字符串的长度小于num,则拷贝完字符串之后,在目标的后面追加0,直到num个。
char* strncat(char* destination,const char* source,size_t num);
比strcat多了一个长度上的限制,即将source的前num个字符连接到destination上。
int main()
{
char str1[20];
char str2[20];
strcpy(str1, "To be ");
strcpy(str2, "or not to be");
strncat(str1, str2, 6);
puts(str1);
return 0;
}
结果如下:
int strncmp(const char* str1,const char* str2,size_t num);
比较前num个字符二者的大小,比strcmp多了数量上的限制
int main()
{
char str[][5] = { "R2D2","C3Po","R2A6" };
int n;
puts("Looking for R2 astromech droids...");
for (n = 0; n < 3; n++)
{
if (strncmp(str[n], "R2xx", 2) == 0)
{
printf("found %s\n", str[n]);
}
}
return 0;
}
char* strstr(const char*str1,const char* str2);
在字符串str1中查找str2是否在str1内部,即查找子串,若找到,则返回str2在str1处的地址,若找不到,则返回NULL。
char* strtok(char* str, const char* sep);
注: 1)sep参数是个字符串,定义了用作分隔符的字符集合 。 2第一个参数指定一个字符串,它包含了0个或多个由sep字符串中一个或者 多个分隔符分割的标记。 3)strtok函数找到str中的下一个标记,并将其用’\0’结尾,返回一个指向这个标记的指针。(strtok会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改) 4)strtok函数的第一个参数不为NULL,函数将找到str中的第一个标记,strtok函数将保存它在字符串中的位置。 5)strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记。 6)如果字符串不存在更多标记,则返回NULL指针。
//strtok example
int main()
{
char str[] = "- This, a sample string.";
char* pch;
printf("Splitting string \"%s\" into tokens:\n", str);
pch = strtok(str, ",.-");
while (pch != NULL)
{
printf("%s\n", pch);
pch = strtok(NULL, ", .-");//逗号,空格,点,减号
}
return 0;
//下面的循环方法同样适用:
//int main()
//{
// const char* sep = "@.";
// char email[] = "zhangpengwei@bitejiuyeke.com.net";
// char cp[40] = { 0 };//"zhangpengwei@bitejiuyeke.com"
// strcpy(cp, email);
//
// char* ret = NULL;
// for (ret = strtok(cp, sep);
// ret != NULL;
// ret=strtok(NULL, sep))
// {
// printf("%s\n", ret);
// }
}
结果为:
注:当出现两个及以上分隔符连在一起时,看成一个分隔符即可”
char* strerror(int errnum);
strerror的功能是返回错误码,所对应的错误信息。从而找到错误的原因。
//C语言的库函数,在执行失败的时候,都会设置错误码
//0 1 2 3 4 5 6 7 8
#include<errno.h>
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));*/
//errno - C语言设置的一个全局的错误码存放的变量
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
}
else
{
//一系列操作
}
return 0;
}
字符分类函数:<ctype.h> 函数 &如果他的参数符合下列条件就返回真 iscntrl 任何控制字符 isspace 空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’ isdigit 十进制数字 0~9 isxdigit 十六进制数字,包括所有十进制数字,小写字母af,大写字母AF islower 小写字母a~z isupper 大写字母A~Z isalpha 字母az或AZ isalnum 字母或者数字,az,AZ,0~9 ispunct 标点符号,任何不属于数字或者字母的图形字符(可打印) isgraph 任何图形字符 isprint 任何可打印字符,包括图形字符和空白字符
大小写字母转换函数: int tolower ( int c ); int toupper ( int c );
int main()
{
//int a = isspace(' ');
//printf("%d\n", a);
//int a = isdigit('x');
//printf("%d\n", a);
printf("%c\n", tolower('@'));//@
printf("%c\n", tolower('A'));//a
printf("%c\n", toupper('b'));//B
return 0;
}
void* memcpy(void* destination,const void* source,size_t num);
注: 1)函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。 2)这个函数遇到’\0’的时候并不会停下来。 3)如果source和destination有任何的重叠,复制的结果都是未定义的。
struct {
char name[40];
int age;
} person, person_copy;
int main ()
{
char myname[] = "Pierre de Fermat";
memcpy ( person.name, myname, strlen(myname)+1 );
person.age = 46;
memcpy ( &person_copy, &person, sizeof(person) );
printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age );//Pierre de Fermat 46
return 0;
}
memcpy 负责拷贝两块独立空间中的数据,那么重叠内存的拷贝,是怎么做的呢?–>利用memmove
void* memmove(void* destination, const void* source, size_t num);
和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的,如果源空间和目标空间出现重叠,就得使用memmove函数处理。
int main()
{
char str[] = "memmove can be very useful......";
memmove(str + 20, str + 15, 11);
puts(str);
return 0;
}
int memcmp(const void* ptr1, const void* ptr2, size_t num);
比较从ptr1和ptr2指针开始的num个字节,返回值与strncpy相同,区别:strncpy比较的是指针指向的字符的大小,memcmp比较的是所有类型的大小。
方式1:计数器
int my_strlen(const char * str)
{
int count = 0;
while(*str)
{
count++;
str++;
}
return count;
}
方式2:递归
int my_strlen(const char * str)
{
if(*str == '\0')
return 0;
else
return 1+my_strlen(str+1);
}
方式3:指针减指针(得到的为指针指向类型的个数)
int my_strlen(char *s)
{
char *p = s;
while(*p != ‘\0’ )
p++;
return p-s;
}
char* my_strcpy(char* dest, const char* src)
{
char* ret = dest;
assert(dest && src);
while(*dest++ = *src++)
{
;
}
return ret;
}
char* my_strcat(char* dest, const char* src)
{
char* ret = dest;
assert(dest && src);
while (*dest)//不能在里面直接++,这样会跳过一个'\0'的位置
{
dest++;
}
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[20] = "abcdef";
//char arr1[20];
char arr2[5] = "ABCD";
char* p = my_strcat(arr1, arr2);
printf("%s\n", p);
printf("%s\n", arr1);
return 0;
}
strcat不能自己给自己追加,会无限循环下去:
三指针迭代,一指针记录位置,两个指针向后++查找
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
const char* s1 = str1;
const char* s2 = str2;
const char* p = str1;
while (*p)
{
s1 = p;
s2 = str2;
while ( *s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)p;
}
p++;
}
return NULL;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "def";
char* ret = my_strstr(arr1, arr2);
if (ret == NULL)
{
printf("子串不存在\n");
}
else
{
printf("%s\n", ret);
}
return 0;
}
int my_strcmp(const char* s1, const char* s2)
{
assert(s1 && s2);
while (*s1 == *s2)
{
if (*s1 == '\0')
{
return 0;//相等
}
s1++;
s2++;
}
return (*s1 - *s2);
}
int main()
{
char arr1[20] = "zhangsan";
char arr2[] = "zhangsanfeng";
int ret = my_strcmp(arr1, arr2);
if (ret < 0)
printf("arr1<arr2\n");
else if (ret == 0)
printf("arr1=arr2\n");
else
printf("arr1=arr2\n");
return 0;
}
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;//强转的目的是让其+1迈过的长度为char,即一个字节
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memcpy(arr2, arr1, 20);//20个字节大小,即5个整形
return 0;
}
为什么memcpy不能拷贝重叠的内存呢?通过模拟实现我们知道:当我们拷贝到重叠的部分时,那里已经被新的值所覆盖,故再次拷贝将会把新值拷贝过去。
当然,这是模拟实现的memcpy,当我们真正运用memcpy时,会发现出来的结果跟memmove一样:
这是因为由于VS本身的功能会将这个错误避免,在不同编译器下,函数处理的结果可能会不一样,而模拟实现的my_memcpy才是memcpy真正的逻辑原理,因此,memmove还是非常必要的
还以上面arr1数组为例,由于dest>src,故我们可以从右往左拷贝,即从高地址拷贝到低地址:
即对模拟实现的my_memcpy稍作拷贝上的修改即可:
void* my_memmove(void* dest, void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
if (dest < src)
{
//与my_memcpy拷贝方向一致,即从前到后
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;
}
void test3()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr1 + 2, arr1, 20);
//1 2 1 2 3 4 5 8 9 10
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
}
int main()
{
test3();
return 0;
}
这个分支是我后续加上的(2022.7.25),因为我突然想到了一个新的方法来实现这道题目。在此之前,可以用两种方法实现,今天之后,就变成三种了:
那先来介绍第一种,这也是我刚接触到这道题第一反应想到的方法: 思路是通过记住每一个单词的首地址,通过数组(数组的每一个元素为指针变量,即指针数组)封装起来,再将其地址逆序按%s输出:
#include<stdio.h>
#include<string.h>
int main()
{
char* at[500];
char arr[100];
gets(arr);
int len = strlen(arr);
int i = 0;
int j = 0;
at[j++] = &arr[0];
for(i=0;i<len;i++)
{
if(arr[i]==' ')
{
at[j++] = &arr[i+1];
arr[i] = '\0';
}
}
//arr[i] = '\0';
for(i=j-1;i>=0;i--)
{
printf("%s ",at[i]);
}
return 0;
}
当然结果是对的,但实际上,我们只是记录其位置将其打印而已,字符串本身并没有发生变化。因此,我们参考一下第二种方法:
两步翻转,即先翻整体,再翻局部
#include<stdio.h>
#include<string.h>
#include<assert.h>
void reverse(char* left, char* right)
{
assert(left);
assert(right);
while (left < right)
{
char tmp = *left;
*left = *right;
*right = tmp;
left++;
right--;
}
}
int main()
{
char arr[101] = { 0 };
//输入
gets(arr);//I like beijing.
//逆置
int len = strlen(arr);
//1. 逆序整个字符串
reverse(arr, arr + len - 1);
//2. 逆序每个单词
char* start = arr;
while (*start)
{
char* end = start;
while (*end != ' ' && *end != '\0')
{
end++;
}
reverse(start, end - 1);
if (*end != '\0')
end++;
start = end;
}
//输出
printf("%s\n", arr);
return 0;
}
即这种方法在原数组的基础之上逆序了单词。
接下来,也就是增加这个分支的目的,即第三个方法,因为上文已经提到过strtok的用处,这里就不具体描述了。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
char str[] = "I like beijing.";
char* pch;
char flag = *(str + strlen(str) - 1);
pch = strtok(str, " ");
char** arr = (char**)malloc(sizeof(char*) * 5);
int j = 0;
while (pch != NULL)
{
arr[j++] = pch;
printf("%s ", pch);
pch = strtok(NULL, " ");
}
printf("\n");
for (int i = j-1; i>=0; i--)
{
printf("%s ", arr[i]);
}
free(arr);
return 0;
}
这种方式其实也是类似于第一种的思想,存储位置,逆序打印,但很明显,这种方法在strtok的辅助之下使其更为简洁和灵活,值得关注的是,这里的malloc大小为20个字节,如果单词数比较多的话,可以多开辟空间,是没有影响的,因为j记录了实际有效单词的数量。 当然即便面对单词之间有多个空格,strtok也会自动将其忽略,这也是strtok的强大之处。
通过对以上函数的了解,对于字符数组的操作以及内存类的函数会变得得心应手,要用其功能必先了解其原理。那么,这篇文章就到这里,码字不易,你们的支持将是我前进的不竭动力!
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有