Язык С часто называют языком программирования среднего уровня. Но это не значит, что С менее мощный, менее развитой или более трудный в использовании, чем языки высокого уровня, такие как Basic или Pascal. Это также не значит, что С такой же громоздкий и неудобный, как ассемблер. Языком среднего уровня его называют скорее потому, что он объединяет в себе лучшие черты языков высокого уровня с возможностями ассемблера. В табл. 1.1 показано, какое место занимает С среди других языков программирования.
Таблица 1    
Языки высокого уровняAda
 Modula-2
 Pascal
 COBOL
 FORTRAN
 Basic
Языки среднего уровняJava
 C++
 С
 FORTH
Языки низкого уровняМакроассемблер
 Ассемблер

Как язык среднего уровня С позволяет манипулировать битами, байтами и адресами, то есть теми базовыми элементами данных, с которыми работает компьютер. Несмотря на это программа, написанная на С, обладает высокой переносимостью. Переносимость - это свойство программного обеспечения, созданного для одного типа компьютера или операционной системы, позволяющее легко переделать его для другого типа, т.е. перенести в другую вычислительную среду. Например, если программу, работающую под управлением DOS, легко переделать так, чтобы она работала под управлением Windows 2000, то такая программа называется переносимой.
Все языки высокого уровня придерживаются концепции типов данных. Тип данных представляет собой набор значений, хранящихся в переменных, а также набор операций, выполнение которых допускается над этими значениями. Обычные типы данных - это целые числа, символы и числа с плавающей точкой. Язык С имеет несколько встроенных типов данных, однако он не является сильно типизированным языком, как Pascal или Ada. В языке С допускаются почти все преобразования типов. Например, в выражениях можно свободно смешивать переменные символьного и целого типов.
В отличие от большинства языков высокого уровня, в С почти отсутствует контроль ошибок в процессе выполнения программы. Например, не проверяется нарушение границ массивов. Ответственность за подобные ошибки полностью возлагается на программиста.
Аналогично этому С не требует строгой совместимости параметров и аргументов функций. В языках программирования высокого уровня обычно необходимо, чтобы тип аргумента более или менее соответствовал типу параметра. Для С это не характерно, здесь аргумент может иметь почти любой тип, если его можно разумно преобразовать в тип параметра. Более того, компилятор С автоматически осуществляет все виды необходимых преобразований.
Отличительной особенностью языка С является возможность манипулирования непосредственно битами, байтами, словами и указателями. Поэтому С хорошо приспособлен для системного программирования.
Другая важная особенность С - это малое количество ключевых слов, составляющих команды языка. В С89 определено 32 ключевых слова, причем в С99 добавлено только 5 слов. Языки высокого уровня обычно имеют значительно больше ключевых слов, например, в большинстве версий языка Basic их количество превышает сотню!
Язык С хорошо структурирован. Он очень похож на другие структурированные языки, такие как ALGOL, Pascal и Modula-2.
Блочно-структурированные языки допускают определение функций внутри других функций. Поскольку в С такой возможности нет, формально он не может быть причислен к блочно-структурированным языкам.
Отличительной особенностью структурированного языка является отдельное размещение различных частей кода программы и данных. Таким способом программист может "скрыть" часть информации, используемую для выполнения специфической задачи, от тех участков программы, где эта информация не нужна. Один из способов достижения этого - использование подпрограмм с локальными переменными. В этом случае любые действия внутри программы не вызовут побочных эффектов в других ее частях. Это позволяет программам, написанным на С, совместно использовать готовые части кода. Для использования функции, хранящейся отдельно, необходимо только знать, что эта функция делает, при этом вовсе не обязательно знать, как именно она это делает. Но следует помнить, что чрезмерное использование глобальных переменных (то есть переменных, видимых во всей программе) приводит к ошибкам и побочным эффектам, которые очень трудно устранить (особенно хорошо знакомы с этой трудностью программисты, работавшие на стандартной версии языка Basic).
Структурированный язык предоставляет программисту много различных возможностей. Например, структурированные языки обычно содержат несколько типов операторов цикла, таких как while, do-while и for. В структурированных языках использование оператора goto или запрещено, или не рекомендуется, для них он не является приемлемым средством управления процессом (что, однако, не относится к стандартной версии языка Basic и традиционной версии языка FORTRAN). Структурированный язык позволяет по¬местить оператор в любом месте строки, не привязывая его к определенному полю (что характерно, например, для старых версий языка FORTRAN).
Структурированные языки появились сравнительно недавно. Фактически признаком того, что язык создан довольно давно, служит его неструктурированность. Сего¬дня мало кто из программистов решится писать серьезную программу на неструктурированном языке.
Попытки добавить элементы структурированности во многие старые языки предпринимались неоднократно. Так, одна из самых смелых попыток была предпринята для языка Basic. Однако преодолеть недостатки этих языков не удалось, так как при их создании изначально были проигнорированы принципы структурированности.
Главная конструкция структурного программирования на языке С - функция, являющаяся здесь единственным видом подпрограммы. С помощью функций С осуществляются все действия программы. Функции позволяют определить и отдельно закодировать различные задачи, решаемые программой, благодаря чему эта программа становится модульной. Написав правильно функцию, можно быть уверенным в ее надежной работе в различных ситуациях без побочных эффектов в других частях программы. При работе над большим проектом, когда особенно важно, чтобы одна часть кода ни в коем случае не могла непредвиденно подействовать на другую часть, умение создать отдельную функцию приобретает для программиста исключительное значение.
Другой способ структурирования программы, написанной на языке С, заключается в использовании программных блоков. Программный блок — это логически связанная группа операторов программы, которую можно рассматривать как отдельную программную единицу. В языке С блок представляет собой последовательность операторов программы, заключенную в фигурные скобки. В примере кода
if (x<10) {
printf("Слишком мало, попытайтесь еще раз.Хп");
scanf("%d", &х); }
два оператора, стоящие после if в фигурных скобках, выполняются только в том случае, если значение х меньше десяти. Эти два оператора вместе со скобками составляют про¬граммный блок. В данном примере эти операторы представляют собой логический блок, или программную единицу, так как один оператор не может быть выполнен без выполнения другого. Использование программных блоков позволяет сделать программу понятной и эффективной. Более того, программные блоки помогают лучше формализовать задачу и более точно запрограммировать алгоритм ее решения.
В противовес этому язык С был создан и апробирован активно работающими программистами. В результате С обеспечивает то, чего и ждут от него именно программисты: небольшое количество ограничений, блочную структуру, автономные функции и малое количество ключевых слов. Программы, написанные на языке С, обладают эффективностью программ, написанных на языке ассемблера, и структурированностью, присущей программам, созданным на языках Pascal или Modula-2. Поэтому неудивительно, что во всем мире С стал универсальным языком программирования. Решающим фактором успеха языка С стало то, что во многих случаях он может быть использован вместо ассемблера, который основан на символическом представлении бинарного кода, непосредственно выполняемого компьютером. Каждая операция ассемблера представляет для компьютера одну элементарную задачу. Разрабатывая программу на языке ассемблера, программист может сделать программу максимально гибкой и эффективной, однако работа с самой программой ассемблера и ее отладка - чрезвычайно трудоемкий процесс. Более того, из-за отсутствия средств структурного программирования в языке ассемблера окончательная программа представляет собой то, что программисты называют «спагетти» - хаотичную совокупность переходов, индексов и вызовов функций. Из-за своей неструктурированности программа, написанная на языке ассемблера, с большим трудом поддается расширению, модификации и даже просто пониманию. И что, возмоно, наиболее существенно, процедуры, написанные на языке ассемблера, не обладают переносимостью на компьютеры с процессорами, система команд которых отличается от системы команд исходного процессора.
Первоначально С использовался для решения задач системного программирования. Системная программа — это часть операционной системы компьютера или утилита, как, например, редактор, транслятор, компоновщик и т.п. По мере роста популярности С, многие программисты стали использовать его для решения других задач благодаря его переносимости и эффективности.
С появлением языка C++ многим программистам стало казаться, что С прекратил свое существование как отдельный язык программирования, Однако это не так. Во-первых, не для всех программ нужны объектно-ориентированные возможности C++. Например, та¬кие приложения как системы внедрения объектов, по-прежнему программируются главным образом на С. Во-вторых, в настоящее время во всем мире работает чрезвычайно много программ, написанных на С, причем разработчики продолжают модернизировать и поддерживать эти программы. В-третьих, разработка нового стандарта С99 убеждает в том, что развитие и совершенствование С продолжается. То, что С стал базисом для C++, на¬всегда останется его неоспоримой заслугой, и в то же время язык С сам остается одним из лучших языков программирования.