Как использовать memset в c++?

Я из Питона и недавно изучал C++. Я изучал функцию C/C++ под названием memset и следовал онлайн-примеру с веб-сайта 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'
Это печатает дополнительные символы.

[email protected]'

Ошибка при использовании "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.

Как использовать memset в C++?

Дальнейшее обучение
Отличный учебник с недостатками memset приведен здесь: https://web.archive.org/web/20170702122030/https: /augias.org/paercebal/tech_doc/doc.en/cp.memset_is_evil.html

Ответ 1

Эта декларация

char str[] = "geeksforgeeks";

объявляет массив символов, который содержит строку, представляющую собой последовательность символов, включая завершающий нулевой символ '\0'.

Вы можете представить декларацию следующим эквивалентным способом

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

Это вызов функции 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++) вы можете использовать также стандартный класс 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';
}

Другой способ - использовать стандартный алгоритм std::fill или std::fill_n объявленный в заголовке <algorithm>. Например

#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';
}

Вы даже можете использовать метод replace класса std::string одним из следующих способов

#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';
}

Ответ 2

Ошибка при использовании одинарных кавычек 't' Это печатает лишние символы.

Это потому, что вы переписали нулевой терминатор.

Терминатор является частью размера массива (массив не магический), хотя он не является частью размера логической строки.

Итак, я думаю, что вы имели в виду:

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

Ошибка при использовании "t" с двойными кавычками

Совершенно другая вещь. Вы сказали компьютеру установить каждый символ в строке, в строку. Не имеет смысла; не скомпилируется.


Как использовать memset в C++?

Не.

Либо используйте безопасные с точки std::fill типов std::fill в сочетании с std::begin и std::end:

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

(Если вы беспокоитесь о производительности, не беспокойтесь: это будет просто делегировать memset где это возможно, через специализацию шаблона, оптимизация не требуется, без ущерба для безопасности типов; пример здесь в libstd C++.)

Или просто std::string для начала. 😊


Я изучал fuset memset в C++ с https://www.geeksforgeeks.org/memset-in-cpp/, где пример приведен ниже.

Не пытайтесь изучать C++ со случайных сайтов. Вместо этого найдите себе хорошую книгу.

Ответ 3

Это правильный синтаксис для memset...

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

Преобразует значение ch в unsigned char и копирует его в каждый из первых символов подсчета объекта, на который указывает dest. Если объект является потенциально перекрывающимся подобъектом или не является TriviallyCopyable (например, скаляр, C-совместимая структура или массив тривиально копируемого типа), поведение не определено. Если число больше, чем размер объекта, на который указывает dest, поведение не определено.

(источник)

Для первого синтаксиса memset(str, 't', sizeof(str)); , Компилятор пожаловался на дополнительные размеры. Это печатает 18 раз [email protected]. Я предлагаю попробовать с sizeof(str) -1 для массива char.

Для второго синтаксиса memset(str, "t", sizeof(str)); вы предоставляете второй параметр является строкой. По этой причине компилятор жалуется на ошибку: неверное преобразование из 'const char * в' int

Ответ 4

Влад услужливо ответил на первую часть вашего вопроса, но я чувствую, что вторую часть можно объяснить немного более интуитивно:

Как уже упоминалось, 't' - это символ, в то время как "t" - это строка, а строки имеют нулевой терминатор в конце. Это делает "t" массивом не одного, а двух символов - ['t', '\0'] ! Это делает ошибку memset более интуитивно понятной - он может достаточно легко привести один char к int, но он задыхается, когда ему передается массив char. Как и в Python, int(['t', '\0']) (или ord(['t', '\0'])) не вычисляется.