2.1 简单C程序

#include <stdio.h>

int main(void) {
    printf("To C, or not to C: that is the question.\n");
    return 0;
}

2.1.1 编译和链接

流程:

  1. 文件类型检查: 编译器会检查文件扩展名是否为c
  2. 预处理: 预处理器执行#开头的指令, 并执行替换操作
  3. 编译: 将程序编译为机器指令 (目标文件)
  4. 链接: 将编译器产生的目标文件与其他的依赖文件链接在一起, 最终生成可执行文件

2.1.2 集成开发环境

仅记录术语和英文名称, 略.

2.2 简单程序的一般形式

directives

int main(void) {
    statements
}

2.2.1 指令 (directives)

指令定义: 预处理器(preprocessor)执行的命令

示例一中的指令为: #include <stdio.h>

该指令的功能: 在编译之前将<stdio.h>中的信息包含至程序中, 而<stdio.h>中包含了标准IO的信息

2.2.2 函数 (functions)

函数定义: 类似其他语言中的"过程"或"子例程", 是用来构建程序的构建块.

分为两类函数:

  1. 开发者自行开发的函数
  2. 库函数 (library function): C语言提供的函数

2.2.3 语句 (statements)

语句定义: 程序执行时执行的命令 两类语句:

  • 一般语句: 以分号结尾
  • 复合语句: 不以分号结尾 (5.2)

2.2.4 输出字符串

2.3 注释

两类注释:

  • // ( #C99 )
  • /**/

2.4 变量与赋值

2.4.1 类型 (type)

变量(variable)定义: 临时存放数据的存储单元

float的限制:

  1. 计算开销较大
  2. 往往只能得到近似值 (比如: 0.1可能会被舍入为0.099 999 999 999 999 87)

2.4.2 声明 (declaration)

声明定义: 为编译器所做的描述, 步骤: 1. 指定类型; 2. 变量名

#C99 声明可以不在语句之前 (比如: for(int i = 0; i < n; i++)) 本书出于兼容性, 仍采用 #C89 标准, 声明必须位于语句之前.

2.4.3 赋值 (assignment)

变量通过赋值的形式获取值.

注意: 确保某个类型变量赋值得到的是对应类型的常量. (比如: float x = 142.34f)

2.4.4 打印变量

略.

(程序) 计算箱子的空间重量

目标:

  • 空间体积定义: volume(体积) / 166(每磅允许的立方英寸数)
  • 若空间体积 > 重量, 则按照空间体积计算费用
  • 若空间体积 < 重量, 则按照重量计算费用

控制向下取整和向上取整的方法

#include <stdio.h>

int main(void) {
    int height, length, width, volume, weight;

    height = 8;
    length = 12;
    width = 10;

    volume = height * length * width;
    weight = (volume + 165) / 166;

    printf("Dimensions: %dx%dx%d\n", height, length, width);
    printf("Volume (cubic inches): %d\n", volume);
    printf("Dimensional weight: %d.\n", weight);

    return 0;
}

2.4.5 初始化

  • 初始化(initialization)变量具有初始值
  • 未初始化的(uninitialized): 未赋初值的变量
  • 初始化器(initializer): 变量的初始化的初始值 (比如: int a = 8;中8就是初始化器)

2.4.5 显示表达式的值

printf不仅可以显示变量, 还可以显示表达式的值

printf("Volume (cubic inches): %d\n", height * length * width);

2.5 IO

#include <stdio.h>

int main(void) {
    int height, length, width, volume, weight;

    printf("Enter height of box: ");
    scanf("%d", &height);
    printf("Enter length of box: ");
    scanf("%d", &length);
    printf("Enter width of box: ");
    scanf("%d", &width);

    volume = height * length * width;
    weight = (volume + 165) / 166;

    printf("Volume (cubic inches): %d\n", volume);
    printf("Dimensional weight (pounds): %d\n", weight);

    return 0;
}

2.6 定义常量名称

宏定义定义: #define INCHES_PER_POUND 166

宏名称规定: 全部大写

#include <stdio.h>

#define FREEZING_PT 32.0f
#define SCALE_FACTOR (5.0f / 9.0f)

int main(void) {
    float fahrenheit, celsius;

    printf("Enter Fahrenheit temperature: ");
    scanf("%f", &fahrenheit);

    celsius = (fahrenheit - FREEZING_PT) * SCALE_FACTOR;

    printf("Celsius equivalent: %.1f\n", celsius);

    return 0;
}

2.7 标识符

#C-point

标识符定义: 变量, 函数, 宏和其他实体的名称

特性:

  • 区分大小写
  • 标识符长度要求: 虽然标准中没有明确限制, 但是在编译器上 #C89 只会识别前31个字符, #C99 中识别前63个字符, 如果前31/63个字符相同, 编译器将无法区分变量

