讲座 4

欢迎!

  • 今天,我们将卸下许多你在本课程起步时使用的辅助工具。
  • 在前几周,我们讨论了图像是由称为像素的更小构建块组成的。
  • 本周,我们将进一步详细讨论构成这些图像的 0 和 1。具体来说,我们将更深入地探讨构成文件(包括图像)的基本构建块。
  • 此外,我们还将讨论如何访问存储在计算机内存中的底层数据。
  • As we begin today, know that the concepts covered in this 讲座 五月 take some 时间 to fully click.

像素艺术

  • 像素是排列在上下、左右网格中的彩色方块,即单个点。
  • 你可以将图像想象为一张位图,其中 0 代表黑色,1 代表白色。

    0 和 1 被转换为黑白笑脸

十六进制

  • RGB,即红、绿、蓝,是表示每种颜色数量的数值。在 Adobe Photoshop 中,你可以看到如下设置:

    A photoshop panel with RGB values and hexadecimal input

    注意红、蓝、绿的数量如何改变所选颜色。

  • 从上图可以看出,颜色不仅仅由三个数值表示。在窗口底部,有一个由数字和字符组成的特殊值。255 被表示为 FF。为什么会这样呢?
  • 十六进制是一种有 16 个计数值的计数系统,如下所示:

      0 1 2 3 4 5 6 7 8 9 A B C D E F
    

    注意 F 代表 15

  • 十六进制也被称为基数为 16 的计数系统。
  • 用十六进制计数时,每一列都是 16 的幂。
  • 数字 0 表示为 00
  • 数字 1 表示为 01
  • 数字 9 表示为 09
  • 数字 10 表示为 0A
  • 数字 15 表示为 0F
  • 数字 16 表示为 10
  • The number 255 is represented as FF, because 16 x 15 (or F) is 240. Add 15 更多 to make 255. This is the highest number you can count using a two-digit hexadecimal system.
  • 十六进制很有用,因为它可以用更少的位数表示。十六进制让我们能够更简洁地表示信息。

内存

  • 在过去几周,你可能还记得我们对并发内存块的艺术化描绘。将十六进制编号应用于这些内存块中的每一个,你可以想象如下:

    Blocks of 内存 numbered in hex

  • 你可以想象,关于上面的 10 块可能代表内存中的位置还是数值 10 可能会产生混淆。因此,按照惯例,所有十六进制数通常都带有 0x 前缀,如下所示:

    blocks of 内存 numbered in hex with 0x

  • 在你的终端窗口中,输入 code addresses.c 并编写如下代码:

    // Prints an integer
    
    #include <stdio.h>
    
    int main(void)
    {
        int n = 50;
        printf("%i\n", n);
    }
    

    Notice how n is stored in 内存 with the value 50.

  • 你可以这样可视化该程序如何存储这个值:

    the value 50 stored in a 内存 地点 with hex

指针

  • The C language has two powerful operators that relate to 内存:

      & 提供存储在内存中内容的地址。
      * 指示编译器访问内存中的某个位置。
    
  • 我们可以利用这个知识,按如下方式修改我们的代码:

    // Prints an integer's address
    
    #include <stdio.h>
    
    int main(void)
    {
        int n = 50;
        printf("%p\n", &n);
    }
    

    Notice the %p, which allows us to view the address of a 地点 in 内存. &n can be literally translated as “the address of n.” Executing this code will return an address of 内存 beginning with 0x. 你可以在此处下载此代码。

  • A pointer is a variable that stores the address of something. Most succinctly, a pointer is an address in your computer’s 内存.
  • 考虑以下代码:

    int n = 50;
    int *p = &n;
    

    注意 p 是一个指针,它包含了整数 n 的地址。

  • 按如下方式修改你的代码:

    // Stores and prints an integer's address
    
    #include <stdio.h>
    
    int main(void)
    {
        int n = 50;
        int *p = &n;
        printf("%p\n", p);
    }
    

    注意此代码与我们之前的代码效果相同。我们只是运用了对 &* 运算符的新知识。你可以在此处下载此代码。

  • 为了说明 * 运算符的用法,请看以下代码:

    // Stores and prints an integer's address
    
    #include <stdio.h>
    
    int main(void)
    {
        int n = 50;
        int *p = &n;
        printf("%p\n", p);
    }
    

    注意 printf line prints the integer’s address. int *p creates a pointer whose job is to store the 内存 address of an integer. 你可以在此处下载此代码。

  • 你可以这样可视化我们的代码:

    Same value of 50 in a 内存 地点 with a pointer value stored elsewhere

    注意指针看起来相当大。实际上,指针通常以 8 字节值存储。p 存储的是 50 的地址。

  • 你可以更准确地将指针视为一个指向另一个地址的地址:

    A pointer as an arrow, pointing from one 地点 of 内存 to another

