1 of 192

Язык программирования Си

Владимир Валерьевич Соловьёв

Huawei, НГУ

vladimir.conwor@gmail.com

t.me/conwor

vk.com/conwor

Базовый набор инструментов

2 of 192

Содержание

2

3 of 192

Функции

Основные элементы программы - фрагменты кода, исполняющиеся последовательно от входа в функцию до выхода из неё

3

4 of 192

Функции

Основные элементы программы - фрагменты кода, исполняющиеся последовательно от входа в функцию до выхода из неё

Функции могут вызывать друг друга, передавать и принимать параметры, возвращать значения - всё это будет позже

4

5 of 192

Функции

Основные элементы программы - фрагменты кода, исполняющиеся последовательно от входа в функцию до выхода из неё

Функции могут вызывать друг друга, передавать и принимать параметры, возвращать значения - всё это будет позже

Скомпилированный код функций находится в адресном пространстве программы - это факт будет очень важен в дальнейшем

5

6 of 192

Функция main

В базовом наборе инструментов нам достаточно одной функции с именем main, возвращающей значение типа int

6

7 of 192

Функция main

В базовом наборе инструментов нам достаточно одной функции с именем main, возвращающей значение типа int

Это функция, которую позовёт операционная система, создав адресное пространство для нашей программы и загрузив её в него

Это не совсем так, но мы будем работать именно в такой модели (подобных допущений в дальнейшем будет много)

7

8 of 192

Функция main

В базовом наборе инструментов нам достаточно одной функции с именем main, возвращающей значение типа int

Это функция, которую позовёт операционная система, создав адресное пространство для нашей программы и загрузив её в него

Это не совсем так, но мы будем работать именно в такой модели (подобных допущений в дальнейшем будет много)

Возвращаемое значение будет получено операционной системой и может быть использовано, как признак успешного (как правило, 0) или неуспешного (не 0) завершения работы

8

9 of 192

Минимальная программа на языке Си

9

int main() {

return 0;

}

10 of 192

Минимальная программа на языке Си

10

int main() {

return 0;

}

Тип возвращаемого значения

11 of 192

Минимальная программа на языке Си

11

int main() {

return 0;

}

Тип возвращаемого значения

Имя функции

12 of 192

Минимальная программа на языке Си

12

int main() {

return 0;

}

Тип возвращаемого значения

Имя функции

Список параметров (пустой)

13 of 192

Минимальная программа на языке Си

13

int main() {

return 0;

}

Тип возвращаемого значения

Имя функции

Список параметров (пустой)

Тело функции

14 of 192

Прототип функции

Тип возвращаемого значения, имя и список типов параметров образуют прототип функции

14

15 of 192

Тело функции

Список операторов, заключённый в фигурные скобки, написанный после прототипа, образует тело функции

15

16 of 192

Тело функции

Список операторов, заключённый в фигурные скобки, написанный после прототипа, образует тело функции

Выполнение функции - это выполнение её тела сверху вниз, пока не встретится оператор выхода из функции

16

17 of 192

Оператор выхода из функции

17

return expr;

18 of 192

Оператор выхода из функции

18

return expr;

Произвольное выражение, результат вычисления которого является возвращаемым значением

19 of 192

Оператор выхода из функции

Может быть написан в любом месте тела функции

19

20 of 192

Оператор выхода из функции

Может быть написан в любом месте тела функции

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

20

21 of 192

Оператор выхода из функции

Может быть написан в любом месте тела функции

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

21

int main() {

operator1;

operator2;

return 0;

operator3;

operator4;

}

22 of 192

Оператор выхода из функции

Может быть написан в любом месте тела функции

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

22

int main() {

operator1;

operator2;

return 0;

operator3;

operator4;

}

23 of 192

Объекты

23

24 of 192

Объекты

Объекты - это данные, которые читают, модифицируют и записывают функции программы в процессе своего исполнения

24

25 of 192

Объекты

Объекты - это данные, которые читают, модифицируют и записывают функции программы в процессе своего исполнения

Существует три вида объектов:

  1. Глобальные переменные
  2. Локальные переменные
  3. Объекты в динамической памяти

25

26 of 192

Объекты

Объекты - это данные, которые читают, модифицируют и записывают функции программы в процессе своего исполнения

Существует три вида объектов:

  • Глобальные переменные - в базовом наборе инструментов нам их будет достаточно
  • Локальные переменные
  • Объекты в динамической памяти

26

27 of 192

Переменные

Участки памяти, к которым в программе обращаются по именам

27

28 of 192

Переменные

Участки памяти, к которым в программе обращаются по именам

Свойства: тип, имя, область видимости, тип хранения, время жизни

28

29 of 192

Переменные

Участки памяти, к которым в программе обращаются по именам

Свойства: тип, имя, область видимости, тип хранения, время жизни

Операции: чтение, запись

29

30 of 192

Имена

Должны быть уникальными в одной и то же области видимости

30

31 of 192

Имена

Должны быть уникальными в одной и то же области видимости

Имеют значение только для исходного кода, в скомпилированном машинном пропадают

Это не полная правда, имена остаются и используются для связи нескольких скомпилированных по отдельностей частей одной программы

