学习系列第五课 - pengphei/haiku-cn GitHub Wiki

第五课

计算机语言有很多种,其中一些和人类语言很接近,而其他的则接近于机器代码。汇编语言和机器码进一步之遥,而 COBOL 则如它所设计的那样接近于人类语言。C++ 则介于两者之间,在阅读完本节之后,您会深刻的认识到这一点。

数组(Array)

为了弥补单独变量的不足,C++ 提供了可供使用的变量的集合,称为数组(arrays)。数组的单个成员称为 元素(elements),所有的元素具有相同的类型,并且紧密地放置于计算机内存之中。您可以认为数组类似于鸡蛋托盘(包含了同类小物件的容器)。数组的声明和普通变量相类似,除了其后具有一对方括号,方括号中的数值表示了数组中包含元素的个数。

int thisIsAnArray[5];

上面的示例创建了一个包含了五个整型的数组,而且它们占据了一片连续的计算机内存。如果您能够看到内存的剖面,那么那部分包含了该数组的内存则类似于下面的示例:

[integer0] [integer1] [integer2] [integer3] [integer4]

可能,数组掌握需要花费一定的时间。数组中的每个元素都被赋予了数值。您可以使用方括号中的数值来获取数组中具体的元素。

int main(void) 
{ 
        // 声明一个包含五个元素的数组。
        int intArray[5]; 
 
        // 数组首个元素的索引值为0 ,
        // 关于这个问题以后再进行介绍。
        intArray[0] = 10; 
 
        // 数组中的第二个元素的赋值。
        intArray[1] = 11; 
 
        // 数组中其余元素的赋值。
        intArray[2] = 12; 
        intArray[3] = 13; 
        intArray[4] = 14; 
 
        return 0; 
}

在本例中,我们定义了具有五个元素的数组,然后分别为每个元素都赋予了不同的数值。需要注意的是,数组首个元素的索引值是默认关联的数字 0。通常我们从1开始计数,但是计算机则从0开始,因此虽然数组包含 5 个元素,它们分别表示为 0 至 4。由于程序员处理该问题时所透出的古怪,人们会认为程序员的脑袋似乎出了问题。但是别担心,它将会变得更糟。

数组的使用需要特别的注意和培养。用于存放数组的内存通常会以整块分配,而以此种方式声明的数组则具有固定的大小。如果我们尝试为 intArray[6]intArray[16] 赋值,数组的大小并不会因此而自动的增加。在此种情况下,出现的结果将是不可预料的,但是通常操作系统会终止该程序的运行。使用负值来进行索引也将会产生同样的结果。这种错误被称为段错误(segmentation fault),或者简称 segfault。有些用户可能还记得 Windows 95 的时代,我们通常可以看到意外的的错误“General Protection Fault”。GPF 也只是 segfault 的另一种叫法。

指针(pointer)

如果讨论数组时,我们没有涉及到指针,那么关于它的讨论将是不完整的。指针是 C 和 C++ 语言的基础内容,而且它看起来可能要比从0开始计数要疯狂的多。指针是包含(或者指向)一个内存地址的变量。由于指针名的前面有一个星号(*)标识,因此您可以很容易得识别出它的声明。

char *somePointer;

那么让我们来看一个展示了指针用法的代码示例:

    #include <stdio.h> 
 
    int main(void) 
    { 
        // 该变量是我们进行试验的“小白鼠”。 
        int myInt = 5; 
 
        // 声明一个指向无效内存地址的指针。
        int *uselessPointer = NULL; 
 
        // 将该指针赋值为存储 myInt 的值的内存地址。
        // 如果在它的名字之前没有 * ,那么它就是一个 
        // 普通变量,而且这样赋值也会产生编译错误。
        // 在 myInt 之前的取地址符号(&)将会获取 myInt 的地址。 
 
        // 注意,在星号(*)和指针名之间可以有空格,它不会产生任何影响。
        int *intPointer = &myInt; 
 
        // %p 用于打印指针的地址。它在不同的程序之间可能会有所不同。
 
        // 包含星号的指针将会返回它所存储的地址中实际存储的数值。
        // 所以在这里 *intPointer 等于 5。 
        printf("intPointer's address is %p and contains the value %d\n", 
                intPointer, *intPointer); 
    }