字符串

  • Now that we have a mental model for 指针, we can peel 返回 a level of simplification that was offered earlier in this 课程.
  • Modify your code as follows:

    // Prints a string
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        string s = "HI!";
        printf("%s\n", s);
    }
    

    注意打印了一个字符串 s。 你可以在此处下载此代码。

  • Recall that a string is simply an array of characters. For 示例, string s = "HI!" can be represented as follows:

    The string HI with an exclamation point stored in 内存

  • However, what is s really? Where is the s stored in 内存? As you can imagine, s needs to be stored somewhere. 你可以可视化 s 与字符串的关系,如下所示:

    相同的字符串 HI 及指向它的指针

    Notice how a pointer called s tells the compiler where the first byte of the string exists in 内存.

  • Modify your code as follows:

    // Prints a string's address as well the addresses of its chars
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        string s = "HI!";
        printf("%p\n", s);
        printf("%p\n", &s[0]);
        printf("%p\n", &s[1]);
        printf("%p\n", &s[2]);
        printf("%p\n", &s[3]);
    }
    

    Notice the above prints the 内存 locations of each character in the string s. The & symbol is used to show the address of each element of the string. When running this code, notice that elements 0, 1, 2, and 3 are 下一页 to one another in 内存. 你可以在此处下载此代码。

  • 同样,你可以按如下方式修改你的代码:

    // Declares a string with CS50 Library
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        string s = "HI!";
        printf("%s\n", s);
    }
    

    注意此代码使用 cs50.h 库创建了一个字符串。 你可以在此处下载此代码。

  • 卸下辅助工具,你可以再次修改你的代码:

    // Declares a string without CS50 Library
    
    #include <stdio.h>
    
    int main(void)
    {
        char *s = "HI!";
        printf("%s\n", s);
    }
    

    注意 cs50.h 已被移除。 A string is implemented as a char *. This code will present the string that starts at the 地点 of s. This code effectively removes the training wheels of the string data type offered by cs50.h. This is raw C code, without the scaffolding of the cs50 library. 你可以在此处下载此代码。

  • 你可以想象字符串作为一种数据类型是如何创建的。
  • Last 周, we learned how to create your own data type as a struct.
  • cs50 库包含一个如下的类型定义:typedef char *string
  • 使用 cs50 库时,这个类型定义是一个简化的表示,允许人们使用名为 string 的自定义数据类型。

指针算术

  • 指针算术是在内存位置上进行数学运算的能力。
  • 你可以修改代码来打印出字符串中的每个内存位置,如下所示:

    // Prints a string's chars
    
    #include <stdio.h>
    
    int main(void)
    {
        char *s = "HI!";
        printf("%c\n", s[0]);
        printf("%c\n", s[1]);
        printf("%c\n", s[2]);
    }
    

    注意我们正在打印 s 位置处的每个字符。你可以在此处下载此代码。

  • 此外,你可以按如下方式修改你的代码:

    // Prints a string's chars via pointer arithmetic
    
    #include <stdio.h>
    
    int main(void)
    {
        char *s = "HI!";
        printf("%c\n", *s);
        printf("%c\n", *(s + 1));
        printf("%c\n", *(s + 2));
    }
    

    注意 first character at the 地点 of s is printed. Then, the character at the 地点 s + 1 is printed, and so on. 你可以在此处下载此代码。

  • 同样,考虑以下代码:

    // Prints substrings via pointer arithmetic
    
    #include <stdio.h>
    
    int main(void)
    {
        char *s = "HI!";
        printf("%s\n", s);
        printf("%s\n", s + 1);
        printf("%s\n", s + 2);
    }
    

    注意此代码打印了从 s 开始的各种内存位置中存储的值。你可以在此处下载此代码。