31

32 of 192

Имена

Должны быть уникальными в одной и то же области видимости

Имеют значение только для исходного кода, в скомпилированном машинном пропадают

Это не полная правда, имена остаются и используются для связи нескольких скомпилированных по отдельностей частей одной программы

Имена переменных (как и прочие идентификаторы в языке Си) могут содержать буквы, цифры и подчерк; должны начинаться с букв или подчерка

32

33 of 192

Область видимости

Область видимости переменной (scope) - участок программы, в котором переменная может быть использована (записана или прочитана)

33

34 of 192

Область видимости

Область видимости переменной (scope) - участок программы, в котором переменная может быть использована (записана или прочитана)

Существуют четыре вида области видимости:

  1. Функция
  2. Файл
  3. Блок
  4. Прототип функции

34

35 of 192

Область видимости

Область видимости переменной (scope) - участок программы, в котором переменная может быть использована (записана или прочитана)

Существуют четыре вида области видимости:

  • Функция
  • Файл - в базовом наборе инструментов нам его будет достаточно
  • Блок
  • Прототип функции

35

36 of 192

Тип хранения

Тип хранения (storage duration) - способ хранения переменной, по которому определяется её время жизни

36

37 of 192

Тип хранения

Тип хранения (storage duration) - способ хранения переменной, по которому определяется её время жизни

Есть три типа хранения:

  1. Статический (static)
  2. Автоматический (automatic)
  3. Динамический (allocated)

37

38 of 192

Тип хранения

Тип хранения (storage duration) - способ хранения переменной, по которому определяется её время жизни

Есть три типа хранения:

  • Статический (static) - в базовом наборе инструментов нам его будет достаточно
  • Автоматический (automatic)
  • Динамический (allocated)

38

39 of 192

Время жизни

Время жизни (lifetime) - период исполнения программы, во время которого для переменной гарантируется:

  1. Существование
  2. Постоянный адрес
  3. Хранение в ней последнего присвоенного значения

39

40 of 192

Время жизни

Время жизни (lifetime) - период исполнения программы, во время которого для переменной гарантируется:

  • Существование
  • Постоянный адрес
  • Хранение в ней последнего присвоенного значения

Для статического типа хранения время жизни определяется, как всё время исполнения программы

40

41 of 192

TL;DR

Область видимости, тип хранения и время жизни - очень важные понятия, не такие простые, как кажутся на первый взгляд; мы будем периодически возвращаться к ним

41

42 of 192

TL;DR

Область видимости, тип хранения и время жизни - очень важные понятия, не такие простые, как кажутся на первый взгляд; мы будем периодически возвращаться к ним

А пока (в базовом наборе, для глобальных переменных) всё просто:

  1. Область видимости - весь файл (для программы из одного файла - вся программа)
  2. Время жизни - вся программа

42

43 of 192

Глобальные переменные

43

int x;

int main(args) {

x = 42;

x = x + 37;

return 0;

}

44 of 192

Глобальные переменные

44

int x;

int main(args) {

x = 42;

x = x + 37;

return 0;

}

Тип переменной

45 of 192

Глобальные переменные

45

int x;

int main(args) {

x = 42;

x = x + 37;

return 0;

}

Тип переменной

Имя переменной

46 of 192

Глобальные переменные

46

int x;

int main(args) {

x = 42;

x = x + 37;

return 0;

}

Тип переменной

Имя переменной

Определение переменной

47 of 192

Глобальные переменные

47

int x;

int main(args) {

x = 42;

x = x + 37;

return 0;

}

Тип переменной

Имя переменной

Записи (присваивания) переменной

Определение переменной

48 of 192

Глобальные переменные

48

int x;

int main(args) {

x = 42;

x = x + 37;

return 0;

}

Тип переменной

Имя переменной

Определение переменной

Записи (присваивания) переменной

Чтение переменной

49 of 192

Определение переменных

Можно определить несколько переменных через запятую (у них будет один и тот же тип)

49

50 of 192

Определение переменных

Можно определить несколько переменных через запятую (у них будет один и тот же тип)

Каждой переменной можно присвоить начальное значение (initial value)

50

51 of 192

Определение переменных

Можно определить несколько переменных через запятую (у них будет один и тот же тип)

Каждой переменной можно присвоить начальное значение (initial value)

51

int x = 42, y = 37, z;

52 of 192

Инициализация глобальных переменных

Начальное значение глобальной переменной должно вычисляться константным выражением (constant expression) - не содержать использований переменных, вызовов функций и некоторых других конструкций

52

53 of 192

Инициализация глобальных переменных

Начальное значение глобальной переменной должно вычисляться константным выражением (constant expression) - не содержать использований переменных, вызовов функций и некоторых других конструкций

Начальное значение по умолчанию равно 0

53

54 of 192

Инициализация глобальных переменных

Начальное значение глобальной переменной должно вычисляться константным выражением (constant expression) - не содержать использований переменных, вызовов функций и некоторых других конструкций

Начальное значение по умолчанию равно 0

Начальные значения присваиваются один раз перед началом работы функции main

54

55 of 192

