Язык Си в примерах/Таблица умножения: различия между версиями

Содержимое удалено Содержимое добавлено
Переписано.
Исправления, уточнения; →‎Вариант «мультисистемный»: новый раздел.
Строка 1:
{{{{Book template}}/Содержание}}
 
; {{Якорь2 |Дано}}: положительное {{w |целое число}} <var >n</var> (в «текстовом» десятичном представлении), переданное программе как аргумент [[w:Интерфейс командной строки |командной строки.]] Значение <var >n</var><sup >2</sup> не превышает максимального допустимого значения для типа <code >long</code> (<var >LONG_MAX</var>.)
; Надо: вывести на [[w:Стандартные потоки#Стандартный вывод |стандартный вывод]] таблицу умножения размера <var >n</var> × <var >n</var>.
; Указания:
:* Считать, что значение <var >n</var><sup >2</sup> не превышает максимального допустимого значения для типа <code >long</code> (<var >LONG_MAX</var>.)
:* Начать разработку программы можно с цикла (<var >j</var> = 1, …, <var >n</var>) вывода некоторой (<var >i</var>-ой) строки таблицы. Затем, этот цикл (''цикл значений'') можно в свою очередь заключить в ''цикл строк'' (<var >i</var> = 1, …, <var >n</var>.)
:* Для выравнивания столбцов таблицы имеет смысл воспользоваться формулой ''длины десятичного представления'' неотрицательногоположительного целого числа (<var >x</var> ⩾ 0) — <var >w</var> = 1 + ⌊lg (<var >x</var>)⌋ = ⌈lg (1 + <var >x</var>)⌉ (где ⌊<var >α</var>⌋ и ⌈<var >α</var>⌉ — операции округления «к нулю» и «от нуля», соответственно.)
; Дополнительно: включите в программу интерпретацию аргумента (<var >n</var>) согласно правилам языка Си для числовых констант. Реализуйте вывод таблицы умножения в шестнадцатеричном, восьмеричном или десятичном представлении в зависимости от способа записи аргумента командной строки. (Так, например, аргумент <code >0x12</code> должен приводить к выводу таблицы умножения 18 × 18 в шестнадцатеричном представлении.)
; Новые элементы языка: форма записи ''[[#символьные константы |символьных констант]]''; операторы [[#continue |continue]], [[#for |for]]; функции [[#ceil |ceil]], [[#log |log]], [[#putchar |putchar]], [[#strtol |strtol]]; ''указатель разрядности'' [[#%* |<code >*</code>]] форматного преобразования; двуаргументная форма объявления функции <code >main</code>.
; Новые элементы языка: форма записи ''[[#символьные константы |символьных констант]]''; операторы [[#continue |continue]], [[#for |for]]; функции [[#ceil |ceil]], [[#isdigit |isdigit]], [[#log |log]], [[#putchar |putchar]], [[#strchr |strchr]], [[#strtol |strtol]]; ''указатель разрядности'' [[#%* |<code >*</code>]] форматного преобразования; двуаргументная форма объявления функции <code >main</code>.
 
== Решение ==
Строка 14:
<source lang="c">
#include <assert.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
Строка 31 ⟶ 32 :
 
assert (n >= 1);
assert (n <= sqrt (LONG_MAX));
const int width = (int)ceil (2 * log (1 + n) / log (10));
 
Строка 94 ⟶ 96 :
Вторая возможность — функция <code >putchar</code>, выводящая на [[w:Стандартные потоки#Стандартный вывод |стандартный вывод]] заданный символ (код);<ref name="putchar" /> в данном случае, <code >'\n'</code> — [[w:Управляющие символы |управляющий код]] (или ''управляющий символ'') [[w:Перевод строки |перевода]] (''разрыва'', ''завершения'') строки. <strong >Обратите внимание</strong> на использование ''одинарных кавычек'' при записи ''символьных констант'' (в отличие от ''строковых'' — для которых используются ''двойные кавычки''.)
 
Если бы было заранее известно, что значение произведения не превышает, например, 9999, можно было бы обойтись фиксированной разрядностью (<code >" %4ld", <var >i</var> * <var >j</var></code> — или, что то же, — <code >" %*ld", 4, <var >i</var> * <var >j</var></code>.) Однако коль скоро в задаче [[#Дано |требуется]] обрабатывать широкий диапазон чисел (наибольшее значение типа <code >long</code> согласно стандарту {{w |C11}} должно быть не менее порядка 2 × 10⁹), разрядность результата необходимо вычислять исходя из заданного пользователем значения <var >n</var>.
 
Поскольку мы также предваряем дополнительным ''пробелом'' каждое выводимое число, общая ширина каждого столбца окажется на единицу большей, чем используемое значение разрядности <var >width</var>. Вычислению последнего, в свою очередь, посвящен [[#Вычисление предельной разрядности |следующий раздел.]]
Строка 102 ⟶ 104 :
Для получения ровных столбцов в выводе программы, каждое выводимое значение должно состоять из равного количества знаков. Ясно, что из всех возможных произведений <var >i</var> × <var >j</var>, <var >i</var> = 1, …, <var >n</var>, <var >j</var> = 1, …, <var >n</var>, максимальное значение имеет <var >n</var> × <var >n</var> = <var >n</var><sup >2</sup>, а значит именно количество знаков в записи этого числа и будет предельным для таблицы умножения <var >n</var> × <var >n</var>.
 
Далее, мы пользуемся тем фактом, что ''позиционная'' (в частности — десятичная) запись числа является его ''разложением'' по степеням ''основания системы счисления''. Ясно, что максимальная степень <var >n</var> в разложении неотрицательногоположительного целого числа <var >x</var> по степеням основания <var >b</var> равна ⌊log<sub ><var >b</var></sub> <var >x</var>⌋, а общее число разрядов (включая ''единицы'' — при нулевой степени основания) — на единицу больше <var >n</var>.
 
Для получения конечной формулы, нам остается лишь воспользоваться следующими простыми свойствами логарифмов:
Строка 111 ⟶ 113 :
В итоговом выражении — <source lang="c" enclose="none" >(int)ceil (2 * log (1 + n) / log (10))</source> — мы используем функции <code >ceil</code> и <code >log</code>, значением которых являются результат округления «от нуля» (в сторону роста ''абсолютного значения'') и {{w |натуральный логарифм}} аргумента, соответственно. Эти функции являются частью ''стандартной библиотеки'' и ''объявлены'' в ''заголовке'' <code >math.h</code>.<ref name="ceil" /><ref name="log" />
 
Ясно, что данное выражение теряет смысл при <var >n</var> &lt; 0. Кроме того, при <var >n</var> = 0, реализованные нами циклы вычисления—вывода сформируют ''пустой'' результат, который также <em >можно</em> считать ошибочным. Чтобы этого избежать, перед вычислением искомого выражения мы ''требуем'' (посредством макроподстановкимакроподстановкой <code >assert</code><ref name="assert" />) выполнения условия <source lang="c" enclose="none" >n >= 01</source>.
 
Здесь же мы требуем, чтобы значение <var >n</var> не превышало квадратного корня из максимального допустимого значения для используемого нами типа <code >long</code> (макроподстановка <var >LONG_MAX</var> — ''объявлена'' в ''заголовке'' <code >limits.h</code> ''стандартной библиотеки''.) В противном случае, один или более из результатов умножения может ''переполнить'' разрядность данного типа. (Отметим, впрочем, что формирование таблицы умножения теряет практический смысл уже при много меньших значениях <var >n</var>.)
 
=== Разбор аргументов командной строки ===
Строка 128 ⟶ 132 :
</source>
 
Данный фрагмент кода начинается ''требованием'' (посредством макроподстановкимакроподстановкой <code >assert</code><ref name="assert" />) наличия ''единственного'' аргумента командной строки. Здесь следует отметить, что переменная <var >argc</var> отражает общую длину массива <var >argv</var>, в который входит — нулевым элементом — имя самой программы: <code ><var >argv</var>[0]</code>. Тем самым, условие наличия и единственности аргумента может быть записано как <code ><var >argc</var> == 2</code> — или же как приведено в коде.
 
В свою очередь, переменные <var >argc</var> и <var >argv</var> являются аргументами главной функции <code >main</code>, при ''объявлении'' ее следующим образом:<ref name="startup" />
Строка 136 ⟶ 140 :
</source>
 
{{Якорь |strtol}}
Мы объявляем переменную <var >n</var> и, в отдельном вложенном блоке, — указатель <var >tail</var>. Вызываем функцию <code >strtol</code> (''объявленную'' в ''заголовке'' <code >stdlib.h</code>) преобразования строкового представления числа в «машинное» — значение типа <code >long</code>;<ref name="strtol" /> результат сохраняем в переменной <code >n</code>. Первый аргумент функции — строка, подлежащая преобразованию; третий — основание системы счисления, или 0 — для использования правил языка Си для числовых констант (<code >0x12</code> — 12<sub >16</sub>; <code >012</code> — 12<sub >8</sub>; <code >12</code> — 12<sub >10</sub>.)
Мы объявляем переменную <var >n</var> и, в отдельном вложенном блоке, — указатель <var >tail</var>. Вызываем функцию <code >strtol</code> (''объявленную'' в ''заголовке'' <code >stdlib.h</code>) преобразования строкового представления числа в «машинное» — значение типа <code >long</code>;<ref name="strtol" /> результат сохраняем в переменной <code >n</code>. Первый аргумент функции — строка, подлежащая преобразованию; третий — основание системы счисления, или 0 — для использования правил языка Си для числовых констант (например: <code >0x12</code> — 12₁₆; <code >012</code> — 12₈; <code >12</code> — 12₁₀.)
 
Функция <code >strtol</code> пропускает любые ''пробельные символы'' в начале строки (если они есть) и пытается разобрать следующее за ними число. При этом, указатель на первый же символ (код), не являющийся частью такого числа, помещается по адресу, переданному вторым аргументом (если не 0); в нашем случае — в переменную <code >tail</code>. Кроме того, если за ''ведущими'' пробельными символами <em >не следует</em> ничего похожего на число (в соответствии с указанной третьим аргументом формой записи), то по данному адресу помещается значение первого аргумента.
Строка 168 ⟶ 173 :
 
Хорошей практикой представляется определение переменных так, чтобы область их видимости была <em >не большей</em>, чем строго необходимо. (Но не в ущерб ясности кода.) В этом случае, при перемещении (копировании) кода, случайное использование переменной там, где она не имеет смысла, с большей вероятностью будет диагностировано непосредственно реализацией языка.
 
== Вариант «мультисистемный» ==
 
Используемая нами для преобразования строкового представления числа в «машинное» функция [[#strtol |strtol]] определяет ''систему счисления'' по ''префиксу'' строкового представления числа. В следующем примере мы частично воссоздаем эту логику для вывода таблицы умножения в шестнадцатеричном, восьмеричном или десятичном представлении в зависимости от способа записи аргумента командной строки.
 
{{Якорь |multablex.c}}
<source lang="c">
#include <assert.h>
#include <ctype.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int
main (int argc, char *argv[])
{
assert (argc - 1 == 1);
long n;
int base;
const char *fmt;
{
char *tail;
n = strtol (argv[1], &tail, 0);
assert (tail > argv[1]);
assert (*tail == '\0');
 
char *p = strchr (argv[1], '0');
if (p == 0
|| (p > argv[1] && isdigit (p[-1]))) {
base = 10;
fmt = " %*ld";
} else if (p[1] == 'x' || p[1] == 'X') {
base = 16;
fmt = (p[1] == 'X' ? " %*lX" : " %*lx");
} else {
base = 8;
fmt = " %*lo";
}
}
 
assert (n >= 1);
assert (n <= sqrt (LONG_MAX));
const int width = (int)ceil (2 * log (1 + n) / log (base));
 
long i;
for (i = 1; i <= n; i++) {
long j;
for (j = 1; j <= n; j++) {
printf (fmt, width, i * j);
}
putchar ('\n');
}
 
return 0;
}
</source>
 
Пример работы программы (построение таблицы умножения для <var >n</var> = 5 в шестнадцатиричном представлении):
{{Cmdl |$ |./multable 0X5 }}
<samp > 1 2 3 4 5
2 4 6 8 A
3 6 9 C F
4 8 C 10 14
5 A F 14 19
</samp>{{Cmdl |$ |}}
 
По сравнению с [[#multable.c |исходным вариантом]], пример дополнился следующим фрагментом кода:
<source lang="c">
char *p = strchr (argv[1], '0');
if (p == 0
|| (p > argv[1] && isdigit (p[-1]))) {
base = 10;
fmt = " %*ld";
} else if (p[1] == 'x' || p[1] == 'X') {
base = 16;
fmt = (p[1] == 'X' ? " %*lX" : " %*lx");
} else {
base = 8;
fmt = " %*lo";
}
</source>
 
Кроме того, две из исходных ''констант'' (<code >10</code> при [[#Вычисление предельной разрядности |вычислении предельной разрядности]] и <code >" %*ld"</code> в качестве ''строки формата'' функции <code >printf</code>) заменены в данном варианте ''переменными'' <var >base</var> и <var >fmt</var>, соответственно.
 
{{Якорь |strchr}}
Во фрагменте выше, мы используем функцию <code >strchr</code> (''объявленную'' в ''заголовке'' <code >string.h</code>) поиска первого вхождения символа (кода) в строку<ref name="strchr" /> — в данном случае, поиска символа <code >0</code> в аргументе командной строки.
 
Функция возвращает ''нулевой указатель'' (0 или <code >NULL</code>) если символ не найден.<ref name="strchr" /> Поскольку это означает, что аргумент командной строки не содержит символа <code >0</code> <em >вовсе</em>, очевидно, что он не может содержать его и как часть префикса системы счисления (<code >0x</code> или <code >0</code>), а значит следует использовать десятичную систему счисления при [[#Вычисление предельной разрядности |вычислении предельной разрядности]] и ''указатель представления'' <code >d</code> — «десятичное число»: <source lang="c" enclose="none" >base = 10; fmt = " %*ld";</source>.
 
{{Якорь |isdigit}}
Точно так же обрабатывается случай, когда найденному символу <code >0</code> <em >предшествует</em> любая десятичная цифра — <source lang="c" enclose="none" >p > argv[1] && isdigit (p[-1])</source>. Используемая в этом условии функция <code >isdigit</code> (''объявлена'' в ''заголовке'' <code >ctype.h</code>) возвращает ''истину'' (отличное от 0 значение) в том и только том случае, когда ее аргумент — десятичная цифра; в противном случае — возвращается 0.<ref name="isdigit" /> Разумеется, обращение к <em >предшествующему</em> символу имеет смысл только в том случае, когда найденный символ <code >0</code> не является <em >первым</em> в строке — <source lang="c" enclose="none" >p > argv[1]</source>.
 
Следующим мы проверяем условие <source lang="c" enclose="none" >p[1] == 'x' || p[1] == 'X'</source> — наличие после <code >0</code> признака шестнадцатиричного представления <code >x</code> или <code >X</code>. В этом случае, <var >base</var> присваивается значение 16, <var >fmt</var> — <code >" %*lx"</code> или <code >" %*lX"</code> (в зависимости от регистра символа <code >x</code> в аргументе.)
 
Наконец, если условия выше (наличие цифры перед <code >0</code> ''или'' <code >x</code>, <code >X</code> — после) — не выполнены, мы присваиваем <var >base</var> значение 8, <var >fmt</var> — <code >" %*lo"</code>.
 
Логика выше не покрывает <em >всех</em> возможных значений аргумента командной строки, однако она вполне корректна — поскольку мы предполагаем успешное завершение функции [[#strtol |strtol]] (используя макроподстановки <code >assert</code> выше — при получении значения <var >n</var>), что <em >уже</em> накладывает нужные нам дополнительные ограничения на запись данного аргумента.
 
== Задания ==
Строка 173 ⟶ 277 :
# Измените программу для вывода варианта таблицы в одной из трех альтернативных ориентаций: с обратным (от большего к меньшему) порядком строк; с обратным порядком столбцов; с обратным порядком и строк, и столбцов.
# Дополните программу поддержкой ''неположительных'' значений <var >n</var>. А именно, для <var >n</var> &lt; 0 следует строить таблицу умножения (<var >n</var>, …, -1) × (<var >n</var>, …, -1); для <var >n</var> = 0 — таблицу из единственного значения 0 × 0 = 0. (Указание: воспользуйтесь функцией <code >fabs</code>.<ref name="fabs" />)
# Дополните программу поддержкой необязательного второго аргумента командной строки, используемого как значение основания системы счисления (2‒16 — не предполагая использования кодовой таблицы {{w |ASCII}}, или же 2‒36 в противном случае; по-умолчанию — использовать десятичную систему счисления.) Указание: используйте известность предельной разрядности результатов для определения символьного массива переменной длины. Вывести результирующую строку на стандартный вывод можно используя функцию <code >printf</code> — с указателем формата <code >%s</code>, или же функцию <code >fputs</code><ref name="fputs" /> и предопределенный поток <code >stdout</code>.
# Измените программу для вывода таблицы умножения в шестнадцатеричном (вариант: восьмеричном) представлении. <strong >Обратите внимание</strong>, что изменения потребуются как в строке формата, так и в выражении для предельной разрядности.
# Дополните программу поддержкой необязательного второго аргумента командной строки, используемого как значение основания системы счисления (2‒16 — не предполагая использования кодовой таблицы {{w |ASCII}}, или же 2‒36 в противном случае; по-умолчанию — использовать десятичную систему счисления.) Указание: используйте известность предельной разрядности результатов для определения символьного массива переменной длины. Вывести результирующую строку на стандартный вывод можно используя функцию <code >printf</code> (с указателем формата <code >%s</code>), или же функцию <code >fputs</code><ref name="fputs" /> и предопределенный поток <code >stdout</code>.
# Измените программу так, чтобы можно было выводить произвольный прямоугольный «фрагмент» таблицы умножения: (<var >a</var>, …, <var >b</var>) × (<var >c</var>, …, <var >d</var>).
 
Строка 187 ⟶ 290 :
<ref name="fprintf" >{{Cite web | title = 7.21.6.1 The fprintf function | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=327 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref>
<ref name="fputs" >{{Cite web | title = 7.21.7.4 The fputs function | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=349 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref>
<ref name="isdigit" >{{Cite web | title = 7.4.1.5 The isdigit function | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=219 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref>
<ref name="log" >{{Cite web | title = 7.12.6.7 The log functions | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=262 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref>
<ref name="putchar" >{{Cite web | title = 7.21.7.8 The putchar function | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=351 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref>
<ref name="startup" >{{Cite web | title = 5.1.2.2.1 Program startup | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=31 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref>
<ref name="strchr" >{{Cite web | title = 7.24.5.2 The strchr function | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=385 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref>
<ref name="strtol" >{{Cite web | title = 7.22.1.4 The strtol, strtoll, strtoul, and strtoull functions | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=362 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref>
}}