规定: 字母, 数字, 下划线 (必须以字母或者下划线开头)

类型例子
合法标识符times10
get_next_char
_done
不合法标识符10times: 开头为数字
get-next-chat: 不能含有-符号

2.7.1 关键字

#C-point

注意:

  • (1): 表示C99引入
  • (2): 表示C11引入
关键字关键字关键字关键字
autoexternshortwhile
breakfloatsigned_Alignas (2)
caseforsizeof_Alignof (2)
chargotostatic_Atomic (2)
constifstruct_Bool (1)
continueinline (1)switch_Complex (1)
defaultinttypedef_Generic (2)
dolongunion_Imaginary (1)
doubleregisterunsigned_Noreturn (2)
elserestrict (1)void_Static_assert (2)
enumreturnvolatile_Thread_local (2)

2.8 C程序的书写规范

记号(token)定义: 在不改变意思的情况下无法分割的字符组

757

Chapter2-练习题

答案链接: http://knking.com/books/c2/answers/c2.html

记号说明:

  • 红色字体 表明错误的答案或者遗漏的知识点

Practice1-error

建立并运行由Kernighan和Ritchie编写的著名的"hello, world"程序, 在编译时是否有警告? 如果有, 应该如何修改呢?

#include <stdio.h>

int main(void) {
    printf("hello, world.\n");
}

编译过程:

  1. 按照默认选项进行编译, 没有警告回显
  2. 打开-Wall参数, 仍然没有警告回显
  3. 打开-pedantic参数, 警告要求空行
  4. 处理: 结尾添加空行

问题: 为什么没有警告return 0? 回答:

  • C89/C90: 会警告没有return 0; #C89
  • C99及以后: 编译器不会警告, 默认返回0 #C99
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/Practice1-error 
╰─$ gcc error.c -o error
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/Practice1-error 
╰─$ gcc -Wall error.c -o error
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/Practice1-error 
╰─$ gcc -Wall -pedantic error.c -o error
error.c:5:2: warning: no newline at end of file [-Wnewline-eof]
    5 | }
      |  ^
1 warning generated.

Practice2-explain

  1. 请指出程序中的指令和语句。
  2. 程序的输出是什么?
#include <stdio.h>

int main(void) {
    printf("Parkinson's Law:\nWork expands so as to ");
    printf("fill the time\n");
    printf("available for its completion.\n");

    return 0;
}

回答: 指令的定义 (遗忘): 以#开头的语句 语句的定义 (遗忘): 程序中以;结尾的语句

  • 指令:
  • #include <stdio.h>
  • 语句:
  • printf("Parkinson's Law:\nWork expands so as to ");
  • printf("fill the time\n");
  • printf("available for its completion.\n");
  • 遗漏语句: return 0;

输出:

Parkinson's Law:
Work expands so as to fill the time
available for its completion.

Practice3-dweight3

通过下列方法缩写程序dweight.c :

  1. 用初始化式替换对变量height 、length 和width 的赋值;
  2. 去掉变量weight,在最后的printf 语句中计算(volume + 165)/ 166 。

原dweight.c文件:

#include <stdio.h>

int main(void) {
    int height, length, width, volume, weight;

    height = 8;
    length = 12;
    width = 10;

    volume = height * length * width;
    weight = (volume + 165) / 166;

    printf("Dimensions: %dx%dx%d\n", height, length, width);
    printf("Volume (cubic inches): %d\n", volume);
    printf("Dimensional weight: %d.\n", weight);

    return 0;
}

缩写后的dweight.c文件:

#include <stdio.h>

int main(void) {
    int height = 8, width = 10, length = 12;
    int volume = height * width * length;

    printf("Dimensions: %dx%dx%d\n", height, length, width);
    printf("Volume (cubic inches): %d\n", volume);
    printf("Dimentional weight: %d.\n", (volume + 165) / 166);
}

编译并运行:

╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/Practice3-dweight3 
╰─$ gcc dweight.c -o dweight
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/Practice3-dweight3 
╰─$ ./dweight 
Dimensions: 8x12x10
Volume (cubic inches): 960
Dimentional weight: 6.
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/Practice3-dweight3 
╰─$  

Practice4-without declaration

编写一个程序来声明几个int 型和float 型变量,不对这些变量进行初始化,然后显示它们的值。这些值是否有规律?(通常情况下没有。)

解释:

  1. 局部变量保存在栈中
  2. 栈清理过程: 仅对RSP进行修改 (比如: add esp, 0x10), 并不会对栈上的数据进行清理
  3. 所以如果不对变量进行初始化, 则其内容就是之前调用的启动函数残留下来的数据或者其他未定义的数据.
#include <stdio.h>

int main(void) {
    int a;
    int b;
    int c;
    float d;
    float e;
    float f;

    printf("%d %d %d %f %f %f\n", a, b, c, d, e, f);

    return 0;
}

