C语言学习栏目目录
目录
1、前导程序
2、字符串简介
3 常量和C预处理器
这篇介绍下列内容:
函数:strlen()
关键字:const
字符串
如何创建、存储字符串 如何使用strlen()函数获取字符串的长度,用C预处理器指令#define和ANSIC的const修饰符创建符号常量。
本篇重点介绍输入和输出。与程序交互和使用字符串可以编写个性化的 程序,本章将详细介绍C语言的两个输入/输出函数:printf()和scanf()。学会 使用这两个函数,不仅能与用户交互,还可根据个人喜好和任务要求格式化 输出。最后,简要介绍一个重要的工具——C预处理器指令,并学习如何定 义、使用符号常量。
1、前导程序
// 这次是用xcode编译,所以代码书写风格有点差异,但是运行结果是一样的// main.c// day05// function:演示与用户交互// Created by 陈文坚 on /12/31.// Copyright © 陈文坚. All rights reserved.//a#include <stdio.h>#include <string.h> // 提供strlen()函数的原型#define PRICE 25.5 // 书本价格int main(void){float sum;int num,size,letters;char name[40]; // name是一个可容纳40个字符的数组printf("你的名字是什么?\n");scanf("%s", name);//在控制台输入你的名字,英文/拼音printf("%s, 你打算买几本书?\n", name);scanf("%d", &num);//输入书本的数量size = sizeof name;letters = strlen(name);sum = num * PRICE;printf(" %s, 你购买书的总价是:%2.2f 元.\n", name, sum);printf("而且你名字的长度是: %d ,\n", letters);printf("并且它占用的空间是 %d(字节).\n", size);return 0;}
运行程序,得到的结果如下:
你的名字是什么?chenchen, 你打算买几本书?5chen, 你购买书的总价是:127.50 元.而且你名字的长度是: 4 ,并且它占用的空间是 40(字节).Program ended with exit code: 0
该程序包含以下新特性。
用数组(array)储存字符串(character string)。在该程序中,用户输 入的名被储存在数组中,该数组占用内存中40个连续的字节,每个字节储存 一个字符值。使用%s转换说明来处理字符串的输入和输出。注意,在scanf()中,name没有&前缀,而num有(稍后解释,&num和name都是地址)。用C预处理器把字符常量PRICE定义为25.5。用C函数strlen()获取字符串的长度。
对于BASIC的输入/输出而言,C的输入/输出看上去有些复杂。不过, 复杂换来的是程序的高效和方便控制输入/输出。而且,一旦熟悉用法后, 会发现它很简单。
2、字符串简介
字符串(character string)是一个或多个字符的序列,如下所示:
"Zing went the strings of my heart!"
双引号不是字符串的一部分。双引号仅告知编译器它括起来的是字符串,正如单引号用于标识单个字符一样。
2.1 char类型数组和null字符
C语言没有专门用于储存字符串的变量类型,字符串都被储存在char类型的数组中。数组由连续的存储单元组成,字符串中的字符被储存在相邻的存储单元中,每个单元储存一个字符(如下图)
上图中数组末尾位置的字符\0。这是空字符(null character),C语言用它标记字符串的结束。空字符不是数字0,它是非打印字符,其ASCII码值是(或等价于)0。C中的字符串一定以空字符结束,这意味着数组的容量必须至少比待存储字符串中的字符数多1。因此,程序清单4.1中有40个存储单元的字符串,只能储存39个字符,剩下一个字节留给空字符。那么,什么是数组?可以把数组看作是一行连续的多个存储单元。用更正式的说法是,数组是同类型数据元素的有序序列。程序清单4.1通过以下声明创建了一个包含40个存储单元(或元素)的数组,每个单元储存一个
char类型的值:char name[40];
name后面的方括号表明这是一个数组,方括号中的40表明该数组中的元素数量。char表明每个元素的类型
2.2 使用字符串
先来个程序:
/************************************************************************程序功能:输入你的名字,并显示在控制台函数:name[40] char数组,存放名字 ************************************************************************/#include<stdio.h>#define PRAISE "You are an extraordinary being."//定义字符串常量int main(void) {char name[40];printf("What's your name? \n");scanf("%s", name);//输入名字printf("Hello, %s.%s\n", name, PRAISE); //打印名字与常量system("pause");return 0;}
%s告诉printf()打印一个字符串。%s出现了两次,因为程序要打印两个字符串:一个储存在name数组中;一个由PRAISE来表示。运行程序,其输出如下所示:
What's your name?陈 大Hello, 陈.You are an extraordinary being.请按任意键继续. . .
你不用亲自把空字符放入字符串末尾,scanf()在读取输入时就已完成这项工作。也不用在字符串常量PRAISE末尾添加空字符。稍后我们会解释#define指令,现在先理解PRAISE后面用双引号括起来的文本是一个字符串。编译器会在末尾加上空字符。注意(这很重要)scanf()只读取了陈 大中的陈,它在遇到第1个空白(空格、制表符或换行符)时就不再读取输入。因此,scanf()在读到“陈”和”大“之间的空格时就停止了。一般而言,根据%s转换说明,scanf()只会读取字符串中的一个单词,而不是一整句。C语言还有其他的输入函数(如fgets()),用于读取一般字符串。后面章节将详细介绍这些函数。
字符串和字符
字符串常量"x"和字符常量'x'不同。区别之一在于'x'是基本类型(char),而"x"是派生类型(char数组);区别之二是"x"实际上由两个字符
组成:'x'和空字符\0(如下图)
2.3 strlen()函数
前面提到了 sizeof 运算符,它以字节为单位给出对象的大小。strlen()函数给出字符串中的字符长度。因为 1 字节储存一个字符,读者可能认为把两种方法应用于字符串得到的结果相同,但事实并非如此。在上面程序清单中添加几行代码,看看为什么会这样。
/************************************************************************程序功能:main: 输入你的名字,并显示在控制台main1:输入你的名字,并显示在控制台,显示字符串长度函数:name[40] char数组,存放名字 ************************************************************************/#include<stdio.h>#include <string.h>//提供strlen()函数的原型/#define PRAISE "You are an extraordinary being."//定义字符串常量int main(void){char name[40];printf("What's your name? \n");scanf("%s", name);//输入名字printf("Hello, %s.%s\n", name, PRAISE); //打印名字与常量//显示名字的长度与name数组存储单元printf("Your name of %zd letters occupies %zd memory cells.\n",strlen(name), sizeof name);printf("The phrase of praise has %zd letters ",strlen(PRAISE));printf("and occupies %zd memory cells.\n", sizeof PRAISE);system("pause");return 0;}
#include <string.h>
string.h头文件包含多个与字符串相关的函数原型,包括strlen()。后续j将详细介绍该头文件(顺带一提,一些ANSI之前的UNIX系统用strings.h代替string.h,其中也包含了一些字符串函数的声明)。
一般而言,C 把函数库中相关的函数归为一类,并为每类函数提供一个头文件。例如,printf()和scanf()都隶属标准输入和输出函数,使用stdio.h头文件。string.h头文件中包含了strlen()函数和其他一些与字符串相关的函数(如拷贝字符串的函数和字符串查找函数)。
注意,程序清单使用了两种方法处理很长的printf()语句。第1种方法是将printf()语句分为两行(可以在参数之间断为两行,但是不要在双引号中的字符串中间断开);第 2 种方法是使用两个printf()语句打印一行内容,只在第2条printf()语句中使用换行符(\n)。运行该程序,其交互输出如下:
What's your name?陈Hello, 陈.You are an extraordinary being.Your name of 2 letters occupies 40 memory cells.The phrase of praise has 31 letters and occupies 32 memory cells.请按任意键继续. . .
sizeof运算符报告,name数组有40个存储单元。但是,只有前2个单元用来储存"陈",所以strlen()得出的结果是2。name数组的第3个单元
储存空字符,strlen()并未将其计入。下图演示了这个概念。
对于 PRAISE,用 strlen()得出的也是字符串中的字符数(包括空格和标点符号)。然而,sizeof运算符给出的数更大,因为它把字符串末尾不可见的空字符也计算在内。该程序并未明确告诉计算机要给字符串预留多少空间,所以它必须计算双引号内的字符数。C99 和 C11 标准专门为 sizeof 运算符的返回类型添加了%zd 转换说明,这对于strlen()同样适用。对于早期的C,还要知道sizeof和strlen()返回的实际类型(通常是unsigned或unsigned long)。另外,还要注意一点:上一章的 sizeof 使用了圆括号,但本例没有。圆括号的使用时机否取决于运算对象是类型还是特定量?运算对象是类型时,圆括号必不可少,但是对于特定量,可有可无。也就是说,对于类型,应写成sizeof(char)或sizeof(float);对于特定量,可写成sizeof name或sizeof 6.28。尽管如此,还是建议所有情况下都使用圆括号,如sizeof(6.28)。
3 常量和C预处理器
有时,在程序中要使用常量。例如,可以这样计算圆的周长:
circumference = 3.14159 * diameter
这里,常量3.14159代表著名的常量pi(π)。在该例中,输入实际值便可使用这个常量。然而,这种情况使用符号常量(symbolic constant)会更好。也就是说,使用下面的语句,计算机稍后会用实际值完成替换:
circumference = pi * diameter
为什么使用符号常量更好?首先,常量名比数字表达的信息更多。请比较以下两条语句:
owed = 0.015 * housevalue;owed = taxrate * housevalue;
如果阅读一个很长的程序,第2条语句所表达的含义更清楚。另外,假设程序中的多处使用一个常量,有时需要改变它的值。毕竟,税率通常是浮动的。如果程序使用符号常量,则只需更改符号常量的定义,不用在程序中查找使用常量的地方,然后逐一修改。那么,如何创建符号常量?方法之一是声明一个变量,然后将该变量设置为所需的常量。可以这样写:
float taxrate;taxrate = 0.015;
这样做提供了一个符号名,但是taxrate是一个变量,程序可能会无意间改变它的值。C语言还提供了一个更好的方案——C预处理器。第2 章中介绍了预处理器如何使用#include包含其他文件的信息。预处理器也可用来定义常量。只需在程序顶部添加下面一行:
#define TAXRATE 0.015
编译程序时,程序中所有的TAXRATE都会被替换成0.015。这一过程被称为编译时替换(compile-time substitution)。在运行程序时,程序中所有的替换均已完成)。通常,这样定义的常量也称为明示常量(manifest constant)
请注意格式,首先是#define,接着是符号常量名(TAXRATE),然后是符号常量的值(0.015)(注意,其中并没有=符号)。所以,其通用格式如下:
#define NAME value
实际应用时,用选定的符号常量名和合适的值来替换NAME和value。注意,末尾不用加分号,因为这是一种由预处理器处理的替换机制。为什么TAXRATE 要用大写?用大写表示符号常量是 C 语言一贯的传统。这样,在程序中看到全大写的名称就立刻明白这是一个符号常量,而非变量。大写常量只是为了提高程序的可读性,即使全用小写来表示符号常量,程序也能照常运行。尽管如此,初学者还是应该养成大写常量的好习惯。另外,还有一个不常用的命名约定,即在名称前带c_或k_前缀来表示常量(如,c_level或k_line)。符号常量的命名规则与变量相同。可以使用大小写字母、数字和下划线字符,首字符不能为数字。
来个程序:
/************************************************************************功能:演示常量使用************************************************************************/#include <stdio.h>#define PI 3.14159//定义常量int main(void){float area, circum, radius;printf("你的圆饼半径是多少?\n");scanf("%f", &radius);//输入半径area = PI * radius * radius;//计算面积circum = 2.0 * PI *radius;//计算周长printf("你的圆饼参数是:\n");printf("circumference = %1.2f, area = %1.2f\n", circum, area);system("pause");return 0;}
printf()语句中的%1.2f表明,结果被四舍五入为两位小数输出。下面是一个输出示例:
你的圆饼半径是多少?2你的圆饼参数是:circumference = 12.57, area = 12.57请按任意键继续. . .
#define指令还可定义字符和字符串常量。前者使用单引号,后者使用双引号。如下所示:
#define BEEP '\a'#define TEE 'T'#define ESC '\033'#define OOPS "Now you have done it!"
记住,符号常量名后面的内容被用来替换符号常量。不要犯这样的常见错误:
/* 错误的格式 */#define TOES = 20如果这样做,替换TOES的是= 20,而不是20。这种情况下,下面的语句:digits = fingers + TOES;将被转换成错误的语句:digits = fingers + = 20;