前言

马上就期末考试了,老师上传了要考的内容,有一些在之前的笔记中没有涉及到的,就在这里补充一下笔记。其实没讲的部分就是那个预处理器和枚举以及内存管理了。

考试内容:https://files.hoyue.fun/myc/CS110-2109-final-preparation.txt


GCC编译命令

考试说明中关于直接使用gcc命令的一些要求:

How to compile a program using gcc or cl at command line; how to use the -o and -c option of gcc.

掌握使用gcc或cl在命令行编译程序;掌握使用gcc的-o和-c命令。

基本命令

回忆一下,gcc在命令指示符(CMD)中的命令,最简单的编译.c文件的命令:gcc filename.c

它会在filename.c这个文件的目录下生成a.exe的编译成功文件。所以我们在输入这个命令之前要加载目录先,即先让控制台知道你这个在什么地方。

你可以用cd foldername打开一个文件夹,你也可以输入完整地址加载目录。


-o

-o用于指定要生成的结果文件,后面跟的就是结果文件名字。o是output的意思,不是目标的意思。结果文件可能是预处理文件、汇编文件、目标文件或者最终可执行文件。

例如:gcc test.c -o test表示生成了一个test.exe。

当然你还可以指定其他的文件类型,例如:gcc -c test.c -o test.o.这个test.o和test一样,都是目标文件。


-c

刚才上面的例子有-c的,-c告诉gcc对源文件进行编译会汇编,但不进行链接。此时,将生成目标文件,如果没有指定输出文件,就生成同名的.o文件。

例如:gcc -c test.c 就会输出a.o这样的文件。


预处理

我们知道预处理语句有常见的#include 和 #define其实还有一种:#ifndef...#endif

#ifndef...#endif

#ifndef都是一种宏定义判断,作用是防止多重定义。#ifndef是if not define的简写。
使用场景为:

  1. 头文件中使用,防止头文件被多重调用
  2. 作为测试使用,省去注释代码的麻烦
  3. 作为不同角色或者场景的判断使用。
  4. 模块化操作,不要在主程序写大量代码,直接调用模块。
    定义

使用时:

#ifndef STDIO_H
#define STDIO_H
…
#endif

其实对于考试的话用得不多。


清空输入队列

这个虽然在之前也讲过,但是老师强调了几次,就再讲一次,考纲内容:

How to clear the input queue of stdin.

如何清空输入队列(标准输入流stdin)。

不清空队列的话,可能会导致接下来的输入错误,我们一般使用字符来读取。

有一个这样的清空函数,调用时就会清空队列。

void flush()
{
    char ch;
    while((ch=getchar())!='\n');//如果不是换行符就继续读取,即清空了读入。
}

当然,如果不止一行要清空的话,可能会有其他的结束表示,你可以替换\n那个地方。


枚举

枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读。可以用枚举类型(enumerated type) 声明符号名称来表示整型常量。使用enum 关键字, 可以创建一个新” 类型” 并指定它可具有的值。

注意,实际上, enum 常量是int 类型, 因此, 只要能使用int 类型的地方就可以使用枚举类型

它的语法与结构的语法相同,你可以这样声明。

enum 枚举名 {枚举元素1,枚举元素2,……};

接下来我们举个例子,比如:一星期有 7 天,如果不用枚举,我们需要使用 #define 来为每个整数定义一个别名:

#define MON  1
#define TUE  2
#define WED  3
#define THU  4
#define FRI  5
#define SAT  6
#define SUN  7

这个看起来代码量就比较多,接下来我们看看使用枚举的方式:

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};

这样看起来是不是更简洁了。

注意:第一个枚举成员的默认值为整型的 0后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。

可以在定义枚举类型时改变枚举元素的值:

enumseason {spring, summer=3, autumn, winter};

没有指定值的枚举元素,其值为前一元素加 1。也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5

枚举变量

前面我们只是声明了枚举类型,和结构一样,我们看看还需要定义枚举变量。

我们可以先定义枚举类型再定义枚举变量定义枚举类型的同时定义枚举变量还可以省略枚举名称,直接定义枚举变量

例如:

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;
enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
enum
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

使用枚举变量

我们不需要像结构一样使用.来表示枚举变量,直接定义枚举变量,令其等于枚举成员即可。

例如,输出日期:

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
int main()
{
    // 遍历枚举元素
    for (day = MON; day <= SUN; day++) {
        printf("枚举元素:%d \n", day);
    }
}

总结

总结一下枚举:

  • 第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1 。
  • 直接定义枚举变量,令其等于枚举成员即可使用。

内存管理

分配内存

C 语言为内存的分配和管理提供了几个函数。这些函数可以在 <stdlib.h> 头文件中找到。

编程时,如果预先知道数组的大小,那么定义数组时就比较容易。

