建议略微读我的笔记之后就去看原书吧,经过我的思考写出来的,终究不是原作者想表达的东西。也正因为这样,我跨出了读原著的第一步。
借了原版和中文版,先看原版再看翻译,所以看得有点吃力。我能说今天只看了三页么。趁着记忆还清楚先写写吧(读两个版本印象还真是深刻呢)。还有,图书馆很给力,我晚上申请借阅,第二天就给我从另一个校区送过来了。
序:
这不是一本入门教材,而是C语言的“第二本”书,对原有知识的补充和学习一些C语言的小技巧。
1. 穿越时空的迷雾
1.1 C语言的史前阶段
C语言是从一个失败的项目中产生的,一个通用电气,MIT,还有等等一起弄的一个叫Multics的项目,该项目遇到了许多麻烦,最后失败了,这个项目后来被看作是失败的参考仓库。
一个叫Ken Thompson的研究人员对另一个操作系统感兴趣,在PDP-7上写了一个更简易的操作系统,名为UNIX,Thompson又创建了B语言,用B语言对UNIX编程,但是B语言的效率十分低下,甚至不能给UNIX编程,在后来的新B语言中优化了性能。名为New B语言,就是后来的C语言。
对于编译器的开发者来说,效率等于一切。
1.1~1.8是C语言的更新迭代过程,再次不再累述。
1.9阅读ANSIC标准,寻找乐趣和脾益
如果实参是char**,形参是const char **。则编译器会发出警报。参数传递的过程类似赋值过程,一个char **的值不能赋值给一个const char **类型的对象。
因为在标准中对指针的赋值有这两条要求
两边的操作数都是相容类型的指针,
左边的指针指向的类型必须含有右边的指针指向的类型所含有的所有限定条件。
正因为如此,以下的代码是合法的
char *p;
const char *ccp;
ccp=p;
左边的操作数所指向的类型具有右边的操作数所指向的类型的所有限定符,以及自身的const限定符
另:
const char *指的不是一个有限定内容的char指针,而是一个指向const char类型的指针。也就是说const char *指针的内容是可以改变的,但是它指向的内容是不可以被改变的。
所以:
const int limit=10;
const int *limitp=&limit;//把limit的地址赋给limitp这个指针
int i=27;
limitp=&i;//虽然limitp指向的内容不可以被改变,但是它本身是可以被改变的,把它变为i所在的地址
如果输出*limitp的话,会显示i的值27
那么回归到最初的问题,char 指的是指向一个char指针的指针,const char指的是一个指向有const限定的const char指针的指针,他们指向的东西不相同,因此他们是不相容的。
1.10 “安静地改变”究竟是有多安静
- 两个不同类型的变量进行运算时,永远会自动地把变量转换成
精度更大和范围更广的那个变量类型。
2. 这不是Bug,而是语言特性
2.1这关语言何时,在Fortran里这就是bug啊,
Fortran里的一些bug。很简短的一节
2.2多错之过
const不是一个真正的常量,而switch语句的case要求是一个常量。所以以下代码会编译出错。
const int two=2;
switch(i)
{
case 1:
printf("case 1\n");
case two:
printf("case two");
}
名词fall through:意思就是如果case语句后面不加break,程序就会继续执行下去
一个忘了的知识点,->表示的是指向结构体成员。
2.2.2粉笔也成了可用的硬件
ANSI C的一个新特性是相邻字符串常量会被制动合并成一个字符串
#include
int main()
{
char *available_resouces[]={
"clor monitor",
"big disk",
"Cray" //这少了个逗号,所以后面被合并
"on-line drawing routhines"
};
int i=0;
while(i<3)
{
printf("%s",*(available_resouces+i));
i++;
}
return 0;
}
在以上代码中,由于Cray后面少了个逗号,于是两个字符串就被合并了。输出结果为Crayon-line drawing routhines
2.3误做之过
2.3.1骆驼背上的重载
C语言中有很多符号会有重载,也就是不同的意义。
###2.3.2有些运算符的优先级是错误的
p=N*sizeof*q;
这里其实是一个乘号,sizeofq表示的是取指针q所指向的数据类型的大小。
apple=sizeof(int) *p;
上面这行代码表示int的长度乘以一个数p,而不是将p指针所指向的数据类型变为int,因为当p真的是一个指针时,程序会报错。
2.3.3早期gets()中Bug导致了Internet蠕虫
gets()函数不会检查缓冲区的空间,如果gets()函数读入的字符数量超过了缓冲空间,gets()函数会将多余的字符写入堆栈中。黑客可以通过这些多余的字符修改堆栈中某个项目的内容。
2.4少做之过
2.4.1用户名中若有字母f,便不能收到邮件
bug代码
if(argv[argc-1][0]=='-'||(argv[argc-2][1]=='f'))
readmain(argc,argv);
else
sendmail(argc,argv);
3分析C语言的声明
3.1只有编译器才喜欢的语法
char *p[3]
表示一个char指针类型的数组,并且能以*p[i]这种形式访问指针所指向的内容
char (*p)[3]
表示一个指向数组的指针
3.2声明是如何形成的
4 令人震惊的事实,数组和指针并不相同
4.1数组并非指针
4.3什么是声明,什么是定义
定义:只能出现在一个地方,确定对象类型并分配内存。
声明:可以多次出现
extern用于变量的用法:
extern int a;//声明一个全局变量a
int a; //定义一个全局变量a
extern int a =0 ;//定义一个全局变量a 并给初值。一旦给予赋值,一定是定义,定义才会分配存储空间。
int a =0;//定义一个全局变量a,并给初值,
声明之后你不能直接使用这个变量,需要定义之后才能使用。
当你要引用一个全局变量的时候,你就要声明extern int a;这时候extern不能省略,因为省略了,就变成int a;这是一个定义,不是声明。
4.3.1数组和指针时如何访问的
X=Y;
在这里,符号“X”表示的是X所代表的地址,这被称为左值。
符号“Y”的含义是Y所代表的地址的内容。这被称为右值。
extern char *p
表示的是一个指针而不是一个数组。
5对链接的思考
C语言程序生成的过程,和早期的图灵测试
6运动的诗篇:运行时数据结构
6.8 setjmp和longjmp
setjmp(jmp_buf j)函数必须先被调用,它表示“使用变量j记录现在的位置,函数返回值为0”
longjmp(jmp_buf j,int i)可以接着被调用,它表示“回到j所记录的位置”,但它的返回值是i。
第一次看到这么神奇的代码
#include
#include
jmp_buf buf;
#include
void banana(){
printf("in banana()\n");
longjmp(buf,1);
printf("you'll never see this,because i longjmp'd");
}
int main()
{
if(setjmp(buf))
printf("back in main\n");
else{
printf("first time through\n");
banana();
}
return 0;
}
7. 对内存的思考
讲了历史上的一些处理器和内存,由于学识有限,不能完全理解这章的内容。
8. 为什么程序员无法分清万圣节和圣诞节
8.6 不需要按回车键就能得到一个字符
kbhit()
getch()
getche()