Если вы хотите попрактиковаться в расширенных функциях C, как насчет указателей?
Мы можем добавить макросы и xor-swap для удовольствия!
#include <string.h> // for strlen()
// reverse the given null-terminated string in place
void inplace_reverse(char * str)
{
if (str)
{
char * end = str + strlen(str) - 1;
// swap the values in the two given variables
// XXX: fails when a and b refer to same memory location
# define XOR_SWAP(a,b) do\
{\
a ^= b;\
b ^= a;\
a ^= b;\
} while (0)
// walk inwards from both ends of the string,
// swapping until we get to the middle
while (str < end)
{
XOR_SWAP(*str, *end);
str++;
end--;
}
# undef XOR_SWAP
}
}
A указатель (например, char *
, читаемый справа налево как указатель на char
) - это тип данных в C, который используется
ссылаться на местоположение в памяти другого значения. В этом случае,
место, где хранится char
. Мы можем разыменование
указатели с префиксом *
, что дает нам значение
хранится в этом месте. Таким образом, значение, хранящееся в str
, равно *str
.
Мы можем сделать простую арифметику с указателями. Когда мы увеличиваем (или уменьшаем)
указатель, мы просто перемещаем его для ссылки на следующий (или предыдущий)
место в памяти для этого типа значения. Увеличивающие указатели
разные типы могут перемещать указатель на разное число
байты, потому что разные значения имеют разные размеры байтов в C.
Здесь мы используем один указатель для ссылки на первый необработанный
char
строки (str
) и другой для ссылки на последний (end
).
Мы меняем их значения (*str
и *end
) и перемещаем указатели
внутрь к середине строки. Однажды str >= end
, либо
они оба указывают на один и тот же char
, что означает, что наша оригинальная строка имела
нечетная длина (и середина char
не должна быть обращена), или
мы обработали все.
Чтобы выполнить обмен, я определил макрос . Макросы текстовые подстановки
сделано препроцессором C. Они очень отличаются от функций,
и важно знать разницу. Когда вы вызываете функцию,
функция работает с копией значений, которые вы ей даете. Когда вы звоните
макрос, он просто делает текстовую подстановку - поэтому аргументы вы даете
используются напрямую.
Поскольку я использовал макрос XOR_SWAP
только один раз, вероятно, было излишним определять его,
но это сделало более ясным, что я делал. После того, как препроцессор C расширяет макрос,
цикл while выглядит так:
while (str < end)
{
do { *str ^= *end; *end ^= *str; *str ^= *end; } while (0);
str++;
end--;
}
Обратите внимание, что аргументы макроса отображаются один раз при каждом использовании в
макроопределение. Это может быть очень полезно - но может также сломать ваш код
если используется неправильно. Например, если бы я сжал увеличение / уменьшение
инструкции и вызов макроса в одну строку, например
XOR_SWAP(*str++, *end--);
Тогда это расширится до
do { *str++ ^= *end--; *end-- ^= *str++; *str++ ^= *end--; } while (0);
Имеет тройные операции увеличения / уменьшения, но на самом деле не имеет
сделайте обмен, который он должен сделать.
Пока мы говорим на эту тему, вы должны знать, что означает xor (^
). Это основной
арифметическая операция - как сложение, вычитание, умножение, деление, кроме
это обычно не преподается в начальной школе. Он объединяет два целых по крупицам
- как дополнение, но нас не волнуют переходы. 1^1 = 0
, 1^0 = 1
,
0^1 = 1
, 0^0 = 0
.
Хорошо известный трюк - использовать xor для обмена двумя значениями. Это работает из-за трех основных
свойства xor: x ^ 0 = x
, x ^ x = 0
и x ^ y = y ^ x
для всех значений x
и y
. Скажем, у нас есть два
переменные a
и b
, которые изначально хранят два значения
v<sub>a</sub>
и v<sub>b</sub>
.
// initially:
// a == v<sub>a</sub>
// b == v<sub>b</sub>
a ^= b;
// now: a == v<sub>a</sub> ^ v<sub>b</sub>
b ^= a;
// now: b == v<sub>b</sub> ^ (v<sub>a</sub> ^ v<sub>b</sub>)
// == v<sub>a</sub> ^ (v<sub>b</sub> ^ v<sub>b</sub>)
// == v<sub>a</sub> ^ 0
// == v<sub>a</sub>
a ^= b;
// now: a == (v<sub>a</sub> ^ v<sub>b</sub>) ^ v<sub>a</sub>
// == (v<sub>a</sub> ^ v<sub>a</sub>) ^ v<sub>b</sub>
// == 0 ^ v<sub>b</sub>
// == v<sub>b</sub>
Значит, значения меняются местами. Это имеет одну ошибку - когда a
и b
являются одной и той же переменной:
// initially:
// a == v<sub>a</sub>
a ^= a;
// now: a == v<sub>a</sub> ^ v<sub>a</sub>
// == 0
a ^= a;
// now: a == 0 ^ 0
// == 0
a ^= a;
// now: a == 0 ^ 0
// == 0
Так как мы str < end
, в вышеприведенном коде этого не происходит, поэтому мы в порядке.
Пока мы обеспокоены правильностью, мы должны проверить наши крайние случаи. Строка if (str)
должна гарантировать, что нам не был дан указатель NULL
для строки. Как насчет пустой строки ""
? Хорошо strlen("") == 0
, поэтому мы инициализируем end
как str - 1
, что означает, что условие while (str < end)
никогда не выполняется, поэтому мы ничего не делаем. Что правильно.
Есть куча Си для изучения. Веселитесь вместе с ним!
Обновление: mmw поднимает хороший вопрос, который вы должны быть немного осторожны, как вы вызываете это, поскольку он работает на месте.
char stack_string[] = "This string is copied onto the stack.";
inplace_reverse(stack_string);
Это прекрасно работает, так как stack_string
- это массив, содержимое которого инициализируется заданной строковой константой. Тем не менее
char * string_literal = "This string is part of the executable.";
inplace_reverse(string_literal);
Заставит ваш код загореться и умереть во время выполнения. Это потому, что string_literal
просто указывает на строку, которая хранится как часть вашего исполняемого файла - обычно это память, которую вы не можете редактировать в ОС. В более счастливом мире ваш компилятор знает об этом и выдает ошибку, когда вы пытаетесь скомпилировать, сообщая вам, что string_literal
должен иметь тип char const *
, поскольку вы не можете изменять содержимое. Однако, это не тот мир, в котором живет мой компилятор.
Есть несколько хаков, которые вы можете попытаться убедиться, что некоторая память находится в стеке или в куче (и, следовательно, редактируема), но они не обязательно переносимы, и это может быть довольно уродливо. Тем не менее, я более чем счастлив переложить ответственность за это на функцию invoker. Я сказал им, что эта функция заменяет память, и они обязаны дать мне аргумент, который это позволяет.