При попытке создать функцию, возвращающую x с n биты, которые начинаются с позиции p перевернутый, оставив остальные без изменений — следуя упражнению в K&R — я придумал следующий фрагмент кода.
Тем не менее, мне непонятно, с чего начать подсчет, т. Е. Результат был бы другим, если бы начинать справа. Я начинаю слева — бит первый — это первый бит слева.
unsigned invert(unsigned x, unsigned p, unsigned n)
{
return x ^ ((~0U >> (p - 1 + (sizeof(int) * CHAR_BIT - p + 1 - n))) << (sizeof(int) * CHAR_BIT - p + 1 - n));
}
Что я бы затем упростил:
unsigned invert(unsigned x, unsigned p, unsigned n)
{
return x ^ ((~0U >> (sizeof(int) * CHAR_BIT - n)) << (sizeof(int) * CHAR_BIT - p + 1 - n));
}
Я не выполняю никаких проверок границ.
Это хорошая практика? Есть ли более чистое решение?
1 ответ
Когда ты найдешь это «непонятно с чего начать подсчет«вы столкнулись с одной из величайших проблем в разработке программного обеспечения: получение полезных спецификаций от ваших заинтересованных сторон! В таких ситуациях, когда мы не можем просто спросить, тогда нам нужно выбрать интерпретацию, которая кажется наиболее полезной и разумной, и (это важный момент), будьте предельно ясны, что мы должны были сделать выбор, и то, что мы выбрали.
В этом случае я бы, вероятно, сделал несколько более конкретное имя, а также добавил бы пояснительный комментарий:
/*
* Returns x with n bits inverted starting at bit p
* (counting from the left)
* E.g. invert_bits(0x0000, 4, 2) == 0x0c00 for 16-bit unsigned
*/
unsigned invert_bits(unsigned x, unsigned p, unsigned n)
Этот пример в комментарии обычно становится одним из тестов, которые я использую для проверки функции.
Мне кажется странным, что мы работаем с unsigned пока мы используем sizeof (int) для расчета ширины. Хотя мы знаем, что int и unsigned int одинакового размера, понятнее быть последовательным. На самом деле, я бы просто использовал sizeof x, что затем упрощает повторное использование кода — например, если мы напишем версию для unsigned long.
Я думаю, что арифметику можно было бы упростить, если бы учесть, что маска с крайним левым p бит не установлен ~0u >> p. С помощью invert_bits(0x0000, 4, 2) опять же, как в комментарии, мы можем визуализировать то, что делаем:
0000111111111111 mask_p = ~0u >> p;
0000001111111111 mask_n = mask_p >> n;
0000110000000000 mask_p ^ mask_n
В качестве завершенной функции мы можем повторно использовать один mask переменная для этого:
unsigned invert_bits(unsigned x, unsigned p, unsigned n)
{
unsigned mask = ~0u >> p;
mask ^= mask >> n;
return x ^ mask;
}
Приятно то, что он автоматически адаптируется к размеру шрифта без необходимости sizeof вообще в вычислении.
На случай, если я привел здесь слишком много спойлеров, предлагаю следующие упражнения:
- Напишите ту же функцию, но предполагая, что вы считаете биты из крайний правый (наименее значимый) бит.
- Напишите версию для
unsigned long. - Напишите версии, которые безоговорочно устанавливают и сбрасывают указанную группу битов.
- Написать
main()который проверяет, работают ли вышеуказанные функции как рекламируемые. Не забудьте вернутьсяEXIT_SUCCESSили жеEXIT_FAILUREпо мере необходимости.
Каким кодом можно поделиться в ответах на эти вопросы?

Вы кажетесь очень скромным, спасибо за добрый и очень четкий отзыв!
— j3141592653589793238