赞
踩
- 写在最前面
在工作中,字符串处理操作还是比较多的,但是,有些函数是不安全的,用不好,就会出现段错误(指针)。其实,只要清晰知道这些字符串处理函数的特性,在使用时可以避免掉。
目录
char* strcpy(char* dest, const char* src);
void *memcpy( void *dest, const void *src, size_t count );
char*strncpy(char*dest,char*src,size_tn);
5.字符串格式化输出sprintf 、fprintf、snprintf、_snprintf、asprintf
应用举例sprintf、snprintf 、_snprintf
- 第一组函数的名字以str开头,它主要处理以'\0'结尾的字符串,所以字符串内部不能包含任何'\0'字符。
- 第二组函数的名字以mem开头,主要考虑非字符串内部含有零值的情形,它能够处理任意的字节序列,操作与字符串函数类似
- 除了memmove函数外,其他函数都没定义重叠对象间的行为
char *
, cs和ct的类型是const char *
;n的类型为size_t
,c的类型为int
void *
, cs,ct的类型是const void *
; n类型为size_t
,c类型为int
字符串的结尾是默认有一个\0作为结束符的。字符数组你不给他手动赋值,他是没有的。如果用strcpy这个拷贝函数,就要小心自己的输入参数是不是一个字符串,如果是字符数组,一定不要忘记结尾处赋值\0,不然的话就会溢出了,可以手动试试。这是这个函数的缺陷
- char * strcpy(char * dest, const char * src) // 实现src到dest的复制
- {
- if ((src == NULL) || (dest == NULL)) //判断参数src和dest的有效性
- {
-
- return NULL;
- }
- char *strdest = dest; //保存目标字符串的首地址
- while ((*strDest++ = *strSrc++)!='\0'); //把src字符串的内容复制到dest下
- return strdest;
- }
转:
1.为什么函数列表的第二个形式参数要用const修饰?
答:用const修饰就可以阻止src指向的区域被有意无意修改,const修饰*src,即修饰src指向的区域,所以对该区域进行写保护。
2.为什么要进行参数合法性检查?为什么不写成if((!dst)||(!src))?
答:①如果src的值为NULL,那么就会把一个非法区域的数据拷贝到dst指向的缓冲区中,而这很容易造成内存越界(因为很有可能很晚才遇到"/0"标记符或者无法遇到"/0"标记符);如果dst的值为NULL,那么就相当于把一串数据拷贝到一些非法区域,从而造成系统崩溃。②因为dst和src类型为char*,并不是BOOL类型,虽然if((!dst)||(!src))可以进行从char*到BOOL的隐形类型转换,但是这相当于把可靠性交给了第三方去维护,显然不是一个合格程序员的素养。
3.为什么while循环里面要判断!="\0"?为什么while循环不写成while (*strSrc!='\0') *dst++=*src++;?
答:①字符串都是以"\0"作为结束标志的,即便你写一个字符串"shenzhen",系统也会自动在结尾增加"\0",只是你看不到而已。②这样写就会导致字符串的"\0"无法拷贝到目的区域,这是不行的。
- void *memcpy(void *memTo, const void *memFrom, size_t size)
- {
- if((memTo == NULL) || (memFrom == NULL)) //memTo和memFrom必须有效
- return NULL;
- char *tempFrom = (char *)memFrom; //保存memFrom首地址
- char *tempTo = (char *)memTo; //保存memTo首地址
- while(size -- > 0) //循环size次,复制memFrom的值到memTo中
- *tempTo++ = *tempFrom++ ;
- return memTo;
- }
-
- ①函数返回值为什么是void *?
-
- 答:由于memcpy拷贝的数据并不限于字符串,理论上可以是指向任何类型的数据指针,因此这里设计为void *类型。
-
- ②函数形式参数为什么是void *?
-
- 答:memcpy拷贝的数据可以是任何类型的数据,所以这里采用void *类型。
-
- char* strncpy(char* dest, const char* source, size_t count)
- {
- char* start=dest;
- while (count && (*dest++=*source++))
- count--;
- if(count)
- while (--count)
- *dest++='\0';
- return(start);
- }
1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy
为什么是上图那样的结果呢?
这里有几个知识点:
1. C语言中的局部变量存储在栈里。所以p1~p6存放情况如下。
2. 栈大家都知道是从栈底往上存东西的,栈的生长方向向上的,这是形象的说法;若从地址大小上来说,这里栈是从到 高地址乡低地址的地方生长(可以自己写程序判断栈的生长方向)
3,但对于变量个体来说存放顺序是向下的,低地址到高地址(这里又涉及到大小端模式,很明显这里是大端模式)
简单解释一下:
大端:低地址存储高位字节,高地址存放低位字节
(符合人平时的书写顺序:比如写78,地址可以理解为顺序,先写7再写8,由小序号到大序号,但小序号写 的是7,7是十位,是高位,也就是高字节。。。)
小端: 反之
4. %s打印默认是遇到\0结束
所以现在我们就可以理解p3为什么是那样的打印,因为p3没有结束符,他会顺着地址一直打印,知道p2的结束符
p3前面的乱码是因为,我们赋值的是数字1,2,3,相当于是askll码了,对应的字符就是那个正方形方框,至于后面的烫烫打印是因为p2的大小15字节,而赋值的字符串长度是11,其余的是随机值,编译器在运行时对这种默认赋值oxcc,对应的字符就是烫
以上可以知道的是memcpy,看似好像挺安全的,有长度限制他不会溢出;但是也得会用他,参数长度一定要输入对了,你看最后p6最后两种输入法,最终输出结果不同,是因为strlen()求字符串的长度不会算\0.所以打印结果不一样了。
这里用%s打印,他是遇到\0才会结束,可以看到用result的时候也要注意长度。
无论是哪种拷贝函数,都要注意这个这个\0的存在,
只不过,memcpy限制了长度,只要输入的没错,不至于导致程序崩溃,但是strcpy就要很注意子符串有无\0,否则很容易越界访问引起程序崩溃。
- 解决:
- 只要记住以下两点,就不会出错
1.对于目的数组,拷贝之前可以都初始化为0,这就相当于给他加了\0
2.还有一个大前提,目的数组的长度最好大于等于源数组长度。
满足以上两个条件,就不会越界啦!!!
void *memset(void *s, int c, size_t n);
用途:1.清空数组 memset(a, 0, sizeof(a));
也可以全部设置为其他值memset(a, 888, sizeof(a));
strlen是遇到\0结束的,所以下面的result2输出结果很能说明问题。只能根据\0来判断是否结尾?不然怎么判断?
https://blog.csdn.net/chenxun_2010/article/details/21721419
sizeof事实上不是一个函数,他的功能是求一个变量实际占的空间,你申请的多大,他求出来就是多大。
见另一篇文章详细用法:sizeof他是一个函数吗?
char *strcat(char *dest, const char *src)
其实,拷贝函数也可以实现拼接的功能,只要给出起始地址即可:区别如下:
strcat(dst, src)把src所指字符串添加到dest结尾处(覆盖dest结尾处的“\0")并添加'\0'。
strcpy(dest, src)把从src地址开始且含有null结束符的字符串复制到以dest开始的地址空间。
int sprintf(char *str, const char &format, ...);
sprintf是字符串格式化命令,主要功能是把格式化的数据写入字符串str中,返回值为写入str的字节数,结束字符‘\0’不计入内。其中, str是指要写入的缓冲区,format控制要写入str中数据的格式,例如%s、%d、%x等。
int snprintf(char *str, size_t size, const char *format, ...);
snprintf是字符串格式化命令,主要功能是把格式化的数据写入字符串str中,最多写size个字节,包括自动添加在字符串末尾处的结束字符‘\0’;返回值为写入str的字节数,包括结束字符‘\0’
snprintf是C语言提供的字符串格式化函数,int snprintf ( char * restrict dest , size_t n , const char * restrict format , ... ); _snprintf是vc提供的字符串格式化函数,int _snprintf( char *buffer, size_t count, const char *format , ...);
区别:
VC中的_snprintf的count参数表示,会向buff中写入count个字符,不包括'\0'字符,并且不会在字符串末尾添加'\0'符,并且,如果字符串超过count,函数返回-1以标志可能导致的错误;
gcc中的snprintf函数的count参数表示,向buff中写入count个字符,包括'\0'字符,并且,返回实际的字符串长度.
int asprintf (char **ptr, const char *template, ...)
本函数跟sprintf()函数很类似,只是它将字符串的分配改成动态分配的形式,参数ptr是指一个char *对象的地址函数返回指向一个新建的指针。如下例子:
/* Construct a message describing the value of a variable whose name is name and whose value is value. */
- char *make_message (char *name, char *value)
- {
- char *result;
- asprintf (&result, "value of %s is %s", name, value);
- return result;
- }
fprintf()函数
fprintf( ) 函数中格式化的规定与printf( ) 函数相同, 所不同的只是fprintf()函数是向文件中写入。而printf()是向屏幕输出。
下面介绍一个例子, 运行后产后一个test.dat的文件
- #include <stdio.h>
- #include <stdlib.h>
-
- void main()
- {
- char *s="That's good news"}; /*定义字符串指针并初始化*/
- int i=617; /*定义整型变量并初始化*/
- FILE *fp; /*定义文件指针*/
- fp=fopne("test.dat", "w"); /*建立一个文字文件只写*/
- fputs("Your score of TOEFLis", fp); /*向所建文件写入一串字符*/
- fputc(':', fp); /*向所建文件写冒号:*/
- fprintf(fp, "%d", i); /*向所建文件写一整型数*/
- fprintf(fp, "%s", s); /*向所建文件写一字符串*/
- fclose(fp); /*关闭文件*/
- }
转:
4.6 Vprintf()函数
int vprintf (const char *template, va_list ap)
本函数跟printf函数很类似,只是将参数的数目可变的,变成了一个指针的列表。
4.7 Vfprintf()函数
int vfprintf (FILE *stream, const char *template, va_list ap)
本函数跟fprintf函数很类似,只是将参数的数目可变的,变成了一个指针的列表。
4.8 vfprintf()函数
int vsprintf (char *s, const char *template, va_list ap)
本函数跟sprintf函数很类似,只是将参数的数目可变的,变成了一个指针的列表。
4.9 vsnprintf()函数
int vsnprintf (char *s, size_t size, const char *template, va_list ap)
本函数跟snprintf函数很类似,只是将参数的数目可变的,变成了一个指针的列表。
4.10 vasprintf()函数
int vasprintf (char **ptr, const char *template, va_list ap)
本函数跟asprintf函数很类似,只是将参数的数目可变的,变成了一个指针的列表
- #include<stdio.h>
- #include<string.h>
- #include <stdlib.h>//system头文件
- int main()
- {
- //sprintf 最常见的应用之一莫过于把整数打印到字符串中,所以,spritnf 在大多数场合可以替代itoa。如:
- //把整数123 打印成一个字符串保存在s 中。
- char s[10] = {0};
- sprintf(s, "%d", 123); //产生"123"可以指定宽度,不足的左边补空格:
- printf("%s\n",s);
- sprintf(s, "%8d%8d", 123, 4567); //产生:" 123 4567"当然也可以左对齐:
- printf("%s\n",s);
- sprintf(s, "%-8d%8d", 123, 4567); //产生:"123 4567"
- printf("%s\n",s);
-
- //也可以按照16 进制打印:
- sprintf(s, "%8x", 4567); //小写16 进制,宽度占8 个位置,右对齐
- sprintf(s, "%-8X", 4568); //大写16 进制,宽度占8 个位置,左对齐
-
- //这样,一个整数的16 进制字符串就很容易得到,但我们在打印16 进制内容时,通常想要一种左边补0 的等宽格式,
- //那该怎么做呢?很简单,在表示宽度的数字前面加个0 就可以了。
- sprintf(s, "%08X", 4567); //产生:"000011D7"
- //上面以"%d"进行的10 进制打印同样也可以使用这种左边补0 的方式
-
- //这里要注意一个符号扩展的问题:比如,假如我们想打印短整数(short)-1 的内存16 进制表示形式,在Win32 平台
- //上,一个short 型占2 个字节,所以我们自然希望用4 个16 进制数字来打印它:
- short si = -1;
- sprintf(s, "%04X", si);
- //产 生"FFFFFFFF",怎么回事?因为spritnf 是个变参函数,除了前面两个参数之外,后面的参数都不是类型安全的,
- //函数更没有办法仅仅通过一个"%X"就能得知当初函数调用前参数压栈时被压进来的到底 是个4 字节的整数还是个2
- //字节的短整数,所以采取了统一4 字节的处理方式,导致参数压栈时做了符号扩展,扩展成了32 位的整数-1,打印时
- //4 个位置不够了,就把32 位整数-1 的8 位16 进制都打印出来了。
-
- //如果你想看si 的本来面目,那么就应该让编译器做0 扩展而不是符号扩展(扩展时二进制左边补0 而不是补符号位):
- sprintf(s, "%04X", (unsigned short)si);
- //就可以了。或者:
- unsigned short si = -1;
- sprintf(s, "%04X", si);
-
- system("pause");
- return 0;
- }
- //浮点数的打印和格式控制是sprintf 的又一大常用功能,浮点数使用格式符"%f"控制,默认保留小数点后
- //6 位数字,
- //比如:
- sprintf(s, "%f", 3.1415926); //产生"3.141593"
- //但有时希望自己控制打印的宽度和小数位数,这时就的使用:"%m.nf"格式,其中m 表示打印的宽度,n 表示
- //小数点后的位数。
- //比如:
- sprintf(s, "%10.3f", 3.1415626); //产生:" 3.142"
- sprintf(s, "%-10.3f", 3.1415626); //产生:"3.142 "
- sprintf(s, "%.3f", 3.1415626); //不指定总宽度,产生:"3.142"
-
- //注意一个问题,你猜
- int i = 100;
- sprintf(s, "%.2f", i);
- //会打出什么东东来?"100.00"?对吗?自己试试就知道了,同时也试试下面这个:
- sprintf(s, "%.2f", (double)i);
/*第 一个打出来的肯定不是正确结果,原因跟前面提到的一样,参数压栈时调用者并不知道跟i相对应的格式控制符是个"%f"。而函数执行时函数本身则并不知道当 年被压入栈里的是个整数,于是可怜的保存整数i 的那4 个字节就被不由分说地强行作为浮点数格式来解释了,整个乱套了。不过,如果有人有兴趣使用手工编码一个浮点数,那么倒可以使用这种方法来检验一下你手工编 排的结果是否正确*/
sprintf 的格式控制串中既然可以插入各种东西,并最终把它们"连成一串",自然也就能够连接字符串,从而在许多场合可以替代strcat,sprintf 能够一次连接多个字符串(自然也可以同时在它们中间插入别的内容,总之非常灵活)
比如:
注意:
- #include<stdio.h>
- #include<string.h>
- #include <stdlib.h>//system头文件
- int main()
- {
- char a1[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
- char a2[] = {'H', 'I', 'J', 'K', 'L', 'M', 'N'};
- char s[20];
- //如果:
- sprintf(s, "%s%s", a1, a2); //Don't do that!
- printf("%s\n",s);
- //十有八九要出问题了。是否可以改成:没有"\0"作为结束符,在使用他时很容易出问题
- sprintf(s, "%7s%7s", a1, a2);
- printf("%s\n",s);
- //也没好到哪儿去,正确的应该是:
- sprintf(s, "%.7s%.7s", a1, a2);//产生:"ABCDEFGHIJKLMN"
- //这 可以类比打印浮点数的"%m.nf",在"%m.ns"中,m 表示占用宽度(字符串长度不足时补空格,超出了则按照实际宽度打印),
- //n 才表示从相应的字符串中最多取用的字符数。通常在打印字符串时m 没什么大用,还是点号后面的n 用的多。自然,也可以前后都只取部分字符:
- sprintf(s, "%.6s%.5s", a1, a2);//产生:"ABCDEFHIJKL"
- //在许多时候,我们或许还希望这些格式控制符中用以指定长度信息的数字是动态的,而不是静态指定的
- //因为许多时候,程序要到运行时才会清楚到底需要取字符数 组 中的几个字符,
- //这种动态的宽度/精度设置功能在sprintf 的实现中也被考虑到了,sprintf 采用"*"来占用一个本来需要一个指定宽度或精度的常数数字的位置,\
- //同样,而实际的宽度或精度就可以和其它被打印的变量一样被提供出来,于是,上面的例子 可以变成:
- //sprintf(s, "%.*s%.*s", 7, a1, 7, a2);
- //或者:
- sprintf(s, "%.*s%.*s", sizeof(a1), a1, sizeof(a2), a2);
- //实际上,前面介绍的打印字符、整数、浮点数等都可以动态指定那些常量值,比如:
- sprintf(s, "%-*d", 4, 'A'); //产生"65 "
- sprintf(s, "%#0*X", 8, 128); //产生"0X000080","#"产生0X
- sprintf(s, "%*.*f", 10, 2, 3.1415926); //产生" 3.14"
- system("pause");
- return 0;
- }
较少有人注意printf/sprintf 函数的返回值,但有时它却是有用的,spritnf 返回了本次函数调用最终打印到字符缓冲区中的字符数目。也就是说每当一次sprinf 调用结束以后,你无须再调用一次strlen 便已经知道了结果字符串的长度
将其转为为字符串类型,其实可用sprinf替代
未完待续。。。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。