c程序设计语言第2版 1~3 章

c语言提供了很多基本数据类型, 并通过指针,数组,结构和联合派生出其他数据类型.

指针提供了与具体机器无关的地址算术运算.

标准库提供了一个与操作系统交互的公共接口.

美国国家标准学会(American National Standards Institute,ANSI)

ISO - International Organization for Standardization, 国际标准化组织.

第二版序

函数声明形式(函数原型),可以对函数的定义进行交叉检查

C语言本身并不算大型语言, 涵盖的核心概念也比较少.

学习的精髓在于在重复中不断提升: 随着使用经验的增加,使用者会越来越感到得心应手.

第一版序

本书试图讲解的部分内容

  • 语言本身
  • 高效的算法
  • 良好的程序设计风格
  • 正确的设计原则

知识越丰富, 学习起来越容易.

引言

如果将编程理解成多个数据项在存储中的创建,修改,删除的动作, 编程会变的更容易. 存储主要是内存, 当然也包括cpu中的寄存器,硬盘,网络,数据库.....

源代码是供人类阅读和理解的文本, 源代码表达了语言的语法规则, 同时也可能会掩盖接近硬件的底层实现, 从而让我们对编程的理解局限于表面. 所以不要被语言在源代码中体现出的语法的写法所迷惑.

从编程思想的角度看, C语言也有面向对象的特性, 因为C语言定义的数据类型也想 OOP中的对象一样支持特定的操作.

第1章 导言

C程序由函数和变量构成

  • 变量定义数值
  • 函数定义对数值的操作

需要注意的是定义的数值和我们直观中的10进制数值不同:

1.计算机用二进制表示数, 这意味着并不能精确表示小数

2.计算机表示的数都有明确的范围,因为是用固定的有限的位数表示数值的

3.C语言中的整数除法会舍弃小数部分(舍位): 整数除法的结果是整数, 所以舍位.

转义字符后跟了一个没有定义的字符,只会输出该字符本身:

#include <stdio.h>

int main() {
    char temp = '\c';
    if (temp == 'c') {
        printf("%c", temp); /* c */
    }
}

整数除法的舍位是在每一步的整数除法中都进行的, 而不是最后结果再取整:

#include <stdio.h>

int main() {
    printf("%d\n", 5 / 9 * 100); // 0
    printf("%d\n", 5 * 100 / 9); // 55
}

用#define定义符号常量并没有声明数据类型,实际上是一种文本替换的宏定义.

通用形式:

#define 名字 替换文本

例子:

#include <stdio.h>
#define HIGH 200

int main() {
 #define LOW 100
    printf("%d\n",LOW / 3); //33
    printf("%f", HIGH / 3.0); //66.666667
}

字符输入输出

  • getchar() : 读入一个字符
  • putchar(c):打印一个字符,即将整型变量或字符变量c的内容以字符形式打印如来
  • 命令行输入EOF的方式
  • windows:首先在最后一行结束后(此时未换行)输入ENTER键,新起一行,再输入ctrl+z,再输入时ENTER键即可。
  • Linux: 直接按CTRL+D快捷键

C,C++中, 赋值语句的返回值是所赋的值

#include <stdio.h>

int main() {

    /* 将输入显示到输出 */
    int c;  /* 定义成char 可能不够大而不能表示 EOF */
    long long int num_c = 0; // 统计输入的字数
    long long int num_line = 0;//统计输入的行数
    while ((c = getchar()) != EOF) { /* eof, end of file, 在 stdio.h 中定义 */
        putchar(c);
        num_c++;
        if (c == '\n') ++num_line;
    }

    printf("\n 字符数 : %lld\n", num_c);
    printf("\n 输入的行数 : %lld\n", num_line);
    printf("\n%d\n", EOF); // -1
}
/*
hello world!
hello world!
^Z

 字符数 : 13
 输入的行数 : 1
-1

*/

单引号中的字符对应一个整数,称为字符常量.

#include <stdio.h>

