- 这本书内容看懂不难,但想要在用的时候避免出现其中问题可能还和实践与记忆力相关,所以推荐边写边查。
词法陷阱
- 单双引号的意义
单引号括起字符:整数
双引号括起字符:指针
判断是否允许嵌套注释的例子
int nest = /*/*/ 0 */**/ 1;
// 如果允许,nest = 1
// 如果不允许,nest = 0 * 1;
语法陷阱
可以将常量强制转化为各种类型的指针
int *x;
char *c = "kkkkkk";
x = (int*)1;
x = (int*)c;
printf("%p\n", x);
优先级记忆
- 最高:() [] -> .
- 单目>双目>三目>逗号
- 双目:算术>移位>关系>逻辑(单>双,&>&&)> 赋值 > 条件(即三目)
- else悬挂
```c
if(x == 0)
if(y == 0)
else { z = x + y; f(&z); } /*error();
- c语言中只有四个运算符(&&,||,?:,逗号)存在求值顺序
- &&和||,先运算左侧再右侧
- a?b:c,先算a,根据a的值计算b、c
- 逗号运算符,先对左侧操作数求值,然后丢弃该值,再对右侧操作数求值。例:x =(a,b)先对a进行运算,然后丢弃a,最后求b,将b赋值给x
#include <stdio.h>
#include <stdlib.h>
void mcpy(char *&dst, char *src, int n)
{
while(n--)
{
*dst++ = *src++;
printf("%p\n", dst);
}
}
void buffflush(char *test, char *idx)
{
printf("flush : %s\n", test);
idx = test;
}
void bufwrite(char *p, int n, char *bufptr, char *buffer)
{
while(n > 0)
{
int k, rem;
if(bufptr == &buffer[9])
buffflush(buffer, bufptr);
rem = 10 - (bufptr - buffer);
k = rem > n ? n : rem;
mcpy(bufptr, p, k);
p += k;
n -= k;
}
}
int main(int argc, char **argv)
{
char str[200] = "1234567890abcdefghijklmnopqrstuvwxyz";
char *s = (char *)malloc(sizeof(char)*200);
char *buff, *bufidx;
buff = (char*)malloc(sizeof(char)*10);
bufidx = buff;
printf("%p\n", buff);
mcpy(bufidx, (char*)str, 30);
printf("%p\n", bufidx);
printf("%s\n%s\n", bufidx, buff );
// bufidx和buff位置依然相同,你能找出错在哪吗?
// bufwrite(s, 22, bufidx, buff);
return 0;
}
/**
* 原因:上面传递的是指针,虽然指针可以修改指向的变量,但是其自身依旧是临时变量
* 解决:要真正修改其自身地址,c可以使用二维指针,c++可以使用指针引用,所以上面的写法是错误的
*/
void mcpy(char *dst, char src, int n) { while(n—) { (dst) = src++; (dst)++; printf(“%p\n”, *dst); } }
void buffflush(char test, char **idx) { printf(“flush : %s\n”, test); memcpy(test, “”, 10); idx = test; }
void bufwrite(char p, int n, char bufptr, char *buffer) { while(n > 0) { int k, rem; if(bufptr == &buffer[10]) buffflush(buffer, &bufptr); rem = 10 - (bufptr - buffer); k = rem > n ? n : rem; mcpy(&bufptr, p, k); p += k; n -= k; } }
int main(int argc, char **argv) { char str[200] = “1234567890abcdefghijklmnopqrstuvwxyz”;
char *s = (char *)malloc(sizeof(char)*200);
char *buff, *bufidx;
buff = (char*)malloc(sizeof(char)*10);
bufidx = buff;
bufwrite(str, 22, bufidx, buff);
return 0;
}
2. 如何在a文件之内调用b文件中的变量和函数
```c
// a.h
#ifndf _A_H_
#define _A_H_
#include <stdio.h>
int arg = 0x10000000;
double square(double x);
#endif
// a.c
#include "a.h"
double square(double x)
{
return x * x;
}
// b.c
#include "a.h"
// 调用别的文件中变量,需要加extern,说明别的文件中已经声明且赋值过
extern int arg;
// 调用其他文件中函数,仅需要将所需函数头文件导入,同时再次声明即可
double square(double x);
int main(int argc, char **argv)
{
printf("%lf\n", square(1.22));
return 0;
}
- extern、static
- 形参、实参、返回值
- 如果一个函数在被定义之前被调用,那么它的返回类型就被默认为整型
- 如果main函数和square函数在不同的文件中
- 必须在调用它的文件中声明square函数 ```c double square(double);
main() { printf(“%g\n”, square(0.3)); }
- 如果一个函数没有float、short或char类型的参数,在函数声明中完全可以省略参数类型的说明,但是要求自己注意函数调用时实参的输入,示例如下
```c
#include <stdio.h>
int func();
int main(int argc, char **argv)
{
func(1, 2, 3);
return 0;
}
int func(int x, int y, int z)
{
printf("%d\n", x + y + z);
return x + z + y;
}
- 检查外部类型
- 假设a.c中声明int a,而b.c中声明extern long a,这里可能会引起几种情况
- 编译器报错
- 恰好能工作,比如32位机器的long和int相同
- 两个实例虽然要求存储空间大小不同,但它们共享存储空间的方式恰好满足:赋给其中一个值,对另一个也有效,比如,long的低位和int共享存储空间,当给long类型赋值时,恰好把低位赋给int,本来错误的程序看起来似乎能正常工作
- 共享存储空间,使得对其中一个赋值,其效果相当于同时给另一个服了完全不同的值,程序不能正常工作
库函数
- 返回整型的getchar ```c // getchar函数原型为 int getchar(void);
// 它返回一个整型数据,但是如果按照下面写法则有可能导致错误
include
int main() { char x; while((x = getchar()) != EOF) putchar(); }
// int会被截断,转化为char,我们知道int的其他部分会被舍弃只留下低八位 /*
- 以下几种可能:
- 被截断之后,刚好是EOF,被迫跳出循环
- 被截断之后,永远得不到EOF,死循环
- 似乎能正常工作,是因为编译器实现不正确,依然将getchar返回值与EOF比较,
- 而非用c与EOF比较 */ ```
- 更新顺序文件
想要对文件进行自由交错的读写操作,单纯通过fopen(filename, “r+”)是不行的。为了保持与过去不能同时进行读写操作的程序的向下兼容性,一个输入操作不能随后直接紧跟一个输出操作,反之亦然。
何为向下兼容性(Downward Compatibility)呢?
- 在计算机中指在一个程序或者类库更新到较新的版本后,用旧的版本程序创建的文档或系统仍能被正常操作或使用,或在旧版本的类库的基础上开发的程序仍能正常编译运行的情况。 ```c // 上面所说的不行,如下 FILE *fp; fp = fopen(filename, “r+”); struct record rec;
while(fread((char )&rec, sizeof(rec), 1, fp) == 1) // rec需要被转为char { // do some operations to rec if(/ rec must be rewritten in file /) { fseek(fp, -(long)sizeof(rec), 1); fwrite((char )&rec, sizeof(rec), 1, fp); // rec需要被转为char } }
// 看起来已经很小心地转化了类型,同时也在fread和fwrite之间填入fseek,解决了历史遗留问题 // 但依然不对能看出来吗?
// 问题出在read、seek、write、seek、read… // 上面只有read、seek、write、read、seek,没有真正的全部插入seek
3. 缓冲输出与内存分配
```c
setbuf(stdout, buf)
fflush(FILE *fp) 清空缓存
- 使用errno检测错误
- 直接使用if(errno)检测,不能保证之前某些函数修改过errno
- 在if(errno)前添加errno=0,但是不能保证某些成功调用的函数依然有可能修改errno的情况。
- 比如:使用开启新文件的函数,其中需要先检查有没有同名文件,如果没有则修改errno,再新开一个文件
- 比较好的方式是:先检测函数返回值,确定函数出错后再进行errno的判断
/* 调用库函数 */
if(判断函数返回值)
{
检查errno
}
- 库函数signal
#include <signal.h>
signal(signal type, handler function);
- 信号在C程序任何时候有可能发生,甚至是在某些复杂库函数(如malloc)的执行过程中,就安全性来讲,信号处理函数不应该调用上述函数。可能出现一下不好情况:
- malloc在执行过程中被信号中断,malloc函数用来跟踪内存的数据结构可能只有部分被更新。如果signal处理函数再调用malloc函数结果可能是malloc函数用到的数据结构完全崩溃
- 在signal处理函数中使用longjmp退出,也不安全:信号可能发生在某些库函数未完全更新数据结构过程中
- 结论:信号处理异常棘手,我们要采取守势,让signal处理函数尽可能简单
预处理器
上面f后多一个空格,代表(x) ((x) - 1)
上述情况出现在宏定义,宏调用则是f(x)和f (x) 表示相同含义
2. 宏不是类型定义,在声明多个变量的情况下最好还是用typedef,考虑可移植性的情况也推荐使用typedef
<a name="QS3lH"></a>
## 可移植性缺陷
1. 字符是有符号整数还是无符号整数
char到int,无符号整数只需要在多余位上添加0,有符号整数应该同时复制符号位
```c
#include <stdio.h>
int main(){
printf("%d\n", sizeof(int)); // at first, show size of integer on your pc
unsigned char x = 0xff;
printf("%u\n", x);
printf("%d\n", x);
char y = 0xff;
printf("%u\n", y);
printf("%d\n", y);
return 0;
}
输出结果是:
4
255
255
4294967295
-1
这里面反映出一些有趣的点:
- 如果直接声明为unsigned char,那么无论是什么编译器将该字符转换为整数都是需要填充0
- 如果是一个char,将其强制转换为unsigned也不是直接得到等价无符号整数(填充0类型),其中是因为转为无符号整数时,编译器是将其先转化为int
- 除数运算时发生的截断 ```c q = a / b; r = a % b;
假定b大于0
// 我们希望a、b、q、r保持如下关系
- q * b + r == a
- 如果改变a的正负号,q会被改变符号,但不会改变绝对值
- 当b > 0时,我们希望保证r >= 0且r < b
/**
############################简要测试程序如下
*/
include
include
int main(){ int a, b, q, r; while(~scanf(“%d %d”, &a, &b)) { q = a / b; r = a % b; int newQ = (-a) / b;
printf("q : %d\nr : %d\n", q, r);
// rule 1 test
if(q * b + r == a)
printf("Obey the first rule.\n");
else
printf("Violate the first rule.\n");
// rule 2 test
if(newQ == (-q) && abs(q) == abs(newQ))
printf("Obey the second rule.\n");
else
printf("Violate the second rule.\n");
// rule 3 test
if(b > 0 && r >= 0 && r < b)
printf("Obey the third rule.\n");
else if(b < 0)
printf("Do not reach the third rule.\n");
else
printf("Violate the third rule.\n");
}
return 0;
}
// 对于取模运算,c语言做到将结果的正负号和被取模数相同,无论模数是什么符号 /**
############################简要测试程序如下
*/
include
include
int main() { int x; while(~scanf(“%d”, &x)) { printf(“%d %% 3 = %d\n”, x ,x % 3); printf(“%d / 3 = %d\n”, x, x / 3); printf(“%d %% -3 = %d\n”, x, x % -3); printf(“%d / -3 = %d\n”, x, x / -3); }
return 0;
}
```