Введение в программирование на Си

мая 2, 2012  |  Published in Си и C++  |  5 Comments

programming c
Из серии:

  1. * Введение в программирование на Си
  2. Переменные и типы данных в Си
  3. Операторы, условия и циклы языка Си

Несмотря на название RubyDev старается охватить максимальное количество тем касающихся разработки. Одной из таких тем является программирование на языке Си. Си был выбран потому, что это достаточно популярный язык, который уже более 30 лет остается весьма актуальным. Кроме того, Си будет интересен программистам на Ruby тем, что на нем можно легко писать расширения ваших gem’ов или критические куски вашего приложения, где производительность крайне важна.

Мы не будем погружаться в исторический экскурс. Совершенно не важно знать кто и когда создал Си и почему. Это совершенно бесполезная информация для изучения Си и я зря потратил бы время пытаясь рассказать историю языка. С другой стороны важно знать, что Си является императивным (иначе говоря — процедурным) языком программирования. Как показала практика и исследования, процедурные языки совершили переворот в мире разработки, поскольку программы написанные на процедурных языках легче читать и поддерживать, чем программы написанные на функциональных языка.

Лично для меня Си — это такое увлечение, по факту я ничего серьезного на нем не писал — не было потребности, потому советую сильно не доверять всему, что здесь написано. В целях предотвращения священных кровопролитных войн касательно преимуществ императивной парадигмы над функциональной, замечу, что говоря об этом преимуществе я лишь цитировал классиков. Признаться честно, я также (в меньшей мере) увлекаюсь языками CommonLisp и Erlang, которые являются функциональными. Разумеется, в каждом языке имеются свои недостатки, преимущества и изюм — что-то, что делает его выделяющимся из подобных ему.

Мною запланирована серия статей по программированию на языке Си. Цель этой серии статей — структурировать имеющиеся знания Си попутно узнав что-то новое, и в удобочитаемом и понимабельном виде представить их на, надеюсь, не строгий суд читателю.

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

  • Переменные и типы данных
  • Операторы
  • Управляющие структуры. Циклы и условия
  • Функции
  • Массивы
  • Строки
  • Указатели
  • Сводим все вместе
  • Препроцессор
  • Разработка крупных приложений
  • Разработка под Linux

Данная же статья является скорее философской, но при этом не теряет своей практичности и пользы. Ее изучении позволит понять философию языка Си, разобраться с некоторой терминологией и важными концепциями императивного (процедурного) программирования. Кроме того, в этой статье мы напишем первое наше приложение на Си.

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

Язык низкого высокого уровня
Я называю Си языком низкого высокого уровня. С одной стороны у нас имеется три типа языков: машинный код, ассемблеры и языки высокого уровня, к коим безусловно относится Си. С другой стороны в сравнении с Ruby или Python, Си представляет собой достаточно низкоуровневый язык программирования, который например позволяет работать с памятью напрямую, писать драйвера устройств, операционный системы и прошивки для различных девайсов.

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

Несмотря на то, что ассемблеры являются языками низкоуровневыми, а значит, казалось бы и программы на них более производительны, на практике все часто обстоит совсем иначе. Писать на ассемблерах код очень долго и сложно. В ходе использования ассемблера можно натворить огромное количество ошибок, а еще этот код будет платформа-зависимым. Работая с Си мы можем писать платформа-независимый код (кросс платформенный код), допускать в ходе его разработки значительно меньше ошибок, а еще современные компиляторы способны его так оптимизировать, что большинство разработчиков не смогли бы добиться такой же производительности кода на языках семейства ассемблеров.

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

Вы уже не раз сталкивались с концепцией черного ящика. Например, когда вы смотрите телевизор, то вы получаете от него определенные данные, он просто работает и вам не нужно знать его внутреннее устройство. Щелк-щелк-щелк… Вот и вся ваша работа с телевизором.

В программирования концепция черного языка используется повсеместно, в Ruby концепция черного языка используется, например, в методах. Вы используете метод #sort для сортировки массива, но вы совершенно не знаете как он работает. Вы помещаете в черный ящик массив, встряхиваете его три раза произнося волшебные слова «Рекс! Пекс! Фекс!» и затем вытаскиваете из него кролика — отсортированный массив.

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

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

Рассмотрим пример простого приложения:

/* Поиск суммы двух чисел programm.c */

#include <stdio.h>

int main(void) {
    int a, b;

    printf("Введите числа a и b, которые необходимо сложить: ");
    scanf("%d%d", &a, &b);
    printf("Результатом сложения %d и %d является число %d\n", a, b, a + b);

    return 0;
}

Чтобы запустить эту программу, необходимо ее предварительно скомпилировать. Компилятор — это программа, которая преобразует код на языке Си в машинный код. Для компиляции нашей программы воспользуемся компилятором входящим в состав GCC, который поставляется совместно с большинством дистрибутивов операционной системы GNU/Linux.

$ cc -v
Using built-in specs.
COLLECT_GCC=cc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/i686-linux-gnu/4.6.1/lto-wrapper
Target: i686-linux-gnu
Thread model: posix
gcc version 4.6.1 (Ubuntu/Linaro 4.6.1-9ubuntu3)

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

$ cc program.c

Процесс компиляции занял мгновение. Компилятором был создан исполняемый файл, который по умолчанию называется a.out. Вы можете запустить его в консоли и проверить как работает наше приложение:

$ ./a.out
Введите числа a и b, которые необходимо сложить: 111 345
Результатом сложения 111 и 345 является число 456

Как видите, начать программировать на Си очень просто. Главное не забросить свое увлечение этим замечательным языком программирования после того, как начнутся более сложные темы.

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

#include <stdio.h>

void print_sum(int, int);

int main(void) {
    int a, b;

    printf("Введите числа a и b, которые необходимо сложить: ");
    scanf("%d%d", &a, &b);
    print_sum(a, b);

    return 0;
}

void print_sum(int a, int b) {
    printf("Результатом сложения %d и %d является число %d\n", a, b, a + b);
}

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

 

Tags: , ,

Responses

  1. Daniel Black says:

    мая 2, 2012 at 13:33 (#)

    Статья нужная, но пока не очень удачная.
    Например, мне, как рубисту, не очень понятно (1) что такое void, (2) почему на третьей строке «void print_sum(int, int);» эта хрень пишется перед названием метода, и почему в виде параметра на строке 5; (3) почему сам метод объявляется 2 раза?
    И это только начало.

  2. admin says:

    мая 2, 2012 at 13:50 (#)

    Daniel Black,
    1. В данном случае void указывает на то, что функция ничего не возвращает.

    2, 3. Это называется прототипом функции. Обычно они пишутся в отдельных заголовочных файлах. В контексте данной программы прототип использовать не обязательно, но тогда функцию print_sum() необходимо было бы объявить перед main() так как функция водима только после ее определения. Прототип описывает т.с. формат функции: тип возвращяемого значение или void, если его нет, имя функции и типы аргументов (можно рядом с типами писать и названия аргументов, но они будут игнорироваться). Если не ошибаюсь, прототипы были добавлены только в ANSI C и идея была взята из надмножества Cи — C++.

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

  3. says:

    мая 2, 2012 at 18:19 (#)

    Прям как в 1993 год вернулся на лекции по программированию (-:

  4. says:

    мая 3, 2012 at 15:36 (#)

    Спасибо, с удовольствием почитал и выполнил пример.
    Домашние задания будут?

  5. Nikk says:

    мая 9, 2012 at 10:21 (#)

    Да, прикольно было почитать! Спс!

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.