int main() {

    /* 练习1-8: 复制输出程序, 将其中连续的多个空格用一个空格代替. */
    int c; // 存储输入的字符, 包括 EOF
    int c_pre; //存储前一个字符

    c = getchar();
    c_pre = 'c';


    while (c != EOF) {
        if (c_pre != ' ') {
            putchar(c);
        }
        else {
            if (c != ' ')putchar(c);
        }

        c_pre = c;
        c = getchar();
    }
}

/*
 test test    test
 test test test
^Z
*/

单词计数:

  • 统计行数,单词数,字符数
  • 单词定义: 不包含空格,制表符或换行符的序列
#include <stdio.h>

int main() {
    /* 单词计数:
    - 统计行数,单词数,字符数
    - 单词定义: 不包含空格,制表符或换行符的序列
    */

    int c;// 存输入的字符,包括 EOF:ENTER, CTRL +Z, ENTEER
    short tag = 0; // 0,1 目前是否为单词
    int num_lines = 0, num_words = 0, num_chars = 0;

    while ((c = getchar()) != EOF) {

        /* 更新单词数,tag 由非单词变为单词标记 */
        if (c == '\t' || c == ' ' || c == '\n') {
            /* 非单词字符 */
            if (tag == 1) {
                tag = 0;
            }
        }
        else {
            if (tag == 0) {
                tag = 1;
                num_words++ ;
            }
        }

        /* 更新行数,字符数*/
        if (c == '\n') num_lines++;
        num_chars++;
    }

    printf("单词数量 %d,字符数量 %d, 行数 %d \n", num_words, num_chars, num_lines);
}

/*
hello world!
hello World!
the End.
^Z
单词数量 6,字符数量 35, 行数 3
*/

按行输出单词:

#include <stdio.h>

int main() {
    /* 每行一个单词的形式打印输入 */
    int c;

    while ((c = getchar()) != EOF) {
        if (c == ' ' || c == '\n' || c == '\t') { /* 注意制表符是 Tab键, 不是输入 \t */
            putchar('\n');
        }
        else putchar(c);
    }
    /* 
    test1 test2     test3
    test1
    test2
    test3
    */
}

编写一个程序, 打印输入中各个字符出现频度的直方图

#include <stdio.h>

int main() {
    /* 编写一个程序, 打印输入中各个字符出现频度的直方图    */
    int num_c_distinct = 0; // 不同字符的数量
    int c; // 字符输入, EOF 结束
    char c_distinct[500]; //存储不同的字符
    int num_c[500]; // 存储  c_distinct 出现的次数
    int temp, i;

    for (i = 0; i < 500; i++) {
        num_c[i] = 0;
        c_distinct[i] = EOF;
    }

    while ((c = getchar()) != EOF) {
        temp = -1;/* c 在 c_distinct 中的位置*/

        for (i = 0; i < num_c_distinct; i++) {
            if (c == c_distinct[i]) {
                num_c[i]++;
                temp = i;
                break;
            }
        }

        if (temp == -1) {
            /* 新字符 */
            num_c_distinct++;
            c_distinct[num_c_distinct - 1] = c;
            num_c[num_c_distinct - 1] = 1;
        }
    }

    /* 输出各字符数量*/
    for (i = 0; i < num_c_distinct; i++) {
        if (c_distinct[i] == '\n')printf("\\n ");
        else printf("%c  ", c_distinct[i]);

        for (temp = 0; temp < num_c[i]; temp++) printf("*");
        printf("\n");
    }
}
/*
hello world!
hello world!
^Z
h  **
e  **
l  ******
o  ****
   **
w  **
r  **
d  **
!  **
\n **
*/

函数

函数为计算提供了封装.

  • 函数定义中的参数为形式参数
  • 函数调用中与形式参数对应的值为实际参数
  • main函数的调用值会返回给程序的执行环境

函数参数一般都是值传递: 参数值存放在临时变量中, 而不是原来的变量中, 因而函数不能修改主调函数中的值.

当需要修改主调函数中的值时, 需要传递的参数是指针, 通过指针修改指针指向的值.

当传递的是数组名时, 传递的实际是指针:数组的第一个元素的指针.

字符数组

字符数组是C语言中最常用的数组类型?

读入一组文本行, 打印最长的那一行文本. 没有用到自定义函数, 比原书中代码行少很多.

