观感
C语言之父写的C语言指南,阅读的时候怎么能不怀着一点点崇敬的心情。
这本书的第一观感是真的很薄,一共250+页,从167页开始就是附录了。整个目录只有3页纸,这么薄的书在我的书架上只有《C陷阱与缺陷》能够一战。
进入到正文开始阅读后发现,信息量真的很大,要在这一百多页中介绍C语言的基本使用,信息密度可想而知。读起来并不轻松,不是太推荐C语言纯新人阅读,真怕造成“C语言从入门到放弃”。
目的
这里我摘录一些读书的心得和要点,进一步压缩篇幅,方便自己在时间紧迫的情况下快速梳理所有要点。
准备
为了验证书中的代码和做书中的练习,需要准备一个能够编译和运行C语言程序的环境。为了简便起见,我使用了ubuntu系统,命令行方式编译运行比较方便快捷。
为了少打一些字,提高效率,使用以下Makefile来编译目录下所有的.c文件到对应的可执行文件:
[Makefile]
src := $(wildcard *.c)
obj := $(patsubst %.c, %.o, $(src))
out := $(patsubst %.o, %, $(obj))
%.o: %.c
gcc -c -o $@ $<
%: %.o
gcc -o $@ $^
all: $(out)
clean:
rm -f $(obj) $(out)
这样,每次修改或者新编写了一个C语言程序源文件后,直接在命令行执行 make 就可以了。
基本概念
类型
- C程序的3个组成要素:变量、函数、语句
- C程序处理的数据对象:变量、常量
- 变量有类型 - int float char short long double
- 常量也有类型
- 1234 int型
- 1234L long int型
- 1.0 double型
- 1.0f float型
- 1.0L long double型
- 0XFUL unsigned long类型,值为15
- ‘c’, ‘\n’, ‘\123’,’\0x20’ 都是字符型常量
- 字符串或者字符串常量,“hello world\n”
- printf函数不会自动添加换行符,多条printf并不会自动产生行的输出
- 转义字符序列,escape sequence,\n \t \b \” \\
#include <stdio.h>
int main()
{
printf("hello ");
printf("world");
printf("\n");
return 0;
}
- 变量必须先声明后使用,声明位置在函数起始处,执行任何语句之前
- 建议每行只书写一条语句,运算符两边加上空格
- %3d 右对齐占用几个字符空间,%3.1f 小数点保留几位
- 5 / 9,5.0 / 9.0 ,整数舍位,浮点常数也要写成浮点的样式
#include <stdio.h>
int main()
{
float fahr, celsius;
int lower = 0;
int upper = 300;
int step = 20;
printf("exercise 1_3\n");
printf("\tFahr to Celsius\n");
fahr = lower;
while(fahr <= upper)
{
celsius = (5.0 / 9.0) * (fahr - 32);
printf("%3.0f\t%6.1f\n", fahr, celsius);
fahr = fahr + step;
}
return 0;
}
- %o %x %s %%
- 可以出现变量值的位置,都可以用产生该变量的表达式
- #define 定义符号常量,通常大写,末尾没有分号,预编译替换的时候如果不在引号内,不是更长名字的一部分,都会按照#define替换
#define LEAP 1 /* 闰年*/
int days[31 + 28 + LEAP + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31]; /* 这些数字常量和符号常量在编译的时候,就进行了求值,所以编译之后就是一年的天数了*/
- 文本流,包括多行字符,每行包括0个或者多个字符,每行以换行符结尾,文本流以EOF(end of file)结尾,EOF长度超过字符类型,一般定义成FFFFFFFF (-1)
- c = getchar(),从文本流读入下一个字符,putchar(c)向屏幕输出一个字符
#include <stdio.h>
int main()
{
int c;
while((c = getchar()) != EOF)
putchar(c);
return 0;
}
运算符
- 自增自减,前缀后缀,n++, ++n
- 使用更大的整数表示范围,用double 类型,当做整数运算
- 关系运算符 == ,!=, >, >=, <, <=
- 位运算符 &, |, ~, <<, >>
- 字符常量,单引号内部的一个字符,数值是一个比较小的整数,’A’, ‘\t’
#include <stdio.h>
int main()
{
double nl;
int c;
nl = 0;
while((c = getchar()) != EOF)
{
if (c == '\n')
nl++;
}
printf("line number: %.0f\n", nl);
return 0;
}
- 逻辑运算符 && , ||, !,遵循短路求值
- 函数的定义,存放的位置不重要,只要不跨文件即可
- 函数的参数传递,是值传递,不是引用传递;形参名称只在函数内部有意义
- 编译时遇到函数名,需要已经声明过,但可以不必知道定义在哪里
- 字符串以’\0’标记结束
/* strlen函数:返回字符串s的长度*/
int strlen(char s[])
{
int ;
i = 0;
while(s[i] != '\0')
++i;
return i;
}
变量
- 变量类型
- 自动变量 - 函数内部定义的变量,作用域函数内部,生存期函数执行
- 外部变量 - 函数外部定义的变量,只定义一次,编译器为其分配存储单元。使用的时候先声明
- 声明在函数内部,从声明的位置起到本函数结束有效
- 声明在函数外部,从声明的位置起到本文件结束有效
- 外部变量定义在某个c文件中(此文件中使用它不需要声明为extern,直接用就可以),通常声明在h文件中(使用它的各c文件需要Include这个头文件)
- 静态变量
- static 全局变量 - 本文件可见,程序的其他文件不可见,名字不会冲突
- static 局部变量 - 本函数可见,却别于自动变量,函数结束也不会释放
- 复杂类型变量
- 枚举
- 结构
- 联合
enum months {JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC};
enum months a;
enum months b;
a = JAN;
b = MAY;
- 变量声明
- 显式,隐式,形参就是隐式声明
- 声明同时可以进行初始化
- 建议一个变量单独用一行声明
- 全局或者静态变量的声明可以同时进行初始化,初始化的动作只进行一次
- 声明时可以使用const限定符,表示不可以修改,数组的话,表示数组的所有元素都不能修改
一些有意思的小程序
/* atoi函数:将字符串s转换为相应的整数*/
int atoi(char s[])
{
int i;
int n = 0;
for (i = 0; s[i] >= '0' && s[i] <= '9'; ++i)
{
n = 10 * n +(s[i] - '0');
}
return n;
}
/* strcat函数:将字符串t接到字符串s的尾部,s必须有足够大的空间*/
void strcat(char s[], char t[])
{
int i, j;
i = j = 0;
while (s[i] != '\0')
i++;
while((s[i++] = t[j++]) != '\0')
;
}
/* getbits函数:返回x中从第p位开始的n个位*/
unsigned getbits(unsigned x, int p, int n)
{
return (x >> (p + 1 - n)) & ~(~0 << n);
}
/* 删除数字最右边的一位值为1的二进制位*/
/* 可以用于快速判断是否是只有一个位等于1的二进制数 */
x &= (x - 1);