前 言 : 复 杂 类 型 说 明 让 你 不 再 害 怕 指 针 要 了 解 指 针, 多 多 少 少 会 出 现 一 些 比 较 复 杂 的 类 型, 所 以 我 先 介 绍 一 下 如 何 完 全 理 解 一 个 复 杂 类 型, 要 理 解 复 杂 类 型 其 实 很 简 单, 一 个 类 型 里 会 出 现 很 多 运 算 符, 他 们 也 像 普 通 的 表 达 式 一 样, 有 优 先 级, 其 优 先 级 和 运 算 优 先 级 一 样, 所 以 我 总 结 了 一 下 其 原 则 : 从 变 量 名 处 起, 根 据 运 算 符 优 先 级 结 合, 一 步 一 步 分 析. 下 面 让 我 们 先 从 简 单 的 类 型 开 始 慢 慢 分 析 吧 : int p; // 这 是 一 个 普 通 的 整 型 变 量 int *p; // 首 先 从 P 处 开 始, 先 与 * 结 合, 所 以 说 明 P 是 一 // 个 指 针, 然 后 再 与 int 结 合, 说 明 指 针 所 指 向 // 的 内 容 的 类 型 为 int 型. 所 以 P 是 一 个 返 回 整 // 型 数 据 的 指 针 int p[3]; // 首 先 从 P 处 开 始, 先 与 [] 结 合, 说 明 P 是 一 个 数 // 组, 然 后 与 int 结 合, 说 明 数 组 里 的 元 素 是 整 // 型 的, 所 以 P 是 一 个 由 整 型 数 据 组 成 的 数 组 int *p[3]; // 首 先 从 P 处 开 始, 先 与 [] 结 合, 因 为 其 优 先 级
// 比 * 高, 所 以 P 是 一 个 数 组, 然 后 再 与 * 结 合, 说 明 // 数 组 里 的 元 素 是 指 针 类 型, 然 后 再 与 int 结 合, // 说 明 指 针 所 指 向 的 内 容 的 类 型 是 整 型 的, 所 以 //P 是 一 个 由 返 回 整 型 数 据 的 指 针 所 组 成 的 数 组 int (*p)[3]; // 首 先 从 P 处 开 始, 先 与 * 结 合, 说 明 P 是 一 个 指 针 // 然 后 再 与 [] 结 合 ( 与 "()" 这 步 可 以 忽 略, 只 是 为 // 了 改 变 优 先 级 ), 说 明 指 针 所 指 向 的 内 容 是 一 个 // 数 组, 然 后 再 与 int 结 合, 说 明 数 组 里 的 元 素 是 // 整 型 的. 所 以 P 是 一 个 指 向 由 整 型 数 据 组 成 的 数 // 组 的 指 针 int **p; // 首 先 从 P 开 始, 先 与 * 结 合, 说 是 P 是 一 个 指 针, 然 // 后 再 与 * 结 合, 说 明 指 针 所 指 向 的 元 素 是 指 针, 然 // 后 再 与 int 结 合, 说 明 该 指 针 所 指 向 的 元 素 是 整 // 型 数 据. 由 于 二 级 指 针 以 及 更 高 级 的 指 针 极 少 用 // 在 复 杂 的 类 型 中, 所 以 后 面 更 复 杂 的 类 型 我 们 就 // 不 考 虑 多 级 指 针 了, 最 多 只 考 虑 一 级 指 针. int p(int); // 从 P 处 起, 先 与 () 结 合, 说 明 P 是 一 个 函 数, 然 后 进 入 //() 里 分 析, 说 明 该 函 数 有 一 个 整 型 变 量 的 参 数 // 然 后 再 与 外 面 的 int 结 合, 说 明 函 数 的 返 回 值 是 // 一 个 整 型 数 据
Int (*p)(int); // 从 P 处 开 始, 先 与 指 针 结 合, 说 明 P 是 一 个 指 针, 然 后 与 //() 结 合, 说 明 指 针 指 向 的 是 一 个 函 数, 然 后 再 与 () 里 的 //int 结 合, 说 明 函 数 有 一 个 int 型 的 参 数, 再 与 最 外 层 的 //int 结 合, 说 明 函 数 的 返 回 类 型 是 整 型, 所 以 P 是 一 个 指 // 向 有 一 个 整 型 参 数 且 返 回 类 型 为 整 型 的 函 数 的 指 针 int *(*p(int))[3]; // 可 以 先 跳 过, 不 看 这 个 类 型, 过 于 复 杂 // 从 P 开 始, 先 与 () 结 合, 说 明 P 是 一 个 函 数, 然 后 进 // 入 () 里 面, 与 int 结 合, 说 明 函 数 有 一 个 整 型 变 量 // 参 数, 然 后 再 与 外 面 的 * 结 合, 说 明 函 数 返 回 的 是 // 一 个 指 针,, 然 后 到 最 外 面 一 层, 先 与 [] 结 合, 说 明 // 返 回 的 指 针 指 向 的 是 一 个 数 组, 然 后 再 与 * 结 合, 说 // 明 数 组 里 的 元 素 是 指 针, 然 后 再 与 int 结 合, 说 明 指 // 针 指 向 的 内 容 是 整 型 数 据. 所 以 P 是 一 个 参 数 为 一 个 // 整 数 据 且 返 回 一 个 指 向 由 整 型 指 针 变 量 组 成 的 数 组 // 的 指 针 变 量 的 函 数. 说 到 这 里 也 就 差 不 多 了, 我 们 的 任 务 也 就 这 么 多, 理 解 了 这 几 个 类 型, 其 它 的 类 型 对 我 们 来 说 也 是 小 菜 了, 不 过 我 们 一 般 不 会 用 太 复 杂 的 类 型, 那 样 会 大 大 减 小 程 序 的 可 读 性, 请 慎 用, 这 上 面 的 几 种 类 型 已 经 足 够 我 们 用 了.
1 细 说 指 针 指 针 是 一 个 特 殊 的 变 量, 它 里 面 存 储 的 数 值 被 解 释 成 为 内 存 里 的 一 个 地 址 要 搞 清 一 个 指 针 需 要 搞 清 指 针 的 四 方 面 的 内 容 : 指 针 的 类 型 指 针 所 指 向 的 类 型 指 针 的 值 或 者 叫 指 针 所 指 向 的 内 存 区 指 针 本 身 所 占 据 的 内 存 区 让 我 们 分 别 说 明 先 声 明 几 个 指 针 放 着 做 例 子 : 例 一 : (1)int*ptr; (2)char*ptr; (3)int**ptr; (4)int(*ptr)[3]; (5)int*(*ptr)[4]; 1. 指 针 的 类 型 从 语 法 的 角 度 看, 你 只 要 把 指 针 声 明 语 句 里 的 指 针 名 字 去 掉, 剩 下 的 部 分 就 是 这 个 指 针 的 类 型 这 是 指 针 本 身 所 具 有 的 类 型 让 我 们 看 看 例 一 中 各 个 指 针 的 类 型 : (1)int*ptr;// 指 针 的 类 型 是 int* (2)char*ptr;// 指 针 的 类 型 是 char* (3)int**ptr;// 指 针 的 类 型 是 int** (4)int(*ptr)[3];// 指 针 的 类 型 是 int(*)[3]
(5)int*(*ptr)[4];// 指 针 的 类 型 是 int*(*)[4] 怎 么 样? 找 出 指 针 的 类 型 的 方 法 是 不 是 很 简 单? 2. 指 针 所 指 向 的 类 型 当 你 通 过 指 针 来 访 问 指 针 所 指 向 的 内 存 区 时, 指 针 所 指 向 的 类 型 决 定 了 编 译 器 将 把 那 片 内 存 区 里 的 内 容 当 做 什 么 来 看 待 从 语 法 上 看, 你 只 须 把 指 针 声 明 语 句 中 的 指 针 名 字 和 名 字 左 边 的 指 针 声 明 符 * 去 掉, 剩 下 的 就 是 指 针 所 指 向 的 类 型 例 如 : (1)int*ptr; (2)char*ptr; (3)int**ptr; // 指 针 所 指 向 的 类 型 是 int // 指 针 所 指 向 的 的 类 型 是 char // 指 针 所 指 向 的 的 类 型 是 int* (4)int(*ptr)[3]; // 指 针 所 指 向 的 的 类 型 是 int()[3] (5)int*(*ptr)[4]; // 指 针 所 指 向 的 的 类 型 是 int*()[4] 在 指 针 的 算 术 运 算 中, 指 针 所 指 向 的 类 型 有 很 大 的 作 用 指 针 的 类 型 ( 即 指 针 本 身 的 类 型 ) 和 指 针 所 指 向 的 类 型 是 两 个 概 念 当 你 对 C 越 来 越 熟 悉 时, 你 会 发 现, 把 与 指 针 搅 和 在 一 起 的 " 类 型 " 这 个 概 念 分 成 " 指 针 的 类 型 " 和 " 指 针 所 指 向 的 类 型 " 两 个 概 念, 是 精 通 指 针 的 关 键 点 之 一 我 看 了 不 少 书, 发 现 有 些 写 得 差 的 书 中, 就 把 指 针 的 这 两 个 概 念 搅 在 一 起 了, 所 以 看 起 书 来 前 后 矛 盾, 越 看 越 糊 涂 3. 指 针 的 值 ---- 或 者 叫 指 针 所 指 向 的 内 存 区 或 地 址 指 针 的 值 是 指 针 本 身 存 储 的 数 值, 这 个 值 将 被 编 译 器 当 作 一 个 地 址, 而 不 是 一 个 一 般 的 数 值 在 32 位 程 序 里, 所 有 类 型 的 指 针 的 值 都 是 一 个 32 位
整 数, 因 为 32 位 程 序 里 内 存 地 址 全 都 是 32 位 长 指 针 所 指 向 的 内 存 区 就 是 从 指 针 的 值 所 代 表 的 那 个 内 存 地 址 开 始, 长 度 为 si zeof( 指 针 所 指 向 的 类 型 ) 的 一 片 内 存 区 以 后, 我 们 说 一 个 指 针 的 值 是 XX, 就 相 当 于 说 该 指 针 指 向 了 以 XX 为 首 地 址 的 一 片 内 存 区 域 ; 我 们 说 一 个 指 针 指 向 了 某 块 内 存 区 域, 就 相 当 于 说 该 指 针 的 值 是 这 块 内 存 区 域 的 首 地 址 指 针 所 指 向 的 内 存 区 和 指 针 所 指 向 的 类 型 是 两 个 完 全 不 同 的 概 念 在 例 一 中, 指 针 所 指 向 的 类 型 已 经 有 了, 但 由 于 指 针 还 未 初 始 化, 所 以 它 所 指 向 的 内 存 区 是 不 存 在 的, 或 者 说 是 无 意 义 的 以 后, 每 遇 到 一 个 指 针, 都 应 该 问 问 : 这 个 指 针 的 类 型 是 什 么? 指 针 指 的 类 型 是 什 么? 该 指 针 指 向 了 哪 里?( 重 点 注 意 ) 4 指 针 本 身 所 占 据 的 内 存 区 指 针 本 身 占 了 多 大 的 内 存? 你 只 要 用 函 数 sizeof( 指 针 的 类 型 ) 测 一 下 就 知 道 了 在 32 位 平 台 里, 指 针 本 身 占 据 了 4 个 字 节 的 长 度 指 针 本 身 占 据 的 内 存 这 个 概 念 在 判 断 一 个 指 针 表 达 式 ( 后 面 会 解 释 ) 是 否 是 左 值 时 很 有 用
2 指 针 的 算 术 运 算 指 针 可 以 加 上 或 减 去 一 个 整 数 指 针 的 这 种 运 算 的 意 义 和 通 常 的 数 值 的 加 减 运 算 的 意 义 是 不 一 样 的, 以 单 元 为 单 位 例 如 : 例 二 : char a[20]; int *ptr=(int *)a; // 强 制 类 型 转 换 并 不 会 改 变 a 的 类 型 ptr++; 在 上 例 中, 指 针 ptr 的 类 型 是 int*, 它 指 向 的 类 型 是 int, 它 被 初 始 化 为 指 向 整 型 变 量 a 接 下 来 的 第 3 句 中, 指 针 ptr 被 加 了 1, 编 译 器 是 这 样 处 理 的 : 它 把 指 针 ptr 的 值 加 上 了 sizeof(int), 在 32 位 程 序 中, 是 被 加 上 了 4, 因 为 在 32 位 程 序 中,int 占 4 个 字 节 由 于 地 址 是 用 字 节 做 单 位 的, 故 ptr 所 指 向 的 地 址 由 原 来 的 变 量 a 的 地 址 向 高 地 址 方 向 增 加 了 4 个 字 节 由 于 char 类 型 的 长 度 是 一 个 字 节, 所 以, 原 来 ptr 是 指 向 数 组 a 的 第 0 号 单 元 开 始 的 四 个 字 节, 此 时 指 向 了 数 组 a 中 从 第 4 号 单 元 开 始 的 四 个 字 节 我 们 可 以 用 一 个 指 针 和 一 个 循 环 来 遍 历 一 个 数 组, 看 例 子 : 例 三 : int array[20]={0}; int *ptr=array; for(i=0;i<20;i++) { (*ptr)++; ptr++;
} 这 个 例 子 将 整 型 数 组 中 各 个 单 元 的 值 加 1 由 于 每 次 循 环 都 将 指 针 ptr 加 1 个 单 元, 所 以 每 次 循 环 都 能 访 问 数 组 的 下 一 个 单 元 再 看 例 子 : 例 四 : char a[20]="you_are_a_girl"; int *ptr=(int *)a; ptr+=5; 在 这 个 例 子 中,ptr 被 加 上 了 5, 编 译 器 是 这 样 处 理 的 : 将 指 针 ptr 的 值 加 上 5 乘 sizeof(int), 在 32 位 程 序 中 就 是 加 上 了 5 乘 4=20 由 于 地 址 的 单 位 是 字 节, 故 现 在 的 ptr 所 指 向 的 地 址 比 起 加 5 后 的 ptr 所 指 向 的 地 址 来 说, 向 高 地 址 方 向 移 动 了 20 个 字 节 在 这 个 例 子 中, 没 加 5 前 的 ptr 指 向 数 组 a 的 第 0 号 单 元 开 始 的 四 个 字 节, 加 5 后,ptr 已 经 指 向 了 数 组 a 的 合 法 范 围 之 外 了 虽 然 这 种 情 况 在 应 用 上 会 出 问 题, 但 在 语 法 上 却 是 可 以 的 这 也 体 现 出 了 指 针 的 灵 活 性 如 果 上 例 中,ptr 是 被 减 去 5, 那 么 处 理 过 程 大 同 小 异, 只 不 过 ptr 的 值 是 被 减 去 5 乘 sizeof(int), 新 的 ptr 指 向 的 地 址 将 比 原 来 的 ptr 所 指 向 的 地 址 向 低 地 址 方 向 移 动 了 20 个 字 节 下 面 请 允 许 我 再 举 一 个 例 子 :( 一 个 误 区 ) 例 五 : #include<stdio.h> int main() {
char a[20]=" You_are_a_girl"; char *p=a; char **ptr=&p; //printf("p=%d\n",p); //printf("ptr=%d\n",ptr); //printf("*ptr=%d\n",*ptr); printf("**ptr=%c\n",**ptr); ptr++; //printf("ptr=%d\n",ptr); //printf("*ptr=%d\n",*ptr); printf("**ptr=%c\n",**ptr); } 误 区 一 输 出 答 案 为 Y 和 o 误 解 :ptr 是 一 个 char 的 二 级 指 针, 当 执 行 ptr++; 时, 会 使 指 针 加 一 个 sizeof(char), 所 以 输 出 如 上 结 果, 这 个 可 能 只 是 少 部 分 人 的 结 果. 误 区 二 输 出 答 案 为 Y 和 a 误 解 :ptr 指 向 的 是 一 个 char * 类 型, 当 执 行 ptr++; 时, 会 使 指 针 加 一 个 sizeof(char *)( 有 可 能 会 有 人 认 为 这 个 值 为 1, 那 就 会 得 到 误 区 一 的 答 案, 这 个 值 应 该 是 4, 参 考 前 面 内 容 ), 即 &p+4; 那 进 行 一 次 取 值 运 算 不
就 指 向 数 组 中 的 第 五 个 元 素 了 吗? 那 输 出 的 结 果 不 就 是 数 组 中 第 五 个 元 素 了 吗? 答 案 是 否 定 的. 正 解 : ptr 的 类 型 是 char **, 指 向 的 类 型 是 一 个 char * 类 型, 该 指 向 的 地 址 就 是 p 的 地 址 (&p), 当 执 行 ptr++; 时, 会 使 指 针 加 一 个 sizeof(char *), 即 &p+4; 那 *(&p+4) 指 向 哪 呢, 这 个 你 去 问 上 帝 吧, 或 者 他 会 告 诉 你 在 哪? 所 以 最 后 的 输 出 会 是 一 个 随 机 的 值, 或 许 是 一 个 非 法 操 作. 总 结 一 下 : 一 个 指 针 ptrold 加 ( 减 ) 一 个 整 数 n 后, 结 果 是 一 个 新 的 指 针 ptrnew, ptrnew 的 类 型 和 ptrold 的 类 型 相 同,ptrnew 所 指 向 的 类 型 和 ptrold 所 指 向 的 类 型 也 相 同 ptrnew 的 值 将 比 ptrold 的 值 增 加 ( 减 少 ) 了 n 乘 sizeof(ptrold 所 指 向 的 类 型 ) 个 字 节 就 是 说,ptrnew 所 指 向 的 内 存 区 将 比 ptrold 所 指 向 的 内 存 区 向 高 ( 低 ) 地 址 方 向 移 动 了 n 乘 sizeof(ptrold 所 指 向 的 类 型 ) 个 字 节 指 针 和 指 针 进 行 加 减 : 两 个 指 针 不 能 进 行 加 法 运 算, 这 是 非 法 操 作, 因 为 进 行 加 法 后, 得 到 的 结 果 指 向 一 个 不 知 所 向 的 地 方, 而 且 毫 无 意 义 两 个 指 针 可 以 进 行 减 法 操 作, 但 必 须 类 型 相 同, 一 般 用 在 数 组 方 面, 不 多 说 了
3 运 算 符 & 和 * 这 里 & 是 取 地 址 运 算 符,* 是 间 接 运 算 符 &a 的 运 算 结 果 是 一 个 指 针, 指 针 的 类 型 是 a 的 类 型 加 个 *, 指 针 所 指 向 的 类 型 是 a 的 类 型, 指 针 所 指 向 的 地 址 嘛, 那 就 是 a 的 地 址 *p 的 运 算 结 果 就 五 花 八 门 了 总 之 *p 的 结 果 是 p 所 指 向 的 东 西, 这 个 东 西 有 这 些 特 点 : 它 的 类 型 是 p 指 向 的 类 型, 它 所 占 用 的 地 址 是 p 所 指 向 的 地 址 例 六 : int a=12; int b; int *p; int **ptr; p=&a; //&a 的 结 果 是 一 个 指 针, 类 型 是 int*, 指 向 的 类 型 是 //int, 指 向 的 地 址 是 a 的 地 址 *p=24; //*p 的 结 果, 在 这 里 它 的 类 型 是 int, 它 所 占 用 的 地 址 是 //p 所 指 向 的 地 址, 显 然,*p 就 是 变 量 a ptr=&p; //&p 的 结 果 是 个 指 针, 该 指 针 的 类 型 是 p 的 类 型 加 个 *, // 在 这 里 是 int ** 该 指 针 所 指 向 的 类 型 是 p 的 类 型, 这 // 里 是 int* 该 指 针 所 指 向 的 地 址 就 是 指 针 p 自 己 的 地 址 *ptr=&b; //*ptr 是 个 指 针,&b 的 结 果 也 是 个 指 针, 且 这 两 个 指 针 // 的 类 型 和 所 指 向 的 类 型 是 一 样 的, 所 以 用 &b 来 给 *ptr 赋 // 值 就 是 毫 无 问 题 的 了 **ptr=34; //*ptr 的 结 果 是 ptr 所 指 向 的 东 西, 在 这 里 是 一 个 指 针, // 对 这 个 指 针 再 做 一 次 * 运 算, 结 果 是 一 个 int 类 型 的 变 量
4 指 针 表 达 式 一 个 表 达 式 的 结 果 如 果 是 一 个 指 针, 那 么 这 个 表 达 式 就 叫 指 针 表 式 下 面 是 一 些 指 针 表 达 式 的 例 子 : 例 七 : int a,b; int array[10]; int *pa; pa=&a; //&a 是 一 个 指 针 表 达 式 Int **ptr=&pa; //&pa 也 是 一 个 指 针 表 达 式 *ptr=&b; //*ptr 和 &b 都 是 指 针 表 达 式 pa=array; pa++; // 这 也 是 指 针 表 达 式 例 八 : char *arr[20]; char **parr=arr; // 如 果 把 arr 看 作 指 针 的 话,arr 也 是 指 针 表 达 式 char *str; str=*parr; str=*(parr+1); str=*(parr+2); //*parr 是 指 针 表 达 式 //*(parr+1) 是 指 针 表 达 式 //*(parr+2) 是 指 针 表 达 式 由 于 指 针 表 达 式 的 结 果 是 一 个 指 针, 所 以 指 针 表 达 式 也 具 有 指 针 所 具 有 的 四 个 要 素 : 指 针 的 类 型, 指 针 所 指 向 的 类 型, 指 针 指 向 的 内 存 区, 指 针 自 身 占 据 的 内 存
好 了, 当 一 个 指 针 表 达 式 的 结 果 指 针 已 经 明 确 地 具 有 了 指 针 自 身 占 据 的 内 存 的 话, 这 个 指 针 表 达 式 就 是 一 个 左 值, 否 则 就 不 是 一 个 左 值 在 例 七 中,&a 不 是 一 个 左 值, 因 为 它 还 没 有 占 据 明 确 的 内 存 *ptr 是 一 个 左 值, 因 为 *ptr 这 个 指 针 已 经 占 据 了 内 存, 其 实 *ptr 就 是 指 针 pa, 既 然 pa 已 经 在 内 存 中 有 了 自 己 的 位 置, 那 么 *ptr 当 然 也 有 了 自 己 的 位 置
5 数 组 和 指 针 的 关 系 例 九 : 数 组 的 数 组 名 其 实 可 以 看 作 一 个 指 针 看 下 例 : intarray[10]={0,1,2,3,4,5,6,7,8,9},value; value=array[0]; value=array[3]; value=array[4]; // 也 可 写 成 :value=*array; // 也 可 写 成 :value=*(array+3); // 也 可 写 成 :value=*(array+4); 上 例 中, 一 般 而 言 数 组 名 array 代 表 数 组 本 身, 类 型 是 int[10], 但 如 果 把 array 看 做 指 针 的 话, 它 指 向 数 组 的 第 0 个 单 元, 类 型 是 int*, 所 指 向 的 类 型 是 数 组 单 元 的 类 型 即 int 因 此 *array 等 于 0 就 一 点 也 不 奇 怪 了 同 理,array+3 是 一 个 指 向 数 组 第 3 个 单 元 的 指 针, 所 以 *(array+3) 等 于 3 其 它 依 此 类 推 例 十 : char *str[3]={ "Hello,thisisasample!", "Hi,goodmorning.", "Helloworld" }; chars[80]; strcpy(s,str[0]); strcpy(s,str[1]); strcpy(s,str[2]); // 也 可 写 成 strcpy(s,*str); // 也 可 写 成 strcpy(s,*(str+1)); // 也 可 写 成 strcpy(s,*(str+2));
上 例 中,str 是 一 个 三 单 元 的 数 组, 该 数 组 的 每 个 单 元 都 是 一 个 指 针, 这 些 指 针 各 指 向 一 个 字 符 串 把 指 针 数 组 名 str 当 作 一 个 指 针 的 话, 它 指 向 数 组 的 第 0 号 单 元, 它 的 类 型 是 char **, 它 指 向 的 类 型 是 char * *str 也 是 一 个 指 针, 它 的 类 型 是 char *, 它 所 指 向 的 类 型 是 char, 它 指 向 的 地 址 是 字 符 串 "Hello,thisisasample!" 的 第 一 个 字 符 的 地 址, 即 'H' 的 地 址 注 意 : 字 符 串 相 当 于 是 一 个 数 组, 在 内 存 中 以 数 组 的 形 式 储 存, 只 不 过 字 符 串 是 一 个 数 组 常 量, 内 容 不 可 改 变, 且 只 能 是 右 值. 如 果 看 成 指 针 的 话, 他 即 是 常 量 指 针, 也 是 指 针 常 量. str+1 也 是 一 个 指 针, 它 指 向 数 组 的 第 1 号 单 元, 它 的 类 型 是 char**, 它 指 向 的 类 型 是 char* *(str+1) 也 是 一 个 指 针, 它 的 类 型 是 char*, 它 所 指 向 的 类 型 是 char, 它 指 向 "Hi,goodmorning." 的 第 一 个 字 符 'H' 下 面 总 结 一 下 数 组 的 数 组 名 ( 数 组 中 储 存 的 也 是 数 组 ) 的 问 题 : 声 明 了 一 个 数 组 TYPE array[n], 则 数 组 名 称 array 就 有 了 两 重 含 义 : 第 一, 它 代 表 整 个 数 组, 它 的 类 型 是 TYPE[n]; 第 二, 它 是 一 个 常 量 指 针, 该 指 针 的 类 型 是 TYPE*, 该 指 针 指 向 的 类 型 是 TYPE, 也 就 是 数 组 单 元 的 类 型, 该 指 针 指 向 的 内 存 区 就 是 数 组 第 0 号 单 元, 该 指 针 自 己 占 有 单 独 的 内 存 区, 注 意 它 和 数 组 第 0 号 单 元 占 据 的 内 存 区 是 不 同 的 该 指 针 的 值 是 不 能 修 改 的, 即 类 似 array++ 的 表 达 式 是 错 误 的
在 不 同 的 表 达 式 中 数 组 名 array 可 以 扮 演 不 同 的 角 色 在 表 达 式 sizeof(array) 中, 数 组 名 array 代 表 数 组 本 身, 故 这 时 sizeof 函 数 测 出 的 是 整 个 数 组 的 大 小 在 表 达 式 *array 中,array 扮 演 的 是 指 针, 因 此 这 个 表 达 式 的 结 果 就 是 数 组 第 0 号 单 元 的 值 sizeof(*array) 测 出 的 是 数 组 单 元 的 大 小 表 达 式 array+n( 其 中 n=0,1,2,...) 中,array 扮 演 的 是 指 针, 故 array+n 的 结 果 是 一 个 指 针, 它 的 类 型 是 TYPE *, 它 指 向 的 类 型 是 TYPE, 它 指 向 数 组 第 n 号 单 元 故 sizeof(array+n) 测 出 的 是 指 针 类 型 的 大 小 在 32 位 程 序 中 结 果 是 4 例 十 一 : int array[10]; int (*ptr)[10]; ptr=&array;: 上 例 中 ptr 是 一 个 指 针, 它 的 类 型 是 int(*)[10], 他 指 向 的 类 型 是 int[10], 我 们 用 整 个 数 组 的 首 地 址 来 初 始 化 它 在 语 句 ptr=&array 中,array 代 表 数 组 本 身 本 节 中 提 到 了 函 数 sizeof(), 那 么 我 来 问 一 问,sizeof( 指 针 名 称 ) 测 出 的 究 竟 是 指 针 自 身 类 型 的 大 小 呢 还 是 指 针 所 指 向 的 类 型 的 大 小? 答 案 是 前 者 例 如 : int(*ptr)[10]; 则 在 32 位 程 序 中, 有 : sizeof(int(*)[10])==4 sizeof(int[10])==40
sizeof(ptr)==4 实 际 上,sizeof( 对 象 ) 测 出 的 都 是 对 象 自 身 的 类 型 的 大 小, 而 不 是 别 的 什 么 类 型 的 大 小
6 指 针 和 结 构 类 型 的 关 系 可 以 声 明 一 个 指 向 结 构 类 型 对 象 的 指 针 例 十 二 : struct MyStruct { int a; int b; int c; }; struct MyStruct ss={20,30,40}; // 声 明 了 结 构 对 象 ss, 并 把 ss 的 成 员 初 始 化 为 20,30 和 40 struct MyStruct *ptr=&ss; // 声 明 了 一 个 指 向 结 构 对 象 ss 的 指 针 它 的 类 型 是 //MyStruct *, 它 指 向 的 类 型 是 MyStruct int *pstr=(int*)&ss; // 声 明 了 一 个 指 向 结 构 对 象 ss 的 指 针 但 是 pstr 和 // 它 被 指 向 的 类 型 ptr 是 不 同 的 请 问 怎 样 通 过 指 针 ptr 来 访 问 ss 的 三 个 成 员 变 量? 答 案 : ptr->a; // 指 向 运 算 符, 或 者 可 以 这 们 (*ptr).a, 建 议 使 用 前 者 ptr->b;
ptr->c; 又 请 问 怎 样 通 过 指 针 pstr 来 访 问 ss 的 三 个 成 员 变 量? 答 案 : *pstr; // 访 问 了 ss 的 成 员 a *(pstr+1); // 访 问 了 ss 的 成 员 b *(pstr+2) // 访 问 了 ss 的 成 员 c 虽 然 我 在 我 的 MSVC++6.0 上 调 式 过 上 述 代 码, 但 是 要 知 道, 这 样 使 用 pstr 来 访 问 结 构 成 员 是 不 正 规 的, 为 了 说 明 为 什 么 不 正 规, 让 我 们 看 看 怎 样 通 过 指 针 来 访 问 数 组 的 各 个 单 元 : ( 将 结 构 体 换 成 数 组 ) 例 十 三 : int array[3]={35,56,37}; int *pa=array; 通 过 指 针 pa 访 问 数 组 array 的 三 个 单 元 的 方 法 是 : *pa; *(pa+1); *(pa+2); // 访 问 了 第 0 号 单 元 // 访 问 了 第 1 号 单 元 // 访 问 了 第 2 号 单 元 从 格 式 上 看 倒 是 与 通 过 指 针 访 问 结 构 成 员 的 不 正 规 方 法 的 格 式 一 样 所 有 的 C/C++ 编 译 器 在 排 列 数 组 的 单 元 时, 总 是 把 各 个 数 组 单 元 存 放 在 连 续 的 存 储 区 里, 单 元 和 单 元 之 间 没 有 空 隙 但 在 存 放 结 构 对 象 的 各 个 成 员 时, 在 某 种 编 译 环 境 下, 可 能 会 需 要 字 对 齐 或 双 字 对 齐 或 者 是 别 的 什 么 对 齐, 需 要 在 相 邻 两 个 成 员 之 间 加 若 干 个 " 填 充 字 节 ", 这 就 导
致 各 个 成 员 之 间 可 能 会 有 若 干 个 字 节 的 空 隙 所 以, 在 例 十 二 中, 即 使 *pstr 访 问 到 了 结 构 对 象 ss 的 第 一 个 成 员 变 量 a, 也 不 能 保 证 *(pstr+1) 就 一 定 能 访 问 到 结 构 成 员 b 因 为 成 员 a 和 成 员 b 之 间 可 能 会 有 若 干 填 充 字 节, 说 不 定 *(pstr+1) 就 正 好 访 问 到 了 这 些 填 充 字 节 呢 这 也 证 明 了 指 针 的 灵 活 性 要 是 你 的 目 的 就 是 想 看 看 各 个 结 构 成 员 之 间 到 底 有 没 有 填 充 字 节, 嘿, 这 倒 是 个 不 错 的 方 法 不 过 指 针 访 问 结 构 成 员 的 正 确 方 法 应 该 是 象 例 十 二 中 使 用 指 针 ptr 的 方 法
7 指 针 和 函 数 的 关 系 可 以 把 一 个 指 针 声 明 成 为 一 个 指 向 函 数 的 指 针 int fun1(char *,int); int (*pfun1)(char *,int); pfun1=fun1; int a=(*pfun1)("abcdefg",7); // 通 过 函 数 指 针 调 用 函 数 可 以 把 指 针 作 为 函 数 的 形 参 在 函 数 调 用 语 句 中, 可 以 用 指 针 表 达 式 来 作 为 实 参 例 十 四 : int fun(char *); inta; char str[]="abcdefghijklmn"; a=fun(str); int fun(char *s) { int num=0; for(int i=0;;) { num+=*s;s++; } return num; }
这 个 例 子 中 的 函 数 fun 统 计 一 个 字 符 串 中 各 个 字 符 的 ASCII 码 值 之 和 前 面 说 了, 数 组 的 名 字 也 是 一 个 指 针 在 函 数 调 用 中, 当 把 str 作 为 实 参 传 递 给 形 参 s 后, 实 际 是 把 str 的 值 传 递 给 了 s,s 所 指 向 的 地 址 就 和 str 所 指 向 的 地 址 一 致, 但 是 str 和 s 各 自 占 用 各 自 的 存 储 空 间 在 函 数 体 内 对 s 进 行 自 加 1 运 算, 并 不 意 味 着 同 时 对 str 进 行 了 自 加 1 运 算
8 指 针 类 型 转 换 当 我 们 初 始 化 一 个 指 针 或 给 一 个 指 针 赋 值 时, 赋 值 号 的 左 边 是 一 个 指 针, 赋 值 号 的 右 边 是 一 个 指 针 表 达 式 在 我 们 前 面 所 举 的 例 子 中, 绝 大 多 数 情 况 下, 指 针 的 类 型 和 指 针 表 达 式 的 类 型 是 一 样 的, 指 针 所 指 向 的 类 型 和 指 针 表 达 式 所 指 向 的 类 型 是 一 样 的 例 十 五 : float f=12.3; float *fptr=&f; int *p; 在 上 面 的 例 子 中, 假 如 我 们 想 让 指 针 p 指 向 实 数 f, 应 该 怎 么 办? 是 用 下 面 的 语 句 吗? p=&f; 不 对 因 为 指 针 p 的 类 型 是 int *, 它 指 向 的 类 型 是 int 表 达 式 &f 的 结 果 是 一 个 指 针, 指 针 的 类 型 是 float *, 它 指 向 的 类 型 是 float 两 者 不 一 致, 直 接 赋 值 的 方 法 是 不 行 的 至 少 在 我 的 MSVC++6.0 上, 对 指 针 的 赋 值 语 句 要 求 赋 值 号 两 边 的 类 型 一 致, 所 指 向 的 类 型 也 一 致, 其 它 的 编 译 器 上 我 没 试 过, 大 家 可 以 试 试 为 了 实 现 我 们 的 目 的, 需 要 进 行 " 强 制 类 型 转 换 ": p=(int*)&f; 如 果 有 一 个 指 针 p, 我 们 需 要 把 它 的 类 型 和 所 指 向 的 类 型 改 为 TYEP *TYPE, 那 么 语 法 格 式 是 : (TYPE *)p; 这 样 强 制 类 型 转 换 的 结 果 是 一 个 新 指 针, 该 新 指 针 的 类 型 是
TYPE *, 它 指 向 的 类 型 是 TYPE, 它 指 向 的 地 址 就 是 原 指 针 指 向 的 地 址 而 原 来 的 指 针 p 的 一 切 属 性 都 没 有 被 修 改 ( 切 记 ) 一 个 函 数 如 果 使 用 了 指 针 作 为 形 参, 那 么 在 函 数 调 用 语 句 的 实 参 和 形 参 的 结 合 过 程 中, 必 须 保 证 类 型 一 致, 否 则 需 要 强 制 转 换 例 十 六 : void fun(char*); int a=125,b; fun((char*)&a); void fun(char*s) { charc; c=*(s+3);*(s+3)=*(s+0);*(s+0)=c; c=*(s+2);*(s+2)=*(s+1);*(s+1)=c; } 注 意 这 是 一 个 32 位 程 序, 故 int 类 型 占 了 四 个 字 节,char 类 型 占 一 个 字 节 函 数 fun 的 作 用 是 把 一 个 整 数 的 四 个 字 节 的 顺 序 来 个 颠 倒 注 意 到 了 吗? 在 函 数 调 用 语 句 中, 实 参 &a 的 结 果 是 一 个 指 针, 它 的 类 型 是 int *, 它 指 向 的 类 型 是 int 形 参 这 个 指 针 的 类 型 是 char *, 它 指 向 的 类 型 是 char 这 样, 在 实 参 和 形 参 的 结 合 过 程 中, 我 们 必 须 进 行 一 次 从 int * 类 型 到 char * 类 型 的 转 换 结 合 这 个 例 子, 我 们 可 以 这 样 来 想 象 编 译 器 进 行 转 换 的 过 程 : 编 译 器 先 构 造 一 个 临 时 指 针 char *temp, 然 后 执 行 temp=(char *)&a, 最 后 再 把 temp 的 值 传 递 给 s 所 以 最 后 的 结 果 是 :s 的 类 型 是 char *, 它 指 向 的 类 型 是 char, 它 指 向 的 地 址 就 是
a 的 首 地 址 我 们 已 经 知 道, 指 针 的 值 就 是 指 针 指 向 的 地 址, 在 32 位 程 序 中, 指 针 的 值 其 实 是 一 个 32 位 整 数 那 可 不 可 以 把 一 个 整 数 当 作 指 针 的 值 直 接 赋 给 指 针 呢? 就 象 下 面 的 语 句 : unsigned int a; TYPE *ptr; //TYPE 是 int,char 或 结 构 类 型 等 等 类 型 a=20345686; ptr=20345686; // 我 们 的 目 的 是 要 使 指 针 ptr 指 向 地 址 20345686 ptr=a; // 我 们 的 目 的 是 要 使 指 针 ptr 指 向 地 址 20345686 编 译 一 下 吧 结 果 发 现 后 面 两 条 语 句 全 是 错 的 那 么 我 们 的 目 的 就 不 能 达 到 了 吗? 不, 还 有 办 法 : unsigned int a; TYPE *ptr; //TYPE 是 int,char 或 结 构 类 型 等 等 类 型 a=n //N 必 须 代 表 一 个 合 法 的 地 址 ; ptr=(type*)a; // 呵 呵, 这 就 可 以 了 严 格 说 来 这 里 的 (TYPE *) 和 指 针 类 型 转 换 中 的 (TYPE *) 还 不 一 样 这 里 的 (TYPE*) 的 意 思 是 把 无 符 号 整 数 a 的 值 当 作 一 个 地 址 来 看 待 上 面 强 调 了 a 的 值 必 须 代 表 一 个 合 法 的 地 址, 否 则 的 话, 在 你 使 用 ptr 的 时 候, 就 会 出 现 非 法 操 作 错 误 想 想 能 不 能 反 过 来, 把 指 针 指 向 的 地 址 即 指 针 的 值 当 作 一 个 整 数 取 出 来 完 全 可 以 下 面 的 例 子 演 示 了 把 一 个 指 针 的 值 当 作 一 个 整 数 取 出 来, 然 后 再 把 这 个 整 数 当 作 一 个 地 址 赋 给 一 个 指 针 :
例 十 七 : int a=123,b; int *ptr=&a; char *str; b=(int)ptr; // 把 指 针 ptr 的 值 当 作 一 个 整 数 取 出 来 str=(char*)b; // 把 这 个 整 数 的 值 当 作 一 个 地 址 赋 给 指 针 str 现 在 我 们 已 经 知 道 了, 可 以 把 指 针 的 值 当 作 一 个 整 数 取 出 来, 也 可 以 把 一 个 整 数 值 当 作 地 址 赋 给 一 个 指 针
9 指 针 的 安 全 问 题 看 下 面 的 例 子 : 例 十 八 : char s='a'; int *ptr; ptr=(int *)&s; *ptr=1298; 指 针 ptr 是 一 个 int * 类 型 的 指 针, 它 指 向 的 类 型 是 int 它 指 向 的 地 址 就 是 s 的 首 地 址 在 32 位 程 序 中,s 占 一 个 字 节,int 类 型 占 四 个 字 节 最 后 一 条 语 句 不 但 改 变 了 s 所 占 的 一 个 字 节, 还 把 和 s 相 临 的 高 地 址 方 向 的 三 个 字 节 也 改 变 了 这 三 个 字 节 是 干 什 么 的? 只 有 编 译 程 序 知 道, 而 写 程 序 的 人 是 不 太 可 能 知 道 的 也 许 这 三 个 字 节 里 存 储 了 非 常 重 要 的 数 据, 也 许 这 三 个 字 节 里 正 好 是 程 序 的 一 条 代 码, 而 由 于 你 对 指 针 的 马 虎 应 用, 这 三 个 字 节 的 值 被 改 变 了! 这 会 造 成 崩 溃 性 的 错 误 让 我 们 再 来 看 一 例 : 例 十 九 : char a; int *ptr=&a; ptr++; *ptr=115; 该 例 子 完 全 可 以 通 过 编 译, 并 能 执 行 但 是 看 到 没 有? 第 3 句 对 指 针 ptr 进 行 自 加 1 运 算 后,ptr 指 向 了 和 整 形 变 量 a 相 邻 的 高 地 址 方 向
的 一 块 存 储 区 这 块 存 储 区 里 是 什 么? 我 们 不 知 道 有 可 能 它 是 一 个 非 常 重 要 的 数 据, 甚 至 可 能 是 一 条 代 码 而 第 4 句 竟 然 往 这 片 存 储 区 里 写 入 一 个 数 据! 这 是 严 重 的 错 误 所 以 在 使 用 指 针 时, 程 序 员 心 里 必 须 非 常 清 楚 : 我 的 指 针 究 竟 指 向 了 哪 里 在 用 指 针 访 问 数 组 的 时 候, 也 要 注 意 不 要 超 出 数 组 的 低 端 和 高 端 界 限, 否 则 也 会 造 成 类 似 的 错 误 在 指 针 的 强 制 类 型 转 换 :ptr1=(type *)ptr2 中, 如 果 sizeof(ptr2 的 类 型 ) 大 于 sizeof(ptr1 的 类 型 ), 那 么 在 使 用 指 针 ptr1 来 访 问 ptr2 所 指 向 的 存 储 区 时 是 安 全 的 如 果 sizeof(ptr2 的 类 型 ) 小 于 sizeof(ptr1 的 类 型 ), 那 么 在 使 用 指 针 ptr1 来 访 问 ptr2 所 指 向 的 存 储 区 时 是 不 安 全 的 至 于 为 什 么, 读 者 结 合 例 十 八 来 想 一 想, 应 该 会 明 白 的
10 结 束 语 现 在 你 是 否 已 经 觉 得 指 针 再 也 不 是 你 所 想 的 那 么 害 怕 了, 如 果 你 的 回 答 是 : 对, 我 不 怕 了! 哈 哈, 恭 喜 你, 你 已 经 掌 握 C 语 言 的 精 华 了,C 中 唯 一 的 难 点 就 是 指 针, 指 针 搞 定 其 它 小 菜 而 已, 重 要 的 是 实 践, 好 吧, 让 我 们 先 暂 停 C 的 旅 程 吧, 开 始 我 们 的 C++ 编 程,C 是 对 底 层 操 作 非 常 方 便 的 语 言, 但 开 发 大 型 程 序 本 人 觉 得 还 是 没 有 C++ 方 便, 至 少 维 护 方 面 不 太 好 做 而 且 C++ 是 面 向 对 象 的 语 言, 现 在 基 本 已 经 是 面 向 对 象 的 天 下 了, 所 以 建 议 学 C++ C++ 是 一 门 难 学 易 用 的 语 言, 要 真 正 掌 握 C++ 可 不 是 那 么 容 易 的, 将 基 本 的 学 完 后, 就 学 数 据 结 构 吧, 算 法 才 是 永 恒 的, 程 序 设 计 语 言 层 出 不 穷, 永 远 学 不 完 学 完 之 后 就 认 真 啃 下 STL 这 根 骨 头 吧, 推 荐 书 籍 -------- 范 型 编 程 与 STL 和 STL 源 码 剖 析 如 果 你 达 到 了 这 样 要 求, 再 一 次 恭 喜 你, 你 已 经 是 个 程 序 高 手 了, 甚 至 可 以 说 是 个 算 法 高 手, 因 为 STL 里 有 大 量 的 精 华 而 高 效 的 算 法 唉, 已 经 该 说 再 见 的 时 候 了, 让 我 们 一 起 用 我 们 的 语 言 来 谱 写 我 们 的 人 生 吧, 最 后 笑 个, 哈 哈, 睡 觉 了 好 累, 都 2:00 了