C Primer Plus 第6版中文版 4~7章

第4章 字符串和格式化输入/输出

本章重点介绍输入和输出。 与程序交互和使用字符串可以编写个性化的程序, 本章将详细介绍 C 语言的两个输入/ 输出函数: scanf() 和 printf()。 学会使用这两个函数, 不仅能与用户交互, 还可根据个人喜好和任务要求格式化输出。

// talkback.c -- nosy, informative program
#include <stdio.h>
#include <string.h>      // for strlen() prototype
#define DENSITY 62.4     // human density in lbs per cu ft ,人体密度( 单位: 磅 / 立方英尺)

int main()
{
    float weight, volume;
    int size, letters;
    char name[40];        // name is an array of 40 chars

    printf("Hi! What's your first name?\n");
    scanf("%s", name);   // 字符串, 并没有像数值变量那样 &variableName 取地址
    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;
}

/*
Hi! What's your first name?
FirstName
FirstName, what's your weight in pounds?
150
Well, FirstName, your volume is 2.40 cubic feet.
Also, your first name has 9 letters,
and we have 40 bytes to store it.
*/

该程序包含以下新特性。

  • 用数组 (array )存储字符串 (character string )。 在该程序中, 用户输入的名被存储在数组中, 该数组占用内存中 40 个连续的字节, 每个字节存储一个字符值。 数组并没有用完.
  • 使用% s 转换说明来处理字符串的输入和输出。
  • 注意, 在 scanf() 中, name 没有& 前缀, 而 weight 有( 稍后解释,& weight 和 name 都是地址)。
  • 用 C 预处理器把字符常量 DENSITY 定义为 62.4 。
  • 用 C 函数 strlen() 获取字符串的长度。

字符串:

  • 字符串 (character string )是一个或多个字符的序列,
  • 双引号不是字符串的一部分。 双引号仅告知编译器它括起来的是字符串, 正如单引号用于标识单个字符一样。
  • C 语言没有专门用于存储字符串的变量类型, 字符串都被存储在 char 类型的数组中。 数组由连续的存储单元组成, 字符串中的字符被存储在相邻的存储单元中, 每个单元存储一个字符
  • 数组末尾位置的字符 \0 (\ 后是数字0) 。这是空字符 (null character ), C 语言用它标记字符串的结束。
  • 空字符不是数字 0, 它是非打印字符, 其 ASCII 码值是( 或等价于) 0。
  • C 中的字符串一定以空字符结束, 这意味着数组的容量必须至少比待存储字符串中的字符数多 1。 因此, 有 40 个存储单元的字符串, 只能存储 39 个字符, 剩下一个字节留给空字符。
  • 可以把数组看作是一行连续的多个存储单元。 用更正式的说法是, 数组是同类型数据元素的有序序列。
  • 通过以下声明 char name[ 40]; 创建了一个包含 40 个存储单元( 或元素) 的数组, 每个单元存储一个 char 类型的值
  • 字符串看上去比较复杂! 必须先创建一个数组, 把字符串中的字符逐个放入数组, 还要记得在末尾加上一个 \0 。还好, 计算机可以自己处理这些细节。编译器会在末尾加上空字符。

scanf() 在遇到第 1 个空白( 空格、 制表符或换行符) 时就不再读取输入。

#include <stdio.h>
#define PRAISE "You are an extraordinary being."

int main(void)
{
    char name[40];

    printf("What's your name? ");
    // 你不用亲自把空字符放入字符串末尾, scanf() 在读取输入时就已完成这项工作。
    // scanf() 只读取了 Angela Plains 中的 Angela ,它在遇到第 1 个空白( 空格、 制表符或换行符) 时就不再读取输入。
    // 因此, scanf() 在读到 Angela 和 Plains 之间的空格时就停止了。
    scanf("%s", name);
    printf("Hello, %s. %s\n", name, PRAISE); //% s 告诉 printf() 打印一个字符串。

    return 0;
}


