作者:
2013 年 4 月 2 日(最後更新:2013 年 4 月 3 日)

C99 特性

評分:4.4/5(71 票)
*****

簡介

C99 是 C 程式語言的 1999 年標準。C 是一種簡單、低階的語言,最適合系統程式設計。

本文將介紹 C99 的一些特性。其中一些特性尚未出現在 C++ 中,因此可能 C++ 程式設計師不太熟悉。

我們將從簡單的開始,介紹一些從 C++ 向後移植的次要特性,然後轉向 C99 特有的功能,最後透過根據一個小型的真實專案改編的“嚴肅”程式碼來結束。

本文中的原始碼已在 Pelles C IDE 7 中進行過編譯測試,但由於 C99 的普及度和歷史,該程式碼應該也能與許多其他 C 編譯器良好地構建。只需確保在需要時啟用 C99 支援。

main() 無需強制返回

與 C++ 一樣,如果在 main() 函式中省略了 return 語句,則會預設假定為 return 0;

布林值

引入了 _Bool 資料型別,其行為類似於只能儲存 1 或 0 的無符號整數。

支援的標頭檔案 stdbool.h 包含宏 booltruefalse,它們分別展開為 _Bool、1 和 0。

示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdbool.h>
#include <stdio.h>

int main(void)
{
    bool b = false;

    printf("%u\n", b);

    b = 5 > 3;
    printf("%u\n", b);

    b = 0;
    printf("%u\n", b);

    b = -987;
    printf("%u\n", b);
}


輸出
0
1
0
1

%zu 用於 size_t

引入 %zu 格式說明符是為了專門用於 size_t,以消除在無符號整數說明符 %u%lu 以及最近的 %llu 之間選擇的混淆。

示例
1
2
3
4
5
6
7
8
9
10
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

int main(void)
{
    size_t sz = SIZE_MAX;

    printf("%zu\n", sz);
}


可能的輸出
4294967295

函式知道自己的名稱

__func__ 識別符號的行為類似於一個常量 char 陣列,其中包含它被隱式宣告的函式的名稱。

示例
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

void i_know_my_name(void)
{
    printf("%s\n", __func__);
}

int main(void)
{
    i_know_my_name();
    printf("%s\n", __func__);
}


輸出
i_know_my_name
main

變長陣列

變長陣列(或 VLA)是可以使用變數而非編譯時常量來宣告其大小的陣列。它們不是指大小可變,而是指宣告時大小不固定。

VLA 的名聲不好,因為它們是在棧上分配而不是在堆上分配。棧區域用於區域性變數,其大小比堆更有限。如果 VLA 的大小過大,會發生棧溢位,導致程式崩潰。

儘管如此,當程式設計師希望使用小型陣列,同時又想避免繁瑣的 malloc() + free() 操作時,VLA 是一個非常有用的工具。

示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// This program will construct and display an n*n identity matrix.

#include <stddef.h>
#include <stdio.h>

int main(void)
{
    size_t n=0;

    printf("Please input `n': ");
    scanf("%zu", &n);

    int matrix[n][n];

    for (size_t i=0; i < n; ++i)
        for (size_t j=0; j < n; ++j)
            if (i == j)
                matrix[i][j] = 1;
            else
                matrix[i][j] = 0;

    for (size_t i=0; i < n; ++i)
    {
        for (size_t j=0; j < n; ++j)
            printf("%d ", matrix[i][j]);

        printf("\n");
    }
}