字符串比较

  • 一个字符串本质上是一个字符数组,由其第一个字节的位置标识。
  • Earlier in the 课程, we considered the comparison of integers. We could represent this in code by typing code compare.c into the terminal window as follows:

    // Compares two integers
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Get two integers
        int i = get_int("i: ");
        int j = get_int("j: ");
    
        // Compare integers
        if (i == j)
        {
            printf("Same\n");
        }
        else
        {
            printf("Different\n");
        }
    }
    

    注意此代码从用户处获取两个整数并进行比较。 你可以在此处下载此代码。

  • 然而,对于字符串,不能使用 == 运算符来比较两个字符串。
  • Utilizing the == operator in an attempt to compare strings will attempt to compare the 内存 locations of the strings instead of the characters therein. Accordingly, we recommended the use of strcmp.
  • 为了说明这一点,按如下方式修改你的代码:

    // Compares two strings' addresses
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Get two strings
        char *s = get_string("s: ");
        char *t = get_string("t: ");
    
        // Compare strings' addresses
        if (s == t)
        {
            printf("Same\n");
        }
        else
        {
            printf("Different\n");
        }
    }
    

    注意在两个字符串中都输入 HI! 仍然会输出 Different。 你可以在此处下载此代码。

  • 为什么这些字符串看起来不同?你可以用下图来理解原因:

    two strings stored separately in 内存

  • Therefore, the code for compare.c above is actually attempting to see if the 内存 addresses are different, not the strings themselves.
  • 使用 strcmp,我们可以修正代码:

    // Compares two strings using strcmp
    
    #include <cs50.h>
    #include <stdio.h>
    #include <string.h>
    
    int main(void)
    {
        // Get two strings
        char *s = get_string("s: ");
        char *t = get_string("t: ");
    
        // Compare strings
        if (strcmp(s, t) == 0)
        {
            printf("Same\n");
        }
        else
        {
            printf("Different\n");
        }
    }
    

    注意如果字符串相同,strcmp 会返回 0。 你可以在此处下载此代码。

  • 为了进一步说明这两个字符串如何存在于两个不同的位置,按如下方式修改你的代码:

    // Prints two strings
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Get two strings
        char *s = get_string("s: ");
        char *t = get_string("t: ");
    
        // Print strings
        printf("%s\n", s);
        printf("%s\n", t);
    }
    

    注意我们现在存储了两个独立的字符串,很可能在两个不同的位置。 你可以在此处下载此代码。

  • 稍作修改,你就可以看到这两个存储字符串的位置:

    // Prints two strings' addresses
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Get two strings
        char *s = get_string("s: ");
        char *t = get_string("t: ");
    
        // Print strings' addresses
        printf("%p\n", s);
        printf("%p\n", t);
    }
    

    注意在打印语句中 %s 已改为 %p。 你可以在此处下载此代码。

