Как поменять местами строку в C или C++?

Как вы можете перевернуть строку в C или C++, не требуя отдельного буфера для хранения перевернутой строки?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
177
0
321 794
19
Перейти к ответу Данный вопрос помечен как решенный

Ответы 19

#include <cstdio>
#include <cstdlib>
#include <string>

void strrev(char *str)
{
        if ( str == NULL )
                return;

        char *end_ptr = &str[strlen(str) - 1];
        char temp;
        while( end_ptr > str )
        {
                temp = *str;
                *str++ = *end_ptr;
                *end_ptr-- = temp;
        }
}

int main(int argc, char *argv[])
{
        char buffer[32];

        strcpy(buffer, "testing");
        strrev(buffer);
        printf("%s\n", buffer);

        strcpy(buffer, "a");
        strrev(buffer);
        printf("%s\n", buffer);

        strcpy(buffer, "abc");
        strrev(buffer);
        printf("%s\n", buffer);

        strcpy(buffer, "");
        strrev(buffer);
        printf("%s\n", buffer);

        strrev(NULL);

        return 0;
}

Этот код производит такой вывод:

gnitset
a
cba

@uvote, не используйте strcpy. Всегда. Если вам нужно использовать что-то вроде strcpy, используйте strncpy. strcpy опасен. Между прочим, C и C++ - это два отдельных языка с разными возможностями. Я думаю, вы используете файлы заголовков, доступные только на C++, так что вам действительно нужен ответ на C?

Onorio Catenacci 13.10.2008 20:48

Если строка пуста, strlen вернет 0, и вы будете индексировать за пределами массива, что недопустимо в C (вы можете адресовать один элемент за концом массива, но не на один элемент раньше).

Robert Gamble 13.10.2008 20:48

strcpy совершенно безопасен, если программист может отслеживать размер своих массивов, многие будут утверждать, что strncpy менее безопасен, поскольку он не гарантирует, что результирующая строка будет завершена нулем. В любом случае, здесь нет ничего плохого в том, что uvote использует strcpy.

Robert Gamble 13.10.2008 20:52

@Onorio Catenacci, strcpy не опасен, если вы знаете, что исходная строка поместится внутри целевого буфера, как в случаях, указанных в приведенном выше коде. Кроме того, strncpy заполняет нулями до количества символов, указанного в параметре размера, если остается свободное место, что может быть нежелательно.

Chris Young 13.10.2008 20:54

Я считаю, что strcpy относится к тому же классу потенциально опасных языковых функций, что и перегрузка оператора - это нормально и безопасно, если кто-то знает, как его правильно использовать, но большинство разработчиков не знают, как правильно его использовать. Следовательно, этого следует просто избегать. Я верю своему совету.

Onorio Catenacci 13.10.2008 21:27

Любой, кто не может правильно использовать strcpy, не должен программировать на C.

Robert Gamble 13.10.2008 22:15

@ Роберт Гэмбл, я согласен. Однако, поскольку я не знаю способа удержать людей от программирования на C, независимо от их компетенции, я обычно не рекомендую этого делать.

Onorio Catenacci 13.10.2008 22:35

@Robert Gamble: Теперь есть непонятное правило C (индексирование одного перед массивом). Теперь, когда вы упомянули об этом, я это запомнил, и это хороший улов. Но, конечно, это работает в gcc на x86, как показывает мой тест. (и я в порядке с использованием strcpy)

uvote 14.10.2008 09:30

@OnorioCatenacci Мусор. Это совершенно безопасно, если вы следите за вещами. Это черно-белое отношение возникает из-за небезопасного программирования и / или повторения того, что они прочитали / услышали. Это просто неправда. И точно так же, как указывает Роберт, некоторые - в том числе и я - считают strncpy() менее безопасным. Вы не знаете лучшего способа, чем рекомендовать это? Почему бы не рекомендовать вместо этого действительно проверять наличие ошибок и т. д.? Это гораздо ценнее, чем занесение чего-либо в черный список, потому что некоторые люди используют это неправильно.

Pryftan 21.02.2020 20:27

@Pryftan Я бы сказал, что если SEI считает, что осторожность с strcpy - это хорошо, то я согласен: wiki.sei.cmu.edu/confluence/display/c/…. Strcpy полагается на дисциплину программиста и потенциально полагается на знания, которыми программист может просто не обладать. Есть более безопасная альтернатива - почему бы не использовать ее?

Onorio Catenacci 21.02.2020 23:14