编译并运行

╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/Practice4-non-declaration 
╰─$ gcc test.c -o test
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/Practice4-non-declaration 
╰─$ ./test            
-272793520 1 -272793440 0.000000 -0.000000 0.000000
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/Practice4-non-declaration 
╰─$ 

Practice5-identifier1

  1. 判断下列C语言标识符哪些是不合法的?

  2. 100_bottles: 不合法, 不能以数字开头

  3. _100_bottles: 合法

  4. one__hundred__bottles: 合法

  5. bottles_by_the_hundred_: 合法

Practice6-identifier2

问题: 为什么说在标识符中使用多个相邻的下划线(如current___balance )不太合适? 回答: 因为看起来像是两个变量, 影响阅读, 而且太长了也没有什么意义.

补充:

  1. 某些编译器对下划线的处理可能不一致,特别是多个连续的下划线。
  2. 使用多个下划线的变量名通常不符合简洁、清晰的代码风格。

Practice7-keyword

题目是否是关键字说明
for循环关键字
IfI是大写的
main只是函数名
printf只是函数名
while循环关键字

Practice8-token

下面的语句中有多少记号?

  1. token定义 (遗忘): 在不改变意思的情况下无法分割的字符组
  2. 错误: 遗漏了;也是记号
answer =    (    3    *     q    -    p    *    p    )    /    3    ;
|     1                 2    3     4    5   6    7    8   9  10   11  12

Practice9-empty1

在练习题8的记号之间插入空格,使该语句更易于阅读。

answer=(3*q-p*p)/3;

answer = (3 * q - p * p) / 3;

Practice10-empty2

在dweight.c 程序(➤2.4节)中,哪些空格是必不可少的?

回答:

  1. #include <stdio.h>
  2. int main
  3. int height
  4. 字符串内的空格
  5. return 0
#include <stdio.h>

int main(void) {
    int height, length, width, volume, weight;

    height = 8;
    length = 12;
    width = 10;

    volume = height * length * width;
    weight = (volume + 165) / 166;

    printf("Dimensions: %dx%dx%d\n", height, length, width);
    printf("Volume (cubic inches): %d\n", volume);
    printf("Dimensional weight: %d.\n", weight);

    return 0;
}

编程题

code1-Graphics

编写一个程序,使用printf在屏幕上显示下面的图形:

#include <stdio.h>

int main(void) {
    printf("              *\n\n");
    printf("            *\n\n");
    printf("          *\n\n");
    printf("*       *\n\n");
    printf("  *   *\n\n");
    printf("    *\n\n");
}

编译并运行

╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code1-graphics 
╰─$ gcc graphics.c -o graphics                                                                                                                                    1 ↵
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code1-graphics 
╰─$ ./graphics 
|              *
|
|            *
|
|          *
|
|*       *
|
|  *   *
|
|    *

╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code1-graphics 
╰─$

code2-sphere

编写一个计算球体体积的程序,其中球体半径为10 m,参考公式$v = 4/3\pi r^3$。注意,分数4/3应写为4.0f/3.0f 。(如果分数写成4/3 会产生什么结果?) 提示 :C语言没有指数运算符,所以需要对$r^3$自乘两次来计算 。

#include <stdio.h>

int main(void) {
    float r = 10;
    float v = 4.0f / 3.0f * 3.1415926 * (r * r * r);

    printf("volume = %f\n", v);

    return 0;
}

编译并运行:

╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code2-sphere 
╰─$ gcc sphere.c -o sphere
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code2-sphere 
╰─$ ./sphere              
volume = 4188.790039
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code2-sphere 
╰─$ 

code3-sphere2

修改上题中的程序,使用户可以自行录入球体的半径。

#include <stdio.h>

int main(void) {
    float r;
    printf("r = ");
    scanf("%f", &r);
    float v = 4.0f / 3.0f * 3.1415926 * (r * r * r);

    printf("volume = %f\n", v);

    return 0;
}

编译并运行:

╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code3-sphere2 
╰─$ gcc sphere2.c -o sphere2
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code3-sphere2 
╰─$ ./sphere2               
r = 1
volume = 4.188790
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code3-sphere2 
╰─$ 

code4-tax

编写一个程序,要求用户输入一个美元数量,然后显示出增加5%税率后的相应金额。格式如下所示:

Enter an amount: 100.00

With tax added: $105.00

代码如下

#include <stdio.h>

int main(void) {
    float cost = 0;

    printf("Enter an amount: ");
    scanf("%f", &cost);

    printf("With tax added: $%.2f\n", (cost * 1.05));

    return 0;
}

编译并运行

╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code4-tax 
╰─$ gcc tax.c -o tax
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code4-tax 
╰─$ ./tax           
Enter an amount: 100
With tax added: $105.00