/*
What's your name? Angela Kitty
Hello, Angela. You are an extraordinary being.
*/

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

  • 区别之一在于' x' 是基本类型( char ), 而" x" 是派生类型( char 数组);
  • 区别之二是" x" 实际上由两个字符组成:' x' 和空字符\0

strlen() 函数和 sizeof() 函数

  • sizeof 运算符, 它以字节为单位给出对象的大小。字符数组是一个对象
  • strlen() 函数给出字符串中的字符长度, 但不包括表示字符串结束的空字符.
  • C99 和 C11 标准专门为 sizeof 运算符的返回类型添加了% zd 转换说明, 这对于 strlen() 同样适用。
  • 上一章的 sizeof 使用了圆括号, 但本例没有。
  • 何时使用圆括号取决于运算对象是类型还是特定量。
  • 运算对象是类型时, 圆括号必不可少, 但是对于特定量, 圆括号可有可无。
  • 也就是说, 对于类型, 应写成 sizeof( char) 或 sizeof( float) ;对于特定量, 可写成 sizeof name 或 sizeof 6.28 。
  • 尽管如此, 还是建议所有情况下都使用圆括号, 如 sizeof( 6.28) 。

一般而言, C 把函数库中相关的函数归为一类, 并为每类函数提供一个头文件:

  • 例如, printf() 和 scanf() 都隶属标准输入和输出函数, 使用 stdio.h 头文件。
  • string.h 头文件中包含了 strlen() 函数和其他一些与字符串相关的函数( 如拷贝字符串的函数和字符串查找函数)。

预处理器也可用来定义常量。 只需在程序顶部添加下面一行:

#define TAXRATE 0.015

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

常量命名约定:

  • 大写常量只是为了提高程序的可读性, 即使全用小写来表示符号常量, 程序也能照常运行。
  • 还有一个不常用的命名约定, 即在名称前带 c 或 k 前缀来表示常量( 如, c_level 或 k_line )。
  • #define 指令还可定义字符和字符串常量。 前者使用单引号, 后者使用双引号。

C90 标准新增了 const 关键字, 用于限定一个变量为只读,const 用起来比# define 更灵活,

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

明示常量: limits.h 和 float.h

C 头文件 limits.h 和 float.h 分别提供了与整数类型和浮点类型大小限制相关的详细信息。

每个头文件都定义了一系列供实现使用的明示常量 。例如, limits.h 头文件包含以下类似的代码:

#define INT_MAX + 32767 
#define INT_MIN -32768

这些明示常量代表 int 类型可表示的最大值和最小值。不同系统的头文件内容有差别.

类似地, float.h 头文件中也定义一些明示常量, 如 FLT_DIG 和 DBL_DIG ,分别表示 float 类型和 double 类型的有效数字位数。

printf() 和 scanf()

虽然 printf() 是输出函数, scanf() 是输入函数, 但是它们的工作原理几乎相同。 两个函数都使用格式字符串和参数列表。

1.printf() 函数

请求 printf() 函数打印数据的指令要与待打印数据的类型相匹配。 例如, 打印整数时使用% d ,打印字符时使用% c 。这些符号被称为转换说明 ( conversion specification ), 它们指定了如何把数据转换成可显示的形式。

转换说明 输出
%a 浮点数、 十六进制数和 p 记数法( C99/ C11)
%A 浮点数、 十六进制数和 p 记数法( C99/ C11)
%c 单个字符
%d 有符号十进制整数
%e 浮点数, e 记数法
%E 浮点数, e 记数法
%f 浮点数, 十进制记数法
%g 根据值的不同, 自动选择% f 或% e 。% e 格式用于指数小于-4 或者大于或等于精度时
%G 根据值的不同, 自动选择% f 或% E 。% E 格式用于指数小于-4 或者大于或等于精度时
%i 有符号十进制整数( 与% d 相同)
%o 无符号八进制整数
%p 指针
%s 字符串
%u 无符号十进制整数
%x 无符号十六进制整数, 使用十六进制数 0f
%X 无符号十六进制整数, 使用十六进制数 0F
%% 打印一个百分号