@OnorioCatenacci Может быть, это не всегда безопаснее? Кто-то уже указал на это. Дело в том, что вы можете сделать что угодно небезопасным, если захотите. Но если вы знаете правильный размер, угадайте, что? Вы также можете правильно провести тестирование. Это здравый смысл. Быть осторожным с strcpy, как вы говорите, - это не то же самое, что и ваш предыдущий Не используйте strcpy. Всегда.. Большая разница. Огромная разница. Ваш предыдущий комментарий черно-белый. Быть в безопасности - это не одно и то же. Черно-белое редко бывает хорошей идеей. Жизнь не так проста. strncpy также может создавать незавершенные строки. Небезопасно.

Pryftan 22.02.2020 03:46

#include <algorithm>
std::reverse(str.begin(), str.end());

Это самый простой способ в C++.

В C++ строка представлена ​​строковым классом. Он не просил «звездочку» или «скобки». Оставайся стильным, С.

jokoon 31.08.2011 01:53

@fredsbend, «смехотворно длинная» версия выбранного ответа обрабатывает случай, которого нет в этом простом ответе - ввод UTF-8. Это показывает важность полного определения проблемы. Кроме того, вопрос был о коде, который также будет работать на C.

Mark Ransom 16.08.2013 18:45

Этот ответ обрабатывает случай, если вы используете класс строк, поддерживающий UTF-8 (или, возможно, класс символов utf-8 с std :: basic_string). Кроме того, в вопросе говорилось «C или C++», а не «C и C++». Только C++ - это «C или C++».

Taywee 05.07.2016 21:54

Это полностью сломает многобайтовые строки UTF-8 и в наши дни полностью не запускается.

Cameron Lowell Palmer 27.01.2021 22:18
Ответ принят как подходящий

Стандартный алгоритм состоит в том, чтобы использовать указатели на начало / конец и обходить их внутрь, пока они не встретятся или не пересекутся посередине. Меняйте местами на ходу.


Обратная строка ASCII, то есть массив с завершающим нулем, в котором каждый символ помещается в 1 char. (Или другие не многобайтовые наборы символов).

void strrev(char *head)
{
  if (!head) return;
  char *tail = head;
  while(*tail) ++tail;    // find the 0 terminator, like head+strlen
  --tail;               // tail points to the last real char
                        // head still points to the first
  for( ; head < tail; ++head, --tail) {
      // walk pointers inwards until they meet or cross in the middle
      char h = *head, t = *tail;
      *head = t;           // swapping as we go
      *tail = h;
  }
}

// test program that reverses its args
#include <stdio.h>

int main(int argc, char **argv)
{
  do {
    printf("%s ",  argv[argc-1]);
    strrev(argv[argc-1]);
    printf("%s\n", argv[argc-1]);
  } while(--argc);

  return 0;
}

Тот же алгоритм работает для целочисленных массивов известной длины, просто используйте tail = start + length - 1 вместо цикла поиска конца.

(Примечание редактора: в этом ответе изначально использовался XOR-swap и для этой простой версии. Исправлено в интересах будущих читателей этого популярного вопроса. XOR-swap высоко не рекомендуется; трудно читать, и ваш код компилируется менее эффективно. Вы можете увидеть в обозревателе компилятора Godbolt, насколько больше усложняет тело цикла asm, когда xor-swap компилируется для x86-64 с помощью gcc -O3.)


Хорошо, хорошо, давайте исправим символы UTF-8 ...

(Это XOR-замена. Обратите внимание, что вы меняете должен избегать на self, потому что, если *p и *q находятся в одном месте, вы обнуляете его с помощью ^ a == 0. XOR-замена зависит от наличия двух разных местоположений , используя каждый из них как временное хранилище.)

Примечание редактора: вы можете заменить SWP на безопасную встроенную функцию, используя переменную tmp.

#include <bits/types.h>
#include <stdio.h>

#define SWP(x,y) (x^=y, y^=x, x^=y)

void strrev(char *p)
{
  char *q = p;
  while(q && *q) ++q; /* find eos */
  for(--q; p < q; ++p, --q) SWP(*p, *q);
}

void strrev_utf8(char *p)
{
  char *q = p;
  strrev(p); /* call base case */

  /* Ok, now fix bass-ackwards UTF chars. */
  while(q && *q) ++q; /* find eos */
  while(p < --q)
    switch( (*q & 0xF0) >> 4 ) {
    case 0xF: /* U+010000-U+10FFFF: four bytes. */
      SWP(*(q-0), *(q-3));
      SWP(*(q-1), *(q-2));
      q -= 3;
      break;
    case 0xE: /* U+000800-U+00FFFF: three bytes. */
      SWP(*(q-0), *(q-2));
      q -= 2;
      break;
    case 0xC: /* fall-through */
    case 0xD: /* U+000080-U+0007FF: two bytes. */
      SWP(*(q-0), *(q-1));
      q--;
      break;
    }
}

