字符函数和字符串函数
一、字符串函数
注:
- 调用下面介绍对字符串操作的函数要包含头文件
<string.h>
- siez_t即无符号整型,但是不代表使用该类型做返回值类型就更适合,返回类型还是需要根据实际情况而定。
eg:strlen函数中有些情况就不能使用,该类型来返回
1. str开头的字符串函数
①strlen
函数声明:
size_t strlen (const char* str);
作用:计算字符串长度不包括’\0’
函数介绍:
- 字符串以’\0’为结束标志,返回值是字符串中’\0’前面出现的字符个数(不包括’\0’)。
- 参数 str指向的字符串必须以’\0’结束。
使用:
eg:
#include <stdio.h>
#include <string.h>
int main()
{
const char* str1 = "abcdef";
const char* str2 = "bbb";
if (strlen(str2) - strlen(str1) > 0)
{
printf("str2 > str1\n");
}
else
{
printf("str1 > str2\n");
}
return 0;
}
//输出:str2 > str1
解释输出结果:输出结果并不符合预期
原因:两个无符号数相减的结果还是无符号数,它会把最高位符号位,当作无符号数进行计算。
模拟实现:
// 1. 正常模拟实现
size_t my_strlen(const char *str)
{
// 使用assert函数检测传进来的指针
// assert包含在头文件<assert.h>中
assert(str);
size_t length = 0;
while (*str != '\0')
{
length++;
str++;
}
return length;
}
// 2. 递归的方式模拟实现
size_t my_strlen(const char* str)
{
assert(str);
if (*str != '\0')
{
return 1 + my_strlen(str + 1);
}
else
return 0;
}
②strcpy
函数声明:
char* strcpy(char* destination, const char* source);
作用:字符串拷贝
函数介绍:
- 源字符串必须以 ‘\0’ 结束
- 会将源字符串的’\0’拷贝到目标空间。
- 保证目标空间足够大,以确保可以存放源字符串。
注:长度不受限制的字符串操作函数,到’\0’才停止,不安全
模拟实现:
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest; // 保存dest的原始值,以便返回
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
*dest = '\0';
return ret;
}
③strcat
函数声明:
char* strcat(char* destination, const char* source);
作用:追加字符串
函数介绍:
- 源字符串和目标空间都要有’\0’。(原因:要先寻找到目标空间的’\0’才能进行追加。)
- 目标空间要足够大
- 不能自己给自己追加字符串。(原因:会导致死循环,在追加第一个字符的同时,源字符串的’\0’就被改变了。
注:长度不受限制的字符串操作函数,到’\0’才停止,不安全
模拟实现:
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
while (*dest)
{
dest++;
}
while (*src)
{
*dest = *src;
dest++;
src++;
}
*dest = '\0';
return ret;
}
④strcmp
函数声明:
int strcmp (const char* str1, const char* str2)
作用:字符串比较
规定:
- str1 > str2 返回值是一个大于0的数字。
- str1 < str2 返回值是一个小于0的数字。
- str1 = str2 返回值是0。
注:长度不受限制的字符串操作函数,到’\0’才停止,不安全
模拟实现:
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 && (*str1 == *str2))
{
str1++;
str2++;
}
return *(unsigned char*)str1 - *(unsigned char*)str2;
}
⑤strncpy
函数声明:
char* strncpy(char* destination, const char* source, size_t num);
作用:拷贝num个字符从源字符串到目标空间。
函数介绍:
- 如果源字符串的长度小于num,则拷贝完字符串之后,在目标的后面追加0,直到num个
⑥strncat
函数声明:
char* strncat( char* destination, const char* source, size_t num)
作用:追加字符串
⑦strncmp
函数声明:
int strncmp (const char* str1, const char* str2, size_t num)
规定:比较到出现另个字符不一样或者一个字符串结束或者num个字符比较完。
注意:
- string1 substring less than string2 substring 返回值小于0.
- string1 substring identical to string2 substring 返回值为0.
- string1 substring greater than string2 substring 返回值大于0.
⑧strstr
函数声明:
char* strstr (const char* str1, const char* str2);
作用:查找一个字符串是否在另一个字符串之中
函数介绍:
- 如果str2在str1中,返回当前位置的指针,否则返回NULL;
模拟实现:
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
char* cp = (char*)str1;
char* s1, * s2;
// 如果 str2 是一个空字符串,则返回 str1 的指针
if (!*str2)
return((char*)str1);
while (*cp)
{
s1 = cp;
s2 = (char*)str2;
// 比较从 cp 开始的 str1 的子字符串和 str2。
// 如果两个字符串匹配,s2 会到达其结尾的 \0
while (*s1 && *s2 && !(*s1 - *s2))
s1++, s2++;
// s2到达了其结尾的 \0,cp的位置就是字串str2在str1的位置
if (!*s2)
return cp;
cp++;
}
return NULL;
}
⑨strtok
函数声明:
char* strtok (char* str, const char* sep);
作用:切割字符串
函数声明:
- str:被切割字符串地址,sep:分隔符集合的字符串地址(里面存放的都是分隔符)。
- strtok函数找到str中的下一个标记,并且用’\0’结尾,返回一个指向这个标记的指针。(注意:在操作切割字符串的同时最好拷贝到一个新的空间,再进行切割)
- strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数为NULL,函数将在上一个被保存的位置,查找下一个标记。
- 如果字符串到结尾,则返回NULL;
使用:
eg:
#include <stdio.h>
int main()
{
const char* p = "zhangpengwei@bitedu.tech";
const char* sep = ".@"; // '\0'不能当分隔符,这是字符串结束标志
char arr[30];
char* str = NULL;
strcpy(arr, p);
// str = strtok(NULL, sep) 这里strtok会记住上一个标志,所以使用NULL即可
for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
{
printf("%s\n", str);
}
return 0;
}
//输出
//zhangpengwei
//bitedu
//tech
⑩strerror
char* strerror (int errnum)
返回错误码所对应的信息。
函数介绍:
- strerror函数要和errno函数连用
- perror函数,这个函数相当于strerror函数和errno和printf函数联合使用。如果不需要打印错误信息,将strerror和errno联合使用就行
使用:
eg:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
FILE* pFile = fopen("test.txt", "r");
//这里涉及文件操作的知识
if (NULL == pFile)
{
printf("Error opening file test.txt:%s\n", strerror(errno));
//使用erron需要包含头文件<errno.h>
}
fclose(pFile);
pFile = NULL;
return 0;
}
2. mem开头的内存操作函数
- mem开头的内存操作函数也是进行字符串处理的函数
- 当然也不仅仅知识处理字符串,可以作用在所有类型上的内存操作函数
①memcpy
函数声明:
void* memcpy (void* destination, const void* source, size_t num);
函数介绍:
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
- 如果source和destination有任何重叠,复制的结果都是未定义的。
注:
- 这里我们可以注意它的返回类型和第一个、第二个参数类型,都是void*。因为函数不知道我们复制什么类型的,所以留给程序员操作。
- 该函数遇到’\0’的时候是不会停下来的。
使用:
eg:
#include <stdio.h>
#include <string.h>
struct
{
char name[40];
int age;
}person,person_copy;
int main()
{
char myname[] = "Pierre de Fermat";
//使用memcpy
memcpy(person.name, myname, strlen(myname) + 1);
person.age = 40;
//使用memcpy复制结构体
memcpy(&person_copy, &person, sizeof(person));
printf("person_copy:%s,%d\n", person.name, person.age);
return 0;
}
//输出: person_copy:Pierre de Fermat,40
模拟实现:
void* my_memcpy(void* dst, const void* src, size_t count)
{
assert(dst && src);
void* ret = dst; // 保存dst的原始值,以便返回
while (count--)
{
*(char*)dst = *(char*)src;
dst = (char*)dst + 1;
src = (char*)src + 1;
}
return ret;
}
②memmove
函数声明:
void* memmove (void* destination, const void* source, size_t num);
作用:memmove和memcpy作用一致,唯一区别就是memcpy函数源内存块和目标内存块是不可以重叠的,memmove函数可以。
使用:
eg:
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "memmove can be very useful......";
memmove(str + 20, str + 15, 11);
puts(str);
return 0;
}
//output: memmove can be very very useful.
模拟实现:
void* my_memmove(void* dst, const void* src, size_t count)
{
assert(dst && src);
void* ret = dst; // 保存dst的原始值,以便返回
char* dst_char = (char*)dst;
char* src_char = (char*)src;
if ((dst < src && dst_char + count > src) || (src < dst && src_char + count > dst)) {
// 需要反向复制以避免覆盖
dst = dst_char + count - 1;
src = src_char + count - 1;
while (count--)
{
*dst_char = *src_char;
dst = dst_char - 1;
src = src_char - 1;
}
}
else {
// 非重叠或dst在src之前,可以正向复制
while (count--) {
*dst_char = *src_char;
dst = dst_char + 1;
src = src_char + 1;
}
}
return ret;
}
③memcmp
函数声明:
int memcmp( const void *buf1, const void *buf2, size_t count );
作用:比较buf1和buf2指针开始的count个字节
规定:
- buf1 < buf2 返回值小于0
- buf1 = buf2 返回值0
- buf1 > buf2 返回值大于0
使用:
eg:
#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;
}
④memset
函数声明:
void *memset( void *destination, int c, size_t count );
作用:用c初始化内存区域
函数介绍:
- destination:指向要设置的内存区域的指针。
- c:要设置的值。这个值被转换为 unsigned char,然后复制到内存区域。
- count:要设置的字节数。
memset 函数将 destination 指向的内存区域的前 count 个字节设置为值 c。返回值是一个指向 destination 的指针。
使用:
eg:
#include <string.h>
#include <stdio.h>
int main()
{
char buffer[] = "This is a test of the memset function";
printf("Before: %s\n", buffer);
memset(buffer, '*', 4);
printf("After: %s\n", buffer);
}
//输出:
//Before: This is a test of the memset function
//After : **** is a test of the memset function
模拟实现:
eg:
#include <stddef.h> // 包含 size_t 类型定义
void *my_memset(void *s, int c, size_t n)
{
// 将 void* 转换为 unsigned char* 以进行字节操作
unsigned char *p = (unsigned char *)s;
// 确保 c 是 unsigned char 类型,以避免符号扩展
unsigned char value = (unsigned char)c;
size_t i;
for (i = 0; i < n; i++) {
p[i] = value; // 设置每个字节的值为 value
}
return s; // 返回原始指针
}
二、字符函数
注:
- 调用下面介绍对字符操作的函数要包含头文件
<ctype.h>
- 0x : 十六进制前缀,本文ASCII码表范围都是用十六进制表示
- 下列的所有字符操作函数满足为真的范围,文字描述其作用范围,再描述其在ASCII码表中的范围
如果对字符函数会使用,指向看满足条件的范围,可以看第3点小结部分
1. 字符分类函数
①iscntrl
函数声明:
int iscntrl( int c );
函数介绍:
作用范围:任何控制字符
ASCII码表:(0x00 ~ 0x1F or 0x7F)。
如果参数c属于该范围,则符合条件返回真(非0)。
例子:
#include <stdio.h>
#include <ctype.h>
int main ()
{
int i=0;
char str[]="first line \n second line \n";
//遇到任何控制字符直接跳出循环
while (!iscntrl(str[i]))
{
putchar (str[i]);
i++;
}
return 0;
}
//输出:first line
②isspace
函数声明:
int isspace( int c );
函数介绍:
作用范围:空白字符
ASCII码表:(0x09 ~ 0x0D or 0x20)。也就是 空格’ ‘,换页’\f’,换行’\n’,回车’\r’,制表符’\t’,垂直制表符’\v’。
如果参数c属于该范围,则符合条件返回真(非0)。
例子:
#include <stdio.h>
#include <ctype.h>
int main()
{
int i = 0;
char str[] = "first line \n second line \n";
//遇到任何形式空白字符直接跳出循环
while (!isspace(str[i]))
{
putchar(str[i]);
i++;
}
return 0;
}
//output:first
③isdigit
函数声明:
int isdigit( int c );
函数介绍:
作用范围:十进制数字0~9
ASCII码表:(0x30 ~ 0x39)
如果参数c属于该范围,则符合条件返回真(非0)。
例子:
#include <stdio.h>
#include <ctype.h>
int main()
{
char str[] = "1776ad";
int year;
if (isdigit(str[0]))//1 满足条件 可以尝试下标4,5是不满足条件的,会执行else语句
{
printf("str[0]是数字:%d\n", (str[0]-'0'));
}
else
{
printf("str[0]不是数字\n");
}
return 0;
}
//str[0]是数字:1
④isxdigit
函数声明:
int isxdigit ( int c );
函数介绍:
作用范围:十六进制数字。包括所有十进制数字和字母a~f(字母大小写都行)。
ASCII码表:(0x30 ~ 0x39)or(0x41 ~ 0x46)or(0x61 ~ 0x66)
如果参数c属于该范围,则符合条件返回真(非0)。
例子:
#include <stdio.h>
#include <ctype.h>
int main()
{
char str[] = "1776ad";
int year;
if (isxdigit(str[4])) //a满足条件
{
printf("str[4]是十六进制数字:%c\n", str[4]);
}
else
{
printf("str[4]不是十六进制数字\n");
}
return 0;
}
//str[4]是十六进制数字:a
⑤islower
函数声明:
int islower ( int c );
函数介绍:
作用范围:小写字母a ~ z
ASCII码表:(0x61 ~ 0x7A)
如果参数c属于该范围,则符合条件返回真(非0)。
例子:
//把小写字母转换成大写字母
#include <stdio.h>
#include <ctype.h>
int main()
{
int i = 0;
char str[] = "Test String.";
char c;
while (str[i])
{
c = str[i];
if (islower(c))
{
//字符转换函数,后文有介绍
c = toupper(c);
}
putchar(c);
i++;
}
return 0;
}
//output:TEST STRING.
⑥isupper
函数声明:
int isupper ( int c );
函数介绍:
作用范围:大写字母A ~ Z
ASCII码表:(0x41 ~ 0x4A)
如果参数c属于该范围,则符合条件返回真(非0)。
例子:
//大写字母转换成小写字母
#include <stdio.h>
#include <ctype.h>
int main()
{
int i = 0;
char str[] = "Test String.\n";
char c;
while (str[i])
{
c = str[i];
if (isupper(c))
{
//字符转换函数,后文有介绍
c = tolower(c);
}
putchar(c);
i++;
}
return 0;
}
//output:test string.
⑦isalpha
函数声明:
int isalpha ( int c );
函数介绍:
作用范围:字母a ~ z或A ~ Z
ASCII码表(0x41 ~ 0x4A)or(0x61 ~ 0x7A)
如果参数c属于该范围,则符合条件返回真(非0)。
例子:
#include <stdio.h>
#include <ctype.h>
int main()
{
int i = 0;
char str[] = "C++";
while (str[i])
{
if (isalpha(str[i]))
printf("%c is alphabetic\n", str[i]);
else
printf("%c is not alphabetic\n", str[i]);
i++;
}
return 0;
}
//output:
//C is alphabetic
//+ is not alphabetic
//+ is not alphabetic
⑧isalnum
函数声明:
int isalnum ( int c );
函数介绍:
作用范围:字母或者数字,a ~ z, A ~ Z, 0 ~ 9
ASCII码表:(0x41 ~ 0x4A)or(0x61 ~ 0x7A)or(0x30 ~ 0x39)
如果参数c属于该范围,则符合条件返回真(非0)。
例子:
#include <stdio.h>
#include <ctype.h>
int main()
{
char str[] = "...c3po";
//判断数组第一个字符是不是字母或者数字
if (isalnum(str[0]))
{
printf("Yes\n");
}
else
{
printf("No\n");
}
return 0;
}
//output:No
⑨ispunct
函数声明:
int ispunct ( int c );
函数介绍:
作用范围:标点符号,任何不属于数字或者字母的图形字符(可打印)
如果参数c属于该范围,则符合条件返回真(非0)。
例子:
#include <stdio.h>
#include <ctype.h>
int main()
{
int i = 0;
int cx = 0;
char str[] = "Hello, welcome!";
while (str[i])
{
if (ispunct(str[i]))
{
//用cx统计该数组的标点元素个数
cx++;
}
i++;
}
printf("Sentence contains %d punctuation characters.\n", cx);
return 0;
}
//output:Sentence contains 2 punctuation characters.
//数组有两个标点符号字符(,!)
⑩isgraph
函数声明:
int isgraph ( int c );
函数介绍:
作用范围:任何图形字符
c语言全部图形字符(这里用ASCII表示范围不太好,所以直接,把所有图形字符放到下面)! " # $ % & ’ ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ 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 [ \ ] ^ _ ` 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 { | } ~
如果参数c属于该范围,则符合条件返回真(非0)。
例子:
#include <stdio.h>
#include <ctype.h>
int main()
{
char str[] = "3m ";
int i = 0;
for (i = 0; i < 3; i++)
{
if (isgraph(str[i]))
{
printf("YES:%c\n", str[i]);
}
else
{
break;
}
}
printf("NO:%c\n", str[i]);
return 0;
}
//output:
//YES:3
//YES:m
//NO :
isprint
函数声明:
int isprint ( int c );
函数介绍:
作用范围:任何可打印字符,包括图形字符和空白字符
水平制表符不可以
如果参数c属于该范围,则符合条件返回真(非0)。
例子:
#include <stdio.h>
#include <ctype.h>
int main()
{
int i = 0;
char str[] = "first line \t second line \t";
//遇到不能打印的字符例如'\t',会直接跳过循环,因为循环条件为假
while (isprint(str[i]))
{
putchar(str[i]);
i++;
}
return 0;
}
//output:first line
2. 字符转换函数
①tolower
函数声明:
int tolower ( int c );
函数介绍:
大写转换为小写的字母
如果 c 有相对应的小写字母,则该函数返回 c 的小写字母,否则 c 保持不变。
例子:上文介绍isupper也说过这个例子
//转换小写字母
#include <stdio.h>
#include <ctype.h>
int main()
{
int i = 0;
char str[] = "Test String.\n";
char c;
while (str[i])
{
c = str[i];
if (isupper(c))
c = tolower(c);
putchar(c);
i++;
}
return 0;
}
//output:test string.
②toupper
函数声明:
int toupper ( int c );
函数介绍:
把小写字母转换成大写字母
如果 c 有相对应的大写字母,则该函数返回 c 的大写字母,否则 c 保持不变
例子:例子:上文介绍islower也说过这个例子
//把小写字母转换成大写字母
#include <stdio.h>
#include <ctype.h>
int main()
{
int i = 0;
char str[] = "Test String.";
char c;
while (str[i])
{
c = str[i];
if (islower(c))
{
//字符转换函数,后文有介绍
c = toupper(c);
}
putchar(c);
i++;
}
return 0;
}
//output:TEST STRING.
3. 小结
字符分类函数:
三、其它相关函数atoi模拟实现
函数声明:
int atoi (const char * str);
作用:将字符串转换为int类型
atoi库函数定义在头文件<stdlib.h>
模拟实现:
#include <ctype.h>
#include <limits.h>
int my_atoi(const char* str) {
int sign = 1, base = 0;
// 跳过空白字符
while (isspace(*str))
{
str++;
}
// 检查转换后的数字是不是负数,看第一个不为空的字符为不为‘-’
if (*str == '-')
{
sign = -1;
str++;
}
else if (*str == '+')
{
// 这里也要执行以下,如果第一个字符为‘+’需要跳过
str++;
}
// 转换操作
while (isdigit(*str))
{
if (base > INT_MAX / 10 || (base == INT_MAX / 10 && *str - '0' > 7))
{
// 处理溢出情况,当结果可能大于INT_MAX时返回INT_MAX或INT_MIN
return (sign == 1) ? INT_MAX : INT_MIN;
}
base = base * 10 + (*str - '0');
str++;
}
return sign * base;
}