常量字符串数组
将一组常量字符串存储到数组中,就具备了迭代与随机访问的优势,这是一种非常典型的应用,如存储一周7天的名称,可以方便的在实现某个计划任务程序中输出表头。
const char *week_names[] =
{
"Monday",
"Tuesday",
"Wednesday",
"Thursady",
"Friday",
"Saturday",
"Sunday"
};
对于像"Monday"之类的字符串字面量,它是不可修改的,如果需要使用一个名称引用这个字面量,可以定义 const char* 指针指向它。
const char *monday = "Monday";
现在week_names存储的每一个元素都是像"Monday"这样的不可写字面量,所以week_names的定义需要const修饰符。
我的生肖
下面我们借助于常量字符串数组,编写一个根据年份计算生肖的小程序。
#include <stdio.h>
int main()
{
const char *zodiacs[] =
{
"猴", "鸡", "狗", "猪", "鼠", "牛",
"虎", "兔", "龙", "蛇", "马", "羊"
};
const int years[] =
{
1954, 1955, 1983, 1987, 1998, 2001, 2010, 2020, 2021
};
for (int i = 0; i < sizeof(years) / sizeof(int); i++)
{
printf("%d -- %s\n", years[i], zodiacs[years[i] % 12]);
}
return 0;
}

数组zodiacs中的排列是有依据的,这对应到年份与12求余的结果;代码 zodiacs[years[i] % 12] 展示了数组套用数组的情形:years[i] % 12计算的结果作为数组zodiacs的索引,以取得对应生肖名称。
消息描述
假如我们使用了一个第三方库,当调用库函数时,会返回状态值表示调用的结果,状态码由#define定义的常量组成。
#define ERR_NO 100
#define ERR_COMMAND 101
#define ERR_PERMISSION 102
#define ERR_FATAL 103
#define ERR_UNKNOWN 104
作为应用层,我们希望能给用户显示一个直观的状态提示,如显示消息框提示出错,消息框的内容就是这些状态码的文字描述,为此我们可以定义一个消息数组,与这些状态码逐一对应。
const char *err_msg[] =
{
"No error",
"Bad command or file name",
"No permission",
"Fatal error",
"Unknown error"
};
接下来,我们模拟一个该库的函数实现,函数前导名lib指示这个函数位于第三方库中。
int lib_open_file(const char *fname)
{
int file_exist = 0;
if (!file_exist)
return ERR_COMMAND;
return ERR_NO;
}
然后在我们的客户端应用程序中,使用此函数。
int my_app_task()
{
// 调用库函数
int result = lib_open_file("glimix.com");
// 如果发生错误,就输出文本信息。
if (result != ERR_NO)
{
printf("%s", err_msg[result - 100]);
return -1;
}
return 0;
}
int main()
{
my_app_task();
return 0;
}

嘀嘀嘀嘀 嘀嘀(.... ..)
摩斯电码(Morse code)是莫斯于1844年发明的,由点(.)、划(-)两种符号组成。关于摩斯码的具体知识,我们可以通过搜索引擎获得,下面这张表展示了字母、数字、符号对应的编码。

