Learning C language notes: strings and formatted input/output

Learning content:
1. Function - strlen();
2. Keyword - const;
3. String;
4. How to create and store a string;
5. How to use the strlen() function to get the length of a string;
6 . Create symbolic constants with the C preprocessor directive #define and ANSIC's const modifier.

You can write personalized programs by interacting with programs and using strings. This time, you will learn two input/output functions of C language: scanf() and printf(). Learn to use these two functions, not only to interact with the user, but also to format the output according to personal preferences and task requirements. In addition, learn a very important tool - C preprocessing instructions, and learn how to define and use symbolic constants.

1. Start with a simple example C program

The program talkback.c simply interacts with the user. In order to make the program flexible and diverse, a new comment style is used in the code.

#include <stdio.h>
#include <string.h>     //提供strlen()函数的原型
#define DENSITY 62.4        //人体密度(单位:磅/立方英尺)
int main()
{
    float weight, volume;
    int size, letters;
    char name[40];      //name是一个可容纳40个字符的数组

    printf("Hi! What's your first name?\n");
    scanf("%s", name);
    printf("%s, what's your weight in pounds?\n",name);
    scanf("%f", &weight);
    size = sizeof(name);
    letters = strlen(name);
    volume = weight / DENSITY;
    printf("Well, %s, your volume is %2.2f cubic feet.\n", name, volume);
    printf("Also, your first name has %d letters,\n", letters);
    printf("and we have %d bytes to store it.\n",size);

    return 0;
}

The program contains the following new features:

  1. Use an array to store strings. In this program, the name entered by the user is stored in an array, which occupies 40 consecutive bytes in memory, and each byte stores a character value.

  1. Use the %s conversion specification to handle string input and output. Note that in scanf(), name does not have & prefix, but weight does.

  1. Define the character constant DENSITY to 62.4 with the C preprocessor.

  1. Use the C function strlen() to get the length of a string.

C's I/O looks a bit more complicated than BASIC's I/O. but. Complexity is exchanged for high efficiency and convenient control input/output of the program. And, once you get used to it, it's easy.

2. Introduction to strings

A string is a sequence of one or more characters, as follows:

"Zing went the strings of my heart!"

Double quotes are not part of the string. Double quotes only tell the compiler that it is enclosing a string, just as single quotes are used to identify a single character.

2.1 char type array and null character

There is no variable type specially used to store strings in C language, and strings are stored in arrays of char type. The array is composed of continuous storage units. The characters in the string are stored in adjacent storage units, and each unit stores a character, as shown in the following figure:

The character \0 at the end of the array in the figure above. This is the space character, which is used by the C language to mark the end of the string. The null character is not the number 0, it is a non-printing character, and its ASCII code value is 0. The string in C must end with a null character, which means that the capacity of the array must be at least 1 more than the number of characters in the string to be stored. Therefore , There are 40 storage unit strings in the talkback.c program, only 39 characters can be stored, and one byte is left for the null character.

So, what is an array? You can think of an array as a row of multiple storage units. More formally, an array is an ordered sequence of data elements of the same type . In the above program, an array containing 40 storage units is created by the following declaration, and each unit stores a value of type char:

char name[40];

The square brackets after the name indicate that this is an array, and the 40 in the square brackets indicates the number of elements in the array. char indicates the type of each element, as shown in the figure:

Strings look complicated! You must create an array first, put the characters in the string into the array one by one, and remember to add a \0 at the end.

2.2 Working with strings

praise1.c

#include <stdio.h>
#define PRAISE "You are an extraordinary being."
int main()
{
    char name[40];

    printf("What's your name? ");
    scanf("%s", name);
    printf("Hello, %s. %s\n", name, PRAISE);

    return 0;
}

%s tells printf() to print a string. %s appears twice because the program prints two strings: one stored in the name array; one represented by PRAISE.

You don't have to put the null character at the end of the string yourself, scanf() does the job when it reads the input. There is also no need to add a null character at the end of the string constant PRAISE. The #define directive will be explained later, but now understand that the text enclosed in double quotes after PRAISE is a string. The compiler will append a null character at the end.

注意 (这很重要), scanf()只读取了Angela Plains中的Angela, 它在遇到第1个空白 (空格、制表符或换行符) 时就不再读取输入。因此,scanf()在读到Angela和Plains之间的空格时就停止了。一般而言, 根据%s转换说明, scanf()只会读取字符串中的一个单词, 而不是一整句。C语言还有其他的输入函数 (如, fgets()),用于读取一般字符串。

字符串和字符

字符串常量"x"和字符常量'x'不同。