Объявление перед использованием

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

Это связано с устаревшими ограничениями на эффективность работы компилятора и неактуально для современных языков

55

56 of 192

Объявление перед использованием

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

Это связано с устаревшими ограничениями на эффективность работы компилятора и неактуально для современных языков

56

Не определена, а именно объявлена; это разные термины, но об этом позже

57 of 192

Объявление перед использованием

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

Это связано с устаревшими ограничениями на эффективность работы компилятора и неактуально для современных языков

Если вы объявите переменные текстуально после функции main, вы не сможете их использовать

57

Не определена, а именно объявлена; это разные термины, но об этом позже

58 of 192

58

int main() {

x = 37;

return 0;

}

int x = 42;

59 of 192

59

int main() {

x = 37;

return 0;

}

int x = 42;

main.c: In function ‘main’:

main.c:2:5: error: ‘x’ undeclared (first use in this function)

2 | x = 37;

| ^

60 of 192

Вывод данных

В языке Си принят вывод данных по формату (formatted output) - функции вывода принимают:

60

61 of 192

Вывод данных

В языке Си принят вывод данных по формату (formatted output) - функции вывода принимают:

  1. Строку формата, содержащую:
    1. Обычные символы, не являющиеся символами \ и %, передающиеся на вывод без изменений

61

62 of 192

Вывод данных

В языке Си принят вывод данных по формату (formatted output) - функции вывода принимают:

  • Строку формата, содержащую:
    • Обычные символы, не являющиеся символами \ и %, передающиеся на вывод без изменений
    • Эскейп (управляющие) последовательности (escape-sequence), начинающиеся с символа \, заменяющиеся в выводе на специальные символы

62

63 of 192

Вывод данных

В языке Си принят вывод данных по формату (formatted output) - функции вывода принимают:

  • Строку формата, содержащую:
    • Обычные символы, не являющиеся символами \ и %, передающиеся на вывод без изменений
    • Эскейп (управляющие) последовательности (escape-sequence), начинающиеся с символа \, заменяющиеся в выводе на специальные символы
    • Спецификаторы формата (conversion specifications), начинающиеся с символа %, заменяющиеся в выводе на значение очередного аргумента

63

64 of 192

Вывод данных

В языке Си принят вывод данных по формату (formatted output) - функции вывода принимают:

  • Строку формата, содержащую:
    • Обычные символы, не являющиеся символами \ и %, передающиеся на вывод без изменений
    • Эскейп (управляющие) последовательности (escape-sequence), начинающиеся с символа \, заменяющиеся в выводе на специальные символы
    • Спецификаторы формата (conversion specifications), начинающиеся с символа %, заменяющиеся в выводе на значение очередного аргумента

  • Список аргументов произвольной длины

64

65 of 192

Вывод данных

65

#include <stdio.h>

int main() {

printf("Hello, world!");

return 0;

}

66 of 192

Вывод данных

66

#include <stdio.h>

int main() {

printf("Hello, world!");

return 0;

}

Подключаем библиотеку

67 of 192

Вывод данных

67

#include <stdio.h>

int main() {

printf("Hello, world!");

return 0;

}

print formatted - печать по формату

функция, печатающая в стандартный поток вывода (если по простому, на экран)

Подключаем библиотеку

68 of 192

printf

68

printf("...", ...);

Строка формата - может содержать обычный текст, эскейп последовательности и спецификаторы формата

Список аргументов, которые будут подставлены вместо спецификаторов формата, в порядке их указания

69 of 192

printf

69

printf("...", ...);

Список аргументов, которые будут подставлены вместо спецификаторов формата, в порядке их указания

printf("Hello, world!");

Строка формата - может содержать обычный текст, эскейп последовательности и спецификаторы формата

70 of 192

printf

70

printf("...", ...);

Список аргументов, которые будут подставлены вместо спецификаторов формата, в порядке их указания

printf("Hello, world!");

printf("Hello, world!\n");

Строка формата - может содержать обычный текст, эскейп последовательности и спецификаторы формата

71 of 192

printf

71

printf("...", ...);

Список аргументов, которые будут подставлены вместо спецификаторов формата, в порядке их указания

printf("Hello, world!");

printf("Hello, world!\n");

printf("value = %d", value);

Строка формата - может содержать обычный текст, эскейп последовательности и спецификаторы формата

72 of 192

printf

72

printf("...", ...);

Список аргументов, которые будут подставлены вместо спецификаторов формата, в порядке их указания

printf("Hello, world!");

printf("Hello, world!\n");

printf("value = %d", value);

Строка формата - может содержать обычный текст, эскейп последовательности и спецификаторы формата

73 of 192

printf

73

#include <stdio.h>

int x, y;

int main() {

x = 42;

y = 37;

printf("x = %d\ny = %d", x, y);

return 0;

}

74 of 192

printf

74

#include <stdio.h>

int x, y;

int main() {

x = 42;

y = 37;

printf("x = %d\ny = %d", x, y);

return 0;

}

75 of 192

printf

75

#include <stdio.h>

int x, y;

int main() {

x = 42;

y = 37;

printf("x = %d\ny = %d", x, y);

return 0;

}