#include <stdio.h>

int main() {
    /* 读入一组文本行, 打印最长的那一行文本  */

    int c;// 存储输入的字符
    char array_c[500];// 存储当前行
    char array_c_max[500];// 存储最长的行;
    int num_max = 0; // 存储最长行的长度
    int temp = 0;


    while ((c = getchar()) != EOF) {
        if (c != '\n') {
            /* 当前行 */
            array_c[temp] = c;
            temp++;
        }
        else {
            /* 新行开始*/
            if (temp > num_max) {
                num_max = temp;

                for (int i = 0; i < temp; i++) {
                /* 复制数组 */
                    array_c_max[i] = array_c[i];
                }
                array_c_max[temp] = '\0';/* 添加字符串结束标志 */
            }
            temp = 0;
        }
    }

    /* 打印最长行 */
    for (int i = 0; i < 100; i++) {
        printf("-");
    }
    printf("\n");
    for (int i = 0; i < num_max; i++) {
        putchar(array_c_max[i]);
    }
}
/*
“Be yourself; everyone else is already taken.”
― Oscar Wilde
tags: attributed-no-source, be-yourself, honesty, inspirational, misattributed-oscar-wilde157017 likesLike
I'm selfish, impatient and a little insecure. I make mistakes, I am out of control
“I'm selfish, impatient and a little insecure. I make mistakes, I am out of control and at times hard to handle. But if you can't handle me at my worst, then you sure as hell don't deserve me at my best.”
― Marilyn Monroe
tags: attributed-no-source, best, life, love, mistakes, out-of-control, truth, worst155377 likesLike
Albert Einstein
“Two things are infinite: the universe and human stupidity; and I'm not sure about the universe.”
― Albert Einstein
tags: attributed-no-source, human-nature, humor, infinity, philosophy, science, stupidity, universe137423 likesLike
Frank Zappa
“So many books, so little time.”
― Frank Zappa
tags: books, humor134736 likesLike
Marcus Tullius Cicero
“A room without books is like a body without a soul.”
― Marcus Tullius Cicero
tags: attributed-no-source, books, simile, sou
^Z
----------------------------------------------------------------------------------------------------
I'm selfish, impatient and a little insecure. I make mistakes, I am out of control and at times hard to handle. But if you can't handle me at my worst, then you sure as hell don't deserve me at my best.
*/

第二章 类型,运算符与表达式

  • 基本的数据对象: 变量,常量
  • 运算符: 对数据对象进行的操作
  • 表达式: 将数据对象和运算符组合起来, 产生新的值

基本数据类型

  • char
  • int
  • float
  • double

整数限定符: short,long,signed,unsigned

浮点数扩展: long double

各数据类型取值范围等属性:

  • limits.h
  • float.h

整型,浮点型常量前后缀

  • 前缀: 8 进制 0
  • 前缀: 16进制 0x,0X
  • 整数后缀: u,U,l,L,ul,UL,
  • 浮点数后缀: f,l,L,

字符,字符串常量

  • 字符常量
    • 'a',用单引号
    • 95, 用整数
    • 转义 \n
    • 8进制转义 \ooo, 用1~3个八进制数表示(0~7)
    • 16进制转义 \xhh, hh代表1个或多个16进制数
    • \0 表示值为0的字符, 不是字符 '0', 表示 null
  • 字符串常量: 用双引号引起来的字符串字面值
    • 字符串常量就是以 \0结尾字符串数组
    • strlen(s) 返回的值并不包括 \0, 所以字符串占用的物理空间比字符串长度本身大

```C #include

int main() { printf("%d\n", '0' == '\0'); / 0 / printf("%d\n", 0 == '\0'); / 1 / }

> 0 和 `'0'`,`"0"`  是完全不同的几个东西:数字, 字符,字符串
>
> 通过定义 `\0`, `EOF`, 在源代码中可以为字符串加入特殊的字符, 从而控制字符数组和字符串


```C
#include <stdio.h>

int strlen(char s[]);

int main() {
    printf("% d \n", strlen(""));    //0
    printf("% d \n", strlen("hello")); // 5
}

