C语言探索之旅 | 第二部分第二课:进击的指针,C语言的王牌!

做者 谢恩铭,公众号「程序员联盟」(微信号:coderhub)。 转载请注明出处。 原文:www.jianshu.com/p/e5e685b67…程序员

《C语言探索之旅》全系列编程

内容简介


  1. 前言
  2. 棘手的问题
  3. 内存,地址的问题
  4. 指针的使用
  5. 传递指针给函数
  6. 谁说“棘手的问题”了?
  7. 总结
  8. 第二部分第三课预告

1. 前言


上一课是 C语言探索之旅 | 第二部分第一课:模块化编程小程序

终于来到了这一刻(课)。是的,这一课咱们就来学习《C语言探索之旅》的重头戏中的重头戏:指针数组

  • 若是把这个系列课程比做寻宝之旅的话,那么指针就是最贵重的那个宝藏。
  • 若是把 C语言比做一棵佳美的葡萄树,那么指针就是那累累硕果;
  • 若是把 C语言比做太阳系,那么指针就是咱们美丽的地球;
  • 若是把 C语言比做人的一辈子,那么指针就是使人神往的爱情;
  • 若是必定要在这份爱上加一个期限,我但愿是一万年...

“很差意思,又跑题了~”安全

总而言之,言而总之,一块儿来享用这份精心烹制的指针大餐吧!bash

在开始这一课前,请深吸一口气,由于这一课可能不会像以前那些课通常“悠哉”了。微信

指针也许是 C语言设计最出彩的地方了,也是精华部分。若是没有了指针,C语言也会黯然失色。模块化

可能你以为我有点夸大其词,可是在 C语言的编程中,指针是随处可见的,使用很普遍,因此咱们不得不来吃掉这个“烫手山芋”。函数

很多朋友学 C语言的时候,指针那块老是有点“蹒跚却步”。在这一课里咱们会努力使你再也不如此。你会发现,指针也没那么难。学习

好的开始是成功的一半,一块儿加油吧!

2. 棘手的问题


对于初学 C语言的朋友,除了以为指针有点神秘以外,最大的问题之一多是:搞明白指针究竟是用来干什么的

对此,我会回答说:“指针绝对是必不可少的,咱们会一直使用它,请相信我!”

你可能会丢我砖头(说不定还有西红柿、鸡蛋,等物品),由于我说了等于没说。

好吧,我会给你一个问题,你会发现假如不用指针是不能解决的。这个问题有点相似咱们这一课的引子。

在这一课的结尾咱们还会从新来谈这个问题,并尝试用咱们立刻要学到的知识来解决。

问题是这样:我要写一个函数,它返回两个值

“这不可能!”,你会义正词严地说。

确实,以前咱们学过:一个函数只能经过 return 返回一个值

int function()
{
    return value;
}
复制代码

如上,咱们将函数的返回值类型定为 int,那么就用 return 返回一个 int 类型的值。

咱们也能够不返回任何值,只要把函数的返回值类型定为 void :

void function()
{
}
复制代码

你会说:“是啊,要一个函数一次返回两个值,不可能啊,咱们不能写两个 return 啊。臣妾作不到啊...”

假设我要写的这个函数是这样:

咱们给它输入的参数是一个分钟数,它返回给咱们两个值:小时数和分钟数。 例如: 传给函数 30,它返回 0 小时 30 分钟。 传给函数 60,它返回 1 小时 0 分钟。 传给函数 90,它返回 1 小时 30 分钟。

咱们能够试着来写一下这个函数:

#include <stdio.h>

/* 我把函数原型放在开头了,并无用到 .h 头文件,由于程序实在过小了。
固然在正常状况下,通常是用 .h 头文件比较好 */
void transformMinutes(int hours, int minutes);

int main(int argc, char *argv[])
{
    int hours = 0, minutes = 90;

    /* 咱们的分钟数是 90。我想要在调用 transformMinutes 函数后,
    小时数变为 1,分钟数变为 30 */
    transformMinutes(hours, minutes);

    printf("%d 小时 : %d 分钟\n", hours, minutes);

    return 0;
}

