魅力如C——表达与倾听的艺术

字符串常量、字符数组、const char*

在C中,并没有string类型,而是将字符串常量处理成以‘\0’结尾的字符数组,若要将字符串常量赋给左值,左值可为char型数组,也可为const char*的指针,但前者是将字符串传副本到字符数组中,后者则是将字符串传地址给指针。字符串常量属于静态存储类别,只会被储存一次,在整个程序的生命期内存在。另外,字符串常量存储于只读存储区中,不允许修改,小编写了个测试代码,从gcc的返回结果可知:

下面提下常用的字符串函数,这些函数的原型放在string.h头文件中。

int strlen(constchar* string)用于获取字符串的长度,返回的是字符的个数,但是不会包括’\0’结束符。

int strncmp(const char*str1, const char *str2, int size):按照ascii码来进行比较,并由函数返回值进行判断,返回0表示字符串1等于字符串2,正数表示字符串1位于字符串2的后面,负数表示字符串1位于字符串2的前面。

char* strchr(const char*,int c):从左往右寻找字符c。

稍微提下,大小写字母ascii码的关系是对应的大写字母与小写字母之间相差32,其中大写字母ascii码小于小写字母ascii码,也就是说大写字母在ascii码表中的排序位置在小写字母之前,下面是可打印字符的ascii表:

函数使用指向字符串首字符的指针来表示待处理的字符串,通常,对应的实际参数是数组名、指针变量或用双引号括起来的字符串,无论是哪种情况,传递的都是首字符的地址,一般而言,没必要传递字符串的长度,因为函数可以通过末尾的空字符确定字符串的结束。

字符串,无论是由字符数组、指针还是字符串常量标识,都储存为包含字符编码的一系列字节,并以空字符结尾

Scanf与Printf

下面将介绍输入输出函数:

scanf(“格式控制字符串”, 地址表列):格式字符串的一般形式为:%[*][输入数据宽度][长度]类型、其中若将*置于%与转换字符之间,会使得scanf跳过相应的输入项;二、输入数据宽度用于控制scanf读取数据的宽度,切要注意scanf没有精度控制,如scanf("%5.2f",&a);是非法的,不能企图用此语句读入小数为2位的实数。三、长度格式符为l和h,l表示输入长整型数据(如%ld)和双精度浮点数(如%lf)。h表示输入短整型数据。

、常用的读取类型如下所示,如果格式控制串中有非格式字符(如逗号分隔符)则输入时也要输入该非格式字符,如果输入的数据与格式控制字符串中的类型不一致时,虽然编译能够通过,但结果或将不正确。

scanf()以Space、Enter、Tab结束一次输入,不会舍弃最后的回车符(即回车符会残留在缓冲区中),如果想用scanf读取字符串其实并不好,因为scanf()跳过空白开始读取第一个非空白字符,并保存非空白字符直到再次遇到空白,这意味着scanf只能读取不包含空白字符的字符串,实验示例如下,可见scanf不能读取内含space的字符串,scanf适合用于读取字符,而不是字符串

scanf()在读取字符时也要留心,除了%c,其它类型都会自动跳过待输入值前面所有的空白,但是%c不会,其会读取空白字符,示例如下所示,小编输入的第一个字符是space空白符,但是%c下,scanf当其是有效字符读取进来,因此最后printf实际上是打印了一个空白。

scanf函数的返回值为成功读取的项数,如果没有读取到任何项,scanf便会返回0,可以使用scanf的返回值来检测和处理不匹配的输入。

空白字符(制表符、空格、换行符)在scanf处理输入时起着至关重要的作用,除了%c模式,scanf在读取输入时会跳过非空白字符前的所有空白字符,然后一直读取字符,直到遇到空白字符或与正在读取字符不匹配的字符

另外,对缓冲区的理解和处理也很重要,强烈建议在下一次scanf读取前应先清空缓冲区中的残留数据,C语言里提供了函数用于清空缓冲区fflush(stdin),下面只是提供了一个简单的示例,这告诫我们加强对缓冲区的理解和处理是很重要的,可以试试如果将fflush(stdin)去掉会发生怎样的情况。

printf(“格式控制字符串”, 输出表列):格式字符串的一般形式为:[标志][输出最小宽度][.精度][长度]类型、标志字符为 -、+、# 和空格四种,第一个是左对齐,第二个是显示符号,第三个是加进制前缀;、用十进制整数来表示输出的最少位数。若实际位数多于定义的宽度,则按实际位数输出,若实际位数少于定义的宽度则补以空格或0。、精度格式符以“.”开头,后跟十进制整数。意义是:如果输出数字,则表示小数的位数;如果输出的是字符,则表示输出字符的个数;若实际位数大于所定义的精度数,则截去超过的部分。、长度格式符为h、l两种,h表示按短整型量输出,l表示按长整型量输出;、类型就不说了,和上面scanf的类型是一致的。