区别之一在于'x'是基本类型(char),而"x"是派生类型(char数组);

区别至二是"x"实际上由两个字符组成:'x'和空字符\0,见下图:

2.3 strlen()函数

sizeof运算符以字节为单位给出对象的大小。strlen()函数给出字符串中的字符长度。因为1字节存储一个字符,也许会认为这两种方法应用于字符串得到的结果相同,但事实并非如此。

//如果编译器不识别%zd,尝试换成%u或%lu
#include <stdio.h>
#include <string.h>
#define PRAISE "You are an extraordinary being."
int main()
{
    char name[40];

    printf("What's your name? ");
    scanf("%s", name);
    printf("Hello, %s. %s\n", name, PRAISE);
    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);
    return 0;
}

string.h头文件包含多个与字符串相关的函数原型,包括strlen()。一般而言,C把函数库中相关的函数归为一类,并为每类函数提供一个头文件。例如,printf()和scanf()都隶属标准输入和输出函数,使用stdio.h头文件。string.h头文件中包含了strlen()函数和其他一些与字符串相关的函数(如拷贝字符串的函数和字符串查找函数)。

从输出结果可以看出,sizeof运算符报告,name数组有40个存储单元。但是,只有前6个单元用来存储xiaogu,所以strlen()函数得出的结果是6.name数组的第7个单元存储空字符,strlen()并未将其计入。如下图:

对于PRAISE,用strlen()得出的也是字符串中的字符数(包括空格和标点符号)。然而,sizeof运算符给出的数更大,因为它把字符串末尾不可见的空字符也计算在内。该程序并未明确告诉计算机字符串预留多少个空间,所以它必须计算双引号内的字节数。

这里sizeof没有使用圆括号,何时使用圆括号取决于运算对象是类型还是特定量。运算对象是类型时,圆括号必不可少,但是对于特定量,圆括号可有可无。也就是说,对于类型,应写成sizeof(char)或sizeof(float);对于特定量,可写成sizeof name 或sizeof 3.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预处理器。预处理器可用来定义常量。只需在程序顶部添加下面一行:

#define TAXRATE 0.015

编译程序时,程序中所有的TAXRATE都会被替换成0.015.这一过程被称为编译时替换。在运行程序时,程序中所有的替换均已完成。通常,这样定义的常量也称为明示常量。

请注意格式, 首先是#define, 接着是符号常量名(TAXRATE), 然后是符号常量的值 (0.015) (注意, 其中并没有=符号)。所以, 其通用格式 如下:

#define NAME value

实际应用时, 用选定的符号常量名和合适的值来替换NAME和value。注意, 末尾不用加分号, 因为这是一种由预处理器处理的替换机制。为什么 TAXRATE 要用大写? 用大写表示符号常量是 C 语言一贯的传统。这样, 在程序中看到全大写的名称就立刻明白这是一个符号常量, 而非变量。大写常量只是为了提高程序的可读性, 即使全用小写来表示符号常量, 程序也能照常运行。

另外, 还有一个不常用的命名约定, 即在名称前带_c或_k前缀来表示常量(如, c_level或k_line)。

符号常量的命名规则与变量相同。可以使用大小写字母、数字和下划线字符, 首字符不能为数字。程序pizza.c演示了一个简单的示例。

#include <stdio.h>
#define PI 3.14159
int main()
{
    float area, circum, radius;
    printf("What is the radius of your pizza?\n");
    scanf("%f", &radius);
    area = PI * radius * radius;
    circum = 2.0 * PI * radius;
    printf("Your basic pizza parameters are as follows:\n");
    printf("circumference = %1.2f, area = %1.2f\n", circum, area);

    return 0;
}

printf()语句中的%1.2f表明,结果被四舍五入为两位小数输出。

#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;

3.1 const限定符

C90标准新增了const关键字,用于限定一个变量为只读。其声明如下:

const int MONTHS = 12; // MONTHS在程序中不可更改,值为12

这使得MONTHS成为一个只读值。也就是说,可以在计算中使用MONTHS,可以打印MONTHS,但是不能更改MONTHS的值。const用起来比#define更灵活。

3.2 明示常量

C头文件limits.h和float.h分别提供了与整数类型和浮点类型大小限制相关的详细信息。每个头文件都定义了一系列供实现使用的明示常量。例如,limits.h头文件包含以下类似的代码:

#define INT_MAX +32767
#define INT_MIN -32768

这些明示常量代表int类型可表示的最大值和最小值。如果系统使用32位的int,该头文件会为这些明示常量提供不同的值。如果在程序中包含limits.h头文件,就可编写下面的代码:

