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;
- }
- # ./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。在偶数个字符的情况下,容易出错。