【问题标题】:Use signed or unsigned char in constructing CString?在构造 CString 时使用有符号或无符号字符?
【发布时间】:2018-11-01 23:07:20
【问题描述】:

我正在检查 document for CString 。在以下声明中:

  • CStringT( LPCSTR lpsz ):从 ANSI 字符串构造 Unicode CStringT。您还可以使用此构造函数来加载字符串资源,如下例所示。

  • CStringT( LPCWSTR lpsz ):从 Unicode 字符串构造 CStringT

  • CStringT( const unsigned char* psz ):允许您从指向unsigned char 的指针构造CStringT

我有一些问题:

  1. 为什么会有两个版本,一个用于const char* (LPCSTR),一个用于unsigned char*?对于不同的情况,我应该使用哪个版本?例如,CStringT("Hello") 是使用第一版还是第二版?当从第三方获取以空结尾的字符串时,例如sqlite3_column_text() (see here),我应该将其转换为char* 还是unsigned char *?即,我应该使用CString((LPCSTR)sqlite3_column_text(...)) 还是CString(sqlite3_column_text(...))?似乎两者都可以,对吗?

  2. 为什么char* 版本会构造一个“Unicode”CStringTunsigned char* 版本会构造一个CStringTCStringT是一个模板类,表示所有3个实例,即CStringCStringACStringW,那么为什么在使用LPCSTRconst char*)构造时强调“Unicode”CStringT

【问题讨论】:

  • @ThomasMatthews CStringT 是一个类,C 中不存在类,所以这绝对是 C++ 独有的

标签: c++ string mfc


【解决方案1】:

LPCSTR 只是 const char*,而不是 const signed char*char 是有符号还是无符号取决于编译器实现,但 charsigned charunsigned char 是用于重载的 3 种不同类型。 C++ 中的字符串字面量是const char[] 类型,所以CStringT("Hello") 将始终使用LPCSTR 构造函数,而不是unsigned char* 构造函数。

sqlite3_column_text(...) 返回 unsigned char*,因为它返回 UTF-8 编码的文本。我不知道CStringTunsigned char* 构造函数实际上做了什么(它与MBCS 字符串有关),但LPCSTR 构造函数使用用户的默认语言环境执行从ANSI 到UNICODE 的转换。这会破坏包含非 ASCII 字符的 UTF-8 文本。

在这种情况下,您最好的选择是将 UTF-8 文本转换为 UTF-16(使用 MultiByteToWideChar() 或等效项,或者直接使用 sqlite3_column_text16(),它会返回 UTF-16 编码文本),然后使用CStringTLPCWSTR (const wchar_t*) 构造函数,因为 Windows 使用 wchar_t 处理 UTF-16 数据。

