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放在一起编译,其对应的寄义就为换行。

    常见的转义字符和所对应的寄义(泉源百度百科<转义字符>):

转义字符

寄义

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

\ddd

13位八进制数所代表的随便字符

三位八进制

\xhh

十六进制所代表的随便字符

十六进制


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

1.2字符操作函数

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

1.2.1字符分类函数

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

函数

若是该函数的参数相符下述条件就返回真,否则返回假

iscntrl

任何控制字符

isspace

空缺字符:空格’ ’,换页’\f,换行’\n’,回车’\r’,制表符’\t’,垂直制表符’\v’。

isdigit

十进制数字0~9

isxdigit

十六进制数字,包罗所有的十进制数字,小写字母a~f,大写字母A~F

islower

小写字母a~z

isupper

大写字母A~Z

isalpha

字母a~z或A~Z

isalnum

字母或数字,0~9,a~z,A~Z

ispunct

标点符号,任何不属于数字或字母的字符(可打印)

isgraph

任何图形字符

isprint

任何可打印字符,包罗图形字符和空缺字符

    注:ASCII中,第031号及第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:

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

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

2.2字符串函数

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

函数名

寄义

strlen

求取字符串长度

strcpy

字符串拷贝

strcat

字符串拼接函数

strcmp

字符串对照函数

strncpy

字符串指定字符数拷贝

strncat

字符串指定字符数拼接

strcnmp

字符串指定字符对照函数

strstr

判断一个字符串是否为另一个字符串的片断

,

以太坊数据网

www.326681.com采用以太坊区块链高度哈希值作为统计数据,联博以太坊统计数据开源、公平、无任何作弊可能性。联博统计免费提供API接口,支持多语言接入。

,

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之后。

    该函数使用有如下注重事项:

  • 目的字符串剩余空间足够大可以容纳下源字符串。

  • 源字符串必须要以'\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

    另有另外一个函数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对应的位置上,然则上边也谈到,当dest在src+count局限(或者src在dest+count的局限内)内,即源空间和目的空间有重叠时,memcpy就无法保证拷贝效果的准确性,memmove函数就是为了解决此问题。

    剖析,当dest在src+count局限内时(也就是dest在src的右边),即如图所示:

图3.1

D为重叠区域,若是早年向后拷贝,即先把A拷贝到D处,则会把原来D位置的数据覆盖掉,那么再把D处的数据拷向G时,现实上拷贝的是A的数据。若是从后向前拷贝,即先把D处的数据拷贝到G,再把C处的数据拷贝到F,依次类推,此时可以到达我们想要的效果。

而当src在dest+count的局限内(即dest在src的左边),如图所示,根据上述剖析此时应当早年向后拷贝,即先把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)举行初始化,否则容易失足。