UNIX编程中的实用技巧与ANSI C标准变革
1. 正则表达式相关
在UNIX系统中,正则表达式的使用十分广泛。例如,使用
regexp
命令可以进行模式匹配:
% regexp '^A....d$' /usr/dict/words
Aeneid
Alfred
Arnold
Atwood
% regexp 'b(an){2,}' /usr/dict/words
banana
不过,不同的UNIX系统版本在正则表达式函数的支持上有所不同。基于System V的系统提供
regcmp
和
regex
函数,而基于BSD的系统则提供了不同的函数:
char *re_comp(const char *re);
int re_exec(const char *str);
re_comp
函数用于编译正则表达式,并将结果内部存储。如果编译成功,返回
NULL
;否则返回指向错误信息的指针。
re_exec
函数将字符串与最后编译的正则表达式进行比较,匹配返回1,不匹配返回0,出错返回 -1。
BSD函数的优点是接受标准
ed
正则表达式,但System V函数允许同时使用多个正则表达式而无需不断重新编译,还能获取匹配的字符串部分。如果需要考虑可移植性,可以编写代码使得两种正则表达式函数都能使用,或者获取免费或公共领域的正则表达式函数实现。例如,多伦多大学的Henry Spencer提供了Research UNIX Version 8中正则表达式函数的公共领域实现,可从
ftp://ftp.cs.toronto.edu/pub/regexp.shar.Z
获取;GNU项目也提供了较为健壮的实现,可从
ftp://prep.ai.mit.edu/pub/gnu/regex-0.12.tar.gz
获取。
2. 国际化编程
长期以来,UNIX使用ASCII字符集,这在美国运行良好,但在其他国家会出现问题。例如,英国的货币符号
£
是非ASCII字符,使用变音符号的国家以及像日本这样字符集非拉丁起源的国家,ASCII字符集就无法满足需求。
为了实现国际化,程序应处理当地国家的字符集,以当地常用格式打印日期和时间,使用正确的字符标记小数点等。下面介绍一些基本的国际化函数。
2.1 定义区域设置
区域设置定义了程序运行环境的国际化特征。“UNIX”区域设置名为“C”,其他区域通常使用两个字符的名称,一般是国家名称的ISO标准双字母缩写。例如,“de”是德国区域设置,“fr”是法国区域设置,“ja”是日本区域设置。
使用
setlocale
函数可以为程序设置不同类别的区域设置:
#include <locale.h>
char *setlocale(int category, const char *locale);
locale
参数包含区域设置的名称,国际化函数会使用它来查找
/usr/lib/locale
下同名子目录中的各种数据库。如果
locale
为空字符串,值将从环境变量中获取;如果
locale
为
NULL
,则返回当前区域设置且不做更改。
category
参数必须是以下之一:
| 类别 | 说明 |
| ---- | ---- |
|
LC_CTYPE
| 影响字符类型函数(如
isdigit
和
tolower
)的行为 |
|
LC_NUMERIC
| 影响格式化输入/输出函数(如
scanf
、
printf
等)和字符串转换函数(如
strtol
等)的小数点字符和千位分隔符字符 |
|
LC_TIME
| 影响
ascftime
、
cftime
、
getdate
和
strftime
提供的日期和时间格式 |
|
LC_COLLATE
| 影响
strcoll
和
strxfrm
产生的排序顺序 |
|
LC_MONETARY
| 影响
localeconv
返回的货币格式化信息 |
|
LC_MESSAGES
| 影响
dgettext
、
gettext
和
gettxt
的行为 |
|
LC_ALL
| 用于指定上述所有类别 |
如果
setlocale
成功,返回
locale
;失败则返回
NULL
。
2.2 数字格式化
不同国家在数字格式化方面存在诸多问题,除了货币符号的明显差异,小数点字符和千位分隔符也有所不同。
localeconv
函数可以返回有关如何在程序当前区域设置中格式化数字的信息:
#include <locale.h>
struct lconv *localeconv(void);
该函数返回一个指向
struct lconv
类型结构的指针:
struct lconv {
char *decimal_point;
char *thousands_sep;
char *grouping;
char *int_curr_symbol;
char *currency_symbol;
char *mon_decimal_point;
char *mon_thousands_sep;
char *mon_grouping;
char *positive_sign;
char *negative_sign;
char int_frac_digits;
char frac_digits;
char p_cs_precedes;
char p_sep_by_space;
char n_cs_precedes;
char n_sep_by_space;
char p_sign_posn;
char n_sign_posn;
};
该结构的字段说明如下:
| 字段 | 说明 |
| ---- | ---- |
|
decimal_point
| 用于格式化非货币数量的小数点字符 |
|
thousands_sep
| 用于分隔非货币数量中小数点左侧数字组的字符 |
|
grouping
| 一个字符串,每个字节作为一个整数,表示格式化非货币数量中当前组的数字位数 |
|
int_curr_symbol
| 当前区域设置适用的国际货币符号 |
|
currency_symbol
| 当前区域设置适用的本地货币符号 |
|
mon_decimal_point
| 用于格式化货币数量的小数点字符 |
|
mon_grouping
| 一个字符串,每个字节作为一个整数,表示格式化货币数量中当前组的数字位数 |
|
positive_sign
| 用于表示非负格式化货币数量的字符串 |
|
negative_sign
| 用于表示负格式化货币数量的字符串 |
|
int_frac_digits
| 国际格式化货币数量中小数点右侧显示的小数位数 |
|
frac_digits
| 本地格式化货币数量中小数点右侧显示的小数位数 |
|
p_cs_precedes
| 设置为1或0,表示货币符号是否在非负格式化货币数量的值之前 |
|
p_sep_by_space
| 设置为1或0,表示货币符号是否与非负格式化货币数量的值用空格分隔 |
|
n_cs_precedes
| 设置为1或0,表示货币符号是否在负格式化货币数量的值之前 |
|
n_sep_by_space
| 设置为1或0,表示货币符号是否与负格式化货币数量的值用空格分隔 |
|
p_sign_posn
| 指示非负格式化货币数量中正号的位置 |
|
n_sign_posn
| 指示负格式化货币数量中负号的位置 |
2.3 排序序列
像
strcmp
这样的函数基于ASCII排序序列比较字符串,在处理非ASCII字符集时可能无法正常工作。因此,在国际环境中,不能使用
qsort
和
strcmp
对字符串进行正确排序。可以使用
strcoll
和
strxfrm
函数进行比较:
#include <string.h>
int strcoll(const char *s1, const char *s2);
size_t strxfrm(char *dst, const char *src, size_t n);
strcoll
函数比较字符串
s1
和
s2
,根据在程序的
LC_COLLATE
类别区域设置中
s1
是否应小于、等于或大于
s2
,返回小于、等于或大于零的值。
strxfrm
函数将字符串
src
进行转换,结果存储在
dst
中。如果对两个转换后的字符串应用
strcmp
,将得到与对原始字符串应用
strcoll
相同的结果。最多将
n
个字节(包括终止空字符)放入
dst
。如果
dst
为
NULL
且
n
为0,
strxfrm
将返回存储转换后字符串所需的字节数。
3. ANSI C标准的重大变化
早期的C语言由Brian Kernighan和Dennis Ritchie的《The C Programming Language》定义,但该定义不够明确,导致不同编译器在处理某些结构时存在差异,造成了可移植性问题。此外,一些扩展(如枚举类型、
void
类型以及结构作为函数参数和返回值)缺乏充分文档,不同编译器的支持程度也不同。
为了解决这些问题,美国国家标准协会在20世纪80年代末制定了ANSI C标准(X3.159)。以下是ANSI C标准中的一些重大变化:
3.1 标记
标记是语言中最小的可识别单元,如运算符、变量名、关键字和常量等。
3.2 字符串连接
ANSI C标准规定,相邻的字符串常量(中间无运算符)应直接连接。例如:
"foo" "bar"
等价于
"foobar"
这在定义长字符串时非常有用,例如:
char *usage = "Usage: thisprogram [-b] [-g] [-l] files...\n"
" -b babble incessantly about everything\n"
" -g babble in ancient greek\n"
" -l babble in latin\n";
3.3 转义序列
ANSI C标准定义了一些新的反斜杠转义序列:
-
\a
:用于“警报”,打印时会使终端响铃。
-
\v
:垂直制表符(许多编译器之前已支持)。
-
\x
:引入十六进制常量,类似于反斜杠后跟数字引入八进制常量。
此外,八进制常量的数字位数正式限制为三位,八进制常量中不再允许出现数字8和9。
3.4 预处理器
C预处理器一直是可移植性问题的来源,许多预处理器构造的使用依赖于对预处理器内部工作原理的了解。
- 字符串替换 :在预处理器宏中,宏参数在字符串内的展开方式有所不同。ANSI标准规定了正确的行为,并引入了新的语法:
#define PRINT(value) printf(#value " = %d\n", value)
- 字符常量 :预处理器标记在字符常量内不被替换。例如,在ANSI C中,以下宏需要修改:
// 原宏
#define CTRL(c) (037 & 'c')
// 修改后的宏
#define CTRL(c) (037 & c)
调用时使用
CTRL('L')
。
-
标记粘贴
:一些预处理器允许“标记粘贴”,但这一行为未被文档记录。ANSI C标准定义了新的语法:
#define glue(a, b) a ## b
-
#elif指令 :ANSI C预处理器现在提供#elif指令,可与#ifdef和#endif结合使用。 -
#error指令 :ANSI C预处理器提供#error指令,用于打印作为参数给出的错误消息并退出,例如:
#if defined(BSD)
... BSD stuff ...
#elif defined(SYSV)
... System V stuff ...
#else
#error "One of BSD or SYSV must be defined."
#endif
综上所述,在UNIX编程中,我们需要了解正则表达式的不同实现以确保可移植性,掌握国际化编程的基本函数来适应不同地区的需求,同时要注意ANSI C标准的变化,以编写更规范、可移植的代码。
UNIX编程中的实用技巧与ANSI C标准变革
4. 正则表达式与国际化编程的应用案例
在实际的UNIX编程中,正则表达式和国际化编程的知识有着广泛的应用场景。下面我们通过具体的案例来展示如何运用前面介绍的知识。
4.1 正则表达式在文本搜索中的应用
假设我们有一个文本文件,需要从中找出所有以字母“B”开头且长度为5的单词。我们可以使用正则表达式来实现这个需求。以下是一个使用基于System V系统的
regexp
函数的简单示例:
% regexp '^B....$' /your/path/to/textfile
如果要在基于BSD的系统上实现相同的功能,可以使用
re_comp
和
re_exec
函数编写一个C程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>
int main() {
FILE *file;
char line[1024];
regex_t regex;
int reti;
// 编译正则表达式
reti = regcomp(®ex, "^B....$", 0);
if (reti) {
fprintf(stderr, "Could not compile regex\n");
return 1;
}
// 打开文件
file = fopen("/your/path/to/textfile", "r");
if (file == NULL) {
perror("Failed to open file");
regfree(®ex);
return 1;
}
// 逐行读取文件并匹配
while (fgets(line, sizeof(line), file)) {
line[strcspn(line, "\n")] = 0; // 去除换行符
reti = regexec(®ex, line, 0, NULL, 0);
if (!reti) {
printf("%s\n", line);
}
}
// 关闭文件和释放正则表达式资源
fclose(file);
regfree(®ex);
return 0;
}
这个程序的执行流程如下:
1. 编译正则表达式
^B....$
。
2. 打开指定的文本文件。
3. 逐行读取文件内容。
4. 对每一行内容使用
regexec
函数进行匹配。
5. 如果匹配成功,则打印该行内容。
6. 关闭文件并释放正则表达式资源。
4.2 国际化编程在财务软件中的应用
考虑一个简单的财务软件,需要根据不同的地区设置来格式化货币金额。以下是一个使用
setlocale
和
localeconv
函数的示例:
#include <stdio.h>
#include <locale.h>
int main() {
double amount = 12345.67;
// 设置区域为美国
setlocale(LC_ALL, "en_US.UTF-8");
struct lconv *lc = localeconv();
printf("In US locale:\n");
printf("Currency symbol: %s\n", lc->currency_symbol);
printf("Decimal point: %s\n", lc->decimal_point);
printf("Thousands separator: %s\n", lc->thousands_sep);
printf("Formatted amount: %s%.2f\n", lc->currency_symbol, amount);
// 设置区域为德国
setlocale(LC_ALL, "de_DE.UTF-8");
lc = localeconv();
printf("\nIn German locale:\n");
printf("Currency symbol: %s\n", lc->currency_symbol);
printf("Decimal point: %s\n", lc->decimal_point);
printf("Thousands separator: %s\n", lc->thousands_sep);
printf("Formatted amount: %s%.2f\n", lc->currency_symbol, amount);
return 0;
}
这个程序的执行步骤如下:
1. 定义一个货币金额
amount
。
2. 设置区域为美国,调用
localeconv
函数获取当前区域的格式化信息,并打印相关信息和格式化后的金额。
3. 设置区域为德国,再次调用
localeconv
函数获取新的格式化信息,并打印相关信息和格式化后的金额。
5. 总结与展望
在现代的UNIX编程中,正则表达式和国际化编程是非常重要的技能。正则表达式可以帮助我们高效地处理文本数据,而国际化编程则使我们的程序能够在不同的地区和文化环境中正常运行。
ANSI C标准的变革为C语言的发展和应用提供了更规范和统一的标准,使得代码的可移植性和可读性得到了显著提升。然而,我们也需要注意到,不同的UNIX系统和编译器可能对这些标准的支持程度存在差异,因此在实际编程中需要进行充分的测试和验证。
未来,随着计算机技术的不断发展和全球化的推进,正则表达式和国际化编程的需求将会越来越广泛。我们可以期待更多的工具和库的出现,来简化这些复杂的编程任务。同时,对于C语言标准的进一步完善和扩展也将为程序员带来更多的便利和可能性。
总之,掌握正则表达式、国际化编程和ANSI C标准的相关知识,将有助于我们编写更加高效、健壮和可移植的UNIX程序。希望本文介绍的内容能够对读者在实际的编程工作中有所帮助。
超级会员免费看
4

被折叠的 条评论
为什么被折叠?



