Sunday 26 February 2017

C 语言学习点滴 2 —— 指针的理解

    前一阵子学习了指针(pointer)的概念,略微感觉有些复杂。我仔细思考了一下,感觉并不是逻辑复杂,而是概念复杂。
    指针在C语言中用于储存变量的地址。或者,从根本上看,指针(pointer)是一个值为内存地址的变量。我们可以类比来看,int类型变量的值是整数,char类型变量的值是字符,那么指针变量的值就是地址。
    接下来,我们要弄清楚几个概念。
  1. 假设一个指针变量名是ptr,我们可以编写以下语句: Source file
     1 ptr = &pooh;
    
    对于这条语句,我们可以说,ptr“指向”pooh,那么现在ptr的值就是pooh的地址。
  2. 紧接着,我们可以使用间接运算符 *(indirection operator)找出储存在pooh上的值(也可以说是找出ptr所指向的值)。 Source file
     1 val = *ptr;
    
    说了这么多,其实我们可以这样来理解指针的概念:
    将计算机的整个内存想象成你所在的宿舍楼,宿舍楼里都是挨个的小房间(相当于一个个宿舍)。

    计算机的内存就是由一组组的储存单元在一起构成的,为了定位每一个单元格,我们会使用十六进制给予每个储存单元一个唯一的身份识别码,称之为地址。比如第一个宿舍我们称之为0x00,第二个宿舍称之为0x01,以此类推。内存的寻址是线性的,对于32位寻址的内存,其最大地址刚好是0xFFFFFFFF,从0到0xFFFFFFFF恰好是4GBytes个单元。诸如此般,我们即可以通过地址来定位一个宿舍了啦!

    但是呢,通过数字来记忆宿舍的位置实在是太麻烦了,很容易就记混,我们可以选择给一个特定的宿舍起一个别名。
    比如,我们给内存位置是0x02的宿舍起名字叫做DOC。这样,DOC就代替了0x02这个地址,帮我们找到这个宿舍。
    这就是指针。我们可以这么说,DOC指向了0x02。
    宿舍里都会住人,在DOC中住了一个我,小汪。我刚刚去上大学,班主任还要很多同学都不认识我,但他们知道我住在DOC房间里,所以呢,都叫我“住在DOC里的那个人”。
    在C语言中的表示就是, *DOC。
    结果我因为敢于发言,当了班长,所以大家都叫我monitor了。
    这个在C语言中就是 monitor = *DOC
    不知道大家看懂了没有,monitor就是变量名,而我,小汪,就是变量啦。

C语言学习点滴 1—— 对函数的理解

    近期正在逐步地深入学习C语言,为了将自己学习的知识整理成体系,同时加深理解与记忆,我决定开始写写关于C语言的博文。就让我现在就开始吧!

    一,函数
    函数就像我们儿时搭积木的一个个造型各异的木块一样,我们只需要关心该如何使用这些小木块搭建成摩天大楼,而并不需要关心小木块内部究竟有什么结构。函数的设计思想即是将程序内部复杂的逻辑结构封装起来,使用者只需要弄清楚每一个函数的作用,而不需要知道这个函数究竟是如何实现的。我在未学习函数时,写过一个简陋的飞机大战游戏(无GUI界面,只打击移动靶):


Source file



#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

int main(void)
{
    int i, j;
    int x = 5;
    int y = 10;
    char input;
    int isFire = 0;

    int ny = 5; // 一个靶子,放在第一行,ny列上
    int isKilled = 0;

    while (1)
    {
        system("cls");   // 清屏函数

        //
        if (!isKilled)  // 输出靶子
        {
            for (j = 0; j<ny; j++)
                printf(" ");
            printf("+\n");
        }

        if (isFire == 0) // 输出飞机上面的空行
        {
            for (i = 0; i<x; i++)
                printf("\n");
        }
        else   // 输出飞机上面的激光竖线
        {
            for (i = 0; i<x; i++)
            {
                for (j = 0; j<y; j++)
                    printf(" ");
                printf("  |\n");
            }
            if (y + 2 == ny)
                isKilled = 1; // 击中靶子
            isFire = 0;
        }

        // 下面输出一个复杂的飞机图案
        for (j = 0; j<y; j++)
            printf(" ");
        printf("  *\n");
        for (j = 0; j<y; j++)
            printf(" ");
        printf("*****\n");
        for (j = 0; j<y; j++)
            printf(" ");
        printf(" * * \n");

        if (kbhit())  // 判断是否有输入
        {
            input = getch();  // 根据用户的不同输入来移动,不必输入回车
            if (input == 'a')
                y--;  // 位置左移
            if (input == 'd')
                y++;  // 位置右移
            if (input == 'w')
                x--;  // 位置上移
            if (input == 's')
                x++;  // 位置下移
            if (input == ' ')
                isFire = 1;
        }
    }
    return 0;
}
如果把全部的功能都放在一个main()函数之中实现,那是比较痛苦的事情,但有了函数,我们可以对每一个功能进行封装实现,这样后期代码的维护以及调试都会比较容易。
     在C中,使用函数要注意:
  1. 首先要使用函数原型(function prototype),告诉编译器函数的类型,包括是否接收变量以及是否返回值。
  2. 函数调用(function call),表示在某处使用该函数。
  3. 函数定义(function definition),明确指明函数要做什么。
    要注意几个概念:
  1. 全局变量与局部变量,局部变量(如变量count)只属于某一个函数私有,程序员可以在程序的其他地方,包括main()函数中使用count,这样并不会引起名称的冲突。
  2. 形式参数与实际参数:实际参数是出现在函数调用圆括号中的表达式,而形式参数则是函数定义的函数头中声明的变量。
    为什么我们在使用函数之前需要先声明函数原型呢?
        原因是:我们首先假设一个函数,需要读取两个int类型的数值,而函数调用只使用了imax(3),在C中,主调函数把它的参数储存在栈(stack)中,被调函数从栈中读取这些参数,被调函数根据函数原型中函数声明的形式参数来读取值,因此,函数调用imax(3),将一个整数放在栈中,而当imax()开始执行时,它要从栈中读取两个整数,但是栈中实际上只放了一个待读取的整数,这样就会引起错误,而如果不首先声明函数原型,并完整的声明函数参数的话,编译器是不会报错的。有了函数原型中的这些信息,编译器可以检查函数调用是否与函数原型相匹配。