C語言之結構體最全面總結

2021-02-19 C語言與C++編程

來自公眾號:TechZone

作者:HarrisWilde

有的時候,我們所遇到的數據結構,不僅僅是一群數字或者是字符串那麼簡單。比如我們每一個人的學籍信息,學號是一個長整數,名字卻是字符;甚至有更複雜的情況,這種問題在現實生活中並不少見。我們之前學過一種叫數組的數據結構,它可以允許我們把很多同類型的數據集中在一起處理。相對於之前,這已經是一次極大的進步。但是,新的問題,往往又會出現,這個時候,我們就得上更高端的裝備——結構體。

相比於數組,結構體有以下的更強大的優勢:

結構體的聲明與定義聲明

結構體的聲明使用struct關鍵字,如果我們想要把我們的學籍信息組織一下的話,可以這樣表示:

struct Info
{
    unsigned long identifier;//學號,用無符號長整數表示
    char name[20];//名字,用字符數組表示
    unsigned int year;//入學年份,用無符號整數表示
    unsigned int years;//學制,用無符號整數表示
}

這樣,我們就相當於描繪好了一個框架,以後要用的話直接定義一個這種類型的變量就好了。

定義

我們剛剛申請了一個名叫Info的結構體類型,那麼理論上我們可以像聲明其他變量的操作一樣,去聲明我們的結構體操作,但是C語言中規定,聲明結構體變量的時候,struct關鍵字是不可少的。

struct 結構體類型名 結構體變量名

不過,你可以在某個函數裡面定義:

#include <stdio.h>

struct Info
{
    unsigned long identifier;//學號,用無符號長整數表示
    char name[20];//名字,用字符數組表示
    unsigned int year;//入學年份,用無符號整數表示
    unsigned int years;//學制,用無符號整數表示
};

int main(void)
{
    /**
     *在main函數中聲明結構體變量
     *結構體變量名叫info
     *struct關鍵字不能丟
     */
    struct Info info;
    ...
}

也可以在聲明的時候就把變量名定義下來(此時這個變量是全局變量):

#include <stdio.h>

struct Info
{
    unsigned long identifier;//學號,用無符號長整數表示
    char name[20];//名字,用字符數組表示
    unsigned int year;//入學年份,用無符號整數表示
    unsigned int years;//學制,用無符號整數表示
} info;
/**
 *此時直接定義了變量
 *該變量是全局變量
 *變量名叫info
 */

int main(void)
{
    ...
}

訪問結構體成員

結構體成員的訪問有點不同於以往的任何變量,它是採用點號運算符.來訪問成員的。比如,info.name就是引用info結構體的name成員,是一個字符數組,而info.year則可以查到入學年份,是個無符號整型。

比如,下面開始錄入學生的信息:

//Example 01
#include <stdio.h>

struct Info
{
    unsigned long identifier;//學號,用無符號長整數表示
    char name[20];//名字,用字符數組表示
    unsigned int year;//入學年份,用無符號整數表示
    unsigned int years;//學制,用無符號整數表示
};

int main(void)
{
    struct Info info;

    printf("請輸入學生的學號:");
    scanf("%d", &info.identifier);
    printf("請輸入學生的姓名:");
    scanf("%s", info.name);
    printf("請輸入學生的入學年份:");
    scanf("%d", &info.year);
    printf("請輸入學生的學制:");
    scanf("%d", &info.years);

    printf("\n數據錄入完畢\n\n");

    printf("學號:%d\n姓名:%s\n入學年份:%d\n學制:%d\n畢業時間:%d\n", \
        info.identifier, info.name, info.year, info.years, info.year + info.years);
    return 0;
}

運行結果如下:

//Consequence 01
請輸入學生的學號:20191101
請輸入學生的姓名:Harris
請輸入學生的入學年份:2019
請輸入學生的學制:4

數據錄入完畢

學號:20191101
姓名:Harris
入學年份:2019
學制:4
畢業時間:2023

初始化結構體

像數組一樣,結構體也可以在定義的時候初始化,方法也幾乎一樣:

struct Info info = {
    20191101,
    "Harris",
    2019,
    4
};

在C99標準中,還支持給指定元素賦值(就像數組一樣):

struct Info info = {
    .name = "Harris",
    .year = 2019
};

對於沒有被初始化的成員,則「數值型」成員初始化為0,「字符型」成員初始化為『\0』。

對齊

下面這個代碼,大家來看看會發生什麼:

//EXample 02 V1
#include <stdio.h>

int main(void)
{
    struct A
    {
        char a;
        int b;
        char c;
    } a = {'a', 10, 'o'};
    
    printf("size of a = %d\n", sizeof(a));
    
    return 0;
}

我們之前學過,char類型的變量佔1位元組,int類型的變量佔4位元組,那麼這麼一算,一個結構體A型的變量應該就是6位元組了。別急,我們看運行結果:

//COnsequence 02 V1
size of a = 12

怎麼變成12了呢?標準更新了?老師教錯了?都不是。我們把代碼改一下:

//EXample 02 V2
#include <stdio.h>

int main(void)
{
    struct A
    {
        char a;
        char c;
        int b;
    } a = {'a', 'o', 10};
    
    printf("size of a = %d\n", sizeof(a));
    
    return 0;
}

結果:

//Consequence 02 V2
size of a = 8

實際上,這是編譯器對我們程序的一種優化——內存對齊。在第一個例子中,第一個和第三個成員是char類型是1個字節,而中間的int卻有4個字節,為了對齊,兩個char也佔用了4個字節,於是就是12個字節。

而在第二個例子裡面,前兩個都是char,最後一個是int,那麼前兩個可以一起佔用4個字節(實際只用2個,第一個例子也同理,只是為了訪問速度更快,而不是為了擴展),最後的int佔用4位元組,合起來就是8個字節。

關於如何聲明結構體來節省內存容量,可以閱讀下面的這篇文章,作者是艾瑞克·雷蒙,時尚最具爭議性的黑客之一,被公認為開源運動的主要領導者之一:

英文原版,中文版

結構體嵌套

在學籍裡面,如果我們的日期想要更加詳細一些,精確到day,這時候就可以使用結構體嵌套來完成:

#include <stdio.h>

struct Date
{
    unsigned int year;
    unsigned int month;
    unsigned int day;
};

struct Info
{
    unsigned long identifier;//學號,用無符號長整數表示
    char name[20];//名字,用字符數組表示
    struct Date date;/*---入學日期,用結構體Date表示---*/
    unsigned int years;//學制,用無符號整數表示
};

int main(void)
{
    ...
}

如此一來,比我們單獨聲明普通變量快多了。

不過,這樣訪問變量,就必須用點號一層層往下訪問。比如要訪問day這個成員,那就只能info.date.day而不能直接info.date或者info,day。

//Example 03
#include <stdio.h>

struct Date
{
    unsigned int year;
    unsigned int month;
    unsigned int day;
};

struct Info
{
    unsigned long identifier;//學號,用無符號長整數表示
    char name[20];//名字,用字符數組表示
    struct Date date;/*---入學日期,用結構體Date表示---*/
    unsigned int years;//學制,用無符號整數表示
};

