The C Programming Language 第1章

本文深入讲解C语言基础知识,包括转义字符、变量与算术运算、for循环、符号常量等核心概念,并通过实例演示了文件复制、字符计数等功能。

1.1 概述
1、注意各种转义字符。

      以前从来没有注意过转义字符。有个很奇怪的问题是这样的。怎么手动输入一个'\0'字符。答案是Ctrl + @就可以输入一个'\0'结束符。

     比如一个简单的测试程序:

    

#include <stdio.h>

int main(void) {
    char str[100];
    while (scanf("%s", str) != EOF) {
        printf("%s\n", str);
    }
    return 0;
}

$ cc test.c -o test
$ ./test
abc
abc
abc^@
abc
abc^@def
abc
^abcdefg


这里再给出ASCII字符表。



练习1-1

在你自己的系统中运行"Hello, world"程序。再有意去掉程序中的部分内容,看看会得到什么出错信息。

答:

比如删除printf最后的分号。

#include <stdio.h>
int main(void) {
    printf("Hello world\n")
    return 0;
}

会得到出错信息如下:

# cc c.c -o c
c.c: In function ‘main’:
c.c:4:5: error: expected ‘;’ before ‘return’

练习1-2

做个实验,当printf函数的参数字符串中包含\c(其中c上面的转义字符序列中未曾列出的某一个字符)时,观察一下会出现什么情况。

答:

#include <stdio.h>
int main(void) {
    printf("Hello world\c");
    return 0;
}
编译会有警告,但是可以通过编译。

警告信息如下:

# cc c.c -o c
c.c: In function ‘main’:
c.c:3:12: warning: unknown escape sequence: '\c' [enabled by default]

运行结果如下:

# ./c
Hello worldc#

1.2 变量与算术表达式

首先是写一个程序,用来进行华氏温度与摄氏温度的转换。

转换公式如下:

C = 5/9  * (F - 32)

其中C表示摄氏温度,而F表示华氏温度。

我写的程序如下:

#include <stdio.h>

int main(void) {
    const int fbegin = 0, fend = 300, step = 20;
    int i = fbegin - step;

    while ((i+=step) <= fend) {
        printf("%3d\t%.1f\n", i, 5.0*(i-32.0)/9.0);
    }

    return 0;
}

Note printf输出格式

值得注意的是在这里简单地提到了printf的输出格式,

%d          表示输出整数

%6d        按6个字符的宽度进行输出。右对齐,前面不足的补空字符。

%f            按照浮点数进行打印。

%6f          按照小数点之前最少6个字符宽度进行输出。不足的补空字符。

%6.2f      小数点前面最少6个字符,不足补空字符。小数点后面最多2个字符,不足补0.

%o          按8进制进行输出。

%x           按16进制进行输出。

%c           表示输出字符。

%s           表示输出字符串

%%         输出百分号

%lu         输出long unsigned int

%u          输出unsigned int

%llu        输出long long unsigned int


练习1-3

修改温度转换程序,使之可以在顶部输出一个标题。

答:直接在输出表之前,打印出一个标题就可以了。注意格式控制。

#include <stdio.h>

int main(void) {
    const int fbegin = 0, fend = 300, step = 20;
    int i = fbegin - step;

    printf("%3s\t%s\n", "F", "C");
    while ((i+=step) <= fend) {
        printf("%3d\t%.1f\n", i, 5.0*(i-32.0)/9.0);
    }

    return 0;
}
注意%3s的使用。从而可以实现对齐输出。

# ./a
  F     C
  0     -17.8
 20     -6.7
 40     4.4
 60     15.6
 80     26.7
100     37.8
120     48.9
140     60.0
160     71.1
180     82.2
200     93.3
220     104.4
240     115.6
260     126.7
280     137.8
300     148.9

练习1-4

编写一个程序打印摄氏温度转换为相应华氏温度的转换表。

#include <stdio.h>

int main(void) {
    const int cbegin = 0, cend = 100, step = 10;
    int i = cbegin - step;
    double f = 0;

    printf("%3s\t%s\n", "C", "F");
    while ((i+=step) <= cend) {
        printf("%3d\t%.1f\n", i, 9.0*i/5.0 + 32.0);
    }
    return 0;
}

1.3 for 语句

for 语句应该还算比较简单的。

练习1-5

修改温度转换程序,要求以逆序打印温度转换表。

#include <stdio.h>

