C语言中字符字符串以及内存操作函数

1字符及其操作函数

1.1字符

  字符类型char是C语言中极为重要的一种类型,相比整型,浮点型其操作也有略微不同,今天就来介绍C语言中关于字符的那些事。
  我们这里谈到的字符均指的是美国信息交换标准代码(American Standard Code for Information Interchange,下文简称ASCII码)表中的字符,根据该表可知,每一个字符都对应一个编号,例如字符’a’的ASCII码编号为97,字符’A’的ASCII码编号为65,字符’1’的ASCII码编号为49等等。由于计算机只能存储二进制码,所以字符在内存中实际存储的是该字符对应ASCII码的二进制码,因此我们也可以这样认为:char相当于是1个字节的无符号整型。

在这里插入图片描述

图1.1 ASCII码表

  由于有些字符或命令不能直接被表示出来(例如回车符,再如C语言中定义一个字符需要用单引号把字符引起来,而单引号自己本身也是字符),此时我们需要使用转义字符来表示,其书写格式为“反斜杠后边跟指定字符”,而此时转义字符中的反斜杠后边那个字符将不再表示其原来的含义。例如’n’的本意就表示英文字符’n’,如果加上反斜杠:’\n’,此时编译器会把反斜杠和n放在一起编译,其对应的含义就为换行。
  常见的转义字符和所对应的含义(来源百度百科<转义字符>):

表1.1常见转义字符
转义字符含义ASCII码值
\a响铃(BEL)007
\b退格(BS),将当前位置移到前一列008
\f换页(FF),将当前位置移到下页开头012
\n换行(LF),将当前位置移到下一行开头010
\r回车(CR),将当前位置移到本行开头013
\t水平制表(HT) (跳到下一个TAB位置)009
\v垂直制表(VT)011
\代表一个反斜线字符’’’092
\’代表一个单引号(撇号)字符039
\”代表一个双引号字符034
?代表一个问号063
\0空字符(NULL)000
\ddd1到3位八进制数所代表的任意字符三位八进制
\xhh十六进制所代表的任意字符十六进制

  对于汉字,不同的编码对应的汉字所占的字节数不同,因此本文不做讨论。

1.2字符操作函数

  C语言中关于字符函数主要有两大类,一类是字符分类函数,常用于判断用户的输入是否合法,另一类是字符转换函数,用于将英文字母的字符转换为大写或者小写。

1.2.1字符分类函数

  常见的字符分类函数见下表:

表1.2字符分类函数
函数如果该函数的参数符合下述条件就返回真,否则返回假
iscntrl任何控制字符
isspace空白字符:空格’ ’,换页’\f,换行’\n’,回车’\r’,制表符’\t’,垂直制表符’\v’。
isdigit十进制数字0~9
isxdigit十六进制数字,包括所有的十进制数字,小写字母 a ∼ f a\sim f af,大写字母 A ∼ F A\sim F AF
islower小写字母a~z
isupper大写字母A~Z
isalpha字母 a ∼ z a\sim z az A ∼ Z A\sim Z AZ
isalnum字母或数字, 0 ∼ 9 0\sim9 09 , a ∼ z a \sim z az, A ∼ Z A\sim Z AZ
ispunct标点符号,任何不属于数字或字母的字符(可打印)
isgraph任何图形字符
isprint任何可打印字符,包括图形字符和空白字符

  注:在ASCII码中,第0~31号及第127号(共33个)是控制字符或通讯专用字符,如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(振铃)等;通讯专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等。

1.2.2字符转换函数

  • 转小写:
int tolower(int c);
  • 转大写:
int toupper(int c);

  例如:

char s = 'a';
printf("%c\n",toupper(s));
printf("%c\n",s);

在这里插入图片描述

图1.2

  从程序运行结果可以看出来,在转换字符时,字符本身没有发生变化,只是字符转换函数将其对应的大写(或小写)对应的ASCII码值返回。如果想改变某个字符串的大写或小写,只需遍历该字符串,使用字符转换函数即可。

2字符串及其操作函数

2.1字符串

  严格来讲,C语言中并没有字符串类型,因此我们使用字符数组来模拟字符串,或者直接使用常量字符串。既然是以字符数组来模拟字符串,而字符又以ASCII码存放在内存中,何时停止?即编译器如何知道到哪里是某段字符串的结束。C语言标准有如下规定:以’\0’作为字符串的结束标志。
  字符串有两种定义方式:

  方式1:

 char str1[] = "Hello";

  虽然Hello有5个字符,但实际上系统会自动在字符’o’后边加上字符’\0’.如图2.1所示。
在这里插入图片描述

图2.1

  方式2:

char str2[6] = {'H','e','l','l','o','\0'};

  对于这种定义方式,必须要在最后手动加上’\0’,否则我们定义的就是字符数组而不是字符串。

2.2字符串函数

  C语言中,跟字符串相关的函数主要有以下几个:

表2.1字符串函数
函数名含义
strlen求取字符串长度
strcpy字符串拷贝
strcat字符串拼接函数
strcmp字符串比较函数
strncpy字符串指定字符数拷贝
strncat字符串指定字符数拼接
strcnmp字符串指定字符比较函数
strstr判断一个字符串是否为另一个字符串的片段
strtok按指定分隔符分割字符串
strerror错误信息报告函数

  下面将逐一介绍并模拟实现其中的部分函数。

2.2.1strlen

  strlen:求字符串长度函数.

size_t strlen( const char *string );

  可以看出,该函数的返回值为无符号整型,所以实际应用中我们不能直接对两个strlen进行减法,否则会出错。
  strlen的功能是求取一个字符串的长度,在上文中我们已经提到过,字符串的末尾以’\0’作为结束标志,所以我们只要从字符串的开始进行遍历,当到’\0’时自动停止,然后返回’\0’前的字符数。
  所以可以写出如下代码:

size_t my_strlen1( const char *str )
{
    assert(str);
    int count = 0;
    while (*str++ && ++count);
    return count;
}

或者

size_t my_strlen2( const char *str )
{
    assert(str);
    const char *start = str;
    while (*str++);
    return (str - 1 - start);//减1是因为上一步中指针指向'\0'后,虽然条件不满足退出了循环,但str还要接着进行一步加加操作,所以减掉1.
}

或者不使用临时变量:

size_t my_strlen3( const char *str )
{
    assert(str);
    if (*str == '\0')
    {
        return 0;
    }
    return my_strlen3(str+1)+1;
}

2.2.2strcpy

  strcpy:字符串拷贝函数:

char *strcpy( char *strDestination, const char *strSource );

  其含义是把源头字符串strSource拷贝到目的地字符串strDestination中去。
  该函数使用有如下注意事项:

  • 首先要保证目的地字符串的空间足够大,能够放下源头字符串,目的地字符串空间至少要和源头空间一样大,此外,目标空间还应当可修改(不被const修饰).
  • 源字符串必须要以’\0’结束,否则会出错。
  • 源字符串的’\0’会拷贝到目标空间中,作为字符串的结束标志。
  • 返回 char*是为了实现函数的链式访问。

  模拟实现:

char* my_strcpy( char *dest, const char *src )
{
    assert(dest);
    assert(src);
    char* ret = dest;
    while(*dest++ = *src++);
    return ret;
}

2.2.3strcat

  strcat:字符串拼接函数

char *strcat( char *strDestination, const char *strSource );

  其含义是把源字符串strSource拼接到目标字符串strDestination之后。v
  该函数使用有如下注意事项:

  • 目标字符串剩余空间足够大可以容纳下源字符串。
  • 源字符串必须要以’\0’结尾。
  • 追加时是从目标字符串的’\0’位置处开始的,即会把’\0’覆盖掉,因此字符串不能给自己追加自己本身。

模拟实现:

char* my_strcat( char *dest, const char *src )
{
    assert(dest);
    assert(src);
    char* ret = dest;
    while (*dest++);
    dest--;
    while (*dest++ = *src++);
    return ret;
}

2.2.4strcmp

  strcmp:字符串比较函数

int strcmp( const char *string1, const char *string2 );

  字符串本身没有大小,此处比较的是两个字符串对应字符的ASCII码值的大小,即如果string1的第一个字符ASCII码值大于string2的第一个字符ASCII码值,就返回一个大于0的数,如果string1的第一个字符ASCII码值小于string2的第一个字符ASCII码值,就返回一个小于0的数,如果string1的第一个字符ASCII码值等于string2的第一个字符ASCII码值,就接着比较它们的第二个字符,以此类推。
模拟实现:

int my_strcmp( const char *str1, const char *str2 )
{
    assert(str1);
    assert(str1);
    while (*str1 == *str2)
    {
        if (*str1 == '\0')
        {
            return 0;
        }
        str1++;
        str2++;
    }
    return *str1 - *str2;
}

2.2.5strcpy

  strncpy:字符串指定字符数拷贝

char *strncpy( char *strDest, const char *strSource, size_t count );

  其含义是把源字符串的count个字符拷贝到目标字符串空间。
  该函数使用时有如下注意事项:

  • 如果源字符串长度小于count,则在拷贝完源字符串后,在目标后边追加’\0’,直到追加count个。
  • count应当不超过目标字符串空间(因为字符串最后还有’\0’,所以count应当小于目标字符串的空间)。