printf("Maxium int value on this system = %d\n", INT_MAX);

如果系统使用4字节的int,limits.h头文件会提供符合4字节int的INT_MAX和INTMIN。

如下列出了一些limits.h中能找到的明示常量。

类似地,float.h头文件中也定义一些明示常量,如FLT_DIG和DBL_DIG,分别表示float类型和double类型的有效数字位数。下图中列出了float.h中的一些明示常量(可以使用文本编辑器打开并查看系统使用的float.h头文件)。表中所列都与float类型相关。把明示常量名中的FLT分别替换成DBL和LDBL,即可分别表示double和long double类型对应的明示常量(表中假设系统使用2的幂来表示浮点数)。

程序defines.c程序

//使用limits.h和float头文件中定义的明示常量
#include <stdio.h>
#include <limits.h>
#include <float.h>
int main(void)
{
    printf("Some number limits for this system:\n");
    printf("Biggest int: %d\n", INT_MAX);
    printf("Smallest long long: %lld\n", LLONG_MIN);
    printf("One byte = %d bits on this system.\n",CHAR_BIT);
    printf("Largest double: %e\n", DBL_MAX);
    printf("Smallest normal float: %e\n", FLT_MIN);
    printf("float precision = %d digits\n", FLT_DIG);
    printf("float epsilon = %e\n", FLT_EPSILON);

    return 0;
}

4 printf()和scanf()

printf()函数和scanf()函数能让用户可以与程序交流,它们是输出/输入函数,或简称为I/O函数。它们不仅是C语言中的I/O函数,而且是最多才多艺的函数。虽然printf()函数是输出函数,scanf()是输入函数,但是它们的工作原理几乎相同。两个函数都使用格式字符串和参数列表。

4.1 printf()函数

请求printf()函数打印数据的指令要与待打印数据的类型相匹配。例如,打印整数时使用%d,打印字符时使用%c。这些符号被称为转换说明(conversion specification),它们指定了如何把数据转换成可显示的形式。我们先列出ANSI C标准为printf()提供的转换说明,然后再示范如何使用一些较常见的转换说明。下表列出了一些转换说明和各自对应的输出类型。

4.2 使用printf()函数

看一下下面的程序printout.c:

#include <stdio.h>
#define PI 3.141593
int main(void)
{
    int number = 7;
    float pies = 12.75;
    int cost = 7800;

    printf("The %d contestants ate %f berry pies.\n", number, pies);
    printf("The value of pi is %f.\n", PI);
    printf("Farewell! thou art too dear for my possessing,\n");
    printf("%c%d\n", '$', 2 * cost);

    return 0;
}

输出如下:

这是printf()函数的格式:

printf(格式字符串, 待打印项1, 带打印项2,...);

待打印项1、待打印项2等都是要打印的项。它们可以是变量、常量,甚至是在打印之前先要计算的表达式。格式字符串应包含每个待打印项对应的转换说明。例如,下面的语句:

printf("The %d contestants ate %f berry pies.\n", number,pies);

格式字符串是双引号括起来的内容。上面语句的格式字符串包含了两个待打印项number和pies对应的两个转换说明。如图演示了printf()语句的另一个例子:

程序printout.c中的一行:

printf("The value of pi is %f.\n", PI);

该语句中,待打印项列表只有一个项——符号常量PI。

如图,格式字符串包含两种形式不同的信息:

实际要打印的字符;

转换说明。

格式字符串中的转换说明一定要与后面的每个项相匹配,若忘记这个基本要求会导致严重的后果。千万别写成下面这样:

printf("The score was Squids %d, Slugs %d.\n", score1);

这里,第2个%d没有对应任何项。系统不同,导致的结果也不同。不过,出现这种问题最好的状况是得到无意义的值。

如果只打印短语或句子,就不需要使用任何转换说明。如果只打印数据,也不用加入说明文字。程序printout.c中的最后两个printf()语句都没问题:

printf("Farewell! thou art too dear for my possessing,\n");

printf("%c%d\n", '$', 2 * cost);

注意第2条语句,待打印列表的第1个项是一个字符常量,不是变量;第2个项是一个乘法表达式。这说明printf()使用的是值,无论是变量、常量还是表达式的值。

由于 printf()函数使用%符号来标识转换说明,因此打印%符号就成了个问题。如果单独使用一个%符号,编译器会认为漏掉了一个转换字符。解决方法很简单,使用两个%符号就行了:

pc = 2*6;

printf("Only %d%% of Sally's gribbles were edible.\n", pc);

下面是输出结果:

Only 12% of Sally's gribbles were edible.

4.3 printf()的转换说明修饰符