int main(void) {
    const int fbegin = 0, fend = 300, step = 20;
    int i = fend + 20;
    double c = 0;

    printf("%3s\t%s\n", "F", "C");
    while ((i-=step) >= fbegin) {
        printf("%3d\t%.1f\n", i, 5.0/9.0*(i-32.0));
    }
    return 0;
}

1.4. 符号常量

符号常量是我们需要注意的地方。也就是说,在程序里面,除了0,1这种经常使用的变量以外。其他的比如100,200,这种变量应该最好是使用

#define MAXSIZE 100

的方式给出。


1.5.1 文件复制

这里提供一个文件复制程序。唯一值得注意的是。变量ch的类型为int,而不是char类型。因为getchar()的返回值就是int类型。

#include <stdio.h>

int main(void) {
    int ch;
    while ((ch=getchar()) != EOF) {
        putchar(ch);
    }
    return 0;
}

练习1-6

验证表达式getchar() != EOF的值是0还是1.

答:

改写程序如下:

#include <stdio.h>

int main(void) {
    int ch;
    while (ch = getchar()!=EOF) {
        printf("%d\n", ch);
    }
    return 0;
}

练习1-7

编写一个打印EOF值的程序

答:

#include <stdio.h>

int main(void) {
    printf("%d\n", EOF);
    return 0;
}

1.5.2 字符计数

首先是计算输入字符的个数。

#include <stdio.h>

int main(void) {
    int cnt = 0;
    while (getchar() != EOF)
        ++cnt;
    printf("%d\n", cnt);
    return 0;
}

计算输入了多少行。

#include <stdio.h>

int main(void) {
    int cnt = 0, ch;
    while ((ch=getchar()) != EOF)
        cnt += ch == '\n';
    printf("%d\n", cnt);
    return 0;
}

练习1-8

编写一个统计空格、制表符与换行符个数的程序。

#include <stdio.h>

int main(void) {
    int space_cnt = 0, tab_cnt = 0, enter_cnt = 0, ch;
    while ((ch=getchar()) != EOF) {
        space_cnt += ch == ' ' ;
        tab_cnt += ch == '\t';
        enter_cnt += ch == '\n';
    }
    printf("%4s %4s %4s\n", "S", "T", "E");
    printf("%4d %4d %4d\n", space_cnt, tab_cnt, enter_cnt);
    return 0;
}

练习1-9

编写一个程序,把输入中的连续空格用一个空格替代。

#include <stdio.h>

int main(void) {
    int front_ch = 0, ch;
    while ((ch=getchar()) != EOF) {
        if (' ' != ch || (' ' == ch && ' ' != front_ch)) {
            putchar(ch);
            front_ch = ch;
        }
    }
    return 0;
}

其实很简单,只需要注意把前面一个字符的状态记住就可以。

练习1-10

编写一个程序,把输入中的'\t', '\b', '\' 替换为\t, \t, \\。

#include <stdio.h>

int main(void) {
    int ch;
    while ((ch=getchar()) != EOF) {
        if ('\t' == ch) printf("\\t");
        else if ('\b' == ch) printf("\\b");
        else if ('\\' == ch) printf("\\\\");
        else putchar(ch);
    }
    return 0;
}

1.5.3单词计数

方法其实也比较简单,就是统计一下空白符与非空白符交接处的个数。

如果用_表示空白符。那么_______________abbbbbb_________c

很明显,只有两个交接处。所以只有两个单词。

#include <stdio.h>

int main(void) {
    int ch, nw = 0, front_is_space = 1;
    while ((ch=getchar()) != EOF) {
        if ('\t' == ch || ' ' == ch || '\n' == ch)
            front_is_space = 1;
        else if (front_is_space) {
            front_is_space = 0;
            ++nw;
        }
    }
    printf("%d\n", nw);
    return 0;
}

练习1-11

你准备如何测试单词计数程序?如果程序中存在某种错误,那么什么样的输入最可能发现这种错误?

答:

没有输入

没有单词,只有换行符。

没有单词,比如只有空白符。

每个单词占一行。

单词出现于文本行行首的情况

单词都是出现在很多空白符后的情况。

练习1-12

编写一个程序,以每行一个单词的形式打印其输入。

有了前面统计单词个数的程序。这个程序只需要匹配到_____abc这种空白符与非空白符交接处的时候,打印一个回车应该就可以了。

#include <stdio.h>

