联博接口(326681.com)_C语言中字符字符串以及内存操作函数
发表时间:2021-12-22 浏览量:70
C语言中字符字符串以及内存操作函数
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 |
1到3位八进制数所代表的随便字符 |
三位八进制 |
\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码中,第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:
char str2[6] = {'H','e','l','l','o','\0'};
对于这种界说方式,必须要在最后手动加上'\0',否则我们界说的就是字符数组而不是字符串。
2.2字符串函数
C语言中,跟字符串相关的函数主要有以下几个:
函数名 |
寄义 |
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之后。
该函数使用有如下注重事项:
目的字符串剩余空间足够大可以容纳下源字符串。
源字符串必须要以'\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)举行初始化,否则容易失足。
0
珍藏