int main(void)
{
    struct Info info;
    printf("請輸入學生的學號:");
    scanf("%d", &info.identifier);
    printf("請輸入學生的姓名:");
    scanf("%s", info.name);
    printf("請輸入學生的入學年份:");
    scanf("%d", &info.date.year);
    printf("請輸入學生的入學月份:");
    scanf("%d", &info.date.month);
    printf("請輸入學生的入學日期:");
    scanf("%d", &info.date.day);
    printf("請輸入學生的學制:");
    scanf("%d", &info.years);

    printf("\n數據錄入完畢\n\n");

    printf("學號:%d\n姓名:%s\n入學時間:%d/%d/%d\n學制:%d\n畢業時間:%d\n",\
           info.identifier, info.name,\
           info.date.year, info.date.month, info.date.day,\
           info.years, info.date.year + info.years);
    return 0;
}

運行結果如下:

//Consequence 03
請輸入學生的學號:20191101
請輸入學生的姓名:Harris
請輸入學生的入學年份:2019
請輸入學生的入學月份:9
請輸入學生的入學日期:7
請輸入學生的學制:4

數據錄入完畢

學號:20191101
姓名:Harris
入學時間:2019/9/7
學制:4
畢業時間:2023

結構體數組

剛剛我們演示了存儲一個學生的學籍信息的時候,使用結構體的例子。那麼,如果要錄入一批學生,這時候我們就可以沿用之前的思路,使用結構體數組。

我們知道,數組的定義,就是存放一堆相同類型的數據的容器。而結構體一旦被我們聲明,那麼你就可以把它看作一個類型,只不過是你自己定義的罷了。

定義結構體數組也很簡單:

struct 結構體類型
{
    成員;
} 數組名[長度];

/****或者這樣****/

struct 結構體類型
{
    成員;
};
struct 結構體類型 數組名[長度];

結構體指針

既然我們可以把結構體看作一個類型,那麼也就必然有對應的指針變量。

struct Info* pinfo;

但是在指針這裡,結構體和數組就不一樣了。我們知道,數組名實際上就是指向這個數組第一個元素的地址,所以可以將數組名直接賦值給指針。而結構體的變量名並不是指向該結構體的地址,所以要使用取地址運算符&才能獲取地址:

pinfo = &info;

通過結構體指針來訪問結構體有以下兩種方法:

第一個方法由於點號運算符比指針的取值運算符優先級更高,因此需要加一個小括號來確定優先級,讓指針先解引用變成結構體變量,在使用點號的方法去訪問。

相比之下,第二種方法就直觀許多。

這兩種方法在實現上是完全等價的,但是點號只能用於結構體變量,而箭頭只能夠用於指針。

第一種方法:

#include <stdio.h>
...
int main(void)
{
    struct Info *p;
    p = &info;
    
    printf("學號:\n", (*p).identifier);
    printf("姓名:\n", (*p).name);
    printf("入學時間:%d/%d/%d\n", (*p).date.year, (*p).date.month, (*p).date.day);
    printf("學制:\n", (*p).years);
    return 0;
}

第二種方法:

#include <stdio.h>
...
int main(void)
{
    struct Info *p;
    p = &info;
    
    printf("學號:\n", p -> identifier);
    printf("姓名:\n", p -> name);
    printf("入學時間:%d/%d/%d\n", p -> date.year, p -> date.month, p -> date.day);
    printf("學制:\n", p -> years);
    return 0;
}

傳遞結構體信息傳遞結構體變量

我們先來看看下面的代碼:

//Example 04
#include <stdio.h>

int main(void)
{
    struct Test
    {
        int x;
        int y;
    }t1, t2;

    t1.x = 3;
    t1.y = 4;
    t2 = t1;

    printf("t2.x = %d, t2.y = %d\n", t2.x, t2.y);
    return 0;
}

運行結果如下:

//Consequence 04
t2.x = 3, t2.y = 4

這麼看來,結構體是可以直接賦值的。那麼既然這樣,作為函數的參數和返回值也自然是沒問題的了。

先來試試作為參數:

//Example 05
#include <stdio.h>
struct Date
{
    unsigned int year;
    unsigned int month;
    unsigned int day;
};

struct Info
{
    unsigned long identifier;
    char name[20];
    struct Date date;
    unsigned int years;
};

struct Info getInput(struct Info info);
void printInfo(struct Info info);

struct Info getInput(struct Info info)
{
    printf("請輸入學號:");
    scanf("%d", &info.identifier);
    printf("請輸入姓名:");
    scanf("%s", info.name);
    printf("請輸入入學年份:");
    scanf("%d", &info.date.year);
    printf("請輸入月份:");
    scanf("%d", &info.date.month);
    printf("請輸入日期:");
    scanf("%d", &info.date.day);
    printf("請輸入學制:");
    scanf("%d", &info.years);

    return info;
}

void printInfo(struct Info info)
{
    printf("學號:%d\n姓名:%s\n入學時間:%d/%d/%d\n學制:%d\n畢業時間:%d\n", \
        info.identifier, info.name, \
        info.date.year, info.date.month, info.date.day, \
        info.years, info.date.year + info.years);
}

int main(void)
{
    struct Info i1 = {};
    struct Info i2 = {};
    printf("請錄入第一個同學的信息...\n");
    i1 = getInput(i1);
    putchar('\n');
    printf("請錄入第二個學生的信息...\n");
    i2 = getInput(i2);

    printf("\n錄入完畢,現在開始列印...\n\n");
    printf("列印第一個學生的信息...\n");
    printInfo(i1);
    putchar('\n');
    printf("列印第二個學生的信息...\n");
    printInfo(i2);

    return 0;
}

運行結果如下:

//Consequence 05
請錄入第一個同學的信息...
請輸入學號:20191101
請輸入姓名:Harris
請輸入入學年份:2019
請輸入月份:9
請輸入日期:7
請輸入學制:4

請錄入第二個學生的信息...
請輸入學號:20191102
請輸入姓名:Joy
請輸入入學年份:2019
請輸入月份:9
請輸入日期:8
請輸入學制:5

錄入完畢,現在開始列印...

列印第一個學生的信息...
學號:20191101
姓名:Harris
入學時間:2019/9/7
學制:4
畢業時間:2023

列印第二個學生的信息...
學號:20191102
姓名:Joy
入學時間:2019/9/8
學制:5
畢業時間:2024

傳遞指向結構體變量的指針

早期的C語言是不允許直接將結構體作為參數直接傳遞進去的。主要是考慮到如果結構體的內存佔用太大,那麼整個程序的內存開銷就會爆炸。不過現在的C語言已經放開了這方面的限制。

不過,作為一名合格的開發者,我們應該要去珍惜硬體資源。那麼,傳遞指針就是一個很好的辦法。

將剛才的代碼修改一下:

//Example 06
#include <stdio.h>
struct Date
{
    unsigned int year;
    unsigned int month;
    unsigned int day;
};

struct Info
{
    unsigned long identifier;
    char name[20];
    struct Date date;
    unsigned int years;
};

void getInput(struct Info *info);
void printInfo(struct Info *info);

