C语言的本质(22)——C标准库之字符串操作
编译器、浏览器、Office套件等程序的主要功能都是符号处理,符号处理功能在程序中占相当大的比例,无论多复杂的符号处理都是由各种基本的字符串操作组成的,下面介绍如何用C语言的库函数做字符串初始化、取长度、拷贝、连接、比较、搜索等基本操作。
1. 初始化字符串
头文件:string.h
函数原型:
- void *memset(void *s, int c, size_t n);
memset函数将s所指向的某一块内存中的前n个 字节的内容全部设置为ch指定的ASCII值, 块的大小由第三个参数指定,这个函数通常为新申请的内存做初始化工作, 其返回值为指向s的指针。
例如定义char buf[10];,如果它是全局变量或静态变量,则自动初始化为0,如果它是函数的局部变量,则初值不确定,可以用memset(buf, 0, 10)清零,由malloc分配的内存初值也是不确定的,也可以用memset清零。
- #include<string.h>
- #include<stdio.h>
- int main(void)
- {
- buf[]="Hello world!\n";
- printf("Buffer before memset:%s\n",buf);
- memset(buffer,'*',strlen(buf));
- printf("Buffer after memset:%s\n",buf);
- return0;
- }
2. 取字符串的长度
头文件:string.h
函数原型:
- size_t strlen(const char *s);
strlen函数返回s所指的字符串的长度。该函数从s所指的第一个字符开始找'\0'字符,一旦找到就返回,返回的长度不包括'\0'字符在内。例如定义char buf[] = "hello";,则strlen(buf)的值是5,但要注意,如果定义charbuf[5] = "hello";,则调用strlen(buf)是危险的,会造成数组访问越界。
- #include<string.h>
- #include<stdio.h>
- int main(void)
- {
- char *s="Hello world!\n";
- printf("%s has %d chars",s,strlen(s));
- return0;
- }
strlen与sizeof的区别
strlen(char*)函数求的是字符串的实际长度,它求得方法是从开始到遇到第一个'\0',如果你只定义没有给它赋初值,这个结果是不定的,它会从aa首地址一直找下去,直到遇到'\0'停止。
若char aa[10]; 则strlen(aa)结果是不定的
若char aa[10]={'\0'}; 则strlen(aa)则结果为0
若char aa[10]="jun"; 则strlen(aa)则结果为3
而sizeof()返回的是变量声明后所占的内存数,不是实际长度,此外sizeof不是函数,仅仅是一个操作符,strlen是函数。
sizeof(aa) 返回10
int a[10]; sizeof(a) 返回40
1、sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。
该类型保证能容纳实现所建立的最大对象的字节大小。
2、sizeof是操作符(关键字),strlen是函数。
3、sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以''\0''结尾的。
sizeof还可以用函数做参数,比如:
- short f();
- printf("%d\n",sizeof(f()));
输出的结果是sizeof(short),即2。
4、数组做sizeof的参数不退化,传递给strlen就退化为指针了。
5、大部分编译程序 在编译的时候就把sizeof计算过了是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因
char str[20]="0123456789";
int a=strlen(str); //a=10;
int b=sizeof(str); //而b=20;
6、strlen的结果要在运行的时候才能计算出来,是用来计算字符串的长度,不是类型占内存的大小。
7、sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof是个操作符不是个函数。
8、当适用了于一个结构类型时或变量,sizeof 返回实际的大小,
当适用一静态地空间数组, sizeof 归还全部数组的尺寸。
sizeof 操作符不能返回动态地被分派了的数组或外部的数组的尺寸
9、数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址,如:
- fun(char [8])
- fun(char [])
都等价于 fun(char *)
3. 拷贝字符串
前面的博文介绍了strcpy和strncpy函数,拷贝以'\0'结尾的字符串,strncpy还带一个参数指定最多拷贝多少个字节,此外,strncpy并不保证缓冲区以'\0'结尾。现在介绍memcpy和memmove函数。
头文件:string.h
函数原型:
- void *memcpy(void *dest, const void *src, size_t n);
- void *memmove(void *dest, const void *src,size_t n);
返回值:dest指向哪,返回的指针就指向哪
memcpy函数从src所指的内存地址拷贝n个字节到dest所指的内存地址,和strncpy不同,memcpy并不是遇到'\0'就结束,而是一定会拷贝完n个字节。这里的命名规律是,以str开头的函数处理以'\0'结尾的字符串,而以mem开头的函数则不关心'\0'字符,或者说这些函数并不把参数当字符串看待,因此参数的指针类型是void *而非char *。
memmove也是从src所指的内存地址拷贝n个字节到dest所指的内存地址,虽然叫move但其实也是拷贝而非移动。但是和memcpy有一点不同,memcpy的两个参数src和dest所指的内存区间如果重叠则无法保证正确拷贝,而memmove却可以正确拷贝。假设定义了一个数组char buf[20] = "hello world\n";,如果想把其中的字符串往后移动一个字节(变成"hhello world\n"),调用memcpy(buf + 1,buf, 13)是无法保证正确拷贝的:
错误的memcpy调用
- #include <stdio.h>
- #include <string.h>
- int main(void)
- {
- charbuf[20] = "hello world\n";
- memcpy(buf+ 1, buf, 13);
- printf("%s",buf);
- return0;
- }
运行结果:
hhelloo wold
4. 连接字符串
头文件:#include <string.h>
函数原型:
- char *strcat(char *dest, const char *src);
- char *strncat(char *dest, const char *src,size_t n);
返回值:dest指向哪,返回的指针就指向哪strcat把src所指的字符串连接到dest所指的字符串后面,例如:
- char d[10] = "foo";
- char s[10] = "bar";
- strcat(d, s);
- printf("%s %s\n", d, s);
调用strcat函数后,缓冲区s的内容没变,缓冲区d中保存着字符串"foobar",注意原来"foo"后面的'\0'被连接上来的字符串"bar"覆盖掉了,"bar"后面的'\0'仍保留。
strcat和strcpy有同样的问题,调用者必须确保dest缓冲区足够大,否则会导致缓冲区溢出错误。strncat函数通过参数n指定一个长度,就可以避免缓冲区溢出错误。注意这个参数n的含义和strncpy的参数n不同,它并不是缓冲区dest的长度,而是表示最多从src缓冲区中取n个字符(不包括结尾的'\0')连接到dest后面。如果src中前n个字符没有出现'\0',则取前n个字符再加一个'\0'连接到dest后面,所以strncat总是保证dest缓冲区以'\0'结尾,这一点又和strncpy不同,strncpy并不保证dest缓冲区以'\0'结尾。所以,提供给strncat函数的dest缓冲区的大小至少应该是strlen(dest)+n+1个字节,才能保证不溢出。
5. 比较字符串
头文件:#include <string.h>
函数原型:
- int memcmp(const void *s1, const void *s2,size_t n);
- int strcmp(const char *s1, const char *s2);
- int strncmp(const char *s1, const char *s2,size_t n);
返回值:负值表示s1小于s2,0表示s1等于s2,正值表示s1大于s2memcmp从前到后逐个比较缓冲区s1和s2的前n个字节(不管里面有没有'\0'),如果s1和s2的前n个字节全都一样就返回0,如果遇到不一样的字节,s1的字节比s2小就返回负值,s1的字节比s2大就返回正值。
strcmp把s1和s2当字符串比较,在其中一个字符串中遇到'\0'时结束,按照上面的比较准则,"ABC"比"abc"小,"ABCD"比"ABC"大,"123A9"比"123B2"小。
strncmp的比较结束条件是:要么在其中一个字符串中遇到'\0'结束(类似于strcmp),要么比较完n个字符结束(类似于memcmp)。例如,strncmp("ABCD", "ABC", 3)的返回值是0,strncmp("ABCD","ABC", 4)的返回值是正值。
头文件:#include <strings.h>
函数原型:
- int strcasecmp(const char *s1, const char*s2);
- int strncasecmp(const char *s1, const char*s2, size_t n);
返回值:负值表示s1小于s2,0表示s1等于s2,正值表示s1大于s2这两个函数和strcmp/strncmp类似,但在比较过程中忽略大小写,大写字母A和小写字母a认为是相等的。这两个函数不属于C标准库,是POSIX标准中定义的。
6. 搜索字符串
头文件:#include <string.h>
函数原型:
- char *strchr(const char *s, int c);
- char *strrchr(const char *s, int c);
返回值:如果找到字符c,返回字符串s中指向字符c的指针,如果找不到就返回NULLstrchr在字符串s中从前到后查找字符c,找到字符c第一次出现的位置时就返回,返回值指向这个位置,如果找不到字符c就返回NULL。strrchr和strchr类似,但是从右向左找字符c,找到字符c第一次出现的位置就返回,函数名中间多了一个字母r可以理解为Right-to-left。
头文件:#include <string.h>
函数原型:
- char *strstr(const char *haystack, const char*needle);
返回值:如果找到子串,返回值指向子串的开头,如果找不到就返回NULLstrstr在一个长字符串中从前到后找一个子串(Substring),找到子串第一次出现的位置就返回,返回值指向子串的开头,如果找不到就返回NULL。这两个参数名很形象,在干草堆haystack中找一根针needle,按中文的说法叫大海捞针,显然haystack是长字符串,needle是要找的子串。
7. 分割字符串
头文件:#include <string.h>
函数原型:
- char *strtok(char *str, const char *delim);
- char *strtok_r(char *str, const char*delim, char **saveptr);
返回值:返回指向下一个Token的指针,如果没有下一个Token了就返回NULL参数str是待分割的字符串,delim是分隔符,可以指定一个或多个分隔符,strtok遇到其中任何一个分隔符就会分割字符串。看下面的例子。
很多文件格式或协议格式中会规定一些分隔符或者叫界定符(Delimiter),例如/etc/passwd文件中保存着系统的帐号信息:
- $ cat /etc/passwd
- root:x:0:0:root:/root:/bin/bash
- daemon:x:1:1:daemon:/usr/sbin:/bin/sh
- bin:x:2:2:bin:/bin:/bin/sh
- ......
每条记录占一行,也就是说记录之间的分隔符是换行符,每条记录又由若干个字段组成,这些字段包括用户名、密码、用户id、组id、个人信息、主目录、登录Shell,字段之间的分隔符是:号。解析这样的字符串需要根据分隔符把字符串分割成几段,C标准库提供的strtok函数可以很方便地完成分割字符串的操作。tok是Token的缩写,分割出来的每一段字符串称为一个Token。
- #include <stdio.h>
- #include <string.h>
- int main(void)
- {
- charstr[] = "root:x::0:root:/root:/bin/bash:";
- char*token;
- token= strtok(str, ":");
- printf("%s\n",token);
- while( (token = strtok(NULL, ":")) != NULL)
- printf("%s\n",token);
- return0;
- }
- $ ./a.out
- root
- x
- 0
- root
- /root
- /bin/bash
从"root:x::0:root:/root:/bin/bash:"这个例子可以看出,如果在字符串开头或结尾出现分隔符会被忽略,如果字符串中连续出现两个分隔符就认为是一个分隔符,而不会认为两个分隔符中间有一个空字符串的Token。第一次调用时把字符串传给strtok,以后每次调用时第一个参数只要传NULL就可以了,strtok函数自己会记住上次处理到字符串的什么位置(显然这是通过strtok函数中的一个静态指针变量记住的)。
C语言的本质(22)——C标准库之字符串操作的更多相关文章
- C++解析(18):C++标准库与字符串类
0.目录 1.C++标准库 2.字符串类 3.数组操作符的重载 4.小结 1.C++标准库 有趣的重载--操作符 << 的原生意义是按位左移,例:1 << 2;,其意义是将整数 ...
- 吴裕雄--天生自然C++语言学习笔记:C++ 标准库
C++ 标准库可以分为两部分: 标准函数库: 这个库是由通用的.独立的.不属于任何类的函数组成的.函数库继承自 C 语言. 面向对象类库: 这个库是类及其相关函数的集合. C++ 标准库包含了所有的 ...
- C 标准库基础 IO 操作总结
其实输入与输出对于不管什么系统的设计都是异常重要的,比如设计 C 接口函数,首先要设计好输入参数.输出参数和返回值,接下来才能开始设计具体的实现过程.C 语言标准库提供的接口功能很有限,不像 Pyth ...
- C标准库-数值字符串转换与内存分配函数
原文链接:http://www.orlion.ga/977/ 一.数值字符串转换函数 #include <stdlib.h> int atoi(const char *nptr); dou ...
- C++ 标准库string字符串的截取
标准库的string有一个substr函数用来截取子字符串.一般使用时传入两个参数,第一个是开始的坐标(第一个字符是0),第二个是截取的长度. #include <iostream> #i ...
- C++标准库删除字符串中指定字符,比如空格
参见:https://zh.cppreference.com/w/cpp/algorithm/remove 使用 erase 和 remove 配合. #include <algorithm&g ...
- 数据分析处理库Pandas——字符串操作
字符串小写 字符串大写 字符串长度 去掉字符串中的空格 去掉字符串中的左空格 去掉字符串中的右空格 字符串替换 按字符串切割 字符串是否包含在另一个字符串中
- C 语言函数手册:涵盖字符测试、字符串操作、内存管理、时间换算、数学计算、文件操作、进程管理、文件权限控制、信号处理、接口处理、环境变量、终端控制
1. 字符测试函数 函数 说明 isascii() 判断字符是否为ASCII码字符 2. 字符串操作 函数 说明 gcvt() 将浮点型数转换为字符串(四舍五入) index() 查找字符串并返回首次 ...
- Boost程序库完全开发指南——深入C++“准”标准库(第3版)
内容简介 · · · · · · Boost 是一个功能强大.构造精巧.跨平台.开源并且完全免费的C++程序库,有着“C++‘准’标准库”的美誉. Boost 由C++标准委员会部分成员所设立的Bo ...
随机推荐
- 学DSP(一):开始
DSP有digital signal process 和 digital signal processor 2个意思,数字信号处理和数字信号处理器,我这里就是学数字信号处理器了. 我为什么要学DSP, ...
- Clone Graph 解答
Question Clone an undirected graph. Each node in the graph contains a label and a list of its neighb ...
- 【LeetCode练习题】Minimum Path Sum
Minimum Path Sum Given a m x n grid filled with non-negative numbers, find a path from top left to b ...
- python语言磁力搜索引擎源码公开,基于DHT协议
原文地址: http://www.cnblogs.com/huangxie/p/5550680.html
- Lua多重继承
http://blog.csdn.net/ssihc0/article/details/7742421 代码收藏了,以后用的到 --多重继承 local function search(k,plist ...
- python3 module中__init__.py的需要注意的地方
网上关于__init__.py的作用的资料到处都是,我在此就不再啰嗦哪些了. 若有需要.请各位看官去搜搜即可. 最近刚开始用Python3 就遇到了这个比较有意思的事情 闲言少叙,下面要介绍的是pyt ...
- apache启动问题: Could not reliably determine the server's fully qualified domain name
[root@rusky]# service httpd startStarting httpd: httpd: apr_sockaddr_info_get() failed for ruskyhttp ...
- android——fragment详解
在android开发过程中,如果使用到了导航栏.那么不可避免的就需要使用fragment来处理界面.闲着没事,就详解一下Framgent的使用方法吧. 难得写一次.本人 shoneworn shone ...
- Node.js实践第一天
实践案例一 表单提交 demo.html <!doctype html> <html> <head> <title>表单提交</title> ...
- null值的判断
select * from Students where Address IS null --判断address是nulselect * from Students where Address is ...