Начнем со справочных данных, которые пригодятся нам при решении задач.
Полный набор эскейп-последовательностей:
последовательность | значение |
---|---|
\а | сигнал-звонок |
\b | возврат-на-шаг (забой) |
\f | перевод-страницы |
\n | новая-строка |
\r | возврат-каретки |
\t | горизонтальная-табуляция |
\v | вертикальная-табуляция |
\ | обратная наклонная черта |
\? | знак вопроса |
\' | одиночная кавычка |
\" | двойная кавычка |
\ooo | восьмеричный код |
\xhh | шестнадцатеричный код |
Таблица 2.1. Приоритеты и очередность вычислений операторов
операторы | выполняются |
---|---|
() , [] , -> , . |
слева направо |
! , ~ , ++ , -- , + , - , * , & , (type) , sizeof |
справа налево |
* , / , % |
слева направо |
+ , - |
слева направо |
<< , >> |
слева направо |
< , <= , > , >= |
слева направо |
== , != |
слева направо |
& |
слева направо |
^ |
слева направо |
| |
слева направо |
&& |
слева направо |
|| |
слева направо |
? : |
справа налево |
= , += , -= , *= , /= , %= , &= , ^= , |= , <<= , >>= |
справа налево |
, |
слева направо |
Упражнение 2.1. Напишите программу, которая будет выдавать диапазоны значений типов char, short, int и long, описанных как signed и как unsigned, с помощью печати соответствующих значений из стандартных заголовочных файлов и путем прямого вычисления. Определите диапазоны чисел с плавающей точкой различных типов. Вычислить эти диапазоны сложнее.
Начнем с целых чисел и использования значений из заголовочных файлов, а конкретно из limits.h:
#include <stdio.h>
#include <limits.h>
int main()
{
printf("Integer datatypes:\n");
printf("%d <= char <= %d\n", CHAR_MIN, CHAR_MAX);
printf("%d <= int <= %d\n", INT_MIN, INT_MAX);
printf("%ld <= long <= %ld\n", LONG_MIN, LONG_MAX);
printf("%d <= signed char <= %d\n", SCHAR_MIN, SCHAR_MAX);
printf("%d <= short <= %d\n", SHRT_MIN, SHRT_MAX);
printf("0 <= unsigned char <= %d\n", UCHAR_MAX);
printf("0 <= unsigned int <= %u\n", UINT_MAX);
printf("0 <= unsigned long <= %lu\n", ULONG_MAX);
printf("0 <= unsigned short <= %d\n", USHRT_MAX);
return 0;
}
Все так просто, что нечего и комментировать. У меня получилось вот что:
Вычислим теперь пределы изменения целочисленных типов непосредственно. Ограничимся типом short, для остальных все будет аналогично.
Идея такая: будем увеличивать переменную x заданного типа до тех пор, пока она не станет бесконечностью, т. е. для нее перестанут выполняться элементарные арифметические правила, например, x > x - 1.
Реализация этой идеи, однако, показала, что как только в С достигается максимальное значение какого-либо из типов, то за ним следует минимальное значение тог же типа. Т.е. тип как бы закольцован и максимум "склеен" с минимумом.
Ну что ж, нам это не помешает:
#include <stdio.h>
int main()
{
short x;
for (x = 1; x > x-1; ++x) {
if (x < 0)
break;
printf("%d\n", x); // печатаем для отладки программы
}
printf("MIN: %d\n", x);
printf("MAX: %d\n", --x);
return 0;
}
В результате получим:
Перейдем к вещественным типам. Начнем с использования констант из float.h:
#include <stdio.h>
#include <float.h>
int main()
{
printf("Float datatypes:\n");
printf("%e <= float <= %e\n", FLT_MIN, FLT_MAX);
printf("%e <= double <= %e\n", DBL_MIN, DBL_MAX);
return 0;
}
Вопреки предупреждениям, вычислить границы вещественного типа не сложнее, но дольше. Поэтому увеличим шаг прироста x:
#include <stdio.h>
int main()
{
float x;
for (x = 1; x > x/2; x *= 2 ) {
printf("%e\n", x);
}
return 0;
}
Эта программа определяет верхнюю границу вещественного типа. Идея здесь такая же, как и при определении границ целочисленных типов. Однако, в отличие от последних, вещественные типы не закольцованы, т. е. следом за максимальным значением представимым числом идет inf. Вычисление нижней границы выполняется аналогично.
Вот что мы получаем:
Мы можем заменить шаг x = 2 на меньший (например, x = 1.01): точность расчета границы будет выше, но сам расчте будет длиться дольше.
Упражнение 2.2. Напишите цикл, эквивалентный приведенному выше for-циклу, не пользуясь операторами && и ||.
Цикл, о котором идет речь, выглядит так:
for (i = 0; i < lim-1 && (с = getchar()) != EOF && с != '\n'; ++i)
s[i] = c;
Он уже знаком нам по функции getl() (или getline()) из главы 1.
Исходный вариант, с &&:
#include <stdio.h>
int lim = 10;
int main()
{
int i, c;
char s[lim];
for (i = 0; i < lim-1 && (c = getchar()) != EOF && c != '\n'; ++i)
s[i] = c;
s[i] = '\0';
printf("%s", s);
return 0;
}
Удобнее использовать while:
#include <stdio.h>
int lim = 10;
int main()
{
int i, c;
char s[lim];
i = 0;
while (i < lim-1) {
c = getchar();
if (c == EOF)
break;
if (c == '\n')
break;
s[i++] = c;
}
s[i] = '\0';
printf("%s", s);
return 0;
}
Не удалось обойтись без break'ов, которых мы к этому месту книги еще не знаем. У меня были и другие варианты решения, но они также требовали "незнакомых" конструкций. Приведенный вариант -- самый прямолинейный.
Упражнение 2.3. Напишите функцию htoi(s), которая преобразует последовательность шестнадцатеричных цифр, начинающуюся с 0x или 0X, в соответствующее целое. Шестнадцатеричными цифрами являются символы 0...9, a...f, А...F.
Ограничимся случаем положительных чисел. Кроме того, будем рассматривать только достаточно малые числа (вписывающиеся в тип int).
Разобьем задачу на части. Пусть вначале шестнадцатиричное число в строковой форме состоит лишь из чисел 0...9. В этом случае разрабатываемая функция будет похожа на имеющуюся в учебнике atoi(). Кроме того нам надо проверить корректность задания 16-ричного числа (оно должно начинаться с 0x или 0X, внутри числа этих символов быть не может) и позаботиться о его выводе.
#include <stdio.h>
int htoi(char m[]);
int main()
{
int n;
char s[] = "0x20\0";
n = htoi(s);
printf("%x", n);
return 0;
}
/* htoi: преобразование hex в целое */
int htoi(char s[])
{
int i = 0;
int answer = 0;
int valid = 1;
if(s[i] == '0') {
++i;
if(s[i] == 'x' || s[i] == 'X') {
++i;
}
else
valid = 0;
}
else
valid = 0;
while(valid && s[i] != '\0') {
answer = answer * 16;
if(s[i] >= '0' && s[i] <= '9') {
answer = answer + (s[i] - '0');
}
++i;
}
if(!valid) {
printf("The string isn't valid hex numbern");
answer = 0;
}
return answer;
}
Для корректно заданного числа valid=1, иначе выдается сообщение об ошибке. Вывод 16-ричного числа оргазован с помощью форматного спецификатора %x.
Теперь разберемся с буквами. Чтобы не мучится с регистрами, переведем все буквы в нижний регистр -- благо для этого в учебнике приведена функция lower(). Затем напишем вспомогательную функцию hex_a_f_to_int(), которая разбирает буквы и переделывает их в соотвествующие целые числа. Вот что получается в итоге:
#include <stdio.h>
int htoi(char m[]);
int lower(int c);
int hex_a_f_to_int(int c);
int main()
{
int n;
char s[] = "0xb1A\0";
n = htoi(s);
printf("%x", n);
return 0;
}
/* htoi: преобразование hex в целое */
int htoi(char s[])
{
int i = 0;
int answer = 0;
int valid = 1;
int hex_a_f;
if(s[i] == '0') {
++i;
if(s[i] == 'x' || s[i] == 'X') {
++i;
}
else
valid = 0;
}
else
valid = 0;
while(valid && s[i] != '\0') {
answer = answer * 16;
if(s[i] >= '0' && s[i] <= '9') {
answer = answer + (s[i] - '0');
}
else {
s[i] = lower(s[i]);
hex_a_f = hex_a_f_to_int(s[i]);
if (hex_a_f == 0) {
valid = 0;
}
else {
answer = answer + hex_a_f;
}
}
++i;
}
if(!valid) {
printf("The string isn't valid hex numbern");
answer = 0;
}
return answer;
}
/* lower: преобразование прописных c в строчные, только для ASCII */
int lower(int c)
{
if (c >= 'A' && c <='Z')
return c +'a'-'A';
else
return c;
}
/* hex_a_f_to_int: преобразование a-f в соответствующие целые числа */
int hex_a_f_to_int(int c)
{
char hex_a_f[] = "abcdef";
int i;
int answer = 0;
for(i = 0; answer == 0 && hex_a_f[i] != '\0'; i++) {
if(hex_a_f[i] == c) {
answer = 10 + i;
}
}
return answer;
}
Упражнение 2.4. Напишите версию функции squeeze(s1,s2), которая удаляет из s1 все символы, встречающиеся в строке s2.
#include <stdio.h>
int len;
void squeeze(char s1[], char s2[]);
int strlen(char s[]);
int main()
{
char s1[] = "BHiartpphdayy\0";
printf("s1 = %s\n", s1);
char s2[] = "Happy\0";
printf("s2 = %s\n", s2);
len = strlen(s2);
squeeze(s1, s2);
printf("new s1 = %s\n", s1);
return 0;
}
/* squeeze: удаляет все символы s2 из s1*/
void squeeze(char s1[], char s2[])
{
int i, j, k;
for (i = j = 0; s1[i] != '\0'; i++) {
for (k = 0; s2[k] != s1[i] && s2[k] != '\0'; k++);
if (k == len)
s1[j++] = s1[i];
}
s1[j] = '\0';
}
/* strlen: возвращает длину строки s */
int strlen(char s[])
{
int i;
i = 0;
while (s[i] != '\0')
++i;
return i;
}
Для вычисление длины строки мы воспользовались strlen(), приведенной в п. 2.3
Результат:
Упражнение 2.5. Напишите функцию any(s1,s2), которая возвращает либо ту позицию в s1, где стоит первый символ, совпавший с любым из символов в s2, либо -1 (если ни один символ из s1 не совпадает с символами из s2). (Стандартная библиотечная функция strpbrk делает то же самое, но выдает не номер позиции символа, а указатель на символ.)
#include <stdio.h>
int any(char s1[], char s2[]);
int strlen(char s[]);
int main()
{
char s1[] = "test string\0";
printf("s1 = %s\n\n", s1);
char s2[] = "ring\0";
printf("s2 = %s\n", s2);
printf("position of first occurrence = %d\n", any(s1, s2));
return 0;
}
/* any: возвращает индекс первого вхождения символа из s2 в s1*/
int any(char s1[], char s2[])
{
int i, j, k;
int len = strlen(s1);
int first = len;
for (i = j = 0; s1[i] != '\0'; i++)
for (k = 0; s2[k] != '\0'; k++)
if (s2[k] == s1[i] && i < first)
first = i;
if (first == len)
first = -1;
return first;
}
strlen() осталась без изменений. Получаем:
Упражнение 2.6. Напишите функцию setbits(x, p, n, y), возвращающую значение x, в котором n битов, начиная с p-й позиции, заменены на n правых разрядов из y (остальные биты не изменяются).
Эта задача открывает серию задач, посвященных побитовым операциям. Поэтому перед тем, как ее решать, позаботимся о наглдности результатов. Нужно выводить целые числа в двоичном представлении. Ограничимся беззнаковыми числами:
/* printfbit: выводит число в двоичном представлении */
void printfbit(unsigned n)
{
int i;
for(i = 7; i >= 0; --i) {
printf("%d", (n >> i) & 1);
}
printf("\n");
}
Теперь перейдем непосредственно к решению. Сразу предупрежу -- наверняка это можно реализовать короче. Удачи вам! Для меня это был первый опыт и тратить время на оптимизацию без понимания чего-ради не хотелось.
Пусть интересующая нас подстрока расположена внутри x. Нам нужно сохранить биты слева и справа от нее, а саму подстроку заменить подстрокой той же длины из правой части y.
К результату лучше идти по частям, выводя получившееся на экран. Здесь и пригодится printfbit(). Итак:
- Готовим маску из 1 для сохранения левой части x:
(~0 << (p+1))
- Сохраняем биты из левой части x:
(~0 << (p+1)) & x )
- Готовим маску для сохранения правой части x:
~(~0 << (p+1-n))
- Сохраняем биты из правой части x:
( ~(~0 << (p+1-n)) & x )
- Готовим маску для сохранения n правых битов из y:
~(~0 << n)
- Сохраняем n правых битов y:
(~(~0 << n) & y)
- Сдвигаем сохраненные биты y так, чтобы они оказались "напротив" нулей (места рассматриваемой подстроки) x:
(~(~0 << n) & y) << (p+1-n)
А теперь -- все вместе:
#include <stdio.h>
unsigned setbits(unsigned x, int p, int n, unsigned y);
void printfbit(unsigned n);
int main()
{
printf("76543210\n\n");
unsigned c1 = 'f';
printfbit(c1);
unsigned c2 = 'z';
printfbit(c2);
printfbit(setbits(c1, 5, 3, c2));
return 0;
}
/* setbits: x получает n правых бит из y, начиная с p-й позиции */
unsigned setbits(unsigned x, int p, int n, unsigned y)
{
return ( (~0 << (p+1)) & x ) | ( ~(~0 << (p+1-n)) & x ) |
(~(~0 << n) & y) << (p+1-n);
}
Здесь '\' -- символ разрыва строки. У меня в коде вики он не отобразился, но у вас-то получится!
Получаем:
Первая строка вывода -- номера битов, вторая -- строка x, третья -- y, четвертая -- результат замены.
Упражнение 2.7. Напишите функцию invert(x, p, n), возвращающую значение x с инвертированными n битами, начиная с позиции p (остальные биты не изменяются).
#include <stdio.h>
unsigned invert(unsigned x, int p, int n);
void printfbit(unsigned n);
int main()
{
printf("76543210\n\n");
unsigned c = 'f';
printfbit(c);
printfbit(invert(c, 5, 3));
return 0;
}
/* invert: инвертирует n бит из x, начиная с p-й позиции */
unsigned invert(unsigned x, int p, int n)
{
return ( ((~x >> (p+1-n)) & ~(~0 << n)) << (p+1-n) ) |
(~( ((~0 >> (p+1-n)) & ~(~0 << n)) << (p+1-n) ) & x);
}
Результат:
Упражнение 2.8. Напишите функцию rightrot (x, n), которая циклически сдвигает x вправо на n разрядов.
#include <stdio.h>
unsigned rightrot(unsigned x, int n);
void printfbit(unsigned n);
int main()
{
printf("76543210\n\n");
unsigned c = 'f';
printfbit(c);
printfbit(rightrot(c, 3));
return 0;
}
/* rightrot: сдвиг x на n вправо */
unsigned rightrot(unsigned x, int n)
{
return ((~(~0 << n) & x) << (8-n)) | x >> n;
}
В rightrot() явно вписано число бит (8), что не есть хорошо. В unsigned целом их больше, как видно хотя бы из упражнения 2.1. Но -- так проще ибо во всех рассмотренных задачах я работая только с одним битом.
Результат циклического сдвига на 3 бита вправо -- во второй строке:
Упражнение 2.9. Применительно к числам, в представлении которых использован дополнительный код, выражение x &= (x-1) уничтожает самую правую единицу в x. Объясните, почему. Используйте это наблюдение при написании более быстрого варианта функции bitcount.
Исходный bitcount() выглядит так:
/* bitcount: подсчет единиц в х */
int bitcount(unsigned x)
{
int b;
for (b = 0; x != 0; x >>= 1)
if (x & 01)
b++;
return b;
}
Объяснение. Если х нечетно, то (х-1) имеет такое же битовое представление как и х, за исключением того, что крайний правый бит в нем равен 0. В этом случае (х & (х-1)) == (х-1). Если же х четно, то в представлении (х-1) нули, стоявшие в х справа становятся единицами, а крайняя правая единица -- нулем. Конъюнкция х & (х-1) очищает эти позиции вплоть до того места, когда встретит единицы в представлениях обоих чисел (т.е. единицу, бывшую в x до этого шага второй справа).
Новая версия bitcount():
int bitcount(unsigned x)
{
int b;
for (b = 0; x != 0; x &= (x-1))
b++;
return b;
}
Упражнение 2.10. Напишите функцию lower, которая переводит большие буквы в малые, используя условное выражение (а не конструкцию if-else).
#include <stdio.h>
int lower(int c);
int main()
{
int i;
char s[] = "ABRAKADABRA\0";
printf("s = %s\n", s);
printf("lower s = ");
for (i = 0; s[i] != '\0'; i++)
printf("%c", lower(s[i]));
printf("\n");
return 0;
}
/* lower: преобразование прописных c в строчные, только для ASCII */
int lower(int c)
{
return (c >= 'A' && c <='Z') ? c +'a'-'A' : c;
}
Комментарии
comments powered by Disqus