void getInput(struct Info *info)
{
    printf("請輸入學號:");
    scanf("%d", &info->identifier);
    printf("請輸入姓名:");
    scanf("%s", info->name);
    printf("請輸入入學年份:");
    scanf("%d", &info->date.year);
    printf("請輸入月份:");
    scanf("%d", &info->date.month);
    printf("請輸入日期:");
    scanf("%d", &info->date.day);
    printf("請輸入學制:");
    scanf("%d", &info->years);
}

void printInfo(struct Info *info)
{
    printf("學號:%d\n姓名:%s\n入學時間:%d/%d/%d\n學制:%d\n畢業時間:%d\n", \
        info->identifier, info->name, \
        info->date.year, info->date.month, info->date.day, \
        info->years, info->date.year + info->years);
}

int main(void)
{
    struct Info i1 = {};
    struct Info i2 = {};
    printf("請錄入第一個同學的信息...\n");
    getInput(&i1);
    putchar('\n');
    printf("請錄入第二個學生的信息...\n");
    getInput(&i2);

    printf("\n錄入完畢,現在開始列印...\n\n");
    printf("列印第一個學生的信息...\n");
    printInfo(&i1);
    putchar('\n');
    printf("列印第二個學生的信息...\n");
    printInfo(&i2);

    return 0;
}

此時傳遞的就是一個指針,而不是一個龐大的結構體。

動態申請結構體

結構體也可以在堆裡面動態申請:

//Example 01
#include <stdio.h>
...
int main(void)
{
    struct Info *i1;
    struct Info *i2;
    
    i1 = (struct Info *)malloc(sizeof(struct Info));
    i2 = (struct Info *)malloc(sizeof(struct Info));
    if (i1 == NULL || i2 == NULL)
    {
        printf("內存分配失敗!\n");
        exit(1);
    }
    
    printf("請錄入第一個同學的信息...\n");
    getInput(i1);
    putchar('\n');
    printf("請錄入第二個學生的信息...\n");
    getInput(i2);

    printf("\n錄入完畢,現在開始列印...\n\n");
    printf("列印第一個學生的信息...\n");
    printInfo(i1);
    putchar('\n');
    printf("列印第二個學生的信息...\n");
    printInfo(i2);
    
    free(i1);
    free(i2);
    
    return 0;
}

實戰:建立一個圖書館資料庫

實際上,我們建立的數組可以是指向結構體指針的數組。

代碼實現如下:

//Example 02
#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 100

struct Date
{
    int year;
    int month;
    int day;
};

struct Book
{
    char title[128];
    char author[48];
    float price;
    struct Date date;
    char publisher[48];
};

void getInput(struct Book* book);//錄入數據
void printBook(struct Book* book);//列印數據
void initLibrary(struct Book* lib[]);//初始化結構體
void printLibrary(struct Book* lib[]);//列印單本書數據
void releaseLibrary(struct Book* lib[]);//釋放內存

void getInput(struct Book* book)
{
    printf("請輸入書名:");
    scanf("%s", book->title);
    printf("請輸入作者:");
    scanf("%s", book->author);
    printf("請輸入售價:");
    scanf("%f", &book->price);
    printf("請輸入出版日期:");
    scanf("%d-%d-%d", &book->date.year, &book->date.month, &book->date.day);
    printf("請輸入出版社:");
    scanf("%s", book->publisher);
}

void printBook(struct Book* book)
{
    printf("書名:%s\n", book->title);
    printf("作者:%s\n", book->author);
    printf("售價:%.2f\n", book->price);
    printf("出版日期:%d-%d-%d\n", book->date.year, book->date.month, book->date.day);
    printf("出版社:%s\n", book->publisher);
}

void initLibrary(struct Book* lib[])
{
    for (int i = 0; i < MAX_SIZE; i++)
    {
        lib[i] = NULL;
    }
}

void printLibrary(struct Book* lib[])
{
    for (int i = 0; i < MAX_SIZE; i++)
    {
        if (lib[i] != NULL)
        {
            printBook(lib[i]);
            putchar('\n');
        }
    }
}

void releaseLibrary(struct Book* lib[])
{
    for (int i = 0; i < MAX_SIZE; i++)
    {
        if (lib[i] != NULL)
        {
            free(lib[i]);
        }
    }
}

int main(void)
{
    struct Book* lib[MAX_SIZE];
    struct Book* p = NULL;
    int ch, index = 0;

    initLibrary(lib);

    while (1)
    {
        printf("請問是否要錄入圖書信息(Y/N):");
        do
        {
            ch = getchar();
        } while (ch != 'Y' && ch != 'N');

        if (ch == 'Y')
        {
            if (index < MAX_SIZE)
            {
                p = (struct Book*)malloc(sizeof(struct Book));
                getInput(p);
                lib[index] = p;
                index++;
                putchar('\n');
            }
            else
            {
                printf("資料庫已滿!\n");
                break;
            }
        }
        else
        {
            break;
        }
    }

    printf("\n數據錄入完畢,開始列印驗證...\n\n");
    printLibrary(lib);
    releaseLibrary(lib);

    return 0;
}

運行結果如下:

//Consequence 02
請問是否要錄入圖書信息(Y/N):Y
請輸入書名:人類簡史
請輸入作者:尤瓦爾·赫拉利
請輸入售價:32.25
請輸入出版日期:2016-3-4
請輸入出版社:中信出版集團

請問是否要錄入圖書信息(Y/N):N

數據錄入完畢,開始列印驗證...

書名:人類簡史
作者:尤瓦爾·赫拉利
售價:32.25
出版日期:2016-3-4
出版社:中信出版集團

單鍊表

我們知道,數組變量在內存中,是連續的,而且不可拓展。顯然在一些情況下,這種數據結構擁有很大的局限性。比如移動數據的時候,會牽一髮而動全身,尤其是反轉這種操作更加令人窒息。那麼,需要需要一種數據結構來弄出一種更加靈活的「數組」,那麼這,就是「鍊表」

本節我們只講講單鍊表。

所謂鍊表,就是由一個個「結點」組成的一個數據結構。每個結點都有「數據域」「指針域」組成。其中數據域用來存儲你想要存儲的信息,而指針域用來存儲下一個結點的地址。如圖:

單鍊表

當然,鍊表最前面還有一個頭指針,用來存儲頭結點的地址。

這樣一來,鍊表中的每一個結點都可以不用挨個存放,因為有了指針把他們串起來。因此結點放在哪都無所謂,反正指針總是能夠指向下一個元素。我們只需要知道頭指針,就能夠順藤摸瓜地找到整個鍊表。

因此對於學籍資料庫來說,我們只需要在Info結構體中加上一個指向自身類型的成員即可:

struct Info
{
    unsigned long identifier;
    char name[20];
    struct Date date;
    unsigned int years;
    struct Info* next;
};

在單鍊表中插入元素頭插法

這種每次都將數據插入單鍊表的頭部(頭指針後面)的插入法就叫頭插法。

如果要把學生信息加入到單鍊表,可以這麼寫:

void addInfo(struct Info** students)//students是頭指針
{
    struct Info* info, *temp;
    info = (struct Info*)malloc(sizeof(struct Info));
    if (info == NULL)
    {
        printf("內存分配失敗!\n");
        exit(1);
    }
    
    getInput(info);
    
    if (*students != NULL)
    {
        temp = *students;
        *students = info;
        info->next = temp;
    }
    else
    {
        *students = info;
        info->next = NULL;
    }
}