模拟实现:

char* my_strncpy( char *dest, const char *src, size_t n)
{
    assert(dest);
    assert(src);
    char* ret = dest;
    while(n && (*dest++ = *src++))
    {
        n--;
    }
    if(n)
    {
        while (--n)
        {
            *dest++ ='\0';
        }
    }
    return ret;
}

2.2.6strncat

  strncat:字符串指定字符数拼接

char *strncat( char *strDest, const char *strSource, size_t count );

  其含义是把源字符串的count个字符追加到目标字符串后边。
  该函数使用时有如下注意事项:

  • 目标字符串必须以’\0’结尾。
  • 追加时从目标字符串的’\0’开始追加,count个字符串追加结束后,在后边补’\0’。
  • count不应当超过目标字符串剩余的空间。
  • 如果源字符串长度不够count个,则在后边追加’\0’,直到补满count个为止。

模拟实现:

char *my_strncat( char *dest, const char *src, size_t n )
{
    assert(dest);
    assert(src);
    int i;
    char* ret = dest;
    while(*dest)
    {
        dest++;
    }
    for(i=0;src[i] && i<n;i++)
    {
        dest[i] = src[i];
    }
    while(i <= n)
    {
        dest[i] = '\0';
        i++;
    }
    return ret;
}

2.2.7strncmp

  strncmp:比较两个字符串的前n个字符

int strncmp( const char *string1, const char *string2, size_t count );

  比较两个字符串的前count字符,原理同strcmp。

2.2.8strstr

  strstr:判断一个字符串是否为另一个字符串的子字符串。

char *strstr( const char *string, const char *strCharSet );

  其含义为判断strCharSet是否为string的子字符串。返回值为一个指针,如果不是子字符串,则返回一个NULL指针,如果是,则返回strCharSet在string中第一次出现的位置。
  实现原理,从string的第一个字符与strCharSet的第一个字符进行比较,如果不相等,就比较string的第二个字符和strCharSet的第一个字符,如果相等,比较string的第三个字符和strCharSet的第二个字符,如果不相等,则从string的第三个字符开始再与strCharSet的第一个字符比较,以此类推。
模拟实现:

char *my_strstr( const char *str1, const char *str2)
{
    const char* s1 = str1;
    const char* s2 = str2;
    const char* cp = str1;
    if(*str2 == '\0')
    {
        return str1;
    }
    while(cp)
    {
        s1 = cp;
        s2 = str2;
        while(s1 && s2 && *s1 = *s2)
        {
            s1++;
            s2++;
        }
        if(*s2 = '\0')
        {
            return (char*)cp;
        }
        cp++;
    }
    return NULL;
}

2.2.9strtok

  strtok:按指定分隔符分割字符串

char *strtok( char *strToken, const char *strDelimit );

  其含义是按照strDelimit中的字符来分割字符串strToken。
  该函数使用时应注意:

  • strToken包含了0个或多个由strDelimit字符串中一个或多个分隔符分割的标记。
  • strtok函数找到strToken中的下一个标记,将该标记改为’\0’,并返回一个指向该子串的指针。
  • 如果strtok函数的第一个参数不为NULL,函数将找到str中的第一个标记,strtok函数将保存它在字符串中的位置。并返回被已经分隔好的字符串的起始地址。
  • 如果strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
  • 如果字符串中没有更多的标记(即所有的标记已经查找完),则返回NULL指针。

使用案例:

char str1[] = "123.456.55.88";
char str2[] = ".";
char* p = NULL;
char str3[50];
strcpy(str3,str1);
for (p = strtok(str3, str2); p != NULL; p = strtok(NULL, str2))
{
    printf("%s\n",p);
}

2.2.10strerror

  strerror:返回错误码所对应的错误信息

char* strerror(int errnum)

  在编写程序时,总有某些情况我们考虑不周全,在程序的某些可能出错地方,我们可以预先设置一些错误提示,这样在程序运行时,可以帮助我们迅速定位到出错的位置,使程序更加易于调试。在系统中提前定义好了一些错误和错误码,这些错误码放在全局变量errno(需要引用头文件errno.h)中。我们在使用时只需要调用上述函数,如果发生错误,将会为我们返回错误码及其对应的信息,没有错误时,errno默认的值为0.
  例如:打开一个文件前,我们需要判断文件是否存在,如果不存在就不能打开,此时就可以调用该函数。

FILE* pFile;
pFile = fopen("1.txt","r");
if (pFile == NULL)
{
    printf("Error opening file 1.txt:%s\n", strerror(errno));
}

  由于实际中,并没有该文件,所以程序输出该文件不存在,如图2.2所示。

