zoukankan      html  css  js  c++  java
  • C语言探索之旅 | 第二部分第二课:进击的指针,C语言的王牌!

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

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

    内容简介


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

    1. 前言


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

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

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

    “不好意思,又跑题了~”

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

    在开始这一课前,请深吸一口气,因为这一课可能不会像之前那些课一般“悠哉”了。

    指针也许是 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 分钟
    ", 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
    ", 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 字节
    ", 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
    ", pointerOnAge);
    

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

    123456
    

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

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

    我们需要再一次用到 * 号,但这次它的作用和上一次声明指针变量时不一样,这一次它的作用是 取得指针所指向的地址上的变量值

    例如:

    int age = 10;
    
    int *pointerOnAge= &age;
    
    printf("%d
    ", *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
    ", *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
    ", 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
    ", *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 分钟
    ", 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 老师,终生学习者。
    热爱生活,喜欢游泳,略懂烹饪。
    人生格言:「向着标杆直跑」

  • 相关阅读:
    poj 2312 Battle City
    poj 2002 Squares
    poj 3641 Pseudoprime numbers
    poj 3580 SuperMemo
    poj 3281 Dining
    poj 3259 Wormholes
    poj 3080 Blue Jeans
    poj 3070 Fibonacci
    poj 2887 Big String
    poj 2631 Roads in the North
  • 原文地址:https://www.cnblogs.com/frogoscar/p/13020367.html
Copyright © 2011-2022 走看看