class: center, title # Как выучить Си *Введение для программиста высокого уровня* --- # Необходимые знания ### 1. Знать как минимум 1 язык программирования Предпочтительно два, потому что вы будете знать, как логически решить проблему, но выразить это двумя различными способами. *Не выбирайте Cи в качестве первого языка.* ### 2. Логическое мышление Вы должны быть в состоянии разбивать задачи в простые шаги, которые компьютер может понять. Если вы не можете думать, как компьютер, вам будет сложно отлаживать программу. ### 3. Время Си — это не тот язык, который вы быстро выучите и начнете что-то достойное писать безошибочно. Вы будете писать программы, которые не будут правильно работать. Пройдет какое-то время, прежде чем вы сможете быстро определять проблемы в коде. --- # Почему вам надо учить Си
Си вам нужен только для проектов, в которых требуется низкоуровневый доступ к системе или требуется очень быстрое выполнение каких-либо операций.
### 1. Продвинутая разработка игр (в сочетании с хорошим фреймворком для этого) Для *улучшений* производительности ресурсоёмких 3D приложений. *
Вам необходимо иметь некие математические знания (линейная алгебра, мат. анализ и, возможно, физика) для этого.
* ### 2. Компиляторы Можете почитать книжку про дизайн компиляторов. ### 3. Встроенные приложения Когда вы пытаетесь выжать производительность на максимум, используя минимум ресурсов, вы вряд ли захотите прошивать микроконтроллер Ruby интерпретатором чтобы запустить скрипт. --- # Почему вам надо учить Си ### 4. Операционные системы ДА! LINUX!!! Или ваша собственная доморощенная ОС, если хотите. ### 5. LINUX ДА! LINUX!!! Хакинг ядра, разработка утилит коммандной строки, писать замену Systemd и т.д.
Если вы хотите написать скрипт, считающий количество вхождений строки "Foo" в некотором текстовом файле, то вы ошиблись адресом. --- class: center, middle, dark # Если вы все еще уверены, что хотите узнать C, давайте по-быстрому пробежимся по синтаксису, чтобы вы знали, что тут вообще происходит. --- class: center, middle, dark # Это слайд-шоу поможет вам узнать и понять запутанную концепцию языка Си. --- class: center, middle, dark # За кулисами --- class: center, middle, dark ### Интрепретация vs ### Компиляция --- # Пример ### Ruby Как только вы закончите писать код, вы можете запустить его. Интерпретатор Ruby будет выполнять код шаг за шагом: ```bash ruby test.rb #=> Да! Рабочий код! ``` ### C Си требует, чтобы вы сначала **компилировали** свой исходный код в бинарный исполняемый файл. ```bash # Скомпилировать код моей программы в новый исполняемый файл с названием 'test' cc -o test test.c # Запустить мою программу ./test ``` --- ## Плюсы компилирования - Просто распространять - Быстро работает - Автоматическая обфускация (они не смогут прочитать твой код!) - Компилятор связывает код в одно целое ## Минусы - Должно быть скомпилировано под каждую архитектуру(ARM, x86, x86-64, etc) - Сложнее отлаживать код --- class: center, middle, dark ### Динамическое типизирование vs ### Статическое типизирование --- # Пример ### Ruby Переменные могут быть чем угодно! ```ruby my_var = 4 # Посмотрите на этот integer my_var = 2.0 # Теперь он float my_var = [1,2,3] # Оооо, массив! my_var = 'a string of stuff' # СЛОВО! (так же известное как string) ``` ### C Единажды определив тип переменной для вашей переменной, вы не сможете его изменить в дальнейшем. ```c int my_var = 3; // Это int! my_var = "abc"; // ОШИБКА КОМПИЛЯТОРА! Вы уже сказали, что это — int! /* вставить взрыв здесь */ ``` --- class: center, middle, dark ### Безопасно vs ### Небезопасно --- # Пример ### Ruby Интерпретатор Ruby усиливает здравомыслие в коде. ```ruby a = ['a', 'b', 'c'] a[2] # => c a[5] # ну уж нет! # => Ошибка: идиот-программист пытается получить доступ к элементу, # которого не существует! ``` ### C В Си, вы можете делать все, что вы, черт побери, хотите. ```c char a[] = {'a', 'b', 'c'}; a[2] //=> 'c' a[5] // Да без вопросов! Держи немного мусора! //=> ajskdi&E3j ... Ошбика: segfault ``` --- class: center, middle, dark ### Автоматическое распределение памяти и сборка мусора vs ### Ручное распределение памяти и сборка мусора --- # Пример ### Ruby ```ruby def my_memory_function cats = get_all_teh_images_on_imgur process_images(cats) # Юху, она занимает МНОГО памяти end # УДАЛИТЬ КИСОК # Переменная cats больше не существует! Память была от нее очищена! ``` ### C ```c void my_memory_function() { // Выделяем память под набор картинок с кисками images *cats = malloc(sizeof(cat_image) * NUMBER_OF_CATS_ON_IMGUR); get_all_teh_images_on_imgur(cats); // теперь мы можем их скачать process_images(cats); // а киски то еще в памяти! free(cats); // не забудте это, а то эта память так // и останется зарезервирована для этих картинок ;) // ооп, теперь они пропали } ``` --- class: center, middle, dark # Заметьте: --- class: center, middle, dark # ВЫ ДОЛЖНЫ --- class: center, middle, dark # .bigger[ВСЕГДА] --- class: center, middle, dark # .evenbigger[СЛЕДИТЬ] --- class: center, middle, dark # .evenbiggerthanthat[ЗА] --- class: center, middle, dark, esplosion # .biggest_memory[ПАМЯТЬЮ] --- class: center, middle, dark # Иначе произойдут BadThings
TM
*Взрывы и плачущие щенки. Это плохо.* --- class: center, middle, dark # Вперед! К сложностям! --- class: center, middle, dark, thehorror #.biggest[Указатели!] --- # Указатели ... как основа Си. Они дают вам великую силу (поэтому будте осторожны). *Указатель — это переменная, содержащая адрес памяти (возможно, другой переменной).* В Си, символ '`*`' обозначает переменную как указатель на любой тип данных обозначеный ранее. ```c int *i_ptr; // этот указатель может указывать на целое число float *f_ptr; // этот указатель может указывать на число с плавающей точкой int **ii_ptr; // этот указатель указывает на указатель на целое число ``` Символ '`&`' позволяет вам получить ячейку памяти переменной: ```c int x = 3; int *i_ptr = &x; // i_ptr — это указатель на ячейку памяти переменной 'x' ``` --- Память выглядит примерно так: адрес | значение -------------|--------- `0x00` | `0x01` | `0x02` | `0x03` | И пермеменная — это просто именованый адрес ячейки памяти: ```c int x = 3; // Компилятор выбирает ячейку памяти. Скажем, 0x01 ``` адрес | значение -------------|--------- `0x00` | `0x01` | `3` `0x02` | `0x03` |
В реальной жизни вы не должны знать расположение переменных в памяти. Это просто для демонстрации.
--- Если мы создадим указатель, указывающий на переменную `x`: ```c int *i_ptr = &x; // итак, i_ptr должна содержать 0x01. ``` `i_ptr` — это **переменная**, и так же **располагается в памяти.** Пусть в `0x02`. адрес | значение -------------|--------- `0x00` | `0x01` | `3` `0x02` | `0x01` `0x03` | Если мы решим создать указатель на указатель на целое число (`int **`) на `i_ptr`: ```c int **ii_ptr = &i_ptr; // пусть валяется ii_ptr в 0x03 ``` адрес | значение -------------|--------- `0x00` | `0x01` | `3` `0x02` | `0x01` `0x03` | `0x02` --- class: center, middle, dark # Замечательно, теперь у нас есть указатель на указатель на целое число. Но как нам получить значение? --- И тут вступает в игру оператор разыменовки '`*`'. `*` разыменовывает (или следует по ссылкам) указатель в адрес, на который он указывает: ```c int y = *i_ptr; // даёт нам значение, на которое указывает i_ptr int z = **ii_ptr; // даёт сначала следует на первый указатель, // затем следует по второму указателю к значению. ``` Мне нравится думать, что указатель — это папка, а значение — это файл. ``` ─── ii_ptr └── i_ptr └── x ``` Поэтому, '`*`' перемещает папку вниз и '`&`' поднимает её вверх. --- Тааааак, стооооп! А как на счет нескольких файлов? Увы, но это сломает аналогию! ``` ─── ii_ptr └── i_ptr ├── x └── y ``` На самом деле, так обрабатываются массивы в Си. Массив в Си — это множество последовательно расположенных адресов памяти. ```c int arr[] = {1, 2, 3}; // создаем массив // arr - это просто указатель на первый элемент... int first_val = *a; // ... поэтому его разыменование даст // нам первое значение. int second_val = *(a + 1); // получаем следующий адрес в памяти (a + 1), // и разыменовываем его ('*') int third_value = a[2]; // на самом деле это всего лишь синтаксический сахар // вот для этого: *(a + 2) ``` --- class: center, middle, dark # Существует ограничение для массивов в Си --- class: center, middle, dark # Они не динамические. --- class: center, middle, dark # Хочешь положить новый элемент в конец массива? --- class: center, middle, dark # .bigger[Знаешь че?] --- class: center, middle, dark # А не взять ли тебе и ... --- class: center, middle, dark # выделить место под новое значение ... --- class: center, middle, dark # .biggest[*ВРУЧНУЮ*] --- # `malloc` и `realloc` *Вручную выделеяет места в памяти с 1972.* `malloc` получает 1 аргумент: количество байт, которое ему надо выделить. `sizeof(int)` возвращает количество байт в integer, которые будут умножены на количество элементов в массиве. ```c int num_of_elements = 3; int *resize_arr = malloc(sizeof(int) * num_of_elements); // ... положить остальной код тут ... ``` Чтобы освободить место для нового элемента: ```c resize_arr = realloc(resize_arr, sizeof(int) * (num_of_elements + 1)); ``` Первым аргументом является указатель, размер элемента которого надо изменить, второй — новый размер указателя. Не забудте очистить память, когда закончите: ```c free(resize_arr); ``` --- class: center, middle, dark # И наконец --- class: center, middle, dark # Концепция, которую не трудно понять --- class: center, middle, dark # Но которая является фундаментальной --- class: center, middle, dark # Си пофиг на ваши данные --- # Что это значит? Си не очень то переживает над тем, что вы делаете с типами. Ты хочешь привести целое число в символ? Да пожайлуйста: ```c char letter = 'a'; int x = (int) a; // смтори. теперь это int ``` Указатель на целое число: ```c int x = 3; int *ptr = &x; int y = (int) ptr; ``` Он предупредит, что int'ы имеют не такой же размер, как указатели? Да. Имеет ли это смысл? Нет. Си приведет указатель к int? Да. Используя библиотеку для работы с изображениями и желая привести int к pixel, сможем ли мы это сделать? Погнали: ```c #include
int num = 30; image *my_img = load_raw_image("./test.png"); my_img[0][0] = (pixel) num; // Вуаля ``` --- class: center, middle, dark # Ну, вроде о всех самых сложных для новичка концептах языка Си рассказано --- # Что вы должны запомнить? - Си следит за миром, как за кучей случайных байтов - Указатели — это просто адреса. Они же еще и переменные. - Массивы — это коллекции последовательно расположенных адресов --- # Ресурся для изучения Си - [The C Programming Language](http://www.amazon.com/Programming-Language-Brian-W-Kernighan/dp/0131103628), a book by the original creators of C. They know what they're talking about. - Интерактивное [введение в Си](http://www.learn-c.org/) - Коллекция [хороших постов о Си](http://www.cprogramming.com/tutorial/c-tutorial.html) --- class: center, middle, dark Если вы считаете, что можете помочь с улучшением слайд-шоу, .white[[вот репозиторий на github :)](https://github.com/mokeev1995/intro_to_c_slideshow)] --- class: center, middle, dark Переведено .white[[@mokeev1995](http://twitter.com/mokeev1995)] (.white[[mokeev1995.ru](http://mokeev1995.ru)]) --- class: center, middle, dark Создано .white[[@theninjacharlie](http://twitter.com/theninjacharlie)]