你必须知道的495个C语言问题,学习体会五

本文是 你必须知道的495个C语言问题 系列的第五篇,主要还是来了解下字符和字符串相关的东西,同时也会涉及内存相关的知识。

一般来说,字符串以及字符的处理都是编程语言中的常客,在C语言中,我们被给予了很好的灵活性,但是同时字符也成了最诡秘的处理单元。

在指针的内容里,我基本上讲解了字符串和字符数组的差异,所以这里就不再赘述了。

我们知道字符的英语翻译是character,这也是为什么字符类型为char的原因,我们使用单引号‘‘来标示一个字符而使用双引号“”来标示字符串。实际上,在C语言中没有内建的字符串类型,所以传统上使用以‘\0‘结束的字符数组来表示字符串,或者以字符串常量的形式出现。而再退一步讲,C语言也没有真正的字符类型,字符是用它在机器字符集中的整数值来表示的,

字符操作

标准库提供了两组函数,用于操作单独的字符,原形位于ctype.h中,第一组用于字符分类,第二组用于转换字符。

字符分类

以is开头,用于判断该字符属于何种类型:是则返回真

iscntrl //任何控制字符
isspace // 空白字符 :‘ ‘ 换页‘\f‘换行‘\n‘回车‘\r‘制表符‘\t‘或者垂直制表符‘\v‘
isdigit //10十进制任何数字
isxdigit // 16进制任何数字
islower // 小写字母
isupper // 大写字母
isalpha // 所有字母
isalnum // 所有字母和数字
isputct // 标点符号
isgraph // 图形符号
isprint // 任何可以打印的字符

  

字符转换

int tolower(int ch);//转变为小写字母
int toupper(int ch);//转变为大写字母

  

字符串操作

为了在C语言中操作字符串,标准库在头文件string.h中声明了一些函数。比如字符串的拼接,拷贝,查找,等。比如总有面试会考察strcpy的写法,这的确是很讨厌的一件事。既然C风格的字符串是一个由字符组成的char类型的数组,那么,我们就可以通过指针以及数组下标来操作字符串中一个个的字符。

字符串长度

可能你觉得字符串长度计算不值得一提,strlen是你写过的最简单的字符串处理函数

如果你觉得以下写法是你的作品,请注意下:

int mystrlen(char *str)
{
int lenght =0;
while(*str++!=‘\0‘)
lenght++;
return lenght;
}

  

乍一看,程序运行正确,也没有出现bug,但是,如果总分是100,这个函数只能得50分。老手就不要看了,心知肚明,新手可以从以下方面更正下:

  1. 程序的返回值类型
  2. 程序的入参类型限定
  3. 入参非空判断
  4. 不要试图写一个mystrlen而是直接调用标准strlen函数

可以做如下优化:

#include<stddef.h>
size_t myStrlen(char const*str)
{
int lenght =0;
while(*str++!=‘\0‘)
lenght++;
return lenght;
}

备注,size_t 与环境相关,一般为unsigned int类型,入参数为NULL未作检查,是为了说明,此类函数应该在调用前,就让调用者显示判断入参不为空。

字符串拷贝

char *strcpy(char *dst ,char const * src);
char *strncpy(char *dst,char const * src ,size_t len);

很清楚,strcpy将src字符串内容拷贝到dst处,而strncpy则只拷贝len个字节,二者有以下注意点:

  1. 调用者必须保证dst内存区间大小是合理的,以避免拷贝src时溢出,strcpy本身并不作检查。
  2. 正常调用后,strcpy能在拷贝最后自动加上‘\0‘,而不需要手动添加,但是strncpy则不然,如果,src的长度比参数len长,需要调用者手动添加‘\0‘。这一点需要注意。

字符串的拼接

char *strcat(char *dst,char const *src);
char *strncat(char *dst,char const *src,size_t len);

strcat 将src的内容拼接到dst的后面,是一个 字符串的连接动作,而strncat则是向dst连接src的前len个字符。但是与strncpy不同的是,它会保证在完成后向新的字符串末尾添加‘\0‘.

字符串拷贝和拼接函数的返回值 都是char* ,目的是为了嵌套调用这些函数。而实际上,返回值经常性被忽略。

字符串的比较

int strcmp(cha const *s1,char const* s2);
int strncmp(char const *s1,char const* s2,size_t len);

两个字符串的比较完全一致则 返回0,前者大于后者 返回一个大于零的值,否则返回一个小于零的值。

strcmp比较全部,而strncmp比较二者的前len个字符。