void transformMinutes(int hours, int minutes)
{
    hours = minutes / 60;     // 90 / 60 = 1
    minutes = minutes % 60;   // 90 % 60 = 30
}
复制代码

看上去还不错对么?咱们来运行一下这个程序。输出:

0 hours and 90 minutes
复制代码

不对,不对,这个函数没有按照咱们所想的来运行嘛!

到底这里发生了什么呢?

事实上,C语言的函数参数默认是传值调用的(也叫值传递),就是说当咱们传给函数的参数一个变量时,事实上传递的是这个变量的一份拷贝,并非这个变量自己!

程序会先对这个要传递给函数的变量作一份拷贝,而后把这份拷贝传给函数使用。

因此说:上面咱们 main 函数里的 hours 变量和实际传给 transformMinutes 函数的 hours,是不同的,传给 transformMinutes 函数的只是 hours 变量的一个拷贝而已。就比如用复印机复印了一份纸张,内容是同样,可是一个是原件,一个是复印件。

固然了,咱们的函数 transformMinutes 很乖很呆萌,你叫它干的活它确定出色完成:在函数内部,它把参数 hours 和 minutes 的值经过计算转换成了 1 和 30。

可是,注意了,由于 transformMinutes 拿到的两个参数 hours 和 minutes 的值自己只是实际的变量 hours 和 minutes 的一份拷贝。

并且咱们以前学过,在函数结束时,它里面的非 static 变量都会销毁,因此 hours 和 minutes 这两个拷贝就都会被删除了。

因此函数 transformMinutes 勤勤恳恳地工做以后,把程序交还给 main 函数继续执行,可是 hours 和 minutes 这两个 main 函数里的变量的值并无被改变,仍是 0 和 90。惋惜啊!

注意:函数的参数的名字和要传给它的变量的名字不须要是同样的,上例中咱们为了清楚表示含义,才把 main 函数里的两个变量和函数 transformMinutes 的两个参数都命名为 hours 和 minutes。

其实你大能够把函数的参数写成随便什么名字,例如:

void transformMinutes(int h, int m)
复制代码

简而言之,问题仍是在那里。

这里咱们须要用函数来改变两个变量的值,咱们不能用 return,由于一个函数只能 return 一个返回值;也不能用全局变量,虽然全局变量行得通,可是咱们以前的课已经说了,尽可能不用这种不安全的变量。

那么指针到底能如何解决咱们的难题呢?且听咱们慢慢道来。

3. 内存,地址的问题


往事重提

“When I was young, I listened to the radio, waiting for my favorite songs...”

很差意思,我搞错了,不是《昨日重现》(Yesterday Once More)这首歌,咱们说的是回顾一下以前“变量”的那一课。

咱们以前在 C语言探索之旅 | 第一部分第四课:变量的世界(一),内存那档事 那一课中展现过一张很重要的图,以下:

咱们用上图简单地展现了内存(RAM)。

咱们应该一行一行地来“研究”这张图:

第一行(地址为 0 的那一行)展现了内存的第一个“区块”(内存地址的最小单位是 1 个 Byte,也就是一个字节,一个字节有 8 个比特位(bit))。

每个“区块”都有一个“门牌号码”,就是它的地址。比如咱们有一排信箱,每一个信箱上有不一样的号码,编号由小到大。每一个信箱里储存的东西就是信啦,就至关于内存地址上存放的数据。

但咱们知道电脑数数是从 0 开始的(由于二进制的关系),因此内存地址的第一位地址是 0。

咱们的内存通常有好多地址,从 0 一直到某一个比较大的数。通常来讲,内存容量越大,可用地址就越多。好比 4GB 的内存的地址数就比 1GB 的多得多。

在每个内存地址上,咱们均可以存放一个数,也只能存放一个数,一个内存地址不能存放两个数。

内存的主要功用就是存储数值嘛。它不能存储字母也不能存储句子(由于电脑只认识 0 和 1 组成的数字)。