在这里插入图片描述

图2.2

  还有另外一个函数perror,整合了打印和strerror的功能,所以上述代码行中printf对应的那行可以改写为:

perror("Error opening file 1.txt")

两者输出结果一样。

3内存(memory)操作函数

  在字符串操作中,有字符串的比较,拷贝,拼接等等,但其只能实现字符串的操作,往往还受其结束符’\0’的限制,当我们想拷贝比较或者其他类型时,这些函数则失去了作用,所以在此处引入内存操作函数,其与字符串操作函数类似,但又不尽相同。

3.1memcpy

  memcpy:字符串拷贝函数

void *memcpy( void *dest, const void *src, size_t count );

  其含义为该函数会从src对应的起始地址开始向后拷贝count个字节的数据到dest指向的地址中。拷贝结束,返回dest的起始地址。
  由于是对内存直接进行拷贝,所以其可以拷贝任何类型的数据,当然也不受’\0’的限制,即遇到该字符时并不会停下来,而是接着拷贝,直到拷贝满count个字节为止。
  当dest在src+count的范围内时,则复制结果不一定正确(对于不同的平台该函数实现的方式不太一样,如果拷贝和粘贴同时进行,则重叠区域会被新数据覆盖掉,拷贝结果可能就不是我们所期望的,而如果是先拷贝完后再粘贴,才可能是我们所期望的)。
模拟实现:

void *my_memcpy1( void *dest, const void *src, size_t num )
{
    assert(dest);
    assert(src);
    void* ret = dest;
    int i;
    for (i = 0; i < num; i++)
    {
        *((char*)dest+i) = *((char*)src+i);
    }
    return ret;
}

或者

void *my_memcpy2( void *dest, const void *src, size_t num )
{
    assert(dest);
    assert(src);
    void* ret = dest;
    while(num--)
    {
        *((char*)dest) = *((char*)src);
        dest = (char*)dest+1;
        src = (char*)src+1;
    }
    return ret;
}

3.2memmove

  memmove:内存移动

void *memmove( void *dest, const void *src, size_t count );

  顾名思义,内存移动,就是把src开始向后的count个字节内存拷贝移动到dest对应的位置上。和memcpy函数一样,都是把src开始向后的count个字节内存拷贝移动到dest对应的位置上,但是上边也谈到,当destsrc+count范围(或者src在dest+count的范围内)内,即源空间和目标空间有重叠时,memcpy就无法保证拷贝结果的正确性,memmove函数就是为了解决此问题。
  分析,当dest在src+count范围内时(也就是dest在src的右边),即如图3.1所示:
在这里插入图片描述

图3.1

  D为重叠区域,如果从前向后拷贝,即先把A拷贝到D处,则会把原来D位置的数据覆盖掉,那么再把D处的数据拷向G时,实际上拷贝的是A的数据。如果从后向前拷贝,即先把D处的数据拷贝到G,再把C处的数据拷贝到F,依次类推,此时可以达到我们想要的结果。
  而当src在dest+count的范围内(即dest在src的左边),如图3.2所示,按照上述分析此时应当从前向后拷贝,即先把D处得到数据拷贝到A中,再把E处的数据拷贝到B中,依次类推。
在这里插入图片描述

图3.2

  有了上述分析,进行模拟实现(实际上主要是判断dest是在src的左边还是右边):

void *my_memmove( void *dest, const void *src, size_t num )
{
    assert(dest);
    assert(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+count) = *((char*)src+count);
        }
    }
    return ret;
}

3.3memcmp

  memcmp:比较函数

int memcmp( const void *buf1, const void *buf2, size_t count );

  其含义为比较内存区域buf1与buf2的count个字节的大小。

  • 如果buf1>buf2,则返回正数;
  • 如果buf1=buf2,返回0;
  • 如果buf1<buf2,返回负数;

模拟实现:

int my_memcmp( const void *buf1, const void *buf2, size_t num )
{
    assert(buf1);
    assert(buf2);
    while(*((*char)buf1) == *((*char)buf2)&& count--)
    {
        buf1 = (*char)buf1+1;
        buf2 = (*char)buf2+1;
    }
    if (count == 0)
    {
        return 0;
    }
    return *((char*)buf1) - *((char*)buf2)
}

3.4memset

  memset:初始化

void *memset( void *dest, int c, size_t count );

  其含义为从dest位置开始,将接下来的count个字节置为整数c,并最终返回dest的地址。
  因为是按字节赋值,所以无论c多大,系统都只能取c的后八位二进制码对其进行赋值,也正是因为如此,一般用0(二进制码全0)或-1(二进制码全1)进行初始化,否则容易出错。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值