【讨论】:

    【解决方案2】:

    tl;dr:使用以下任意一种:

    • CStringW value( sqlite3_column_text16() );(可选地将 SQLite 的内部编码设置为 UTF-16),或者
    • CStringW value( CA2WEX( sqlite3_column_text(), CP_UTF8 ) );

    其他的一切都不管用,不管怎样。


    第一件事:CStringT 是一个类模板,参数化(除其他外)它用于表示存储序列的字符类型。这是作为BaseType 模板类型参数传递的。有 2 个具体的模板实例化,CStringACStringW,分别使用 charwchar_t 来存储字符序列1

    CStringT 公开了以下描述模板实例化属性的predefined types

    • XCHAR:用于存储序列的字符类型。
    • YCHAR: 实例可以相互转换的字符类型。

    下表显示了CStringACStringW的具体类型:

             | XCHAR   | YCHAR
    ---------+---------+--------
    CStringA | char    | wchar_t
    CStringW | wchar_t | char
    

    虽然CStringT 实例化的存储对所使用的字符编码没有任何限制,但转换 c'tors 和运算符是基于以下假设实现的:

    • char 代表 ANSI2 编码的代码单元。
    • whcar_t 代表 UTF-16 编码的代码单元。

    如果您的程序不符合这些假设,强烈建议禁用隐式的宽到窄和窄到宽的转换。为此,请在包含任何 ATL/MFC 头文件之前定义 _CSTRING_DISABLE_NARROW_WIDE_CONVERSION 预处理器符号。建议您这样做,即使您的程序满足了防止意外转换的假设,这种转换既昂贵又具有潜在的破坏性。

    说完这些,让我们继续提问:

    为什么会有两个版本,一个用于const char* (LPCSTR),一个用于unsigned char*

    这很简单:方便。重载只允许您构造一个CString 实例,而不管字符类型3 的符号。采用const unsigned char* 参数的重载实现“转发”到采用const char* 的c'tor:

    CSTRING_EXPLICIT CStringT(_In_z_ const unsigned char* pszSrc) :
        CThisSimpleString( StringTraits::GetDefaultManager() )
    {
        *this = reinterpret_cast< const char* >( pszSrc );
    }
    

    我应该为不同的情况使用哪个版本?

    没关系,只要你构造一个CStringA,即不应用转换。如果您正在构建CStringW,则不应使用其中任何一个(如上所述)。

    例如CStringT("Hello")是使用第一版还是第二版?

    "Hello"const char[6] 类型,当传递给CString c'tor 时,它会衰减为数组中第一个元素的const char*。它使用const char* 参数调用重载。

    从第三方获取以空结尾的字符串时,例如sqlite3_column_text() (see here),我应该将其转换为char* 还是unsigned char *?即,我应该使用CString((LPCSTR)sqlite3_column_text(...)) 还是CString(sqlite3_column_text(...))

    SQLite 在这种情况下采用 UTF-8 编码。 CStringA 可以存储 UTF-8 编码的文本,但这样做真的,真的很危险。 CStringA 采用 ANSI 编码,您的代码的读者也可能会这样做。建议将您的 SQLite 数据库更改为存储 UTF-16(并使用sqlite_column_text16)来构造CStringW。如果这不可行,请在使用 CA2WEX 宏将数据存储到 CStringW 实例之前手动从 UTF-8 转换为 UTF-16:

    CStringW data( CA2WEX( sqlite3_column_text(), CP_UTF8 ) );
    

    似乎两者都可以,对吗?

    这是不正确的。一旦您从数据库中获取非 ASCII 字符,这两种方法都不起作用。

    为什么char* 版本会构造一个“Unicode”CStringTunsigned char* 版本会构造一个CStringT

    这看起来是文档试图紧凑的结果。 CStringT 是一个类模板。它既不是 Unicode,也不存在。我猜constructors 上的备注部分旨在强调从 ANSI 输入构造 Unicode 字符串的能力(反之亦然)。也简要提到了这一点(“请注意,其中一些构造函数充当转换函数。”)。

    总而言之,以下是使用 MFC/ATL 字符串时的通用建议列表:

    • 首选使用CStringW。这是唯一隐含字符编码明确 (UTF-16) 的字符串类型。
    • 仅在与旧代码交互时使用CStringA。确保明确注意使用的字符编码。还要确保了解“当前活动的语言环境”可以随时更改。请参阅Keep your eye on the code page: Is this string CP_ACP or UTF-8? 了解更多信息。
    • 从不使用CString。仅通过查看代码,就不再清楚这是什么类型(可能是两种类型中的任何一种)。同样,在查看构造函数调用时,不再可能看到这是复制还是转换操作。
    • 禁用 CStringT 类模板实例化的隐式转换。

    1还有CString 使用通用文本映射TCHAR 作为其BaseTypeTCHAR 扩展为 charwchar_t,具体取决于预处理器符号。因此,CStringCStringACStringW 的别名,具体取决于这些相同的预处理器符号。 除非您的目标是 Win9x,否则不要使用任何通用文本映射。

    2与 Unicode 编码不同,ANSI 不是自包含的表示。代码单元的解释取决于外部状态(当前活动的语言环境)。 除非您与旧代码交互,否则请勿使用。

    3由实现定义,char 被解释为有符号还是无符号。无论哪种方式,charunsigned charsigned char 都是 3 种不同的类型。默认情况下,Visual Studio 将 char 解释为已签名。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-08-10
      • 2010-09-30
      • 2019-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多