但是,如果预先不知道需要存储的文本长度,就比较难办了。在这里,我们需要定义一个指针,该指针指向未定义所需内存大小的字符,后续再根据需求来分配内存,如下所示:

char *description;
/* 动态分配内存 */
description = (char *)malloc( 200 * sizeof(char) );//(char *)是强制转换类型为char *,因为maloc函数返回值是void *

其中,malloc()就是分配内存的函数。void *malloc(int num); 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。

当然我们还可以用calloc()函数动态分配。void *calloc(int num, int size); 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。

上面的语句可以替换为:calloc(200, sizeof(char));

当动态分配内存时,可以传递任何大小的值。而那些预先定义了大小的数组,一旦定义则无法改变大小。

调整内存

我们可以通过调用函数 realloc() 来增加或减少已分配的内存块的大小。void *realloc(void *address, int newsize); 该函数重新分配内存,把内存扩展到 newsize

例如调整之前例子的内存:description = (char *) realloc( description, 100 * sizeof(char) );

释放内存

当程序退出时,操作系统会自动释放所有分配给程序的内存,但是,建议在不需要内存时,都应该调用函数 free() 来释放内存。void free(void *address); 该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。

例如释放例子中的内存:free(description);


链表

结构体和链表部分请看:

【C专题】结构体和链表

考试内容:

Define functions on linked list and use linked list.

定义并使用链表。

最简单的单链表定义:

struct link
{
    int data;
    struct link *next;
};

双链表定义:

struct dNode{ // type of a node in a double linked list
    int data; 
    struct dNode * prev; // address of the previous node  
    struct dNode * next; // address of the next node 
};

这是在main函数上方的定义,我们使用链表存东西的时候还要在main函数里初始化。假设我们输入一串不知道多少个数字,用空格隔开,请看:

int main()
{
    int i = 0;
    int n;
    char ch;
    struct link *head = NULL;    //链表头指针
    while((ch=getchar())!='\n')
    {
       head = AppendNode(head,ch-'0'); //向head为头指针的链表末尾添加节点
       i++;//计数
       ch=getchar();//去除空格
    }
    DisplyNode(head); //打印各结点的数据    
    DeletMemory(head);    //释放所有动态分配的内存
}

这是一个main函数的内容,表示读入不知道多少个数,放进链表中。当然我们主要的读入函数还没写。
/* 函数功能:新建一个节点并添加到链表末尾,返回添加节点后的链表的头指针 */

struct link *AppendNode(struct link *head,int data)
{
    struct link *p = NULL, *pr = head;
    int data;
    p = (struct link *)malloc(sizeof(struct link));//让p指向新建的节点
    if (p == NULL)        //若新建节点申请内存失败,则退出程序
    {
        printf("错误!内存不足。\n");
        exit(0);//退出程序
    }
    if (head == NULL)    //若原链表为空表
    {
        head = p;        //将新建节点置为头节点
    }
    else                //若原链表为非空,则将新建节点添加到表尾
    {
        while (pr->next != NULL)//若未到表尾,则移动pr直到pr指向表尾
        {
            pr = pr->next;        //让pr指向下一个节点
        }
        pr->next = p;            //让末节点的指针指向新建的节点
    }
    p->data = data;        //将新建节点的数据域赋值为输入的节点数据值
    p->next = NULL;        //将新建的节点置为表尾
    return head;        //返回添加节点后的链表的头指针
}

上面是写进了链表,那我们怎么读取链表中的数据呢?我们可以用定义一个指向结构的指针p->data,得到数据。下面是用函数输出所有的数据:

/* 函数的功能:显示链表中所有节点的节点号和该节点中的数据项的内容*/
void DisplyNode (struct link *head)
{
    struct link *p = head;
    int j = 1;
    while (p != NULL)  //若不是表尾,则循环打印节点的数值
    {
        printf("第%d个数据:%d\n", j, p->data);//打印第j个节点数据
        p = p->next;  //让p指向下一个节点
        j++;
    }
}

接下来是释放内存,

//函数的功能:释放head所指向的链表中所有节点占用的内存
void DeletMemory(struct link *head)
{
    struct link *p = head, *pr = NULL;
    while (p != NULL)  //若不是表尾,则释放节点占用的内存
    {
        pr = p;        //在pr中保存当前节点的指针
        p = p->next;//让p指向下一个节点
        free(pr);    //释放pr指向的当前节点占用的内存
    }
}

 以上就是使用链表存储一串数字了,考试只考到存数字。


考试题型

Types of questions:

1) Fill some blanks (填空题)
- Given an expression, what is its value?
- What is the value of a variable x after running some code fragment
- Answer of a question
- Given some code, provide the missing code

2) Writing functions (写完整的函数)

因为是笔试,其实和竞赛的初赛后面题目差不多的,基本上可能不会很难。

这里的一切都有始有终,却能容纳所有的不期而遇和久别重逢。
最后更新于 2024-01-14