> x = 42

> y = 37

76 of 192

Эскейп последовательности

Нужны для того, чтобы выводить символы, которые нельзя просто так написать в строке формата

76

77 of 192

Эскейп последовательности

Нужны для того, чтобы выводить символы, которые нельзя просто так написать в строке формата

77

Эскейп последовательность

Символ в выводе

\n

перевод строки

\t

горизонтальная табуляция

\’

\”

\\

\

78 of 192

Спецификаторы формата

Определяют тип, по которому будет выводиться следующий аргумент, и детали вывода, например, систему счисления, в которой нужно отобразить число

78

79 of 192

Спецификаторы формата

Определяют тип, по которому будет выводиться следующий аргумент, и детали вывода, например, систему счисления, в которой нужно отобразить число

В базовом наборе нам будет достаточно только одного спецификатора %d - вывод числа типа int в десятичной системе счисления

79

80 of 192

Спецификаторы формата

Определяют тип, по которому будет выводиться следующий аргумент, и детали вывода, например, систему счисления, в которой нужно отобразить число

В базовом наборе нам будет достаточно только одного спецификатора %d - вывод числа типа int в десятичной системе счисления

В дальнейшем мы будем добавлять спецификаторы по мере необходимости

80

81 of 192

Ввод данных

Похож на вывод - строка формата и список переменных, в которые нужно записать значения, прочитанные из потока ввода (в базовом наборе - с клавиатуры)

81

82 of 192

Ввод данных

Похож на вывод - строка формата и список переменных, в которые нужно записать значения, прочитанные из потока ввода (в базовом наборе - с клавиатуры)

Очень важное отличие - к каждой переменной должен быть применён оператор взятия адреса

Что это за оператор, и зачем он должен быть применён - через пару лекций

82

83 of 192

Ввод данных

Похож на вывод - строка формата и список переменных, в которые нужно записать значения, прочитанные из потока ввода (в базовом наборе - с клавиатуры)

Очень важное отличие - к каждой переменной должен быть применён оператор взятия адреса

Что это за оператор, и зачем он должен быть применён - через пару лекций

83

scanf("%d", &x);

84 of 192

Ввод данных

Похож на вывод - строка формата и список переменных, в которые нужно записать значения, прочитанные из потока ввода (в базовом наборе - с клавиатуры)

Очень важное отличие - к каждой переменной должен быть применён оператор взятия адреса

Что это за оператор, и зачем он должен быть применён - через пару лекций

84

scanf("%d", &x);

85 of 192

Ввод данных и разделители

Ввод с клавиатуры в консоль устроен таким образом, что вы можете вводить текст, пока не встретится символ перевода строки или признак конца файла

C клавиатуры можно ввести признак конца файла, потому что она - файл

85

86 of 192

Ввод данных и разделители

Ввод с клавиатуры в консоль устроен таким образом, что вы можете вводить текст, пока не встретится символ перевода строки или признак конца файла

C клавиатуры можно ввести признак конца файла, потому что она - файл

После того, как текст введён, scanf вытаскивает из него для чтения символы только до первого разделителя

Разделителями являются символы пробела, перевода строки, горизонтальной табуляции и признак конца файла

86

87 of 192

Ввод данных и разделители

Ввод с клавиатуры в консоль устроен таким образом, что вы можете вводить текст, пока не встретится символ перевода строки или признак конца файла

C клавиатуры можно ввести признак конца файла, потому что она - файл

После того, как текст введён, scanf вытаскивает из него для чтения символы только до первого разделителя

Разделителями являются символы пробела, перевода строки, горизонтальной табуляции и признак конца файла

Все набранные символы будут сохранены и использованы в дальнейших вызовах scanf функций

87

88 of 192

Ввод данных и разделители

Там, где может быть один разделитель, может быть сколько угодно - все они будут пропущены очередным scanf

88

89 of 192

Ввод данных и разделители

Там, где может быть один разделитель, может быть сколько угодно - все они будут пропущены очередным scanf

Не очень хорошей идеей является вызывать scanf для чтения больше, чем одной переменной - в таком случае символы между спецификаторами формата трактуются особым образом

89

90 of 192

Ввод данных и разделители

Там, где может быть один разделитель, может быть сколько угодно - все они будут пропущены очередным scanf

Не очень хорошей идеей является вызывать scanf для чтения больше, чем одной переменной - в таком случае символы между спецификаторами формата трактуются особым образом

В целом scanf довольно неудобный инструмент для чтения текстовых данных - в базовом наборе нам его будет достаточно, далее изучим другие средства

90

91 of 192

Ввод-вывод данных

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

91

92 of 192

Ввод-вывод данных

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

Для простоты тестирования научитесь в собственном окружении перенаправлять потоки ввода-вывода программы из и в файлы (система тестирования делает именно это)

Этому (и многому другому) вас научат в курсе ИСП

92

93 of 192

Операторы (operators)

Каждый оператор характеризуется:

  1. Семантикой - как правило, очевидной
  2. Преобразованием типа - не очень очевидным

93

94 of 192

Операторы (operators)

