案前独忆灯明灭

怕他什么整理无穷, 进一寸有一寸的欢喜

指针与名称空间

本篇为机器人协会新生培训所用.

让我们来考虑一个问题: > 如何写一个函数来交换两个变量的值?

再来看一段代码:

a 的值并没有改变, 因为C语言采取切仅采用 按值传递 的方式, 也即值在函数与函数之间传递时, 得到的仅仅是变量的备份, 并得到变量本身.

change_value(a) 中, 系统做了:

  1. 建立一个 int 类型的局部变量 value
  2. 把它赋值为 a
  3. value 加1

整个过程都没有对 a 本身进行操作.


如果我们非要用另外的一个函数去改变 a 的值该怎么做呢? 既然无法直接传递 a 那么能否传递一个可以链接 a 的东西, 然后通过这个东西去修改 a 呢?

答案当然是可以, 这个东西就叫做 指针(pointer)

给大家画了一张图:

内存模型
内存模型
内存模型

图中的代码片段 *pt = &a; 出现了 *&, 两个新的操作符, & 表示变量的地址, * 我们稍后再说.

现在我们利用指针对 change_value(a) 进行修改

我们再来继续摆弄一下这个函数:

显然, * 是用来表示 pt 所指的那个地址上面的值, 可是, 真的这么简单吗?

回顾一下上面第7行的 int *pt = &a; 我们来推理一下

这个推理结果和输出 the value of a is 1 矛盾.

那么 * 到底是什么呢? 在大部分情况下, 它都表示 pt 所指的那个地址上面的值, 只有在声明变量的时候, 是用来表示 pt 是个指针. 我们也可以将

写成

不过下面那种写法在C语言中使用的比较少, 而在C++中使用的比较多, 不过这只是风格问题.

现在请动手完成我们最开始提出的问题, 写一个函数交换两个变量的值.

在指针的角度看数组

我们来回顾一下声明数组

再来看看他们的地址

可以看到 nice_array 的内存时连续的. 而 nice_array 的地址步长为4, 这是因为 int 类型占4个字节(32位). 所以总的来说, 数组的内存时连续的.

再来看一个大胆的想法:

这个结果回答了三个问题:

  1. 数组名既是数组首地址的指针
  2. 指针是可以进行加法运算的(请思考: 减法呢?乘除呢?)
  3. 声明指针时需要说明所指的数据类型, 因为不同的数据类型所占用的内存长度不同

于是我们又有了两个问题:

  1. int 指针和 char 有什么区别吗?
  2. 既然指针的值是且仅是一个地址, 那么 int 指针怎么知道要读取四个字节的内存, 而 char 又怎么知道只要读取一个字节?

变量存在时期

一般的变量仅在声明它的最近的大括号内有效, 这个大括号, 我们可以理解为变量的名称空间. 但是有两种特殊的变量修饰符需要我们注意,

static

static 修饰的变量会放在静态变量区, 其只在第一次使用时被初始化, 名称空间也不仅限于大括号中.

extern

另一个是 extern, 用于引用再别的文件中声明的变量. 这东西, 在一个文件的时候没用, 在两个以上文件的工程才有作用. 还是先来看一个例子

我们在 fileA.c 中声明了 say_hello_time 这个变量. 接下来, 让我们在另外一个文件中引用这个变量

上面这段程序是最直接的写法, 但是不是最常用的写法. 在工程中, 一般我们并不会把函数原型放在 .c 文件中, 也不会把外部变量的声明和变量声明放在不同的文件中, 因为这样可能会出现声明冲突的问题. 取而代之, 我们将函数原型, 外部变量声明, 类型定义等需要被外部引用的定义写入 .h 文件中, 将函数主体, 变量声明写入 .c 文件中, 再将 .c 文件加入工程, 而 .h 文件则用 #include 引用.

因而我们可以改进成如下样子:

.h 文件中, 我们常用

来判断和建立文件标记, 以防止重复引用导致预处理器陷入死循环.

最后在强调一遍, 文件需要被添加在工程中. 在 C-Free5 中, 我们可以通过 工程-新建 来新建一个 Win32控制台 工程.

⬅️ Go back