int strlen(char s[]) {
    int i;
    for (i = 0;; i++) {
        if (s[i] == '\0') {
            break;
        }
    }
    return i ;
}

枚举常量

枚举常量是整型值的列表,默认从0开始, 如果指定了整型值, 则从最后一个指定值递增.

#include <stdio.h>

int main() {
    enum MyEnum
    {
        One = 65,
        Two = 3,
        Three =2,
        Four
    };
    printf("%d %d %d  %d", One, Two, Three, Four); // 65 3 2 3, 值可以重复
}

变量声明

  • 外部变量和静态变量会被初始化为0
  • const 定义不能修改的常量, 包括数组
#include <stdio.h>

int e_i;
char e_c;

int main() {
    extern e_i;
    static int i;
    int j;
    const float f[2];

    printf("%d \n", e_i); //0
    printf("%d \n", e_c=='\0'); // 1
    printf("%d \n", i); //0
    printf("%d \n", j=1); //1, 必须初始化 j

    //f[0] = 0.0; // 报错
}

闰年:

  • 能被4整除但不能被100整除
  • 能被400 整除
#include <stdio.h>

int main() {
    int year = 1999;
    for (int i = 0; i < 401; ++i) {
        if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) printf("%d ", year);
        year++;
        if (i % 50 == 0) printf("\n");
    }
}

/*
2000 2004 2008 2012 2016 2020 2024 2028 2032 2036 2040 2044 2048
2052 2056 2060 2064 2068 2072 2076 2080 2084 2088 2092 2096
2104 2108 2112 2116 2120 2124 2128 2132 2136 2140 2144 2148
2152 2156 2160 2164 2168 2172 2176 2180 2184 2188 2192 2196
2204 2208 2212 2216 2220 2224 2228 2232 2236 2240 2244 2248
2252 2256 2260 2264 2268 2272 2276 2280 2284 2288 2292 2296
2304 2308 2312 2316 2320 2324 2328 2332 2336 2340 2344 2348
2352 2356 2360 2364 2368 2372 2376 2380 2384 2388 2392 2396
*/

将输入的字符串转换为整数

#include <stdio.h>
#include <math.h>

int str2int(char str[], int strLen);

int main() {
    int n = str2int("12345d7890", 10);
    printf("%d", n); //1234507890 ,d 对应的值为 0
}

int str2int(char str[], int strLen) {
    int n = 0;
    // 默认str 中的每个字符都是数字
    for (int i = 0; i < strLen; i++) {
        if (str[strLen - i - 1] >= '0' && str[strLen - i - 1] <= '9') {
            n = n + (str[strLen - i - 1] - '0') * pow(10, i);
        }
    }
    return n;
}

类型转换

(类型) 表达式

按位运算符: 只能作用于整数

  • &, 按位与,AND
  • |, 按位或, OR
  • ^, 按位异或,XOR
  • <<, 左移
  • >> ,右移动
  • ~,按位求反, 一元运算符

以下 int2bit() 函数可以很好的演示位操作:

#include <stdio.h>

void int2bit(int i);

int main() {
    int2bit(0177); // 7 对应三位为1
    int2bit(1234567);
    //----------------------------------
    //127 的二进制形式:
    //00000000000000000000000001111111

    //----------------------------------
    //1234567 的二进制形式:
    //00000000000100101101011010000111


    // & 常用于屏蔽某些二进制位, 需要屏蔽的位对应为0即可
    // 屏蔽1234567的低7位之外的位:
    int2bit(1234567 & 0177);

    //----------------------------------
    //7 的二进制形式:
    //00000000000000000000000000000111

    // | 常用于将某些位设置为1
    int2bit(1234567 | 0177);
    //----------------------------------
    //1234687 的二进制形式:
    //00000000000100101101011011111111

    // ^ 异或是双方不相同为1,否则为0
    int2bit(1234567 ^ 0177);
    //----------------------------------
    //  1234680 的二进制形式:
    //00000000000100101101011011111000

    // <<,>> 按位左移或者右移, 空出的一般补0或者补符号位
    int2bit(127);
    int2bit(127 << 2);
    int2bit(127 >> 2);
    //----------------------------------
    //  127 的二进制形式:
    //00000000000000000000000001111111

    //----------------------------------
    //508 的二进制形式 :
    //00000000000000000000000111111100

    //----------------------------------
    //31 的二进制形式 :
    //00000000000000000000000000011111

    //~按位取反码, 注意前端的非数字位0 不会取反
    int2bit(127);
    int2bit(~127);
    int2bit(~(~127));
    //----------------------------------
    //  127 的二进制形式:
    //00000000000000000000000001111111

    //----------------------------------
    //- 128 的二进制形式 :
    //00000000000000000000000010000000

    //----------------------------------
    //127 的二进制形式 :
    //00000000000000000000000001111111
}