Каждый оператор характеризуется:

  • Семантикой - как правило, очевидной
  • Преобразованием типа - не очень очевидным

Самое распространённое поведение, касающееся типов:

  1. Если аргументы имеют разные типы, они преобразуются к наиболее широкому типу
  2. Тип результата совпадает с типом аргументов, либо увеличивается до int

94

95 of 192

Бинарные арифметические операторы

95

Оператор

Действие

+

сложение

-

вычитание

*

умножение

/

целая часть от деления

%

остаток от деления

96 of 192

Унарные арифметические операторы

96

Оператор

Действие

-

унарный минус

+

унарный плюс

x++

постинкремент

x–-

постдекремент

++x

преинкремент

--x

предекремент

97 of 192

Пост- и пре- инкременты

Оба увеличивают на 1 переменную, к которой применяются

97

98 of 192

Пост- и пре- инкременты

Оба увеличивают на 1 переменную, к которой применяются

Оба возвращают результат:

  1. Постинкремент - значение переменной до увеличения
  2. Преинкремент - значение переменной после увеличения

98

99 of 192

Пост- и пре- инкременты

Оба увеличивают на 1 переменную, к которой применяются

Оба возвращают результат:

  • Постинкремент - значение переменной до увеличения
  • Преинкремент - значение переменной после увеличения

Декременты то же самое, только уменьшают на 1

99

100 of 192

Пост- и пре- инкременты

100

x = 0;

y = x++;

x = 0;

y = ++x;

x: 1

x: 1

101 of 192

Пост- и пре- инкременты

101

x = 0;

y = x++;

x = 0;

y = ++x;

x: 1 y: 0

x: 1 y: 1

102 of 192

Переполнение арифметических операторов

Для беззнаковых типов - результат определяется вычислением по модулю

102

103 of 192

Переполнение арифметических операторов

Для беззнаковых типов - результат определяется вычислением по модулю

Для знаковых типов - UB

103

104 of 192

Переполнение арифметических операторов

Для беззнаковых типов - результат определяется вычислением по модулю

Для знаковых типов - UB

Применяя операторы к операндам неизвестной природы (например, введённых пользователям), вы, согласно стандарту, должны перед операцией проверить, не случится ли переполнение

В нашем курсе мы договорились это игнорировать; 3-е домашнее задание убедит вас, что это разумное решение

104

105 of 192

Логические операторы

105

Оператор

Действие

&&

Конъюнкция

||

Дизъюнкция

!

Отрицание

106 of 192

Логические операторы

Принимают любые скалярные типы (scalar types) - целые, вещественные и указатели (скоро будут)

Аргументы трактуются как 0 (ложь) и не-ноль (истина) - любое значение, отличающееся от 0

106

107 of 192

Логические операторы

Принимают любые скалярные типы (scalar types) - целые, вещественные и указатели (скоро будут)

Аргументы трактуются как 0 (ложь) и не-ноль (истина) - любое значение, отличающееся от 0

Возвращают значение типа int: 0 (ложь) или 1 (истина)

107

108 of 192

Логические операторы

Принимают любые скалярные типы (scalar types) - целые, вещественные и указатели (скоро будут)

Аргументы трактуются как 0 (ложь) и не-ноль (истина) - любое значение, отличающееся от 0

Возвращают значение типа int: 0 (ложь) или 1 (истина)

Некоторые компиляторы раньше нарушали это правило и возвращали 0 (ложь) и не-ноль (истина)

Из-за этого при программировании на языке Си не принято сравнивать результат логической операции с 1; вместо этого сравнение инвертируется и результат сравнивается с 0

108

109 of 192

Ленивые вычисления

Бинарные логические операторы обладают свойством ленивых вычислений - если первый операнд однозначно определяет результат операции, второй аргумент не вычисляется

Если второй операнд - переменная, это ни на что не влияет, но если это сложное выражение (содержит вызовы функций или побочные эффекты), поведение программы меняется

109

110 of 192

Ленивые вычисления

Бинарные логические операторы обладают свойством ленивых вычислений - если первый операнд однозначно определяет результат операции, второй аргумент не вычисляется

Если второй операнд - переменная, это ни на что не влияет, но если это сложное выражение (содержит вызовы функций или побочные эффекты), поведение программы меняется

&& - если первый операнд равен 0, результат гарантированно 0, второй операнд не вычисляется

110

111 of 192

Ленивые вычисления

Бинарные логические операторы обладают свойством ленивых вычислений - если первый операнд однозначно определяет результат операции, второй аргумент не вычисляется

Если второй операнд - переменная, это ни на что не влияет, но если это сложное выражение (содержит вызовы функций или побочные эффекты), поведение программы меняется

&& - если первый операнд равен 0, результат гарантированно 0, второй операнд не вычисляется

|| - если первый операнд не равен 0, результат гарантированно 1, второй операнд не вычисляется

111

112 of 192

Битовые операторы

112

Оператор

Действие

&

Битовая конъюнкция

|

Битовая дизюнкция

^

Битовое исключающее ИЛИ (XOR)

<<

Битовый сдвиг вправо

>>

Битовый сдвиг влево

~

Битовое отрицание

