作者:
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 文章
软件