void int2bit(int i) {
    // 打印整数 i 对应的二级制形式
    int temp = i;
    int len = 0;
    int sizebits = sizeof(int) * 8;
    char c_bits[250];
    c_bits[0] = '0';

    if (i == 0)len = 1;

    while (temp != 0) {
        if ((temp % 2) == 0) {
            c_bits[len] = '0';
        }
        else c_bits[len] = '1';

        temp /= 2;
        len++;
    }

    // 逆序打印, 空位补0
    printf("\n----------------------------------\n");
    printf("%d 的二进制形式:\n", i);
    for (int j = sizebits - len; j > 0; j--) putchar('0');
    for (int j = len - 1; j >= 0; j--) putchar(c_bits[j]);
    printf("\n");
}

赋值表达式

expr_1 or = expr_2

等价于:

expr_1 = (expr_1 ) or (expr_2)

#include <stdio.h>

int main() {
    int x,y;

    x = 2, y = 1;
    printf("x *= y + 1 : %d\n", x *= y + 1);
    x = 2, y = 1;
    printf("x = x * (y + 1) :%d\n", x = x * (y + 1));
    //x *= y + 1 : 4
    //x = x * (y + 1) : 4
}

条件表达式

if (expr1){
    expr_2;
}else{
    expr_3;
}

等价于:

expr1?expr_2:exp3

#include <stdio.h>

int main() {
    int x = 2;

    if (x > 2)printf("x>2\n"); else printf("x<=2\n");
    (x > 2) ? printf("x>2\n") : printf("x<=2\n");
    //x <= 2
    //x <= 2
}

运算符优先级与结核性

() 的优先级很高, 所以不清楚优先级时, 用() 分割. LISP用 () 和这个也有关系吗?

第3章 控制流

{} 之间的语句构成的复合语句逻辑上等价于单条语句.

嵌套的if最好用{} 分隔以避免歧义.

python中的缩进结构是非常好的设计.

switch语句

语法:

switch(表达式){
    case 常量表达式:语句序列
    case 常量表达式:语句序列
    default: 语句序列
}
  • default 分支可选, 在没有匹配的情况下执行
  • break 退出switch语句
#include <stdio.h>

int main() {
    char c;
    int num_digits = 0, num_whites = 0, num_others = 0;

    for (int i = 0; i < 128; i++) {
        switch ((char)i)
        {
        case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9':
            num_digits++;
            break;
        case ' ':case '\t':case '\n':
            num_whites++;
            break;
        default:
            num_others++;
            break;
        }
    }

    printf("数字字符数量: %d \n", num_digits);
    printf("空白字符数量: %d \n", num_whites);
    printf("其他字符数量: %d \n", num_others);
//数字字符数量: 10
//空白字符数量 : 3
//其他字符数量 : 115
}

尽量不要用goto语句.

#include <stdio.h>

int main() {
goto1: //goto 语句使用到的标号
    printf("go to here:goto1\n");
    goto goto2;

goto3:
    for (int i = 1; i < 10; i++) {
        if (i % 6 == 0) {
            goto goto4;
        }
        else {
            printf("%d ", i);
        }
    }

goto2: //goto 语句使用到的标号
    printf("go to here:goto2\n");
    goto goto3;

goto4:
    printf("the end;");
}
//go to here : goto1
//go to here : goto2
//1 2 3 4 5 the end;
© Licensed under CC BY-NC-SA 4.0

你自己的代码如果超过6个月不看,再看的时候也一样像是别人写——伊格尔森定律

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

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