由於students存放的是頭指針,因此我們需要傳入它的地址傳遞給函數,才能夠改變它本身的值。而students本身又是一個指向Info結構體的指針,所以參數的類型應該就是struct Info**。

往單鍊表裡面添加一個結點,也就是先申請一個結點,然後判斷鍊表是否為空。如果為空,那麼直接將頭指針指向它,然後next成員指向NULL。若不為空,那麼先將next指向頭指針原本指向的結點,然後將頭指針指向新結點即可。

那麼,列印鍊表也變得很簡單:

void printStu(struct Info* students)
{
    struct Info* info;
    int count = 1;
    
    info = students;
    while (book != NULL)
    {
        printf("Student%d:\n", count);
        printf("姓名:%s\n", info->name);
        printf("學號:%d\n", info->identifier);
        info = info->next;
        count++;
    }
}

想要讀取單鍊表裡面的數據,只需要迭代單鍊表中的每一個結點,直到next成員為NULL,即表示單鍊表的結束。

最後,當然還是別忘了釋放空間:

void releaseStu(struct Info** students)
{
    struct Info* temp;
    
    while (*students != NULL)
    {
        temp = *students;
        *students = (*students)->next;
        free(temp);
    }
}

尾插法

與頭插法類似,尾插法就是把每一個數據都插入到鍊表的末尾。

void addInfo(struct Info** students)
{
    struct Info* info, *temp;
    info = (struct Info*)malloc(sizeof(struct Info));
    if (info == NULL)
    {
        printf("內存分配失敗!\n");
        exit(1);
    }
    
    getInput(info);
    
    if (*students != NULL)
    {
        temp = *students;
        *students = info;
        //定位到鍊表的末尾的位置
        while (temp->next != NULL)
        {
            temp = temp->next;
        }
        //插入數據
        temp->next = info;
        info->next = temp;
    }
    else
    {
        *students = info;
        info->next = NULL;
    }
}

這麼一來,程序執行的效率難免要降低很多,因為每次插入數據,都要先遍歷一次鍊表。如果鍊表很長,那麼對於插入數據來說就是一次災難。不過,我們可以給程序添加一個指針,讓它永遠都指向鍊表的尾部,這樣一來,就可以用很少的空間換取很高的程序執行效率。

代碼更改如下:

void addInfo(struct Info** students)
{
    struct Info* info, *temp;
    static struct Info* tail;//設置靜態指針
    info = (struct Info*)malloc(sizeof(struct Info));
    if (info == NULL)
    {
        printf("內存分配失敗!\n");
        exit(1);
    }
    
    getInput(info);
    
    if (*students != NULL)
    {
        tail->next = info;
        info->next = NULL;
    }
    else
    {
        *students = info;
        info->next = NULL;
    }
}

搜索單鍊表

單鍊表是我們用來存儲數據的一個容器,那麼有時候需要快速查找信息就需要開發相關搜索的功能。比如說輸入學號,查找同學的所有信息。

struct Info *searchInfo(struct Info* students, long* target)
{
    struct Info* info;
    info = students;
    while (info != NULL)
    {
        if (info->identifier == target)
        {
            break;
        }
        info = info->next;
    }
    
    return book;
};

void printInfo(struct Info* info)
{
    ...
}
...

int main(void)
{
    ...
    printf("\n請輸入學生學號:");
    scanf("%d", input);
    info = searchInfo(students, input);
    if (info == NULL)
    {
        printf("抱歉,未找到相關結果!\n");
    }
    else
    {
        do
        {
            printf("相關結果如下:\n");
            printInfo(book);
        } while ((info = searchInfo(info->next, input)) != NULL);
    }
    
    releaseInfo(...);
    return 0;
}

插入結點到指定位置

到了這裡,才體現出鍊表真正的優勢。

設想一下,如果有一個有序數組,現在要求你去插入一個數字,插入完成之後,數組依然保持有序。你會怎麼做?

沒錯,你應該會挨個去比較,然後找到合適的位置(當然這裡也可以使用二分法,比較節省算力),把這個位置後面的所有數都往後移動一個位置,然後將我們要插入的數字放入剛剛我們騰出來的空間裡面。

你會發現,這樣的處理方法,經常需要移動大量的數據,對於程序的執行效率來說,是一個不利因素。那麼鍊表,就無所謂。反正在內存中,鍊表的存儲毫無邏輯,我們只需要改變指針的值就可以實現鍊表的中間插入。

//Example 03
#include <stdio.h>
#include <stdlib.h>

struct Node
{
    int value;
    struct Node* next;
};

void insNode(struct Node** head, int value)
{
    struct Node* pre;
    struct Node* cur;
    struct Node* New;

    cur = *head;
    pre = NULL;

    while (cur != NULL && cur->value < value)
    {
        pre = cur;
        cur = cur->next;
    }

    New = (struct Node*)malloc(sizeof(struct Node));
    if (New == NULL)
    {
        printf("內存分配失敗!\n");
        exit(1);
    }
    New->value = value;
    New->next = cur;

    if (pre == NULL)
    {
        *head = New;
    }
    else
    {
        pre->next = New;
    }
}

void printNode(struct Node* head)
{
    struct Node* cur;

    cur = head;
    while (cur != NULL)
    {
        printf("%d ", cur->value);
        cur = cur->next;
    }
    putchar('\n');
}

int main(void)
{
    struct Node* head = NULL;
    int input;

    printf("開始插入整數...\n");
    while (1)
    {
        printf("請輸入一個整數,輸入-1表示結束:");
        scanf("%d", &input);
        if (input == -1)
        {
            break;
        }
        insNode(&head, input);
        printNode(head);
    }

    return 0;
}

運行結果如下:

//Consequence 03
開始插入整數...
請輸入一個整數,輸入-1表示結束:4
4
請輸入一個整數,輸入-1表示結束:5
4 5
請輸入一個整數,輸入-1表示結束:3
3 4 5
請輸入一個整數,輸入-1表示結束:6
3 4 5 6
請輸入一個整數,輸入-1表示結束:2
2 3 4 5 6
請輸入一個整數,輸入-1表示結束:5
2 3 4 5 5 6
請輸入一個整數,輸入-1表示結束:1
1 2 3 4 5 5 6
請輸入一個整數,輸入-1表示結束:7
1 2 3 4 5 5 6 7
請輸入一個整數,輸入-1表示結束:-1

刪除結點

刪除結點的思路也差不多,首先修改待刪除的結點的上一個結點的指針,將其指向待刪除結點的下一個結點。然後釋放待刪除結點的空間。

...
void delNode(struct Node** head, int value)
{
    struct Node* pre;
    struct Node* cur;
    
    cur = *head;
    pre = NULL;
    while (cur != NULL && cur->value != value)
    {
        pre = cur;
        cur = cur->next;
    }
    if (cur == NULL)
    {
        printf("未找到匹配項!\n");
        return ;
    }
    else
    {
        if (pre == NULL)
        {
            *head = cur->next;
        }
        else
        {
            pre->next = cur->next;
        }
        free(cur);
    }
}

內存池