int main(int argc, char **argv)
{
  do {
    printf("%s ",  argv[argc-1]);
    strrev_utf8(argv[argc-1]);
    printf("%s\n", argv[argc-1]);
  } while(--argc);

  return 0;
}
  • Почему да, если вход заборчен, то бодро поменяется вне места.
  • Полезная ссылка при вандализме в UNICODE: http://www.macchiato.com/unicode/chart/
  • Кроме того, UTF-8 более 0x10000 не протестирован (поскольку у меня, похоже, нет для него шрифта, а также терпения использовать шестнадцатеричный редактор)

Примеры:

$ ./strrev Räksmörgås ░▒▓○◔◑◕●

░▒▓○◔◑◕● ●◕◑◔○▓▒░

Räksmörgås sågrömskäR

./strrev verrts/.

Нет веских причин использовать замену XOR вне конкуренции с обфусцированным кодом.

Chris Conway 13.10.2008 21:02

Почему не «* p ^ = * q, * q ^ = * p, * p ^ = * q»?

Chris Conway 13.10.2008 21:19

Я бы сказал, что если вы собираетесь попросить «на месте», не вдаваясь в подробности, это ДОЛЖНО быть предметом xor. Все остальное не на своем месте. Тем не менее, это не имеет никакого отношения к производственному коду где-либо. если у вас даже возникнет искушение использовать его, бросьте разработку прямо сейчас.

Bill K 13.10.2008 21:24

Вы думаете, что «на месте» означает «без дополнительной памяти», даже без памяти O (1) для временных файлов? Как насчет места в стеке для str и адреса возврата?

Chris Conway 13.10.2008 21:30

@Bill, это не то, что означает обычное определение «на месте». Алгоритмы на месте май используют дополнительную память. Однако объем этой дополнительной памяти не должен зависеть от ввода - т.е. он должен быть постоянным. Таким образом, замена значений с использованием дополнительного хранилища полностью на месте.

Konrad Rudolph 13.10.2008 21:31

О нет, Раксморгос! Теперь мне нужно пойти к холодильнику и приготовить себе один. :П

Jonas Engström 14.10.2008 02:04

Возможно, вы захотите проверить этот вопрос, который я задал конкретно о том, как справиться с этой задачей со строками UTF8 (хотя это не универсальное решение). stackoverflow.com/questions/199260/…

Juan Pablo Califano 14.10.2008 02:41

Не голосовать за это, пока не исчезнет своп xor.

Adam Rosenfield 24.01.2010 00:42

Замена XOR выполняется медленнее, чем замена через регистр на современных вышедших из строя процессорах.

Patrick Schlüter 15.08.2011 21:42

Я погуглил "переворот строки на месте" конкретно, чтобы найти реализацию трюка XOR. Ненавистники возненавидят, но я нашел то, что искал, так +1 от меня.

John Dibling 03.10.2012 02:06

