如何构建一个 C 语言程序

本文内容

目录汇总:C 语言零基础入门教程

我们已经看过构成 C 程序的主要元素,现在应该为编排这些元素开发一套方法了。目前只考虑单个文件的程序。

迄今为止,已经知道程序可以包含:

  • #include#define 这样的预处理指令;
  • 类型定义;
  • 外部变量声明;
  • 函数原型;
  • 函数定义。

C 语言对上述这些项的顺序要求极少:执行到预处理指令所在的代码行时,预处理指令才会起作用;类型名定义后才可以使用;变量声明后才可以使用。虽然 C 语言对函数没有什么要求,但是这里强烈建议在第一次调用函数前要对每个函数进行定义或声明。(至少 C99 要求我们这么做。)

为了遵守这些规则,这里有几个构建程序的方法。下面是一种可能的编排顺序:

  • #include 指令;
  • #define 指令;
  • 类型定义;
  • 外部变量的声明;
  • main 函数之外的函数的原型;
  • main 函数的定义;
  • 其他函数的定义。

因为 #include 指令带来的信息可能在程序中的好几个地方都需要,所以先放置这条指令是合理的。#define 指令创建宏,对这些宏的使用通常遍布整个程序。类型定义放置在外部变量声明的上面是合乎逻辑的,因为这些外部变量的声明可能会引用刚刚定义的类型名。接下来,声明外部变量使得它们对于跟随在其后的所有函数都是可用的。在编译器看见原型之前调用函数,可能会产生问题,而此时声明除了 main 函数以外的所有函数可以避免这些问题。这种方法也使得无论用什么顺序编排函数定义都是可能的。例如,根据函数名的字母顺序编排,或者把相关函数组合在一起进行编排。在其他函数前定义 main 函数使得阅读程序的人容易定位程序的起始点。

最后的建议:在每个函数定义前放盒型注释可以给出函数名、描述函数的目的、讨论每个形式参数的含义、描述返回值(如果有的话)并罗列所有的副作用(如修改了外部变量的值)。

程序 给一手牌分类

为了说明构建 C 程序的方法,下面编写一个比前面的例子更复杂的程序。这个程序会对一手牌进行读取和分类。手中的每张牌都有花色(方块、梅花、红桃和黑桃)和点数(2、3、4、5、6、7、8、9、10、J、Q、K 和 A)。不允许使用王牌,并且假设 A 是最高的点数。程序将读取一手 5 张牌,然后把手中的牌分为下列某一类(列出的顺序从最好到最坏)。

  • 同花顺(即顺序相连又都是同花色)。
  • 四张(4 张牌点数相同)。
  • 葫芦(3 张牌是同样的点数,另外 2 张牌是同样的点数)。
  • 同花(5 张牌是同花色的)。
  • 顺子(5 张牌的点数顺序相连)。
  • 三张(3 张牌的点数相同)。
  • 两对。
  • 对于(2 张牌的点数相同)。
  • 其他牌(任何其他情况的牌)。

如果一手牌可分为两种或多种类别,程序将选择最好的一种。

为了便于输入,把牌的点数和花色简化如下(字母可以是大写,也可以是小写)。

  • 点数:2 3 4 5 6 7 8 9 t j q k a。
  • 花色:c d h s。

如果用户输入非法牌或者输入同一张牌两次,程序将忽略此牌,产生出错消息,然后要求输入另外一张牌。如果输入为 0 而不是一张牌,就会导致程序终止。

与程序的会话如下所示:

Enter a card: 2s
Enter a card: 5s
Enter a card: 4s
Enter a card: 3s
Enter a card: 6s
Straight flush

Enter a card: 8c
Enter a card: as
Enter a card: 8c
Duplicate card; ignored.
Enter a card: 7c
Enter a card: ad
Enter a card: 3h
Pair

Enter a card: 6s
Enter a card: d2
Bad card; ignored.
Enter a card: 2d
Enter a card: 9c
Enter a card: 4h
Enter a card: ts
High card

Enter a card: 0