C語言的內存管理,從來都是一個讓人頭禿的問題。要想更自由地管理內存,就必須去堆中申請,然後還需要考慮何時釋放,萬一釋放不當,或者沒有及時釋放,造成的後果都是難以估量的。

當然如果就這些,那倒也還不算什麼。問題就在於,如果大量地使用malloc和free函數來申請內存,首先使要經歷一個從應用層切入系統內核層,調用完成之後,再返回應用層的一系列步驟,實際上使非常浪費時間的。更重要的是,還會產生大量的內存碎片。比如,先申請了一個1KB的空間,緊接著又申請了一個8KB的空間。而後,這個1KB使用完了,被釋放,但是這個空間卻只有等到下一次有剛好1KB的空間申請,才能夠被重新調用。這麼一來,極限情況下,整個堆有可能被弄得支離破碎,最終導致大量內存浪費。

那麼這種情況下,我們解決這類問題的思路,就是創建一個內存池。

內存池,實際上就是我們讓程序創建出來的一塊額外的緩存區域,如果有需要釋放內存,先不必使用free函數,如果內存池有空,那麼直接放入內存池。同樣的道理,下一次程序申請空間的時候,先檢查下內存池裡面有沒有合適的內存,如果有,則直接拿出來調用,如果沒有,那麼再使用malloc。

其實內存池我們就可以使用單鍊表來進行維護,下面通過一個通訊錄的程序來說明內存池的運用。

普通的版本:

//Example 04 V1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Person
{
 char name[40];
 char phone[20];
 struct Person* next;
};

void getInput(struct Person* person);
void printPerson(struct Person* person);
void addPerson(struct Person** contects);
void changePerson(struct Person* contacts);
void delPerson(struct Person** contacts);
struct Person* findPerson(struct Person* contacts);
void displayContacts(struct Person* contacts);
void releaseContacts(struct Person** contacts);

void getInput(struct Person* person)
{
 printf("請輸入姓名:");
 scanf("%s", person->name);
 printf("請輸入電話:");
 scanf("%s", person->phone);
}

void addPerson(struct Person** contacts)
{
 struct Person* person;
 struct Person* temp;

 person = (struct Person*)malloc(sizeof(struct Person));
 if (person == NULL)
 {
  printf("內存分配失敗!\n");
  exit(1);
 }

 getInput(person);

 //將person添加到通訊錄中
 if (*contacts != NULL)
 {
  temp = *contacts;
  *contacts = person;
  person->next = temp;
 }
 else
 {
  *contacts = person;
  person->next = NULL;
 }
}

void printPerson(struct Person* person)
{
 printf("聯繫人:%s\n", person->name);
 printf("電話:%s\n", person->phone);
}

struct Person* findPerson(struct Person* contacts)
{
 struct Person* current;
 char input[40];

 printf("請輸入聯繫人:");
 scanf("%s", input);

 current = contacts;
 while (current != NULL && strcmp(current->name, input))
 {
  current = current->next;
 }

 return current;
}

void changePerson(struct Person* contacts)
{
 struct Person* person;

 person = findPerson(contacts);
 if (person == NULL)
 {
  printf("找不到聯繫人!\n");
 }
 else
 {
  printf("請輸入聯繫電話:");
  scanf("%s", person->phone);
 }
}

void delPerson(struct Person** contacts)
{
 struct Person* person;
 struct Person* current;
 struct Person* previous;

 //先找到待刪除的節點的指針
 person = findPerson(*contacts);
 if (person == NULL)
 {
  printf("找不到該聯繫人!\n");
 }
 else
 {
  current = *contacts;
  previous = NULL;

  //將current定位到待刪除的節點
  while (current != NULL && current != person)
  {
   previous = current;
   current = current->next;
  }

  if (previous == NULL)
  {
   //若待刪除的是第一個節點
   *contacts = current->next;
  }
  else
  {
   //若待刪除的不是第一個節點
   previous->next = current->next;
  }

  free(person);//將內存空間釋放
 }
}

void displayContacts(struct Person* contacts)
{
 struct Person* current;

 current = contacts;
 while (current != NULL)
 {
  printPerson(current);
  current = current->next;
 }
}

void releaseContacts(struct Person** contacts)
{
 struct Person* temp;

 while (*contacts != NULL)
 {
  temp = *contacts;
  *contacts = (*contacts)->next;
  free(temp);
 }
}

int main(void)
{
 int code;
 struct Person* contacts = NULL;
 struct Person* person;

 printf("| 歡迎使用通訊錄管理程序 |\n");
 printf("|--- 1:插入新的聯繫人 ---|\n");
 printf("|--- 2:查找現有聯繫人 ---|\n");
 printf("|--- 3:更改現有聯繫人 ---|\n");
 printf("|--- 4:刪除現有聯繫人 ---|\n");
 printf("|--- 5:顯示當前通訊錄 ---|\n");
 printf("|--- 6:退出通訊錄程序 ---|\n");

 while (1)
 {
  printf("\n請輸入指令代碼:");
  scanf("%d", &code);
  switch (code)
  {
  case 1:addPerson(&contacts); break;
  case 2:person = findPerson(contacts);
   if (person == NULL)
   {
    printf("找不到該聯繫人!\n");
   }
   else
   {
    printPerson(person);
   }
   break;
  case 3:changePerson(contacts); break;
  case 4:delPerson(&contacts); break;
  case 5:displayContacts(contacts); break;
  case 6:goto END;
  }
 }

END://此處直接跳出恆循環
 releaseContacts(&contacts);

 return 0;

}

運行結果如下:

//Consequence 04 V1
| 歡迎使用通訊錄管理程序 |
|--- 1:插入新的聯繫人 ---|
|--- 2:查找現有聯繫人 ---|
|--- 3:更改現有聯繫人 ---|
|--- 4:刪除現有聯繫人 ---|
|--- 5:顯示當前通訊錄 ---|
|--- 6:退出通訊錄程序 ---|

請輸入指令代碼:1
請輸入姓名:HarrisWilde
請輸入電話:0101111

請輸入指令代碼:1
請輸入姓名:Jack
請輸入電話:0101112

請輸入指令代碼:1
請輸入姓名:Rose
請輸入電話:0101113

請輸入指令代碼:2
請輸入聯繫人:HarrisWilde
聯繫人:HarrisWilde
電話:0101111

請輸入指令代碼:2
請輸入聯繫人:Mike
找不到該聯繫人!

請輸入指令代碼:5
聯繫人:Rose
電話:0101113
聯繫人:Jack
電話:0101112
聯繫人:HarrisWilde
電話:0101111

請輸入指令代碼:3
請輸入聯繫人:HarrisWilde
請輸入聯繫電話:0101234

請輸入指令代碼:5
聯繫人:Rose
電話:0101113
聯繫人:Jack
電話:0101112
聯繫人:HarrisWilde
電話:0101234

請輸入指令代碼:6

下面加入內存池:

//Example 04 V2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX 1024

struct Person
{
 char name[40];
 char phone[20];
 struct Person* next;
};

struct Person* pool = NULL;
int count;

void getInput(struct Person* person);
void printPerson(struct Person* person);
void addPerson(struct Person** contects);
void changePerson(struct Person* contacts);
void delPerson(struct Person** contacts);
struct Person* findPerson(struct Person* contacts);
void displayContacts(struct Person* contacts);
void releaseContacts(struct Person** contacts);
void releasePool(void);

