跳转至

3.1 Data Structure

Mon & Wed Lecture

主要讲述了数据结构与 struct 数组定义

arrays

无论你用什么编程语言,双列表第一个做的事永远是把第一个维度视为行,第二个维度视为列

Initialization

我们一般倾向于在 C 文件开头使用 define 语句提前定义好列表的内存大小

#define  N   20

int   myarray[ N ];
int   evensum;

evensum = 0;
for(int i=0 ; i < N ; ++i) {
    myarray[ i ] = i * 2;                                  
    evensum      = evensum + myarray[ i ];
}

一维列表的初始化方法如下,和 Python 不同的是 C 的一切都需要提前在内存中初始化好后再接入其元素,有运行时初始化并分配元素与编译时初始化两种方案

initialize the elements at run-time, by executing statements to assign values to the elements

#define  N   5

int   myarray[ N ];

....

    for(int i=0 ; i < N ; ++i) {
        myarray[ i ] = i;
    }

initialize the values at compile-time, by telling the compiler what values to initially store in the memory

#define  N   5

int   myarray[ N ] = { 0, 1, 2, 3, 4 };

编译时让自动初始化分配内存大小

int   myarray[ ] = { 0, 1, 2, 3, 4 };

#define  N   (sizeof(myarray) / sizeof(myarray[0]))

在不指定内存大小的时候,编译时会根据元素数量自动推倒,define 这行列表中都是同一类型的数据,每个 int 元素占据 4 bytes, sizeof(array) 会返回列表大小也就是 4 * 5 = 20, 第一个元素的大小为 4 相除就成了列表长度。


提前预留内存与指定部分元素

#define  HUGE   10000

int   myarray[ HUGE ] = { 4, 5 };

在 run-time 后 define 才能确定大小的数组被称之为 variable-length arrays, or variable-sized arrays 也就是可变长度数组和可变大小数组;但由于其在嵌入式设备中实现效率低下,现代 Linux 内核移除了可变长度数组,C11 中成为了可选定义。并且可变长度数组都可以在一个函数中定义并传递给另一个函数。但是,由于大小在运行时之前是未知的,因此也必须传递数组的大小。无法根据数组的名称确定数组的大小。

void function2(int array_size, char vla[ ])
{
    for(int i=0 ; i < array_size ; ++i) {
        // access vla[i] ...
        ....
    }
}

void function1(void)
{
    int size = read an integer from keyboard or a file;

    char vla[ size ];

    function2(size, vla);
}

strings

由于 C 中不具备字符串这种基础的数据类型,使用的是 char array 数组字符去实现,无论是 #include <string.h> 下可被调用的功能计算字符长度,是否相等,拷贝等和 printf 一样都本质是管理字符数组。

char greeting[5] = { 'h', 'e', 'l', 'l', 'o' };

char today[6]    = "Monday";

char month[]     = "August";

和 int 一样同样支持自动计算分配所需大小

Strings are terminated by a special character

在数组中 \0 被称为 null byte, 一般用于 char arrary 中的末尾序列,标明 string 到这里就处理结束了:

h e l l o \0

这个字符需要六个字节的内存才能被正常储存,但是 strlen 这样的函数返回会显示 5

先前的案例 char month[] = "August"; 中,实际上系统自动给 August 末尾分配了一个 \0 符,使用了 7 个位置储存,,但在 Monday 中由于指定了内存大小因此不会被自动分配 \0

h e l l o wo r l d \0

如果执行 arrary[5] = '\0', 数组仍然会占据 12 个字节的储存空间,但是如果打印出来就只会得到 hello

copy one string into another

solution one: for loop

void my_strcpy(char destination[], char source[])
{
    int length = strlen(source);

    for(int i = 0 ; i < length ; ++i) {
        destination[i] = source[i];
    }
    destination[length] = '\0';
}

解决方案一是首先计算长度然后插进新数组内容,最后插入 \0 结束

solution two: while loop unbounded loop:

void my_strcpy(char destination[], char source[])
{
    int  i = 0;

    while(source[i] != '\0') {
        destination[i] = source[i];
        i = i+1;
    }
    destination[i] = '\0';
}

