11.6 设计程序
在本章的最后,通过以下的案例实践本章学到的知识。
11.6.1 问题
数值数据用图表表示通常更容易理解。现在要处理的问题是编写一个程序,从一组数值中生成柱状图。选择柱状图的理由有如下三个:
(1) 实践结构的用法。
(2) 了解如何在有效的空间中放置并显示柱状图。
(3) 柱状图在实际应用中很常见。
11.6.2 分析
无须对纸张大小、列数甚至图的比例作任何假设。只需编写一个函数,它将纸张大小作为参数,使柱状图能放在该纸张上。这可以使函数适用于任何的情况。我们将数值存放在链表的一系列结构里。这样,就只需将第一个结构传递给函数,函数就能够从链表中得到所有的结构。这个结构非常简单,但以后可以用自己设计的信息去修饰它。
假定柱状图中的竖条显示顺序和数据输入的顺序相同,因此无须排序数据。这个程序有两个函数:生成柱状图的函数和main()函数,用来练习柱状图生成过程。
以下是所需的步骤:
(1) 编写柱状图函数。
(2) 编写main()函数,测试柱状图函数。
11.6.3 解决方案
本节列出了解决问题的步骤。
1. 步骤1
很明显,这个程序将使用结构,因为这是本章讨论的主题。第一步是设计程序要使用的结构。这里将使用typedef,以避免重复使用关键字struct。
/* Program 11.9 Generating a bar chart */
#include <stdio.h>
typedef struct barTAG
{
double value;
struct barTAG *pnextbar;
}bar;
int main(void)
{
/* Code for main */
}
/* Definition of the bar-chart function */
barTAG结构用它的值定义一个竖条。注意将该结构中的指针定义为指向下一个结构。这样就可以把竖条存储为链表,其优点是在分配内存时不会浪费内存。链表适合于本例,因为我们只想按顺序遍历所有的竖条,从第一个到最后一个。接着按输入值的顺序创建竖条,将新建的竖条追加到前一个竖条之后。然后遍历链表中的结构,生成柱状图的可视化表示。
这里必须使用struct barTAG定义结构,而不能使用类型名称bar,因为此时编译器还没有完成typedef的处理,所以bar还未定义。换句话说,编译器先分析barTAG结构,再利用typedef定义bar的意义。
现在为柱状图函数指定函数原型,给这个函数定义添加框架。它需要的参数有指向链表中第一个竖条的指针、页高、页宽及图表的标题。
/* Program 11.9 Generating a bar chart */
#include <stdio.h>
#define PAGE_HEIGHT 20
#define PAGE_WIDTH 40
typedef struct barTAG
{
double value;
struct barTAG *pnextbar;
}bar;
typedef unsigned int uint; /* Type definition*/
/* Function prototype */
int bar_chart(bar *pfirstbar, uint page_width,
uint page_height, char *title);
int main(void)
{
/* Code for main */
}
int bar_chart(bar *pfirstbar, uint page_width,
uint page_height,char *title)
{
/* Code for function…*/
return 0;
}
添加一条typedef语句,将uint定义为unsigned int的同义词。这样可以缩短用unsigned int声明变量的语句长度。
接下来为柱状图需要的基本数据添加一些声明和代码。需要竖条的最大和最小值、图表的垂直高度(即最大值和最小值之差)。另外,需要根据纸张的宽度及竖条的数量计算竖条的宽,并调整高度,以包含水平轴和标题。
/* Program 11.9 Generating a bar chart */
#include <stdio.h>
#define PAGE_HEIGHT 20
#define PAGE_WIDTH 40
typedef struct barTAG
{
double value;
struct barTAG *pnextbar;
}bar;
typedef unsigned int uint; /* Type definition */
/* Function prototype */
int bar_chart(bar *pfirstbar, uint page_width,
uint page_height, char *title);
int main(void)
{
/* Code for main */
}
int bar_chart(bar *pfirstbar, uint page_width,
uint page_height,char *title)
{
bar *plastbar = pfirstbar; /* Pointer to previous bar */
double max = 0.0; /* Maximum bar value */
double min = 0.0; /* Minimum bar value */
double vert_scale = 0.0; /* Unit step in vertical direction */
uint bar_count = 1; /* Number of bars - at least 1 */
uint barwidth = 0; /* Width of a bar */
uint space = 2; /* spaces between bars */
/* Find maximum and minimum of all bar values */
/* Set max and min to first bar value */
max = min = plastbar->value;
while((plastbar = plastbar->pnextbar) != NULL)
{
bar_count++; /* Increment bar count */
max = (max < plastbar->value)? plastbar->value : max;
min = (min > plastbar->value)? plastbar->value : min;
}
vert_scale = (max - min)/page_height; /* Calculate step length */
/* Check bar width */
if((barwidth = page_width/bar_count - space) < 1)
{
printf("\nPage width too narrow.\n");
return -1;
}
/* Code for rest of the function…*/
return 0;
}
space变量存放两个竖条间的空格数,初值设为2。柱状图在输出时是一次显示一行,因此需要一个字符串来对应一行一行地绘制该竖条时所使用的区段,还需要另一个相同长度的字符串,包含页面上特定位置没有竖条时所使用的空格数。下面添加如下代码:
/* Program 11.9 Generating a bar chart */
#include <stdio.h>
#include <stdlib.h>
#define PAGE_HEIGHT 20
#define PAGE_WIDTH 40
typedef struct barTAG
{
double value;
struct barTAG *pnextbar;
}bar;
typedef unsigned int uint; /* Type definition */
/* Function prototype */
int bar_chart(bar *pfirstbar, uint page_width,
uint page_height, char *title);
int main(void)
{
/* Code for main */
}
int bar_chart(bar *pfirstbar, uint page_width,
uint page_height, char *title)
{
bar *plastbar = pfirstbar; /* Pointer to previous bar */
double max = 0.0; /* Maximum bar value */
double min = 0.0; /* Minimum bar value */
double vert_scale = 0.0; /* Unit step in vertical direction */
uint bar_count = 1; /* Number of bars - at least 1 */
uint barwidth = 0; /* Width of a bar */
uint space = 2; /* spaces between bars */
uint i = 0; /* Loop counter */
char *column = NULL; /* Pointer to bar column section */
char *blank = NULL; /* Blank string for bar+space */
/* Find maximum and minimum of all bar values */
/* Set max and min to first bar value */
max = min = plastbar->value;
while((plastbar = plastbar->pnextbar) != NULL)
{
bar_count++; /* Increment bar count */
max = (max < plastbar->value)? plastbar->value : max;
min = (min > plastbar->value)? plastbar->value : min;
}
vert_scale = (max - min)/page_height; /* Calculate step length */
/* Check bar width */
if((barwidth = page_width/bar_count - space) < 1)
{
printf("\nPage width too narrow.\n");
return -1;
}
/* Set up a string that will be used to build the columns */
/* Get the memory */
if((column = malloc(barwidth + space + 1)) == NULL)
{
printf("\nFailed to allocate memory in barchart()"
" - terminating program.\n");
exit(1);
}
for(i = 0 ; i<space ; i++)
*(column+i)=' '; /* Blank the space between bars */
for( ; i<space+barwidth ; i++)
*(column+i)='#'; /* Enter the bar characters */
*(column+i) = '\0'; /* Add string terminator */
/* Set up a string that will be used as a blank column */
/* Get the memory */
if((blank = malloc(barwidth + space + 1)) == NULL)
{
printf("\nFailed to allocate memory in barchart()"
" - terminating program.\n");
exit(1);
}
for(i = 0 ; i<space+barwidth ; i++)
*(blank+i) = ' '; /* Blank total width of bar+space */
*(blank+i) = '\0'; /* Add string terminator */
/* Code for rest of the function…*/
free(blank); /* Free memory for blank string */
free(column); /* Free memory for column string */
return 0;
}
这里用字符'#'绘制竖条。画竖条时,要先编写一个字符串,使之含有space个空格和barwidth个'#'字符。之后使用库函数malloc()为它动态分配内存,因此必须给头文件<stdlib.h>添加#include指令。用来画竖条的字符串是column,blank是包含空格的且长度与column相同的字符串。画完柱状图后,在退出之前要释放column和blank所占的内存。
接下来,添加画竖条图的最后一段代码:
/* Program 11.9 Generating a bar chart */
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define PAGE_HEIGHT 20
#define PAGE_WIDTH 40
typedef struct barTAG
{
double value;
struct barTAG *pnextbar;
}bar;
typedef unsigned int uint; /* Type definition */
/* Function prototype */
int bar_chart(bar *pfirstbar, uint page_width,
uint page_height, char *title);
int main(void)
{
/* Code for main */
}
int bar_chart(bar *pfirstbar, uint page_width, ,
uint page_height char *title)
{
bar *plastbar = pfirstbar; /* Pointer to previous bar */
double max = 0.0; /* Maximum bar value */
double min = 0.0; /* Minimum bar value */
double vert_scale = 0.0; /* Unit step in vertical direction */
double position = 0.0; /* Current vertical position on chart */
uint bar_count = 1; /* Number of bars - at least 1 */
uint barwidth = 0; /* Width of a bar */
uint space = 2; /* spaces between bars */
uint i = 0; /* Loop counter */
uint bars = 0; /* Loop counter through bars */
char *column = NULL; /* Pointer to bar column section */
char *blank = NULL; /* Blank string for bar+space */
bool axis = false; /* Indicates axis drawn */
/* Find maximum and minimum of all bar values */
/* Set max and min to first bar value */
max = min = plastbar->value;
while((plastbar = plastbar->pnextbar) != NULL)
{
bar_count++; /* Increment bar count */
max = (max < plastbar->value)? plastbar->value : max;
min = (min > plastbar->value)? plastbar->value : min;
}
vert_scale = (max - min)/page_height; /* Calculate step length */
/* Check bar width */
if((barwidth = page_width/bar_count - space) < 1)
{
printf("\nPage width too narrow.\n");
return -1;
}
/* Set up a string that will be used to build the columns */
/* Get the memory */
if((column = malloc(barwidth + space + 1)) == NULL)
{
printf("\nFailed to allocate memory in barchart()"
" - terminating program.\n");
exit(1);
}
for(i = 0 ; i<space ; i++)
*(column+i)=' '; /* Blank the space between bars */
for( ; i < space+barwidth ; i++)
*(column+i)='#'; /* Enter the bar characters */
*(column+i) = '\0'; /* Add string terminator */
/* Set up a string that will be used as a blank column */
/* Get the memory */
if((blank = malloc(barwidth + space + 1)) == NULL)
{
printf("\nFailed to allocate memory in barchart()"
" - terminating program.\n");
exit(1);
}
for(i = 0 ; i<space+barwidth ; i++)
*(blank+i) = ' '; /* Blank total width of bar+space */
*(blank+i) = '\0'; /* Add string terminator */
printf("^ %s\n", title); /* Output the chart title */
/* Draw the bar chart */
position = max;
for(i = 0 ; i <= page_height ; i++)
{
/* Check if we need to output the horizontal axis */
if(position <= 0.0 && !axis)
{
printf("+"); /* Start of horizontal axis */
for(bars = 0; bars < bar_count*(barwidth+space); bars++)
printf("-"); /* Output horizontal axis */
printf(">\n");
axis = true; /* Axis was drawn */
position -= vert_scale;/* Decrement position */
continue;
}
printf("|"); /* Output vertical axis */
plastbar = pfirstbar; /* start with the first bar */
/* For each bar…*/
for(bars = 1; bars <= bar_count; bars++)
{
/* If position is between axis and value, output column */
/* otherwise output blank */
printf("%s", position <= plastbar->value &&
plastbar->value >= 0.0 && position > 0.0 ||
position >= plastbar->value &&
plastbar->value <= 0.0 &&
position <= 0.0 ? column: blank);
plastbar = plastbar->pnextbar;
}
printf("\n"); /* End the line of output */
position -= vert_scale; /* Decrement position */
}
if(!axis) /* Have we output the horizontal axis? */
{ /* No, so do it now */
printf("+");
for(bars = 0; bars < bar_count*(barwidth+space); bars++)
printf("-");
printf(">\n");
}
free(blank); /* Free memory for blank string */
free(column); /* Free memory for column string */
return 0;
}
for循环会输出page_height行字符。每行表示垂直轴上一段由vert_scale代表的距离。这个值是page_height除以最大值和最小值之差得到的。因此,第一行输出对应的position是max,在每次迭代中,position都会递减vert_scale,直到到达min为止。
在输出每一行之前,都要先确定是否要输出水平轴。当position小于或等于0,且尚未显示水平轴时,就需要输出水平轴。
除了水平轴之外,还必须确定每个竖条位置显示什么内容。这是为每个竖条执行的内层for循环内决定的。printf()函数内的条件运算符会选择输出column或blank。如果position的值介于竖条最大值和0之间,则输出column,否则输出blank。在输出完整的一行后,输出'\n'以结束这行,并递增position的值。
所有的竖条可能都是正的,此时需要确保在循环结束后输出水平轴,因为它在循环内没有输出。
2. 步骤2
现在需要实现main()函数,调用bar_chart()函数:
/* Program 11.9 Generating a bar chart */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#define PAGE_HEIGHT 20
#define PAGE_WIDTH 40
typedef struct barTAG /* Bar structure */
{
double value; /* Value of bar */
struct barTAG *pnextbar; /* Pointer to next bar */
}bar; /* Type for a bar */
typedef unsigned int uint; /* Type definition */
/* Function prototype */
int bar_chart(bar *pfirstbar, uint page_width,
uint page_height, char *title);
int main()
{
bar firstbar; /* First bar structure */
bar *plastbar = NULL; /* Pointer to last bar */
char value[80]; /* Input buffer */
char title[80]; /* Chart title */
printf("\nEnter the chart title: ");
gets(title); /* Read chart title */
for( ;; ) /* Loop for bar input */
{
printf("Enter the value of the bar, or use quit to end: ");
gets(value);
if(strcmp(value, "quit") == 0) /* quit entered? */
break; /* then input finished */
/* Store in next bar */
if(!plastbar) /* First time? */
{
firstbar.pnextbar = NULL; /* Initialize next pointer */
plastbar = &firstbar; /* Use the first */
}
else
{
/* Get memory */
if(!(plastbar-> = malloc(sizeof(bar))))
{
printf("Oops! Couldn't allocate memory\n");
return -1;
}
plastbar = plastbar->pnextbar; /* Old next is new bar */
plastbar->pnextbar = NULL; /* New bar next is NULL */
}
plastbar->value = atof(value); /* Store the value */
}
/* Create bar-chart */
bar_chart(&firstbar, PAGE_WIDTH, PAGE_HEIGHT, title);
/* We are done, so release all the memory we allocated */
while(firstbar.pnextbar)
{
plastbar = firstbar.pnextbar; /* Save pointer to next */
firstbar.pnextbar = plastbar->pnextbar; /* Get one after next */
free(plastbar); /* Free next memory */
}
return 0;
}
int bar_chart(bar *pfirstbar, uint page_width, uint page_height, char *title)
{
/* Implementation of function as before…*/
}
使用gets()函数读取柱状图的标题后,就在for循环内读入连续的数值。除了第一个值外,其他值都要用于给新的竖条结构分配内存,之后存储该值。当然,可以跟踪第一个结构,因为它链接了其他结构,跟踪上一个新增结构中的指针,在添加下一个结构时更新它的指针pnextbar。输入了所有的值后,就调用bar_chart()函数,生成柱状图。最后,释放竖条结构的内存。注意,不能删除firstbar,因为它的内存不是动态分配的。给头文件<string.h>包含#include指令,因为使用了gets()函数。
之后在main()函数中添加一行代码,根据输入的值生成柱状图。程序的输出如下:
Enter the chart title: Trial Bar Chart
Enter the value of the bar, or use quit to end: 6
Enter the value of the bar, or use quit to end: 3
Enter the value of the bar, or use quit to end: -5
Enter the value of the bar, or use quit to end: -7
Enter the value of the bar, or use quit to end: 9
Enter the value of the bar, or use quit to end: 4
Enter the value of the bar, or use quit to end: quit
^ Trial Bar Chart
| ####
| ####
| ####
| ####
| #### ####
| #### ####
| #### ####
| #### #### ####
| #### #### #### ####
| #### #### #### ####
| #### #### #### ####
| #### #### #### ####
+------------------------------------>
| #### ####
| #### ####
| #### ####
| #### ####
| #### ####
| ####
| ####
| ####