为了解决这个问题,计算机先驱们创立了一个表,这个表格创建了字符与数字的一一对应关系,比较经常使用的就是 ASCII 码表(更全面的是 Unicode 表)。

在这个表里,大写字母 Y 对应的数字是 89,小写字母 a 对应的数字是97,等等。你能够去网上搜索 ASCII 表或 Unicode 表。

在以后的课程里咱们会再讨论字符的处理,目前咱们先把注意力集中在内存的功用上。

地址和值


当咱们建立了一个 int 类型的变量,例如:

int age = 10;
复制代码

实际上,你的程序首先会“请示”一下操做系统(Operating System,简称 OS。Windows、Linux、macOS、Unix,等等都是经常使用的操做系统):“可否拨一点内存给我用用?”。

通常来讲都是能够的,因而操做系统“告诉”你哪一小块内存地址能够用来存放 age 变量的值。

上面所说的其实也正是操做系统的一个主要任务:分配内存给程序。它好像一个大 boss、大管家,控制每一个程序,确认程序是否有使用某一块内存区域的权利。

这其实也是咱们的程序不少时候奔溃的缘由:若是你的程序试图操做一块没有使用权的内存,那么操做系统就会“横加干涉”,“粗暴”地中止你的程序(老大,就是这么任性...)。

用户就会看到一个“美丽”的窗口弹出来,里面写着“出现了一个问题,致使程序中止正常工做,Windows 正在寻找问题的解决方案” (但其实通常是没什么解决方案的,微软只会忽悠你...)。

以下图:

好了,从新说回咱们的变量 age。由于咱们将 age 的值定为 10,因此在内存的某个地址上就储存了 10 这个值,假设内存地址为 123456。

那咱们的程序编译的时候会发生什么呢?对了,还记得咱们的编译器么。它负责把咱们的源代码转成电脑能够理解的二进制数。因此 age 这个变量在程序运行时就被 123456 这个地址所取代了。

这样,每次你在程序里调用 age 这个变量时,电脑都会把其替换为 123456 这个地址,而且去内存中的 123456 这个地址取它的值。

因此,咱们就知道变量的值是怎么被取得的了。在程序中,咱们只须要在想要使用变量的值的地方简单地输入变量的名字就能够了。例如咱们要显示 age 的值,咱们能够这样调用 printf 函数:

printf("变量 age 的值是 : %d\n", age);
复制代码

运行程序会显示:

变量 age 的值是 : 10
复制代码

至此,并无太多新的知识点。可是...

稍微来点劲爆的


咱们已经知道怎么显示变量的值了,可是你可知道咱们也能够显示变量在内存上的地址?

是的,你没有听错。

为了显示变量的地址,咱们须要使用符号组合 %p(p 是 pointer 的首字母,pointer 就是英语“指针”的意思)。

并且咱们此次不是把 age 传给 printf 函数这么简单啦,咱们是要传递 age 变量的地址。

为了作到这一点,咱们须要在 age 前面加上 & 这个符号。

咱们以前的课里面,说到 scanf 函数的用法时,就是用的 &age 这样的形式,那时候没有解释为何,如今你知道了吧。

所以,咱们的代码能够这么写:

printf("变量 age 的地址是 : %p", &age);
复制代码

运行的结果是:

变量 age 的地址是 : 0034FFE6
复制代码

你看到的 0034FFE6 就是我运行程序时获得的 age 的地址的值。

0034FFE6 是一个数,并且是用 16 进制来表示的。固然你能够将上述代码中的 %p 改写为 %d,那就能看到以十进制来表示的 age 的地址值了。

固然了,你运行程序获得的地址值通常来讲确定与个人不同。这个值实际上是电脑分配给 age 变量的一个可用地址值,因此每一个人运行出来的结果不尽相同。

你也能够屡次运行程序,会看到这个值没变,由于这段时间里,内存仍是保持原样。不过,若是你重启电脑,再次运行程序,那么这个值通常会变得不同。

上面的知识点,能够概括以下:

  • age :表示变量的值。
  • &age :表示变量的地址。

不难吧~