字符串查找

  • 查找一个字符,
char *strchr(char const *src,int ch);
char *strrchr(char const *src,int ch);

strchr从src找到ch字符第一次出现的位置,返回的就是该指针

而strrchr则是反向查找,或者说是最后一次出现的位置。

  • 查找任何几个字符
char *strpbrk(char const *str,char const* group);

该函数查找group中的任何一个在str中第一次出现的位置、

  • 查找一个子串
char *strstr(char const * s1,char const*s2);

  

这个函数在s1中查找整个s2第一次出现的起始位置,并返回一个指向该位置的指针。

备注:标准库中 并没有提供strrpbrk和strrstr的原形,但是,我们可以自己实现一个,

  • 查找一个字符串的前缀
size_t strspn(char const* str,char const*group);
size_t strcspn(char const* str,char const*group);

  

strspn返回str起始匹配group中任意字符的字符数,数量之和

而strcspn则是 与group中任意字符都不同的字符数量之和,有求补的意思。

直接看代码,更好理解:

#include<string.h>
char* str ="123123,hello world";
printf("%d\n",(int)strspn(str,"123,"));
printf("%d\n",(int)strcspn(str," "));

输出结果

  1. 7
  2. 12

strspn查找前面所有123和,的数量之和,为7;而strcspn查找前缀中不为空格的字符的个数,为12,如此,这两个函数的用法,你应该清楚了吧

  • 查找标记

    一个字符串常常包含几个单独的部分,它们彼此被分隔开来,每次为了处理各个部分,你必须将它们从字符串中抽取出来,这个任务是strtok所实现的功能。

char *strtok(char *str,char const* sep);

  

sep参数是一个字符串,定义了用作分隔符的字符集合。第一个参数时一个字符串,它包含零个或者多个由sep字符分隔的标记,strtok找到str的下一个标记,并将其用NUL结尾,然后返回一个指向这个标记的指针。警告:strtok会改变它所查找的字符串,所以你懂的怎么做的。

如果strtok函数的第一个参数不是NULL,函数将找到字符串的第一个标记,strtok同时将保存它在字符串中的位置,如果strtok函数的第一个参数是NULL,函数就在同一个字符串中从这个位置开始像前面一样查找下一个标记,如果字符串中不再有更多这样的标记则返回一个NULL指针,在典型情况下,在第一次调用strtok时,向它传递一个指向字符串的指针。然后这个函数被重复调用(第一个参数为NULL),直到它返回一个NULL为止。

记住:strtok有“记忆”功能,所以不要试图同时解析两个字符串。同理,因为strtok会改变传入的字符串本身,所以我们不应该传入字符串常量,所以请复制一份来使用。

看代码:

char str[]="123,45,67;6,23;2;31";//这里换成char *str就会崩溃
static char*sep =",;";
char* token = NULL;
for(token = strtok(str,sep);
token != NULL;
token = strtok(NULL,sep))
printf("%s ",token);

输出结果如你所想;

内存操作

我们知道,C字符串必须以‘\0‘结尾,所以非字符串处理时,含有零值得情况并不罕见,所以我们需要直接操作内存来保证‘\0‘也得到处理。

void* memcpy(void *dst,void const*src,size_t lenght);
void* memmove(void *dst,void const*src,size_t lenght);
void* memcmp(void const*a,void const*b,size_t lenght);
void* memchr(void const*a,int ch,size_t lenght);
void* memset(void* a ,int ch,size_t lenght);

因为 内存操作并没有结束标志,所以必须在最后一个参数中注明长度(以字节为单位)。这些函数的含义与字符串相同后缀的函数一样,所以不再赘述。

时间: 2024-11-03 03:45:41

你必须知道的495个C语言问题,学习体会五的相关文章

《你必须知道的495个C语言问题》笔记--库函数

怎样把数字转为字符串(与atoi相反)?有itoa函数吗? 用sprintf就可以了: sprintf(string, "%d", number); 同理,也可以同sprintf把long型或浮点型转换成字符串(使用%ld或%f),也就是说,可以把sprintf看成是atol或者atof的 反函数. 怎样在日期上加n天?怎样取得两个日期的时间间隔? 第一个问题,mktime接受没有规范话的日期,所以可以用一个日期的struct tm结构,直接在tm_mday域上进行加减,然后 调用mk

《你必须知道的495个C语言问题》笔记--杂项