Я провел несколько тестов, и мне кажется, что тест «while (q && * q)» нужно изменить. Это должно быть «while (* q)» или быть заменено отдельным «if (! Q) {return;}» (если намерение состояло в том, чтобы перехватывать указатели NULL). Проблема в случае NULL, следующий оператор for (- q; p <q; ++ p, --q) заставляет q переходить на 0xFFFFFFFFFFFFFFFF (по крайней мере, на 64-битной машине CentOS я был тестирование включено), что приведет к выполнению цикла for (поскольку 0 меньше 0xFFFFFFFFFFFFFFFF, а первый «* p = * p ^ * q;» затем вызовет отмену ссылки на нулевой указатель и отключение ссылка 0xFFFFFFFFFFFFFFFF ...

mmucklo 14.07.2013 00:06

Ребята, большинство из вас ошибается насчет определения in-place. У него есть строгое определение: алгоритм на месте использует память O (logn). Пример Fox: вы рассматриваете быструю сортировку на месте? Он есть и использует логарифмическую память для хранения данных в стеке рекурсии.

sasha.sochka 19.07.2013 00:40

@ sasha.sochka Вы ошибаетесь. На месте означает одно и только одно: память постоянный, а не O (logn). И вы получили это: быстрая сортировка не на месте. Тот, кто утверждает, что это неправ. Фактически, многие (якобы «на месте») реализации быстрой сортировки даже используют O (n) дополнительной памяти в худшем случае (поскольку они не гарантируют логарифмическую глубину рекурсии. Но, как я уже сказал, быстрая сортировка на самом деле не работает). место для начала.

Konrad Rudolph 16.10.2013 21:25

@KonradRudolph, да, я понял. Возможно, некоторое время назад я прочитал эту статью en.wikipedia.org/wiki/In-place_algorithm (раздел «По вычислительной сложности») и забыл, что этот log n применялся там в несколько ином контексте.

sasha.sochka 16.10.2013 23:26

@AndersEurenius, не могли бы вы пояснить, зачем использовать while(q && *q) ++q; для поиска eos? Разве это не то же самое, что и while(*q) ++q;, поскольку q никогда не будет 0? Как вы можете быть уверены, что q или * q будут равны 0?

AR89 17.01.2014 15:09

"вы должны избегать подкачки с self, потому что a^a==0" - неверно. a ^ a == 0, но это не проблема, потому что тогда вы будете использовать a ^ (a ^ a), то есть a ^ 0, то есть a. Таким образом, обмен XOR работает, даже если два обмена (есть такое слово) равны.

user529758 21.01.2014 19:52

char *q = p; while(q && *q) ++q; for(--q; p < q; ++p, --q) для обнаружения NULL проблематичен. Для продвижения кода до --q, когда q имеет значение NULL и в лучшем случае это самый большой допустимый указатель, в худшем - это может быть UB. Тогда цикл for() будет повторяться очень долго. Подскажите простой if (q != NULL) { do the rest of code }.

chux - Reinstate Monica 14.06.2014 02:08

Как интервьюер, я док указывает на использование старого трюка xor-swap. Он менее эффективен на современных процессорах, и любой, кто его использует, потому что он «аккуратный», является нет отличным программистом - возможно, «хорошо», но не «великим».

Brian White 11.09.2015 04:39

Вау, каждый раз, когда я думаю, что не могу найти более уродливый код, чем в прошлый раз, я нахожу более уродливый.

Xam 13.09.2018 01:03

@ChrisConway: Да, и как участник и победитель я также могу сказать, что это не собьет многих людей с толку. Там он может найти свое применение, но это тоже не сбивает с толку. Я могу придумать множество способов, которыми это могло бы быть полезно в статье, но в целом это настолько неоригинально, что не имеет отношения к делу. Им нужны более сбалансированные записи. Тем не менее, есть моменты вне конкурса, когда я мог бы рассмотреть это, хотя это тоже очень ограничено - и не в обычном коде.

Pryftan 21.02.2020 19:50

Что касается while(q && *q) ++q;, почему бы просто не использовать strchr(q, '\0');? Наверное, не имеет значения, но кажется более естественным. Конечно, есть и другие способы. Возможно, вы просто не хотели даже включать string.h.

Pryftan 21.02.2020 20:00

Не злой C, предполагающий общий случай, когда строка представляет собой массив char с завершающим нулем:

#include <stddef.h>
#include <string.h>

/* PRE: str must be either NULL or a pointer to a 
 * (possibly empty) null-terminated string. */
void strrev(char *str) {
  char temp, *end_ptr;

  /* If str is NULL or empty, do nothing */
  if ( str == NULL || !(*str) )
    return;

  end_ptr = str + strlen(str) - 1;

  /* Swap the chars */
  while( end_ptr > str ) {
    temp = *str;
    *str = *end_ptr;
    *end_ptr = temp;
    str++;
    end_ptr--;
  }
}

Вместо использования цикла while для поиска конечного указателя нельзя использовать что-то вроде end_ptr = str + strlen (str); Я знаю, что это будет практически то же самое, но я нахожу это более ясным.

Peter Kühne 13.10.2008 21:43

Справедливо. Я пытался (и у меня не получалось) избежать одной ошибки в ответе @ uvote.

Chris Conway 13.10.2008 23:57

Помимо улучшения производительности потенциал, возможно, с int temp, это решение выглядит лучше всего. +1

chux - Reinstate Monica 14.06.2014 02:17

@ chux-ReinstateMonica Да. Это прекрасно. Лично я бы удалил () из !(*str).

Pryftan 21.02.2020 20:08

@ PeterKühne Yes или strchr() ищет '\0'. Я думал об обоих. Не нужна петля.

Pryftan 21.02.2020 20:10

В интересах полноты следует отметить, что существуют представления строк на различных платформах, в которых количество байтов на символ варьируется зависит от символа. Программисты старой закалки назвали бы это DBCS (двухбайтовый набор символов). Современные программисты чаще сталкиваются с этим в UTF-8 (а также UTF-16 и других). Есть и другие такие кодировки.

В любой из этих схем кодирования с переменной шириной простые алгоритмы, размещенные здесь (зло, не злой или иначе), не будут работать правильно! Фактически, они могут даже привести к тому, что строка станет неразборчивой или даже станет недопустимой в этой схеме кодирования. См. Ответ Хуана Пабло Калифано для некоторых хороших примеров.

std :: reverse () потенциально может работать и в этом случае, если реализация стандартной библиотеки C++ на вашей платформе (в частности, строковые итераторы) должным образом учитывает это.

std :: reverse НЕ принимает это во внимание. Он меняет значение типа value_type на противоположное. В случае std :: string он переворачивает char. Не персонажи.

MSalters 14.10.2008 11:24

Скорее скажем, что мы, программисты старой школы, знаем о DBCS, но также знаем о UTF-8: поскольку все мы знаем, что программисты похожи на наркоманов, когда они говорят «Еще одна строчка, и я уйду!» Я уверен, что некоторые программисты в конечном итоге бросают, но, честно говоря, программирование действительно похоже на зависимость для меня ; Я получаю отказ от программирования. Это хороший момент, который вы добавили сюда. Мне не нравится C++ (я действительно очень старался, чтобы он понравился, даже написав немало, но он все еще для меня эстетически непривлекателен, мягко говоря), поэтому я не могу там комментировать, но вы все равно делаете хорошее замечание, так что иметь +1.

Pryftan 21.02.2020 20:23

Обратите внимание, что прелесть std :: reverse заключается в том, что он работает со строками char * и std::wstring так же хорошо, как с std::string.

void strrev(char *str)
{
    if (str == NULL)
        return;
    std::reverse(str, str + strlen(str));
}

Если вы ищете реверсирование буферов с нулевым завершением, большинство решений, размещенных здесь, подходят. Но, как уже указывал Тим Фарли, эти алгоритмы будут работать только в том случае, если допустимо предположить, что строка семантически представляет собой массив байтов (то есть однобайтовые строки), что, я думаю, является неправильным предположением.

Возьмем, к примеру, строку «año» (год на испанском языке).

Кодовые точки Unicode: 0x61, 0xf1, 0x6f.

Рассмотрим некоторые из наиболее часто используемых кодировок:

Latin1 / iso-8859-1 (однобайтовая кодировка, 1 символ равен 1 байту и наоборот):

Original:

0x61, 0xf1, 0x6f, 0x00

Reverse:

0x6f, 0xf1, 0x61, 0x00

The result is OK

UTF-8:

Original:

0x61, 0xc3, 0xb1, 0x6f, 0x00

Reverse:

0x6f, 0xb1, 0xc3, 0x61, 0x00

The result is gibberish and an illegal UTF-8 sequence

UTF-16 с прямым порядком байтов:

Original:

0x00, 0x61, 0x00, 0xf1, 0x00, 0x6f, 0x00, 0x00

The first byte will be treated as a NUL-terminator. No reversing will take place.

UTF-16 Little Endian:

Original:

0x61, 0x00, 0xf1, 0x00, 0x6f, 0x00, 0x00, 0x00

The second byte will be treated as a NUL-terminator. The result will be 0x61, 0x00, a string containing the 'a' character.

std :: reverse будет работать для двухбайтовых типов Unicode, если вы используете wstring.

Eclipse 13.10.2008 23:02

Я не очень знаком с C++, но предполагаю, что любая уважаемая стандартная библиотечная функция, работающая со строками, сможет обрабатывать различные кодировки, поэтому я согласен с вами. Под «этими алгоритмами» я имел в виду специальные реверсивные функции, размещенные здесь.

Juan Pablo Califano 13.10.2008 23:10

К сожалению, в стандартном C++ нет такой вещи, как «респектабельная функция, работающая со строками».

Jem 30.03.2012 18:08

@Eclipse Если он меняет суррогатную пару, результат больше не будет правильным. Юникод на самом деле не является кодировкой с фиксированной шириной

phuclv 17.05.2014 07:55

Что мне нравится в этом ответе, так это то, что он показывает фактический порядок байтов (хотя порядок байтов может быть чем-то вроде - а, я вижу, вы действительно его учли) и то, насколько он правильный или неправильный. Но я думаю, что ответ был бы лучше, если бы вы включили некоторый код, демонстрирующий это.

Pryftan 21.02.2020 20:19

Прочтите Керниган и Ричи

#include <string.h>

void reverse(char s[])
{
    int length = strlen(s) ;
    int c, i, j;

    for (i = 0, j = length - 1; i < j; i++, j--)
    {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
    }
}

Проверено на моем iphone, примерно на 15% медленнее, чем при использовании адресов необработанных указателей.

jjxtra 30.04.2013 00:16

Разве переменная "c" не должна быть char вместо int?

Lesswire 17.11.2013 21:25

@Lesswire - в C всякий раз, когда символьная константа или переменная используется в выражении в C, она автоматически преобразуется и обрабатывается как целое число. Если у вас есть терминал linux, вы можете увидеть коды ascii, набрав man ascii

user1527227 09.04.2014 05:48

В этом примере важно отметить, что строка s должна быть объявлена ​​в виде массива. Другими словами, char s[] = "this is ok", а не char *s = "cannot do this", потому что последний приводит к строковой константе, которую нельзя изменить.

user1527227 09.04.2014 05:50

length, i, j должен быть size_t, но тогда возникнут проблемы, когда length == 0.

chux - Reinstate Monica 14.06.2014 02:16

@PsychoDad Вы включали оптимизацию, когда выполняли тест iPhone для доступа к массиву по сравнению с указателем ??

Brandin 01.09.2014 10:27

@brandin Надеюсь, я сделал это в режиме выпуска, но я не помню.

jjxtra 01.09.2014 18:40

С извинениями перед «Крестным отцом» .... «Оставь оружие, принеси K&R». Как фанат C, я бы использовал указатели, поскольку они проще и понятнее для решения этой проблемы, но код будет менее переносимым на C#, Java и т. д.

user1899861 27.11.2014 05:14

Я не понимаю, почему это не принятый ответ. Это самое простое и надежное решение, работающее в пространстве O (1). Он также работает за время O (log (n)), что замечательно.

Eric 07.12.2016 22:05

@Eric Это не выполняется за время O (log (n)). Он выполняется за O (n), если вы имеете в виду количество замен символов, выполняемых кодом, для n длины строки выполняется n замен. Если вы говорите о количестве выполненных циклов, то это все еще O (n) - хотя и O (n / 2), но вы отбрасываете константы в нотации Big O.

Stephen Fox 18.12.2016 21:43

@Eric В дополнение к тому, что говорит Стивен, как также говорит user1527227, он требует, чтобы он был в форме массива. Это будет еще одна веская причина, по которой это не принято.

Pryftan 21.02.2020 20:04

@jjxtra Значит, у вас плохой компилятор

klutt 25.01.2021 16:41

Если вы используете GLib, у него есть две функции для этого: g_strreverse () и g_utf8_strreverse ().

Мне нравится ответ Евгения K&R. Однако приятно видеть версию с использованием указателей. В остальном, по сути, то же самое:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *reverse(char *str) {
    if ( str == NULL || !(*str) ) return NULL;
    int i, j = strlen(str)-1;
    char *sallocd;
    sallocd = malloc(sizeof(char) * (j+1));
    for(i=0; j>=0; i++, j--) {
        *(sallocd+i) = *(str+j);
    }
    return sallocd;
}

int main(void) {
    char *s = "a man a plan a canal panama";
    char *sret = reverse(s);
    printf("%s\n", reverse(sret));
    free(sret);
    return 0;
}

Прошло некоторое время, и я не помню, в какой книге меня научили этому алгоритму, но я подумал, что он довольно гениальный и простой для понимания:

char input[] = "moc.wolfrevokcats";

int length = strlen(input);
int last_pos = length-1;
for(int i = 0; i < length/2; i++)
{
    char tmp = input[i];
    input[i] = input[last_pos - i];
    input[last_pos - i] = tmp;
}

printf("%s\n", input);

Визуализация этого алгоритма любезно предоставлена ​​слэшдоттир:

Visualization of the algorithm to reverse a string in place

Да, это интересный вариант. Хотя технически это должен быть size_t.

Pryftan 21.02.2020 20:14

Рекурсивная функция для переворота строки на месте (без дополнительного буфера, malloc).

Короткий, сексуальный код. Плохое, неправильное использование стека.

#include <stdio.h>

/* Store the each value and move to next char going down
 * the stack. Assign value to start ptr and increment 
 * when coming back up the stack (return).
 * Neat code, horrible stack usage.
 *
 * val - value of current pointer.
 * s - start pointer
 * n - next char pointer in string.
 */
char *reverse_r(char val, char *s, char *n)
{
    if (*n)
        s = reverse_r(*n, s, n+1);
   *s = val;
   return s+1;
}

/*
 * expect the string to be passed as argv[1]
 */
int main(int argc, char *argv[])
{
    char *aString;

    if (argc < 2)
    {
        printf("Usage: RSIP <string>\n");
        return 0;
    }

    aString = argv[1];
    printf("String to reverse: %s\n", aString );

    reverse_r(*aString, aString, aString+1); 
    printf("Reversed String:   %s\n", aString );

    return 0;
}

Это довольно забавное решение, вы должны добавить трассировку, например printf ("% * s> [% d] reverse_r ('% c',% p = \"% s \ ",% p = \"% s \ ") \ n ", глубина," ", глубина, val, s, (s? s:" null "), n, (n? n:" null ")); в начале и <в конце.

Benoît 16.10.2013 00:59

Помещение каждого char в стек не считается «на месте». Особенно, когда вы фактически нажимаете 4 * 8B на символ (на 64-битной машине: 3 аргумента + адрес возврата).

Peter Cordes 28.03.2017 10:15

Исходный вопрос: «Как вы перевернете строку в C или C++, не требуя отдельного буфера для хранения перевернутой строки?» - не требовалось менять местами. Кроме того, в этом решении с самого начала упоминается неправильное использование стека. Я проиграл голосование из-за плохого понимания прочитанного другими людьми?

Simon Peverett 24.10.2017 15:33

Я бы не назвал это «сексуальным», но сказал бы, что это демонстративно и поучительно. Если рекурсия используется правильно, она может быть очень ценной. Однако следует отметить, что - последнее, что я знал - C даже не требует стека как такового; однако он делает рекурсию. В любом случае это пример рекурсии, которая при правильном использовании может быть очень полезной и ценной. Я не думаю, что когда-либо видел, как он использовался для переворота строки.

Pryftan 21.02.2020 20:30

Еще один:

#include <stdio.h>
#include <strings.h>

int main(int argc, char **argv) {

  char *reverse = argv[argc-1];
  char *left = reverse;
  int length = strlen(reverse);
  char *right = reverse+length-1;
  char temp;

  while(right-left>=1){

    temp=*left;
    *left=*right;
    *right=temp;
    ++left;
    --right;

  }

  printf("%s\n", reverse);

}

Это демонстрирует арифметику указателей, как и мой ответ, но объединяет ее с Swap. Я считаю, что этот ответ на самом деле многое добавляет. Вы должны быть в состоянии понять этот тип кода, прежде чем добавлять миллиард библиотек в качестве зависимостей, просто чтобы получить простое текстовое поле (что я слишком часто вижу в современных приложениях, с которыми мне приходится работать)

Stephen J 27.11.2018 00:26

Другой способ C++ (хотя я бы, наверное, сам использовал std :: reverse () :) как более выразительный и быстрый)

