第3章 语义陷阱

本文深入探讨了C语言中的指针与数组的关系,包括数组的声明、指针操作数组的方式、作为参数传递的数组以及数组边界的安全处理。此外,还介绍了非数组指针在字符串操作中的应用,以及如何有效地处理内存分配和释放。同时,文章强调了避免指针复制导致的共享数据问题,以及空指针和边界计算的重要性。最后,提到了函数返回值的处理和代码优化技巧,如使用memcpy提高效率。
摘要由CSDN通过智能技术生成

3.1 指针与数组

  1. C 语言只有一维数组,且数组的大小必须在编译期就确定。数组中的元素可以是任意类型。
  2. 对于数组,除了确定数组的大小和获得指向下标为 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)

  1. 任何指针都指向某种类型的变量,指针初始化时,右侧必须是地址值
  2. 如果一个指针指向的是数组中的元素,那么对指针的加减操作就相当于对数组下标的操作。
  3. 指针加一,表示指向内存中的下一个变量的首地址,而不是原指针所指向的地址加一,具体地址值移动多少,取决于指针所指向变量的类型。
  4. 指向同一数组的两个指针,可以通过加减互相得到。指向不同数组的两个指针,即便所指向的地址在内存中正好间隔一个数组元素的整数倍,结果也未必是正确的。
  5. 数组名代表指向下标为 0 的元素的指针,&a代表指向数组的指针,而不是指向数组的第一个元素。
  6. 特殊情况,sizeof(数组名),计算的是整个数组的大小,而不是指向数组的元素的指针的大小。
  7. a[2]2[a]含义相同
  8. 二维数组的数组的每一个“行”下标都代表一个数组的首地址,数组的大小由“列”值决定。

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 作为参数的数组说明

  1. C语言会自动地将作为参数的数组声明转换为对应的指针声明。
int strlen(char a[])  
{             }
等同于
int strlen(cahr* a)  //指针参数不代表数组,仅代表数组的第一个元素的地址
{             }
  1. 指针参数代表一个数组,如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

这样写的好处有:

  1. 取值范围大小就是上下界之差。
  2. 如果取值范围为空,即上界等于下界。
  3. 即使取值范围为空,上界也永远不可能小于下届。




另一种考虑不对称边界的方法是,将序列中第一个被占用的元素当作上界,序列中第一个被释放的元素当作下界。例如向缓存区中输送数据:

#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提供返回值















-------------------------持续更新--------------------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值