在 C++ 中处理字符串时如何使用 memset?

How to use memset while handling strings in C++?

我来自 Python 背景,最近正在学习 C++。我正在学习一个名为 memset 的 C/C++ 函数,并遵循网站 https://www.geeksforgeeks.org/memset-in-cpp/ 的在线示例,在那里我遇到了一些编译错误:

/**
 * @author      : Bhishan Poudel
 * @file        : a02_memset_geeks.cpp
 * @created     : Wednesday Jun 05, 2019 11:07:03 EDT
 * 
 * Ref: 
 */

#include <iostream>
#include <vector>
#include <cstring>

using namespace std;

int main(int argc, char *argv[]){
    char str[] = "geeksforgeeks";

    //memset(str, "t", sizeof(str));
    memset(str, 't', sizeof(str));

    cout << str << endl;

    return 0;
}

使用单引号时出错't'
这会打印额外的字符。

tttttttttttttt!R@`

使用带双引号的 "t" 时出错

$ g++ -std=c++11 a02_memset_geeks.cpp 
a02_memset_geeks.cpp:17:5: error: no matching function for call to 'memset'
    memset(str, "t", sizeof(str));
    ^~~~~~
/usr/include/string.h:74:7: note: candidate function not viable: no known
      conversion from 'const char [2]' to 'int' for 2nd argument
void    *memset(void *, int, size_t);
         ^
1 error generated.

如何在 C++ 中使用 memset?

进一步研究
这里给出了具有 memset 缺点的优秀教程: https://web.archive.org/web/20170702122030/https:/augias.org/paercebal/tech_doc/doc.en/cp.memset_is_evil.html

Error when using single quotes 't' This prints extra characters.

那是因为你覆盖了空终止符。

终止符是数组大小的一部分(数组不是魔法),尽管 it's not part of the logical string size.

所以,我想你的意思是:

memset(str, 't', strlen(str));
//               ^^^^^^

Error when using "t" with double quotes

完全不同的东西。您告诉计算机将字符串中的每个字符设置为一个字符串。没有意义;不会编译。


How to use memset in C++?

不要。

或者使用 type-safe std::fill,结合 std::beginstd::end:

std::fill(std::begin(str), std::end(str)-1, 't');

(如果您担心性能,请不要担心:这将在可能的情况下通过模板专业化委托给 memset,不需要优化,而不会牺牲 type-safety; example here in libstdc++.)

或者只是一个 std::string 开头。


I was learning the fuction memset in C++ from https://www.geeksforgeeks.org/memset-in-cpp/ where the example is given as below

不要试图从随机网站学习 C++。取而代之的是 a good book

这个声明

char str[] = "geeksforgeeks";

声明一个字符数组,其中包含一个字符串,该字符串是一个字符序列,包括终止零符号 '[=26=]'

你可以想象下面的等价声明方式

char str[] = 
{ 
    'g', 'e', 'e', 'k', 's', 'f', 'o', 'r', 'g', 'e', 'e', 'k', 's', '[=11=]'
};

本次函数的调用memset

memset(str, 't', sizeof(str));

覆盖数组的所有字符,包括终止零。

所以接下来的语句

cout << str << endl;

导致未定义的行为,因为它输出字符直到遇到终止零。

你可以改写

#include <iostream>
#include <cstring>

int main()
{
    char str[] = "geeksforgeeks";

    std::memset( str, 't', sizeof( str ) - 1 );
    
    std::cout << str << '\n';
}

或以下方式

#include <iostream>
#include <cstring>

int main()
{
    char str[] = "geeksforgeeks";

    std::memset( str, 't', std::strlen( str ) );
    
    std::cout << str << '\n';
}

也就是保持数组中的终止零不变。

如果你想覆盖数组中的所有字符,包括终止零,那么你应该替换这个语句

std::cout << str << '\n';

对于此声明

std::cout.write( str, sizeof( str ) ) << '\n';

如下面的程序所示,因为数组现在不包含字符串。

#include <iostream>
#include <cstring>

int main()
{
    char str[] = "geeksforgeeks";

    std::memset( str, 't', sizeof( str ) );
    
    std::cout.write( str, sizeof( str ) ) << '\n';
}

至于这次通话

memset(str, "t", sizeof(str));

则第二个参数的类型(即类型const char *)与类型为int的第二个函数参数的类型不对应。查看函数的声明

void * memset ( void * ptr, int value, size_t num );

因此编译器发出错误消息。

除了字符数组(甚至在 C++ 中也经常使用)之外,您还可以使用模拟字符串的标准 class std::string(或 std::basic_string)。

在这种情况下,无需使用标准 C 函数 memset 来用单个字符填充字符串。最简单的方法如下

#include <iostream>
#include <string>

int main()
{
    std::string s( "geeksforgeeks" );
    
    s.assign( s.length(), 't' );
    
    std::cout << s << '\n';
}

另一种方法是使用在header <algorithm> 中声明的标准算法std::fillstd::fill_n。例如

#include <iostream>
#include <string>
#include <iterator>
#include <algorithm>

int main()
{
    std::string s( "geeksforgeeks" );
    
    std::fill( std::begin( s ), std::end( s ), 't' );
    
    std::cout << s << '\n';
}

#include <iostream>
#include <string>
#include <iterator>
#include <algorithm>

int main()
{
    std::string s( "geeksforgeeks" );
    
    std::fill_n( std::begin( s ), s.length(), 't' );
    
    std::cout << s << '\n';
}

您甚至可以使用 class std::string 的方法 replace 以下方法之一

#include <iostream>
#include <string>

int main()
{
    std::string s( "geeksforgeeks" );
    
    s.replace( 0, s.length(), s.length(), 't' );
    
    std::cout << s << '\n';
}

或者

#include <iostream>
#include <string>

int main()
{
    std::string s( "geeksforgeeks" );
    
    s.replace( std::begin( s ), std::end( s ), s.length(), 't' );
    
    std::cout << s << '\n';
}

这是 memset 的正确语法...

void* memset( void* dest, int ch, std::size_t count );

Converts the value ch to unsigned char and copies it into each of the first count characters of the object pointed to by dest. If the object is a potentially-overlapping subobject or is not TriviallyCopyable (e.g., scalar, C-compatible struct, or an array of trivially copyable type), the behaviour is undefined. If count is greater than the size of the object pointed to by dest, the behaviour is undefined.

(source)

对于第一个语法 memset(str, 't', sizeof(str));。编译器因为额外的大小而抱怨。它打印 18 次 tttttttttttttt!R@。我建议尝试使用 sizeof(str) -1 作为字符数组。

对于第二种语法 memset(str, "t", sizeof(str));,您提供的第二个参数是一个字符串。这就是编译器报错的原因:从‘const char*’到‘int’的无效转换

Vlad 对您问题的第一部分做出了有益的回答,但我觉得第二部分可以更直观地解释一下:

正如其他人提到的,'t' 是一个 字符 "t" 是一个 字符串 ,并且字符串最后有一个空终止符。这使得 "t" 不是一个数组,而是 两个 个字符 - ['t', '[=13=]']!这使得 memset 的错误更直观 - 它可以很容易地将单个 char 强制转换为 int,但是当它被赋予一个 char 数组时它会窒息。就像在 Python 中一样,int(['t', '[=17=]'])(或 ord(['t', '[=18=]']))不计算。