复制与 malloc

  • 编程中的一个常见需求是将一个字符串复制到另一个字符串。
  • 在你的终端窗口中,输入 code copy.c 并编写如下代码:

    // Capitalizes a string
    
    #include <cs50.h>
    #include <ctype.h>
    #include <stdio.h>
    #include <string.h>
    
    int main(void)
    {
        // Get a string
        string s = get_string("s: ");
    
        // Copy string's address
        string t = s;
    
        // Capitalize first letter in string
        t[0] = toupper(t[0]);
    
        // Print string twice
        printf("s: %s\n", s);
        printf("t: %s\n", t);
    }
    

    注意 string t = ss to t. This does not accomplish what we are desiring. The string is not copied – only the address is. Further, notice the inclusion of ctype.h. 你可以在此处下载此代码。

  • 你可以这样可视化上述代码:

    two 指针 pointing at the same 内存 地点 with a string

    注意 st 仍然指向相同的内存块。这不是真正的字符串复制。相反,这是两个指向同一字符串的指针。

  • 在我们解决这个挑战之前,确保我们的代码不会经历段错误非常重要,段错误是指我们试图将 string s 复制到 string t,但 string t 并不存在。我们可以使用 strlen 函数来帮助解决这个问题,如下所示:

    // Capitalizes a string, checking length first
    
    #include <cs50.h>
    #include <ctype.h>
    #include <stdio.h>
    #include <string.h>
    
    int main(void)
    {
        // Get a string
        string s = get_string("s: ");
    
        // Copy string's address
        string t = s;
    
        // Capitalize first letter in string
        if (strlen(t) > 0)
        {
            t[0] = toupper(t[0]);
        }
    
        // Print string twice
        printf("s: %s\n", s);
        printf("t: %s\n", t);
    }
    

    注意 strlen 用于确保 string t 有内容。如果没有,则不会复制任何内容。 你可以在此处下载此代码。

  • To be able to make an authentic copy of the string, we will need to introduce two new building blocks. First, malloc allows you, the programmer, to allocate a block of a specific size of 内存. Second, free allows you to tell the compiler to free up that block of 内存 you previously allocated.

  • 我们可以修改代码来创建字符串的真正副本,如下所示:

    // Capitalizes a copy of a string
    
    #include <cs50.h>
    #include <ctype.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(void)
    {
        // Get a string
        char *s = get_string("s: ");
    
        // Allocate memory for another string
        char *t = malloc(strlen(s) + 1);
    
        // Copy string into memory, including '\0'
        for (int i = 0; i <= strlen(s); i++)
        {
            t[i] = s[i];
        }
    
        // Capitalize copy
        t[0] = toupper(t[0]);
    
        // Print strings
        printf("s: %s\n", s);
        printf("t: %s\n", t);
    }
    

    注意 malloc(strlen(s) + 1) 创建了一个长度为字符串 s 长度加一的内存块。这允许我们在最终复制的字符串中包含字符 \0。然后,for 循环遍历字符串 s,并将每个值赋给字符串 t 上的相同位置。你可以在此处下载此代码。

  • 事实证明我们的代码效率低下。按如下方式修改你的代码:

    // Capitalizes a copy of a string, defining n in loop too
    
    #include <cs50.h>
    #include <ctype.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(void)
    {
        // Get a string
        char *s = get_string("s: ");
    
        // Allocate memory for another string
        char *t = malloc(strlen(s) + 1);
    
        // Copy string into memory, including '\0'
        for (int i = 0, n = strlen(s); i <= n; i++)
        {
            t[i] = s[i];
        }
    
        // Capitalize copy
        t[0] = toupper(t[0]);
    
        // Print strings
        printf("s: %s\n", s);
        printf("t: %s\n", t);
    }
    

    注意现在 n = strlen(s) 被定义在 for 循环的左侧。最好不要在 for 循环的中间条件中调用不必要的函数,因为它会一遍又一遍地运行。将 n = strlen(s) 移到左侧后,strlen 函数只运行一次。 你可以在此处下载此代码。

  • C 语言有一个内置的字符串复制函数叫做 strcpy。可以如下实现:

    // Capitalizes a copy of a string using strcpy
    
    #include <cs50.h>
    #include <ctype.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(void)
    {
        // Get a string
        char *s = get_string("s: ");
    
        // Allocate memory for another string
        char *t = malloc(strlen(s) + 1);
    
        // Copy string into memory
        strcpy(t, s);
    
        // Capitalize copy
        t[0] = toupper(t[0]);
    
        // Print strings
        printf("s: %s\n", s);
        printf("t: %s\n", t);
    }
    

    注意 strcpy 完成了我们之前 for 循环所做的相同工作。 你可以在此处下载此代码。

  • Both get_string and malloc return NULL, a special value in 内存, in the event that something goes wrong. 你可以编写代码来检查此 NULL 条件,如下所示:

    // Capitalizes a copy of a string without memory errors
    
    #include <cs50.h>
    #include <ctype.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(void)
    {
        // Get a string
        char *s = get_string("s: ");
        if (s == NULL)
        {
            return 1;
        }
    
        // Allocate memory for another string
        char *t = malloc(strlen(s) + 1);
        if (t == NULL)
        {
            return 1;
        }
    
        // Copy string into memory
        strcpy(t, s);
    
        // Capitalize copy
        if (strlen(t) > 0)
        {
            t[0] = toupper(t[0]);
        }
    
        // Print strings
        printf("s: %s\n", s);
        printf("t: %s\n", t);
    
        // Free memory
        free(t);
        return 0;
    }
    

    注意如果获取的字符串长度为 0 or malloc fails, NULL is returned. Further, notice that free lets the computer know you are done with this block of 内存 you created via malloc. 你可以在此处下载此代码。

Valgrind

  • Valgrind is a 工具 that can check to see if there are 内存-related issues with your programs wherein you utilized malloc. Specifically, it checks to see if you free all the 内存 you allocated.
  • 考虑以下 memory.c 的代码:

    // Demonstrates memory errors via valgrind
    
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
        int *x = malloc(3 * sizeof(int));
        x[1] = 72;
        x[2] = 73;
        x[3] = 33;
    }
    

    注意运行此程序不会引起任何错误。虽然 malloc 用于为数组分配足够的内存,但代码未能 free 已分配的内存。你可以在此处下载此代码。

  • If you type make memory followed by valgrind ./memory, you will get a report from valgrind that will report where 内存 has been lost as a result of your program. One error that valgrind reveals is that we attempted to assign the value of 33 at x of 3, where we only allocated an array of size 3 (x[0], x[1], and x[2]). Another error is that we never freed x.
  • 你可以修改代码以释放 x 的内存,如下所示:

    // Demonstrates memory errors via valgrind
    
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
        int *x = malloc(3 * sizeof(int));
        x[0] = 72;
        x[1] = 73;
        x[2] = 33;
        free(x);
    }
    

    注意 running valgrind again now results in no 内存 leaks or errors

