C和指针 第 6 章 指针 -- 笔记

内存和地址

内存按照字节(byte)存储, 一般每个字节包含 8 个位. 可以存储无符号 的值 0 ~ 255, 或有符号的 -128 ~ 127. 内存中的每个位置总是包含 一些值, 每个字节通过地址来标识.

为了存储更大的值, 把两个或多个字节合在一起作为一个更大的内存单位.

多个字节的内存仍只有一个地址. 在要求 边界对齐(boundary alignment) 的机器上, 整数值存储的起始位置只能是某些特定的字节, 通常是 2 或 4 的倍数.

  1. 内存中的每个位置由一个独一无二的地址标识
  2. 内存中的每个位置都包含一个值.

地址可能通过名字也就是我们所称的变量访问. 名字和内存位置之间的 关联并不是硬件提供, 而是由编译器为我们实现的. 所以变量给了我们 一种更方便的方法记住地址 ---- 硬件仍然通过地址访问内存位置

值和类型

变量包含了一序列内容为 0 或者 1 的位. 它们可以被解释为整数, 也 可以被解释为浮点数.

不能简单的通过检查一个值的位来判断它的类型, 需要通过程序中使用 这个值的方式.

指针变量的内容

指针使用其他变量的地址予以初始化, 指针的初始化是用 & 操作符完成, 它用于产生操作数的内存地址.

变量的值就是分配该变量的内存位置所存储的值

间接访问操作符

通过一个指针访问它所指向的地址的过程称为间接访问(indirection)或 解引用指针(dereferencing the pointer). 其操作符是单目操作符*.

未初始化和非法指针

下面代码段说明了一个极为常见的错误:

int *a;
*a = 12;
由于没有对 a 指针进行初始化, 所以 a 可能指向 0 或者一个随即不合法 的地址, 往那个地址存值可能会引发 段错误.

NULL 指针

标准定义了 NULL 指针, 表示不指向任何东西. 要使一个指针变量为 NULL , 你可以给它赋一个零值. 将指针和零值比较可以测试一个指针变量是否 为 NULL.

对一个 NULL 指针进行解引用操作是非法的.

指针,间接访问和左值

指针变量可以作为左值, 并不是因为他们是指针, 而是因为他们是变量.

对指针变量进行间接访问表示我们应该访问指针所指向的位置. 间接访问指定了一个特定的内存位置, 这两我们可以把间接访问表达式 的结果作为左值使用.

指针,间接访问和变量

*&a = 25; 相当于 a = 25;, 但第一个做了多余的操作.

指针常量

假定变量 a 的存储于位置 100:

*100 = 25;

这是错的, 间接访问操作只能作用域指针类型的表达式. 如果确实想 把 25 存储于位置 100, 你必须使用强制类型转换:

*(int *) 100 = 25;

但是, 你需要使用这种技巧的机会是绝无仅有的!

指针的指针

指针变量和其他变量一样, 占据内存中某个特定的位置, & 操作符可以 取得指针变量的地址, 同样指针也可以指向指针, 通俗的称之为 指针的指针.

*操作符具有从右向左的结合性, 所以 **c 相当于 *(*c).

指针表达式

先看一些声明

char ch = 'a';
char *cp = &ch;

&ch 作为右值这个表达式的值是变量 ch 地址. & 操作符的结果是个右值, 它不能当做左值使用.

*cp + 1 这里有两个操作符. * 的优先级高于+, 所以首先进行间接访问 错做, 其实就是 'a' + 1 结果是字符 'b'. +的及格过不能作为左值

*(cp + 1) 先执行加法运算就是把 1 和 cp 中所存储的地址相加, 然后间接访问操作访问的是紧随 ch 之后的内存位置. 这个表达式的 右值就是这个位置的值, 而它的左值是这个位置本身.

指针加法运算的结果是个右值, 因为它存储的位置并位清晰定义. 如果没有间接访问操作, 这个表达式将不是一个合法的值. 间接访问 跟随指针访问一个特定的位置, *(cp+1) 可以用作左值使用. 间接访问操作符是少数几个结果为左值的操作符之一.

++cp , 表达式增加了指针变量 cp 的值. 表达式的结构是增值后的指针 的一份拷贝, 存储位置不清晰, 所以它不是一个合法的左值

后缀++操作符同样增加 cp 的值, 但它先返回 cp 值的一份拷贝然后再 增加 cp 的值.

如果给它们(++/--)增加了间接访问操作符, 它们就是合法的左值 :

*++cp;

*cp++ 所产生的结果不同: 它的右值和左值分别是变量 ch 的值和 ch 的内存位置. 后缀 ++ 操作符的优先级高于*操作符, 这里涉及3个步骤:

  1. ++ 操作符产生 cp 的一份拷贝
  2. ++ 操作符增加 cp 的值
  3. 在 cp 的拷贝上执行间接访问操作

(*cp)++ 增加 ch 变量的值.

++*++cp , 这些操作符的结合性都是从右向左, 所以先执行的是 ++cp, 然后对这个拷贝值进行间接引用, 访问到的是 ch 后面的那个内存位置, 最后在这个位置上进行自增

++*cp++ 略.

实例

计算字符串长度

/* 计算字符串长度(标准库里 strlen)
 */
#include <stdlib.h>

size_t
strlen(char *string)
{
    int length = 0;
    while (*string++ != '\0')
        length += 1;
    return length;
}

指针运算

指针加上一个整数的结构是另一个指针.

当一个指针和一个整数执行算数运算时, 整数在执行加法运算前是在用 根据合适的大小进行调整. 也就是指针所指类型的大小.

指针算法并不依赖于指针的类型. 如果 p 是指向一个 char 指针, 那么表达式 p+1 就是指向下一个 char, 如果 p 指向 float 指针, 那么 p + 1 就是指向下一个 float.

算数运算

C 的指针算数运算只限于两种形式, 第一种:

指针 +/- 整数

标准定义这种形式只能用于指向数组中某个元素的指针.并且这类表达式 的结构类型也是指针. 这种形式也适用于使用 malloc 函数动态分配 获得的内存.

数组中的元素存储与连续的内存位置中, 后面的元素地址大于前面的元素 地址.

指针指向数组的第一个元素的前面和最后一个元素的后面, 效果是未定义 , 让指针指向数组最后一个元素后面的那个位置是合法的, 但对这个指针 执行间接访问可能会失败.

第 2种 类型如下:

指针 - 指针

只有当两个指针都指向同一个数组的元素时, 才允许一个指针减去另一个 指针.

两个指针想减的结果类型是 ptrdiff_t, 一种有符号的整数类型. 减法 运算的值是两个指针内存中的距离(以元素的长度为单位).

如果两个指针所指向的不是同一个数组中的元素, 那么他们之间想减的结 果是未定义.

关系运算

对指针执行关系运算也是有限制的, 下面操作对两个指针比较是可能的:

< <= > >=

不过前提是他们都指向同一个数组中的元素. 根据操作符, 比较指针 指向的数组中更前或者更后的元素.

任意两个指针之间可以执行相等或不相等测试

总结

声明一个指针变量不会自动分配内存

在对指针执行间接访问前, 指针必须进行初始化