我们的任务是编写程序将字符串以摩斯码输出,如:
Cat -.-. .- -
2021/9/12 ..--- ----- ..--- .---- -..-. ----. -..-. .---- ..---
首先,我们定义一个全局变量morse_code保存编码,这里依据数字、字母、符号分为三个区。
// 摩斯密码表
const char *morse_code[] =
{
//===== 数字区
"-----", // 0
".----", // 1
"..---", // 2
"...--", // 3
"....-", // 4
".....", // 5
"-....", // 6
"--...", // 7
"---..", // 8
"----.", // 9
//===== 字母区
".-", // A
"-...", // B
"-.-.", // C
"-..", // D
".", // E
"..-.", // F
"--.", // G
"....", // H
"..", // I
".---", // J
"-.-", // K
".-..", // L
"--", // M
"-.", // N
"---", // O
".--.", // P
"--.-", // Q
".-.", // R
"...", // S
"-", // T
"..-", // U
"...-", // V
".--", // W
"-..-", // X
"-.--", // Y
"--..", // Z
//===== 符号区
".-.-.-", // .
"---...", // :
"--..--", // ,
"-.-.-.", // ;
"..--..", // ?
"-...-", // =
".----", // `
"-..-.", // /
"-.-.--", // !
"-....-", // -
"..--.-", // _
".-..-.", // "
"-.--.", // (
"-.--.-", // )
"...-..-", // $
"....", // &
".--.-.", // @
};
同时定义三个分区的起始索引量,方便计算字符在morse_code中的索引。
#define SECT_DIGIT 0 // 数字段起始索引
#define SECT_CHAR 10 // 字母段起始索引
#define SECT_SYMBOL 36 // 符号段起始索引
比如,对于字符串"9Hi!",我们按字符分别计算索引:
字符 对应的索引
'9': index = SECT_DIGIT + ('9' - '0');
'H': index = SECT_CHAR + ('H' - 'A');
由于摩斯码仅编码了大写字母,而大小写字母的ASCII码值不一样,因此对于小写字母'i',要先将其转换成大写,再计算索引,
char ch = 'i';
ch = 'A' + (ch - 'a'); // 利用ASCII码中字符的关系,将小写转换为大写。
ch = toupper(ch); // 利用库函数将小写字符转换为大写(优先选用库函数)
index = SECT_CHAR + (ch - 'A');
与数字、字母不同,符号的ASCII码并不连续,之前的方法不再适用;一种方案就是采用分支语句硬编码。
switch (ch)
{
case '.': return SECT_SYMBOL + 0;
case ':': return SECT_SYMBOL + 1;
case ',': return SECT_SYMBOL + 2;
// ...
}
也可以使用查找策略,每遇到一个符号字符,就调用find_symbol_offset()一次,假定符号字符串的长度是n,那查找某个符号的最坏情况就是循环执行n次,所以这种方案的效率并不高;相对于硬编码,其优点就是,morse_code符号定义的顺序改变,这里改动最小。
int find_symbol_offset(char c)
{
const char *symbols = ".:,;?=`/!-_\"()$&@";
for (int i = 0; i < strlen(symbols); i++)
{
if (symbols[i] == c)
return i;
}
return -1;
}
也可以将两者结合起来,在效率与维护上达到最优解,这也是我们采用的方案。原理是创建一张符号映射表,当需要知道某个符号的索引时,直接查表即可。
char tab[128]; // 定义一张足以容纳标准ASCII码表的所有字符
void make_symbol_tab()
{
// 符号顺序与morse_code中定义的一致
const char *symbol = ".:,;?=`/!-_\"()$&@";
// 先将表清空,-1代表此处索引无效
for (int i = 0; i < 128; i++)
tab[i] = -1;
// 填充每个符号对应的索引
for (int i = 0; i < strlen(symbol); i++)
{
int index = *(symbol + i); // 取得当前符号的ASCII码
tab[index] = i; // 为表中的符号设置索引
}
// 这里的循环相当于
// tab['.'] = 0;
// tab[':'] = 1;
// tab[','] = 2;
// ...
}
最后,你会发现我们忽略了一个问题,那就是如何判断当前字符所属分区,这可以通过 <ctype.h> 提供的库函数解决。
int isdigit(int c): 检查所传的字符是否是十进制数字字符。
void isalpha(int c): 检查所传的字符是否是字母。
将这些组合起来,我们的摩斯编码机就运行起来了。
void print_morse_str(const char *str)
{
printf("%s \t\t", str);
for (int i = 0, len = strlen(str); i < len; i++)
{
char c = str[i];
int index = -1;
if (isdigit(c))
{
index = SECT_DIGIT + (c - '0');
}
else if (isalpha(c))
{
index = SECT_CHAR + (toupper(c) - 'A');
}
else
{
index = SECT_SYMBOL + tab[c];
}
assert(index != -1);
printf((i != len - 1) ? "%s " : "%s", morse_code[index]);
}
printf("\n");
}
int main()
{
make_symbol_tab();
print_morse_str("Hi!");
print_morse_str("SOS");
print_morse_str("9/12");
return 0;
}

陕公网安备61011202001108号