43、UNIX编程中的实用技巧与ANSI C标准变革

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(&regex, "^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(&regex);
        return 1;
    }

    // 逐行读取文件并匹配
    while (fgets(line, sizeof(line), file)) {
        line[strcspn(line, "\n")] = 0; // 去除换行符
        reti = regexec(&regex, line, 0, NULL, 0);
        if (!reti) {
            printf("%s\n", line);
        }
    }

    // 关闭文件和释放正则表达式资源
    fclose(file);
    regfree(&regex);

    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程序。希望本文介绍的内容能够对读者在实际的编程工作中有所帮助。

考虑柔性负荷的综合能源系统低碳经济优化调度【考虑碳交易机制】(Matlab代码实现)内容概要:本文围绕“考虑柔性负荷的综合能源系统低碳经济优化调度”展开,重点研究在碳交易机制下如何实现综合能源系统的低碳化经济性协同优化。通过构建包含风电、光伏、储能、柔性负荷等多种能源形式的系统模型,结合碳交易成本能源调度成本,提出优化调度策略,以降低碳排放并提升系统运行经济性。文中采用Matlab进行仿真代码实现,验证了所提模型在平衡能源供需、平抑可再生能源波动、引导柔性负荷参调度等方面的有效性,为低碳能源系统的设计运行提供了技术支撑。; 适合人群:具备一定电力系统、能源系统背景,熟悉Matlab编程,从事能源优化、低碳调度、综合能源系统等相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①研究碳交易机制对综合能源系统调度决策的影响;②实现柔性负荷在削峰填谷、促进可再生能源消纳中的作用;③掌握基于Matlab的能源系统建模优化求解方法;④为实际综合能源项目提供低碳经济调度方案参考。; 阅读建议:建议读者结合Matlab代码深入理解模型构建求解过程,重点关注目标函数设计、约束条件设置及碳交易成本的量化方式,可进一步扩展至多能互补、需求响应等场景进行二次开发仿真验证。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值