C 语言指针和数组
1 简介
C 语言是一门很重要的语言, 即使你不会使用 C 语言进行程序开发, 但是学习了解 C 语言依然是一件很有必要的事情。
因为, 你学的是计算机 !
而指针和数组, 算是 C 语言中比较难以理解的两个东西了。
学 C 开始到现在, 快要两年了, 在指针和数组上遇到过不少坑, 也见识过不少奇淫技巧,到了现在, 感觉可以对两者进行一定的总结了。
NOTE: 本篇博客主要内容是对指针和数组的理解与总结, 语法涉及的不多
2 基本对象
为了更好的理解 指针和数组, 需要引入两个基本对象:
- 内存地址: 内存中的一个地址, 如:
0xFFFFFFFF
- 地址的值: 内存中一个地址保存的值, 如: 地址
0xFFFFFFFF
的值为0x01234567
3 指针的值
学习指针的过程中, 一定要分清楚的两个值是: 指针地址 和 指针的值.
这是容易混淆的两个值, 因为这两个值都是 内存地址.
但是, 在使用指针的时候需要分清楚这两个值的关系: 指针地址 的值是 指针的值.
这是指针比较特殊的一点, 即: 所有的指针操作都是围绕着 内存地址 进行的。
这两个概念需要分清楚的原因是为了理解 指针 与 其他类型 的区别:
- 指针 和 其他类型 没有实质性的区别
- 两者的主要区别体现在 保存的值 与 指针运算 上
4 指针运算
和其他类型不同, 由于指针保存的值是 内存地址, 因此指针运算的直接操作对象就是 内存地址.
C 语言通过操作符 &
和 *
来产生 指针 和 间接引用指针.
指针, 是类似于 &var
产生的值, 代表 var
的 内存地址.
间接引用指针, 类似于 *ptr
的使用, 操作修改 指针的值(内存地址) 的 值(地址的值).
因此, 表达式 *&Expr
和 Expr
是等价的, 都是操作 Expr
的值。
5 指针类型
由于所有的指针保存的值都是一个 内存地址, 因此所有指针变量的大小都是一样的, 等于操作系统的 地址宽度.
这时, 为了区分不同的 指针, 指针类型 的作用便体现出来了。
首先, 需要明白的一点是: 单个地址能够保存的数据大小是 一个字节.
然而, 除了 char
, 其他大多数类型的变量的大小都大于一个字节, 因此这些类型的变量会占据 连续 的几个内存来保存自身的值。
自身的 内存地址 仅为这几个 连续 内存地址的 首地址.
同样, 指针 保存的值就是这个 首地址.
指针类型 的作用就是确定这个 连续 的内存地址究竟有多少个。
比如, int*
类型的指针就表示这个连续的内存地址有 4
个。
同时, 指针 加减一个 整数 的时候, 实际加减的值为 整数 和 类型大小 的乘积。
而 指针 减 指针 的时候, 得出的结果会 除 以 类型大小.
另外, 需要注意的一点是: 转换指针类型不会改变 指针的值.
6 指针参数
当函数的参数是一个指针的时候, 传参时的行为和其他类型的变量 没有区别.
都是传递 变量的值.
#include <stdio.h> void test(int* ptr) { printf("%p %p\n", &ptr, ptr); } int main(void) { int var = 10; int* ptr = &var; test(ptr); printf("%p %p\n", &ptr, ptr); return 0; }
以上代码的执行结果为:
000000000022FE20 000000000022FE4C 000000000022FE40 000000000022FE4C
可以看到, 指针地址 是不一样的, 相同的是 指针的值.
函数内部的修改能够反馈到函数的调用者的原因仅仅是两者操作的 内存地址 相同而已。
7 函数指针
如果你学过操作系统, 那么应该就知道, 函数 只是内存中的一个 指令块.
调用函数的过程就是将要运行的指令转移到该 指令块.
C 语言中, 函数名 相当于该 指令块 的首地址。
既然是地址, 那么就可以用 指针 来保存。
因此可以用下面的方式来定义函数指针:
int func(int a, int b) { return a + b; } int (*fptr)(int, int) = func;
其中, int
表示函数的返回值类型为 int
, (*fptr)()
表示这是一个函数指针。
括号内部为参数类型。
由于 func
就代表了函数地址, 因此可以省略操作符 &
.
当然, 这样也是等价操作:
int (*fptr)(int, int) = &func;
8 数组
在 C 语言中, 数组和指针的关系密切, 主要原因就是 数组名 相当于指向数组第一个元素地址的 指针.
但是这个指针很特殊:
#include <stdio.h> int main(void) { int arr[4] = {1, 2, 3, 4}; printf("%p %p", &arr, arr); return 0; }
上述代码的输出为:
000000000022FE40 000000000022FE40
可以看到, &arr
和 arr
的值是相同的。
也就是说, 数组名 相当于 指针地址 和 指针的值 相同的 指针.
但是, &arr
的 arr
并不相同:
&arr
产生的是 数组类型 的指针,&arr + 1
的结果是&arr
的值和 数组大小 的和arr
相当于指向 数组 第一个元素的 指针, 指针类型就是 元素类型.
同时, 数组取值操作 arr[i]
等价于 *(arr + i)
. 因此在使用指针操作数组的时候, 不要忘了符号 *
.
当然, 如果不想使用 *ptr
的方式来使用指针, ptr[0]
的结果也是相同的, 当然, 不推荐。
另: arr[i]
和 i[arr]
没有区别。
9 指针数组和数组指针
指针数组和数组指针是容易犯错的一个地方, 简单梳理一下:
- 指针数组是一个数组, 这个数组保存的元素的类型是指针
- 数组指针是一个指针, 这个指针指向的对象是一个数组
通常来说, 指针数组使用如下方式声明:
int* prt_arr[4];
声明一个数组 ptr_arr
, 保存的元素类型为 int*
.
而数组指针使用下面的方式声明:
int (*arr_ptr)[3];
声明一个指针 arr_ptr
, 指向的对象是一个拥有三个元素的 数组.