第3章 语义陷阱
3.1 指针与数组
- C 语言只有一维数组,且数组的大小必须在编译期就确定。数组中的元素可以是任意类型。
- 对于数组,除了确定数组的大小和获得指向下标为 0 的元素的指针外,其他的对于数组的操作都是通过指针进行的,表现为以数组下标操作。
int a[3];
该语句声明了 a 是一个拥有3个整型元素的数组。
strcut {
int p[4];
double x;
}b[17];
表示数组 b 有17个元素,每个元素都是一个结构体,结构体中包含一个4个元素的整型数组 p 和一个双精度类型变量 x 。
int calendar[12][31];
二维数组的声明,表示该数组有12个数组类型的元素,每个元素都是一个拥有31个整型元素的数组。(先12后31)
- 任何指针都指向某种类型的变量,指针初始化时,右侧必须是地址值。
- 如果一个指针指向的是数组中的元素,那么对指针的加减操作就相当于对数组下标的操作。
- 指针加一,表示指向内存中的下一个变量的首地址,而不是原指针所指向的地址加一,具体地址值移动多少,取决于指针所指向变量的类型。
- 指向同一数组的两个指针,可以通过加减互相得到。指向不同数组的两个指针,即便所指向的地址在内存中正好间隔一个数组元素的整数倍,结果也未必是正确的。
- 数组名代表指向下标为 0 的元素的指针,
&a
代表指向数组的指针,而不是指向数组的第一个元素。 - 特殊情况,sizeof(数组名),计算的是整个数组的大小,而不是指向数组的元素的指针的大小。
a[2]
与2[a]
含义相同- 二维数组的数组的每一个“行”下标都代表一个数组的首地址,数组的大小由“列”值决定。
3.2 非数组的指针
非数组情况主要是对字符串的操作。
合并字符串:
char *r , *malloc();
//动态申请空间,大小为两个字符串长度加一个'\0'字符
r = malloc(strlen(s) + strlen(t) +1);
//如果malloc运行失败,会返回一个空指针
if(!r)
{
complain(); //输出错误提示
exit(1);
}
strcpy(r,s);
strcat(r,t);
//一段时间后释放malloc分配的空间
free(r);
3.3 作为参数的数组说明
- C语言会自动地将作为参数的数组声明转换为对应的指针声明。
int strlen(char a[])
{ }
等同于
int strlen(cahr* a) //指针参数不代表数组,仅代表数组的第一个元素的地址
{ }
- 指针参数代表一个数组,如main函数的第二个参数:
maiun(int argc, char* argv[]) //强调的重点在于argv是一个指向某数组的起始元素的指针,且该数组的元素为字符指针类型
{ }
等同于
maiun(int argc, char** argv)
{ }
3.4 避免“举隅法”
C语言中复制指针时,并不复制指针所指向的内存数据,所以复制后的指针与原指针指向同一个地址的内存数据,改变任意一个指针所指向的内容,另一个指针所指向的内容也会改变,因为本质是同一个数据。
3.5 空指针并非空字符串
C语言中,对于空指针,不能使用该指针所指向的内存中存储的内容,不能作为函数的参数进行调用,
3.6 边界计算与不对称边界
int i, a[10];
for( i=1; i<=10; i++ )
{
a[i] = 0;
}
本意是将数组 所有元素置 0,当 i 等于 10 时,超出数组长度,计数器 i 会被赋值 0 ,所以会陷入死循环。
不对称边界,用第一个入界点和第一个出界点表示一个数值范围,入界点包含在取值范围内,出界点不包含在取值范围内,例如:
x >= 16 && x <= 37 改为 x >= 16 && x < 38
这样写的好处有:
- 取值范围大小就是上下界之差。
- 如果取值范围为空,即上界等于下界。
- 即使取值范围为空,上界也永远不可能小于下届。
另一种考虑不对称边界的方法是,将序列中第一个被占用的元素当作上界,序列中第一个被释放的元素当作下界。例如向缓存区中输送数据:
#define N 1024
static char buffer[N]; //缓存区
static char *bufptr; //指向缓存区的指针
bufptr = buffer; //将缓存区第一个被占用的元素当作上界
while( (bufptr - buffer) != N ) //(bufptr - buffer)就是已存放字符数量
{
*bufptr++ = c; //将字符c放入缓存区,然后 bufptr 加一
}
利用上述知识编写函数bufwrite()
void bufwrite(char *p, int n) //指针参数为写入缓存区的第1个字符;第二个参数为写入缓存区的字符数
{
while (--n >= 0) //--n 相较于 n-- 运行速度较快
{
if (bufptr == &buffer[N]) //buffer[N]元素不存在,但其地址真实存在
flushbuffer(); //释放缓存区,重置 bufptr 指向缓存区起始位置
*bufptr++ = *p++; //将字符存入,然后指针加一
}
}
对此函数继续进行优化,该函数存在的问题是每次迭代都要进行两次计数器的检查,且每次只能写入一个字符,我么可以使用memcpy
函数在每次迭代中进行批量转移字符,提高运行效率。
void bufwrite(char *p, int n) //指针为待写入缓存区的字符,n为字符个数
{
while (n > 0)
{
int k,rem; //k为一批转移的字符个数,rem为缓存区剩余内存
if(bufptr == &buffer[N])
flushbuffer(); //释放缓存区,重置 bufptr 指向缓存区起始位置
rem = N - (bufptr - buffer);
k = n > rem? rem: n; //判断剩余字符能否全部放入缓存区,不能则转移缓存区剩余数量的字符
memcpy(bufptr, p, k); //批量转移字符
bufptr += k; //指向缓存区第一个未被占用的元素
p += k; //待写入字符序列指针移动 k
n -= k; //待写入字符数减 k
}
}
3.7 求值顺序
3.8 运算符&&、||和!
3.9 整数溢出
3.10 为函数main提供返回值
-------------------------持续更新--------------------------