Работая над некоторыми задачами на собеседовании по программированию, которые я нашел в Интернете, мне пришлось написать алгоритм для обратного преобразования const char * и возврата указателя на новый char *. Я думаю, что он у меня есть, но для того, чтобы он работал должным образом, мне пришлось сделать некоторые шаткие вещи - в основном, чтобы самому учитывать символ завершения нуля. Почему-то я чувствую, что это неправильно, но я в тупике, и мне было интересно, может ли кто-нибудь мне помочь:
char * reverse(const char * str)
{
int length = strlen(str);
char * reversed_string = new char[length+1];
for(int i = 0; i < length; ++i)
{
reversed_string[i] = str[(length-1) - i];
}
//need to null terminate the string
reversed_string[length] = '\0';
return reversed_string;
}
int main(int argc, char * argv[])
{
char * rev_str = reverse("Testing");
cout << "Your string reversed is this: " << rev_str << endl;
delete rev_str;
rev_str = 0;
return 0;
}
Не забудьте проверить строку NULL.
спасибо адам и майкл, хорошие советы!
Мой C++ заржавел, но не следует ли ему удалить [] rev_str вместо delete rev_str?
Хорошо, у меня действительно был этот вопрос во время интервью на прошлой неделе.
Это C++, а не C. Если вы хотите, чтобы это было и то, и другое, используйте malloc () и free (), а не new и delete.
Это должно быть в CodeReview.
«Это должно быть в CodeReview» Я не думаю, что это было в 2008 году :)





