【问题标题】:How to print special characters explicitly in C?如何在 C 中显式打印特殊字符?
【发布时间】:2015-04-06 18:35:57
【问题描述】:

当我使用下面的代码时:

#include <stdio.h>

int main(void)
{
    printf("%s","Hello world\nHello world");
    return 0;
}

它打印为:

 Hello world
 Hello world

如何防止这种情况并将其打印为 C 中的原始字符串文字?我的意思是它应该在终端窗口中显示,如下所示:

Hello world\nHello world

我知道我可以通过对 printf 使用反斜杠来实现这​​一点,但有没有其他 C 函数或方法可以在不使用反斜杠的情况下做到这一点?在读取文件时会很有帮助。

【问题讨论】:

  • 如果您读入包含反斜杠的文件,您可以使用printf 打印它们,而无需做任何不同的事情。你试过了吗?在您的示例中,编译器正在解释 \n 并将其替换为换行符。如果您以其他方式填写字符串,例如将文件的一行读入字符串,则不会发生这种情况。
  • 谢谢。我需要将 \n 字符显示为原始字符。它默认不可见并在终端窗口中创建一个新行。
  • 哦,您希望字符串中的换行符显示为\n?因此,如果您读取包含换行符的文件,它将替换为 \n?你可以写一个函数来做到这一点。
  • 哦,C语言中没有标准函数吗?我应该通过函数的反斜杠替换所有转义字符吗?听起来有点尴尬。

标签: c printf


【解决方案1】:

没有内置机制可以做到这一点。您必须逐个字符地手动完成。但是,ctype.h 中的函数可能会有所帮助。具体来说,在“C”语言环境中,函数isprint保证对基本执行字符集中的所有图形字符都为真,这实际上与所有图形字符相同7 位 ASCII,加空格;并且保证 not 对于 7 位 ASCII 中的所有 control 字符都为 true,包括制表符、回车符等。

这是一个草图:

#include <stdio.h>
#include <ctype.h>
#include <locale.h>

int main(void)
{
    int x;
    setlocale(LC_ALL, "C"); // (1)

    while ((x = getchar()) != EOF)
    {
        unsigned int c = (unsigned int)(unsigned char)x; // (2)

        if (isprint(c) && c != '\\')
            putchar(c);
        else
            printf("\\x%02x", c);
    }
    return 0;
}

这不会转义'",但会转义\,如果需要,可以直接扩展它。

为 U+000A 打印 \n,为 U+000D 打印 \r 等作为练习。处理基本执行字符集之外的字符(例如 U+0080 到 U+10FFFF 的 UTF-8 编码)也留作练习。

这个程序包含两个完全符合标准的 C 库不需要的东西,但根据我的经验,在实际操作系统上是必需的。它们标有(1)(2)

1) 这会显式设置“区域设置”配置,使其默认设置为应该