str = std::string(str.rbegin(), str.rend());

Способ C (более или менее :)) и, пожалуйста, будьте осторожны с трюком XOR для подкачки, компиляторы иногда не могут это оптимизировать.

В таком случае это обычно намного медленнее.

char* reverse(char* s)
{
    char* beg = s, *end = s, tmp;
    while (*end) end++;
    while (end-- > beg)
    { 
        tmp  = *beg; 
        *beg++ = *end;  
        *end =  tmp;
    }
    return s;
} // fixed: check history for details, as those are interesting ones

Я бы использовал strlen, чтобы найти конец строки, если он потенциально длинный. Хорошая реализация библиотеки будет использовать векторы SIMD для поиска быстрее, чем 1 байт на итерацию. Но для очень коротких строк while (*++end); будет выполнен до того, как вызов библиотечной функции начнет поиск.

Peter Cordes 28.03.2017 10:17

@PeterCordes хорошо, согласен, strlen в любом случае следует использовать для удобства чтения. В любом случае, для более длинной струны вы всегда должны сохранять длину в вариалбе. strlen на SIMD обычно приводится в качестве примера с этим заявлением об отказе от ответственности, это не реальное приложение, или, по крайней мере, это было 5 лет назад, когда был написан код. ;)

pprzemek 04.04.2017 12:46