int main(void) {
    int ch, front_is_space = 0;
    while ((ch=getchar()) != EOF) {
        if (' ' == ch || '\t' == ch || '\n' == ch) {
            front_is_space = 1;
        } else if (front_is_space) {
            front_is_space = 0;
            putchar('\n');
            putchar(ch);
        } else putchar(ch);
    }
    return 0;
}

1.6 数组


写一个程序对数字,空格,其他字符进行计数。

#include <stdio.h>
#include <string.h>

int main(void) {
    int cnt[10], nw = 0, ns = 0, ch, i;
    memset(cnt, 0, sizeof(cnt));

    while ((ch=getchar()) != EOF) {
        if ('0' <= ch && ch <= '9') ++cnt[ch-'0'];
        if (' ' == ch || '\n' == ch || '\t' == ch) ++ns;
        else ++ns;
    }

    for (i = 0; i < 10; ++i) {
        printf("%d ", i);
    }
    printf("\n");
    for (i = 0; i < 10; ++i) {
        printf("%d ", cnt[i]);
    }
    printf("\n");

    printf("Spaces: %d\n", ns);
    printf("Others: %d\n", nw);
    return 0;
}


练习1-13

编写一个程序,统计输入的单词的长度。水平方向的直方图比较好打印。打印一个垂直方向的直方图。

答:

首先我们要写一个程序,当我们输入一个单词的时候,他就可以返回这个单词的长度。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    int front_is_space = 1, ch, len = 0;
    while ((ch=getchar()) != EOF) {
        if (' ' == ch || '\t' == ch || '\n' == ch) {
            if (!front_is_space) {
                printf("%d\n", len);
                len = 0;
            }
            front_is_space = 1;
        } else {
            ++len;
            front_is_space = 0;
        }
    }
    return 0;
}


在此基础上,则很容易对单词进行计数处理了。

在这里,我们假设每个单词的长度都不超过255。这个设定当然是有一定的局限性的。当统计完成单词的长度之后,我们开始打印水平方面的直方图。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LEN 255

int main(void) {
    int front_is_space = 1, ch, len = 0;
    int cnt[MAX_LEN], i;

    memset(cnt, 0, sizeof(cnt));

    while ((ch=getchar()) != EOF) {
        if (' ' == ch || '\t' == ch || '\n' == ch) {
            if (!front_is_space) {
                ++cnt[len];
                len = 0;
            }
            front_is_space = 1;
        } else {
            ++len;
            front_is_space = 0;
        }
    }

    for (i = 0; i < MAX_LEN; ++i) {
        if (cnt[i]) {
            printf("%3d : ", i);
            for (ch = 0; ch < cnt[i]; ++ch)
                printf("-");
            printf("\n");
        }
    }
    return 0;
}


水平方向打印直方图是比较简单的。那么看一下垂直方向的直方图。

打印垂直方向的直方图。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LEN 255

int main(void) {
    int front_is_space = 1, ch, len = 0;
    int cnt[MAX_LEN], i, max_value = 0;
    int idx[MAX_LEN], iter;

    memset(cnt, 0, sizeof(cnt));

    while ((ch=getchar()) != EOF) {
        if (' ' == ch || '\t' == ch || '\n' == ch) {
            if (!front_is_space) { 
                ++cnt[len];
                len = 0;
            }
            front_is_space = 1;
        } else {
            ++len;
            front_is_space = 0;
        }
    }

    for (max_value = i = iter = 0; i < MAX_LEN; ++i) {
        if (cnt[i]) {
            max_value = max_value > cnt[i] ? max_value : cnt[i];
            cnt[iter] = cnt[i];
            idx[iter] = i;
            ++iter;
        }
    }

    for (i = 0; i < iter; ++i) {
        if (cnt[i] == max_value) {
            printf("%5d", max_value);
        } else printf("     ");
        printf(" ");
    }
    printf("\n");

    while (max_value > 0) {
        for (i = 0; i < iter; ++i) {
            if (cnt[i] == (max_value-1)) printf("%5d", cnt[i]);
            else if (cnt[i] >= max_value) printf("|---|");
            else printf("     ");
            printf(" ");
        }
        printf("\n");
        --max_value;
    }

    for (i = 0; i < iter+1; ++i) printf("******");
    printf("\n");
    for (i = 0; i < iter; ++i) {
        printf("%5d ", idx[i]);
    }
    printf("\n");
    return 0;
}


打印出来的效果图。