在% 和转换字符之间插入修饰符可修饰基本的转换说明。 下表列出可作为修饰符的合法字符。 如果要插入多个字符, 其书写顺序应该与表中列出的顺序相同。

修饰符 含义
5 种标记(- 、+ 、空格、# 和 0 ) 可以不使用这些标记或使用多个标记 :

- :待打印项左对齐。 即, 从字段的左侧开始打印该项

+ :有符号值若为正, 则在值前面显示加号; 若为负, 则在值前面显示减号示例:"% + 6.2f"

空格: 有符号值若为正, 则在值前面显示前导空格( 不显示任何符号); 若为负, 则在值前面显示减号 + 标记并覆盖空格

# :把结果转换为另一种形式。 如果是%o 格式, 则以0 开始; 如果是%x 或% X 格式, 则以 0x 或 0X 开始; 对于所有的浮点格式,# 保证了即使后面没有任何数字, 也打印一个小数点字符。 对于% g 和% G 格式,# 防止结果后面的 0 被删除示例:"%#o" 、"%#8.0f" 、"%+#10.3e"

0: 对于数值格式, 用前导 0 代替空格填充字段宽度。 对于整数格式, 如果出现- 标记或指定精度, 则忽略该标记示例:"%010d" 和"%08.3f"
数字 最小字段宽度如果该字段不能容纳待打印的数字或字符串, 系统会使用更宽的字段示例:"%4d"
.数字 精度

对于% e 、% E 和% f 转换, 表示小数点右边数字的位数

对于% g 和% G 转换, 表示有效数字最大位数

对于% s 转换, 表示待打印字符的最大数量

对于整型转换, 表示待打印数字的最小位数

如有必要, 使用前导 0 来达到这个位数

只使用. 表示其后跟随一个 0 ,所以%. f 和%. 0f 相同

示例:"% 5.2f" 打印一个浮点数, 字段宽度为 5 字符, 其中小数点后有两位数字
h 和整型转换说明一起使用, 表示 short int 或 unsigned short int 类型的值示例:"% hu" 、"% hx" 、"% 6.4hd"
hh 和整型转换说明一起使用, 表示 signed char 或 unsigned char 类型的值示例:"% hhu" 、"% hhx" 、"% 6.4hhd"
j 和整型转换说明一起使用, 表示 intmax_t 或 uintmax_t 类型的值。 这些类型定义在 stdint.h 中示例:"% jd" 、"% 8jx"
l 和整型转换说明一起使用, 表示 long int 或 unsigned long int 类型的值示例:"% ld" 、"% 8lu"
ll 和整型转换说明一起使用, 表示 long long int 或 unsigned long long int 类型的值( C99) 示例:"% lld" 、"% 8llu"
L 和浮点转换说明一起使用, 表示 long double 类型的值示例:"% Lf" 、"% 10.4Le"
t 和整型转换说明一起使用, 表示 ptrdiff_t 类型的值。 ptrdiff_t 是两个指针差值的类型( C99) 示例:"% td" 、"% 12ti"
z 和整型转换说明一起使用, 表示 size_t 类型的值。 size_t 是 sizeof 返回的类型( C99) 示例:"% zd" 、"% 12zd"

类型可移植性(用zd打印sizeof返回值): 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 参数的转换(printf 会将 float 作为 double 打印): 对于浮点类型, 有用于 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 类型值专用的转换说明。

#include <stdio.h>
#define PAGES 959
#define BLURB "Authentic imitation!"

int main(void)
{
    /* 打印宽度 */
    printf("*%d*\n", PAGES);
    printf("*%2d*\n", PAGES);
    printf("*%10d*\n", PAGES);
    printf("*%-10d*\n", PAGES);
/*
*959*
*959*
*       959*
*959       *
*/

    const double RENT = 3852.99;  // const-style constant

    printf("*%f*\n", RENT);
    printf("*%e*\n", RENT);
    printf("*%4.2f*\n", RENT);
    printf("*%3.1f*\n", RENT);
    printf("*%10.3f*\n", RENT);
    printf("*%10.3E*\n", RENT);
    printf("*%+4.2f*\n", RENT);
    printf("*%010.2f*\n", RENT);
/*
*3852.990000*
*3.852990e+03*
*3852.99*
*3853.0*
*  3852.990*
* 3.853E+03*
*+3852.99*
*0003852.99*
*/

    printf("%x\n%X\n%#x\n", 31, 31, 31);
/*
1f
1F
0x1f
*/

    printf("**%d**% d**% d**\n", 42, 42, -42); //**42** 42**-42**
    printf("**%5d \n**%5.3d \n**%05d \n**%05.3d**\n", 6, 6, 6, 6);
/*
**    6
**  006
**00006
**  006**

*/

    // #define BLURB "Authentic imitation!"
    /*
    虽然第 1 个转换说明是 % 2s ,但是字段被扩大为可容纳字符串中的所有字符。 
    精度限制了待打印字符的个数。. 5 告诉 printf() 只打印 5 个字符。 
    - 标记使得文本左对齐输出。
    */
    printf("[%2s]\n", BLURB);
    printf("[%24s]\n", BLURB);
    printf("[%24.5s]\n", BLURB);
    printf("[%-24.5s]\n", BLURB);
/*
[Authentic imitation!]
[    Authentic imitation!]
[                   Authe]
[Authe                   ]
*/

    return 0;
}

转换说明的意义

转换说明把以二进制格式存储在计算机中的值转换成一系列字符( 字符串) 以便于显示。

例如, 数字 76 在计算机内部的存储格式是二进制数 01001100 。% d 转换说明将其转换成字符 7 和 6 ,并显示为 76 ;

% x 转换说明把相同的值( 01001100 )转换成十六进制记数法 4c ;

% c 转换说明把 01001100 转换成字符 L 。

转换 ( conversion )可能会误导读者认为原始值被替换成转换后的值。 实际上, 转换说明是翻译说明,% d 的意思是“ 把给定的值翻译成十进制整数文本并打印出来”。

转换的参数如果用错导致看到的显示内容不符合预期,并不会改变原始的值.

打印较长的字符串时, 给字符串断行有 3 种方法:

#include <stdio.h>
#define PAGES 959
#define BLURB "Authentic imitation!"

int main(void)
{
    // 1. 用 \n
    printf("Here's one way to print a ");
    printf("long string.\n");


    // 2.用反斜杠(\) 和 Enter( 或 Return) 键组合来断行。 
    /*这使得光标移至下一行, 而且字符串中不会包含换行符。
        其效果是在下一行继续输出。
        但是, 下一行代码必须和程序清单中的代码一样从最左边开始。
        如果缩进该行, 比如缩进 5 个空格, 那么这 5 个空格就会成为字符串的一部分。*/
    printf("Here's another way to print a \
long string.\n");

    /*3: ANSI C 引入的字符串连接。
        在两个用双引号括起来的字符串之间用空白隔开*/

    printf("Here's the newest way to print a "
        "long string.\n");      /* ANSI C */

    return 0;
}


//Here's one way to print a long string.
//Here's another way to print a long string.
//Here's the newest way to print a long string.

2.scanf

C 库包含了多个输入函数, scanf() 是最通用的一个, 因为它可以读取不同格式的数据。

当然, 从键盘输入的都是文本, 因为键盘只能生成文本字符: 字母、 数字和标点符号。

如果要输入整数 2014 ,就要键入字符 2 、0 、1 、4 。如果要将其存储为数值而不是字符串, 程序就必须把字符依次转换成数值, 这就是 scanf() 要做的。

scanf() 把输入的字符串转换成整数、 浮点数、 字符或字符串, 而 printf() 正好与它相反, 把整数、 浮点数、 字符和字符串转换成显示在屏幕上的文本。

scanf() 和 printf() 类似, 也使用格式字符串和参数列表。 scanf() 中的格式字符串表明字符输入流的目标数据类型。

两个函数主要的区别在参数列表中。 printf() 函数使用变量、 常量和表达式, 而 scanf() 函数使用指向变量的指针。

两条简单的规则:

  • 如果用 scanf() 读取基本变量类型的值, 在变量名前加上一个& ;
  • 如果用 scanf() 把字符串读入字符数组中, 不要使用& 。

scanf() 函数使用空白( 换行符、 制表符和空格) 把输入分成多个字段。 在依次把转换说明和字段匹配时跳过空白。唯一例外的是%c 转换说明。 根据% c ,scanf() 会读取每个字符, 包括空白。

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

c语言中的 printf 和 scanf 太繁琐了.

第5章 运算符、表达式和语句

赋值运算符左侧必须引用一个存储位置。 最简单的方法就是使用变量名。

赋值表达式语句的目的是把值存储到内存位置上。 用于存储值的数据存储区域统称为数据对象 (data object )。 C 标准只有在提到这个概念时才会用到对象这个术语。 使用变量名是标识对象的一种方法。 除此之外, 还有其他方法, 但是要在后面的章节中才学到。 例如, 可以指定数组的元素、 结构的成员, 或者使用指针表达式( 指针中存储的是它所指向对象的地址)。 左值 (lvalue )是 C 语言的术语, 用于标识特定数据对象的名称或表达式。 因此, 对象指的是实际的数据存储, 而左值是用于标识或定位存储位置的标签。

整数除法和浮点数除法不同。 浮点数除法的结果是浮点数, 而整数除法的结果是整数。 整数是没有小数部分的数。 这使得 5 除以 3 很让人头痛, 因为实际结果有小数部分。 在 C 语言中, 整数除法结果的小数部分被丢弃, 这一过程被称为截断 (truncation )。C99 规定使用趋零截断。 所以, 应把-3.8 转换成-3 。

整数除法会截断计算结果的小数部分( 丢弃整个小数部分), 不会四舍五入结果。

混合整数和浮点数计算的结果是浮点数。 实际上, 计算机不能真正用浮点数除以整数, 编译器会把两个运算对象转换成相同的类型。 整数和浮点数在进行除法运算前, 整数会被转换成浮点数。

sizeof 运算符和 size_t 类型

sizeof 运算符以字节为单位返回运算对象的大小, 运算对象可以是具体的数据对象( 如, 变量名) 或类型。 如果运算对象是类型( 如, float ), 则必须用圆括号将其括起来。

C 语言规定, sizeof 返回 size_t 类型的值。 这是一个无符号整数类型, 但它不是新类型。 前面介绍过, size_t 是语言定义的标准类型。

C 有一个 typedef 机制, 允许程序员为现有类型创建别名: typedef double real; , real 就是 double 的别名。

类似地, C 头文件系统可以使用 typedef 把 size_t 作为 unsigned int 或 unsigned long 的别名。 这样, 在使用 size_t 类型时, 编译器会根据不同的系统替换标准类型。

C99 做了进一步调整, 新增了% zd 转换说明用于 printf() 显示 size_t 类型的值。 如果系统不支持% zd ,可使用% u 或% lu 代替% zd 。

求模运算符 (modulus operator )用于整数运算。 求模运算符给出其左侧整数除以右侧整数的余数 (remainder )。 例如, 13 % 5 (读作“ 13 求模 5 ”) 得 3 ,因为 13 比 5 的两倍多 3 ,即 13 除以 5 的余数是 3 。求模运算符只能用于整数, 不能用于浮点数。

求模运算符常用于控制程序流。 例如, 假设你正在设计一个账单预算程序, 每 3 个月要加进一笔额外的费用。 这种情况可以在程序中对月份求模 3( 即, month % 3 ), 并检查结果是否为 0。 如果为 0, 便加进额外的费用。

负数求模如何进行? C99 规定“ 趋零截断” : 如果第 1 个运算对象是负数, 那么求模的结果为负数; 如果第 1 个运算对象是正数, 那么求模的结果也是正数.

递增运算符: + + 递增运算符 (increment operator )执行简单的任务, 将其运算对象递增 1。 该运算符以两种方式出现。 第 1 种方式, + + 出现在其作用的变量前面, 这是前缀模式; 第 2 种方式, + + 出现在其作用的变量后面, 这是后缀模式。 两种模式的区别在于递增行为发生的时间不同。

强制类型转换: (type)value

实参和形参

在英文中, argument 和 parameter 经常可以互换使用, 但是 C99 标准规定了: 对于 actual argument 或 actual parameter 使用术语 argument( 译为实参); 对于 formal argument 或 formal parameter 使用术语 parameter( 译为形参)。

变量名是函数私有的, 即在函数中定义的变量名不会和别处的相同名称发生冲突。

定义函数时的变量名是形式参数, 调用函数时传递给函数的变量是实际参数.

第6章 C控制语句:循环

对于计算机科学( 是研究计算机, 不是用计算机做研究) 而言, 一门语言应该提供以下 3 种形式的程序流:

  • 执行语句序列;
  • 如果满足某些条件就重复执行语句序列( 循环);
  • 通过测试选择执行哪一个语句序列( 分支)。

对很多事情的执行的规划,也可以考虑这三种方式: 按部就班执行, 迭代优化, 关键点决策.

什么是真: 这是一个古老的问题, 但是对 C 而言还不算难。 在 C 中, 表达式一定有一个值, 关系表达式也不例外。

  • 对 C 而言, 表达式为真的值是 1, 表达式为假的值是 0
  • 在 C 中, 一般而言, 所有的非零值都视为真, 只有 0 被视为假。
  • _Bool 是 C 语言中布尔变量的类型名。_Bool 类型的变量只能存储 1 (真) 或 0 (假)。 如果把其他非零数值赋给_Bool 类型的变量, 该变量会被设置为 1 。这反映了 C 把所有的非零值都视为真。
  • C99 提供了 stdbool.h 头文件, 该头文件让 bool 成为_Bool 的别名, 而且还把 true 和 false 分别定义为 1 和 0 的符号常量。 包含该头文件后, 写出的代码可以与 C + + 兼容, 因为 C + + 把 bool 、true 和 false 定义为关键字。

for 循环把 3 个行为( 初始化、 测试和更新) 组合在一处。关键字 for 后面的圆括号中有 3 个表达式, 分别用两个分号隔开。 第 1 个表达式是初始化, 只会在 for 循环开始时执行一次。 第 2 个表达式是测试条件, 在执行循环之前对表达式求值。 如果表达式为假( 本例中, count 大于 NUMBER 时), 循环结束。 第 3 个表达式执行更新, 在每次循环结束时求值。

逗号运算符并不局限于在 for 循环中使用, 但是这是它最常用的地方。 逗号运算符有两个其他性质。 首先, 它保证了被它分隔的表达式从左往右求值( 换言之, 逗号是一个序列点, 所以逗号左侧项的所有副作用都在程序执行逗号右侧项之前发生)。

#include <stdio.h>

int main(void)
{
    const int END = 10;
    int i,j,k;
    int tag = 10;

    for (i = 1; i <= END; i++) {
        printf("%d \n", i);
    }
    /*
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    */

    // 可以省略一个或多个表达式( 但是不能省略分号), 只要在循环中包含能结束循环的语句即可。
    for (i = 1; tag > 0;) {
        printf("%d \n", tag);
        tag--;
    }
    /*
    10
    9
    8
    7
    6
    5
    4
    3
    2
    1
    */

    // for 的第一个子句只执行一次, 可以是任何内容
    for (printf("Only print once!\n"); tag <= 5;) {
        printf("%d \n", tag);
        tag++;
    }
    /*
    Only print once!
    0
    1
    2
    3
    4
    5
    */


    // for 循环条件中的变量都是可以改变的,但是这样做很危险
    // i 并没有按 i++ 进行循环, 可以自由控制i 的变化
    for (i = 1; i < 100; i++) {
        if (i % 5 == 0) {
            printf("%d\n", i);
            i += 11;
        }
    }
    /*
    5
    20
    35
    50
    65
    80
    95
    */

    // 使用逗号, 可以在 for循环开头加入更多控制
    for (i = 0, j = 0, k = 0; (i + j + k) < 100; i++, j += 2, k += 3) {
        printf("i=%d,j=%d,k=%d, i+j+k=%d \n", i, j, k, i + j + k);
    }
    /*
    i=0,j=0,k=0, i+j+k=0
    i=1,j=2,k=3, i+j+k=6
    i=2,j=4,k=6, i+j+k=12
    i=3,j=6,k=9, i+j+k=18
    i=4,j=8,k=12, i+j+k=24
    i=5,j=10,k=15, i+j+k=30
    i=6,j=12,k=18, i+j+k=36
    i=7,j=14,k=21, i+j+k=42
    i=8,j=16,k=24, i+j+k=48
    i=9,j=18,k=27, i+j+k=54
    i=10,j=20,k=30, i+j+k=60
    i=11,j=22,k=33, i+j+k=66
    i=12,j=24,k=36, i+j+k=72
    i=13,j=26,k=39, i+j+k=78
    i=14,j=28,k=42, i+j+k=84
    i=15,j=30,k=45, i+j+k=90
    i=16,j=32,k=48, i+j+k=96
    */

    return 0;
}

数组 (array )是按顺序存储的一系列类型相同的值, 如 10 个 char 类型的字符或 15 个 int 类型的值。 整个数组有一个数组名, 通过整数下标访问数组中单独的项或元素 (element )。

使用数组元素和使用同类型的变量一样。

考虑到影响执行的速度, C 编译器不会检查数组的下标是否正确。

当运行程序时, 这会导致数据被放置在已被其他数据占用的地方, 可能会破坏程序的结果甚至导致程序异常中断。

可以把字符串存储在 char 类型的数组中( 一般而言, char 类型数组的所有元素都存储 char 类型的值)。 如果 char 类型的数组末尾包含一个表示字符串末尾的空字符\ 0 ,则该数组中的内容就构成了一个字符串.字符串是含有\0 元素的字符数组,字符数组不一定是字符串.

#include <stdio.h>

int main(void)
{
    char c_array[10];
    char c_str[10];
    int i;

    for (i = 65; i < 65 + 10; i++) {
        c_array[i - 65] = i;
    }

    printf("%s ", c_array);
    // ABCDEFGHIJ烫烫烫b霬轲???
    // c_array  并不是字符串, 没有结束标志, 所以会出现打印异常

    printf("\n");

    for (i = 0; i < 10; i++) {
        printf("%c", c_array[i]);
    }
    // ABCDEFGHIJ
    // 依次打印了字符数组中的所有字符

    printf("\n");

    /***********************************************/
    for (i = 65; i < 65 + 7; i++) {
        c_str[i - 65] = i;
    }

    c_str[2] = '\0'; // 添加字符串结束标志, 替换掉字母C
    printf("%s\n", c_str); 
    // AB
    // 作为字符串打印时, 只会打印前两个字符,第三个字符是字符串结束标志

    for (i = 0; i < 10; i++) {
        printf("%c", c_str[i]);
    }
    // ABDEFG烫?
    // 会打印字符数组

    printf("\n");

    // 输入填充后三个字符
    printf("Please enter 3 char:\n");
    for (i = 72; i < 72 + 3; i++) {
        scanf("%c", &c_str[i-65]);
    }

    for (i = 0; i < 10; i++) {
        printf("%c", c_str[i]);
    }
    /*
    Please enter 3 char:
    xyz
    ABDEFGxyz
    */
    return 0;
}

第7章 C控制语句:分支和跳转

ctype.h 系列的字符函数

C 有一系列专门处理字符的函数, ctype.h 头文件包含了这些函数的原型。 这些函数接受一个字符作为参数, 如果该字符属于某特殊的类别, 就返回一个非零值( 真); 否则, 返回 0( 假)。 例如, 如果 isalpha() 函数的参数是一个字母, 则返回一个非零值。

# include <stdio.h>
# include <ctype.h>

int main(void)
{
    char ch;

    while ((ch = getchar()) != '\n') {
        // 依次读取回车前的字符
        if (isalpha(ch)) //是字符
            putchar(ch + 1); // 显示后一个字符
        else
            putchar(ch);
    }

    putchar(ch); // 显示换行符

    /*
    abc$$$%%%DEF
    bcd$$$%%%EFG
    */


    // 字符映射函数不会修改原始的参数, 这些函数只会返回已修改的值。
    ch = 'A';
    printf("%c\n", tolower(ch)); //a
    printf("%c\n", ch); //A

    return 0;
}

嵌套if else:从技术角度看, if   else 语句作为一条单独的语句, 不必使用花括号。 外层 if 也是一条单独的语句, 也不必使用花括号。 但是, 当语句太长时, 使用花括号能提高代码的可读性, 而且还可防止今后在 if 循环中添加其他语句时忘记加花括号。

逻辑运算符 含义
&&
||
!

C99 标准新增了可代替逻辑运算符的拼写, 它们被定义在 iso646.h 头文件中。 如果在程序中包含该头文件, 便可用 and 代替 && 、or 代替 || 、not 代替!

条件运算符:?:

C 提供条件表达式 (conditional expression )作为表达 if   else 语句的一种便捷方式, 该表达式使用?: 条件运算符。 该运算符分为两部分, 需要 3 个运算对象。

带一个运算对象的运算符称为一元运算符, 带两个运算对象的运算符称为二元运算符。 以此类推, 带 3 个运算对象的运算符称为三元运算符。 条件运算符是 C 语言中唯一的三元运算符。 下面的代码得到一个数的绝对值:

# include <stdio.h>
# include <ctype.h>

double abs(double a);

int main(void)
{
    int i;

    i = 5;
    printf("abs of %d: %f\n", i, abs(i));
    i = -5;
    printf("abs of %d: %f\n", i, abs(i));
    // abs of 5: 5.000000
    // abs of - 5: 5.000000

    return 0;
}

double abs(double a) {
    return (a < 0) ? -a : a;
}

continue 语句

3 种循环都可以使用 continue 语句。 执行到该语句时, 会跳过本次迭代的剩余部分, 并开始下一轮迭代。

如果 continue 语句在嵌套循环内, 则只会影响包含该语句的内层循环。

break 语句

程序执行到循环中的 break 语句时, 会终止包含它的循环, 并继续执行下一阶段。

如果 break 语句位于嵌套循环内, 它只会影响包含它的当前循环。

break 语句对于稍后讨论的 switch 语句而言至关重要。

多重选择: switch 和 break

使用条件运算符和 if   else 语句很容易编写二选一的程序。 然而, 有时程序需要在多个选项中进行选择。 可以用 if   else   if... else 来完成。 但是, 大多数情况下使用 switch 语句更方便。

break 语句让程序离开 switch 语句, 跳至 switch 语句后面的下一条语句( 见图 7.4)。 如果没有 break 语句, 就会从匹配标签开始执行到 switch 末尾。

© Licensed under CC BY-NC-SA 4.0

计算机没什么用。他们只会告诉你答案。——毕加索

发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!