UP | HOME

C 语言指针和数组

目录

1 简介

C 语言是一门很重要的语言, 即使你不会使用 C 语言进行程序开发, 但是学习了解 C 语言依然是一件很有必要的事情。

因为, 你学的是计算机 !

而指针和数组, 算是 C 语言中比较难以理解的两个东西了。

学 C 开始到现在, 快要两年了, 在指针和数组上遇到过不少坑, 也见识过不少奇淫技巧,到了现在, 感觉可以对两者进行一定的总结了。

NOTE: 本篇博客主要内容是对指针和数组的理解与总结, 语法涉及的不多

2 基本对象

为了更好的理解 指针和数组, 需要引入两个基本对象:

  • 内存地址: 内存中的一个地址, 如: 0xFFFFFFFF
  • 地址的值: 内存中一个地址保存的值, 如: 地址 0xFFFFFFFF 的值为 0x01234567

3 指针的值

学习指针的过程中, 一定要分清楚的两个值是: 指针地址指针的值.

这是容易混淆的两个值, 因为这两个值都是 内存地址.

但是, 在使用指针的时候需要分清楚这两个值的关系: 指针地址 的值是 指针的值.

这是指针比较特殊的一点, 即: 所有的指针操作都是围绕着 内存地址 进行的。

这两个概念需要分清楚的原因是为了理解 指针其他类型 的区别:

  • 指针其他类型 没有实质性的区别
  • 两者的主要区别体现在 保存的值指针运算

4 指针运算

和其他类型不同, 由于指针保存的值是 内存地址, 因此指针运算的直接操作对象就是 内存地址.

C 语言通过操作符 &* 来产生 指针间接引用指针.

指针, 是类似于 &var 产生的值, 代表 var内存地址.

间接引用指针, 类似于 *ptr 的使用, 操作修改 指针的值(内存地址)值(地址的值).

因此, 表达式 *&ExprExpr 是等价的, 都是操作 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

可以看到, &arrarr 的值是相同的。

也就是说, 数组名 相当于 指针地址指针的值 相同的 指针.

但是, &arrarr 并不相同:

  • &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, 指向的对象是一个拥有三个元素的 数组.

10 参考链接

版权声明:本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可