如何从std::string获取可写的C缓冲区?

7

我正在尝试将我的代码从使用MFC的CString转换为使用std::string,用于Microsoft Windows平台。而且我很好奇一件事情。比如在下面的例子中:

CString MakeLowerString(LPCTSTR pStr)
{
    CString strLower = pStr ? pStr : L"";
    CharLower(strLower.GetBuffer());        //Use WinAPI
    strLower.ReleaseBuffer();

    return strLower;
}

我使用 strLower.GetBuffer() 获取可写缓冲区以传递给 CharLower API。但是在 std::string 中没有类似的方法。
我是否遗漏了什么?如果是这样,您将如何使用 std::string 覆盖上面的方法?

1
不需要。如果你需要修改字符串,就直接修改字符串。只有当你将字符串传递给一个接受常量字符指针的旧函数时,才需要使用“C缓冲区”。 - Some programmer dude
1
@JoachimPileborg:好的,让我们假装CharLowerAPI不是CharLower,而是一些将修改其输入缓冲区但我需要从std::string中获取的任意API。我该怎么做?这就是我的问题。 - c00000fd
我告诉你,你不需要原始缓冲区,你需要的一切已经在字符串类或标准库中了。例如,可以访问http://en.cppreference.com/w/cpp并浏览一段时间。 - Some programmer dude
@JoachimPileborg 所以你的意思是,无论如何都应该从头开始编写代码,而不是更新旧代码...? - user253751
1
我的意思是,几乎所有修改std::string对象所需的代码都已经存在,无论是在std::string类本身中还是通过使用标准库中的其他函数。你不必从头开始重写或启动任何东西,因为代码已经存在供你使用。大小写转换、修改子字符串、追加、前置、插入,所有这些功能都已经存在。 - Some programmer dude
显示剩余3条评论
4个回答

6

在我的新工作中,我们不使用MFC,但幸运的是我们使用标准库和C++11。因此,我遇到了与c00000fd相同的问题。感谢BitTickler的回答,我想到了通过&s[0]&s.front()来使用字符串的内部缓冲区进行Win32-API。

使用一个收缩内部字符串缓冲区的 Win32-API 函数

假设你有一个字符串,希望通过Win32-API函数(例如::PathRemoveFileSpec(path))将其缩短,可以采用以下方法:

std::string path( R("?(C:\TESTING\toBeCutOff)?") );
::PathRemoveFileSpec( &path.front() ); // Using the Win32-API
                                       // and the the string's internal buffer
path.resize( strlen( path.data() ) );  // adjust the string's length 
                                       // to the first \0 character
path.shrink_to_fit();                  // optional to adjust the string's
                                       // capacity - useful if you
                                       // do not plan to modify the string again

Unicode版本:

std::wstring path( LR("?(C:\TESTING\toBeCutOff)?") );
::PathRemoveFileSpec( &path.front() ); // Using the Win32-API
                                       // and the the string's internal buffer
path.resize( wcslen( path.data() ) );  // adjust the string's length 
                                       // to the first \0 character
path.shrink_to_fit();                  // optional to adjust the string's
                                       // capacity - useful if you
                                       // do not plan to modify the string again

使用扩展内部字符串缓冲区的Win32-API函数

假设您有一个字符串需要在Win32-API函数中进行扩展或填充 - 例如使用::GetModuleFileName(NULL, path, cPath)函数来检索可执行文件的路径 - 您可以按照以下方式操作:

std::string path;
path.resize(MAX_PATH);                 // adjust the internal buffer's size
                                       // to the expected (max) size of the
                                       // output-buffer of the Win32-API function
::GetModuleFileName( NULL, &path.front(), static_cast<DWORD>( path.size() ) );
                                       // Using the Win32-API
                                       // and the the string's internal buffer
path.resize( strlen( path.data() ) );  // adjust the string's length 
                                       // to the first \0 character
path.shrink_to_fit();                  // optional to adjust the string's
                                       // capacity - useful if you
                                       // do not plan to modify the string again

Unicode版本:

std::wstring path;
path.resize(MAX_PATH);                 // adjust the internal buffer's size
                                       // to the expected (max) size of the
                                       // output-buffer of the Win32-API function
::GetModuleFileName( NULL, &path.front(), static_cast<DWORD>( path.size() ) );
                                       // Using the Win32-API
                                       // and the the string's internal buffer
path.resize( wcslen( path.data() ) );  // adjust the string's length 
                                       // to the first \0 character
path.shrink_to_fit();                  // optional to adjust the string's
                                       // capacity - useful if you
                                       // do not plan to modify the string again