根据原 source 数组判断,只要不是 null byte 就继续复制,do while 同样可以实现

// USE AN UNBOUNDED LOOP, COPYING UNTIL THE NULL-BYTE 

void my_strcpy(char destination[], char source[])
{
    int  i = 0;

    do {
        destination[i] = source[i];
        i = i+1;
    } while(source[i-1] != '\0');
}

sprintf - 格式化写入变量

在很多情况下,我们希望我们的“输出”被写入字符数组,而不是到屏幕上。

#include <stdio.h>

char chess_outcome[64];

if(winner == WHITE) {
    sprintf(chess_outcome, "WHITE with %i", nwhite_pieces);
}
else {
    sprintf(chess_outcome, "BLACK with %i", nblack_pieces);
}
printf("The winner: %s\n", chess_outcome);

默认可以这样用,但由于不要超过接收格式化打印的数组的最大长度,因此可以定义

char chess_outcome[64];

//  FORMAT, AT MOST, A KNOWN NUMBER OF CHARACTERS
if(winner == WHITE) {
    snprintf(chess_outcome, 64, "WHITE with %i", nwhite_pieces);
}

//  OR, GREATLY PREFERRED:
if(winner == WHITE) {
    snprintf(chess_outcome, sizeof(chess_outcome), "WHITE with %i", nwhite_pieces);
}

struct

anonymous struct

教授用了 RGB 调色盘首先举例匿名 struct 其机制:

C provides a mechanism to bring related data together, structures, using the struct keyword.

//  DEFINE AND INITIALIZE ONE VARIABLE THAT IS A STRUCTURE
struct {
    char    *name;   // a pointer to a sequence of characters
    int     red;     // in the range 0..255
    int     green;
    int     blue;
} rgb_colour = {
    "DodgerBlue",
     30,
    144,
    255
};

printf("Color name: %s\n", rgb_colour.name);
printf("RGB: (%d, %d, %d)\n", rgb_colour.red, rgb_colour.green, rgb_colour.blue);

匿名结构体在 struct 后没有标明结构体类型 / 数据类型,{} 后定义了一个 variable, 因此在调用的时候不需要先对结构体进行数据类型声明直接用

variable 也可以是数组:

struct myStructure s1[3] = {
    {13, 'B', "Some text"},
    {25, 'C', "Another text"},
    {42, 'D', "More text"}
};

printf("s1[0] - number: %d, letter: %c, text: %s\n", s1[0].number, s1[0].letter, s1[0].text);
printf("s1[1] - number: %d, letter: %c, text: %s\n", s1[1].number, s1[1].letter, s1[1].text);

Multiple football teams

这个案例就是 C11 中 struct 匿名函数的数组 variable 用法

多个 TEAM 的内容都可被自定义,然后循环打印每个 TEAM 的元素

#include <stdio.h>

#define MAX_TEAMS               3           // 定义队伍数量
#define MAX_TEAMNAME_LEN        30          // 最大队伍名称长度

// 定义球队的结构体数组
struct {
    char    teamname[MAX_TEAMNAME_LEN+1];   // 队伍名称
    int     played;                         // 比赛场次
    int     won;                            // 胜场数
    int     lost;                           // 败场数
    int     drawn;                          // 平局场次
    int     bfor;                           // 进球数
    int     bagainst;                       // 失球数
    int     points;                         // 积分
} team[MAX_TEAMS] = {
    {"Team A", 10, 6, 2, 2, 20, 10, 20},    // 定义第一个队伍的数据
    {"Team B", 12, 8, 3, 1, 25, 15, 25},    // 定义第二个队伍的数据
    {"Team C", 11, 5, 5, 1, 18, 18, 16}     // 定义第三个队伍的数据
};

