char* 转为 char[]

6

我有一个char*,它具有固定(已知)的宽度,但没有以空字符结尾。

我想将其传递给LOG4CPLUS_ERROR("Bad string " << char_pointer);,但由于它没有以空字符结尾,它会打印全部内容。

有什么轻量级方法可以获取"(char[length])*char_pointer"而不进行复制吗?


1
所有字符串都必须以空字符结尾。没有任何转换可以解决这个问题。您需要手动添加 '\0' 字符。 - Šimon Tóth
1
@Let_Me_Be:好吧,这不真实,是吗。真正的问题是没有从指向数组的指针进行有效转换。 - Lightness Races in Orbit
1
@Tomalak 除了 sizeof(),数组和指针在实际上是无法区分的。 - Šimon Tóth
1
@Tomalak(a)问题中不存在转换。(b)当然,它们是不兼容的,这就像说 int x[];float x[]; 是兼容的一样。但是 float **x;float *x[]; 兼容,并且 float x[a][b];float (*x)[a]; 兼容。(c) 数组可以隐式转换为第一个元素的指针,这不仅适用于函数调用。 - Šimon Tóth
1
在您的日志库中似乎不是这种情况,但有时实现流(如iostreams库)的库会实现一个可以轻松处理此情况的write(char*,streamsize)函数。 - Ken Bloom
显示剩余3条评论
7个回答

7
不,你需要进行深度复制并添加空终止符。该代码期望一个以空终止符结尾的字符串,也就是一个由字符组成的连续块,并以空终止符结束。

1
最简单的方法是使用const char *size_t构建一个std::string,除非您可以访问底层流对象。 - Nim
是的,我想即使是 char[] 也需要以 null 结尾。 - matt
@matt:要么你需要能够将长度传递给你调用的任何函数。 - Lightness Races in Orbit

5

如果你的目标是打印这样的字符串,你可以:

  1. 存储最后一个字节。
  2. 用 \0 替换它。
  3. 打印字符串。
  4. 打印存储的字节。
  5. 将存储的字节放回字符串的最后位置。

把所有这些包装在一个函数中。


2
我们被char*和char[]之间的转换语义所困扰。退一步,你想做什么?如果这只是在错误条件下将结构体内容流式输出到流中的简单情况,为什么不正确地执行呢?
例如:
struct foo
{
  char a1[10];
  char a2[10];
  char a3[10];
  char a4[10];
};

// free function to stream the above structure properly..
std::ostream operator<<(std::ostream& str, foo const& st)
{
  str << "foo::a1[";
  std::copy(st.a1, st.a1 + sizeof(st.a1), std::ostream_iterator<char>(str));
  str << "]\n";

  str << "foo::a2[";
  std::copy(st.a2, st.a2 + sizeof(st.a2), std::ostream_iterator<char>(str));
  str << "]\n";

  :

  return str;
}

现在您可以简单地流出foo的实例,不必担心空终止字符串等!

Matt在这里没有使用iostreams的优势。他有一个使用自己的类的日志记录库,该类不是从iostreams继承而来的,因此他必须编写自己的ostream_iterator。但是,考虑到您的解决方案使用iostreams,为什么不直接使用ostream.write(char*,streamsize) - Ken Bloom
@Ken,当然,那也很容易,但我想要采用完整的基于算法的方法! ;) 很有趣,肯定log4cpp的东西实现了一些从std :: ostream派生的对象形式? - Nim
看我现在写的答案。它很复杂。 - Ken Bloom

2

真实的 iostream

当你写入真正的 iostream 时,你可以直接使用 ostream.write(),它需要一个 char* 和一个要写入多少个字节的大小--不需要空终止符。(事实上,字符串中的任何空字符都将被写入到 ostream 中,并且不会用于确定大小。)

日志库

在一些日志库中,你写入的流并不是真正的 iostream。这在 Log4CPP 中就是这种情况。

然而,在 Log4CPlus 中,这是 matt 所使用的对象,它是一个 std::basic_ostringstream<tchar>(请参阅 loggingmacros.hstreams.h 的定义,因为从文档中无法明确了解)。只有一个问题:在宏 LOG4CPLUS_ERROR 中,第一个 << 已经内置到宏中,所以他将无法调用 LOG4CPLUS_ERROR(.write(char_pointer,length)) 或类似的东西。不幸的是,我没有看到任何简单的方法来避免这种情况,除非解构 LOG4CPLUS_ERROR 错误宏并进入 Log4CPlus 的内部。