垃圾值

  • When you ask the compiler for a block of 内存, there is no guarantee that this 内存 will be empty.
  • It’s very possible that the 内存 you allocated was previously utilized by the computer. Accordingly, you 五月 see junk or garbage values. This is a result of you getting a block of 内存 but not initializing it. For 示例, consider the following code for garbage.c:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
        int scores[1024];
        for (int i = 0; i < 1024; i++)
        {
            printf("%i\n", scores[i]);
        }
    }
    

    注意运行此代码会分配 1024 locations in 内存 for your array, but the for loop will likely show that not all values therein are 0. It’s always best practice to be aware of the potential for garbage values when you do not initialize blocks of 内存 to some other value like zero or otherwise. 你可以在此处下载此代码。

指针趣玩与 Binky

交换

  • In the real 世界, a common need in 编程 is to swap two values. Naturally, it’s hard to swap two variables without a temporary holding space. In practice, you can type code swap.c and write code as follows to see this in action:

    // Fails to swap two integers
    
    #include <stdio.h>
    
    void swap(int a, int b);
    
    int main(void)
    {
        int x = 1;
        int y = 2;
    
        printf("x is %i, y is %i\n", x, y);
        swap(x, y);
        printf("x is %i, y is %i\n", x, y);
    }
    
    void swap(int a, int b)
    {
        int tmp = a;
        a = b;
        b = tmp;
    }
    

    注意虽然这段代码运行了,但它并没有生效。即使在值被发送到 swap 函数后,它们也没有被交换。为什么? 你可以在此处下载此代码。

  • 当你将值传递给函数时,你提供的只是副本。按照当前的代码写法,xy作用域仅限于 main 函数。也就是说,在 main 函数的花括号 {} 中创建的 xy 的值只具有 main 函数的作用域。在我们上面的代码中,xy 是通过传值方式传递的。
  • 考虑下图:

    一个矩形,顶部是机器代码,下面是全局变量、堆和堆栈

    注意全局变量(我们在本课程中尚未使用过)存在于内存的一个位置中。各种函数存储在内存另一个区域的堆栈中。

  • 现在,考虑下图:

    一个矩形,底部是 main 函数,上方直接是 swap 函数

    注意 mainswap 有两个独立的或内存区域。因此,我们不能简单地将值从一个函数传递到另一个函数来改变它们。

  • Modify your code as follows:

    // Swaps two integers using pointers
    
    #include <stdio.h>
    
    void swap(int *a, int *b);
    
    int main(void)
    {
        int x = 1;
        int y = 2;
    
        printf("x is %i, y is %i\n", x, y);
        swap(&x, &y);
        printf("x is %i, y is %i\n", x, y);
    }
    
    void swap(int *a, int *b)
    {
        int tmp = *a;
        *a = *b;
        *b = tmp;
    }
    

    注意变量不是通过传值而是通过传引用传递的。也就是说,xy 的地址被提供给了函数。因此,swap 函数可以知道在哪里对 main 函数中的实际 xy 进行修改。 你可以在此处下载此代码。

  • 你可以这样可视化:

    x 和 y 存储在 main 函数中,通过传引用传递给 swap 函数

溢出

  • A heap overflow is when you overflow the heap, touching areas of 内存 you are not supposed to.
  • A stack overflow is when too many functions are called, overflowing the amount of 内存 available.
  • 这两种情况都被视为缓冲区溢出

