@(C 语言)[基础, 编程]
薄薄一本书, 却记录了c 编程经常犯下的错误,再读,记录下。
词法
- 词法分析 : 大嘴法
编译器分解符号的方法是从左到右读入, 判断可能组成的最大的一个符号
a---b // (a--) - b
别复杂化, 使用括号,清晰直观
- 字符和字符串
char* pStr = "YES" // 'Y', 'E', 'S', 0 , 4 char
char ch = 'y'// a char
单引号实际代表一个整数
双引号代表指向无名数组的起始字符的指针(字符结尾 0)
使用库函数计算得到的字符串长度不包括结尾的0!
语法
- 理解函数声明
调用首地址为0的子例程 (void fun()),
(* (void(*)())0 )();
// call void fun()
// 声明指向上述fun的指针
void (*pfun)();
// 注意括号,void *rf(); 声明了返回void* 指针的函数。
pfun = &fun;// pfun = fun; 也可以
(*pfun)();
pfun(); // 简写
// 对0地址进行类型转换
pfun = (void (*)())0;
没必要多声明一个‘哑’变量pfun,因此如开端所写,对0进行类型转换,再解引用调用
// 直观的话
typedef void (*pfun)();
(* (pfun)0 )();
准则 : 按照使用的方式来声明
- 运算符优先级问题
对于这个问题,优先级记归记,不熟悉建议使用括号来显示保证。
记录下,吃过这个亏不止一次:
if (flag & 0x01 != 0) {}...
//want
if ((flag & 0x01) != 0) {}...
//actualy
if (flag & (0x01 != 0)) {}...
r = hi << 4 + low;
//watnt
r = (hi << 4) + low;
// actually
r = hi << (4 + low);
a = *p++;
// == *(p++) <——
a = *p; p++;
记录优先级可以写出更加优雅的代码, 不装X。
- 语句结束分号
//少写出错
if (i < 2)
return
i = 2;
//->
if (i < 2) return (i = 2);
//多写出错
if (i < 2);
a = 1;
//->
if (i < 2);
a = 1; // every time
switch 别漏
break
!!!!if else 的对应关系
else 始终与同一对括号内最近的未匹配if 结合
if (x == 0)
if (y == 0 ) {..}
else {
..
}
// 不同所缩进, 实际是
if (x == 0) {
if (y == 0) {..}
else {..}
}
避免悬挂式else, 使用{}进行匹配
if (x == 0) {
if (y == 0 ) {..}
} else {
..
}
语义
- 指针和数组
C语言中只有一维数组
int a[12] // 12个 int
int b[12][31] // 12个 int[31]类型数组的元素
// sizeof(a) == 12 * sizeof(int);
// sizeof(b) == 12 * (31 * sizeof(int))
// attention
/*
* 数组名是数组的首地址(符号表中对应地址)
* 数组操作 :数组地址 + 偏移地址 --> 内容
* 指针操作 : 指针 --> 数组地址 + 偏移 --> 内容
*/
int *p = a;
//sizeof(p) == sizeof(int*) // sizeof of pointer(32位 4, 64位 8)
//sizeof(*p) == sizeof(int)
对数组取sizeof可以得到数组的大小,但是对其他指针取sizeof取到的是平台地址的长度
2.数组根据他自身的类型,决定其在加减时,实际增减的内存地址值
int *p0 = (int*)0x0000;
char *p1 = (char*)0x0000;
++p0; ++p1; // p0 += 1; p1 += 1;
//p0 == 0x0004; p1 == 0x0001; // if sizeof int == 4
指向数组的的指针
char array[12][31];
char (*p)[31];
char **pp = array; //注意这里
p = array;
p[0] -->array[0];
++p -->array[1];
假设array在地址0x0000,那么p 也是地址0x0000, ++p后,p指到array[1],所以地址就是array + sizeof(char)*31
; 但是,pp,指向指针的指针,一开始也是指向array[0], 但是,++pp后,指向的和p不一样,而是加了一个sizeof(char*)的长度
,因为他的单位是一个指针大小,而p的单位是一个char charxx[31]的大小。
所以,请告诉指针,我指的到底是谁?
- 非数组的指针
char *s = "jj";
char *t = "xx";
//1 :strlen 不包括字符串结束字符
char *r = (char*)malloc(strlen(s) + strlen(t) + 1);
if (!r) {
//2 : malloc 可能申请失败
} else {
strcpy(r, s);
strcpy(r, t);
//..
}
//3 : 记得释放!
free(r);
- 数组作为参数传递给函数,已经转换为指针。
- 复制指针不等于复制指针指向的对象,东西只有一份,只是多了一个别称。
- 数组边界问题(
左闭右开
, 在一些用变量去索引的情况下,没有处理好,导致访问读取未位置的内容带来的错误。)
提到这种不对称数学角度来说不优美,却给程序设计带来了一些简化。
for (int i = 0; i < 6; ++i) {
...
}
1, 上下边界差就是元素数目
2, 上下相等,范围为空
3, 即使取值为空,上边界永远不小于下边界
- main 函数返回0代表执行成功,其他地方也可以默认这种操作,返回0代表成功。
连接
- 定义一处, 拿到内存; 其他地方使用, 声明
- 避免冲突,限定作用域,使用static
库函数
...
预处理器
- 注意宏定义错误空格
#define f (x) ((x) -1)
// #define f ((x) ((x) - 1))
宏定义中恰当使用括号,避免实际使用展开后由于优先级而带来的错误。
- 宏不是语句
#define assert(e) if (!e) assert_error(__FILE__, __LINE__)
if (..)
assert(..);
else
assert(..);
// 展开
if (..)
if (..) assert_error(__FILE__, __LINE__)
else
if (..) assert_error(__FILE__, __LINE__)
// 参考前面悬挂式else, 整理后看到实际效果不一致
if (..)
if (..)
assert_error(__FILE__, __LINE__)
else
if (..)
assert_error(__FILE__, __LINE__)
可移植性缺陷
其他
static
1,函数内的变量,静态变量, 作用域限定在该函数的“全局变量”, 函数退出也保存在内存,下次调用仍能使用该值
2,模块内的变量,限定该模块内使用
3,模块内的函数,限定该模块内使用const
不可改,保护,避免意外修改不想被改变的数据volatile
易变的, 避免被优化
如果一个变量存在在程序流程外被改变(多线程,中断等)声明为volatile保证每次都从内存读取,避免编译器优化,带来数据更新不一致等问题,保证访问的稳定。
char fifo[SIZE];
// 指针指向,不能通过指针修改fifo内容
char const *pff = fifo;
// 指针指向,不能修改指针内容, 即保证pff == fifo 永远
char * const pff = fifo;
// 修饰谁,靠近谁。
// pff 不可变,*(pff + xx) 随时可能改变。
static volatile char* const pff = fifo;
- 符号数扩展带来的问题
char a = 0x03;
char b = 0x81;
uint16_t c = a <<8 | b;
// c = 0xFF81 -->
// b 变成16bit 的时候,char b是有符号数,高位填充1(填充符号位)
unsigned char a = 0x03;
unsigned char b = 0x81;
uint16_t c = a <<8 | b; // c = 0x0381