2) 从getchar 返回的值是int假定unsigned char(通常包括0-255)可表示的范围内的数字,或者是特殊值EOF(在不是 unsigned char 表示的范围。但是,众所周知,有缺陷的 C 库会为设置了最高位的字符返回负数。如果发生这种情况,printf 将在应该打印 \xa1 时打印(例如)\xffffffa1。将 x 转换为 unsigned char 然后再转换回 unsigned int 可以更正此问题。

【讨论】:

  • 注意:不需要unsigned int c = (unsigned int)(unsigned char)x;,只需使用xgetchar() 返回 unsigned char 范围或 EOF 中的值。 x 是零扩展的。
  • @chux 就标准而言,您是正确的,但我个人至少遇到了两个不正确的 C 库。 很久以前;也许不再需要额外的防御。
  • 早期战斗的伤痕也覆盖了我的指尖。
  • 感谢@zvol 的解释。但我不明白零扩展背后的目的是什么。为什么 x 类型先转换为 char 再转换为 int 而 x 已经是 char。如果代码更简单,那将对我有所帮助,因为我是 C 新手。
  • @Jessie 不幸的是,该行在实践中是必要的(尽管如上所述,原则上不是)。我已经添加了一些解释。
【解决方案2】:

这样的东西可能就是你想要的。运行 myprint(c) 以打印字符 C 或其可打印表示:

#include <ctype.h>

void myprint(int c)
{
    if (isprint(c))
        putchar(c); // just print printable characters
    else if (c == '\n')
        printf("\\n"); // display newline as \n
    else
        printf("%02x", c); // print everything else as a number
}

如果您使用的是 Windows,我认为您的所有换行符都将是 CRLF(回车,换行),因此它们将按照我编写该函数的方式打印为 0d\n

【讨论】:

    【解决方案3】:

    感谢用户 @chunk 为改进这个答案做出了贡献。


    您为什么不编写通用解决方案?它会让你在未来避免很多问题。

    char *
    str_escape(char str[])
    {
        char chr[3];
        char *buffer = malloc(sizeof(char));
        unsigned int len = 1, blk_size;
    
        while (*str != '\0') {
            blk_size = 2;
            switch (*str) {
                case '\n':
                    strcpy(chr, "\\n");
                    break;
                case '\t':
                    strcpy(chr, "\\t");
                    break;
                case '\v':
                    strcpy(chr, "\\v");
                    break;
                case '\f':
                    strcpy(chr, "\\f");
                    break;
                case '\a':
                    strcpy(chr, "\\a");
                    break;
                case '\b':
                    strcpy(chr, "\\b");
                    break;
                case '\r':
                    strcpy(chr, "\\r");
                    break;
                default:
                    sprintf(chr, "%c", *str);
                    blk_size = 1;
                    break;
            }
            len += blk_size;
            buffer = realloc(buffer, len * sizeof(char));
            strcat(buffer, chr);
            ++str;
        }
        return buffer;
    }
    

    它是如何工作的!

    int
    main(const int argc, const char *argv[])
    {
        puts(str_escape("\tAnbms\n"));
        puts(str_escape("\tA\v\fZ\a"));
        puts(str_escape("txt \t\n\r\f\a\v 1 \t\n\r\f\a\v tt"));
        puts(str_escape("dhsjdsdjhs hjd hjds "));
        puts(str_escape(""));
        puts(str_escape("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\f\a\v"));
        puts(str_escape("\x0b\x0c\t\n\r\f\a\v"));
        puts(str_escape("\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14"));
    }
    

    输出

    \tAnbms\n
    \tA\v\fZ\a
    txt \t\n\r\f\a\v 1 \t\n\r\f\a\v tt
    dhsjdsdjhs hjd hjds 
    
    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ \t\n\r\f\a\v
    \v\f\t\n\r\f\a\v
    \a\b\t\n\v\f\r
    

    此解决方案基于来自 Wikipedia https://en.wikipedia.org/wiki/Escape_sequences_in_C#Table_of_escape_sequences 的信息 以及 stackoverflow.com 的其他用户的答案。


    测试环境

    $ lsb_release -a
    No LSB modules are available.
    Distributor ID: Debian
    Description:    Debian GNU/Linux 8.6 (jessie)
    Release:    8.6
    Codename:   jessie
    $ uname -a
    Linux localhost 3.16.0-4-amd64 #1 SMP Debian 3.16.36-1+deb8u2 (2016-10-19) x86_64 GNU/Linux
    $ gcc --version
    gcc (Debian 4.9.2-10) 4.9.2
    Copyright (C) 2014 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    

    【讨论】:

    • 1) 对于 general 解决方案,答案还应显示它如何处理在 0-127 范围之外打印char。 2) strcat(buffer, chr); 是 UB,因为 buffer[] 未初始化。
    • 亲爱的@chux,为什么您需要回答“它如何处理打印 0-127 范围之外的字符?”为什么“buffer[] 没有初始化?” - 我没有错误,也没有警告。我用于在 GNU/Linux 上编译 GCC(我更新了答案)。
    • 1) 第一次调用strcat(buffer, chr);buffer[0] 的值是多少?代码没有在任何地方设置它,所以它是未初始化的。 2)分配的空间太小了1。
    • @chux,也许你是对的。每个字符串总是包含最后一个字符'\0'。我更新了答案并将您添加为内容顶部的贡献者。
    • "每个字符串总是包含最后一个字符'\0'",但buffer不一定是字符串。在调用strcat(buffer, chr) 之前,代码从不将'\0' 放入buffer[],从而导致未定义的行为(UB)。只需在char *buffer = malloc(sizeof(char)); 之后添加buffer[0] = '\0';
    【解决方案4】:

    你要找的是这个:

    #include <stdio.h>
    int main(void)
    {
        printf("%s","Hello world\\nHello world");
        return 0;
    }
    

    这将产生以下输出: 你好世界\n你好世界

    【讨论】:

    • 我知道我可以通过反斜杠来做到这一点。如果可能的话,我需要一种不使用反斜杠的方法。
    • 没有别的办法。
    • 我同意@IvoValchev。在有机会解释之前不要投反对票。
    • @GRC 还有另一种方法。看看yellowantphil或zwol给出的答案。
    • 两者的作用几乎相同,它们将 \n 替换为 \\n,不是在字符串中,而是在输出中。
    【解决方案5】:

    如果我理解这个问题,如果您有一个包含换行符、制表符、退格等控制字符的字符串,您希望打印这些字符的文本表示,而不是将它们解释为控制字符。

    不幸的是,没有内置的printf 转换说明符可以为您完成此操作。您必须逐个字符地遍历字符串,测试每个字符以查看它是否是控制字符,并为它编写一些等效的文本。

    这是一个简单的测试示例:

    #include <stdio.h>
    #include <limits.h>
    #include <ctype.h>
    ...
    char *src="This\nis\ta\btest";
    
    char *lut[CHAR_MAX] = {0};  // look up table for printable equivalents
                                // of non-printable characters
    lut['\n'] = "\\n";
    lut['\t'] = "\\t";
    lut['\b'] = "\\b";
    ...
    for ( char *p = src; *p != 0; p++ )
    {
      if ( isprint( *p ) )
        putchar( *p );
      else
        fputs( lut[ (int) *p], stdout ); // puts adds a newline at the end,
                                         // fputs does not.
    }
    putchar( '\n' );
    

    【讨论】:

    • 如果char默认签名,如果src包含任何非ASCII字符,上述代码将失败。演员(int) *p 没有解决这个问题!您应该使用UCHAR_MAX 来表示lut 的大小,并在isprint((unsigned char)*p)lut[(unsigned char)*p] 中转换为(unsigned char)
    • 在典型的实现中,isprint(CHAR_MAX) --> 0 导致lut[ (int) CHAR_MAX] 越界访问。 char *lut[CHAR_MAX] 肯定差 1。我希望 char *lut[CHAR_MAX+1] (并以某种方式处理负值)甚至更好 char *lut[UCHAR_MAX+1] 并使用 unsigned char
    【解决方案6】:

    只需使用 putchar(specialCharName)。它显示输入的特殊字符。

    【讨论】:

      【解决方案7】:
        /// My experience Win 10 Code blocks GCC MinGW
      
      #include <stdio.h>
      #include <stdlib.h>
      #include <time.h>
      #include <unistd.h>
      #include <windows.h>
      ///#include <threads.h>
      #include <conio.h>
      /// #include <dos.h>
      #include <direct.h>
      
      int main(void)
      
      {
        /// This will give your desired result, turn string into Raw string :
        printf(R"(Hello world\nHello world)");
        printf(R"(Raw string support printing  *&^%$#@!~()_+-=,<.>/?:;"' )");
        printf("\n");
        printf(R"(.C with a Capital C file format does not support raw string )");
        printf("\n");
        printf(R"(.c with a small c file format does support raw string )");
        printf("\n");
        printf(R"( Raw string did not support \n new line )");
        printf("\n");
      
        printf(
            R"(More reading material at - https: // en.wikipedia.org/wiki/String_literal#Raw_strings;)");
        printf("\n");
        printf(
            R"(More reading material at - https: // en.wikipedia.org/wiki/String_literal;)");
        printf("\n");
        printf(
            R"(More reading material at - https://stackoverflow.com/questions/24850244/does-c-support-raw-string-literals;)");
        printf("\n");
        printf(
            R"(More reading material at - https: // docs.microsoft.com/en-us/cpp/c-language/c-string-literals?view=vs-2019)");
        printf("\n");
        printf(
            R"(More reading material at-https: // docs.microsoft.com/en-us/cpp/c-language/string-literal-concatenation?view=vs-2019)");
        printf("\n");
        /// Raw string.
      
          printf(R"(More reading material at - https://www.geeksforgeeks.org/const-qualifier-in-c/;)");
        printf("\n");
        
        
        return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-12
        • 1970-01-01
        • 2020-08-23
        • 1970-01-01
        • 1970-01-01
        • 2014-07-27
        • 1970-01-01
        相关资源
        最近更新 更多