Если вы хотите, чтобы это работало быстро на реальных процессорах, вы должны использовать тасование SIMD, чтобы сделать обратное для блоков по 16 Байт. : P например на x86 _mm_shuffle_epi8 (PSHUFB) может изменить порядок вектора 16B, учитывая правый вектор управления тасованием. Вероятно, он может работать почти со скоростью memcpy при некоторой тщательной оптимизации, особенно с AVX2.

Peter Cordes 05.04.2017 03:07

char* beg = s-1 имеет неопределенное поведение (по крайней мере, если s указывает на первый элемент массива, что является наиболее распространенным случаем). while (*++end); имеет неопределенное поведение, если s - пустая строка.

melpomene 17.10.2018 07:30

@pprzemek Что ж, вы утверждаете, что s-1 определил поведение, даже если s указывает на первый элемент массива, поэтому вы должны иметь возможность ссылаться на стандарт в поддержку.

melpomene 13.11.2018 00:10

@pprzemek В вашем коде не используется оператор --. Я никогда ничего не говорил о типах. Арифметика указателя в целом действительна только в том случае, если она перемещает указатель внутри того же массива (или на один элемент за конец). См. Также c-faq.com/aryptr/non0based.html, в котором явно указано, что то, что вы делаете, недопустимо.

melpomene 13.11.2018 00:52