У меня однажды был этот вопрос. Это первый ответ, который приходит на ум, но следующий: «Теперь сделайте это, не выделяя памяти».
int length = strlen(string);
for(int i = 0; i < length/2; i++) {
char c = string[i];
string[i] = string[length - i];
string[length - i] = c;
}
Обновлено: Некоторые люди выразили пренебрежение тем, что не используют указатели. Это немного более читабельно, хотя и не полностью оптимально. Другие уже вошли в решение указателя, поэтому я не буду повторять его здесь.
Один из комментаторов высказал сомнение в том, что это должно быть осуществимо без удерживающей ячейки (на основе стека) для свопа. Механизм для этого - побитовое исключающее ИЛИ. Замените внутреннюю часть петли на
string[i] = string[i] ^ string[length - i];
string[length - i] = string[i] ^ string[length - i];
string[i] = string[i] ^ string[length - i];
Но в целом современные компиляторы могут оптимизировать локальную переменную наивного свопа. Подробности: См. Википедию
Поскольку JDS начинается с const char *, требуется выделение (или нарушение постоянной правильности путем литья) ...
Конечно, но последующий вопрос подразумевает, что вы собираетесь изменить строку на месте, что подразумевает избавление от const.
Не забудьте проверить нулевую строку
Теперь сделайте это без использования временной переменной хранения.
Чуть быстрее, с арифметикой указателя: int top = strlen (string) -1; char c; for (; top! = 0; top--, строка ++) {c = строка [0]; строка [0] = строка [вверху]; строка [вверху] = c; }
Собственно, в этом коде есть ошибка. length = 4. Это должно быть: swap(0, 3), swap(1, 2);, текущий код: swap(0, 4), swap(1, 3). Чтобы исправить это, замените length - i на length - i - 1
это прекрасно работает:
#include <algorithm>
#include <iostream>
#include <cstring>
void reverse_string(char *str) {
char *end = str + strlen(str) - 1;
while (str < end) {
std::iter_swap(str++, end--);
}
}
int main() {
char s[] = "this is a test";
reverse_string(s);
std::cout << "[" << s << "]" << std::endl;
}
if ( string[0] )
{
char *end = string + strlen(string)-1;
while( start < end )
{
char temp = *string;
*string++ = *end;
*end-- = temp;
}
}
Вы, вероятно, имеете в виду * end--, а не * end ++.
Даже если *end++ зафиксирован как *end--, строка, которая инициализирует `end, приводит к неопределенному поведению, если strlen () возвращает 0.
(фейспалм) Ой. @Mike B: Не определено, но полностью надежно в реальном мире (как и большая часть продукции C)
Да - у меня не было бы сердечного приступа, если бы я увидел это в обзоре кода, но я бы упомянул об этом и ожидал, что это будет исправлено в результате того, что оно будет поднято в обзоре кода.
Как (p + 0-1) undefined для указателя p? * p будет undefined, но вполне нормально переместить указатель на один назад, даже если он указывает на начало выделенной области. И, конечно же, с end == staring - 1, while () не будет введен. Я называю это «элегантным».
@Arkaiy: Стандарт C гласит: «Если и операнд-указатель, и результат указывают на элементы одного и того же объекта массива или один за последним элементом объекта массива, оценка не должна вызывать переполнения; в противном случае поведение неопределенный."
Однако, как заметил Менкбой, на самом деле он работает практически все время, но, тем не менее, не определен (например, он может сломаться на сегментированных архитектурах).
std::reverse из <algorithm> работает для строк и массивов char:
string str = "Hello";
char chx[] = "Hello";
reverse(str.begin(), str.end());
reverse(chx, chx + strlen(chx));
cout << str << endl;
cout << chx << endl;
/ EDIT: это, конечно, изменяет исходную строку. Но STL пришел на помощь. Следующее создает новую перевернутую строку. К сожалению (?), Это не работает напрямую с массивами C char без создания дополнительной (неявной) копии:
string reverse_string(string const& old) {
return string(old.rbegin(), old.rend());
}
cout << reverse_string("Hello") << endl;
да. Предпочитайте библиотеку, если она доступна.
Изменение исходной строки - очень плохая идея, если исходная строка является указателем на строковый литерал. Лучше бы массив.
b1ke: ой. Следует проверить больше. ;-)
Является ли использование библиотеки хорошим ответом на собеседование по программированию, если задача состоит в том, чтобы «написать алгоритм»?
акайхола: это зависит от ситуации интервью. Но по моему опыту, знания стандартной библиотеки C++ ужасны, даже у программистов, которые заявляют, что они являются опытными программистами на C++. Перевернуть строку (даже эффективно) - тривиальная проблема. Важно знать правильную функцию C++ stdlib.
akaihola: Кроме того, уже были опубликованы правильные решения - зачем их повторять? ИМХО, гораздо больше смысла расширять и показывать альтернативные подходы, даже если они работают для немного других требований.
Я бы решил это примерно так (мой c немного ржавый, простите меня)
char *reverse( const char *source ) {
int len = strlen( source );
char *dest = new char[ len + 1 ];
int i = 0;
int j = len;
while( j > 0 ) {
dest[j--] = src[i++];
}
dest[i] = \0;
return dest;
}
На самом деле, учитывая ограничение, согласно которому исходная строка остается неизменной, я думаю, что исходный подход, указанный в вопросе, является лучшим. Все эти причудливые подходы к изменению места, которое публикуют люди, великолепны, но как только копирование данной строки учтено, все они менее эффективны, чем простое копирование строки в обратном направлении.
Только если он остановится на полпути; как написано, переключает все дважды, не так ли?
Нет - вычеркните этот комментарий; это не на месте.
Ну, конечно, это модные подходы. Это является вопрос интервью, о котором мы говорим. :)
Мы уже задавали этот вопрос раньше - с удивительными результатами, обнаружившими множество людей, которые не могут этого сделать (даже со значительным опытом работы с C / C++!). Я предпочитаю вариант на месте, так как он экономит некоторые накладные расходы и имеет дополнительный поворот, заключающийся в необходимости повторять только символы strlen (s) / 2.
Ваше решение на собеседовании подойдет. Решение (правильное!), Использующее указатель вместо синтаксиса массива, будет оцениваться немного выше, поскольку оно показывает больший уровень комфорта с указателями, которые так важны в программировании на C / C++.
Незначительной критикой было бы указать, что strlen возвращает size_t, а не int, и вы должны использовать delete [] для rev_str.
Вариант на месте должен перебирать 3 * strlen (s) / 2 символа, потому что сначала он должен вызвать strlen.
Я бы сказал, что причиной Только использования указателей в этом упражнении было бы продемонстрировать навыки работы с указателями. Синтаксис массива делает код более понятным. Вы можете возразить, что выбор указателей демонстрирует склонность к показухе!
Это было бы не более эффективно, но вы могли бы продемонстрировать знание структур данных, сделав что-то вроде помещения каждой буквы в стек, а затем выталкивая их в новый выделенный буфер.
Потребовалось бы два прохода и скретч-стек, но я бы, вероятно, больше доверял себе, чтобы сделать это правильно с первого раза, а не делать одну ошибку, подобную приведенной выше.
char* stringReverse(const char* sInput)
{
std::size_t nLen = strlen(sInput);
std::stack<char> charStack;
for(std::size_t i = 0; i < nLen; ++i)
{
charStack.push(sInput[i]);
}
char * result = new char[nLen + 1];
std::size_t counter = 0;
while (!charStack.empty())
{
result[counter++] = charStack.top();
charStack.pop();
}
result[counter] = '\0';
return result;
}
Во-первых, я не думал, что зная что-то настолько простое, как стек, нуждается в демонстрации. Во-вторых, попытка продемонстрировать это в данном случае была бы довольно негативной для интервью, ИМО. Может показаться, что кандидат просто разбрасывает вещи, не понимая этого.
Речь также идет о том, чтобы не изобретать колесо - в других реализациях есть риск единичных ошибок - я отредактирую, чтобы показать код.
Эм-м-м? С указателями никто не делал?
char *reverse(const char *s) {
size_t n = strlen(s);
char *dest = new char[n + 1];
char *d = (dest + n - 1);
dest[n] = 0;
while (*s) {
*d-- = *s++
}
return dest;
}
Надеюсь, годы Java не испортили мой C ;-)
Редактировать: заменил все эти вызовы strlen на дополнительную переменную. Что возвращает strlen в наши дни? (Спасибо цоколь).
Будьте осторожны - вы трижды вызывали strlen () для s, хотя одна из них могла бы сделать это - это означает, что вы проходите строку 4 раза.
Ваш код прост и неудивителен. Несколько вещей:
Другими словами:
char *dst = reversed_string + length;
*dst-- = '\0';
while (*src) {
*dst-- = *src++;
}
Задавая этот вопрос в качестве интервьюера, я ищу ясное, понятное решение и могу спросить, как можно сделать первоначальное решение более эффективным. Меня не интересуют «умные» решения.
Я думаю о таких вещах; кандидат сделал старую с отключенной одной ошибкой в своем цикле, выделяют ли они заранее достаточно памяти, проверяют ли они неправильный ввод, используют ли они эффективные типы достаточно.
К сожалению, как уже отмечалось, слишком многие люди не могут даже этого сделать.
@ Конрад Рудольф: (извините, у меня нет опыта, чтобы оставлять комментарии)
Я хочу отметить, что STL предоставляет алгоритм reverse_copy (), аналогичный обратный(). Вам не нужно вводить временный объект, как вы это сделали, просто выделите новый char * нужного размера.
Насчет reverse_copy: правда. Однако не вижу никаких преимуществ. О временной копии: она необходима, если я хочу вернуть string. Если я выделю char array и верну его, вызывающий должен очистить эту память - беспорядок. Лучше придерживаться строк C++.
В опубликованном вопросе требовалось вернуть char *, я просто пытался указать, что это тоже можно сделать. Если вы действительно хотите вернуть std :: string, ваш метод великолепен, потому что он хорошо работает с RVO и использует конструктор диапазона std :: string для возможных оптимизаций.
WRT: «Теперь сделайте это без временной переменной хранения» ... Возможно, что-то вроде этого (и пока сохраняем индексацию массива):
int length = strlen(string);
for(int i = 0; i < length/2; i++) {
string[i] ^= string[length - i];
string[length - i] ^= string[i];
string[i] ^= string[length - i];
}
Я знаю, что это очень непереносимо, но инструкция ассемблера x86 bswap позволяет вам менять местами четыре байта с помощью всего одной инструкции, что может быть хорошим путем для ускорения кода.
Это пример того, как заставить его работать с GCC.
/*
* reverse.c
*
* $20081020 23:33 fernando DOT miguelez AT gmail DOT com$
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MAX_CHARS 10 * 1024 * 1024
/*
* Borrowed from http://coding.derkeiler.com/Archive/Assembler/comp.lang.asm.x86/2007-03/msg00004.html
* GNU Compiler syntax
*/
inline uint32_t bswap(uint32_t val)
{
__asm__("bswap %0" : "=r" (val) : "0" (val));
return val;
}
char * reverseAsm(const char * str)
{
int i;
int length = strlen(str);
int dwordLength = length/4;
if (length % 4 != 0)
{
printf("Error: Input string length must be multiple of 4: %d\n", length);
return NULL;
}
char * reversed_string = (char *) malloc(length+1);
for(i = 0; i < dwordLength; i++)
{
*(((uint32_t *) reversed_string) + dwordLength - i - 1) = bswap(*(((uint32_t *) str) + i));
}
reversed_string[length] = '\0';
return reversed_string;
}
char * reverse(const char * str)
{
int i;
int length = strlen(str);
char * reversed_string = (char *) malloc(length+1);
for(i = 0; i < length; ++i)
{
reversed_string[i] = str[(length-1) - i];
}
//need to null terminate the string
reversed_string[length] = '\0';
return reversed_string;
}
int main(void)
{
int i;
char *reversed_str, *reversed_str2;
clock_t start, total;
char *str = (char *) malloc(MAX_CHARS+1);
str[MAX_CHARS] = '\0';
srand(time(0));
for(i = 0; i < MAX_CHARS; i++)
{
str[i] = 'A' + rand() % 26;
}
start = clock();
reversed_str = reverse(str);
total = clock() - start;
if (reversed_str != NULL)
{
printf("Total clock ticks to reverse %d chars with pure C method: %d\n", MAX_CHARS, total);
free(reversed_str);
}
start = clock();
reversed_str2 = reverseAsm(str);
total = clock() - start;
if (reversed_str2 != NULL)
{
printf("Total clock ticks to reverse %d chars with ASM+C method: %d\n", MAX_CHARS, total);
free(reversed_str2);
}
free(str);
return 0;
}
Результаты на моем старом компьютере под Cygwin:
fer@fernando /cygdrive/c/tmp$ ./reverse.exe
Total clock ticks to reverse 10485760 chars with pure C method: 221
Total clock ticks to reverse 10485760 chars with ASM+C method: 140
Перевернутая строка на месте, без временной переменной.
static inline void
byteswap (char *a, char *b)
{
*a = *a^*b;
*b = *a^*b;
*a = *a^*b;
}
void
reverse (char *string)
{
char *end = string + strlen(string) - 1;
while (string < end) {
byteswap(string++, end--);
}
}
В целях безопасности, byteswap () должен сначала проверить, имеет ли значение a == b, и немедленно вернуться, если это так. Обратите внимание, что я сказал a == b, а не * a == * b. Если вы попытаетесь поменять местами одну и ту же ячейку памяти с собой, метод XOR вместо этого изменит ее на 0.
Хорошая точка зрения. Я также должен проверить, что строка не ПУСТО (NULL), но я хотел, чтобы пример был компактным.
Метод, не требующий временных переменных
int length = strlen(string);
for(int i = 0; i < length/2; i++) {
string[i] ^= string[length - i] ^= string[i] ^= string[length - i];
}
Если бы я проводил собеседование, я был бы немного более привередлив к качеству решения с точки зрения его надежности, а не только его производительности.
Все ответов, представленных до сих пор, завершится ошибкой, если будет передан нулевой указатель - большинство из них перескакивают к немедленному вызову strlen() по возможному нулевому указателю - что, вероятно, приведет к сбою вашего процесса.
Многие из ответов одержимы производительностью до такой степени, что упускают из виду одну из ключевых проблем вопроса: перевернуть const char *, то есть вам нужно сделать перевернутый копировать, а не перевернуть на месте. Вам будет сложно сократить вдвое количество итераций, если потребуется копия!
Это вопрос интервью, поэтому мы хотим рассмотреть детали алгоритма, но в реальном мире это просто подчеркивает ценность использования стандартных библиотек, когда это возможно.
.
char * reverse(const char * str)
{
if (!str)
return NULL;
int length = strlen(str);
char * reversed_string = new char[length+1];
for(int i = 0; i < length/2; ++i)
{
reversed_string[i] = str[(length-1) - i];
reversed_string[(length-1) - i] = str[i];
}
//need to null terminate the string
reversed_string[length] = '\0';
return reversed_string;
}
Половина времени, но такая же сложность (примечание может быть отключено из-за одной ошибки)
Над циклом for есть опечатка. Проверка переменной цикла i должна быть <= вместо <, othrewise не удастся из-за нечетного количества элементов. для (int i = 0; i <= length / 2; ++ i)
Вы не можете (не должны) этого делать:
string[i] ^= string[length - i] ^= string[i] ^= string[length - i];
От: http://en.wikipedia.org/wiki/XOR_swap_algorithm#Code_example
Вы должны использовать delete [], а не простое удаление.