C和C指针小记(十四)-字符串、字符和字节
1、字符串
C语言没有字符串数据类型,因为字符串以字符串常量的形式出现或存储于字符数组中.
字符串常量和适用于那些程序不会对他们进行修改的字符串.
所有其他字符串都必须存储于字符串数组或动态分配的内存中.
字符串是一个或多个字符,并且以一个位模式全0 的NUL字节结尾
头文件 string.h 包含了使用字符串函数所需的原型和声明.
2、字符串长度
用库函数 strlen 计算字符串长度
size_t strlen(char const *string);
size_t 是一个无符号整型,在stddef.h中定义.
警告:
if( strlen( x ) >= strlen( y ) ) //1
if( strlen( x ) - strlen( y ) >= 0)//2
第一条语句可以正常工作
第二天语句永远是真. 因为strlen的结果是个无符号数,所以操作符 >= 左边的表达式也将是无符号数,而无符号数绝对不可能是负的.
把strlen的返回值强制转换为int可以消除这个问题,但是不推荐.
//计算字符串参数的长度
size_t strlen (char const *string){
int length;
for( length = 0; *string++ != '\0';)
length += 1;
return length;
}
3、不受限制的字符串函数
最常用的字符串函数都是“不受限制”的,就是说他们值是通过寻找字符串参数结尾的NUL字节来判断它的长度.
这些函数一般都指定一块内存用于存放结果字符串.在使用这些函数时,程序员必须保证结果字符串不会溢出这块内存.
3.1 复制字符串
char *strcpy(char *det, char const *src);
这个函数把参数src字符串复制到dst参数.如果参数src和dst在内存中出现重叠(也就是两者相同),其结果是为定义的.
由于dst参数将进行修改,所以它必须是个字符数组或者一个指向动态分配内存的数组指针,不能使用字符串常量.
目标参数的之前的内容将被覆盖并丢失.即使新的字符串比dst原先的内存更短,由于新字符串是以NUL字节结尾,所以老字符串最后剩余的几个字符也会被有效地删除
例子:
char message[] = "Original message";
...
if(...)
strcpy(message, "Different" );
如果if条件为真,并且顺利复制,数组将包含以下的内容:
第一个NUL字节后面的几个字符再也无法被字符串函数访问,因此从实现的角度看,他们都已经丢失了.
注意:
程序员必须保证目标字符数组的空间足以容纳需要复制的字符串.如果字符串比数组长,多余的字符仍被复制, 它们将覆盖原先存储于数组后面的内存空间的值.
scrcpy无法解决这个问题,因为它无法判断目标字符的长度.
3.2 链接字符串
想要把一个字符串添加(连接)到另一个字符串的后面,你可以使用strcat函数.它的原型如下:
char *strcat( char *dst, char const *src);
strcat函数要求dst参数原先已经包含了一个字符串(可以是空字符串).它找到这个字符串的末尾,并把src字符串的一份拷贝添加到这个位置.如果src 和 det 的位置发生重叠,其结果是为定义的.
警告:
程序员必须保证目标字符数组剩余的空间足以保存整个源字符串
3.3 函数的返回值
strcpy和strcat都返回他们第1个参数的一份拷贝,就是一个指向目标字符数组的指针.这样可以方便的嵌套调用这些函数
如:
strcat( strcpy(dst, a), b);
strcpy首先执行.它把字符串从a复制到dst并返回dst.然后这个返回值成为strcat函数的第一个参数, strcat 函数把b 添加到 dst后面.
3.4 字符串的比较
比较两个字符串涉及对两个字符串对应的字符逐个进行比较,知道发现不匹配为止.那个最先不匹配的字符中较“小”(也就是所在字符集中的序数较小)的那个字符所在的字符串被认为“小于”另外一个字符.如果其中一个字符串是另外一个字符串的前面一部分,那么它被认为“小于”另外一个字符串,因为它的NUL结尾字节出现得更早.这种比较被称为“**词典比较**”.对于只包含大写字母或小写字母的字符串比较,,这种比较过程所给出的结果总是和我们日常所用的字母顺序比较相同.
strcmp原型:
int strcmp(char const *s1, char const *s2);
当s1 > s2 时,返回值大于0 ,当s1 == s2 时,返回值等于0, 当 s1 < s2时,返回值小于0;
注意:
if( strcmp(a, b) ) 不要误以为if条件为真.这个条件表达式 当a==b 的时候返回值是0.所以if语句为假.更好的方法是将strcmp的返回值与0比较
标准中规定,s1 > s2 时,返回值大于0 ,当s1 == s2 时,返回值等于0, 当 s1 < s2时,返回值小于0.但并没有规定 s1 > s2 时返回值是 -1, s1 < s2 时返回值是1
由于strcmp并不修改它的任何一个参数,所以不存在溢出字符数组的危险.但是必须保证它和其他不受限制的字符串函数一样,strcmp 函数的字符串参数必须以一个NUL 字节结尾.如果不这样,strcmp就可能对参数后面的字节进行比较,这样的比较结果没有意义
4、长度受限的字符串函数
标准库还包含了一些函数,他们以一种不同的方式处理字符串.这些函数接受一个显式的长度参数,用于限定进行复制或比较的字符数. 这些函数提供了一种方便的机制,可以防止难以预料的长字符串从它们的目标数组溢出.
如下:
char *strncmp( char *dst, char const *src, size_t len);
char *strcnat( char *dst, char const *src, size_t len);
char *strncmp( char *s1, char const *s2, size_t len);
对于strncpy 它总是正好向 dst 写入 len 个字符. 如果 strlen(src) 的值小于 len, dst 数组就用额外的NUL 字节填充到len长度.如果 strlen(src) 的值大于或等于 len,那么只有len个字符串被复制到dst中.注意!它的结果将不会以NUL字节结尾
注意:
strncpy 调用的结果可能不是一个字符串,因此字符串必须以 NUL字节结尾.如果在一个需要字符串的地方(如strlen函数的参数)使用了一个不是以NUL结尾的字符序列,它将会继续拷贝字符序列长度之外的字符,直到它发现一个NUL字节为止.所以这是strlen这时候返回的是一个随机数.如果函数视图访问系统分配给这个程序以外的内存范围,程序就会崩溃
如何避免这样的错误:
这个问题只有当你使用strncpy 函数创建字符串,然后或者对他们使用以str开头的库函数,或者在printf中使用%s格式码打印它们时才会发生.在使用不受限制的函数之前,你首先必须确定字符串实际上是以NUL字节结尾的
char buffer[BSIZE];
strncpy( buffer, name, BSIZE);
buffer[BSIZE - 1] = '\0';
如果name的内容可以容纳于buffer中,那么最后的那个赋值语句没有任何效果和影响.但是,如果name太长,这条语句能能够保证 buffer 中字符串是以 NUL 结尾的.
以后对这个数组使用strlen 或其他不受限制的字符串函数将能够正确的工作.
5、字符串查找
5.1 查找一个字符串
char *strchr( char const *str, int ch );
char *strrchr( char const *str, int ch );
注意,我们之前说过的,第二个参数是个整型类型,但是它包含了一个字符值. strchr 在字符串中查找 ch 第一次出现的位置,找到后函数返回一个指向该位置的指针.
strrchr 返回最后一次出现的位置.
strchr 和 strrchr 区分大小写.
5.2 查找任意几个字符
char *strpbrk( char const *str, char const *group );
这个函数返回一个指向 str 中第1个匹配group 中任何一个字符的字符位置,如果未找到匹配,函数返回一个NULL 指针.区分大小写
char string[20] = "Hello there, honey.";
char *ans;
ans = strpbrk( string, "aeiou");
//ans 所指向的位置是string + 1 , 第二个参数中的e第一次出现在 第一个字符串中
5.3 查找一个子串
char *strstr( char const *s1, char const *s2 );
该函数查找 在s1 中 s2 第一次出现的起始位置,并返回一个指向该位置的指针. 如果s2没有完整的出现在s1中的任何地方,返回一个NULL 指针.如果第二个参数是空字符串,函数返回s1.
标准库中并不存在 strrstr 或者 strrpbrk 函数
//在字符串 s1 中查找字符串 s2 最右出现的位置,并返回一个指向该位置的指针
#include <string.h>
char *my_strrstr( char *const *s1, char const *s2 ){
register char *last;
register char *current;
//把指针初始化为我们已经找到的前一次匹配位置
last = NULL;
//只在第2个字符串不为空时才进行查找,如果s2 为空, 返回NULL
if( *s2 != '\0' ){
//查找s2 在 s1 中第一次出现的位置
current = strstr(s1, s2);
//我们每次找到字符串时,让指针指向它的起始位置,然后查找该字符串下一个匹配位置.
while( current != NULL ){
last = current;
current = strstr(last + 1, s2 );
}
}
//返回指向我们找到的最后一次匹配的起始位置的指针
return last;
}
6、高级字符串查找
strspn 和 strcspn 函数用于在字符串的起始位置对字符计数.
size_t strspn( char const *str, char const *group );
size_t strcspn( char const *str, char const *group );
group 字符串指定一个或者多个字符. strspn 返回 str 起始部分匹配 group 中任意字符的字符数.
如果group 包含了空格,制表符等空白字符,那么这个函数将返回 str 起始部分空白符的数目.
strspn 返回字符串s1中第一个不在字符串s2中出现的字符下标.
unsigned long len1, len2;
char buffer[] = "25,142,330,Smith,J,239-4123";
len1 = strspn(buffer, "0123456789");// buffer 中 第一个不在 “0123456789” 出现的字符是‘ , ’ 它在buffer 的第2个位置(以0开始), 也就是buffer 的0-1 都在 “0123456789” 中
len2 = strspn(buffer, ",0123456789");//buffer 中 第一个不在 “,0123456789” 出现的字符串是 ‘S’ 它在buffer 的第 11个位置, 也就是说 buffer 的 0-11 都在 “,0123456789” 中
printf("len1 :%ld,len2: %ld \n",len1,len2);//2 , 11
size_t strcspn (const char *s, const const *reject );
strcspn() 从参数 s 字符串的开头开始计算连续的字符,而这些字符完全不在参数 reject 所指的字符串中. 简单的说,若 strcspn() 返回的数值为n, 则代表字符串 s 开头连续有n个字符都不含字符串 reject 内的字符.
返回值: 返回字符串s 开头连续不含字符串reject内的字符数目.
#include <string.h>
int main(){
char *str = "Linux was first developed for 386/486-based pcs. ";
printf("%lu\n", strcspn(str, " "));// 只计算到 “ ”(空格)出现 所以返回到“Linux”的长度 5
printf("%lu\n", strcspn(str,"/-"));//返回到 ’/‘ 或 ‘-’出现, 33
printf("%lu\n",strcspn(str,"1234567890"));//返回到 3 出现, 30
}
C和C指针小记(十四)-字符串、字符和字节的更多相关文章
- C和指针 第十四章 预处理器 头文件
编写一个C程序,第一个步骤称为预处理,预处理在代码编译之前,进行一些文本性质的操作,删除注释.插入被include的文件.定义替换由#define定义的符号,以及确定代码的部分内容是否应该按照条件编译 ...
- java提高篇(十四)-----字符串
可以证明,字符串操作是计算机程序设计中最常见的行为. 一.String 首先我们要明确,String并不是基本数据类型,而是一个对象,并且是不可变的对象.查看源码就会发现String类为f ...
- C和C指针小记(十六)-动态内存分配
动态内存分配 1.1 为什么使用动态内存分配 直接声明数组的方式的缺点: 1) 声明数组必须指定长度限制.无法处理超过声明长度的数组. 2) 如果声明更大的常量来弥补第一个缺点,会造成更多的内存浪费. ...
- C和C指针小记(十八)-使用结构和指针-双向链表
1.双链表 1.1 双向链表的声明 在一个双链表中,每个节点都包含两个指针--指向前一个节点的指针和指向后一个节点的指针. 声明 typedef struct NODE { struct NODE * ...
- C和C指针小记(十五)-结构和联合
1.结构 1.1 结构声明 在声明结构时,必须列出它包含的所有成员.这个列表包括每个成员的类型和名称. struct tag {member-list} variable-list; 例如 //A s ...
- Swift学习笔记(十四)——字符,常量字符串与变量字符串
在学习Java过程中,字符串碰到过String和StringBuffer,当中前者是不可变的,不能对字符串进行改动:后者是可变的,能够不断改动. 来到Swift中,对字符串的定义变的更加简单. (1) ...
- C和C指针小记(十)-函数
1.函数的定义 函数的定义就是函数体的实现. 语法: 类型 函数名(形式参数) 代码块 函数返回类型和函数名分开写是代码风格的问题,现代语言如swift返回值在函数名和参数表的后面,这样使得某些工程工 ...
- C和指针 第十四章 习题
14.1 打印函数 #include <stdio.h> void print_ledger_long(){ printf("function print_ledger_long ...
- C和C指针小记(十二)-函数的可变参数表
1.可变参数表是通过宏实现的 宏定义于stdarg.h头文件,它是标准库的一部分.这个头文件声明了一个类型var_list和三个宏--va_start.va_arg.va_end. 我们可以声明一个类 ...
随机推荐
- linux 目录/sys 解析
今天搞树莓派,遇到/sys这个目录,不太清楚,先对/sys目录知识进行一个整理 首先,对 /sys目录下的各个子目录进行具体说明: /sys下的子目录 内容 /sys/devices 该目录下是全局设 ...
- 游戏行业DDoS攻击解决方案
行业综述 根据全球游戏和全球移动互联网行业第三方分析机构Newzoo的数据显示:2017年上半年,中国以275亿美元的游戏市场收入超过美国和日本,成为全球榜首. 游戏行业的快速发展.高额的攻击利润.日 ...
- Xilinx的ISE14.7和PlanAhead与win10系统的兼容性问题解决方案
Xilinx的ISE14.7和PlanAhead与win10系统的兼容性问题解决方案 2018年07月03日 18:27:57 feq123 阅读数:4495 今天在新电脑的win10系统上安装I ...
- delphi 导出到excel的7种方法
本文来自 爱好者8888 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/kpc2000/article/details/17066823?utm_source=cop ...
- Canvas入门到高级详解(下)
四. Canvas 开发库封装 4.1 封装常用的绘制函数 4.1.1 封装一个矩形 //思考:我们用到的矩形需要哪些绘制的东西呢? 矩形的 x.y坐标 矩形的宽高 矩形的边框的线条样式.线条宽度 矩 ...
- Windows 10下安装配置Caffe并支持GPU加速(修改版)
基本环境 建议严格按照版本来 - Windows 10 - Visual Studio 2013 - Matlab R2016b - Anaconda - CUDA 8.0.44 - cuDNN v4 ...
- 集合的最大缺点是无法进行类型判定(这个缺点在JAVA1.5中已经解决),这样就可能出现因为类型不同而出现类型错误。
集合的最大缺点是无法进行类型判定(这个缺点在JAVA1.5中已经解决),这样就可能出现因为类型不同而出现类型错误. 解决的方法是添加类型的判断. LinkedList接口(在代码的使用过程中 ...
- CDN 服务域名解析配置
记录类型请选择为CNAME: 主机记录即加速域名的前缀,例如: 如果您的加速域名为... 前缀为... testcdn.aliyun.com testcdn www.aliyun.com www ...
- Java 8 时间日期
啦啦啦 package lime.java1_8.time; import java.time.*; import java.time.format.DateTimeFormatter; import ...
- LeetCode - 503. Next Greater Element II
Given a circular array (the next element of the last element is the first element of the array), pri ...