4. 指针的使用


到目前为止,咱们还只是建立了储存数值的变量。

如今,咱们一块儿来学习如何建立储存地址的变量,也就是咱们所说的指针

可是,你又会问:“内存的地址也是数值啊。搞了半天都是存储数值,那指针和通常变量到底有什么区别呢?”

好问题!

为何说指针特殊呢?由于指针存储的地址值,指明了内存中另外一个变量的地址。

建立指针


指针的英语是 pointer。point 在英语中表示“指向”,是动词。pointer 在英语中表示“指针,指示器”,是名词。

顾名思义,指针就是表示指向某些东西的一个概念。

为了建立一个指针类型的变量,咱们须要在变量名前再加一个 * 号。例如:

int *myPointer;
复制代码

注意,上面的代码也能够写成

int* myPointer;
复制代码

效果是同样的。可是建议使用第一种写法,由于第二种写法容易引发误解。

假如一行里同时声明好几个指针变量,也许会写成:

int* pointer1, pointer2, pointer3;
复制代码

你可能觉得 pointer2 和 pointer3 也是 int* 的指针变量,但其实不是,它们只是 int 变量,由于 * 号的结合优先级是从左到右,因此上面这行代码的实际效果是:

建立了三个变量,pointer1 是一个指向 int 类型的指针变量,pointer2 和 pointer3 都只是 int 类型的普通变量。

因此正确的写法应该是这样:

int *pointer1, *pointer2, *pointer3;
复制代码

咱们在以前的课程里说过,在声明变量的同时最好初始化,这样能够保证变量的值不是任意的。

这个原则也适用于指针,并且对于指针来讲初始化尤其重要,之后会说为何。

初始化一个指针变量,就是给它赋一个默认值。咱们不用 0,而是用 NULL(null 表示“空,无效的,无价值的”。注意,在 C语言中 NULL 是大写,其余语言如 Java 中 null 是小写)。

int *myPointer = NULL;
复制代码

这样,初始时你的指针里面就不包含有效地址。

实际上,这段代码会在内存中占用一块区域,就和普通的变量定义没什么两样。可是,不一样的是,这块区域(内存地址)上存放的是指针的值,而这个值是一个地址值,一个其余变量的地址。

那咱们何不来试试把变量 age 的地址赋给一个指针呢?以下:

int age = 10;

int *pointerOnAge = &age;
复制代码
  • 第一行的意思是:建立一个 int 型的变量,名字是 age,值为 10。
  • 第二行的意思是:建立一个指针变量,名字是 pointerOnAge,值为变量 age 的地址。

你确定注意到了,咱们虽然用了“指针变量”这个词,但其实并无一种特定的类型叫“指针”,就像 int,double 这样的基础类型(虽然有的书上说有“指针”这个类型)。咱们并无像下面这样写(只是打个比方,pointer 是英语“指针”的意思):

pointer pointerOnAge;   // 这种写法不存在!由于 C语言没有 pointer 这个类型
复制代码

相反地,咱们用符号 * 来声明一个指针变量,至于类型咱们仍是给它 int 这样的基本类型,这意味着什么呢?

事实上,咱们必须指明指针所要包含其地址的那个变量的类型(有点拗口...)。

好比上例中,咱们的 pointerOnAge 指针须要包含 age 的地址,而 age 变量的类型是 int,所以咱们就必须将指针的类型定为 int* 。

若是 age 变量的类型是 double,

double age = 10;
复制代码

那么就得这样声明咱们的指针:

double *pointerOnAge;
复制代码

咱们能够简单地理解为:

一个基本的数据类型(如 int, double,包括结构体等自定义类型(关于自定义类型,咱们立刻就能够学到了)加上 * 号就构成了一个指针类型的“模子”。

这个“模子”的大小是必定的,与 * 号前面的数据类型无关。

  • 号前面的数据类型只是说明指针所指向的内存里存储的数据类型。

因此,在 32 位系统下,无论什么样的指针类型,其大小都为 4 个 Byte(字节,等于 8 个二进制位,也就是 8 个比特位)。在 64 位系统下,无论什么样的指针类型,其大小都为 8 个 Byte。

咱们能够用下面的语句测试一下:

printf("指针的大小是 %lu 字节\n", sizeof(void *));
复制代码

你能够把上面语句中的 void * 改成 int * 或者 double *,等等,输出都是同样的。

术语: “指针 pointerOnAge 指向 age 变量”


为了让你知道到底内存里是怎么一回事,咱们用下图给出一个更直观的展现:

上图中,age 变量被存放在地址 123456 上,咱们能够看到它的值是 10;而咱们可爱的指针变量 pointerOnAge 被存放在地址 3 上(这里的地址值只是举个例子)。

当个人指针变量 pointerOnAge 被建立时,操做系统就在内存上给它分配了一块地址,就跟建立 age 变量同样。

可是不一样的是,变量 pointerOnAge 有点特别。仔细看图,它的值正是 age 变量的地址 123456

好了,亲爱的读者,其实你已经了解了全部 C语言程序的绝对奥秘!

咱们已经作到了!是的,咱们刚刚跨入了指针的美妙世界!(固然了,要精通指针的使用还有很多路要走,可是难道不该该给本身一点鼓励吗?)

那你要问了:“这个机制有什么用呢?”

固然了,这个伟大的机制并没能让你的电脑成为一台能够煮咖啡的机器...

目前咱们还只是有了一个指针变量 pointerOnAge,它的值是 age 这个变量的地址,仅此而已(“我读书少,你可不要骗我...”)。

用 printf 函数来输出 pointerOnAge 这个变量的值吧:

int age = 10;

int *pointerOnAge= &age;

printf("%d\n", pointerOnAge);
复制代码

假设上面的代码输出以下:

123456
复制代码

没什么可惊讶的,咱们用 printf 输出 pointerOnAge 的值,它的值正是 age 变量的地址 123456。

那咱们怎么可以取得 pointerOnAge 这个指针变量中储存的内存地址上的那个变量的值呢(好拗口...)?

咱们须要再一次用到 * 号,但此次它的做用和上一次声明指针变量时不同,这一次它的做用是 取得指针所指向的地址上的变量值

例如:

int age = 10;

int *pointerOnAge= &age;

printf("%d\n", *pointerOnAge);
复制代码

运行以上程序,输出为:

10
复制代码

太棒了,咱们只是使用了 * 号,把它放在指针变量名前,就取得了它所指向的变量的值。

假如上面的程序中,咱们在 pointerOnAge 前面写的不是 * 号,而是 & 号,那么 printf 输出的就是 pointerOnAge 的地址值(在咱们的例子中是 3)了。

可是你又要问了: “大费周章使用指针干什么呢?咱们好像把事情复杂化了,不是吗? 原本咱们要输出变量 age 的值,只须要直接调用 age 就行了。如今还要把 age 的地址赋给指针变量 pointerOnAge,而后再用 * 号来提取 pointerOnAge 里的地址值所存的变量值,就是 age。 搞了半天是同一个东西,为何要绕这么一大圈呢?”

这个问题是很合理的。可是不要急,学下去,你就会发现指针的妙处了。

请暂时不理会这个问题,目前主要是学习指针的功用,这个问题稍后自会“守得云开见月明”的。

毕竟,老爷子 Dennis Ritchie(C语言之父 丹尼斯.里奇)不是傻子,他不会只为了好玩或者把事情搞复杂而发明指针的。

必须牢记的


在这一课中要牢记几点:

  • 对于一个普通变量,例如 age 变量:

age :意味着“age 变量的值”。 &age :意味着“age 变量所在的地址”。

  • 对于一个指针变量,例如 pointerOnAge 变量:

pointerOnAge :意味着“pointerOnAge 的值”(这个值是一个地址)。 *pointerOnAge :意味着“pointerOnAge 的值所标明的地址上的变量值”。

下图能够帮助你加深理解:

注意:不要混淆了 * 号的做用。

在声明一个指针变量时,* 号的做用只是表示我要建立一个指针变量:

int *pointerOnAge;
复制代码

而在以后的程序中,当咱们写:

printf("%d\n", *pointerOnAge);
复制代码

这里的 * 号的做用不是说“我要建立一个指针变量”,而是“取得指针变量 pointerOnAge 储存的地址所指向的变量的值”。

上面的概念是很是重要的。指针的基本概念确实比较难理解,即便你如今感到“云里雾里”,也没什么好羞愧的,我之前也是花了好久才搞清楚指针到底怎么回事。

如今看来有点抽象是彻底正常的,慢慢来,不要急,随着多看代码和多写代码,会慢慢精通的。

5. 传递指针给函数


指针的一个优点就是用来传递给函数,做为函数的参数,使得在函数里修改指针所指向的变量的值,就直接在内存上修改了,而不是像以前看到的那样,只是修改了一份拷贝,并无真正修改到实际的那个变量。

怎么作到呢?有好几种方法,先来看第一种:

#include <stdio.h>

void triplePointer(int *pointerOnNumber);

int main(int argc, char *argv[])
{
    int number = 5;

    triplePointer(&number);  // 将 number 变量的地址传给函数 triplePointer

    printf("%d\n", number);  // 显示number的值。上面函数已经直接修改了 number 的值,由于函数知道 number 的内存地址

    return 0;
}

void triplePointer(int *pointerOnNumber)
{
    *pointerOnNumber *= 3;   // 将 pointerOnNumber 的值乘以 3
}
复制代码

运行程序,显示:

15
复制代码

函数 triplePointer 接受一个 int* 类型的参数(就是说指向 int 类型的指针)。

咱们从 main 函数的开始处分析究竟程序里发生了什么吧:

  • 建立变量 number,类型是 int,值为 5。

  • 调用函数 triplePointer,给它的参数是变量 number 的地址。

  • 函数 triplePointer 接受了这个参数(number 的地址),并把它传递给 pointerOnNumber 储存。如今 triplePointer 函数的内部,咱们就有一个叫 pointerOnNumber 的指针,它的值是 number 的地址。

  • 由于咱们已经有了一个指向 number 的指针,咱们就能够在内存中直接修改 number 的值了,而不是像普通的传值调用那样修改拷贝的值。咱们只须要用 *pointerOnNumber 来表示 number 的值。上例中,咱们将 *pointerOnNumber 乘以 3,其实就是直接将 number 的值乘以 3。

  • 函数 triplePointer 执行完成,把控制权交给 main 函数继续执行,这时 number 的值已经变成 15 了,由于函数 triplePointer 借着指针直接修改了 number 的值(大有“挟天子以令诸侯”之势)。

因此,借着指针,咱们不须要用 return,也能够修改好多个变量的值,直接在内存上修改,就好像咱们能够返回多个值同样。

有了指针,函数就再也不只能返回一个值了。

你的问题又来了:“既然指针这么好,那咱们还要 return 语句干吗呢?”

好问题。

答案是:这取决于你和你的程序,由你决定。要知道的是,return 语句在 C语言里是颇有用也很经常使用的。

有时候咱们能够返回一个值,来代表程序是否正常运行。假如出错,则返回 0(表示 false);假如正常结束,则返回不为 0 的整数,通常为 1(表示 true)。也有反过来用的。

将指针传给函数的另外一种方式


刚才的那段代码中,咱们在 main 函数里并无声明指针,只有一个变量 number,惟一看到指针的地方是在 triplePointer 函数的定义中

如今咱们就来看一下第二种方式,在 main 函数里用到指针的方式:

#include <stdio.h>

void triplePointer(int *pointerOnNumber);

int main(int argc, char *argv[])
{
    int number = 5;

    int *pointer = &number;  // pointer 里面储存的是 number 的地址值

    triplePointer(pointer);  // 将 pointer(值是 number 的地址)传给函数

    printf("%d\n", *pointer);  // 用 *pointer 来显示 number 的值

    return 0;
}

void triplePointer(int *pointerOnNumber)
{
    *pointerOnNumber *= 3;  // 将 number 的值乘以 3
}
复制代码

运行程序,输出:

15
复制代码

对比一下两个程序,有细微的差异,结果倒是相同的。

这两个程序最关键的地方就是:传给函数 triplePointer 的参数是变量 number 的地址。

第二个程序中,pointer 的值就是 number 的地址,因此正确。

在函数 printf 中,咱们用 *pointer 来代替 number。由于它们二者在内存中是一回事。

在之前的课中,咱们写过一个 C语言的游戏,就是《或多或少》(请看 C语言探索之旅 | 第一部分第十课:第一个C语言小游戏 )。

其实在那个程序里你也在不知不觉中使用了指针。

聪明如你可能已经想到了。是的,就是 scanf 函数。

还记得么,咱们当时有这样一小段程序:

int number = 0;

scanf("%d", &number);
复制代码

相似的,咱们也是传递了 number 的地址给 scanf 函数,因此 scanf 就可使用用户经过键盘输入的值来直接修改 number 的值了。

固然咱们也能够这样写:

int number = 0;

int *pointer = &number;

scanf("%d", pointer);
复制代码

这两段小程序的效果是同样的。

6. 谁说“棘手的问题”了?


这一课就要结束了,是时候来调出咱们的引子:那个所谓“棘手”的问题。

若是你跟着这一课学下来,这个问题应该难不倒你了吧?不妨试试。

给出个人解法,能够作对比:

#include <stdio.h>

/* 我把函数原型放在开头,并无用到 .h 头文件,由于程序实在过小了。
固然在正常状况下,通常是用 .h 头文件比较好 */
void transformMinutes(int *hours, int *minutes);

int main(int argc, char *argv[])
{
    int hours = 0, minutes = 90;

    // 这一次咱们传递了 hours 和 minutes 的地址
    transformMinutes(&hours, &minutes);

    // 这一次,数值如咱们所愿改变了
    printf("%d 小时 : %d 分钟\n", hours, minutes);

    return 0;
}

void transformMinutes(int *hours, int *minutes)
{
    // 记得,不要忘了取值符号(*),这样你才能够改变变量的值,而不是它们的地址
    *hours = *minutes / 60;    // 90 / 60 = 1
    *minutes = *minutes % 60;    // 90 % 60 = 30
}
复制代码

运行以上程序,输出:

1 小时 : 30 分钟
复制代码

看到了吗?自从有了指针,天空飘来五个字:“那都不是事”~

7. 总结


  1. 每个变量都储存在内存中的肯定地址上。

  2. 指针是一种特殊的变量。与普通变量存储值不同的是,指针存储的是地址,而在这块地址上,储存着一个变量(或者是另外一个指针)。

  3. 若是咱们将符号 & 放在一个变量前面,那么能够获得这个变量的储存地址,例如 &age。

  4. 若是咱们将符号 * 放在一个指针变量名前,那么能够获得指针所储存的地址上存放的那个变量。

  5. 指针是 C语言的精华,也是强大所在,可是一开始会显得比较难。须要咱们好好花时间来理解指针的机制,由于不少其余知识点是建基于其上的。

固然,今天只是为指针这一个难题开了一个头,由于目前咱们还有不少概念没讲。

以后会慢慢深刻,指针的强(ke)大(pa)毫不仅于此。

并且,我也不能在这一课里丢给你们太多知识点,假如这里就讲

  • 指向指针的指针
  • 数组指针
  • 指针数组
  • 结构体指针
  • 函数指针

等等知识点,那还能不能愉快地玩耍了...

8. 第二部分第三课预告:


今天的课就到这里,一块儿加油吧!

下一课:C语言探索之旅 | 第二部分第三课:数组

数组是最经常使用的一种数据类型,也是 C语言的一个重点。


我是 谢恩铭,公众号「程序员联盟」(微信号:coderhub)运营者,慕课网精英讲师 Oscar 老师,终生学习者。 热爱生活,喜欢游泳,略懂烹饪。 人生格言:「向着标杆直跑」

相关文章
相关标签/搜索