@melpomene, вы действительно очень мудры, вы могли бы просто исправить ответ, как рекомендует SO. Тем не менее вы правы, я был неправ. Я удалю свои комментарии со стыдом и смирением, так как они больше не действительны.

pprzemek 13.11.2018 11:02

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

unsigned char * utf8_reverse(const unsigned char *, int);
void assert_true(bool);

int main(void)
{
    unsigned char str[] = "mañana mañana";
    unsigned char *ret = utf8_reverse(str,  strlen((const char *) str) + 1);

    printf("%s\n", ret);
    assert_true(0 == strncmp((const char *) ret, "anãnam anañam", strlen("anãnam anañam") + 1));

    free(ret);

    return EXIT_SUCCESS;
}

unsigned char * utf8_reverse(const unsigned char *str, int size)
{
    unsigned char *ret = calloc(size, sizeof(unsigned char*));
    int ret_size = 0;
    int pos = size - 2;
    int char_size = 0;

    if (str ==  NULL) {
        fprintf(stderr, "failed to allocate memory.\n");
        exit(EXIT_FAILURE);
    }

    while (pos > -1) {

        if (str[pos] < 0x80) {
            char_size = 1;
        } else if (pos > 0 && str[pos - 1] > 0xC1 && str[pos - 1] < 0xE0) {
            char_size = 2;
        } else if (pos > 1 && str[pos - 2] > 0xDF && str[pos - 2] < 0xF0) {
            char_size = 3;
        } else if (pos > 2 && str[pos - 3] > 0xEF && str[pos - 3] < 0xF5) {
            char_size = 4;
        } else {
            char_size = 1;
        }

        pos -= char_size;
        memcpy(ret + ret_size, str + pos + 1, char_size);
        ret_size += char_size;
    }    

    ret[ret_size] = '\0';

    return ret;
}