code5-expression

编程要求用户输入 $x$ 的值, 然后显示如下多项式的值: 提示: C语言没有指数运算符, 所以需要对 $x$ 进行自乘来计算其幂. (例如, x*x*x 就是 $x$ 的三次方)

$$ 3 x^{5}+2 x^{4}-5 x^{3}-x^{2}+7 x-6 $$

代码如下

#include <stdio.h>

int main(void) {
    float x;

    printf("x = ");
    scanf("%f", &x);

    float exp = 3 * x*x*x*x*x + 2 * x*x*x*x - 5 * x*x*x - x*x + 7 * x - 6;

    printf("exp = %f\n", exp);

    return 0;
}

编译并链接

╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code5-expression 
╰─$ gcc expression.c -o expression
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code5-expression 
╰─$ ./expression                  
x = 1
exp = 0.000000
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code5-expression 
╰─$ ./expression                  
x = 2
exp = 92.000000

code6-expression2

修改上题,用如下公式对多项式求值: 注意: 修改后的程序所需的乘法次数减少了. 这种多项式求值方法即Horner法则 (Horner's Rule).

$$ ((((3 x+2) x-5) x-1) x+7) x-6 $$

代码如下

#include <stdio.h>

int main(void) {
    float x;

    printf("x = ");
    scanf("%f", &x);

    float exp = ((((3 * x + 2) * x - 5) * x - 1) * x + 7) * x - 6;

    printf("exp = %f\n", exp);

    return 0;
}

编译并运行

╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code6-expression2 
╰─$ gcc exp.c -o exp 
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code6-expression2 
╰─$ ./exp 
x = 2
exp = 92.000000

code7-dollar

编写一个程序,要求用户输入一个美金数量,然后显示出如何用最少的20美元、10美元、5美元和1美元来付款:

Enter a dollar amount: 93

$20 bills: 4
$10 bills: 1
$5 bills: 0
$1 bills: 3

代码如下

#include <stdio.h>

/*
* 核心思想: 贪心算法
* 
*/

int main(void) {
    int dollar;

    printf("Enter a dollar amount: ");
    scanf("%d", &dollar);

    int d20 = dollar / 20;
    dollar = dollar % 20;
    int d10 = dollar / 10;
    dollar = dollar % 10;
    int d5 = dollar / 5;
    dollar = dollar % 5;
    int d1 = dollar;

    printf("\n$20 bills: %d\n", d20);
    printf("$10 bills: %d\n", d10);
    printf("$5 bills: %d\n", d5);
    printf("$1 bills: %d\n", d1);

    return 0;
}

编译并运行

╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code7-dollar 
╰─$ gcc dollar.c -o dollar
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code7-dollar 
╰─$ ./dollar 
Enter a dollar amount: 93

$20 bills: 4
$10 bills: 1
$5 bills: 0
$1 bills: 3
╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code7-dollar 
╰─$ 

code8-loan

编程计算第一, 第二, 第三个月还贷后剩余的贷款金额. 在显示每次还款后的余额时保留两位小数。 提示: 每个月的贷款余额减去还款金额后, 还需要加上贷款余额与月利率的乘积. 月利率的计算方法是把用户输入的利率转换成百分数再除以12.

Enter amount of loan: 20000.00

Enter interest rate: 6.0

Enter monthly payment: 386.66



Balance remaining after first payment: $19713.34
Balance remaining after second payment: $19425.25
Balance remaining after third payment: $19135.71

代码如下

#include <stdio.h>

int main(void) {
    float amount, rate, payment;

    printf("Enter amount of loan: ");
    scanf("%f", &amount);
    printf("\nEnter interest rate: ");
    scanf("%f", &rate);
    printf("\nEnter monthly payment: ");
    scanf("%f", &payment);
    printf("\n\n\n");

    // debug
    // amount = 20000.00;
    // rate = 6.0;
    // payment = 386.66;

    rate = rate / 100 / 12;

    for (int i = 0; i < 3; i++) {
        amount = (amount * (1 + rate)) - payment;
        printf("Balance remaining after first payment: $%.2f\n", amount);
    }

    return 0;
}

编译并运行

╭─lamecrow@Lam3Cr0w ~/TRY/code/c/Retest/C-Chapter2/code8-loan 
╰─$ cd "/Users/lamecrow/TRY/code/c/Retest/C-Chapter2/code8-loan/" && gcc loan.c -o loan && "/Users/lamecrow/TRY/code/c/Retest/C-Chapter2/code8-loan/"loan
Enter amount of loan: 20000.00

Enter interest rate: 6.0

Enter monthly payment: 386.66



Balance remaining after first payment: $19713.34
Balance remaining after first payment: $19425.25
Balance remaining after first payment: $19135.71