这里说的类型长度指的是相同类型在不同环境下长度不一致的情况,下面总结表格:
由于这里出现了 32 位和 64 位环境下长度不一致的情况,C 语言特意提供了 stdint.h 头文件 (C++ 中在 cstddef 中引用),定义了定长类型,例如 int64_t 在 32 位环境下其实是 long long,而在 64 位环境下其实是 long。
但这里的问题点在于:
并没有定长格式符
例如 uint64_t 在 32 位环境下对应的格式符是 % llu,但是在 64 位环境下对应的格式符是 % lu。有一种折中的解决办法是自定义一个宏:
- #if(sizeof(void*) == 8)
- #define u64 "%lu"
- #else
- #define u64 "%llu"
- #endif
-
- void demo() {
- uint64_t a;
- printf("a="u64, a);
- }
但这样会让字符串字面量从中间断开,非常不直观。
类型不一致
例如在 64 位环境下,long 和 long long 都是 64 位长,但编译器会识别为不同类型,在一些类型推导的场景会出现和预期不一致的情况,例如:
- template <typename T>
- void func(T t) {}
-
- template <>
- void func<int64_t>(int64_t t) {}
-
- void demo() {
- long long a;
- func(a); // 会匹配通用模板,而匹配不到特例
- }
上述例子表明,func
格式化字符串算是非常经典的 C 的产物,不仅是 C++,非常多的语言都是支持这种格式符的,例如 java、Go、python 等等。
但 C++ 中的格式化字符串可以说完全就是 C 的那一套,根本没有任何扩展。换句话说,除了基本数据类型和 0 结尾的字符串以外,其他任何类型都没有用于匹配的格式符。
例如,对于结构体类型、数组、元组类型等等,都没法匹配到格式符:
- struct Point {
- double x, y;
- };
-
- void Demo() {
- // 打印Point
- Point p {1, 2.5};
- printf("(%lf,%lf)", p.x, p.y); // 无法直接打印p
- // 打印数组
- int arr[] = {1, 2, 3};
- for (int i = 0; i < 3; i++) {
- printf("%d, ", arr[i]); // 无法直接打印整个数组
- }
- // 打印元组
- std::tuple tu(1, 2.5, "abc");
- printf("(%d,%lf,%s)", std::get<0>(tu), std::get<1>(tu), std::get<2>(tu)); // 无法直接打印整个元组
- }
对于这些组合类型,我们就不得不手动去访问内部成员,或者用循环访问,非常不方便。
针对于字符串,还会有一个严重的潜在问题,比如:
- std::string str = "abc";
- str.push_back('\0');
- str.append("abc");
-
- char buf[32];
- sprintf(buf, "str=%s", str.c_str());
由于 str 中出现了 '\0',如果用 % s 格式符来匹配的话,会在 0 的位置截断,也就是说 buf 其实只获取到了 str 中的第一个 abc,第二个 abc 就被丢失了。