void assert_true(bool boolean)
{
    puts(boolean == true ? "true" : "false");
}

Если вы используете ATL / MFC CString, просто позвоните CString::MakeReverse().

Если хранить его не нужно, можно сократить время, потраченное на это:

void showReverse(char s[], int length)
{
    printf("Reversed String without storing is ");
    //could use another variable to test for length, keeping length whole.
    //assumes contiguous memory
    for (; length > 0; length--)
    {
        printf("%c", *(s+ length-1) );
    }
    printf("\n");
}

Кажется, я единственный ответ, у которого нет буфера или временной переменной. Я использую длину строки, но другие, которые делают это, добавляют еще одну для головы (против хвоста). Я предполагаю, что стандартная обратная функция хранит переменную через своп или что-то в этом роде. Таким образом, у меня сложилось впечатление, если предположить, что математика указателя и тип UTF совпадают, что это, возможно, единственный ответ, который действительно отвечает на вопрос. Добавленные функции printf () можно убрать, я просто сделал это, чтобы результат выглядел лучше. Я написал это для скорости. Никаких дополнительных выделений или переменных. Наверное, самый быстрый алгоритм отображения обратного str ()

Stephen J 27.04.2020 10:06

Многобайтовый реверсор C++ UTF-8

Я думаю, вы никогда не можете просто поменять местами концы, вы всегда должны двигаться от начала до конца, перемещаться по строке и искать «сколько байтов потребуется для этого символа?» Я прикрепляю символ, начиная с исходной конечной позиции, и удаляю символ из передней части строки.

void StringReverser(std::string *original)
{
  int eos = original->length() - 1;
  while (eos > 0) {
    char c = (*original)[0];
    int characterBytes;
    switch( (c & 0xF0) >> 4 ) {
    case 0xC:
    case 0xD: /* U+000080-U+0007FF: two bytes. */
      characterBytes = 2;
      break;
    case 0xE: /* U+000800-U+00FFFF: three bytes. */
      characterBytes = 3;
      break;
    case 0xF: /* U+010000-U+10FFFF: four bytes. */
      characterBytes = 4;
      break;
    default:
      characterBytes = 1;
      break;
    }

    for (int i = 0; i < characterBytes; i++) {
      original->insert(eos+i, 1, (*original)[i]);
    }
    original->erase(0, characterBytes);
    eos -= characterBytes;
  }
}

void reverseString(vector<char>& s) {
        int l = s.size();
        char ch ;
        int i = 0 ;
        int j = l-1;
        while(i < j){
                s[i] = s[i]^s[j];
                s[j] = s[i]^s[j];
                s[i] = s[i]^s[j];
                i++;
                j--;
        }
        for(char c : s)
                cout <<c ;
        cout<< endl;
}

Другие вопросы по теме