理财网站模板,定制小程序制作平台,做引流网站怎么赚钱赚谁的钱,层流病房建设单位网站文章目录 前言一、求字符串的长度strlen函数strlen函数学习使用strlen函数模拟实现strlen函数模拟实现方法1#xff1a;计数器法strlen函数模拟实现方法2#xff1a;指针减指针法strlen函数模拟实现方法3#xff1a;递归方法 二、字符串的拷贝#xff0c;拼接和比较strcpy函… 文章目录 前言一、求字符串的长度strlen函数strlen函数学习使用strlen函数模拟实现strlen函数模拟实现方法1计数器法strlen函数模拟实现方法2指针减指针法strlen函数模拟实现方法3递归方法 二、字符串的拷贝拼接和比较strcpy函数strcpy函数学习使用strcpy函数模拟实现strcpy函数模拟实现方法1strcpy函数模拟实现方法2对方法1的改进 strncpy函数strncpy函数学习使用strncpy函数模拟实现 strcat函数strcat函数学习使用strcat函数模拟实现 strncat函数strncat函数学习使用strncat函数模拟实现 strcmp函数strcmp函数学习使用strcmp函数模拟实现 strncmp函数strncmp函数学习使用strncmp函数模拟实现 三、字符串的查找strstr函数strstr函数学习使用strstr函数模拟实现 strtok函数strtok函数学习使用 四、错误信息报告strerror函数strerror函数学习使用 五、内存操作的函数memcpy函数memcpy函数学习使用memcpy函数模拟实现 memmove函数memmove函数学习使用memmove函数模拟实现 六、比较实用的字符操作函数总结 前言
在C语言中我们对字符串的操作大部分都是通过字符串函数来进行的下面就让我们来深入的了解一下关于字符串操作的函数。
一、求字符串的长度
strlen函数
函数原型
size_t strlen( const char *string );
//string起始位置的地址strlen是用来求字符串长度的函数它求的长度不包含结尾的’\0’。 官方文档解释 注意返回字符串中的字符数中不包括终止的null字符
strlen函数学习使用
int main()
{char str1[] abcdef;char str2[] { a,b,c,d,e,f};int test1 strlen(str1);int test2 strlen(str2);printf(%d\n%d\n, test1, test2);return 0;
}下面我们根据内存来看一下原因 我们看到str1和str2相比结尾有一个’\0’而strlen函数找到结束字符才停止str2后面的元素是未知的str2后’\0’的位置也是未知所以得到值和我们str1不同。 如果我们传空指针或是空字符串会出现什么情况累
int main()
{char* str3 ;char* str4 NULL;int test3 strlen(str3);printf(%d, test3);int test4 strlen(str4);printf(%d, test4);return 0;
}我们发现程序会挂掉但是原因是什么呢
空指针不可以进行解引用操作
strlen函数模拟实现
根据上面的案例我们来模拟实现strlen函数的主要思路是找到结束字符对传入的地址进行判断防止为空。下面就让我们来模拟实现一下吧。 测试用例
int main()
{char str1[] abcdef;char str2[] { a,b,c,d,e,f};char* str3 ;int test1 my_strlen(str1);int test2 my_strlen(str2);int test3 my_strlen(str3);printf(%d\n%d\n%d\n, test1, test2,test3);return 0;
}strlen函数模拟实现方法1计数器法
size_t my_strlen(const char* string)
{assert(string);//判断传入的地址是否有效int count 0;//创建一个计数器用来存储字符个数while (*string)//string会先和结合但是是前置进行解引用时使用的是未前的值//当解引用未结束字符时即\0这时循环条件为假结束循环{count;}return count;
}strlen函数模拟实现方法2指针减指针法
size_t my_strlen(const char* string)
{assert(string);//判断传入的地址是否有效char *start string;//记录起始位置char *end string;//记录结束位置while (*end)//判断是否为结束字符{end;//结束位置后移}return end - start;//指针减指针得到的是两个指针之间的元素个数
}strlen函数模拟实现方法3递归方法
size_t my_strlen(const char* string)
{if (*string)//如果字符串这个位置不为结束字符{return 1 my_strlen(string 1);//返回一个字符的数量并且进行递归传入这个元素的下一个位置直到递归到结束字符为止}return 0;//如果是结束字符则返回0因为结束字符是不计算的
}二、字符串的拷贝拼接和比较
strcpy函数
函数原型
char* strcpy(char* strDestination, const char* strSource);
//strDestination目标字符串的位置
//strSource 源头字符串的位置
//返回值传入的目标字符串起始的位置strcpy函数将源头字符串包括终止的null字符复制到目标字符串中。 官方文档解释 注意strcpy是不执行溢出检查的所以我们使用时要注意数组的越界防止拷贝时出现数组的越界访问
strcpy函数学习使用
int main()
{char strDestination1[15] abcdefggggggg;char strDestination2[] abcde;char strSource[] hijklm;printf(拷贝前strDestination1%s\n, strDestination1);strcpy(strDestination1, strSource);printf(拷贝后strDestination1%s\n,strDestination1);printf(拷贝前strDestination2%s\n, strDestination2);strcpy(strDestination2, strSource);printf(拷贝后strDestination2%s\n, strDestination2);return 0;
}我们发现程序崩溃并且提示出来错误。观察发现是我们strDestination2拷贝时发生了越界由原来的5个元素变成了6个产生了越界访问。所以产生了越界访问错误。让我们通过内存再来详细的看一下吧。 上面是strDestination1中内存经过strcpy后的变化。 打印结果可以看出strcpy确实对我们的strDestination1进行了拷贝由于我们strDestination1的内存远大于我们源头的字符串所以并未出现错误信息并且程序可以正常运行。 接下来让我们看strDestination2的内存信息吧 我们来看打印信息 我们可以发现strDestination2内存已经不足了但还是把源头的字符串全部放入到了strDestination2中。 通过错误信息来看是我们越界访问并且修改了strDestination2后面的元素造成的。
strcpy函数模拟实现
通过上面用例学习我们已经了解了strcpy函数的使用方法和注意事项那么我们在模拟实现就解决这些问题并且改进一下吧。 测试用例
int main()
{char strDestination1[15] abcdefggggggg;char strDestination2[] abcde;char strSource[] hijklm;printf(拷贝前strDestination1%s\n, strDestination1);printf(拷贝后strDestination1%s\n, my_strcpy(strDestination1, strSource));printf(拷贝前strDestination2%s\n, strDestination2);printf(拷贝后strDestination2%s\n, my_strcpy(strDestination2, strSource));return 0;
}我们在刚才的测试中小小的改动了一下为了观察函数的返回值目的是为了实现链式访问。
strcpy函数模拟实现方法1
char* my_strcpy(char* strDestination, const char* strSource)
{assert(strDestination strSource);//判断strDestination和strSource地址是否合法char* str strDestination;//用来记录strDestination的起始位置方便对目标字符串的返回while ((*strDestination ! \0) (*strSource ! \0))//只有双方都不为‘\0’才进入循环{*strDestination *strSource;//进行拷贝strDestination;//对目标位置进行加1strSource;//对源头置进行加1}if (*strSource \0)//如果源头为‘\0’我们需要把源头的‘\0’拷贝到目标字符串中//如果是目标字符串中‘\0’则证明我们在进行拷贝会产生越界所以不需要在拷贝了{*strDestination *strSource;}return str;//返回strDestination的起始位置
}这种我们不仅实现拷贝’\0’并且防止了数组越界的产生。
strcpy函数模拟实现方法2对方法1的改进
char* my_strcpy(char* strDestination, const char* strSource)
{assert(strDestination strSource);//判断strDestination和strSource地址是否合法char* str strDestination;//用来记录strDestination的起始位置方便对目标字符串的返回while ((*strDestination ! \0) (*strDestination *strSource))//strDestination不为空//strDestination先和结合和*结合是前的结果解引用等于strSource前的结果//一个等号是进行赋值当*strSource为‘\0’时把这个结果赋给*strSource时这个表达式的值也为0{;}return str;//返回strDestination的起始位置
}strncpy函数
函数原型
char *strncpy( char *strDest, const char *strSource, size_t count );
//strDest目标字符串
//strSource 源字符串
//count要复制的字符数
//返回值传入的目标字符串起始的位置strncpy函数拷贝count个字符从源字符串到目标空间。如果源字符串的长度小于count则拷贝完源字符串之后在目标的后边追加0直到count个。 官方文档解释 注意目标重叠将产生未定义行为
strncpy函数学习使用
int main()
{char strDest1[] abcdefggggggg;char strDest2[] abcdefggggggg;char strDest3[] abcde;char strSource[] hijklm;printf(拷贝前strDestination1%s\n, strDest1);printf(拷贝后strDestination1%s\n, strncpy(strDest1, strSource, 4));printf(拷贝前strDestination2%s\n, strDest2);printf(拷贝后strDestination2%s\n, strncpy(strDest2, strSource, 8));printf(拷贝前strDestination3%s\n, strDest3);printf(拷贝后strDestination3%s\n, strncpy(strDest3, strSource, 8));return 0;
}我们发现strncpy函数也没有实现数组是否越界的检查这需要我们模拟实现时需要思考解决的。 让我们看内存情况了解一下strncpy函数的工作情况吧 当我们的源字符串大于我们要拷贝的数量时只是对目标字符串的替换并会加入‘\0’。 当我们的源字符串小于我们要拷贝的数量时会补‘\0’直到count个。 我们可以发现strncpy对超过限制的没有检查作用。
strncpy函数模拟实现
我们的测试用例用刚才的用例来测试我们自己的函数。 我们strncpy函数模拟实现要解决超过限制的没有检查的问题。
char* my_strncpy(char* strDest, const char* strSource, size_t count)
{assert(strDest strSource);//判断strDest和strSource地址是否合法char* str strDest;//用来记录strDest的起始位置方便对目标字符串的返回while (count--)//对传入字符个数进行循环{if (*strDest \0)//当目标字符串到达结尾时就返回我们记录的起始位置{return str;}else if (*strSource \0)//当我们源字符串到达结尾时{*strDest *strSource;//我们把目标字符替换为\0并且只对目标字符串进行移动strDest;}else//当我们都没有到达结尾时对两个字符串进行移动{*strDest *strSource;strDest;strSource;}}return str;
}注意我们模拟实现要考虑是否补’\0’和数组是否会产生越界的情况。当我们源字符串到达结尾时不可以在对我们的源字符串进行移动。
strcat函数
函数原型
char *strcat( char *strDestination, const char *strSource );
//strDestination目标字符串的位置
//strSource 源头字符串的位置
//返回值传入的目标字符串起始的位置strcat函数追加一个字符串。 官方文档解释 注意这里的未定义行为还是数组的越界访问。
strcat函数学习使用
int main()
{char strDestination[15] abcdefg;char strSource[] hijklm;printf(追加前strDestination%s\n, strDestination);printf(追加后strDestination%s\n, strcat(strDestination, strSource));return 0;
}我们需要把源字符串全部追加到目标字符串中。
strcat函数模拟实现
char* my_strcat(char* strDestination, const char* strSource)
{assert(strDestination strSource);//判断strDest和strSource地址是否合法char* str strDestination;//用来记录strDest的起始位置方便对目标字符串的返回while (*strDestination)//把目标字符串移植末尾结束字符位置{strDestination;}while (*strDestination *strSource)//进行字符串追加{;}return str;
}但我们实现的函数由一个致命的缺陷就是对自己的追加。问题的解决我们放在memmove函数中解决。
strncat函数
函数原型
char *strncat( char *strDest, const char *strSource, size_t count );
//strDest目标字符串的位置
//strSource 源头字符串的位置
//count要追加的字符数
//返回值传入的目标字符串起始的位置strncat函数将源字符串的前count个字符追加到目标再加上一个终止的null字符。如果源字符串的长度小于count则只复制终止null字符之前的内容。 官方文档解释 前面的返回值等我们已经很熟悉了这里我们只看注意事项。
strncat函数学习使用
int main()
{char strDestination1[15] abcdefg;char strDestination2[15] abcdefg;char strSource[] hijklm;printf(追加前strDestination1%s\n, strDestination1);printf(追加后strDestination1%s\n, strncat(strDestination1, strSource,4));printf(追加前strDestination2%s\n, strDestination2);printf(追加后strDestination2%s\n, strncat(strDestination2, strSource,8));return 0;
}在这里我们分别追加方式分别使用小于源字符串追加和大于源字符的追加。
strncat函数模拟实现
有了前面的基础我们来实现一下我们自己的函数吧。
char* my_strncat(char* strDest, const char* strSource, size_t count)
{assert(strDest strSource);//判断strDest和strSource地址是否合法char* str strDest;//用来记录strDest的起始位置方便对目标字符串的返回while (*strDest)//把目标字符串移植末尾结束字符位置{strDest;}while (count--)//对传入字符个数进行循环{if (*strSource \0)//当我们源字符串到达结尾时,{*strDest *strSource;//把结束字符追加后结束本次追加return str;}else//对两个字符串进行移动{*strDest *strSource;strDest;strSource;}}*strDest \0;//当循环结束时代表我们源字符串的不完全追加需要我们额外的补结束字符return str;
}我们自己的实现可以发现思路就是我们strncpy和我们strcat的结合。当让我们这个依然没有解决追加自己的问题。
strcmp函数
函数原型
int strcmp( const char *string1, const char *string2 );
//string1要比较的以Null结尾的字符串
//string2 要比较的以Null结尾的字符串
//返回值这些函数中的每个函数的返回值指示字符串1到字符串2的字典关系。strcpy函数用于两个字符串比较 官方文档解释
strcmp函数学习使用
int main()
{char string1[] abcde;char string2[] abcde;char string3[] abcd;char string4[] abcdf;int cmp1 strcmp(string1, string2);//相等情况int cmp2 strcmp(string1, string3);//string1string2int cmp3 strcmp(string1, string4);//string1string2printf(%d %d %d, cmp1, cmp2, cmp3);return 0;
}这里我们直接用返回值来判断了。 注意strcpy函数用于两个字符串比较是一个字符一个字符进行比较。
strcmp函数模拟实现
int my_strcmp(const char* string1, const char* string2)
{assert(string1 string2);//判断string1和string2地址是否合法while (*string1 ! \0 *string2 ! \0)//只有当两个都不为空字符是才进行比较{if (*string1 *string2){return 1;}else if (*string1 *string2){return -1;}else//当两个字符相等时比较下一个字符{string1;string2;}}//循环结束要判断是单个结束还是两个一起结束if (*string1 ! \0){return 1;}else if (*string2 ! \0){return -1;}else{return 0;}
}strncmp函数
函数原型
int strncmp( const char *string1, const char *string2, size_t count );
//string1要比较的以Null结尾的字符串
//string2 要比较的以Null结尾的字符串
//count要比较的字符数
//返回值这些函数中的每个函数的返回值指示字符串1到字符串2的字典关系。strncpy函数用于两个字符串前count个字符的比较。 官方文档解释
strncmp函数学习使用
int main()
{char string1[] abcde;char string2[] abcd;int cmp1 strncmp(string1, string2,4);//相等情况int cmp2 strncmp(string1, string2,5);//string1string2printf(%d %d, cmp1, cmp2);return 0;
}类比strcmp我们很容易理解我们的strncmp函数。
strncmp函数模拟实现
int my_strncmp(const char* string1, const char* string2, size_t count)
{assert(string1 string2);//判断string1和string2地址是否合法while (count--)//进行要比较的字符次数的循环{if (*string1 *string2){return 1;}else if (*string1 *string2){return -1;}else//当两个字符相等时比较下一个字符{string1;string2;}}//循环结束证明两个相等return 0;
}我们strncmp函数的思想就是循环count次当count的元素都相同时证明这两个字符串相等。
三、字符串的查找
strstr函数
函数原型
char *strstr( const char *string, const char *strCharSet );
//string要搜索的以Null结尾的字符串
//strCharSet 要搜索的以Null结尾的子字符串
//返回值返回一个指针指向字符串中第一个出现的strCharSet如果字符串中没有出现strCharSet则返回NULLstrstr函数在一个字符串中查找子串。 官方文档解释 这个函数我们需要注意返回值为NULL还是返回第一次出现的位置或者是被查找的字符串。
strstr函数学习使用
int main()
{char str[] abcccdefgh;char *str1 strstr(str, ccdef);char *str2 strstr(str, );char *str3 strstr(str, acbd);puts(str1);puts(str2);if (str3 NULL){printf(str3 is NULL\n);}return 0;
}我们分别查询在字符串中不在字符串中和空字符串的情况充分的了解了各种情况下的返回值。
strstr函数模拟实现
char* my_strstr(const char* string, const char* strCharSet)
{assert(string strCharSet);//判断string1和string2地址是否合法if (*strCharSet \0)//判断传入的要查找的子字符是否为空字符串。{return string;}char* retu string;//一个节点用来存储要返回的位置char* hand strCharSet;//用来记录子串的起始位置while (*retu)//当要返回的位置不为空时{strCharSet hand;//把字串的位置置为起始位置string retu;//把被查找的位置改为现在存储返回的位置while (*string *strCharSet)//如果两个字符相等则比较下一个字符{if (*strCharSet \0)//判断是否两个都到达结束位置{return retu;}string;strCharSet;}if (*strCharSet \0)//当strCharSet到达结束标志时证明改返回位置是查找的起始位置{return retu;}retu;}return NULL;
}在这里retu指针的意义是为了防止连续出现多的重复和子串一样元素但最后不同时便于找到开始查找的位置。hand指针的意义是每次对比结果不同时把字串的指针位置回到开始。
strtok函数
函数原型
char *strtok( char *strToken, const char *strDelimit );
//strToken包含令牌的字符串
//strDelimit字符串分隔符字符集strtok函数用于查找字符串中的下一个标记。 官方文档解释 注意strtok函数会改变被操作的字符串所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。strtok函数找到strToken中的下一个标记并将其用 ‘\0’ 结尾返回一个指向这个标记的指针。 strtok函数函数返回值讨论 1.strtok函数的第一个参数不为 NULL 函数将找到str中第一个标记strtok函数将保存它在字符串中的位置。 2.strtok函数的第一个参数为 NULL 函数将在同一个字符串中被保存的位置开始查找下一个标记。 3.如果字符串中不存在更多的标记则返回 NULL 指针。
strtok函数学习使用
看了上面的解释我们看的雨里雾里的。让我们通过实践来学习一下吧。
int main()
{char* strToken xiao/hei.123com.xiaohei;char* strDelimit /.;//strDelimit放置的分隔字符为/,.,char arr[30] { 0 };strcpy(arr, strToken);//将数据拷贝一份处理arr数组的内容char* str NULL;for (str strtok(arr, strDelimit); str ! NULL; str strtok(NULL, strDelimit))//函数第一次需要把要分隔的字符串传入//后续改函数会记住上次分隔符的位置{printf(%s\n, str);}return 0;
}这里注意对同一个字符串进行查找时第二次要传NULL因为该函数会记录上次查找的位置。
四、错误信息报告
strerror函数
函数原型
char *strerror( int errnum );
//errnum错误编号
//返回值指向错误消息字符串的指针strerror函数用于返回错误码所对应的错误信息。 官方文档解释
strerror函数学习使用
下面我们演示一下用法吧
#include errno.h//必须包含的头文件
int main()
{FILE* pf;pf fopen(test.txt, r);if (pf NULL){printf(Error: %s\n, strerror(errno));}int* p (int*)malloc(sizeof(int) * 0xFFFFFFFFF);if (p NULL){printf(Error: %s\n, strerror(errno));}return 0;
}注意返回的错误信息如果不即时打印就会被下一个错误信息覆盖。 和这个函数相似的还有一个perror函数。
int main()
{FILE* pf;pf fopen(test.txt, r);if (pf NULL){printf(Error: %s\n, strerror(errno));perror(Error);}int* p (int*)malloc(sizeof(int) * 0xFFFFFFFFF);if (p NULL){printf(Error: %s\n, strerror(errno));perror(Error);}return 0;
}五、内存操作的函数
我们对内存的操作都是针对所有类型的所以我们的形参都是用void指针接收返回值也是void类型的指针。
memcpy函数
函数原型
void *memcpy( void *dest, const void *src, size_t count );
//dest目标字符串的位置
//src 源头字符串的位置
//返回值传入的目标字符串起始的位置memcpy函数作用是从src的位置开始向后复制count个字节的数据到dest的内存位置。 官方文档解释 memcpy函数在遇到 ‘\0’ 的时候并不会停下来。如果src和dest有任何的重叠复制的结果都是未定义的。
memcpy函数学习使用
struct stu
{char name[10];int age;
};
int main()
{struct stu stu1 { {zhangsan},20 };struct stu stu2;memcpy(stu2, stu1, sizeof(struct stu));printf(%s %d, stu2.name, stu2.age);return 0;
}memcpy函数模拟实现
在实现前我们要先思考为什么库函数中用的void指针来接收的。好处是什么 从上面的例子可以看出我们这个实现的是可以拷贝所有的类型所以类型我们把它限定为void,因为void指针可以接收任何指针。
void* my_memcpy(void* dest, const void* src, size_t count)
{assert(dest src);void* begin dest;while (count--){*((char*)dest) *((char*)src);//把dest和src转化为字符指针对字符指针进行移动(char*)dest 1;(char*)src 1;}return dest;
}这里面我们用字符指针进行移动一个字符一个字符的进行拷贝这样可以做到拷贝任何类型的数据。
memmove函数
函数原型
void *memmove( void *dest, const void *src, size_t count );
//dest目标字符串的位置
//src 源头字符串的位置
//返回值传入的目标字符串起始的位置memmove函数作用是从src的位置开始向后复制count个字节的数据到dest的内存位置。 memmove函数和memcpy函数的区别 1.memmove函数处理的源内存块和目标内存块是可以重叠的有的编译器memmove函数和memcpy功能已经一模一样了。 2. 如果源空间和目标空间出现重叠最好使用memmove函数处理memmove函数就是为了拷贝相同区域而生的 官方文档解释
memmove函数学习使用
int main()
{char arr1[15] abcdefghigkl;char arr2[15] abcdefghigkl;char arr3[15] abcdefghigkl;memmove(arr1 5, arr1, 5);memmove(arr2 5, arr2 3, 5);memmove(arr3 5, arr3 7, 5);printf(%s\n, arr1);printf(%s\n, arr2);printf(%s\n, arr3);return 0;
}我们看到正对三种情况都可以正确的把字符串进行复制接下来我们在函数模拟实现中着重的讨论一下如何复制重叠的区域吧解决我们上面的strcpy等函数的缺陷吧。
memmove函数模拟实现
先让我们看看复制自己会有什么情况吧
void* my_memmove(void* dest, const void* src, size_t count)
{assert(dest src);void* begin dest;if (*((char*)dest) - *((char*)src) 0){while (count--){*((char*)dest count) *((char*)src count);//把dest和src转化为字符指针对字符指针进行移动}}else{while (count--){*((char*)dest) *((char*)src);//把dest和src转化为字符指针对字符指针进行移动(char*)dest 1;(char*)src 1;}}return begin;
}思路 当源头右边重叠时我们从源头的左边进行拷贝时会把我们的值进行覆盖所以我们要从源头右边进行拷贝 情况如下图所示 当源头左边重叠时我们从源头的右边进行拷贝时会把我们的值进行覆盖所以我们要从源头左边进行拷贝 情况如下图所示 我们把两个元素转化为字符指针通过指针相减可以得到两个指针的前后关系。然后选择合适的循环来进行拷贝。当两个不交叉重叠时我们使用哪种方法都相同。
六、比较实用的字符操作函数
下面是一些实用的字符操作函数
函数如果参数符合下列条件就返回真iscntr任何控制字符isspace空白字符isdigit十进制数字isxdigit十六进制数字islower小写字母isupper大写字母isalpha字母isalnum字母或者数字ispunct标点符号isgraph任何图形字符isprint任何可打印字符
函数字符转换tolower大写字母转换为小写字母toupper小写字母转换为大写字母
下面演示几个吧
int main()
{char arr[] aB1cD0eFg2HiGk;int sz strlen(arr);int i 0;for (i 0; i sz; i){if (isupper(arr[i]))//如果是大写就进行字符转化{arr[i] tolower(arr[i]);}else if (islower(arr[i]))//如果是小写就进行字符转化{arr[i] toupper(arr[i]);}else if (isdigit(arr[i]))//如果是十进制数字就打印{printf(%c , arr[i]);//我们存的数字字符如果用%d进行打印需要让该元素减去一个字符0}}printf(\n%s\n, arr);return 0;
}总结
相信你对字符串相关函数已经有了深刻的了解在实战中多多使用吧。欢迎点赞留言呀。