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