解决方案

我不确定为什么你在这个时候尝试避免复制字符串,因为你可以看到日志库内部有很多复制操作。任何试图避免这种额外的字符串复制的尝试可能都是不必要的优化。

我假设这是代码清洁度的问题,也许是确保复制发生在 LOG4CPLUS_ERROR 宏内部,而不是外部的问题。在这种情况下,只需使用:

LOG4CPLUS_ERROR("Bad string " << std::string(char_pointer, length));

谢谢Ken,整洁度是我寻找的重要部分 :) - matt

1

我在我的工具箱中保留了一个字符串引用类,专门用于这些类型的情况。下面是该类的大大简化版本。我已经删除了与此特定问题无关的任何内容:

#include <iostream>

class stringref {
public:
    stringref(const char* ptr, unsigned len) : ptr(ptr), len(len) {}

    unsigned length() { return len; }
    const char* data() { return ptr; }

private:
    const char* ptr;
    unsigned len;
};

std::ostream& operator<< (std::ostream& os, stringref sr) {
    const char* data = sr.data();
    for (unsigned len = sr.length(); len--; )
        os << *data++;
    return os;
}

using namespace std;

int main (int argc, const char * argv[])
{
    cout << "string: " << stringref("test", 4) << endl;
}

或者,在你的情况下:

LOG4CPLUS_ERROR("Bad string " << stringref(char_pointer, length));

应该可以工作。

字符串引用类的想法是保留有关字符串的足够信息(大小和指针),以引用表示字符串的任何内存块。它依赖于您确保在字符串引用对象的整个生命周期内基础字符串数据有效。这样,您可以传递和处理字符串信息,而最小化开销。


1
马特没有iostreams的优势,但你有。为什么不直接使用ostream.write(char*,streamsize) - Ken Bloom
我没有使用过log4c++,但是示例的语法让我相信它在内部使用iostreams。LOG4CPLUS_ERROR("Bad string " << stringref(char_pointer, length))应该可以工作。 - Ferruccio
@Ferruccio:Log4cpp并没有使用iostreams,而是在其自己的类上重载了operator<<。我更仔细地查看了matt正在使用的Log4Cplus,从文档中无法确定它是否使用iostreams,但代码示例让我认为它实际上确实使用了。 - Ken Bloom
快速搜索log4c++源代码发现它在内部使用stringstream来缓冲输出。因此,您可以使用iostreams。但是,您仍然无法使用ostream::write(),因为您无法直接访问stringstream对象。该宏依赖于您使用operator<<来写入所有内容。 - Ferruccio
我没有意识到log4c++和log4cplus是两个不同的库。我的“log4c++”引用应该被理解为“log4cplus”。 - Ferruccio

0
当您知道其长度固定时:为什么不将数组的大小增加一个字符呢?然后,您可以轻松地使用\0终止字符填充这个最后一个字符,一切都会很好。

2
他如何在没有副本的情况下完成这个任务? - Lightness Races in Orbit
在分配内存时,只需使用(knownsize)+1,填充数组中的数据后,只需执行data [knownsize] ='\0'即可。 - Chris
如果他能做到这一点,他就可以让他的C字符串在第一时间正常工作。这个问题是关于绕过“无法做到这一点”的能力的。 - Lightness Races in Orbit
也许他不知道可以用这种方式简单地添加终止字符;无论如何,当他说数据的大小已知时,我不明白为什么他不能分配一个额外的字符。 - Chris
已知并不等同于可变的。 - Lightness Races in Orbit

0

不,你必须复制它。语言中没有适当的转换可以用来获取数组类型。

你想这样做似乎非常奇怪,或者你一开始就有一个非终止的C风格字符串。

为什么不使用std::string


即使你可以,也没有直接流出数组的支持(据我所知)(不使用std::copy)。最好的方法是构造一个std::string(),使用带有const char*size_t参数的变量,并将其流式传输... - Nim
1
char* 是指一个充满 char[] 的结构体内的 char[]。整个结构体作为一个记录,甚至包括 char[]。虽然对于我的位置来说不是理想的,但在其他地方很有用。 - matt
@matt:好的,答案不变。 :) 最好通过使用Nim提到的细节构建一个std::string来复制它。 - Lightness Races in Orbit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接