示例輸出
Please input `n': 10
1 0 0 0 0 0 0 0 0 0 
0 1 0 0 0 0 0 0 0 0 
0 0 1 0 0 0 0 0 0 0 
0 0 0 1 0 0 0 0 0 0 
0 0 0 0 1 0 0 0 0 0 
0 0 0 0 0 1 0 0 0 0 
0 0 0 0 0 0 1 0 0 0 
0 0 0 0 0 0 0 1 0 0 
0 0 0 0 0 0 0 0 1 0 
0 0 0 0 0 0 0 0 0 1 

可變引數宏

函式可以透過使用省略號 (...) 來接受可變數量的引數。從 C99 開始,宏也可以這樣做。在宏定義中,將使用 __VA_ARGS__ 來展開引數。

示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define TIME_PRINTF(format, ...)    do {                        \
    time_t t = time(NULL);                                      \
    const char *prefix = "%s -> ";                              \
    char time_format_vla[strlen(prefix) + strlen(format) + 1];  \
    strcpy(time_format_vla, prefix);                            \
    strcat(time_format_vla, format);                            \
    printf(time_format_vla, ctime(&t), __VA_ARGS__);            \
} while (false)

int main(void)
{
    srand(time(NULL));
    TIME_PRINTF("Hello %s, your number is %d! Please wait...\n\n", "User", rand() % 100);

    // waste some time
    for (size_t n=0; n < SIZE_MAX; ++n);

    // unfortunately, we need to pass at least two parameters    
    TIME_PRINTF("%s", "So how's it going?");
}


示例輸出
Wed Apr  3 12:33:23 2013
 -> Hello User, your number is 75! Please wait...

Wed Apr  3 12:33:33 2013
 -> So how's it going?

指定初始化器

C99 提供了一種方式來控制結構體中的哪個成員或陣列中的哪個元素被初始化以及初始化為哪個值。

最好直接看示例。

示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <ctype.h>
#include <stddef.h>
#include <stdio.h>

int main(void)
{
    char ca[10] = {[4] = 'e', [0] = 'a', [2] = 'c', [1] = 'b', [3] = 'd', [9] = 'z'};

    //         0    1    2    3    4   . . . . . .  9
    // ca == {'a', 'b', 'c', 'd', 'e', 0, 0, 0, 0, 'z'}

    printf("Contents of ca:\n  ");

    // the zeros are not printable, because they aren't the '0' character,
    // so we need to cast them to int so as to print their numeric value
    for (size_t i=0; i < sizeof ca; ++i)
        if (isprint(ca[i]))
            printf("%c ", ca[i]);
        else
            printf("%d ", (int)ca[i]);

    printf("\n\n");

    struct Test
    {
        char    c;
        int     i;
        float   f;
    };

    struct Test t = {.f = 3.14f, .c = 'Z', .i = 10};

    printf("Contents of t:\n  c == %c\n  i == %d\n  f == %f\n", t.c, t.i, t.f);
}


輸出
Contents of ca:
  a b c d e 0 0 0 0 z 

Contents of t:
  c == Z
  i == 10
  f == 3.140000

複合字面量

複合字面量基本上是一個無名的變數,其外觀與型別轉換非常相似。它與可變引數宏和指定初始化器結合使用,可以產生簡潔、高階的程式碼。

在最簡單的使用場景中,複合字面量取代了我們不希望保留的臨時變數。

示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <ctype.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

// this function will change the case of all letters in the message array,
// lowercase letters will become uppercase, and vice versa
void flip_case(char *message)
{
    printf("flip_case()\n");
    printf("Before:   %s\n", message);

    for (size_t i=0, ml = strlen(message); i < ml; ++i)
    {
        const char temp = message[i];

        if (isupper(temp))
            message[i] = tolower(temp);
        else
        if (islower(temp))
            message[i] = toupper(temp);
    }

    printf("After:    %s\n\n", message);
}

// this function will add 10 to an integer i
void add_ten(int *i)
{
    printf("add_ten()\n");
    printf("Before:   %d\n", *i);
    *i += 10;
    printf("After:    %d\n\n", *i);
}

// this function will add 1 to even numbers in the numbers array,
// only the first n numbers are operated on
void kill_evens(int *numbers, size_t n)
{
    printf("kill_evens()\n");
    printf("Before:   ");

    for (size_t i=0; i < n; ++i)
        printf("%d ", numbers[i]);

    printf("\n");

    for (size_t i=0; i < n; ++i)
        if (numbers[i] % 2 == 0)
            numbers[i] += 1;

    printf("After:    ");

    for (size_t i=0; i < n; ++i)
        printf("%d ", numbers[i]);

    printf("\n\n");
}

int main(void)
{
    flip_case((char[]){"Hello C99 World!"});

    add_ten(&(int){5});

    kill_evens((int[]){2, 3, 29, 90, 5, 6, 8, 0}, 8);

    printf("Current time: %s\n", ctime(&(time_t){time(NULL)}));
}


輸出
flip_case()
Before:   Hello C99 World!
After:    hELLO c99 wORLD!

add_ten()
Before:   5
After:    15

kill_evens()
Before:   2 3 29 90 5 6 8 0 
After:    3 3 29 91 5 7 9 1 

Current time: Wed Apr  3 12:44:55 2013

為了更高階地演示覆合字面量的價值,請考慮以下場景:我們編寫了自己的 strscat() 函式,它基本上是一個帶有額外最大長度引數的 strcat() 函式,並且我們想測試它是否正常工作。

現在,讓程式碼來說話。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <stddef.h>
#include <stdio.h>

///
/// @brief Appends contents of array `from` to array `to`.
/// @pre `limit` != `0`
/// @note No operation is performed for a `limit` of `0`.
/// @remarks Resulting array is NUL-terminated.
/// @param [out] to      String to be written to.
/// @param limit         Maximum number of bytes that string `to` can store, including NUL.
/// @param [in] from     String to be copied from.
/// @returns Size of resulting string (NUL not counted).
///
size_t strscat(char *to, size_t limit, const char *from)
{
    size_t s=0;

    if (limit != 0)
    {
        while (to[s] != '\0')
            ++s;

        for (size_t i=0; from[i] != '\0' && s < limit - 1; ++i, ++s)
            to[s] = from[i];

        to[s] = '\0';
    }

    return s;
}

typedef struct
{
    char        *to;
    size_t      limit;
    const char  *from;
    const char  *result;
    size_t      retval;
} test_t;

static size_t tests_failed;

static void run_test(test_t *t)
{
    size_t i=0;

    if (t->retval != strscat(t->to, t->limit, t->from))
    {
        ++tests_failed;
        return;
    }

    while (t->result[i] != '\0' || t->to[i] != '\0')
        if (t->result[i] != t->to[i])
        {
            ++tests_failed;
            break;
        }
        else
            ++i;
}

#define RUN_TEST(...)   run_test(&(test_t){__VA_ARGS__})

int main(void)
{
    RUN_TEST(
        .to     = (char[15]){"The Cutty"},
        .limit  = 15,
        .from   = " Sark is a ship dry-docked in London.",
        .result = "The Cutty Sark",
        .retval = 14
    );

    RUN_TEST(
        .to     = (char[15]){"The Cutty"},
        .limit  = 0,
        .from   = "this won't get appended",
        .result = "The Cutty",
        .retval = 0
    );

    RUN_TEST(
        .to     = (char[15]){"The Cutty"},
        .limit  = 15,
        .from   = "!",
        .result = "The Cutty!",
        .retval = 10
    );

    RUN_TEST(
        .to     = (char[]){"The Cutty Sark"},
        .limit  = 3,
        .from   = "this shouldn't get appended",
        .result = "The Cutty Sark",
        .retval = 14
    );

    RUN_TEST(
        .to     = (char[]){"The Cutty Sark"},
        .limit  = 1,
        .from   = "this shouldn't get appended, either",
        .result = "The Cutty Sark",
        .retval = 14
    );

    RUN_TEST(
        .to     = (char[]){""},
        .limit  = 1,
        .from   = "this had better not get appended!",
        .result = "",
        .retval = 0
    );

    (void)fprintf(stderr, "Number of tests failed: %zu.\n", tests_failed);
}


結束語

希望您喜歡閱讀本文,一如既往,如果您有改進建議,請透過私信聯絡我。

有用連結

C99 文章
軟體