在%和转换字符之间插入修饰符可修饰基本的转换说明。表4.3.1和表4.3.2列出可作为修饰符的合法字符。如果要插入多个字符,其书写顺序应该与表4.3.1中列出的顺序相同。不是所有的组合都可行。表中有些字符是C99新增的,如果编译器不支持C99,则可能不支持表中的所有项。

表4.3.1

注意 类型可移植性

sizeof 运算符以字节为单位返回类型或值的大小。这应该是某种形式的整数,但是标准只规定了该值是无符号整数。在不同的实现中,它可以是unsigned int、unsigned long甚至是unsigned long long。因此,如果要用printf()函数显示sizeof表达式,根据不同系统,可能使用%u、%lu或%llu。这意味着要查找你当前系统的用法,如果把程序移植到不同的系统还要进行修改。

鉴于此, C提供了可移植性更好的类型。首先,stddef.h头文件(在包含stdio.h头文件时已包含其中)把size_t定义成系统使用sizeof返回的类型,这被称为底层类型(underlying type)。其次,printf()使用z修饰符表示打印相应的类型。同样,C还定义了ptrdiff_t类型和t修饰符来表示系统使用的两个地址差值的底层有符号整数类型。

注意 float参数的转换

对于浮点类型,有用于double和long double类型的转换说明,却没有float类型的。这是因为在K&R C中,表达式或参数中的float类型值会被自动转换成double类型。一般而言,ANSI C不会把float自动转换成double。然而,为保护大量假设float类型的参数被自动转换成double的现有程序,

printf()函数中所有float类型的参数(对未使用显式原型的所有C函数都有效)仍自动转换成double类型。因此,无论是K&R C还是ANSI C,都没有显示float类型值专用的转换说明。

表4.3.2

4.4 scanf()函数

C库包含了多个输入函数,scanf()是最通用的一个,因为它可以读取不同格式的数据。当然,从键盘输入的都是文本,因为键盘只能生成文本字符:字母、数字和标点符号。如果要输入整数2014,就要键入字符2、0、1、4.如果要将其存储为数值而不是字符串,程序就必须把字符依次转换成数值,这就是scanf()要做的。scanf()把输入的字符串转换成整数、浮点数、字符或字符串,而printf()正好与其相反,把整数、浮点数、字符和字符串转换成显示在屏幕上的文本。

scanf()和printf()类似,也使用格式化字符串和参数列表。scanf()中的格式字符串表明字符输入流的目标数据类型。两个函数主要的区别在参数列表中。printf()函数使用变量、常量和表达式,而scanf()函数使用指向变量的指针。注意,要记住下面两条简单的规则:

  1. 如果用scanf()读取基本变量类型的值,在变量名前加上一个&;

  1. 如果用scanf()把字符串读入字符数组中,不要使用&。

看下程序 input.c

#include <stdio.h>
int main(void)
{
    int age;
    float assets;
    char pet[30];

    printf("Enter your age, assets, and favorite pet.\n");
    scanf("%d %f", &age, &assets);  //这里要使用&
    scanf("%s", pet);               //字符数组不使用&
    printf("%d $%.2f %s\n", age, assets, pet);

    return 0;
}

下面是该程序与用户交互的示例:

Enter your age, assets, and favorite pet.
38
92360.88 llama
38 $92360.88 llama

scanf()函数使用空白(换行符、制表符和空格)把输入分成多个字段。在依次把转换说明和字段匹配时跳过空白。注意,上面示例的输入项(粗体部分是用户的输入)分成了两行。只要在每个输入项之间输入至少一个换行符、空格或制表符即可,可以在一行或多行输入:

Enter your age, assets, and favorite pet.
42
2121.45
guppy
42 $2121.45 guppy

唯一例外的是%c转换说明。根据%c,scanf()会读取每个字符,包括空白。我们稍后详述这部分。

scanf()函数所用的转换说明与printf()函数几乎相同。主要的区别是,对于float类型和double类型,printf()都使用%f、%e、%E、%g和%G转换说明。而scanf()只把它们用于float类型,对于double类型时要使用l修饰符。表4.4.1列出了C99标准中常用的转换说明。

表4.4.1

可以在上表中的转换说明中(百分号和转换字符之间)使用修饰符。如果要使用多个修饰符,必须按下表所列的顺序书写。

如你所见,使用转换说明比较复杂,而且这些表中还省略了一些特性。省略的主要特性是,从高度格式化源中读取选定数据,如穿孔卡或其他数据记录。

1.scanf()角度看输入