void getInput(struct Person* person)
{
 printf("請輸入姓名:");
 scanf("%s", person->name);
 printf("請輸入電話:");
 scanf("%s", person->phone);
}

void addPerson(struct Person** contacts)
{
 struct Person* person;
 struct Person* temp;

 //如果內存池不是空的,那麼首先從裡面獲取空間
 if (pool != NULL)
 {
  person = pool;
  pool = pool->next;
  count--;
 }
 //內存池為空,則直接申請
 else
 {
  person = (struct Person*)malloc(sizeof(struct Person));
  if (person == NULL)
  {
   printf("內存分配失敗!\n");
   exit(1);
  }
 }


 getInput(person);

 //將person添加到通訊錄中
 if (*contacts != NULL)
 {
  temp = *contacts;
  *contacts = person;
  person->next = temp;
 }
 else
 {
  *contacts = person;
  person->next = NULL;
 }
}

void printPerson(struct Person* person)
{
 printf("聯繫人:%s\n", person->name);
 printf("電話:%s\n", person->phone);
}

struct Person* findPerson(struct Person* contacts)
{
 struct Person* current;
 char input[40];

 printf("請輸入聯繫人:");
 scanf("%s", input);

 current = contacts;
 while (current != NULL && strcmp(current->name, input))
 {
  current = current->next;
 }

 return current;
}

void changePerson(struct Person* contacts)
{
 struct Person* person;

 person = findPerson(contacts);
 if (person == NULL)
 {
  printf("找不到聯繫人!\n");
 }
 else
 {
  printf("請輸入聯繫電話:");
  scanf("%s", person->phone);
 }
}

void delPerson(struct Person** contacts)
{
 struct Person* person;
 struct Person* current;
 struct Person* previous;
 struct Person* temp;
 {

 };

 //先找到待刪除的節點的指針
 person = findPerson(*contacts);
 if (person == NULL)
 {
  printf("找不到該聯繫人!\n");
 }
 else
 {
  current = *contacts;
  previous = NULL;

  //將current定位到待刪除的節點
  while (current != NULL && current != person)
  {
   previous = current;
   current = current->next;
  }

  if (previous == NULL)
  {
   //若待刪除的是第一個節點
   *contacts = current->next;
  }
  else
  {
   //若待刪除的不是第一個節點
   previous->next = current->next;
  }

  //判斷內存池中有沒有空位
  if (count < MAX)
  {
   //使用頭插法將person指向的空間插入內存池中
   if (pool != NULL)
   {
    temp = pool;
    pool = person;
    person->next = temp;
   }
   else
   {
    pool = person;
    person->next = NULL;
   }
   count++;
  }
  //沒有空位,直接釋放
  else
  {
   free(person);//將內存空間釋放
  }
 }
}

void displayContacts(struct Person* contacts)
{
 struct Person* current;

 current = contacts;
 while (current != NULL)
 {
  printPerson(current);
  current = current->next;
 }
}

void releaseContacts(struct Person** contacts)
{
 struct Person* temp;

 while (*contacts != NULL)
 {
  temp = *contacts;
  *contacts = (*contacts)->next;
  free(temp);
 }
}

void releasePool(void)
{
 struct Person* temp;
 while (pool != NULL)
 {
  temp = pool;
  pool = pool->next;
  free(temp);
 }
}

int main(void)
{
 int code;
 struct Person* contacts = NULL;
 struct Person* person;

 printf("| 歡迎使用通訊錄管理程序 |\n");
 printf("|--- 1:插入新的聯繫人 ---|\n");
 printf("|--- 2:查找現有聯繫人 ---|\n");
 printf("|--- 3:更改現有聯繫人 ---|\n");
 printf("|--- 4:刪除現有聯繫人 ---|\n");
 printf("|--- 5:顯示當前通訊錄 ---|\n");
 printf("|--- 6:退出通訊錄程序 ---|\n");

 while (1)
 {
  printf("\n請輸入指令代碼:");
  scanf("%d", &code);
  switch (code)
  {
  case 1:addPerson(&contacts); break;
  case 2:person = findPerson(contacts);
   if (person == NULL)
   {
    printf("找不到該聯繫人!\n");
   }
   else
   {
    printPerson(person);
   }
   break;
  case 3:changePerson(contacts); break;
  case 4:delPerson(&contacts); break;
  case 5:displayContacts(contacts); break;
  case 6:goto END;
  }
 }

END://此處直接跳出恆循環
 releaseContacts(&contacts);
 releasePool();

 return 0;

}

typedef給數據類型起別名

C語言是一門古老的語言,它是在1969至1973年間,由兩位天才丹尼斯·裡奇和肯·湯普遜在貝爾實驗室以B語言為基礎開發出來的,用於他們的重寫UNIX計劃(這也為後來UNIX系統的可移植性打下了基礎,之前的UNIX是使用彙編語言編寫的,當然也是這兩位為了玩一個自己設計的遊戲而編寫的)。天才就是和咱常人不一樣,不過他倆的故事,在這篇裡面不多囉嗦,我們回到話題。

雖然C語言誕生的很早,但是卻依舊不是最早的高級程式語言。目前公認的最早的高級程式語言,是IBM公司於1957年開發的FORTRAN語言。C語言誕生之時,FORTRAN已經統領行業數十年之久。因此,C語言要想快速吸納FORTRAN中的潛在用戶,就必須做出一些妥協。

我們知道,不同的語言的語法,一般來說是不同的,甚至還有較大的差距。比如:

C:

int a, b, c;
float i, j, k;

而FORTRAN語言是這樣的:

integer :: a, b, c;
real :: i, j, k;

如果讓FORTRAN用戶使用原來的變量名稱進行使用,那麼就能夠快速遷移到C語言上面來,這就是typedef的用處之一。

我們使用FORTRAN語言的類型名,那就這麼辦:

typedef int integer;
typedef float real;

integer a, b, c;
real i, j, k;

結構體的搭檔

雖然結構體的出現能夠讓我們有一個更科學的數據結構來管理數據,但是每次使用結構體都需要struct...,未免顯得有些冗長和麻煩。有了typedef的助攻,我們就可以很輕鬆地給結構體類型起一個容易理解的名字:

typedef struct date
{
    int year;
    int month;
    int day;
} DATE;//為了區分,一般用全大寫

int main(void)
{
    DATE* date;
    ...
}

甚至還可以順便給它的指針也定義一個別名:

typedef struct date
{
    int year;
    int month;
    int day;
} DATE, *PDATE;

進階

我們還可以利用typedef來簡化一些比較複雜的命令。

比如:

int (*ptr) [5];

我們知道這是一個數組指針,指向一個5元素的數組。那麼我們可以改寫成這樣:

typedef int(*PTR_TO_ARRAY)[3];

這樣就可以把很複雜的聲明變得很簡單:

PTR_TO_ARRAY a = &array;

取名的時候要儘量使用容易理解的名字,這樣才能達到使用typedef的最終目的。

共用體

共用體也稱聯合體。

聲明

和結構體還是有點像:

union 共用體名稱
{
    成員1;
    成員2;
    成員3;
};