如何进行移位操作? 因为左移操作(<<)不会导致符号位出现缺位,不考虑符号位,低位补0即可.所以对于无符号和有符号数来说,均为逻辑左移. 右移操作(>>)会涉及到符号位出现缺位的问题,所以在有符号数的右移操作时要考虑符号位怎么补的问题.对于无符号数来说, 最左侧补0,即逻辑右移:对于有符号来说,最左侧补符号位,即符号右移. 实践: #include <stdio.h> int main(void) { unsigned rui; int ri; unsigned int

《你必须知道的495个C语言问题》笔记--标准输入输出

getchar的返回值 这样的代码有什么问题: char c; while((c = getchar()) != EOF).... getchar返回值变量必须是int型.因为EOF通常定义为-1,二十进制为255的字符会被符号扩展,和EOF比较时会相等,从而 过早第结束输入. feof函数的使用 为什么这些代码最后一行复制了两遍? #include <stdio.h> #include <unistd.h> #include <fcntl.h> #define MAX

《你必须知道的495个C语言问题》笔记--数组和指针

一.如何动态分配多维数组? 1.分配一个指针数组,然后把每个指针初始化为动态分配的行 代码如下: int **array = (int **)malloc(ROW * sizeof(int*)); int i = 0; for(i=0; i<ROW; i++){ array[i] = (int *)malloc(COL * sizeof(int)); } 2.让数组的内容连续,但在后来重新分配行. 代码如下: int **array = (int**)malloc(ROW * sizeof(in

《你必须知道的495个C语言问题》笔记--表达式

1.怎样才能避免这些未定义的求值顺序问题呢? 有几条简单的规则: 1.确保一个表达式最多只修改一个对象:一个简单变量.一个数组或者一个指针指向的位置. 2.如果一个对象在一个表达式中出现一次以上而且在表达式中被修改,则要确保对该对象的所有读访问都被用于计算它的 最终值.这条规则允许表达式i=i+1,尽管i出现了两次而且被修改了,但对i的旧值读取是用于计算i的新值. 3.如果想破坏第一条规则,就要确保修改的对象互不相同.同时,尽量限制到最多2至3个修改并参照下面例子的风格.在 这条规则下,c=*p

《你必须知道的495个C语言问题》读书笔记之第2章:结构、联合和枚举

1. Q:下面两个声明有何区别? struct x1 {...}; typedef struct {...} x2; A:第一种形式声明了一个“结构标签”,第2种形式声明了一个“类型定义”.前者在声明结构的实例时必须使用struct关键字,如"struct x1 a;",后者则不需要使用struct关键字,如"x2 b;".但这个区别在C++编译器和某些模仿C++的C编译器中已经完全不存在了,在C++中结构标签在本质上都自动声明为类型定义. 2. Q:在C语言中是否

《你必须知道的495个C语言问题》读书笔记之第4-7章:指针

1. Q:为什么我不能对void *指针进行算术运算? A:因为编译器不知道所值对象的大小,而指针的算法运算总是基于所指对象的大小的. 2. Q:C语言可以“按引用传参”吗? A:不可以.严格来说,C语言总是按值传参,你可以模拟按引用传参,定义接受指针的函数,然后在调用时使用&操作符.但C没有任何真正等同于按引用传参的东西. 3. Q:怎样在整型和指针之间进行转换?能否暂时把整数放入指针变量,或者相反? A:C标准中规定整数与指针之间的相互转换是实现定义的,因此没有了指针和整数之间无法修改就相互

《你必须知道的495个C语言问题》笔记

1.10 对于没有初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为 "零", 它可否作为空指针或浮点零? 答: 具有 "静态" 生存期的未初始化变量 (即, 在函数外声明的变量和有静态存储类型的变量) 可以确保初始值为零, 就像程序员键入了 "=0" 一样. 因此, 这些变量如果是指针会被初始化为正确的空指针, 如果是浮点数会被初始化为 0.0 (或正确的类型, 参见第 5 章).具有 "自动" 生存期的变量 (

C#刨根究底:《你必须知道的.NET》读书笔记系列

一.此书到底何方神圣? <你必须知道的.NET>来自于微软MVP-王涛(网名:AnyTao,博客园大牛之一,其博客地址为:http://anytao.cnblogs.com/)的最新技术心得和感悟,将技术问题以生动易懂的语言展开,层层深入,以例说理.全书主要,包括了.NET基础知识及其深度分析,以.NET Framework和CLR研究为核心展开.NET本质论述,涵盖了.NET基本知识几乎所有的重点内容.全书分为5个部分,第1部分讲述.NET与面向对象,从底层实现角度分析了.NET如何实现面向