Язык Си в примерах/Учимся складывать: различия между версиями
Вводная дополнена «стандартной формулой»; новые XML id; →Примечания: {{Cite web | … | publisher = ISO/IEC | …}}. |
Новые XML id для примеров; ссылки; →Вариант «произвольный»: новый раздел. |
||
Строка 17: | Строка 17: | ||
Как и в случае функций (в том числе <code>main</code>), объявление переменных в Си предполагает указание их типов. Основные числовые типы языка — <code>int</code> ([[w:Целое число|целое число]] фиксированной разрядности) и <code>double</code> ([[w:Число с плавающей запятой|число с плавающей запятой]].) Поскольку мы уже использовали тип <code>int</code> в описании функции <code>main</code>, применим его же в данной задаче. |
Как и в случае функций (в том числе <code>main</code>), объявление переменных в Си предполагает указание их типов. Основные числовые типы языка — <code>int</code> ([[w:Целое число|целое число]] фиксированной разрядности) и <code>double</code> ([[w:Число с плавающей запятой|число с плавающей запятой]].) Поскольку мы уже использовали тип <code>int</code> в описании функции <code>main</code>, применим его же в данной задаче. |
||
{{Якорь |plus.c}} |
|||
<source lang="c"> |
<source lang="c"> |
||
#include <assert.h> |
#include <assert.h> |
||
Строка 39: | Строка 40: | ||
{{Якорь |scanf}} |
{{Якорь |scanf}} |
||
Вводимые пользователем числа помещаются в эти переменные функцией <code >scanf</code>, вызываемой в коде выше как <source lang="c" enclose="none" >scanf ("%d%d", &a, &b)</source>.<ref name="fscanf" /> Здесь, как и в <code >printf</code>, используется указатель преобразования <code >%d</code>, означающий на этот раз ''считывание'' числа в десятичной форме (возможно — предваряемого пробелами). Поскольку указатель повторен дважды, два числа будут считаны и помещены в упомянутые в аргументах две переменные <var >a</var> и <var >b</var>. (Необходимый здесь унарный оператор <code >&</code> оставим пока без внимания.) |
Вводимые пользователем числа помещаются в эти переменные функцией <code >scanf</code>, вызываемой в коде выше как <source lang="c" enclose="none" >scanf ("%d%d", &a, &b)</source>.<ref name="fscanf" /> Здесь, как и в <code >printf</code>, используется указатель преобразования <code >%d</code>, означающий на этот раз ''считывание'' числа в десятичной форме (возможно — предваряемого пробелами). Поскольку указатель повторен дважды, два числа будут считаны и помещены в упомянутые в аргументах две переменные <var >a</var> и <var >b</var>. (Необходимый здесь унарный оператор [[#& |<code >&</code>]] оставим пока без внимания.) |
||
Как и <code >printf</code>, функция <code >scanf</code> ''объявлена'' в ''заголовке'' (англ. {{lang |en|header}}) <code >stdio.h</code>.<ref name="stdio.h" /> |
Как и <code >printf</code>, функция <code >scanf</code> ''объявлена'' в ''заголовке'' (англ. {{lang |en|header}}) <code >stdio.h</code>.<ref name="stdio.h" /> |
||
Строка 57: | Строка 58: | ||
Рассмотренную [[#Вариант «простой»|выше]] программу несложно изменить для использования [[w:Число с плавающей запятой|чисел с плавающей запятой.]] |
Рассмотренную [[#Вариант «простой»|выше]] программу несложно изменить для использования [[w:Число с плавающей запятой|чисел с плавающей запятой.]] |
||
{{Якорь |plus-fp.c}} |
|||
<source lang="c"> |
<source lang="c"> |
||
#include <assert.h> |
#include <assert.h> |
||
Строка 92: | Строка 94: | ||
{{Якорь |*}} |
{{Якорь |*}} |
||
Для обращения к ячейки памяти по ссылке используется унарный оператор <code >*</code>. Так, выражение <source lang="c" enclose="none" >*(&a) = 1</source> полностью равнозначно <source lang="c" enclose="none" >a = 1</source>. |
Для обращения к ячейки памяти по ссылке используется унарный оператор <code >*</code>. Так, выражение <source lang="c" enclose="none" >*(&a) = 1</source> полностью равнозначно <source lang="c" enclose="none" >a = 1</source>. |
||
== Вариант «произвольный» == |
|||
Стандарт [[w:C11 |C11]] требует от реализации языка поддержки [[../Скалярные типы#Числовые типы |числовых типов]] разрядности 8, 16, 32 и 64 бит.<ref name="int-least" /> Если по каким-либо причинам в вычислениях требуется бо́льшая разрядность, следует использовать библиотеки операций с числами <em >произвольной</em> разрядности — как, например, [[w:en:GNU Multiple Precision Arithmetic Library |GNU MP.]]<ref name="gmp" /> |
|||
В частности, благодаря GNU MP, пример ниже выведет сумму двух введенных чисел даже если любое из них — или результат сложения — превышает 2⁶⁴. |
|||
Код имеет следующие отличия от [[#plus.c |исходного]] варианта: |
|||
# ввод-вывод чисел произвольной разрядности выполняется функциями <code >gmp_scanf</code> и <code >gmp_printf</code> — ''определенными'' GNU MP и ''объявленными'' в <code >gmp.h</code>; заголовок <code >stdio.h</code> не требуется; |
|||
# вместо оператора сложения <code >+</code> используется вызов функции <source lang="c" enclose="none" >mpz_add (a, a, b)</source>; в такой форме, действие функции подобно действию выражения <source lang="c" enclose="none" >a += b</source> для переменных «встроенных» целочисленных типов; |
|||
# переменные GNU MP требуют обязательной инициализации, которая в примере выполнена функцией <code >mpz_inits ()</code>; |
|||
# тип используемых для вычисления переменных — <code >mpz_t</code>. |
|||
{{Якорь |plus-imp.c}} |
|||
<source lang="c"> |
|||
#include <assert.h> |
|||
#include <gmp.h> |
|||
int |
|||
main (void) |
|||
{ |
|||
mpz_t a, b; |
|||
mpz_inits (a, b, 0); |
|||
int r |
|||
= gmp_scanf ("%Zd%Zd", a, b); |
|||
assert (r == 2); |
|||
mpz_add (a, a, b); |
|||
gmp_printf ("%Zd\n", a); |
|||
return 0; |
|||
} |
|||
</source> |
|||
== Вариант «арифметический» == |
== Вариант «арифметический» == |
||
Строка 97: | Строка 131: | ||
В программе ниже мы кроме суммы вычислим разность и произведение введенных чисел, а также, если второе число отлично от нуля, — частное от деления первого числа на второе. |
В программе ниже мы кроме суммы вычислим разность и произведение введенных чисел, а также, если второе число отлично от нуля, — частное от деления первого числа на второе. |
||
{{Якорь |arith-if.c}} |
|||
<source lang="c"> |
<source lang="c"> |
||
#include <assert.h> |
#include <assert.h> |
||
Строка 146: | Строка 181: | ||
Используя условный оператор контекста ''выражения'' (англ. {{lang|en|conditional operator}}), а также приняв во внимание тот факт, что <code>printf</code> проигнорирует «избыточные» аргументы (аргументы сверх количества, требуемого указателями преобразований в указанной первым аргументом строке формата), можно незначительно сократить код [[#Вариант «арифметический»|предыдущего примера]]. |
Используя условный оператор контекста ''выражения'' (англ. {{lang|en|conditional operator}}), а также приняв во внимание тот факт, что <code>printf</code> проигнорирует «избыточные» аргументы (аргументы сверх количества, требуемого указателями преобразований в указанной первым аргументом строке формата), можно незначительно сократить код [[#Вариант «арифметический»|предыдущего примера]]. |
||
{{Якорь |arith.c}} |
|||
<source lang="c"> |
<source lang="c"> |
||
#include <assert.h> |
#include <assert.h> |
||
Строка 187: | Строка 223: | ||
<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="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="fscanf" >{{Cite web | title = 7.21.6.2 The fscanf function | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=335 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref> |
<ref name="fscanf" >{{Cite web | title = 7.21.6.2 The fscanf function | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=335 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref> |
||
<ref name="gmp" >{{Cite web | title = The GNU MP Bignum Library | url = //gmplib.org/ | lang = en | accessdate = 2015-04-04}}</ref> |
|||
<ref name="if" >{{Cite web | title = 6.8.4.1 The if statement | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=166 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref> |
<ref name="if" >{{Cite web | title = 6.8.4.1 The if statement | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=166 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref> |
||
<ref name="init" >{{Cite web | title = 6.7.9 Initialization | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=157 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref> |
<ref name="init" >{{Cite web | title = 6.7.9 Initialization | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=157 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref> |
||
<ref name="int-least" >{{Cite web | title = 7.20.1.2 Minimum-width integer types | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=308 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref> |
|||
<ref name="lvalues et al" >{{Cite web | title = 6.3.2.1 Lvalues, arrays, and function designators | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=72 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref> |
<ref name="lvalues et al" >{{Cite web | title = 6.3.2.1 Lvalues, arrays, and function designators | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=72 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref> |
||
<ref name="stdio.h" >{{Cite web | title = 7.21 Input/output <code >stdio.h</code> | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=314 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref> |
<ref name="stdio.h" >{{Cite web | title = 7.21 Input/output <code >stdio.h</code> | url = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#page=314 | work = WG14 N1570 Committee Draft | publisher = ISO/IEC | datepublished = 2011-04-12 | lang = en | accessdate = 2012-11-19}}</ref> |
Версия от 21:35, 29 мая 2015
- Компиляция программ
- Простейшая программа «Hello World»
- Учимся складывать
- Максимум
- Таблица умножения
- ASCII-коды символов
- Верхний регистр
- Скобочки
- Факториал
- Степень числа
- Треугольник Паскаля
- Корень уравнения
- Система счисления
- Сортировка
- Библиотека complex
- Сортировка на основе qsort
- RPN-калькулятор
- RPN-калькулятор на Bison
- Простая грамматика
- Задача «Расчёт сопротивления схемы»
- Простая реализация конечного автомата
- Использование аргументов командной строки
- Чтение и печать без использования stdio
- Декодирование звукозаписи в формате ADX
- Другие примеры
Разнообразные вычисления — моделирование, решение алгебраических и дифференциальных уравнений — это то, для чего и создавались первые компьютеры. Давайте и мы научимся использовать компьютер для вычислений. Начнём со сложения двух чисел.
- Дано
- два разделенных пробельными символами целых чисел (в «текстовом» десятичном представлении) на стандартном вводе программы.
- Найти
- значение суммы (разности, произведения, частного от деления — если имеет смысл) этих двух чисел.
- Новые элементы языка
- переменные; тип double; операторы &, *, ? : контекста выражения и оператор if контекста утверждения; макроподстановка assert, функции printf, scanf.
Вариант «простой»
В отличие от рассмотренной ранее простейшей программы, в данной задаче нам потребуются переменные — ячейки памяти, в которые функция ввода сохранит введенные пользователем числа, подлежащие сложению.
Как и в случае функций (в том числе main
), объявление переменных в Си предполагает указание их типов. Основные числовые типы языка — int
(целое число фиксированной разрядности) и double
(число с плавающей запятой.) Поскольку мы уже использовали тип int
в описании функции main
, применим его же в данной задаче.
#include <assert.h>
#include <stdio.h>
int
main ()
{
int a, b;
int r
= scanf ("%d%d", &a, &b);
assert (r == 2);
printf ("%d\n", a + b);
return 0;
}
Рассмотрим выполнение этой программы почти с ее завершения — вызова функции printf
.[1] Данная функция выведет целое число в десятичной форме (согласно указателю преобразования %d
), завершая вывод переводом строки (\n
).
Число, которое будет выведено, является результатом вычисления выражения a + b
— или же, проще говоря, — суммой значений переменных a и b.
Вводимые пользователем числа помещаются в эти переменные функцией scanf
, вызываемой в коде выше как scanf ("%d%d", &a, &b)
.[2] Здесь, как и в printf
, используется указатель преобразования %d
, означающий на этот раз считывание числа в десятичной форме (возможно — предваряемого пробелами). Поскольку указатель повторен дважды, два числа будут считаны и помещены в упомянутые в аргументах две переменные a и b. (Необходимый здесь унарный оператор &
оставим пока без внимания.)
Как и printf
, функция scanf
объявлена в заголовке (англ. header) stdio.h
.[3]
В простейшей программе от действий пользователя не зависело ровным счетом ничего. Каким, однако, будет результат выполнения данной программы, если ввод пользователя не будет начат двумя числами в десятичной форме?
Для проверки соответствия ввода пользователя требованиям программы мы сохраняем (=
) результат выполнения scanf
— количество успешно измененных переменных — в целочисленной переменной с именем r (int r
), после чего требуем равенства ее значения двум (assert (r == 2);
.)
Действие макроподстановки assert
заключается в вычислении выражения, переданного ей первым (и единственным) аргументом и аварийном завершении программы в случае, если полученное значение — ноль («логическая ложь».)[4]
Наконец, в самом начале функции main
определены (помимо упомянутой уже r) целочисленные переменные a и b. Их значение в начале выполнения функции main
может быть произвольным,[5] но после успешного (что проверяется использованием assert
) завершения scanf
они будут содержать два числа, которые удалось последовательно считать со стандартного ввода.
Вариант «дробный»
Рассмотренную выше программу несложно изменить для использования чисел с плавающей запятой.
#include <assert.h>
#include <stdio.h>
int
main ()
{
double a, b;
int r
= scanf ("%lg%lg", &a, &b);
assert (r == 2);
printf ("%lg\n", a + b);
return 0;
}
Как видно, в этом случае изменяются лишь тип переменных a, b (int
→ double
) и указатели преобразований (%d
→ %lg
.)
Здесь следует отметить, что в случае scanf
совершенно идентично будут действовать указатели преобразований %lg
, %lf
и %le
. Напротив, в случае printf
не будет разницы между %lg
и %g
. Причины такого поведения мы также пока оставим без внимания.
Желающим изучить использование других числовых типов в этой задаче предлагается обратиться к разделу «Числовые типы» приложения.
Л-значения и ссылки
Вызов функции в чем-то подобен отправке письма. Мы указываем на конверте имя функции, вкладываем в конверт копии значений, которые хотим передать, и отправляем письмо (вызываем функцию).
В некоторых случаях, однако, мы хотим получить от функции что-либо обратно. Иногда, для этого достаточно предусмотренного языком возвращаемого значения. Иначе, можно отправить функции своего рода конверт с обратным адресом (один или несколько). Именно для этого — для получения адреса л-значения — служит унарный оператор &
.
Так, запись &a
означает адрес ячейки памяти, выделенной для хранения значения переменной a
, или же, проще, — ссылка (англ. reference) на переменную a
.
Справа от оператора &
указывается л-значение (англ. lvalue.[6] Не вдаваясь в подробности отметим, что под л-значением понимают любое выражение, которое может стоять слева от оператора присваивания =
. В частности, л-значением является любое выражение, состоящие из имени действительной в данном контексте (англ. scope) переменной.
Для обращения к ячейки памяти по ссылке используется унарный оператор *
. Так, выражение *(&a) = 1
полностью равнозначно a = 1
.
Вариант «произвольный»
Стандарт C11 требует от реализации языка поддержки числовых типов разрядности 8, 16, 32 и 64 бит.[7] Если по каким-либо причинам в вычислениях требуется бо́льшая разрядность, следует использовать библиотеки операций с числами произвольной разрядности — как, например, GNU MP.[8]
В частности, благодаря GNU MP, пример ниже выведет сумму двух введенных чисел даже если любое из них — или результат сложения — превышает 2⁶⁴.
Код имеет следующие отличия от исходного варианта:
- ввод-вывод чисел произвольной разрядности выполняется функциями
gmp_scanf
иgmp_printf
— определенными GNU MP и объявленными вgmp.h
; заголовокstdio.h
не требуется; - вместо оператора сложения
+
используется вызов функцииmpz_add (a, a, b)
; в такой форме, действие функции подобно действию выраженияa += b
для переменных «встроенных» целочисленных типов; - переменные GNU MP требуют обязательной инициализации, которая в примере выполнена функцией
mpz_inits ()
; - тип используемых для вычисления переменных —
mpz_t
.
#include <assert.h>
#include <gmp.h>
int
main (void)
{
mpz_t a, b;
mpz_inits (a, b, 0);
int r
= gmp_scanf ("%Zd%Zd", a, b);
assert (r == 2);
mpz_add (a, a, b);
gmp_printf ("%Zd\n", a);
return 0;
}
Вариант «арифметический»
В программе ниже мы кроме суммы вычислим разность и произведение введенных чисел, а также, если второе число отлично от нуля, — частное от деления первого числа на второе.
#include <assert.h>
#include <stdio.h>
int
main ()
{
double a, b;
int r
= scanf ("%lg%lg", &a, &b);
assert (r == 2);
if (b != 0) {
printf (("a + b = %lg\n"
"a - b = %lg\n"
"a * b = %lg\n"
"a / b = %lg\n"),
a + b, a - b, a * b, a / b);
} else {
printf (("a + b = %lg\n"
"a - b = %lg\n"
"a * b = %lg\n"),
a + b, a - b, a * b);
}
return 0;
}
В этой программе нам потребовался условный оператор контекста утверждения (англ. if statement) — один из четырех (наряду с &&
, ||
и ? :
) условных операторов языка. Его синтаксис:[9]
if (выражение) тело-если-истинно
if (выражение) тело-если-истинно else тело-если-ложно
Где тело-если-истинно и тело-если-ложно могут быть (каждый) единственным утверждением (завершаемым ;
), или же, как в примере выше, — { }
-блоком.
В случае, если результат вычисления выражения — истина (другими словами — отличен от 0), выполняется тело-если-истинно; в противном случае (и если используется else
) — тело-если-ложно.
Заметьте, что каждое простое утверждение (англ. statement) завершается точкой с запятой. Одна из самых популярных синтаксических ошибок начинающих программистов — не ставить точку c запятой после утверждений.
Вариант «тернарный»
Используя условный оператор контекста выражения (англ. conditional operator), а также приняв во внимание тот факт, что printf
проигнорирует «избыточные» аргументы (аргументы сверх количества, требуемого указателями преобразований в указанной первым аргументом строке формата), можно незначительно сократить код предыдущего примера.
#include <assert.h>
#include <stdio.h>
int
main ()
{
double a, b;
int r
= scanf ("%lg%lg", &a, &b);
assert (r == 2);
printf ((b != 0
? ("a + b = %lg\n"
"a - b = %lg\n"
"a * b = %lg\n"
"a / b = %lg\n")
: ("a + b = %lg\n"
"a - b = %lg\n"
"a * b = %lg\n")),
a + b, a - b, a * b,
(b != 0 ? a / b : 42));
return 0;
}
В этом варианте, код вновь содержит те же самые строковые константы, что и в предыдущем, однако используется лишь один вызов функции printf
— первым аргументом которой (в зависимости от значения переменной b
) окажется одна из этих констант.
Число 42, которое передается printf
пятым аргументом в случае нулевого значения b, может быть произвольным. Поскольку в этом случае функция printf
получает строку формата лишь с тремя указателями формата, фактически использованы будут лишь аргументы с первого (формат) по четвертый (произведение a и b).
Общий синтаксис тернарного оператора ? :
следующий:[10]
выражение ? если-истинно : если-ложно
Где выражение вычисляется первым, после чего, если оно истинно (отлично от 0), вычисляется — и принимается результатом оператора в целом — выражение если-истинно; в противном случае — если-ложно.
Примечания
- ↑ 7.21.6.1 The fprintf function(англ.) WG14 N1570 Committee Draft. ISO/IEC (2011-04-12). Проверено 2012-11-19 г.
- ↑ 7.21.6.2 The fscanf function(англ.) WG14 N1570 Committee Draft. ISO/IEC (2011-04-12). Проверено 2012-11-19 г.
- ↑ 7.21 Input/output
stdio.h
(англ.) WG14 N1570 Committee Draft. ISO/IEC (2011-04-12). Проверено 2012-11-19 г. - ↑ 7.2.1.1 The assert macro(англ.) WG14 N1570 Committee Draft. ISO/IEC (2011-04-12). Проверено 2012-11-19 г.
- ↑ 6.7.9 Initialization(англ.) WG14 N1570 Committee Draft. ISO/IEC (2011-04-12). Проверено 2012-11-19 г.
- ↑ 6.3.2.1 Lvalues, arrays, and function designators(англ.) WG14 N1570 Committee Draft. ISO/IEC (2011-04-12). Проверено 2012-11-19 г.
- ↑ 7.20.1.2 Minimum-width integer types(англ.) WG14 N1570 Committee Draft. ISO/IEC (2011-04-12). Проверено 2012-11-19 г.
- ↑ The GNU MP Bignum Library(англ.) Проверено 2015-04-04 г.
- ↑ 6.8.4.1 The if statement(англ.) WG14 N1570 Committee Draft. ISO/IEC (2011-04-12). Проверено 2012-11-19 г.
- ↑ 6.5.15 Conditional operator(англ.) WG14 N1570 Committee Draft. ISO/IEC (2011-04-12). Проверено 2012-11-19 г.