当您最终缩小字符串时,与MFC替代方案相比,仅需要在扩展字符串的内部缓冲区时再添加一行代码;而在缩小字符串时,几乎有相同的开销。 std::string方法相对于CString方法的优势在于,您无需声明额外的C-String指针变量,只需使用官方的std::string方法和一个strlen/wcslen函数即可。
上面的方法仅适用于缩小变体并且所得到的 Win32-API 缓冲区是以空字符结尾的情况。但对于返回未终止字符串的非常特殊的情况,则必须 - 类似于CString :: ReleaseBuffer 方法 - 显式知道并指定新字符串 / 缓冲区长度,即path.resize(newLength) - 就像CString替代方案中的path.ReleaseBuffer(newLength)一样。

2
void GetString(char * s, size_t capacity)
{
    if (nullptr != s && capacity > 5)
    {
        strcpy_s(s,capacity, "Hello");
    }
}

void FooBar()
{
    std::string ss;
    ss.resize(6);
    GetString(&ss[0], ss.size());
    std::cout << "The message is:" << ss.c_str() << std::endl;
}

正如你所看到的,你可以使用“老派的C指针”将字符串输入到遗留函数中,也可以将其用作输出参数。当然,你需要确保字符串有足够的容量才能正常工作等。


这是否保证是可能的,还是你的标准库实现的一个怪癖? - user253751
是的,我本可以更好地编写容量/大小的部分。从函数GetString()的角度来看,调用方侧的std::string的大小就是其容量。;) - BitTickler
@immibis 是的,在 std::string 定义成员函数如“data()”之前,&s[0] 就已经存在了,并且一直以这种方式工作。 - BitTickler
在C和C++中,"@BitTickler,它一直以这种方式工作"是不够的。 - user253751
@BitTickler 我的程序如果我取消引用空指针,就会崩溃或返回垃圾。但这并不意味着你一定会得到崩溃或垃圾。 - user253751
显示剩余5条评论

1

1
谢谢。是的,我知道我可以得到一个指向字符串的const指针,但我不能修改它,对吗?否则它就不会是一个const了,对吧? - c00000fd
没错,你不能通过data()和c_str()返回的指针修改字符串。 - R Sahu
2
嗯,有趣。人们告诉我std::string比MFC的CString要好得多。但是在无法执行最简单的操作后,我不确定这是否真的如此。 - c00000fd
如果仅凭获取原始数据的可写指针来判断两者的优劣,那么std::string并不是很好。然而,如果您将要求提高到更高的水平,仍然无法像使用CString一样使用std::string,那么我们就谈论真正的问题了。请记住,您可以使用前两个函数访问原始数据的内容。 - R Sahu
2
你看,我正在修改现有的代码。因此,我在某种程度上受到已经编写好的限制。 - c00000fd

-1

如果要将仅包含ASCII字符的std::string转换为小写,可以使用以下代码:

#include <algorithm>
#include <string> 

std::string data = "Abc"; 
std::transform(data.begin(), data.end(), data.begin(), ::tolower);

你真的无法避免迭代每个字符。原始的Windows API调用也会在内部执行相同的字符迭代。

如果你需要针对多字节编码(例如UTF-8)或标准"C"语言环境以外的区域设置进行toLower()操作,可以使用以下方法:

std::string str = "Locale-specific string";
std::locale loc("en_US.UTF8");  // desired locale goes here
const ctype<char>& ct = use_facet<ctype<char> >(loc);
std::transform(str.begin(), str.end(), str.begin(), std::bind1st(std::mem_fun(&ctype<char>::tolower), &ct));

直接回答你的问题,不考虑任何上下文,你可以调用 str.c_str() 来从一个 std::string 获取一个 const char * (LPCSTR)。你不能直接将一个 std::string 转换为 char * (LPTSTR);这是有意设计的,否则会削弱使用 std::string 的一些动机。

2
请不要跑题。我并不是在询问如何转换为小写字母。这只是我举的一个快速的例子。我想要了解的是如何从std::string获取可写缓冲区。 - c00000fd
@c00000fd 我相信这只是一个例子,就像你问题中的代码一样。字符串类和标准库真的拥有你需要的一切。 - Some programmer dude
是的,我可以得到 const char *,但如何从中获取 char *?我需要自己分配并将字符串复制到那里吗?这不会浪费 CPU 周期吗? - c00000fd
@c00000fd 只有常量缓冲区允许包含在 std::string 中的数据。修改此缓冲区将导致 std::string 的未定义行为。所有这些都是按设计而来的。如果 std::string 的特性对您不重要,并且限制过于严格,那么您就不应该使用 std::string - Special Sauce
1
使用 std::transform 像你第一个示例那样是错误的,因为 ::tolower 接收的不是 char 而是 int,而且一个 char 必须 先被转换为 unsigned char。使用 C++ 区域设置的版本应该是正确的。 - Ulrich Eckhardt
显示剩余5条评论

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