类型可移植性也是个重要问题,如sizeof运算符以字节为单位返回类型或值的大小,其应该是某种形式的整数,但是标准只规定该值是无符号整数,在不同的实现中,其可以是unsigned int/unsigned long/unsigned long long,因此如果要用printf显示sizeof表达式,就可能使用不同的转换说明,而C提供了可移植性更好的类型,即是size_t,其是为了方便在系统之间移植而定义的,在32位机中,size_t实际为unsigned int,而在64位机中,size_t实际为unsigned long,其中,C中还定义了ptrdiff_t类型用来表示系统使用的两个地址之间的差值,该类型让小编颇为心动,居然可以得到两个地址之间的差值哦,那我岂不是只要记录下头指针和尾指针,就可以知道数据量了吗!在动态分配了一大块内存后,我们给该块内存填充数据,而很多时候我们希望得知数据量的大小,此时可以通过记录头指针和尾指针来计算获取数据量的大小,下面写了个测试代码:

gets与puts

gets()从标准输入stdin读取字符直到遇到换行符(‘\n’)为止,其读取整行输入,可接受回车键之前输入的所有字符(包含空格、制表符Tab),直至遇到换行符,并用’\0’替代 ‘\n’,回车键不会留在输入缓冲区中。其形参为用于储存所读取字符串的内存地址。

puts()将缓冲区中的字符输出到标准输出stdout,直到遇到空字符(’\0’)为止,puts函数很容易使用,只需把字符串的地址作为参数传递给它即可。puts在显示字符串时会自动在其末尾添加一个换行符。因此,gets最好与puts一起搭配用,前者不读取且会丢弃换行符,而后者则会自动添加换行符,输出更美观规整。

gets使用前须定义字符数组用于储存所读取的字符串,作为实参传给gets。如果输入的字符个数大于定义的数组长度,则会引起危险。 因为gets不会判断缓存是否溢出,会把数组后面的内存覆盖。这就是危险的缓冲区溢出问题。因此,强烈建议永远不要使用gets与puts

fgets与fputs

fgets(char* str, int n, FILE* fp),此处,fp是文件指针,如果要从键盘读入数据,则以标准输入stdin作为参数;str是存放字符串的起始地址,n用于限定读入字符数。函数的功能是从文件流或标准输入流中读入n-1个字符放入以str为起始地址的内存空间内,如果在未满n-1个字符前,已读到一个换行符或一个EOF(文件结束标志),则结束本次读取操作,但其会读取并保留换行符。

如果一切顺利,该函数返回的地址与传入的第一个参数相同,但是,如果在读入数据时出现某些错误或者读到文件结尾,其将返回空指针(null pointer),该指针保证不会指向有效的数据,所以可用于标识这些特殊情况。

fputs(const char* str, FILE* fp),此处fp是用于待写入的文件指针,如果要输出到屏幕上,则以标准输出stdout作为参数;str是存放待写入或待显示字符串的起始地址。注意,若fgets在未读取满n-1个字符前遇到换行符,会读取并保留换行符,而fputs不会在输出中添加换行符,因此,二者最好搭配在一起使用,通过加入限定读取数的形参int n有效防止了缓冲区溢出问题。

这里稍微提下空字符与空指针的差别,空字符‘\0’是用于标记C字符串末尾的字符,其对应字符编码为0,由于其它字符的编码不可能为0,所以不可能是字符串的一部分。空指针NULL有一个值,但该值不会与任何数据的有效地址对应,通常函数使用它返回一个有效地址表示某些特殊情况发生,例如遇到文件结尾或未能按预期执行。空字符是一个字符,占一字节;而空指针是一个地址,通常占4字节。

流与缓冲区

K&R 在 C Programming language中提到流是这样定义的:流stream是与磁盘或其它外围设备关联数据的源或目的地。C提供了两种类型的流:二进制流(字节流)与文本流(字符流),一开始小编也是懵的,渐渐小编认识到两种流本质上是一样的,只是面对不同的应用场景而加以区分。因为字节是计算机保存数据的最终形式,而字符是其它数据结构序列化后的表现形式,也是人可以阅读的形式,与外界的交互需要这些通用的格式。如果不关心数据的内容,只需要完整地传输原始数据时,考虑字节流即可;若关心传输字符和字符串时,就需要对字符流进行操作,stdio.h头文件里那一大坨输入输出函数就是干这个的。比如fgetc(FILE *stream)从文本流中读入一个字符。

缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。

缓冲区分为三种类型:全缓冲、行缓冲和不带缓冲,大部分系统默认使用下列类型的缓存:标准出错是不带缓存的。如果是涉及终端设备的流,则它们是行缓存的;否则是全缓存的全缓冲:当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。行缓冲:当在输入和输出中遇到换行符时,执行真正的I/O操作,典型代表是标准输入(stdin)和标准输出(stdout)。不带缓冲:也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180719G19LIH00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券