接下来,我们更详细地研究scanf()怎样读取输入。假设scanf()根据一个%d转换说明读取一个整数。scanf()函数每次读取一个字符,跳过所有的空白字符,直至遇到第1个非空白字符才开始读取。因为要读取整数,所以scanf()希望发现一个数字字符或者一个符号(+或-)。如果找到一个数字或符号,它便保存该字符,并读取下一个字符。如果下一个字符是数字,它便保存该数字并读取下一个字符。scanf()不断地读取和保存字符,直至遇到非数字字符。如果遇到一个非数字字符,它便认为读到了整数的末尾。然后,scanf()把非数字字符放回输入。这意味着程序在下一次读取输入时,首先读到的是上一次读取丢弃的非数字字符。最后,scanf()计算已读取数字(可能还有符号)相应的数值,并将计算后的值放入指定的变量中。

如果使用字段宽度,scanf()会在字段结尾或第1个空白字符处停止读取(满足两个条件之一便停止)。

如果第1个非空白字符是A而不是数字,会发生什么情况?scanf()将停在那里,并把A放回输入中,不会把值赋给指定变量。程序在下一次读取输入时,首先读到的字符是A。如果程序只使用%d转换说明, scanf()就一直无法越过A读下一个字符。另外,如果使用带多个转换说明的scanf(),C规定在第1个出错处停止读取输入。

用其他数值匹配的转换说明读取输入和用%d 的情况相同。区别在于scanf()会把更多字符识别成数字的一部分。例如,%x转换说明要求scanf()识别十六进制数a~f和A~F。浮点转换说明要求scanf()识别小数点、e记数法(指数记数法)和新增的p记数法(十六进制指数记数法)。

如果使用%s 转换说明,scanf()会读取除空白以外的所有字符。scanf()跳过空白开始读取第 1 个非空白字符,并保存非空白字符直到再次遇到空白。这意味着 scanf()根据%s 转换说明读取一个单词,即不包含空白字符的字符串。如果使用字段宽度,scanf()在字段末尾或第1个空白字符处停止读取。无法利用字段宽度让只有一个%s的scanf()读取多个单词。最后要注意一点:当scanf()把字符串放进指定数组中时,它会在字符序列的末尾加上'\0',让数组中的内容成为一个C字符串。

实际上,在C语言中scanf()并不是最常用的输入函数。这里重点介绍它是因为它能读取不同类型的数据。C 语言还有其他的输入函数,如 getchar()和 fgets()。这两个函数更适合处理一些特殊情况,如读取单个字符或包含空格的字符串。目前,无论程序中需要读取整数、小数、字符还是字符串,都可以使用scanf()函数。

2.格式字符串中的普通字符

scanf()函数允许把普通字符放在格式字符串中。除空格字符外的普通字符必须与输入字符串严格匹配。例如,假设在两个转换说明中添加一个逗号:

scanf("%d,%d", &n, &m);

scanf()函数将其解释成:用户将输入一个数字、一个逗号,然后再输入一个数字。也就是说,用户必须像下面这样进行输入两个整数:

88,121

由于格式字符串中,%d后面紧跟逗号,所以必须在输入88后再输入一个逗号。但是,由于scanf()会跳过整数前面的空白,所以下面两种输入方式都可以:

88, 121

88,

121

格式字符串中的空白意味着跳过下一个输入项前面的所有空白。例如,对于下面的语句:

scanf("%d ,%d", &n, &m);

以下的输入格式都没问题:

88,121
88 ,121
88 , 121

请注意,“所有空白”的概念包括没有空格的特殊情况。

除了%c,其他转换说明都会自动跳过待输入值前面所有的空白。因此,scanf("%d%d", &n, &m)与scanf("%d %d", &n, &m)的行为相同。对于%c,在格式字符串中添加一个空格字符会有所不同。例如,如果把%c放在格式字符串中的空格前面,scanf()便会跳过空格,从第1个非空白字符开始读取。也就是说,scanf("%c", &ch)从输入中的第1个字符开始读取,而scanf(" %c", &ch)则从第1个非空白字符开始读取。

3.scanf()的返回值

scanf()函数返回成功读取的项数。如果没有读取任何项,且需要读取一个数字而用户却输入一个非数值字符串,scanf()便返回0。当scanf()检测到“文件结尾”时,会返回EOF(EOF是stdio.h中定义的特殊值,通常用#define指令把EOF定义为-1)。之后再讨论文件结尾的相关内容以及如何利用scanf()的返回值。在读者学会if语句和while语句后,便可使用scanf()的返回值来检测和处理不匹配的输入。

Guess you like

Origin blog.csdn.net/weixin_51995147/article/details/128539192