scanf

  • 在 CS50 中,我们创建了像 get_int 这样的函数来简化从用户获取输入的操作。
  • scanf 是一个可以获取用户输入的内置函数。
  • 我们可以使用 scanf 相当容易地重新实现 get_int,如下所示:

    // Gets an int from user using scanf
    
    #include <stdio.h>
    
    int main(void)
    {
        int n;
        printf("n: ");
        scanf("%i", &n);
        printf("n: %i\n", n);
    }
    

    注意 value of n is stored at the 地点 of n in the line scanf("%i", &n). 你可以在此处下载此代码。

  • 然而,尝试重新实现 get_string 并不容易。考虑以下代码:

    // Dangerously gets a string from user using scanf with array
    
    #include <stdio.h>
    
    int main(void)
    {
        char s[4];
        printf("s: ");
        scanf("%s", s);
        printf("s: %s\n", s);
    }
    

    注意不需要 &,因为在 C 中数组名充当指针。尽管如此,这个程序不会每次运行都正确工作。在这个程序中,我们从未为目标字符串分配足够的内存。事实上,我们不知道用户可能输入多长的字符串!此外,我们不知道内存位置中可能存在什么垃圾值。你可以在此处下载此代码。

  • 此外,你的代码可以如下修改。但是,我们必须为字符串预先分配一定数量的内存:

    // Using malloc
    
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
        char *s = malloc(4);
        if (s == NULL)
        {
            return 1;
        }
        printf("s: ");
        scanf("%s", s);
        printf("s: %s\n", s);
        free(s);
        return 0;
    }
    

    注意如果提供了四个字节的字符串,你可能会遇到错误。

  • 如下简化我们的代码,我们可以进一步理解预分配这个本质问题:

    #include <stdio.h>
    
    int main(void)
    {
        char s[4];
        printf("s: ");
        scanf("%s", s);
        printf("s: %s\n", s);
    }
    

    注意如果我们预先分配一个大小为 4 的数组,我们可以输入 cat,程序可以运行。但是,比这更大的字符串可能会导致错误。

  • Sometimes, the compiler or the system running it 五月 allocate 更多 内存 than we indicate. Fundamentally, though, the above code is unsafe. We cannot trust that the user will input a string that fits into our pre-allocated 内存.

文件 I/O

  • 你可以读取和操作文件。虽然这个主题将在未来几周进一步讨论,但请考虑以下 phonebook.c 的代码:

    // Saves names and numbers to a CSV file
    
    #include <cs50.h>
    #include <stdio.h>
    #include <string.h>
    
    int main(void)
    {
        // Open CSV file
        FILE *file = fopen("phonebook.csv", "a");
    
        // Get name and number
        char *name = get_string("Name: ");
        char *number = get_string("Number: ");
    
        // Print to file
        fprintf(file, "%s,%s\n", name, number);
    
        // Close file
        fclose(file);
    }
    

    注意此代码使用指针来访问文件。你可以在此处下载此代码。

  • 你可以在运行上述代码之前创建一个名为 phonebook.csv 的文件 or 下载 phonebook.csv. After running the above program and inputting a 姓名 and phone number, you will notice that this data persists in your CSV file.
  • 如果我们想确保 phonebook.csv 在运行程序之前存在,我们可以如下修改代码:

    // Saves names and numbers to a CSV file, checking for NULL
    
    #include <cs50.h>
    #include <stdio.h>
    #include <string.h>
    
    int main(void)
    {
        // Open CSV file
        FILE *file = fopen("phonebook.csv", "a");
        if (file == NULL)
        {
            return 1;
        }
    
        // Get name and number
        char *name = get_string("Name: ");
        char *number = get_string("Number: ");
    
        // Print to file
        fprintf(file, "%s,%s\n", name, number);
    
        // Close file
        fclose(file);
    }
    

    注意此程序通过调用 return 1 来防止 NULL 指针。 你可以在此处下载此代码。

  • 我们可以通过输入 code cp.c 并编写如下代码来实现我们自己的复制程序:

    // Copies a file
    
    #include <stdio.h>
    
    typedef unsigned char BYTE;
    
    int main(int argc, char *argv[])
    {
        FILE *src = fopen(argv[1], "rb");
        FILE *dst = fopen(argv[2], "wb");
    
        BYTE b;
    
        while (fread(&b, sizeof(b), 1, src) != 0)
        {
            fwrite(&b, sizeof(b), 1, dst);
        }
    
        fclose(dst);
        fclose(src);
    }
    

    注意此文件创建了我们自己的数据类型 BYTE,它等同于 uint8_t 的大小。然后,该文件读取一个 BYTE 并将其写入文件。 你可以在此处下载此代码。

  • BMPs are also assortments of data that we can examine and manipulate. This 周, you will be doing just that in your 问题集!

总结

In this lesson, you learned 关于 指针 that provide you with the ability to access and manipulate data at specific 内存 locations. Specifically, we delved into…

  • 像素艺术
  • 十六进制
  • 内存
  • 指针
  • 字符串
  • 指针算术
  • 字符串比较
  • 复制
  • malloc 与 Valgrind
  • 垃圾值
  • 交换
  • 溢出
  • scanf
  • 文件 I/O

See you 下一页 时间!