一个句子哪怕其中的每个单词都拼写正确,而且语法也无懈可击,任然可能有歧义或者并非书写着希望表达的意思。程序也有可能表面看上去是一个意思,而实际是哪个的意思却想去甚远。
3.1 指针与数组
1:数组的大小必须在编译期就作为一个常数确定下来,不能动态分配数组;
2:对于数组,我们只能够做两件事:确定该数组的大小,以及获得指向该数组下标为0的元素的指针;
3:任何一个数组下标运算,都等同于一个对应的指针运算;
4:如果 array_test 不是用于sizeof的操作数,那么 array_test 总是被转换成一个指向 array_test 数组的起始元素(下标为0)的指针;
int array_test[12][31] = {0};
sizeof(array_test) 等于 12*31*sizeof(int),而不是 指针的大小
char test[] = "hello";
printf("%s\n", test);
// 等价于
printf("%s\n", &test[0]);
int a[3];
int *p;
// 把数组a中下标为0的元素的地址赋值给P
p=a;
// 报错, &a 是一个指向数组的指针,p是一个指向整形变量的指针
p=&a
// 建议使用 下标来操作数组中的元素,不要使用指针操作
*a = 1; 等价于 a[0] = 1;
二维数组
int calendar[12][31];
int (*month_p)[31];
int *p;
month_p = calendar; // 正确写法
p = calendar; // 报错,指针类型不一致
3.2 非数组的指针
字符串常量代表代表了一块包括字符串中所有字符以及一个空字符(‘\0’)的内存区域的地址;
将字符串 s 和 字符串 t,连接成单个字符串 r
// 定义一个字符指针,在不确认 s+t 的大小情况下,不能直接分配空间
char *r;
// 字符串有结束符,一定要多申请一个
r = malloc(strlen(s) + strlen(t) +1);
if (r == NULL)
return; // 没有申请到内存空间
strcpy(r, s); // 把 s 指向的字符串, 复制到 r
strcat(r, t); // 把 t 指向的字符串, 复制到 r 的结尾
// 结束一定要释放内存空间
free();
3.3 作为参数的数组声明
C语音会自动的将作为参数的数组声明,转换为相应的指针声明;因此,将数组作为函数参数毫无意义
int strlen(char s[])
{
}
// 等价于
int strlen(char *s)
{
}
如果一个指针参数并不实际代表一个数组,采用数组形式的记法经常会起到误导作用;
extern char *hello; // 表示一个指针
extern char hello[]; // 表示一个数组
main 函数的定义
argv 是一个指向某数组的起始元素的指针,该数组的元素为字符指针类型;
int main(int argc, char* argv[]);
// 等价于
int main(int argc, char** argv);
3.4 避免 举隅法
举隅法:以含义更宽泛的词语来代替含义相对较窄的词语,或者相反; 对应C语言中一个常见的陷阱-混淆指针与指针所指向的数据;
复制指针并不同时复制指针所指向的数据
char *p,*q;
p="xyz"; //
// p和q 同时指向 一段 xyz\0 的内存空间
q=p;
3.5 空指针并非空字符串
常数0 常用一个符号来代替
#define NULL 0
当我们将 0 赋值给一个指针变量时,绝对不能企图使用该指针所指向的内存中存储的内容;
if (p == (char *)0) // 合法的
if (strcmp(p, (char *)0) == 0) // 非法的,因为 strcmp 的实现会去查看指针参数所指向的内容
3.6 边界计算与不对称边界
边界计算原则
原则一:首先考虑最简单情况下的特例,然后将得到的结果外推;
原则二:仔细计算边界,绝不掉以轻心;
边界计算方法:用第一个入界点和第一个出界点来表示一个数值范围;
// 不推荐写法
if ((x>=1) && (x<=8)) // 1-2-3-4-5-6-7-8, 共8个数
// 推荐写法
if ((x>=1) && (x<9)) // 9-1=8
针对数组计算边界
边界计算方法,对数组类型的数据计算特别便利
int a[10], i;
for (i=0; i<10;i++) // 正确写法
for (i=0; i<=9;i++) // 错误写法
针对缓冲区计算边界
根据边界计算方法,很容易的计算缓冲区中已使用内存,未使用内存等参数;
#define MAX_SIZE 1024
char buffer[MAX_SIZE];
char *ptr = &buffer[0];
*ptr++ = c;
// 缓冲区已存放的字节数
saved = ptr - buffer;
// 缓冲区存满
if ((ptr - buffer) == max_size)
// 缓冲区未使用字符数
no_use = max_size - (ptr - buffer);
3.7 求值顺序
C语言中只有四个运算符(&&, ||, ?: 和 ,)存在规定的求值顺序。其它所有运算符对齐操作数的求值顺序,都是未定义的;
运算符 &&和|| 首先对左侧操作数求值,只在需要时才对右侧操作数求值。
运算符 ?: 有三个 操作数,在a?b:c 中,操作数 a首先被求值,根据a的值再去操作 b或者c;
逗号运算符,首先对左侧操作数求值,然后该值被丢弃,再对右侧操作数求值;
//两个参数, x和y 没有先后顺序
fun1(x, y);
//一个参数,先x后y
func2((x,y))
// 如果此时不先对 y!=0求值,那么当y=0是,x/y 则是非法的
if (y!=0 && x/y)
func3();
int i=0;
while (i<n)
y[i] = x[i++]; //异常代码,假设了 y[i]的地址,将在i的自增操作执行之前被求值
// 正确写法
while(i<n)
{
y[i] = x[i]; // 不要写容易被误解的代码,编译器也可能会误解
i++;
}
3.8 运算符 &&,||和!
不要混用& 和 &&,&&是有先后执行顺序的
// 当i的值< tabsize时候,才会去计算 tab[i]
while (i < tabsize && tab[i] != x)
// 无论i值如何,都会去操作 tab[i]; 失去了原先的含义
while (i < tabsize & tab[i] != x)
3.9 整数溢出
当操作两个有符号数的时候,就有可能发生 溢出,产生预期之外的结果;
当发生溢出时,所有关于结果如何的假设都不再可靠;因为不用的编译器上对溢出处理的方式不一致
void func5()
{
char a=10;
char b=20;
char c = 10*20;
printf("c= %d \n",c ); // 溢出了,c= -56
}
3.10 为函数main提供返回值
要习惯养成给main函数设定返回值;当A程序被B程序调用的时候,在main里面设定返回值,B程序才能知道A程序是执行成功还是失败的;
// A程序
main()
{
// something, 当程序发生崩溃是,B程序也无法得知
}
int main()
{
// something
return 0; // 只有当 main函数返回0时,B程序才判断A程序返回成功
}