113 of 192

Битовые операторы

Принимают любые целые типы, возвращают целый тип по правилу арифметических операций (самый широкий тип из операндов + расширение до int)

Для битовых сдвигов тип результата равен типу первого операнда, расширенному до int

113

114 of 192

Битовые операторы

Принимают любые целые типы, возвращают целый тип по правилу арифметических операций (самый широкий тип из операндов + расширение до int)

Для битовых сдвигов тип результата равен типу первого операнда, расширенному до int

&, |, ^, ~ - совершают простое логическое действие над каждой парой битов своих операндов

114

115 of 192

Битовые операторы

Принимают любые целые типы, возвращают целый тип по правилу арифметических операций (самый широкий тип из операндов + расширение до int)

Для битовых сдвигов тип результата равен типу первого операнда, расширенному до int

&, |, ^, ~ - совершают простое логическое действие над каждой парой битов своих операндов

Не обладают свойством ленивых вычислений

При этом результат | может быть использован, как результат || (кроме сравнения его с 1, что и так не стоит делать)

115

116 of 192

Битовые операторы

116

1

0

0

1

0

0

1

1

0

0

1

1

1

0

0

1

117 of 192

Битовые операторы

117

1

0

0

1

0

0

1

1

0

0

1

1

1

0

0

1

||

118 of 192

Битовые операторы

118

1

0

0

1

0

0

1

1

0

0

1

1

1

0

0

1

||

0

0

0

0

0

0

0

1

119 of 192

Битовые операторы

119

1

0

0

1

0

0

1

1

0

0

1

1

1

0

0

1

||

0

0

0

0

0

0

0

1

|

120 of 192

Битовые операторы

120

1

0

0

1

0

0

1

1

0

0

1

1

1

0

0

1

||

0

0

0

0

0

0

0

1

|

1

0

1

1

1

0

1

1

121 of 192

Битовые операторы

121

1

0

0

1

0

0

1

1

0

0

1

1

1

0

0

1

||

0

0

0

0

0

0

0

1

|

1

0

1

1

1

0

1

1

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

122 of 192

Битовый сдвиг влево

122

0

0

0

1

0

0

1

1

123 of 192

Битовый сдвиг влево

123

0

0

0

1

0

0

1

1

<< 2

0

1

0

0

1

1

0

0

124 of 192

Битовый сдвиг влево

124

0

0

0

1

0

0

1

1

<< 2

0

1

0

0

1

1

0

0

125 of 192

Битовый сдвиг числа X влево на N

N старших битов числа X отбрасываются, остальные перемещаются влево на N, справа дописываются N нулевых битов

125

126 of 192

Битовый сдвиг числа X влево на N

N старших битов числа X отбрасываются, остальные перемещаются влево на N, справа дописываются N нулевых битов

X << N равен X * 2N, при этом вычисляется эффективнее

Не надо писать самостоятельно сдвиги ради эффективности, любой компилятор сделает это за вас

126

127 of 192

Битовый сдвиг числа X влево на N

N старших битов числа X отбрасываются, остальные перемещаются влево на N, справа дописываются N нулевых битов

X << N равен X * 2N, при этом вычисляется эффективнее

Не надо писать самостоятельно сдвиги ради эффективности, любой компилятор сделает это за вас

UB:

  1. N < 0
  2. N >= ширины X
  3. число X - знаковое, и среди отбрасываемых битов есть 1

127

128 of 192

Битовый сдвиг вправо

То же самое, только вправо, и результат равен результату деления на 2N. Отличия:

  1. Если среди отбрасываемых битов есть 1, поведение не является неопределённым
  2. Если сдвигается знаковое отрицательное число (старший бит равен 1) - поведение ID

128

129 of 192

Битовый сдвиг вправо

То же самое, только вправо, и результат равен результату деления на 2N. Отличия:

  • Если среди отбрасываемых битов есть 1, поведение не является неопределённым
  • Если сдвигается знаковое отрицательное число (старший бит равен 1) - поведение ID

В нашей платформе на место освободившихся битов копируется старший бит числа (соответствует дополнительному коду)

129

130 of 192

Битовый сдвиг вправо

130

#include <stdio.h>

int main() {

int x = -1;

x = x >> 3;

printf("%d\n", x);

return 0;

}

131 of 192

Битовый сдвиг вправо

131

#include <stdio.h>

int main() {

int x = -1;

x = x >> 3;

printf("%d\n", x);

return 0;

}

1

1

1

1

1

1

...

132 of 192

Битовый сдвиг вправо

132

#include <stdio.h>

int main() {

int x = -1;

x = x >> 3;

printf("%d\n", x);

return 0;

}

?

?

?

1

1

1

...

133 of 192

Битовый сдвиг вправо

133

#include <stdio.h>

int main() {

int x = -1;

x = x >> 3;

printf("%d\n", x);

return 0;

}

1

1

1

1

1

1

...

Старший (знаковый) бит

134 of 192

Битовый сдвиг вправо

134

#include <stdio.h>

int main() {

int x = -1;

x = x >> 3;

printf("%d\n", x);

return 0;

}

1

1

1

1

1