|---| |---|    18
|---| |---| |---|
|---| |---| |---|
|---| |---| |---|
|---| |---| |---|
|---| |---| |---|    13
|---| |---| |---| |---|
|---| |---| |---| |---|       
|---| |---| |---| |---|       
|---| |---| |---| |---|       
|---| |---| |---| |---|       
|---| |---| |---| |---|       
|---| |---| |---| |---|     6 
|---| |---| |---| |---| |---| 
|---| |---| |---| |---| |---| 
|---| |---| |---| |---| |---| 
|---| |---| |---| |---| |---| 
|---| |---| |---| |---| |---| 
|---| |---| |---| |---| |---| 
******************************
    1     2     3     4     5 

 

练习1-14

编写一个程序,用来统计字符出现的频率。用垂直方向的直方图来进行表示。

首先来个效果:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LEN 255

void print_histogram(int *cnt, int n) {
    int max_value, i, iter, idx[MAX_LEN];

    for (max_value = i = iter = 0; i < n; ++i) {
        if (cnt[i]) {
            max_value = max_value > cnt[i] ? max_value : cnt[i];
            cnt[iter] = cnt[i];
            idx[iter] = i;
            ++iter;
        }
    }

    for (i = 0; i < iter; ++i) {
        if (cnt[i] == max_value) {
            printf("%5d", max_value);
        } else printf("     ");
        printf(" ");
    }
    printf("\n");

    while (max_value > 0) {
        for (i = 0; i < iter; ++i) {
            if (cnt[i] == (max_value-1)) printf("%5d", cnt[i]);
            else if (cnt[i] >= max_value) printf("|---|");
            else printf("     ");
            printf(" ");
        }
        printf("\n");
        --max_value;
    }

    for (i = 0; i < iter+1; ++i) printf("******");
    printf("\n");
    for (i = 0; i < iter; ++i) {
        printf("%5d ", idx[i]);
    }
    printf("\n");
 
}

int main(void) {
    int front_is_space = 1, ch, len = 0;
    int cnt[MAX_LEN], i;

    memset(cnt, 0, sizeof(cnt));

    while ((ch=getchar()) != EOF) {
        ++cnt[ch];
    }

    print_histogram(cnt, MAX_LEN);
    return 0;
}


当直方图下面元素太多时,会出现问题,不过也还好了。你把输出重定向到一个文件,然后vim打开。再 set nowrap。

就可以看到想要的效果。

如果想要改进,就再改写一下程序。也不算太难。

1.7 函数

在这里,需要注意的是函数的声明方式:

int power(int x, int n);

不再采用传统的声明方式。

练习1-15
用函数的方式,来写华氏温度向摄氏温度的转换。

#include <stdio.h>

const double h2c(const double d) {
    return 5.0*(d-32.0)/9.0;
}

int main(void) {
    double temp = 0.0;
    while (temp < 300.0) {
        printf("%3.0f %3.1f\n", temp, h2c(temp));
        temp += 20;
    }
    return 0;
}


1.8 参数-传值调用

在这里,需要说明的是,对于C语言而言,所有的参数都是采用传值调用的。

比如

void can_not_change(int a) {
    a = 2;
}
int b = 1;
can_not_change(b); // b still equal to 1


void can_change(int *a) {
    *a = 3; // change *a;
    a = 0;
}
int *a = &b;
can_change(a); // b's value is changed.
               // but pointer a still unchanged.


但是需要注意的是传入数组参数的处理。当传入一维数组的时候,可以用int *a做为参数。当传入两维数组的时候,则不能用int **a做为参数。

1.9 字符数组

书上写了一个程序,是输入一个长度不超过1024的字符串的时候。找出最长的字符串来。

我没有照着书上的写,自己写了一个版本。用不着进行copy操作。

#include <stdio.h>
#define MAXLEN 1024

int _getline(char *str, int n) {
    int i = 0, ch;
    while (i < (n-2) && (ch=getchar()) != EOF && '\n' != ch)
        str[i++] = ch;

    if ('\n' == ch) str[i++] = ch;
    str[i] = 0;
    return i;
}

int main(void) {
    int len, max_len = -1;
    char a[MAXLEN], b[MAXLEN];
    char *input = a, *str = b, *t;

    while ((len = _getline(input, MAXLEN)) > 0) {
        if (len > max_len) {
            max_len = len;
            t = str; str = input; input = t;
        }
    }
    printf("maxlen = %d, %s\n", max_len, str);
    return 0;
}


练习1-16

题目:修改打印最长文本行的程序的主程序main,使之可以打印任何输入行的长度,并且尽可能多地打印文本。