// 打印所有队伍的信息
void printTeams() {
    for (int i = 0; i < MAX_TEAMS; i++) {
        printf("Team Name: %s\n", team[i].teamname);
        printf("Played: %d, Won: %d, Lost: %d, Drawn: %d\n", team[i].played, team[i].won, team[i].lost, team[i].drawn);
        printf("Goals For: %d, Goals Against: %d, Points: %d\n", team[i].bfor, team[i].bagainst, team[i].points);
        printf("--------------------------\n");
    }
}

int main() {
    printTeams();   // 调用函数打印队伍信息
    return 0;
}

这样管理定义极其方便,如果是非匿名函数:

#include <stdio.h>

#define MAX_TEAMS               3           // 定义队伍数量
#define MAX_TEAMNAME_LEN        30          // 最大队伍名称长度

// 定义一个结构体类型 Team
struct Team {
    char    teamname[MAX_TEAMNAME_LEN+1];   // 队伍名称
    int     played;                         // 比赛场次
    int     won;                            // 胜场数
    int     lost;                           // 败场数
    int     drawn;                          // 平局场次
    int     bfor;                           // 进球数
    int     bagainst;                       // 失球数
    int     points;                         // 积分
};

// 定义 Team 类型的数组
struct Team team[MAX_TEAMS] = {
    {"Team A", 10, 6, 2, 2, 20, 10, 20},    // 第一个队伍
    {"Team B", 12, 8, 3, 1, 25, 15, 25},    // 第二个队伍
    {"Team C", 11, 5, 5, 1, 18, 18, 16}     // 第三个队伍
};

// 打印所有队伍的信息
void printTeams() {
    for (int i = 0; i < MAX_TEAMS; i++) {
        printf("Team Name: %s\n", team[i].teamname);
        printf("Played: %d, Won: %d, Lost: %d, Drawn: %d\n", team[i].played, team[i].won, team[i].lost, team[i].drawn);
        printf("Goals For: %d, Goals Against: %d, Points: %d\n", team[i].bfor, team[i].bagainst, team[i].points);
        printf("--------------------------\n");
    }
}

int main() {
    printTeams();   // 调用函数打印队伍信息
    return 0;
}

可以看见需要另外再声明 variable 后进行 assignment

如果不使用 struct 结构,那就要手动重复去定义管理所有队伍:

//  DEFINE THE LIMITS ON PROGRAM'S DATA-STRUCTURES
#define MAX_TEAMS               24
#define MAX_TEAMNAME_LEN        30
....

//  DEFINE A 2-DIMENSIONAL ARRAY HOLDING OUR UNIQUE TEAMNAMES
char    teamname[MAX_TEAMS][MAX_TEAMNAME_LEN+1];        // +1 for null-byte

//  STATISTICS FOR EACH TEAM, INDEXED BY EACH TEAM'S 'TEAM NUMBER'
int     played  [MAX_TEAMS];
int     won     [MAX_TEAMS];
int     lost    [MAX_TEAMS];
int     drawn   [MAX_TEAMS];
int     bfor    [MAX_TEAMS];
int     bagainst[MAX_TEAMS];
int     points  [MAX_TEAMS];
....

//  PRINT EACH TEAM'S RESULTS, ONE-PER-LINE, IN NO SPECIFIC ORDER
for(int t=0 ; t<nteams ; ++t) {
    printf("%s %i %i %i %i %i %i %.2f %i\n", // %age to 2 decimal-places
            teamname[t],
            played[t], won[t], lost[t], drawn[t],
            bfor[t], bagainst[t],
            (100.0 * bfor[t] / bagainst[t]),      // calculate percentage
            points[t]);
}

Accessing system information using structures

操作系统的信息被藏在 /usr/include 和其 /usr/include/sys

我们用非匿名函数作演示作最后收尾:

#include <stdio.h>
#include <sys/time.h>

struct timeval {
    int  tv_sec;       // Seconds
    int  tv_usec;      // Microseconds
};

struct timeval start_time;
struct timeval stop_time;

gettimeofday(&start_time, NULL);
printf("program started at %i.06%i\n",
       (int)start_time.tv_sec, (int)start_time.tv_usec);

按地址传递结构,使用 & 运算符, 使得 gettimeofday()函数,第二个时区信息由于不感兴趣所以设置为 NULL