Перейти к содержанию

Язык Си в примерах/Учимся складывать: различия между версиями

Материал из Викиучебника — открытых книг для открытого мира
Содержимое удалено Содержимое добавлено
Отмена правок до 106713 — для ctags(1) (см. Обсуждение:Язык Си в примерах#Оформление кода); if: новый XML id.
Строка 17: Строка 17:
#include <stdio.h>
#include <stdio.h>


int main () {
int
main ()
{
int a, b;
int a, b;
int r
int r
Строка 51: Строка 53:
#include <stdio.h>
#include <stdio.h>


int main() {
int
main ()
{
double a, b;
double a, b;
int r
int r
Строка 110: Строка 114:
</source>
</source>


{{Якорь |if}}
В этой программе нам потребовался ''условный оператор контекста утверждения'' (англ. {{lang |en|if statement}}) — один из четырех (наряду с <code >&&</code>, <code >||</code> и <code >? :</code>) условных операторов языка. Его синтаксис:<ref name="if" />
В этой программе нам потребовался ''условный оператор контекста утверждения'' (англ. {{lang |en|if statement}}) — один из четырех (наряду с <code >&&</code>, <code >||</code> и <code >? :</code>) условных операторов языка. Его синтаксис:<ref name="if" />
<code >if (<var class="angled" >выражение</var>)
<code >if (<var class="angled" >выражение</var>)
Строка 138: Строка 143:
{
{
double a, b;
double a, b;
int r = scanf ("%lg%lg", &a, &b);
int r
= scanf ("%lg%lg", &a, &b);
assert (r == 2);
assert (r == 2);
printf ((b != 0
printf ((b != 0

Версия от 06:59, 19 марта 2015

Язык Си в примерах


  1. Компиляция программ
  2. Простейшая программа «Hello World»
  3. Учимся складывать
  4. Максимум
  5. Таблица умножения
  6. ASCII-коды символов
  7. Верхний регистр
  8. Скобочки
  9. Факториал
  10. Степень числа
  11. Треугольник Паскаля
  12. Корень уравнения
  13. Система счисления
  14. Сортировка
  15. Библиотека complex
  16. Сортировка на основе qsort
  17. RPN-калькулятор
  18. RPN-калькулятор на Bison
  19. Простая грамматика
  20. Задача «Расчёт сопротивления схемы»
  21. Простая реализация конечного автомата
  22. Использование аргументов командной строки
  23. Чтение и печать без использования stdio
  24. Декодирование звукозаписи в формате ADX
  25. Другие примеры
  26. XCC C

Разнообразные вычисления — моделирование, решение алгебраических и дифференциальных уравнений — это то, для чего и создавались первые компьютеры. Давайте и мы научимся использовать компьютер для вычислений. Начнём со сложения двух чисел.

Вариант «простой»

В отличие от рассмотренной ранее простейшей программы, в данной задаче нам потребуются переменные — ячейки памяти, в которые функция ввода сохранит введенные пользователем числа, подлежащие сложению.

Как и в случае функций (в том числе 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 (intdouble) и указатели преобразований (%d%lg.)

Здесь следует отметить, что в случае scanf совершенно идентично будут действовать указатели преобразований %lg, %lf и %le. Напротив, в случае printf не будет разницы между %lg и %g. Причины такого поведения мы также пока оставим без внимания.

Желающим изучить использование других числовых типов в этой задаче предлагается обратиться к разделу «Числовые типы» приложения.

Л-значения и ссылки

Вызов функции в чем-то подобен отправке письма. Мы указываем на конверте имя функции, вкладываем в конверт копии значений, которые хотим передать, и отправляем письмо (вызываем функцию).

В некоторых случаях, однако, мы хотим получить от функции что-либо обратно. Иногда, для этого достаточно предусмотренного языком возвращаемого значения. Иначе, можно отправить функции своего рода конверт с обратным адресом (один или несколько). Именно для этого — для получения адреса л-значения — служит унарный оператор &.

Так, запись &a означает адрес ячейки памяти, выделенной для хранения значения переменной a, или же, проще, — ссылка (англ. reference) на переменную a.

Справа от оператора & указывается л-значение (англ. lvalue.[6] Не вдаваясь в подробности отметим, что под л-значением понимают любое выражение, которое может стоять слева от оператора присваивания =. В частности, л-значением является любое выражение, состоящие из имени действительной в данном контексте (англ. scope) переменной.

Для обращения к ячейки памяти по ссылке используется унарный оператор *. Так, выражение *(&a) = 1 полностью равнозначно a = 1.

Вариант «арифметический»

В программе ниже мы кроме суммы вычислим разность и произведение введенных чисел, а также, если второе число отлично от нуля, — частное от деления первого числа на второе.

#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) — один из четырех (наряду с &&, || и ? :) условных операторов языка. Его синтаксис:[7]

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).

Общий синтаксис тернарного оператора ? : следующий:[8]

выражение ? если-истинно : если-ложно

Где выражение вычисляется первым, после чего, если оно истинно (отлично от 0), вычисляется — и принимается результатом оператора в целом — выражение если-истинно; в противном случае — если-ложно.

Примечания

  1. 7.21.6.1 The fprintf function(англ.) WG14 N1570 Committee Draft Проверено 2012-11-19 г.
  2. 7.21.6.2 The fscanf function(англ.) WG14 N1570 Committee Draft Проверено 2012-11-19 г.
  3. 7.21 Input/output stdio.h(англ.) WG14 N1570 Committee Draft Проверено 2012-11-19 г.
  4. 7.2.1.1 The assert macro(англ.) WG14 N1570 Committee Draft Проверено 2012-11-19 г.
  5. 6.7.9 Initialization(англ.) WG14 N1570 Committee Draft Проверено 2012-11-19 г.
  6. 6.3.2.1 Lvalues, arrays, and function designators(англ.) WG14 N1570 Committee Draft Проверено 2012-11-19 г.
  7. 6.8.4.1 The if statement(англ.) WG14 N1570 Committee Draft Проверено 2012-11-19 г.
  8. 6.5.15 Conditional operator(англ.) WG14 N1570 Committee Draft Проверено 2012-11-19 г.