但是兩者有本質的不同。共用體的每一個成員共用一段內存,那麼這也就意味著它們不可能同時被正確地訪問。如:

//Example 05
#include <stdio.h>
#include <string.h>

union Test
{
 int i;
 double pi;
 char str[9];
};

int main(void)
{
 union Test test;

 test.i = 10;
 test.pi = 3.14;
 strcpy(test.str, "TechZone");

 printf("test.i: %d\n", test.i);
 printf("test.pi: %.2f\n", test.pi);
 printf("test.str: %s\n", test.str);

 return 0;
}

執行結果如下:

//Consequence 05
test.i: 1751344468
test.pi: 3946574856045802736197446431383475413237648487838717723111623714247921409395495328582015991082102150186282825269379326297769425957893182570875995348588904500564659454087397032067072.00
test.str: TechZone

可以看到,共用體只能正確地展示出最後一次被賦值的成員。共用體的內存應該要能夠滿足最大的成員能夠正常存儲。但是並不一定等於最大的成員的尺寸,因為還要考慮內存對齊的問題。

共用體可以類似結構體一樣來定義和聲明,但是共用體還可以允許不帶名字:

union
{
 int i;
 char ch;
 float f;
} a, b;

初始化

共用體不能在同一時間存放多個成員,所以不能批量初始化

union data
{
    int i;
    char ch;
    float f;
};

union data a = {520};        //初始化第一個成員
union data b = a;            //直接使用一個共用體初始化另一個共用體
union data c = {.ch = 'C'};  //C99的特性,指定初始化成員

枚舉

枚舉是一個基本的數據類型,它可以讓數據更簡潔。

如果寫一個判斷星期的文章,我們當然可以使用宏定義來使代碼更加易懂,不過:

#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,以此類推。

枚舉變量的定義和聲明方法和共用體一樣,也可以省略枚舉名,直接聲明變量名。

//Example 06
#include <stdio.h>
#include <stdlib.h>

int main()
{

    enum color { red = 1, green, blue };

    enum  color favorite_color;

    printf("請輸入你喜歡的顏色: (1. red, 2. green, 3. blue): ");
    scanf("%d", &favorite_color);

    //輸出結果
    switch (favorite_color)
    {
    case red:
        printf("你喜歡的顏色是紅色");
        break;
    case green:
        printf("你喜歡的顏色是綠色");
        break;
    case blue:
        printf("你喜歡的顏色是藍色");
        break;
    default:
        printf("你沒有選擇你喜歡的顏色");
    }

    return 0;
}

執行結果如下:

//Consequence 06
請輸入你喜歡的顏色: (1. red, 2. green, 3. blue): 3
你喜歡的顏色是藍色

也可以把整數轉換為枚舉類型:

//Example 07

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

int main()
{
    enum day
    {
        saturday,
        sunday,
        monday,
        tuesday,
        wednesday,
        thursday,
        friday
    } workday;

    int a = 1;
    enum day weekend;
    weekend = (enum day) a;  //使用強制類型轉換
    //weekend = a; //錯誤
    printf("weekend:%d", weekend);
    return 0;
}

運行結果如下:

//Consequence 07
weekend:1

位域

C語言除了開發桌面應用等,還有一個很重要的領域,那就是「單片機」開發。單片機上的硬體資源十分有限,容不得我們去肆意揮灑。單片機使一種集成電路晶片,使採用超大規模集成電路技術把具有數據處理能力的CPU、RAM、ROM、I/O、中斷系統、定時器/計數器等功能(有的還包括顯示驅動電路、脈寬調製電路、模擬多路轉換器、A/D轉換器等電路)集成到一塊矽片上構成的一個小而完善的微型計算機系統,在工控領域使用廣泛。

對於這樣的設備,通常內存只有256B,那麼能夠給我們利用的資源就十分珍貴了。在這種情況下,如果我們只需要定義一個變量來存放布爾值,一般就申請一個整型變量,通過1和0來間接存儲。但是,顯然1和0隻用1個bit就能夠放完,而一個整型卻是4個字節,也就是32bit。這就造成了內存的浪費。

好在,C語言為我們提供了一種數據結構,稱為「位域」(也叫位端、位欄位)。也就是把一個字節中的二進位位劃分,並且你能夠指定每個區域的位數。每個域有一個域名,並允許程序中按域名進行單獨操作。

使用位域的做法是在結構體定義的時候,在結構體成員後面使用冒號(:)和數字來表示該成員所佔的位數。

//Example 08
#include <stdio.h>

int main(void)
{
 struct Test
 {
  unsigned int a : 1;
  unsigned int b : 1;
  unsigned int c : 2;
 } test;
 
 test.a = 0;
 test.b = 1;
 test.c = 2;

 printf("a = %d, b = %d, c = %d\n", test.a, test.b, test.c);
 printf("size of test = %d\n", sizeof(test));

 return 0;
}

運行結果如下:

//Consequence 08
a = 0, b = 1, c = 2
size of test = 4

如此一來,結構體test只用了4bit,卻存放下了0、1、2三個整數。但是由於2在二進位中是10,因此佔了2個bit。如果把test.b賦值為2,那麼:

//Consequence 08 V2
a = 0, b = 0, c = 2
size of test = 4

可以看到,b中的10溢出了,只剩下0。

當然,位域的寬度不能夠超過本身類型的長度,比如:

unsigned int a : 100;

那麼就會報錯:

錯誤	C2034	「main::test::a」: 位域類型對位數太小

位域成員也可以沒有名稱,只要給出類型和寬度即可:

struct Test
{
    unsigned int x : 1;
    unsigned int y : 2;
    unsigned int z : 3;
    unsigned int : 26;
};

無名位域一般用來作為填充或者調整成員的位置,因為沒有名稱,所以無名位域並不能夠拿來使用。

C語言的標準只說明unsigned int和signed int支持位域,然後C99增加了_Bool類型也支持位域,其他數據類型理論上是不支持的。不過大多數編譯器在具體實現時都進行了擴展,額外支持了signed char、unsigned char以及枚舉類型,所以如果對char類型的結構體成員使用位域,基本上也沒什麼問題。但如果考慮到程序的可移植性,就需要謹慎對待了。另外,由於內存的基本單位是字節,而位域只是字節的一部分,所以並不能對位域進行取地址運算。

雖然科技發展日新月異,但是秉承著節約成本這個放之四海而皆準的原則,還是要注意使用!畢竟5毛錢可能是小錢,但是乘以5000萬呢?

●輸入m獲取文章目錄

分享C/C++技術文章