从上述程序的描述可以看出它有 3 个任务:

  • 读入一手 5 张牌;
  • 分析对子、顺子等情况;
  • 显示一手牌的分类。

把程序分为 3 个函数,分别完成上述 3 个任务,即 read_cards 函数、analyze_hand 函数和 print_result 函数。main 函数只负责在无限循环中调用这些函数。这些函数需要共享大量的信息,所以让它们通过外部变量来进行交流。read_cards 函数将与一手牌相关的信息存进几个外部变量中,然后 analyze_hand 函数将检查这些外部变量,把结果分类放在便于 print_result 函数显示的其他外部变量中。

基于这些初步设计可以开始勾画程序的轮廓:

 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
/* #include directives go here */

/* #define directives go here */

/* declarations of external variables go here */

/* prototypes */
void read_cards(void);
void analyze_hand(void);
void print_result(void);
/************************************************************
* main: Calls read_cards, analyze_hand, and print_result   *
*       repeatedly.                                        *
************************************************************/
int main(void)
{
 for (;;)  {
   read_cards();
   analyze_hand();
   print_result();
 }
}

/***********************************************************
 * read_cards:  Reads the cards into external variables;   *
 *              checks for bad cards and duplicate cards.  *
 ***********************************************************/
void read_cards(void)
{
 ...
}

/************************************************************
* analyze_hand: Determines whether the hand contains a     *
*               straight,  a flush,  four-of-a-kind,       *
*               and/or three-of-a-kind;  determines the    *
*               number of pairs;  stores the results into  *
*               external variables.                        *
************************************************************/
void analyze_hand(void)
{
 ...
}

/************************************************************
* print_result: Notifies the user of the result,  using    *
*               the external variables set by              *
*               analyze_hand.                              *
************************************************************/
void print_result(void)
{
 ...
}

余下的最紧迫的问题是如何表示一手牌。看看 read_cards 函数和 analyze_hand 函数将对这手牌执行什么操作。分析这手牌期间,analyze_hand 函数需要知道每个点数和每个花色的牌的数量。建议使用两个数组,即 num_in_ranknum_in_suitnum_in_rank[r] 的值是点数为 r 的牌的数量,而 num_in_suit[s] 的值是花色为 s 的牌的数量。(把点数编码为 0~12 的数,把花色编码为 0~3 的数。)为了便于 read_cards 函数检查重复的牌,还需要第三个数组 card_exists。每次读取等级为 r 且花色为 s 的牌时,read_cards 函数都会检查 card_ exists[r][s] 的值是否为 true。如果是,则表明此张牌已经输入过;如果不是,则 read_ cards 函数把 true 赋值给 card_exists[r][s]

read_cards 函数和 analyze_hand 函数都需要访问数组 num_in_ranknum_in_suit,所以这两个数组必须是外部变量;而数组 card_exists 只用于 read_cards 函数,所以可将它设为此函数的局部变量。通常只在必要时才把变量设为外部变量。

已经确定了主要的数据结构,现在可以完成程序了:

poker.c

  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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/* Classifies a poker hand */

#include <stdbool.h>   /* C99 only */
#include <stdio.h>
#include <stdlib.h>

#define NUM_RANKS 13
#define NUM_SUITS 4
#define NUM_CARDS 5

/* external variables */
int num_in_rank[NUM_RANKS];
int num_in_suit[NUM_SUITS];
bool straight, flush, four, three;
int pairs;   /* can be 0, 1, or 2 */

/* prototypes */
void read_cards(void);
void analyze_hand(void);
void print_result(void);

/************************************************************
* main: Calls read_cards, analyze_hand, and print_result   *
*       repeatedly.                                        *
************************************************************/
int main(void)
{
 for (;;) {
   read_cards();
   analyze_hand();
   print_result();
 }
}