指针,类似于数组,但是在使用时需要特别的注意,因为它们非常容易的让程序员在程序中制造一个段错误。因此通常需要将指针初始化为 NULL 或者已知的内存地址。您可能会为 NULL 是什么?它只是 0 的另一种写法,但是它是关于指针的。虽然 NULL 指针也无法使用,类似于未初始化的指针-指向随机地址的指针-您非常确定它是不可用的。

字符串(string)

在第三课中,我们了解了可以存储在变量中的信息的不同种类 -- 类型,但是我们忽视了其中很重要的一个:字符串。我们曾经在 printf() 语句中使用过此种类型 -- 包好在一对双引号之间的内容统称为字符串。

C 和 C++ 中的字符串和其他的数据类型有很大的不同。字符串是以 0 字符结尾的字符类型数组。字符变量可以被初始化为 0 至 255 之间的任何一个整数,或者一个字符常量(包含在单引号之间的字符:'a' 或者 'b')。

除了我们常用的字符之外,还有一些特殊字符。这些特殊字符以反斜杠开始,并且虽然我们需要输入多个字符来表示它们,但是它们在内存中仅占一个字节。需要注意的是,它们只可以与反斜杠一起使用(向右倾斜的斜杠,而不是正斜杠)。

字符 字符码
退格 \b
回车 \r
换页 \f
NULL \0
换行 \n
缩进 \t
反斜杠 \\
单引号(') \'
双引号(") \"

前四个字符在 Haiku 或者 unix/linux 编程中不经常使用。而在 Macintosh 计算机中通常使用回车符 (\r) 而不是换行符 (\n) 来进行换行。Windows 系统同时使用两个组合符来进行相同的任务 “\r\n”。在不同的操作系统中处理文本文件时,知道这些将会非常的便捷。

让我们来看一个仅适用单个字符的示例:

    #include <stdio.h>
 
    int main(void) 
    { 
        // 该循环打印了字符表中的所有大写字符。
        for (char i = 65; i < 91; i++)
            printf("%c",i); 
     
        char endline = '\n '; 
        printf("%c",endline); 
    } 

有许多种的方式可以用来处理字符串,现在就让我们看一些例子。最快速的理解方式就是编写一些有关它的代码。您可以慢慢的阅读下面满是注释的代码来对其有一个更好的理解。

    #include <stdio.h>
 
    // 我们引入了一个新的 include!其中仅包含了许多用于处理字符串的函数。  
    #include <string.h>  
 
    int main(void)  
    {  
        // 声明了一个字符串,又称作 char 类型数组。 
        char string[30];  
 
        // 将字符串中的元素初始化为 NULL(0)。虽然在 string.h 中没有直接包含“内存”函数,  
        // 但是,通常它都如下所示用于此种目的。 
 
        // memset: 将内存块中所有字节的值设置为某个数值。
        // 用法:memset(anArray, valueToAssign, sizeOfTheArray);  
 
        // 该调用将我们的数组中的所有元素赋值为 0 。 
        memset(string,0,30);  
 
        // 另一种为字符串中的字符赋值的方法:以数组的形式。下面我们 
        // 对元素进行单个的赋值。字母表字符 A 的数值表示为 65。
        for (char i = 0; i < 26; i++)  
            string[i] = 65 + i;  
 
        printf("String contains: %s\n",string);  
 
        // 为字符串赋值的“另一种”方式。sprintf(),即 "string printf", 
        // 将数据输出到字符串而不是屏幕,否则它与 printf() 一样。  
        // 但是需要注意的是,打印的内容应该小于字符串的大小。
        // 否则,您的程序可能会很荣幸的发生了崩溃。
 
        // 用法:sprintf(aStringVariable,formatString, argumentList)  
 
        sprintf(string,"%f",3.1415927);  
 
        // %s 是 printf() 函数中用于打印字符串的格式说明。
        printf("String changed. Now it contains: %s\n",string);  
 
        return 0;  
    }

在本例中,使用 memset() 函数的原因可能需要一些解释。如上所述,在多数使用中,字符串是以0结尾的字符数组。当我们调用 memtest() 时,我们把所有的数组元素赋值为 0,当我们前 26 个元素分别被赋值为字母表中的大写字母时,第 27 个元素则是最后的 null 字符(0)。sprintf() 函数自动在末尾添加一个 null 终止符。如果没有该终止符,在我们的字符串结尾将会打印出一些无用的字符。

呵!关于数组,指针,和字符串的注意事项,我们再次作个总结。让我们快速的浏览一下:

  • 数组通常声明为特定的大小,并且该值放置在方括号中。
  • 数组元素的索引从 0 开始计数。
  • 访问数组所分配的内存边界以外的内存将会导致段错误(或者崩溃)。
  • 数组名可以像指针一样使用,仅适用数组名,而不包含其后的方括号和索引值。
  • 指针是用于存储内存地址的变量。
  • 声明指针时,在指针名之前有一个型号:int *myPointer
  • 指针应该被初始化为 NULL 或者已知的内存地址。
  • 变量的地址可以通过取地址符号进行获取:&myVariable
  • 字符常量应该放置在单引号中:'a' 或者 'X'
  • 字符变量应该被初始化为字符常量或者 0-255 之间的数值。
  • 特殊的字符常量可能需要输入多个字符,但是它们都被视为单个字符,例如 \n 用于换行。
  • 字符串应该放置在双引号之中:"This is a string"
  • 字符串是以 NULL(0) 最为终止符的字符数组。

项目

一个高效的程序员可以利用多种方式使工具可以更好的为他们的工作服务。那么我们就创建一个程序,要求用户输入一个单词,然后输出每个字符的整数值。

为了从用户获取信息,我们需要使用两个新的函数:gets()strlen()。它们都以一个字符指针作为唯一的参数。我们可以使用两个字符数组 -- 切记,数组可以像指针一样使用,如果您省去其后的方括号和索引值。下面将会给出这两个函数的声明,并且给出相关的描述:

char *gets(char *inString);

gets() 用于从用户获取字符串。用户可以输入尽可能多的字符,之后按下 Enter 键结束输入。用户最后输入的 \n 字符将会被 0 取代作为字符终止符。inString 是一个用于存储用户输入的字符数组。当用户完成输入之后,gets() 将用于输入的内容拷贝至 inString,然后再返回它。似乎看起来该函数没有任何作用,但是不要担心,接下来往后看。

int strlen(char *inString);

strlen() 用于计算给定的以 NULL 结尾的字符串长度。警告:如果仅传递一个 NULL 字符串给它,将会导致程序崩溃。

下面是我们编写程序的基本步骤:

  1. 创建一个 char 数组用于存储用户输入信息。
  2. 调用 gets() 函数从用户获取信息,然后将其保存到我们的数组。
  3. 创建一个 int 变量,然后将字符串的长度赋值给它。
  4. 使用 for 循环打印字符串中的每个字符,包括它的字符表示和数值表示。
        #include <stdio.h>
        #include <string.h>
    
        int main(void)  
        {  
            char inString[1024];  
    
            printf("Type the text to convert and press Enter: ");  
            gets(inString); 
    
            // 这里是您输入代码的地方。 
            // 根据上述步骤进行代码的编写。
            // 前两步已经完成。您只需要完成后续部分。 
    
            return 0;  
        } 

添加:使您的程序打印出字符除了常用的十进制值之外的的十六进制值或者八进制值。更多信息查阅第三课。 提示:关于第四步的提示,请仔细查看前面章节中有关字符串的代码示例。同时,复习第三课中的用于 printf() 函数的占位符列表。

深入阅读

无论何时编译器在编译该项目时,它会抱怨,gets() 非常危险,不应该使用。您是如何认为呢?

查找错误

找错 #1

  • 源码

        #include <stdio.h>  
    
        int main(void)  
        {  
            int number = 0;
            for (int i = 1; i < 10; i) 
            {  
                number += i;  
                printf("At step %d, the number is now %d\n",i,number);  
            }  
        }
  • 错误

    代码编译正确,但是它无法停止屏幕中的打印操作,唯一可以停止的操作就是在终端窗口中按下 Ctrl+C

找错 #2

  • 源码

        #include <stdio.h> 
    
        int main(void) 
        { 
            int a; 
            int b, c; 
    
            a = 1; 
            b = 2; 
            c = 3; 
    
            printf("a is %d, b is %d, and c is %d.\n",a,b); 
            return a + b + c; 
        } 
  • 错误

      foo.cpp: In function ‘int main()’:  
      foo.cpp:12: warning: too few arguments for format  
    

第四课找错答案

  1. for 循环中的 i++ 应该替换为 i+=2
  2. 警告源于 %d (主要用于整型输出)用于浮点型(float)变量。将 printf 语句中的 %d 占位符修改为 %f,该警告将不存在。
⚠️ **GitHub.com Fallback** ⚠️