1

...

Старший (знаковый) бит

Его копии

135 of 192

Битовый сдвиг вправо

135

#include <stdio.h>

int main() {

int x = -1;

x = x >> 3;

printf("%d\n", x);

return 0;

}

1

1

1

1

1

1

...

136 of 192

Битовый сдвиг вправо

136

#include <stdio.h>

int main() {

int x = -1;

x = x >> 3;

printf("%d\n", x);

return 0;

}

1

1

1

1

1

1

...

> -1

137 of 192

Битовый сдвиг вправо

То, что -1, сдвинутое вправо на N (поделённое на 2N), остаётся -1, конечно, немного странно

Зато с остальными отрицательными числами получается всё удачно; например, -4 >> 1 = -2

137

138 of 192

Операторы присваивания

138

Оператор

Действие

=

Присваивание

139 of 192

Операторы присваивания

139

Оператор

Действие

=

Присваивание

+=

Сложение и присваивание

...

...

140 of 192

Операторы присваивания

140

Оператор

Действие

=

Присваивание

+=

Сложение и присваивание

...

...

Оператор присваивания можно комбинировать с любым бинарным оператором

141 of 192

lvalue

Левый операнд оператора присваивания должен быть изменяемым (modifiable) lvalue - выражением, являющимся объектом, который можно изменить

TL;DR - переменной, но на самом деле, всё намного сложнее

141

142 of 192

lvalue

Левый операнд оператора присваивания должен быть изменяемым (modifiable) lvalue - выражением, являющимся объектом, который можно изменить

TL;DR - переменной, но на самом деле, всё намного сложнее

Интуитивно понятное явление - нельзя пытаться присваивать в константы, результаты других выражений, неизменяемые объекты и так далее

142

143 of 192

Операторы присваивания возвращают значение!

143

a = b = c = 42;

144 of 192

Операторы присваивания возвращают значение!

144

a = (b = (c = 42));

145 of 192

Операторы сравнения и равенства

145

Оператор

Действие

==

Сравнение на равенство

!=

Сравнение на неравенство

<

Сравнение на меньше

>

Сравнение на больше

<=

Сравнение на меньше или равно

>=

Сравнение на больше или равно

146 of 192

Операторы сравнения и равенства

146

Оператор

Действие

==

Сравнение на равенство

!=

Сравнение на неравенство

<

Сравнение на меньше

>

Сравнение на больше

<=

Сравнение на меньше или равно

>=

Сравнение на больше или равно

Возвращают значение типа int: 0 (ложь) или 1 (истина)

147 of 192

Ура!

Теперь вы можете решить второе домашнее задание: калькулятор, в котором примените все изученные инструменты

147

148 of 192

Условный оператор

148

a ? b : c;

Если a - истина, возвращает b, иначе - c

149 of 192

Условный оператор

Обладает свойством ленивых вычислений - тот операнд, который не возвращается, не вычисляется вовсе

149

150 of 192

Условный оператор

Обладает свойством ленивых вычислений - тот операнд, который не возвращается, не вычисляется вовсе

Часто называется тернарным, потому что условным оператором называют if statement, но это неправильно (тернарный - это то же самое, что бинарный и унарный)

150

151 of 192

Условный оператор

Обладает свойством ленивых вычислений - тот операнд, который не возвращается, не вычисляется вовсе

Часто называется тернарным, потому что условным оператором называют if statement, но это неправильно (тернарный - это то же самое, что бинарный и унарный)

Этим оператором нельзя злоупотреблять

151

(x % 400) ? 1 : ((x % 100) ? 0 : !(x % 4))

152 of 192

Оператор приведения (cast)

152

int x = ...;

... = (unsigned char) x;

Возвращает значение x, преобразованное к типу unsigned char

153 of 192

Operator, expression, statement

Основные элементы текста программы (ещё есть объявления и определения)

153

154 of 192

Operator, expression, statement

Основные элементы текста программы (ещё есть объявления и определения)

Выражение (expression) - композиция оператора и операндов, которые тоже могут быть выражениями

154

155 of 192

Operator, expression, statement

Основные элементы текста программы (ещё есть объявления и определения)

Выражение (expression) - композиция оператора и операндов, которые тоже могут быть выражениями

… (statement) - действие, которое должно быть выполнено

155

156 of 192

Operator, expression, statement

Основные элементы текста программы (ещё есть объявления и определения)

Выражение (expression) - композиция оператора и операндов, которые тоже могут быть выражениями

Оператор (statement) - действие, которое должно быть выполнено

Придётся пользоваться де-факто стандартным переводом, хоть он и конфликтует с переводом термина operator

156

157 of 192

Условный оператор (statement)

157

code A

if (condition) {

code B

} else {

code C

}

code D

158 of 192

Условный оператор (statement)

158

code A

if (condition) {

code B

} else {

code C

}

code D

159 of 192

Условный оператор (statement)

159

code A

if (condition) {

code B

} else {

code C

}

code D

160 of 192

Условный оператор (statement)

160

code A

if (condition) {

code B

} else {

code C

}

code D

Если condition - истина

Если condition - ложь

161 of 192

