То, что вы неправильно понимаете, - это подразделение, с которым вы работаете. при установке ширины контейнера вы не скажете ему, например, чтобы его ширина составляла ровно 500 пикселей. Это имеет смысл, когда вы начинаете думать о шрифтах. Fontsize 25 был бы хорош для вашего дисплея, но для дисплея 4K он мог бы быть минималистичным маленьким и нечитаемым. Чтобы обойти это стандартное устройство, для которого вы устанавливаете свой контейнер, составляет 1/96 дюйма. Вы можете изменить его, как показано ниже в цитате:
qualDouble Двойное значение, как описано выше, за которым следует одна из следующих строк объявления единиц: px, in, cm, pt.
px (по умолчанию) - независимые от устройства единицы (1/96 дюйма на единицу)
в дюймах; 1 дюйм == 96px
см - это сантиметры; 1см == (96 / 2.54) px
pt - точки; 1pt == (96/72) px
Авто Включает режим автоматического изменения размера. См. Примечания.
BLOCKQUOTE>
Если вы хотите попрактиковаться в расширенных функциях 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
}
}
Указатель (например, char *
, читаемый справа налево как указатель на char
) - это тип данных в C, который используется
ссылаться на место в памяти другого значения. В таком случае,
место, где хранится char
. Мы можем разыменовать
указатели, префикс их с *
, который дает нам значение
хранится в этом месте. Таким образом, значение, хранящееся в str
, равно * str
.
Мы можем сделать простую арифметику с указателями. Когда мы увеличиваем (или уменьшаем) указатель, мы просто перемещаем его для ссылки на следующий (или предыдущий) место в памяти для этого типа значения. Увеличивающие указатели разные типы могут перемещать указатель на разное число байты, потому что разные значения имеют разные размеры байтов в C.
Здесь мы используем один указатель для ссылки на первый необработанный
char
строки ( str
) и другой для ссылки на последний ( конец
).
Мы меняем их значения ( * str
и * end
) и перемещаем указатели
внутрь до середины струны. Once str> = end
, либо
они оба указывают на один и тот же символ
, что означает, что наша оригинальная строка имела
нечетная длина (и средний символ
не должен быть обращен), или
мы обработали все.
Чтобы выполнить обмен, я определил макрос . Макросы текстовые подстановки сделано препроцессором 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 a
и v b
.
// initially: // a == va // b == vb a ^= b; // now: a == va ^ vb b ^= a; // now: b == vb ^ (va ^ vb) // == va ^ (vb ^ vb) // == va ^ 0 // == va a ^= b; // now: a == (va ^ vb) ^ va // == (va ^ va) ^ vb // == 0 ^ vb // == vb
Таким образом, значения меняются местами. Это имеет одну ошибку - когда a
и b
являются одной и той же переменной:
// initially: // a == va a ^= a; // now: a == va ^ va // == 0 a ^= a; // now: a == 0 ^ 0 // == 0 a ^= a; // now: a == 0 ^ 0 // == 0
Поскольку мы str
Пока мы беспокоимся о правильности, мы должны проверить наши крайние случаи. Строка if (str)
должна убедиться, что нам не был дан указатель NULL
для строки. Как насчет пустой строки ""
? Хорошо strlen ("") == 0
, поэтому мы инициализируем end
как str - 1
, что означает, что while (str < конец)
условие никогда не выполняется, поэтому мы ничего не делаем. Это правильно.
Есть куча Си для изучения. Веселитесь с этим!
Обновление: 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. Я сказал им, что эта функция заменяет память, и они обязаны дать мне аргумент, который позволяет это.
Вместо того, чтобы пробиться на полпути, вы должны просто сократить цикл .
size_t length = strlen(str);
size_t i;
for (i = 0; i < (length / 2); i++)
{
char temp = str[length - i - 1];
str[length - i - 1] = str[i];
str[i] = temp;
}
Я не вижу оператор return, и вы меняете входную строку, что может быть проблемой для программиста. Вы можете захотеть, чтобы входная строка была неизменной.
Кроме того, это может быть разборчиво, но len / 2 следует рассчитывать только один раз, IMO.
Кроме этого, это будет работать, пока вы позаботитесь проблемных случаев, упомянутых rossfabricant.
Больше никто не использует указатели?
void inplace_rev( char * s ) {
char t, *e = s + strlen(s);
while ( --e > s ) { t = *s;*s++=*e;*e=t; }
}
РЕДАКТИРОВАТЬ: Извините, только что заметил приведенный выше пример XOR ...
Вы можете изменить объявление цикла for, чтобы сделать код короче:
char* reverse_string(char *str)
{
char temp;
size_t len = strlen(str) - 1;
size_t stop = len/2;
size_t i,k;
for(i = 0, k = len; i < stop; i++, k--)
{
temp = str[k];
str[k] = str[i];
str[i] = temp;
}
return str;
}
Попробуйте:
reverse_string(NULL);
reverse_string("");
Эта полная программа показывает, как я это сделаю. Имейте в виду, что я писал «С», когда большинство из вас, гадюки, были блеском в глазах ваших матерей, так что это старая школа, «делайте работу», «длинные имена для слабаков». Исправьте это, если хотите, меня больше интересует правильность кода.
Он обрабатывает NULL, пустые строки и все размеры строк. Я не проверял его на строках максимального размера (max (size_t)), но он должен работать, и если вы обрабатываете строки такого большого размера, вы все равно безумны: -)
#include <stdio.h>
#include <string.h>
char *revStr (char *str) {
char tmp, *src, *dst;
size_t len;
if (str != NULL)
{
len = strlen (str);
if (len > 1) {
src = str;
dst = src + len - 1;
while (src < dst) {
tmp = *src;
*src++ = *dst;
*dst-- = tmp;
}
}
}
return str;
}
char *str[] = {"", "a", "ab", "abc", "abcd", "abcde"};
int main(int argc, char *argv[]) {
int i;
char s[10000];
for (i=0; i < sizeof(str)/sizeof(str[0]); i++) {
strcpy (s, str[i]);
printf ("'%s' -> '%s'\n", str[i], revStr(s));
}
return 0;
}
Вывод этого:
'' -> ''
'a' -> 'a'
'ab' -> 'ba'
'abc' -> 'cba'
'abcd' -> 'dcba'
'abcde' -> 'edcba'
Вы можете поместить свой тест (len / 2)
в цикл for:
for(i = 0,k=len-1 ; i < (len/2); i++,k--)
{
temp = str[k];
str[k] = str[i];
str[i] = temp;
}
Просто перестановка и проверка безопасности. Я также удалил ваш неиспользуемый тип возврата. Я думаю, что это безопасно и чисто:
#include <stdio.h>
#include <string.h>
void reverse_string(char *str)
{
/* skip null */
if (str == 0)
{
return;
}
/* skip empty string */
if (*str == 0)
{
return;
}
/* get range */
char *start = str;
char *end = start + strlen(str) - 1; /* -1 for \0 */
char temp;
/* reverse */
while (end > start)
{
/* swap */
temp = *start;
*start = *end;
*end = temp;
/* move */
++start;
--end;
}
}
int main(void)
{
char s1[] = "Reverse me!";
char s2[] = "abc";
char s3[] = "ab";
char s4[] = "a";
char s5[] = "";
reverse_string(0);
reverse_string(s1);
reverse_string(s2);
reverse_string(s3);
reverse_string(s4);
reverse_string(s5);
printf("%s\n", s1);
printf("%s\n", s2);
printf("%s\n", s3);
printf("%s\n", s4);
printf("%s\n", s5);
return 0;
}
Отредактировано так, что end не будет указывать на возможно плохую область памяти, когда strlen равен 0.
Поскольку вы говорите, что хотите проявить фантазию, возможно, вы захотите обменяться своими персонажами, используя XOR swap .