相關焦點

  • C語言之結構體(struct)
    long、unsigned int 、short、char (相當於各種文件類型,比如 .txt、.c、.h)這些關鍵字是否很熟悉?這都是 C 語言定義好的數據類型,直接拿來用就行了。但是我想自定義一個別的類型的數據怎麼辦?就靠 struct 了。
  • C語言結構體、枚舉以及位域的講解
    ,信息量很大,建議收藏【置頂】十大排序算法,來這看看-基本思想+動畫演示+C語言實現【置頂】C語言數據結構課程設計-停車場管理【附源碼】【置頂】程式設計師喜歡的 5 款最佳最牛代碼比較神器,必須安利!b.它由若干不同的基本數據類型的數據構成; c.它屬於C語言的一種數據類型,與整型、浮點型相當。
  • C語言編程 — 結構體與位域
    數組類型顯然無法滿足這一需求,此時可以使用結構體(Struct)來存放一組不同類型的數據。C 語言中,像 int、float、char 等是由 C 語言本身提供的數據類型,不能再進行分拆,我們稱之為基本數據類型。
  • C語言結構體(struct)最全的講解(萬字乾貨)
    ——結構體,它就將不同類型的數據存放在一起,作為一個整體進行處理。結構體在函數中的作用不是簡便,其最主要的作用就是封裝。封裝的好處就是可以再次利用。讓使用者不必關心這個是什麼,只要根據定義使用就可以了。結構體的大小不是結構體元素單純相加就行的,因為我們現在主流的計算機使用的都是32Bit字長的CPU,對這類型的CPU取4個字節的數要比取一個字節要高效,也更方便。
  • C語言結構體(Struct)
    在C語言中,可以使用結構體(Struct)來存放一組不同類型的數據。結構體的定義形式為:struct 結構體名{    結構體所包含的變量或數組};結構體是一種集合,它裡面包含了多個變量或數組,它們的類型可以相同,也可以不同,每個這樣的變量或數組都稱為結構體的成員(Member)。
  • C語言中的結構體和聯合體
    C語言實現面向對象的原理在 C 語言中,結構體(struct)是一個或多個變量的集合,這些變量可能為不同的類型,為了處理的方便而將這些變量組織在一個名字之下。由於結構體將一組相關變量看作一個單元而不是各自獨立的實體,因此結構體有助於組織複雜的數據,特別是在大型的程序中。共用體(union),也稱為聯合體,是用於(在不同時刻)保存不同類型和長度的變量,它提供了一種方式,以在單塊存儲區中管理不同類型的數據。今天,我們來介紹一下 C 語言中結構體和共用體的相關概念和使用。
  • C 語言中的結構體和共用體(聯合體)
    今天,我們來介紹一下 C 語言中結構體和共用體的相關概念和使用。結構體數組與之前介紹的數值型數組的不同之處在於每個數組元素都是一個結構體類型的數據,它們都分別包括各個成員項。C 語言為了使用方便和直觀,定義可以把 (*p).num 改用 p->num 來代替,它表示 p 所指向的結構體變量中的 num 成員。
  • C語言中的結構體和共用體(聯合體)
    今天,我們來介紹一下 C 語言中結構體和共用體的相關概念和使用。簡單地說,我們可以把「結構體類型」和「結構體變量」理解為是面向對象語言中「類」和「對象」的概念。此外,結構體裡的成員也可以是一個結構體變量。
  • 剖析c語言結構體的高級用法(二)
    (還有vs,這兩個編譯裡不能申明一個空的結構體,必須要有一個結構體成員來才行)寫成c語言程序空結構體的話,它會報錯,在新一點的編譯器裡面就不會報錯(比如dev,gcc)。說明(這裡是c++裡才這樣,在c語言裡輸出的結果不一樣的):
  • C語言結構體(struct)詳解
    最直觀的,指針可以改指向,數組不可以,因為數組佔用的每一個內存地址都用來保存變量或者對象,而指針佔用的內存地址保存的是一個地址,數組沒有單獨的保存指向地址的這樣一個結構。數組的位置是固定的,正如指針變量自身的位置也是固定的,改的是指針的值,是指向的目標地址,而因為數組不存儲目標地址,所以改不了指向。企圖把地址強制賦值給數組的話,也只是說把指針賦值給數組,類型不兼容。
  • C語言結構體常見方法
    把結構體名稱去掉,這樣更簡潔,不過也不能定義其他同結構體變量了——至少我現在沒掌握這種方法。struct B{               int c;          }          b;  }  a;  //使用如下方式訪問:  a.b.c = 10;   特別的,可以一邊定義結構體B,一邊就使用上:[cpp] view plain copy 在CODE
  • 為什麼C語言中的結構體的size,並不等於它所有成員size之和?
    結構體在C語言程序開發中,是不可或缺的語法。不過,相信不少C語言初學者遇到過這樣的問題:為什麼結構體的 size 有時不等於它的所有成員的 size 之和呢?舉例來說,假設有結構體,它的C語言代碼如下,請看:struct x{short s; int i; char c;};我們繼續編寫C語言代碼
  • C語言結構體(struct)常見使用方法
    ; 把結構體名稱去掉,這樣更簡潔,不過也不能定義其他同結構體變量了——至少我現在沒掌握這種方法。B{ int c; }b; struct B sb; }a; 使用方法與測試:[cpp] a.b.c = 11;
  • C語言結構體常見寫法及用法
    關注+星標公眾號,不錯過精彩內容作者
  • C語言系列(九):結構體
    這就需要一種新的數據類型,能夠將具有內在聯繫的不同類型的數據組成一個整體,在C語言中,這種數據類型就是結構體。在C語言中,結構體屬於構造數據類型,它由若干成員組成,成員的類型既可以是基本數據類型,也可以是構造數據類型,而且可以互不相同。struct 結構體類型名{ 類型1 成員名1; 類型2 成員名2; ...
  • C語言結構體變量
    結構體是一個或多個變量的集合,這些變量可以是不同的類型,為了處理的方便而將這些變量組織在一個名字之下。比如我要統某個班的學生信息(包括姓名,學號,成績),每個學生都需要至少3個變量,如若學生人數較多,那需要的變量就非常之多了。
  • 第六篇:C語言中結構體與文件操作相關知識點梳理
    前面總結過,C語言中的基本數據類型有四種,分別是整型、浮點型和字符型;後面又講到可以保存字符串的字符數組。但這遠遠不夠實際應用的需要。這就是本文要總結的第一個核心知識點:結構體。在C語言中結構體是對數據類型的無限擴展。程式設計師可以根據需要定義各種各樣的數據類型,即:結構體。
  • 51單片機之C語言-4.3結構體
    當然可以,這個就叫做結構體。現在按照數組的學習方法來研究結構體,兩個問題,如何定義;如何訪問結構體成員。同樣對於結構體變量,我們先要一個存儲空間,然後給這個空間取個名字。這個存儲空間可以任意由你定義。比如struct stu.然後給這個空間取個名字,比如boy1.因此我們就知道了如何定義一個結構體變量。與其他變量並無區別。struct stu boy1,boy2;等等。
  • 快速上手系列-C語言之結構體(二)結構體數組與結構體指針
    結構體數組對於結構體數組,我們先回想一下整型數組,然後舉例我們要統計咱們班30個人的姓名,學號 ,成績,如果我們用結構體變量來實現是不現實的。那麼我們就準備用結構體數組來完成這事。結構體數組就是同一類型的結構體變量的集合,內存分布上是連續的。
  • ...也可以面向對象面層,使用「函數指針結構體」為C語言找個「對象」
    函數指針結構體稍微思考一下,應該能夠想到C語言中的普通數據類型不僅可以用於定義數組,還可以用來定義結構體,例如:struct cn{char c;int i;double d;};那麼可以看作「普通數據類型」的函數指針也可以定義結構體嗎?