/************************************************************
* read_cards: Reads the cards into the external            *
*             variables num_in_rank and num_in_suit;       *
*             checks for bad cards and duplicate cards.    *
************************************************************/
void read_cards(void)
{
 bool card_exists[NUM_RANKS][NUM_SUITS];
 char ch, rank_ch, suit_ch;
 int rank, suit;
 bool bad_card;
 int cards_read = 0;

 for (rank = 0; rank < NUM_RANKS; rank++) {
   num_in_rank[rank] = 0;
   for (suit = 0; suit < NUM_SUITS; suit++)
     card_exists[rank][suit] = false;
 }

 for (suit = 0; suit < NUM_SUITS; suit++)
   num_in_suit[suit] = 0;

 while (cards_read < NUM_CARDS) {
   bad_card = false;

   printf("Enter a card: ");

   rank_ch = getchar();
   switch (rank_ch) {
     case '0':           exit(EXIT_SUCCESS);
     case '2':           rank = 0; break;
     case '3':           rank = 1; break;
     case '4':           rank = 2; break;
     case '5':           rank = 3; break;
     case '6':           rank = 4; break;
     case '7':           rank = 5; break;
     case '8':           rank = 6; break;
     case '9':           rank = 7; break;
     case 't': case 'T': rank = 8; break;
     case 'j': case 'J': rank = 9; break;
     case 'q': case 'Q': rank = 10; break;
     case 'k': case 'K': rank = 11; break;
     case 'a': case 'A': rank = 12; break;
     default:            bad_card = true;
   }

   suit_ch = getchar();
   switch (suit_ch) {
     case 'c': case 'C': suit = 0; break;
     case 'd': case 'D': suit = 1; break;
     case 'h': case 'H': suit = 2; break;
     case 's': case 'S': suit = 3; break;
     default:            bad_card = true;
   }

   while ((ch = getchar()) != '\n')
     if (ch != ' ') bad_card = true;

   if (bad_card)
     printf("Bad card; ignored.\n");
   else if (card_exists[rank][suit])
     printf("Duplicate card; ignored.\n");
   else {
     num_in_rank[rank]++;
     num_in_suit[suit]++;
     card_exists[rank][suit] = true;
     cards_read++;
   }
 }
}

/************************************************************
* analyze_hand: Determines whether the hand contains a     *
*               straight, a flush, four-of-a-kind,         *
*               and/or three-of-a-kind; determines the     *
*               number of pairs; stores the results into   *
*               the external variables straight, flush,    *
*               four, three, and pairs.                    *
************************************************************/
void analyze_hand(void)
{
 int num_consec = 0;
 int rank, suit;

 straight = false;
 flush = false;
 four = false;
 three = false;
 pairs = 0;

 /* check for flush */
 for (suit = 0; suit < NUM_SUITS; suit++)
   if (num_in_suit[suit] == NUM_CARDS)
     flush = true;

 /* check for straight */
 rank = 0;
 while (num_in_rank[rank] == 0) rank++;
 for (; rank < NUM_RANKS && num_in_rank[rank] > 0; rank++)
   num_consec++;
 if (num_consec == NUM_CARDS) {
   straight = true;
   return;
 }

 /* check for 4-of-a-kind, 3-of-a-kind, and pairs */
 for (rank = 0; rank < NUM_RANKS; rank++) {
   if (num_in_rank[rank] == 4) four = true;
   if (num_in_rank[rank] == 3) three = true;
   if (num_in_rank[rank] == 2) pairs++;
 }
}

/************************************************************
* print_result: prints the classification of the hand,     *
*               based on the values of the external        *
*               variables straight, flush, four, three,    *
*               and pairs.                                 *
************************************************************/
void print_result(void)
{
 if (straight && flush) printf("Straight flush");
 else if (four)         printf("Four of a kind");
 else if (three &&
          pairs == 1)   printf("Full house");
 else if (flush)        printf("Flush");
 else if (straight)     printf("Straight");
 else if (three)        printf("Three of a kind");
 else if (pairs == 2)   printf("Two pairs");
 else if (pairs == 1)   printf("Pair");
 else                   printf("High card");

 printf("\n\n");
}

注意 read_cards 函数中 exit 函数的使用(第一个 switch 语句的分支 '0')。因为 exit 函数具有在任何地方终止程序执行的能力,所以它对于此程序是十分方便的。

请参阅

(完)

comments powered by Disqus

本文内容