Условный оператор (statement)

161

code A

if (condition) {

code B

} else {

code C

}

code D

162 of 192

Условный оператор (statement)

162

code A

if (condition) {

code B

}

code D

Можно писать без else части, если она не нужна по семантике

163 of 192

Условный оператор (statement)

163

code A

if (condition)

code B

code D

И без фигурных скобок, если code B - один оператор (statement), но этого обычно лучше избегать

164 of 192

Оператор (statement) цикла while

164

while (condition) {

code A

}

code B

165 of 192

Оператор (statement) цикла while

165

while (condition) {

code A

}

code B

166 of 192

Оператор (statement) цикла while

166

while (condition) {

code A

}

code B

Если condition - истина

Если condition - ложь

167 of 192

Оператор (statement) цикла while

167

while (condition) {

code A

}

code B

Если condition - истина

168 of 192

Оператор (statement) цикла while

168

while (condition) {

code A

}

code B

Если condition - истина

Если condition - ложь

169 of 192

Оператор (statement) цикла while

169

while (condition) {

code A

}

code B

Если condition - истина

170 of 192

Оператор (statement) цикла while

170

while (condition) {

code A

}

code B

Если condition - истина

Если condition - ложь

171 of 192

Оператор (statement) цикла while

171

while (condition) {

code A

}

code B

Если condition - истина

Если condition - ложь

И так далее

172 of 192

Оператор (statement) цикла do-while

172

do {

code A

} while (condition);

code B

173 of 192

Оператор (statement) цикла do-while

173

do {

code A

} while (condition);

code B

174 of 192

Оператор (statement) цикла do-while

174

do {

code A

} while (condition);

code B

175 of 192

Оператор (statement) цикла do-while

175

do {

code A

} while (condition);

code B

176 of 192

Оператор (statement) цикла do-while

176

do {

code A

} while (condition);

code B

Если condition - истина

Если condition - ложь

И так далее

177 of 192

Разница между циклами while и do-while

Тело цикла do-while гарантированно исполнится хотя бы один раз

177

178 of 192

Разница между циклами while и do-while

Тело цикла do-while гарантированно исполнится хотя бы один раз

В большинстве случаев на практике цикл while более применимый, но никогда не забывайте о существовании другого

178

179 of 192

Операторы (statement) break и continue

179

while (...) {

...

if (condition) {

continue;

}

...

if (condition) {

break;

}

...

}

code A

180 of 192

Операторы (statement) break и continue

180

while (...) {

...

if (condition) {

continue;

}

...

if (condition) {

break;

}

...

}

code A

181 of 192

Операторы (statement) break и continue

181

while (...) {

...

if (condition) {

continue;

}

...

if (condition) {

break;

}

...

}

code A

182 of 192

Операторы (statement) break и continue

Любой цикл всегда можно написать без их использования, но часто они позволяют сделать более понятный и простой код

182

183 of 192

Вывести числа от 1 до N через пробел

183

int N, i;

...

i = 1;

while (i <= N) {

printf("%d ", i);

i++;

}

184 of 192

Вывести числа от 1 до N через пробел

184

int N, i;

...

i = 1;

while (i <= N) {

printf("%d ", i);

i++;

}

Этот символ попадёт в вывод и после последнего числа

185 of 192

Вывести числа от 1 до N через пробел

185

int N, i;

...

i = 1;

while (i <= N) {

printf("%d", i);

if (i != N) {

printf(" ");

}

i++;

}

186 of 192

Вывести числа от 1 до N через пробел

186

int N, i;

...

i = 1;

while (i <= N) {

printf("%d", i);

if (i != N) {

printf(" ");

}

i++;

}

Начальное значение

187 of 192

Вывести числа от 1 до N через пробел

187

int N, i;

...

i = 1;

while (i <= N) {

printf("%d", i);

if (i != N) {

printf(" ");

}

i++;

}

Начальное значение

Условие продолжения

188 of 192

Вывести числа от 1 до N через пробел

188

int N, i;

...

i = 1;

while (i <= N) {

printf("%d", i);

if (i != N) {

printf(" ");

}

i++;

}

Начальное значение

Условие продолжения

Шаг цикла

189 of 192

Оператор (statement) цикла for

189

int N, i;

...

for (i = 1; i <= N; i++) {

printf("%d", i);

if (i != N) {

printf(" ");

}

}

190 of 192

Оператор (statement) цикла for

Собранные вместе выражения начального значения, условия продолжения и шага цикла читаются намного лучше, чем когда они разбросаны по коду

190

191 of 192

Оператор (statement) цикла for

Собранные вместе выражения начального значения, условия продолжения и шага цикла читаются намного лучше, чем когда они разбросаны по коду

Как правило затрагивают одну переменную, называемую индуктивной

191

192 of 192

Оператор (statement) цикла for

Собранные вместе выражения начального значения, условия продолжения и шага цикла читаются намного лучше, чем когда они разбросаны по коду

Как правило затрагивают одну переменную, называемую индуктивной

Избегайте изменений индуктивной переменной внутри цикла for, такое лучше либо переписать на обычный цикл while, либо обойтись как-то иначе

192