#include <stdio.h>
#include <stdlib.h>
#define MAXLEN 10

int _getline(char **str, int *max_len) {
    int i = 0, ch;
    while ((ch=getchar()) != EOF && '\n' != ch) {
        if ((*max_len - 2) == i) {
            (*max_len) <<= 1;
            *str = (char *)realloc(*str, *max_len);
        }
        (*str)[i++] = ch;
    }
    (*str)[i] = 0;
    return i;
}

int main(void) {
    int len, max_len = -1;
    int ib = MAXLEN, sb = MAXLEN, temp;
    char *input = (char *)malloc(sizeof(char)*MAXLEN);
    char *str = (char *)malloc(sizeof(char)*MAXLEN);
    char *t;

    while ((len = _getline(&input, &ib)) > 0) {
        if (len > max_len) {
            max_len = len;
            t = str; str = input; input = t;
            temp = ib; ib = sb; sb = temp;
        }
    }
    printf("maxlen = %d, %s\n", max_len, str);
	free(input);
	free(str);
    return 0;
}

这里需要用realloc函数。尽管有点超前了。不过需要注意的是,在swap的时候,除了交换指针之外,还要注意交换buffer_size.也就是ib和sb的值。

 练习1-17

题目:编写一个程序,打印长度超过80个字符的所有输入。

#include <stdio.h>
#include <stdlib.h>
#define MAXLEN 10

int _getline(char **str, int *max_len) {
    int i = 0, ch;
    while ((ch=getchar()) != EOF && '\n' != ch) {
        if ((*max_len - 2) == i) {
            (*max_len) <<= 1;
            *str = (char *)realloc(*str, *max_len);
        }
        (*str)[i++] = ch;
    }
    (*str)[i] = 0;
    return i;
}

int main(void) {
    int len;
    int ib = MAXLEN;
    char *input = (char *)malloc(sizeof(char)*MAXLEN);

    while ((len = _getline(&input, &ib)) > 0) {
		if (len > 80) {
			printf("%s\n", input);
		}
    }
	free(input);
    return 0;
}

练习1-18

题目:编写一个程序,删除每个输入行未尾的空白符与制表符。并且删除完全是空白符的行。

#include <stdio.h>
#include <stdlib.h>
#define MAXLEN 10

int _delete_space_from_back(char *str, int n) {
	int i = n;
	while ((--i) >= 0 && (' ' == str[i] || '\t' == str[i]));
	str[i+1] = 0;
	return i + 1;
}

int _getline(char **str, int *max_len) {
    int i = 0, ch;
    while ((ch=getchar()) != EOF && '\n' != ch) {
        if ((*max_len - 2) == i) {
            (*max_len) <<= 1;
            *str = (char *)realloc(*str, *max_len);
        }
        (*str)[i++] = ch;
    }
    (*str)[i] = 0;
	i = _delete_space_from_back(*str, i);
	if (0 == i && EOF == ch) return EOF;
    return i;
}

int main(void) {
    int len;
    int ib = MAXLEN;
    char *input = (char *)malloc(sizeof(char)*MAXLEN);

    while ((len = _getline(&input, &ib)) != EOF) {
		if (len > 0) printf("%s\n", input);
    }
	free(input);
    return 0;
}

练习1-19

题目:编写一下字符串颠倒函数reverse()。

#include <stdio.h>
#include <stdlib.h>
#define MAXLEN 10

void reverse(char *str, int n) {
	char *i = str, *j = str + n - 1, t;
	while (i < j) {
		t = *i; *i = *j; *j = t;
		++i; --j;
	}
}

int _getline(char **str, int *max_len) {
    int i = 0, ch;
    while ((ch=getchar()) != EOF && '\n' != ch) {
        if ((*max_len - 2) == i) {
            (*max_len) <<= 1;
            *str = (char *)realloc(*str, *max_len);
        }
        (*str)[i++] = ch;
    }
    (*str)[i] = 0;
	reverse(*str, i);
	if (0 == i && EOF == ch) return EOF;
    return i;
}

int main(void) {
    int len;
    int ib = MAXLEN;
    char *input = (char *)malloc(sizeof(char)*MAXLEN);

    while ((len = _getline(&input, &ib)) != EOF) {
		if (len > 0) printf("%s\n", input);
    }
	free(input);
    return 0;
}

注意在反转的时候,不要把i < j写成i != j。在偶数个字符的情况下,容易出错。




评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值