Описание языка си


Содержание

ОБЩАЯ ХАРАКТЕРИСТИКА ЯЗЫКА И ПРИМЕР ПРОГРАММЫ НА СИ

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

Язык Си представляет собой удачный компромисс между желанием располагать теми возможностями, которые обычно предоставляют программисту столь понятные и удобные языки высокого уровня, и стремлением эффективно использовать особенности компьютера. Кроме набора средств, присущих современным языкам программирования высокого уровня (структурность, модульность, определяемые типы данных) в него включены средства для программирования «почти» на уровне ассемблера (использование указателей, побитовые операции, операции сдвига). Большой набор операторов позволяет писать компактные и эффективные программы. Однако, такие мощные средства требуют от программиста осторожности, аккуратности и хорошего знания языка со всеми его преимуществами и недостатками. В отличие от языков программирования типа Паскаль, требующих жесткой дисциплины программирования, ограничивающих свободу программиста, содействующих устранению многих ошибок в программах еще на стадии их трансляции, язык Си предоставляет программисту наибольшую свободу. Однако, ответственность за корректность программ при этом полностью ложится на программиста.

Си был создан Денисом Ритчи как инструмент для разработки операционной системы UNIX и реализован в рамках этой операционной системы. Название языка имеет случайное происхождение: «С» — третья буква английского алфавита. Это наименование говорит о чувстве юмора у создателей языка — его предшественником был язык В («В» — вторая буква английского алфавита).

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

Язык Си — компилирующего типа. Текст программы на Си, подготовленный с помощью текстового редактора, для получения объектного модуля обрабатывается компилятором, причем компиляция выполняется в два прохода. При первом проходе (претрансляцни) обрабатываются строки-директивы, начинающиеся со знака #, при втором — транслируется текст программы и создается объектный (машинный) код. Для получения загрузочного (исполняемого) модуля теперь необходимо отредактировать внешние связи объектного модуля — подсоединить к нему соответствующие библиотечные модули.

Рассмотрим простую программу на языке Си. Такой пример позволит нам выявить некоторые основные черты любой программы, написанной на языке Си.

main() /* Простая программа */ — подключение файла stdio.h, который является частью пакета, имеющегося в любом компиляторе языка Си и описывающего функции ввода-вывода (например, средства взаимодействия программы с терминалом). В качестве имени файла используется аббревиатура английских слов: STandard Input/Output header — стандартный заголовок ввода-вывода. Данная строка не является оператором языка Си. Символ # оказывает, что она должна быть обработана «препроцессором» компилятора. Препроцессор осуществляет некоторую предварительную обработку текста программы перед началом компиляции.

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

/* Простая программа */-комментарий. Использование комментариев облегчает процесс понимания программы любым программистом. Большим удобством при написании комментариев является возможность располагать их в той же строке, что и операции, которые они объясняют. Длинный комментарий может помещаться в отдельной строке или даже занимать несколько строк. Все, что находится между символом, указывающим начало комментария /*, и символом, указывающим его

конец */, игнорируется компилятором, поскольку он не в состоянии интерпретировать язык, отличный от Си.

< >— фигурные скобки отмечают начало и конец тела функции. Фигурные скобки применяются также для того, чтобы объединить несколько операторов программы в сегмент или «блок».

int num; — оператор описания. С помощью такого оператора мы объявляем, что будем использовать в программе переменную num, которая принимает целые (int) значения.

Точка с запятой в конце строки является частью оператора языка Си, а не разделителем операторов, как в Паскале.

Слово int служит «ключевым словом», определяющим один из основных типов данных языка Си. Ключевыми словами называются специальные зарезервированные слова, используемые для построения фраз языка.

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

num=l; — оператор присваивания. Служит для присваивания переменной num значения 1.

printf(«этo моя %d-я программа на языке Си.\n»,num); — вызов функции printf() форматного вывода на печать. Этот оператор выводит на печать значение переменной num в формате, содержащемся в строке в кавычках (в данном случае печатается приведенная в кавычках фраза. Символы %d указывают компилятору, где в выводимой строке и в какой форме необходимо вставить значение переменной num. Символ % сигнализирует программе, что, начиная с этой позиции, необходимо вывести значение переменной, a d указывает, что число надо печатать в десятичном формате.

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

В общем случае обращение к этой функции имеет вид:

где , , . — имена переменных, значения которых надо ввести. Наличие символа «&» перед каждым именем обязательно (кроме переменных строкового типа), его смысл будет пояснен ниже.

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

В качестве спецификаций в формате можно использовать те же символы, что и в функции printf(). Спецификации формата должны соответствовать количеству и типу вводимых переменных. В управляющей строке функции scanf нет промежуточных управляющих символов между % и символом преобразования, за исключением «*». Если присутствует символ *, входное поле просто пропускается и никакого присваивания не производится. Обычные символы (кроме °о) сопоставляются с очередными (отличными от символов промежутков) символами входного потока, и в случае отличия дальнейший ввод игнорируется.

int data,month,year; char name[15],town[15];

printf(«Kaк вас зовут? «); scanf(«%s»,name); printf(«Укажите дату, месяц и год вашего рождения.\пДата:»);

printf («Месяц (числом) :») ; scanf («%d», Sinonth) ;

printf(«А в каком городе? «); scanf(«%s»,town);

printf(«Вот мы и познакомились. \n»);

printf(«Вас зовут %s «,name); printf(«Вы родились в городе %s (%d.%d.%d)»,town,data, month,year);)

Результат работы программы:

Как вас зовут? Иван

Укажите дату, месяц и год вашего рождения.

Месяц (номером): 02

А в каком городе? Новгород

Вот мы н познакомились.

Вы родились в городе Новгород

Не нашли то, что искали? Воспользуйтесь поиском:

Лучшие изречения: Сдача сессии и защита диплома — страшная бессонница, которая потом кажется страшным сном. 8774 — | 7145 — или читать все.

188.64.174.135 © studopedia.ru Не является автором материалов, которые размещены. Но предоставляет возможность бесплатного использования. Есть нарушение авторского права? Напишите нам | Обратная связь.

Отключите adBlock!
и обновите страницу (F5)

очень нужно

Советы по языку программирования Си: 10 полезных приемов

Си — это один из самых важных и широко распространённых языков программирования. Его можно использовать не только для общих целей, но и для написания низкоуровневых программ, работающих с “железом”. Си позволяет программисту многое из того, чего не позволяют другие языки. Однако в этом кроется как сильная, так и слабая сторона языка: можно писать высокопроизводительный код, но гораздо проще выстрелить себе в ногу. Поэтому мы делимся с вами десятью советами, которые пригодятся как начинающим, так и опытным Си-разработчикам.

1. Указатели на функцию

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

Этот приём заключается в следующем. Сперва нужно задать тип “указатель на функцию, возвращающую что-то” и использовать его для объявления переменной. Рассмотрим простой пример. Сначала я задаю тип PFC (Pointer to a Function returning a Character):

Затем использую его для объявления переменной z :

Определяю функцию a() :

Адрес функции теперь хранится в z :

Заметим, что вам не нужен оператор & («address-of») ; компилятор знает, что a должна быть адресом функции. Так происходит из-за того, что с функцией можно произвести лишь две операции: 1) вызвать её или 2) взять её адрес. Поскольку вызова функции не происходит (отсутствуют скобки), остаётся лишь вариант с получением адреса, который помещается в z .

Чтобы вызвать функцию, адрес которой находится в z , просто добавьте скобки:

2. Списки аргументов переменной длины

Обычно вы объявляете функцию, которая принимает фиксированное число аргументов. Тем не менее, можно написать функцию, которая принимает любое их количество. Стандартная функция printf() тому доказательство. Разумеется, вы можете сами написать подобную функцию. Вот пример:

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

1 июля – 3 декабря, онлайн, беcплатно

С переменными аргументами работают несколько встроенных функций и макросов: va_list , va_start , va_arg и va_end (они определены в stdarg.h ).

Сперва вам нужно объявить указатель на список аргументов:

Затем установите argp в первый аргумент переменной части. Это первый аргумент после последнего фиксированного (в нашем случае arg_count ):

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

Заметим, что вам нужно знать тип аргумента заранее (в нашем случае int ) и число аргументов (у нас задаётся фиксированным arg_count ).

Наконец, нужно убраться при помощи va_end :

3. Проверка и установка отдельных битов

Битовые операции иногда воспринимаются как некий сорт тёмной магии, используемой продвинутыми программистами. Да, работа с битами напрямую может быть весьма непонятной, но понимание этого процесса может вам пригодиться.

Обсудим, зачем это вообще нужно. Программы часто используют переменные-флаги для хранения булевых величин. У вас в коде вполне могут встречаться такие переменные:

Если они как-то связаны, как в примере выше (они все описывают состояние движения), то зачастую бывает удобнее хранить их в одной “переменной состояния” и использовать каждый бит для задания того или иного состояния:

Тогда вы сможете использовать битовые операции для задания или обнуления отдельных битов:

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

В архиве с кодом, который будет дан в конце статьи, есть пример работы с битами.

Чтобы установить заданный бит переменной value (в диапазоне от 0 до 31), используйте такое выражение:

Для очистки бита используйте:

А для получения значения бита:

4. Ленивые логические операторы

Логические операторы Си, && (“и”) и || (“или”), позволяют вам составлять цепочки условий в тех случаях, когда действие должно выполняться при выполнении всех условий ( && ) или только одного ( || ). Но в Си также есть операторы & и | . Очень важно понимать разницу между ними. Если вкратце, двухсимвольные операторы ( && и || ) называются “ленивыми” операторами. Если они используются между двумя выражениями, то второе будет выполнено, только если первое оказалось верным, иначе оно пропускается. Рассмотрим пример:

Тест (int)f && feof(f) должен вернуть истинный результат, когда будет достигнут конец файла f . Сперва тест вычисляет f ; она будет равна нулю (ложное значение), если файл не был открыт. Это ошибка, поэтому попытка чтения файла не увенчается успехом. Однако, поскольку первая часть теста провалена, вторая не будет обработана, и feof() не будет запущена. Этот пример демонстрирует корректное использование ленивого оператора. Теперь посмотрите на этот код:

В этом случае используется оператор & , а не && . Оператор & — это инструкция для выполнения обоих выражений при любых условиях. Поэтому, даже если первая часть теста провалится, вторая будет выполнена. Это может привести к различным ошибкам.

5. Тернарные операторы

Операция называется тернарной. когда принимает три операнда. В Си тернарный оператор ? : можно использовать для сокращённой записи тестов if..else . Общий синтаксис выглядит так:

Пусть у нас есть две целых переменных, t and items . Мы можем использовать if..else для проверки значения items и присваивания её значения переменной t таким образом:

Используя тернарный оператор, эту запись можно сократить до одной строки:

Если вы не привыкли к тернарным операторам, то они могут показаться вам странными, но на самом деле они весьма удобны.

Рассмотрим ещё один пример. Этот код выводит первую строку, когда у нас один предмет, и вторую, когда их несколько:

Это можно переписать так:

6. Стек

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

Код ниже задаёт очень маленький стек: массив _stack из двух целых. Помните, что при тестировании всегда лучше использовать небольшие числа. Если код содержит ошибки, найти их при работе с массивом из 2 элементов будет проще, чем если их будет 100. Также объявляется указатель на стек _sp и устанавливается в основание стека _stack :

Теперь определим функцию push() , которая помещает целое в стек. Она возвращает новое число элементов в стеке или -1, если стек полон:

Для получения элементов стека нужна функция pop() . Она возвращает новое число элементов в стеке или -1, если он пуст:

А вот пример, демонстрирующий работу со стеком:

7. Копирование данных

Вот три способа копирования данных. Первый использует стандартную функцию memcpy() , которая копирует n байт из src в dst :

Теперь посмотрим на самодельную альтернативу memcpy() . Она может быть полезной, если копируемые данные нужно как-то обработать:

И наконец, функция, использующая 32-битные целые для ускорения копирования. Помните, что скорость в конечном итоге зависит от оптимизации компилятора. В этом примере предполагается, что счётчик данных n кратен 4 из-за работы с 4-байтовыми указателями:

Примеры можно найти в архиве ниже.

8. Использование заголовочных файлов

Си использует заголовочные файлы ( .h ), которые могут содержать объявления функций или констант. Заголовочный файл можно импортировать в код двумя способами: если файл предоставляется компилятором, используйте #include , а если файл написан вами — #include «mystring.h» . В сложных программах есть риск того, что вы можете подключить один и тот же заголовочный файл несколько раз.

Предположим, что у нас есть простой заголовочный файл, header1.h , содержащий следующие определения:

Затем создадим другой файл header2.h , содержащий это:

Добавим в нашу программу, main.c , это:

При компиляции программы мы получим ошибку компиляции, потому что T_SIZE будет объявлена дважды (её определение в header1 подключено к двум разным файлам). Мы должны подключить header1 к header2 для того, чтобы header2 компилировался в тех случаях, когда header1 не используется. И как это исправить? Можно написать макрос для header1 :

Эта проблема настолько распространена, что многие IDE делают это за вас. Тем не менее, если вы будете делать это вручную, делайте это осторожно.

9. Скобки: нужны ли они?

Вот несколько простых правил:

  1. Скобки нужно использовать для изменения порядка выполнения операторов. Например, 3 * (4 + 3) — не то же самое, что 3 * 4 + 3 .
  2. Скобки можно использовать для улучшения читаемости. Здесь они, очевидно, не нужны:

Приоритет оператора || ниже, чем и > . Однако, в этом случае скобки точно не будут лишними:

Скобки стоит использовать в макросах:

Вы не знаете, где будете использовать эту константу, поэтому скобки нужны. Рассмотрим такой случай применения:

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

Но в одном месте скобки точно не нужны: в выражении после return. Например, это…

…выполнится так же, как это:

10. Массивы как адреса

Программисты, которые учат Си после какого-то другого языка, часто удивляются, когда Си работает с массивами как с адресами и наоборот. Массив — это контейнер фиксированного размера, а адрес — это число, связанное с местом в памяти; разве они связаны?

Си прав: массив — это просто адрес базы в блоке памяти, а форма записи массива, например, в Java или JavaScript — просто синтаксический сахар.

Присмотритесь к этому коду:

Первый цикл здесь копирует адрес каждого элемента массива в сам массив:

На каждой итерации адрес увеличивается на i . Поэтому адрес переменной _x будет первым элементом, а каждый следующий адрес — адресом _x плюс 1. Когда мы прибавляем 1 к адресу массива, компилятор Си вычисляет подходящий сдвиг в зависимости от типа данных (в нашем случае 4 байта для массива целых).

Второй цикл выводит значения, хранящиеся в массиве, сперва выводя адрес элемента _x + i , затем значение элемента через привычный вид массива _x[i] , а потом содержимое массива с использованием адресной нотации (где оператор * возвращает содержимое памяти по адресу в скобках): *(_x + i) . Во всех случаях значения будут одинаковыми. Это наглядно демонстрирует, что массив и адрес — это одно и то же.

Обратите внимание, что для получения адреса массива вам не нужен оператор & , поскольку компилятор считает, что массив и есть адрес.

Для того, чтобы попробовать применить эти советы на практике, вы можете скачать исходники.

Реферат: Описание Языка СИ

Элементы Языка СИ


Используемые символы

Множество символов используемых в языке СИ можно разделить на пять групп.

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

Название: Описание Языка СИ
Раздел: Рефераты по информатике, программированию
Тип: реферат Добавлен 11:59:44 24 февраля 2002 Похожие работы
Просмотров: 573 Комментариев: 14 Оценило: 4 человек Средний балл: 5 Оценка: неизвестно Скачать
Прописные буквы латинского алфавита A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Строчные буквы латинского алфавита a b c d e f g h i j k l m n o p q r s t u v w x y z
Символ подчеркивания _

2. Группа прописных и строчных букв русского алфавита и арабские цифры (табл.2).

Прописные буквы русского алфавита А Б В Г Д Е Ж З И К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ы Ь Э Ю Я
Строчные буквы русского алфавита а б в г д е ж з и к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я
Арабские цифры 0 1 2 3 4 5 6 7 8 9

3. Знаки нумерации и специальные символы (табл. 3). Эти символы используются с одной стороны для организации процесса вычислений, а с другой — для передачи компилятору определенного набора инструкций.

Символ Наименование Символ Наименование
, запятая ) круглая скобка правая
. точка ( круглая скобка левая
; точка с запятой > фигурная скобка правая
: двоеточие < фигурная скобка левая
? вопросительный знак больше
! восклицательный знак [ квадратная скобка
| вертикальная черта ] квадратная скобка
/ дробная черта # номер
\ обратная черта % процент
тильда & амперсанд
* звездочка ^ логическое не
+ плюс = равно
мину « кавычки

4. Управляющие и разделительные символы. К той группе символов относятся: пробел, символы табуляции, перевода строки, возврата каретки, новая страница и новая строка. Эти символы отделяют друг от друга объекты, определяемые пользователем, к которым относятся константы и идентификаторы. Последовательность разделительных символов рассматривается компилятором как один символ (последовательность пробелов).

5. Кроме выделенных групп символов в языке СИ широко используются так называемые, управляющие последовательности, т.е. специальные символьные комбинации, используемые в функциях ввода и вывода информации. Управляющая последовательность строится на основе использования обратной дробной черты (\) (обязательный первый символ) и комбинацией латинских букв и цифр (табл.4).

Управляющая последовательность Наименование Шеснадцатеричная замена
\a Звонок 007
\b Возврат на шаг 008
\t Горизонтальная табуляция 009
\n Переход на новую строку 00A
\v Вертикальная табуляция 00B
\r Возврат каретки 00C
\f Перевод формата 00D
Кавычки 022
\’ Апостроф 027
\0 Ноль-символ 000
\\ Обратная дробная черта 05C
\ddd Символ набора кодов ПЭВМ в восьмеричном представлении
\xddd Символ набора кодов ПЭВМ в шестнадцатеричном представлении

Последовательности вида \ddd и \xddd (здесь d обозначает цифру) позволяет представить символ из набора кодов ПЭВМ как последовательность восьмеричных или шестнадцатеричных цифр соответственно. Например символ возврата каретки может быть представлен различными способами:

\r — общая управляющая последовательность,

\015 — восьмеричная управляющая последовательность,

\x00D — шестнадцатеричная управляющая последовательность.

Следует отметить, что в строковых константах всегда обязательно задавать все три цифры в управляющей последовательности. Например отдельную управляющую последовательность \n (переход на новую строку) можно представить как \010 или \xA, но в строковых константах необходимо задавать все три цифры, в противном случае символ или символы следующие за управляющей последовательностью будут рассматриваться как ее недостающая часть. Например:

«ABCDE\x009FGH» данная строковая команда будет напечатана с использованием определенных функций языка СИ, как два слова ABCDE FGH, разделенные 8-ю пробелами, в этом случае если указать неполную управляющую строку»ABCDE\x09FGH»,то на печати появится ABCDE=|=GH, так как компилятор воспримет последовательность \x09F как символ «=+=».

Отметим тот факт, что, если обратная дробная черта предшествует символу не являющемуся управляющей последовательностью (т.е. не включенному в табл.4) и не являющемуся цифрой, то эта черта игнорируется, а сам символ представляется как литеральный. Например:

символ \h представляется символом h в строковой или символьной константе.

Кроме определения управляющей последовательности, символ обратной дробной черты (\) используется также как символ продолжения. Если за (\) следует (\n), то оба символа игнорируются, а следующая строка является продолжением предыдущей. Это свойство может быть использовано для записи длинных строк.

1.1.2. Константы

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

Целая константа: это десятичное, восьмеричное или шестнадцатеричное число, которое представляет целую величину в одной из следующих форм: десятичной, восьмеричной или шестнадцатеричной.

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

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

Шестнадцатеричная константа начинается с обязательной последовательности 0х или 0Х и содержит одну или несколько шестнадцатеричных цифр (цифры представляющие собой набор цифр шеснадцатеричной системы счисления: 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F)

Примеры целых констант: Десятичная Восьмеричная Шестнадцатеричная константа константа константа 16 020 0x10 127 0117 0x2B 240 0360 0XF0

Если требуется сформировать отрицательную целую константу, то используют знак «-» перед записью константы (который будет называться унарным минусом). Например: -0x2A, -088, -16 .

Каждой целой константе присваивается тип, определяющий преобразования, которые должны быть выполнены, если константа используется в выражениях. Тип константы определяется следующим образом:

— десятичные константы рассматриваются как величины со знаком, и им присваивается тип int (целая) или long (длинная целая) в соответствии со значением константы. Если константа меньше 32768, то ей присваивается тип int в противном случае long.

— восьмеричным и шестнадцатеричным константам присваивается тип int, unsigned int (беззнаковая целая), long или unsigned long в зависимости от значения константы согласно табл 5.

Диапазон шестнадцатеричных констант Диапазон восьмеричных констант Тип
0x0 — 0x7FFF 0 — 077777 int
0X8000 — 0XFFFF 0100000 — 0177777 unsigned int
0X10000 — 0X7FFFFFFF 0200000 — 017777777777 long
0X80000000 — 0XFFFFFFFF 020000000000 — 037777777777 unsigned long

Для того чтобы любую целую константу определить типом long, достаточно в конце константы поставить букву «l» или «L». Пример:

5l, 6l, 128L, 0105L, OX2A11L.

Константа с плавающей точкой — десятичное число, представленное в виде действительной величины с десятичной точкой или экспонентой. Формат имеет вид:

[ цифры ].[ цифры ] [ Е|e [+|-] цифры ] .

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

Примеры: 115.75, 1.5Е-2, -0.025, .075, -0.85Е2

Символьная константа — представляется символом заключенном в апострофы. Управляющая последовательность рассматривается как одиночный символ, допустимо ее использовать в символьных константах. Значением символьной константы является числовой код символа. Примеры:

‘\n’ — символ новой строки ,

‘\\’ — обратная дробная черта ,

‘\v’ — вертикальная табуляция .

Символьные константы имеют тип int и при преобразовании типов дополняются знаком.

Строковая константа (литерал) — последовательность символов (включая строковые и прописные буквы русского и латинского а также цифры) заключенные в кавычки («) . Например: «Школа N 35», «город Тамбов», «YZPT КОД».

Отметим, что все управляющие символы, кавычка («), обратная дробная черта (\) и символ новой строки в строковом литерале и в символьной константе представляются соответствующими управляющими последовательностями. Каждая управляющая последовательность представляется как один символ. Например, при печати литерала «Школа \n N 35» его часть «Школа» будет напечатана на одной строке, а вторая часть «N 35» на следующей строке.

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

Строковый литерал имеет тип char[] . Это означает, что строка рассматривается как массив символов. Отметим важную особенность, число элементов массива равно числу символов в строке плюс 1, так как нулевой символ (символ конца строки) также является элементом массива. Все строковые литералы рассматриваются компилятором как различные объекты. Строковые литералы могут располагаться на нескольких строках. Такие литералы формируются на основе использования обратной дробной черты и клавиши ввод. Обратная черта с символом новой строки игнорируется компилятором, что приводит к тому, что следующая строка является продолжением предыдущей. Например:

«строка неопределенной \n

полностью идентична литералу

«строка неопределенной длинны» .

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

1.1.3. Идентификатор

Идентификатором называется последовательность цифр и букв, а также специальных символов, при условии, что первой стоит буква или специальный символ. Для образования идентификаторов могут быть использованы строчные или прописные буквы латинского алфавита. В качестве специального символа может использоваться символ подчеркивание (_). Два идентификатора для образования которых используются совпадающие строчные и прописные буквы, считаются различными. Например: abc, ABC, A128B, a128b .

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

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

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

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

1.1.4. Ключевые слова

Ключевые слова — это зарезервированные идентификаторы, которые наделены определенным смыслом. Их можно использовать только в соответствии со значением известным компилятору языка СИ.

Приведем список ключевых слов

auto double int struct break else long switch register tupedef char extern return void case float unsigned default for signed union do if sizeof volatile continue enum short while

Кроме того в рассматриваемой версии реализации языка СИ, зарезервированными словами являются :

_asm, fortran, near, far, cdecl, huge, paskal, interrupt .

Ключевые слова far, huge, near позволяют определить размеры указателей на области памяти. Ключевые слова _asm, cdelc, fortran, pascal служат для организации связи с функциями написанными на других языках, а также для использования команд языка ассемблера непосредственно в теле разрабатываемой программы на языке СИ.

Ключевые слова не могут быть использованы в качестве идентификаторов.

1.1.5. Использование комментариев в тексте программы

Комментарий — это набор символов, которые игнорируются компилятором, на этот набор символов, однако, накладываются следующие ограничения. Внутри набора символов, который представляет комментарий не может быть специальных символов определяющих начало и конец комментариев, соответственно (/* и */). Отметим, что комментарии могут заменить как одну строку, так и несколько. Например:

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

Описание языка си

Язык программирования Си отличается минимализмом. Авторы языка хотели, чтобы программы на нём легко компилировались с помощью однопроходного компилятора, после компиляции каждой элементарной составляющей программы соответствовало весьма небольшое число машинных команд, а использование базовых элементов языка не задействовало библиотеку времени выполнения. Однопроходный компилятор компилирует программу, не возвращаясь назад, к уже откомпилированному тексту. Поэтому использованию функции должно предшествовать её объявление. Код на Си можно легко писать на низком уровне абстракции, почти как на ассемблере. Иногда Си называют «универсальным ассемблером» или «ассемблером высокого уровня», что отражает различие языков ассемблера для разных платформ и единство стандарта Си, код которого может быть скомпилирован без изменений практически на любой модели компьютера. Си часто называют языком среднего уровня или даже низкого уровня, учитывая то, как близко он работает к реальным устройствам.

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

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

  • простую языковую базу, из которой вынесены в библиотеки многие существенные возможности, вроде математических функций или функций управления файлами;
  • ориентацию на процедурное программирование, обеспечивающую удобство применения структурного стиля программирования;
  • систему типов, предохраняющую от бессмысленных операций;
  • использование препроцессора для, например, определения макросов и включения файлов с исходным кодом;
  • непосредственный доступ к памяти компьютера через использование указателей;
  • минимальное число ключевых слов;
  • передачу параметров в функцию по значению, а не по ссылке (при этом передача по ссылке выполняется с помощью указателей);
  • указатели на функции и статические переменные
  • области действия имён;
  • записи — определяемые пользователем собирательные типы данных (структуры), которыми можно манипулировать как одним целым;

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

  • автоматическое управление памятью;
  • поддержка объектно-ориентированного программирования (при этом первые версии C++ генерировали код программы на языке Си);
  • замыкание;
  • вложенные функции (существуют компиляторы языка Си реализующие эту функцию, например компилятор GNU);
  • полиморфизм функций и операторов;
  • встроенная поддержка многозадачности и сети
  • функции высшего порядка
  • карринг.

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

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

Программа «Hello, World!»

Эта простая программа, появившаяся в первом издании книги «Язык программирования Си» Кернигана и Ритчи, обычно является первой программой большинства учебников Си. Она печатает сообщение «Hello World!» на стандартном устройстве вывода (которым, как правило, является монитор (дисплей), но может быть и файл, какое-либо устройство или область в памяти, в зависимости от того, как отражается стандартное устройство вывода на данной платформе).

Несмотря на то, что на большинстве современных компиляторов эта программа может быть корректно скомпилирована, она порождает несколько предупреждений на компиляторах стандарта ANSI C. Кроме того, этот код не будет компилироваться, если компилятор жёстко следует стандарту int в качестве возвращаемого значения. Эти сообщения можно убрать, если внести в эту программу несколько небольших изменений:

В первой строке программы расположена директива препроцессора #include, встретив которую, компилятор заменяет её на полный текст файла, на который она ссылается. В данном случае эта строка будет заменена стандартным заголовочным файлом stdio.h в каталоге стандартных заголовочных файлов.

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

Фигурные скобки после функции main обозначают её определение. Слово int говорит, что функция main возвращает (вычисляет) целое число. Слово void показывает, что функция main не требует от вызывающего ни параметров, ни аргументов.

Выражение return заставляет программу прекратить выполнение данной функции (main в этом случае), возвращая вызвавшей функции значение, указанное после ключевого слова return (0 в этом случае). Так как текущая функция — это main, то вызывающим является то, что запустило программу. Последняя фигурная скобка обозначает конец определения функции main.

Комментарии

Текст, заключённый в служебные символы /* и */ в этом порядке, полностью игнорируется компилятором. Компиляторы, совместимые со стандартом C99, также позволяют использовать комментарии, начинающиеся с символов // и заканчивающиеся переводом строки

Система типов в Си подобна типам в других потомках Алгола, таких, как Паскаль. В Си имеются типы целых чисел различных размеров, со знаком и без, чисел с плавающей запятой, символов, перечисляемых типов (enum) и записей-структур (struct). Кроме того, язык Си предлагает тип объединение (union), с помощью которого можно либо хранить в одном месте памяти разнородные данные, не пересекающиеся по времени существования (это позволяет экономить память), либо обращаться к содержимому участка памяти, как к данным разных типов (что позволяет менять тип-интерпретацию данных, не меняя сами данные).

Хранение данных

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

В Си есть три разных способа выделения памяти для объектов:

  • Статическое выделение памяти: пространство для объектов создаётся в области хранения данных кода программы в момент компиляции; время жизни таких объектов совпадает со временем жизни этого кода.
  • Автоматическое выделение памяти: объекты можно временно хранить в стеке; эта память затем автоматически освобождается и может быть использована снова, после того, как программа выходит из блока, использующего её.
  • Динамическое выделение памяти: блоки памяти нужного размера могут запрашиваться во время выполнения программы с помощью библиотечных функций malloc, realloc и free из области памяти, называемой кучей. Эти блоки освобождаются и могут быть использованы снова после вызова для них функции free.

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

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

Набор используемых символов

Язык Си был создан уже после внедрения стандарта $ @ `). Более старые языки вынуждены были обходиться более скромным набором — так, Фортран, Лисп и Кобол использовали только круглые скобки ( ), а в Си есть и круглые ( ), и квадратные [ ], и фигурные . Кроме того, в Си различаются заглавные и строчные буквы, а более старые языки использовали только заглавные.

Проблемы

Многие элементы Си потенциально опасны, а последствия неправильного использования этих элементов зачастую непредсказуемы. Керниган говорит: «Си — инструмент, острый, как бритва: с его помощью можно создать и элегантную программу, и кровавое месиво». В связи со сравнительно низким уровнем языка многие случаи неправильного использования опасных элементов не обнаруживаются и не могут быть обнаружены ни при компиляции, ни во время исполнения. Это часто приводит к непредсказуемому поведению программы. Иногда в результате неграмотного использования элементов языка появляются уязвимости в системе безопасности. Необходимо заметить, что использования многих таких элементов можно избежать.

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

Другим потенциальным источником опасных ситуаций служит механизм указателей. Указатель может ссылаться на любой объект в памяти, включая и исполняемый код программы, и неправильное использование указателей может порождать непредсказуемые эффекты и приводить к катастрофичным последствиям. К примеру, указатель может быть неинициализированным или, в результате неверных арифметических операций над указателем, указывать в произвольное место памяти; на некоторых платформах работа с таким указателем может вызвать аппаратный останов программы, на незащищённых же платформах это может привести к порче произвольных данных в памяти, причём эта порча может проявиться в самые произвольные моменты времени и намного позже момента порчи. Также, область динамической памяти, на которую ссылается указатель, может быть освобождена (и даже выделена после этого под другой объект) — такие указатели называются «висячими». Или, наоборот, после манипуляций с указателями на область динамической памяти может не остаться ссылок, и тогда эта область, называемая «мусором» (garbage), никогда не будет освобождена, что может приводить к «утечкам памяти» в программе. В других языках подобные проблемы пытаются решить введением более ограниченных ссылочных типов.

Проблемой является также то, что автоматически и динамически создаваемые объекты не инициализируются и они могут содержать значения, оставшееся в памяти от ранее удалённых объектов. Такое значение полностью непредсказуемо, оно меняется от одной машины к другой, от запуска к запуску, от вызова функции к вызову. Если программа использует такое значение, то результат будет непредсказуемым и не обязательно проявится сразу. Современные компиляторы пытаются диагностировать эту проблему некоторым анализом исходного кода, хотя в общем случае статическим анализом это решить нельзя.

Ещё одной распространённой проблемой является то, что память не может быть использована снова, пока она не будет освобождена программистом с помощью функции free(). В результате программист может случайно забыть освобождать эту память, но продолжать её выделять, занимая всё большее и большее пространство. Это обозначается термином утечка памяти. Наоборот, возможно освободить память слишком рано, но продолжать её использовать. Из-за того, что система выделения может использовать освобождённую память по-другому, это ведёт к непредсказуемым последствиям. Эти проблемы решаются в языках со сборкой мусора. С другой стороны, если память выделяется в функции и должна освобождаться после выхода из функции, данная проблема решается с помощью автоматического вызова деструкторов в языке C++, или с помощью локальных массивов, используя расширения С99.

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

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

История

Ранние разработки

Язык программирования Си был разработан в лабораториях Bell Labs в период с 1969 по 1973 годы. Согласно Ритчи, самый активный период творчества пришёлся на 1972 год. Язык назвали «Си» (C — третья буква латинского алфавита), потому что многие его особенности берут начало от старого языка «Би» (B — вторая буква латинского алфавита). Существует несколько различных версий происхождения названия языка Би. Кен Томпсон указывает на язык программирования

К 1973 году язык Си стал достаточно силён, и большая часть ядра UNIX, первоначально написанная на ассемблере PDP-11/20, была переписана на Си. Это было одно из самых первых ядер операционных систем, написанное на языке, отличном от ассемблера; более ранними были лишь системы ПЛ/1) и TRIPOS (написана на BCPL).

В 1978 году Ритчи и Керниган опубликовали первую редакцию книги «Язык программирования Си». Эта книга, известная среди программистов как «K&R», служила многие годы неформальной спецификацией языка. Версию языка Си, описанную в ней, часто называют «K&R C». (Вторая редакция этой книги посвящена более позднему стандарту ANSI C, описанному ниже.)

K&R ввёл следующие особенности языка:

  • записи (тип данных struct);
  • длинное целое (тип данных long int);
  • целое без знака (тип данных unsigned int);
  • оператор += и подобные ему (старые операторы =+ вводили анализатор лексики компилятора Си в заблуждение, например, при сравнении выражений i =+ 10 и i = +10).

K&R C часто считают самой главной частью языка, которую должен поддерживать компилятор Си. Многие годы даже после выхода ANSI C, он считался минимальным уровнем, которого следовало придерживаться программистам, желающим добиться от своих программ максимальной портативности, потому что не все компиляторы тогда поддерживали ANSI C, а хороший код на K&R C был верен и для ANSI C.

После публикации K&R C в язык было добавлено несколько «неофициальных» возможностей, поддерживаемый компиляторами AT&T и некоторых других производителей:

  • функции, не возвращающие значение (с типом void) и указатели, не имеющие типа (с типом void *);
  • функции, возвращающие объединения и записи;
  • имена полей данных записей в разных пространствах имён для каждой записи;
  • присваивания записей;
  • спецификатор констант (const);
  • стандартная библиотека, реализующая большую часть функций, введённых различными производителями;
  • перечислимый тип;
  • дробное число одинарной точности (float).


ANSI C и ISO C или C89

В конце 1970-х годов Си начал вытеснять Бейсик с позиции ведущего языка для программирования микрокомпьютеров. В 1980-х годах он был адаптирован для использования в IBM PC, что привело к резкому росту его популярности. В то же время Бьярне Строуструп и другие в лабораториях Bell Labs начали работу по добавлению в Си возможностей объектно-ориентированного программирования. Язык, который они в итоге сделали, C++, в настоящее время является самым распространённым языком программирования для платформы Microsoft Windows. Си остаётся более популярным в UNIX-подобных системах.

В 1983 году Американский Национальный Институт Стандартизации (ANSI) сформировал комитет для разработки стандартной спецификации Си. По окончании этого долгого и сложного процесса в 1989 году он был наконец утверждён как «Язык программирования Си» ANSI X3.159-1989. Эту версию языка принято называть ANSI C или C89. В 1990 году стандарт ANSI C был принят с небольшими изменениями (ISO) как ISO/IEC 9899:1990.

Одной из целей этого стандарта была разработка надмножества K&R C, включающего многие особенности языка, созданные позднее. Однако комитет по стандартизации также включил в него и несколько новых возможностей, таких как прототипы функций (заимствованные из С++) и более сложный препроцессор.

ANSI C сейчас поддерживают почти все существующие компиляторы. Почти весь код Си, написанный в последнее время, соответствует ANSI C. Любая программа, написанная только на стандартном Си, гарантированно будет правильно выполняться на любой платформе, имеющей соответствующую реализацию Си. Однако большинство программ написаны так, что они будут компилироваться только определённым компилятором, потому, что:

  1. они используют нестандартные библиотеки, например, для графических дисплеев;
  2. некоторые компиляторы не придерживаются по умолчанию стандарта ANSI C, или его преемника; или
  3. они рассчитаны на определённое значение размера некоторых типов данных или на определённый способ хранения этих данных в памяти для конкретной платформы.

Вот некоторые новые особенности С99:

  • подставляемые функции (inline);
  • отсутствие ограничений на объявление локальных переменных (как и в С++);
  • новые типы данных, такие как long long int (для облегчения перехода от 32- к 64-битным числам), явный булевый тип данных и тип complex для представления комплексных чисел;
  • массивы переменной длины;
  • поддержка ограниченных указателей (restrict);
  • именованная инициализация структур: struct < int x, y, z; >point = < .y=10, .z=20, .x=30 >;
  • поддержка однострочных комментариев, начинающихся на //, заимствованных из C++ (многие компиляторы Си поддерживали их и ранее в качестве дополнения);
  • несколько новых библиотечных функций, таких как snprintf;
  • несколько новых заголовочных файлов, таких как stdint.h.

Интерес к поддержке новых особенностей С99 в настоящее время смешан. В то время как GCC, компилятор Си от Sun Microsystems и некоторые другие компиляторы в настоящее время поддерживают большую часть новых особенностей С99, компиляторы компаний Microsoft не делают этого, причём похоже, что две эти компании и не думают их добавлять.

Связь с C++

Язык программирования С++ произошёл от Си. Однако в дальнейшем Си и C++ развивались независимо, что привело к росту несовместимостей между ними. Последняя редакция Си, С99, добавила в язык несколько конфликтующих с С++ особенностей. Эти различия затрудняют написание программ и библиотек, которые могли бы нормально компилироваться и работать одинаково в компиляторах Си и C++, что, конечно, запутывает тех, кто программирует на обоих языках.

Бьёрн Строуструп, придумавший C++, неоднократно выступал за максимальное сокращение различий между Си и C++ для создания максимальной совместимости между этими языками. Противники же такой точки зрения считают, что так как Си и C++ являются двумя различными языками, то и совместимость между ними не так важна, хоть и полезна. Согласно этому лагерю, усилия по уменьшению несовместимости между ними не должны препятствовать попыткам улучшения каждого языка в отдельности.

Вот различия между этими языками, существующие на сегодня:

  • inline — подставляемые функции существуют в глобальном пространстве С++, а в Си — в пространстве файла (статическом пространстве). Другими словами, это значит, что в С++ любое определение подставляемой функции (независимо от переопределения функций) должно соответствовать правилу одного определения, требующего того, чтобы любая подставляемая функция была определена только один раз. В Си же одна и та же подставляемая функция может быть определена по-разному в разных компилируемых файлах одной программы.
  • В отличие от C++, ключевое слово bool в С99 требует включения соответствующего заголовочного файла stdbool.h. В стандарте C99 определен собственный тип логических данных _Bool. Предыдущий стандарт Си (C89) не определял булевый тип вообще, поэтому для этого часто использовались различные (а значит, несовместимые) методы.
  • Символьные константы (заключённые в одинарные кавычки) имеют тип int в Си и тип char в C++. Поэтому в Си справедливо равенство sizeof('a') == sizeof(int), а в C++ — равенство sizeof('a') == sizeof(char). [1]
  • Некоторые новые возможности C99, в первую очередь, restrict , не включены в стандарт Си++.

Си перенял от C++ ряд особенностей:

  • прототипы объявления функций;
  • однострочные комментарии, начинающиеся на // и заканчивающиеся символом перевода строки;
  • более сильную проверку типов, включая добавление типа void, спецификатора const и удаление принятия по умолчанию типа int в качестве возвращаемого значения.

Приоритет операций в Си

Ниже приведены операции в порядке убывания приоритета. Операции, приведённые на одной строчке, имеют одинаковый приоритет. Операции, помеченные как R->L, имеют правую ассоциативность (то есть при сочетании равноприоритетных операций без скобок они вычисляются справа налево; при этом порядок вычисления аргументов большинства операций не специфицирован и зависит от реализаций):

Постфиксные операции [] () . -> ++ --
Унарные операции (R->L) ++ -- & * + -

! sizeof (type)

Мультипликативные * / %
Аддитивные + -
Сдвиговые >
Операции сравнение =
Операции проверки равенство == !=
Побитовые & ^ |
Логические && ||
Условная операция (R->L) ?:
Операции присваивания (R->L) = *= /= %= += -= >= &= ^= |=
Последовательное вычисление ,

Известные компиляторы языка Си [2]

  • Borland C++
  • C++ Builder
  • Digital Mars
    • GNU Compiler Collection
    • Intel C++ compiler
    • Microsoft Visual Studio
  • Open Watcom
  • Sun Studio
  • TinyCC

Компиляторы на динамические языки и платформы

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

Следующие проекты предназначены для этих целей:

  • Clue [3] — компилятор из ANSI Си в Javascript, Java, Common Lisp.
  • Alchemy [4] — компилятор из Си/C++ в Flash- и Adobe AIR-приложениях.
  • AMPC [5] -- компилятор из Си в виртуальную машину

Модель языка Си

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

Поэтому разберемся с моделью языка Си:

  • Для чего язык Си был разработан?
  • Как выглядит программа с точки зрения языка Си?
  • Какие практические советы из этого следуют?

1. Цель создания языка C

Деннис Ритчи разработал язык C для решения двух задач:

  1. Язык должен как можно более эффективно транслироваться в машинные коды, для обеспечения максимальной производительности.
  2. Язык должен быть максимально переносимым на любые платформы.

Обе поставленные задачи были полностью решены. В 1972 году появился первый вариант языка C.

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

Также язык С в настоящее время реализован практических на всех компьютерных платформах и операционных системах.

Какую же модель языка выбрал Деннис Ритчи?

2. Модель С

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

В этом язык С отличается, например, от языка C++, в котором программа представляет собой суперкласс, от которого наследуются остальные классы.

3. Модель функции в языке С

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

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

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

Отсюда следует важный принцип языка С — принцип изоляции кода.

4. Изоляция кода в языке C

Изоляция кода — это такой принцип построения программы, который предполагает, что между фрагментами программы нет скрытых связей.

Например, предположим, что программист Иванов вызывал функцию, которую написал программист Петров. При этом и у Иванова, и у Петрова есть переменная с именем X. Она же используется в качестве аргумента функции.

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

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

  • Передача по значению.
  • Области видимости переменных.
  • Директивы условной компиляции и т.д.

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

5. Избыточность языка С

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

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

В языке C++ другая модель языка и другой подход к программированию, который получил название объектно-ориентированное программирование (ООП).

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

К счастью, 90% возможностей языка Си избыточны и вам никогда не понадобятся.

Например, есть громадная таблица приоритетов операторов C.

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

Для правильного использования приоритетов нужно всего лишь расставить скобки. И все!

Компилятор будет следовать вашим скобкам, а не этой таблице.

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

А теперь представьте ситуацию: вы вызубрили эту таблицу, написали выражение без единой скобки, а потом смотрите на это выражение через пару лет и пытаетесь в нем разобраться.

Вам захочется сказать: «Зачем я вообще этим занимался?!»

Большинство возможностей языка С похожи на эту таблицу: большие, страшные и бесполезные.

6. Командная разработка с помощью C

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

Для чего они нужны?

Если к разработке приступает команда разработчиков, то необходимо с самого начала дать каждому программисту фронт работ. Но возникает серьезная проблема: если функция, которую пишет Иванов, требуется Петрову, то Петров будет сидеть и ждать, пока Иванов ее напишет.

Тут на помощь приходит изоляция кода. Для начала работы Иванову нужно только записать интерфейс будущей функции в заголовочном файле. А реализацию функции сделать пока пустой. Это называется «поставить заглушку».

Так как в С имеет значение только вход и выход, то Петров может тут же приступать к работе и вызывать функцию Иванова. Таким образом вся команда может приступать к работе незамедлительно.

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

7. Иерархия структур данных в С

Иерархия структур данных — это одна из самых мощных возможностей языка C. В языке С можно с легкостью создавать любые, сколько угодно сложные структуры данных.

К сожалению, произошла путаница в терминологии. Раньше программными объектами назывались любые конструкции в языке программирования. Но с появлением языка C++ стали называть объектами экземпляры классов.

Хотя в языке С объектами изначально назывались структуры данных.

Поэтому для избежания путаницы будем называть эти структуры объектами данных.

Рассмотрим фрагмент иерархии объектов данных библиотеки GTK+

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

Подобная иерархия реализована в библиотеке Win32API для программирования под Windows, и во многих других программных системах.

8. Сильное абстрагирование в С

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

В качестве основного инструмента абстрагирования в языке С применяются библиотеки. Например, в самом языке нет команд работы с графикой. Но программист может подключить любую графическую библиотеку (например, Cairo) и обращаться к ее функциям и объектам данных, так же легко, как и к любым функциям и структурам данных, написанных на языке C.

Уровень абстракции ничем не ограничен. Так, если программисту нужно написать оконный интерфейс для Windows, то достаточно добавить строчку:

Программирование на языке С
Страница 2. 1. Описание языка программирования С

1. Описание языка программирования С

1.1. Элементы языка программирования С

1.1.1. Используемые символы

Множество символов используемых в языке программирования С можно разделить на пять групп.

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

Таблица 1

Прописные буквы латинского алфавита A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Строчные буквы латинского алфавита a b c d e f g h i j k l m n o p q r s t u v w x y z
Символ подчеркивания _

2. Группа прописных и строчных букв русского алфавита и арабские цифры (табл.2).

Таблица 2

Прописные буквы русского алфавита А Б В Г Д Е Ж З И К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ы Ь Э Ю Я
Строчные буквы русского алфавита а б в г д е ж з и к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я
Арабские цифры 0 1 2 3 4 5 6 7 8 9

3. Знаки нумерации и специальные символы (табл. 3). Эти символы используются с одной стороны для организации процесса вычислений, а с другой - для передачи компилятору определенного набора инструкций.

Таблица 2

Символ Наименование Символ Наименование
, запятая ) круглая скобка правая
. точка ( круглая скобка левая
; точка с запятой > фигурная скобка правая
: двоеточие < фигурная скобка левая
? вопросительный знак больше
! восклицательный знак [ квадратная скобка
| вертикальная черта ] квадратная скобка
/ дробная черта # номер
\ обратная черта % процент
тильда & амперсанд
* звездочка ^ логическое не
+ плюс = равно
- мину " кавычки

4. Управляющие и разделительные символы. К той группе символов относятся: пробел, символы табуляции, перевода строки, возврата каретки, новая страница и новая строка. Эти символы отделяют друг от друга объекты, определяемые пользователем, к которым относятся константы и идентификаторы. Последовательность разделительных символов рассматривается компилятором как один символ (последовательность пробелов).

5. Кроме выделенных групп символов в языке программирования С широко используются так называемые, управляющие последовательности, т.е. специальные символьные комбинации, используемые в функциях ввода и вывода информации. Управляющая последовательность строится на основе использования обратной дробной черты (\) (обязательный первый символ) и комбинацией латинских букв и цифр (табл.4).

Таблица 4

Управляющая последовательность Наименование Шеснадцатеричная замена
\a Звонок 007
\b Возврат на шаг 008
\t Горизонтальная табуляция 009
\n Переход на новую строку 00A
\v Вертикальная табуляция 00B
\r Возврат каретки 00C
\f Перевод формата 00D
\" Кавычки 022
\' Апостроф 027
\0 Ноль-символ 000
\\ Обратная дробная черта 05C
\ddd Символ набора кодов ПЭВМ в восьмеричном представлении
\xddd Символ набора кодов ПЭВМ в шестнадцатеричном представлении

Последовательности вида \ddd и \xddd (здесь d обозначает цифру) позволяет представить символ из набора кодов ПЭВМ как последовательность восьмеричных или шестнадцатеричных цифр соответственно. Например символ возврата каретки может быть представлен различными способами:

\r - общая управляющая последовательность,

\015 - восьмеричная управляющая последовательность,

\x00D - шестнадцатеричная управляющая последовательность.

Следует отметить, что в строковых константах всегда обязательно задавать все три цифры в управляющей последовательности. Например отдельную управляющую последовательность \n (переход на новую строку) можно представить как \010 или \xA, но в строковых константах необходимо задавать все три цифры, в противном случае символ или символы следующие за управляющей последовательностью будут рассматриваться как ее недостающая часть. Например:

"ABCDE\x009FGH" данная строковая команда будет напечатана с использованием определенных функций языка программирования С, как два слова ABCDE FGH, разделенные 8-ю пробелами, в этом случае если указать неполную управляющую строку"ABCDE\x09FGH",то на печати появится ABCDE=|=GH, так как компилятор воспримет последовательность \x09F как символ "=+=".

Отметим тот факт, что, если обратная дробная черта предшествует символу не являющемуся управляющей последовательностью (т.е. не включенному в табл.4) и не являющемуся цифрой, то эта черта игнорируется, а сам символ представляется как литеральный. Например:

символ \h представляется символом h в строковой или символьной константе.

Кроме определения управляющей последовательности, символ обратной дробной черты (\) используется также как символ продолжения. Если за (\) следует (\n), то оба символа игнорируются, а следующая строка является продолжением предыдущей. Это свойство может быть использовано для записи длинных строк.

IT Novella


C++ — компилируемый статически типизированный язык программирования общего назначения. Поддерживает разные парадигмы программирования, но, в сравнении с его предшественником — языком Си, — наибольшее внимание уделено поддержке объектно-ориентированного и обобщённого программирования.

В 1990-х годах язык стал одним из наиболее широко применяемых языков программирования общего назначения.

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

Но прежде всего, необходимо подчеркнуть, что оценивать достоинства и, в особенности, недостатки C++ необходимо в контексте тех принципов, на которых строился язык, и требований, которые к нему изначально предъявлялись.

Достоинства.

C++ — чрезвычайно мощный язык, содержащий средства создания эффективных программ практически любого назначения, от низкоуровневых утилит и драйверов до сложных программных комплексов самого различного назначения. В частности: поддерживаются различные стили и технологии программирования, включая традиционное директивное программирование, ООП, обобщённое программирование, метапрограммирование (шаблоны, макросы).

Предсказуемое выполнение программ является важным достоинством для построения систем реального времени. Весь код, неявно генерируемый компилятором для реализации языковых возможностей (например, при x нулевое значение, а потом интерпретирует его как значение условия в операторе if. Так как нуль соответствует логическому значению «ложь», блок операторов в условной конструкции не выполнится никогда. Ошибки такого рода трудно выявлять, но во многих современных компиляторах предлагается диагностика некоторых подобных конструкций.

Операции присваивания (=), инкрементации (++), декрементации (--) и другие возвращают значение. В сочетании с обилием операций это позволяет, хотя и не обязывает, создавать трудночитаемые выражения. Наличие этих операций в Си было вызвано желанием получить инструмент ручной оптимизации кода, но в настоящее время оптимизирующие компиляторы обычно генерируют оптимальный код и на традиционных выражениях. С другой стороны, один из основных принципов языков Си и C++ — позволять программисту писать в любом стиле, а не навязывать «хороший» стиль.

Макросы (#define) являются мощным, но опасным средством. Они сохранены в C++ несмотря на то, что необходимость в них, благодаря шаблонам и встроенным функциям, не так уж велика. В унаследованных стандартных Си-библиотеках много потенциально опасных макросов.

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

C++ позволяет пропускать break в ветви оператора switch с целью последовательного выполнения нескольких ветвей. Такой же подход принят в языке Java. Есть мнение, что это затрудняет понимание кода. Например, в языке C# необходимо всегда писать либо break, либо использовать goto case N для явного указания порядка выполнения.

Препроцессор, унаследованный от Си, очень примитивен. Это приводит с одной стороны к тому, что с его помощью нельзя (или тяжело) осуществлять некоторые задачи метапрограммирования, а с другой, вследствие своей примитивности, он часто приводит к ошибкам и требует много действий по обходу потенциальных проблем. Некоторые языки программирования (например, Scheme и Nemerle) имеют намного более мощные и более безопасные системы метапрограммирования (также называемые макросами, но мало напоминающие макросы Си/C++).

Плохая поддержка модульности (по сути, в классическом Си модульность на уровне языка отсутствует, её обеспечение переложено на компоновщик). Подключение интерфейса внешнего модуля через препроцессорную вставку заголовочного файла (#include) серьёзно замедляет компиляцию при подключении большого количества модулей (потому что результирующий файл, который обрабатывается компилятором, оказывается очень велик). Эта схема без изменений скопирована в C++. Для устранения этого недостатка, многие компиляторы реализуют механизм прекомпиляции заголовочных файлов (англ. Precompiled header).

К собственным недостаткам C++ можно отнести:

·Сложность и избыточность, из-за которых C++ трудно изучать, а построение компилятора сопряжено с большим количеством проблем. В частности:

Многие конструкции С++ позволяют делать то же самое, что и конструкции Си, также присутствующие в С++. Это иногда сбивает с толку новичков. Например, приведение типов при помощи dynamic_cast позволяет привести указатель или ссылку строго в пределах иерархии классов. Это делает код более надёжным, декларативным и позволяет находить приведения в пределах иерархии при помощи инструментов типа grep. Однако вследствие требования высокой степени совместимости с Си старое приведение типов всё ещё поддерживается.

o Иногда шаблоны приводят к порождению кода очень большого объёма. Для снижения размера машинного кода можно специальным образом подготавливать исходный код. Другим решением является стандартизованная ещё в 1998 году возможность экспорта шаблонов. Некоторые авторы считают, что её трудно реализовать и поэтому она доступна не во всех компиляторах. «Раздувание» машинного кода вследствие использования шаблонов часто преувеличивается, и современные компиляторы во многих случаях успешно устраняют это явление.

Метапрограммирование на основе шаблонов C++ сложно и при этом ограничено в возможностях. Оно состоит в реализации средствами шаблонов C++ интерпретатора примитивного функционального языка программирования, выполняющегося во время компиляции. Сама по себе данная возможность весьма привлекательна, но такой код весьма трудно воспринимать и отлаживать. Менее распространённые языки Lisp/Scheme, Nemerle имеют более мощные и одновременно более простые для восприятия подсистемы метапрограммирования. Кроме того, в языке D реализована сравнимая по мощности, но значительно более простая в применении подсистема шаблонного метапрограммирования.

Явная поддержка функционального программирования присутствует только в будущем стандарте c++0x. Данный пробел устраняется различными библиотеками (Loki, Boost), использующими средства метапрограммирования для расширения языка функциональными конструкциями (например, поддержкой лямбд/анонимных методов), но качество подобных решений значительно уступает качеству встроенных в функциональные языки решений. Такие возможности функциональных языков, как сопоставление с образцом, вообще крайне сложно эмулировать средствами метапрограммирования.

Некоторые считают недостатком языка C++ отсутствие встроенной системы сборки мусора. С другой стороны, средства C++ позволяют реализовать сборку мусора на уровне библиотеки. Противники сборки мусора полагают, что RAII является более достойной альтернативой. С++ позволяет пользователю самому выбирать стратегию управления ресурсами.

Замечание для программистов на С

Чем лучше программист знает С, тем труднее будет для него при программировании на С++ отойти от стиля программирования на С. Так он теряет потенциальные преимущества С++.

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

преобразовании переменной к другому типу), определён в стандарте. Также строго определены места программы, в которых этот код выполняется. Это даёт возможность замерять или рассчитывать время реакции программы на внешнее событие.

Автоматический вызов деструкторов объектов при их уничтожении, причём в порядке, обратном вызову конструкторов. Это упрощает (достаточно объявить переменную) и делает более надёжным освобождение ресурсов (память, файлы, семафоры и т. п.), а также позволяет гарантированно выполнять переходы состояний программы, не обязательно связанные с освобождением ресурсов (например, запись в журнал).

Пользовательские функции-операторы позволяют кратко и ёмко записывать выражения над пользовательскими типами в естественной алгебраической форме.

Язык поддерживает понятия физической (const) и логической (mutable) константности. Это делает программу надёжнее, так как позволяет компилятору, например, диагностировать ошибочные попытки изменения значения переменной. Объявление константности даёт программисту, читающему текст программы дополнительное представление о правильном использовании классов и функций, а также может являться подсказкой для оптимизации. Перегрузка функций-членов по признаку константности позволяет определять изнутри объекта цели вызова метода (константный для чтения, неконстантный для изменения). Объявление mutable позволяет сохранять логическую константность при использовании кэшей и ленивых вычислений.

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

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

Возможность создания встроенных предметно-ориентированных языков программирования. Такой подход использует, например библиотека Boost.Spirit, позволяющая задавать EBNF-грамматику парсеров прямо в коде C++.

Используя шаблоны и множественное наследование можно имитировать классы-примеси и комбинаторную параметризацию библиотек. Такой подход применён в библиотеке Loki, класс SmartPrt которой позволяет, управляя всего несколькими параметрами времени компиляции, сгенерировать около 300 видов «умных указателей» для управления ресурсами.

Кроссплатформенность: стандарт языка накладывает минимальные требования на ЭВМ для запуска скомпилированных программ. Для определения реальных свойств системы выполнения в стандартной библиотеке присутствуют соответствующие возможности (например, std::numeric_limits ). Доступны компиляторы для большого количества платформ, на языке C++ разрабатывают программы для самых различных платформ и систем.

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

Имеется возможность работы на низком уровне с памятью, адресами.

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

Недостатки.

Отчасти недостатки C++ унаследованы от языка-предка — Си, — и вызваны изначально заданным требованием возможно большей совместимости с Си. Это такие недостатки, как:

Синтаксис, провоцирующий ошибки:

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

С++ — язык общего назначения и задуман для того, чтобы настоящие программисты получили удовольствие от самого процесса программирования. За исключением второстепенных деталей он содержит язык С как подмножество. Язык С расширяется введением гибких и эффективных средств, предназначенных для построения новых типов. Программист структурирует свою задачу, определив новые типы, которые точно соответствуют понятиям предметной области задачи. Такой метод построения программы обычно называют абстракцией данных. Информация о типах содержится в некоторых объектах типов, определенных пользователем. С такими объектами можно работать надежно и просто даже в тех случаях, когда их тип нельзя установить на стадии трансляции. Программирование с использованием таких объектов обычно называют объектно-ориентированным. Если этот метод применяется правильно, то программы становятся короче и понятнее, а сопровождение их упрощается.

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

Объектно-ориентированное программирование — это метод программирования, способ написания «хороших» программ для множества задач. Если этот термин имеет какой-то смысл, то он должен подразумевать: такой язык программирования, который предоставляет хорошие возможности для объектно-ориентированного стиля программирования.

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

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

Язык C++ явился мощным и стремительным рывком в развитии программирования. C++ и по сей день занимает господствующее положение среди языков программирования в мире. Огромное множество профессиональных программистов использует именно его при разработке разного рода проектов. Очевидно, этот язык будет сохранять свое положение ещё не один год, при этом по-прежнему развиваясь и совершенствуясь.

При написании данной статьи использовались книги:

1. «Бьерн Страуструп — «Язык программирования С++»

2.Павловская Т.А. — «C и C++. Программирование на языке высокого уровня».

СТРУКТУРА ПРОГРАММЫ НА ЯЗЫКЕ СИ

ЭЛЕМЕНТЫ ЯЗЫКА СИ

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

Элементы языка Си:

1. АЛФАВИТ - это набор допустимых символов языка:

ü прописные и строчные латинские буквы (a-z, А-Z);

ü арабские цифры (0-9);

ü специальные символы ( #, \, /, +, -, *, _, % и др.);

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

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

а) /*Это комментарий*/ - может занимать несколько строк

б) // Это тоже комментарий - одна строка

Комментарии воспринимаются компилятором языка, как пробельные символы и игнорируются. Внутри комментариев нельзя использовать сочетание символов */, т.е. комментарии не могут быть вложенными.

Комментарии могут быть следующих видов:

1) после заголовка главной функции – общая информация о программе: ее назначение, входные данные и результаты, метод решения задачи, ФИО программиста, дата написания программы и т.д.;

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

3) при объявлении данных функции – пояснения назначения используемых в ней переменных;

4) пояснения логически сложных частей программы, содержащих разветвления, циклы.

Однако количество комментариев не должно быть чрезмерным.

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

2. ИДЕНТИФИКАТОРЫ - это последовательность латинских букв, цифр и символа подчеркивания, начинающаяся с буквы или символа подчеркивания. Идентификаторы могут иметь произвольную длину, но используются в программе только первые 32. В идентификаторе прописные и строчные буквы различаются, т.е. name, Name и NAME являются различными. Как правило, в языке Си в именах переменных используется только строчные буквы, а для именованных констант - заглавные.

3. КЛЮЧЕВЫЕ (служебные, зарезервированные) СЛОВА - это идентификаторы, которые имеют специальное значение для компилятора и не могут быть идентификаторами переменных и данных пользователя.

auto автоматический int целое
break завершить long длинное
case вариант register регистровый
char символьный return возврат
const константа short короткий
continue продолжить signed знаковый
default по умолчанию sizeof размер
do выполнить static статический
double двойной точности struct структура
else иначе switch переключатель
enum перечисляемый typedef определение типа
extern внешний union объединение
float плавающее unsigned беззнаковый
for для void пустой
goto перейти volatile изменчивый
if если while пока

4. МЕТКИ. Любой оператор в программе может быть помечен меткой. Метка ставится перед оператором и отделяется от него двоеточием. Метка в Си – это идентификатор. Метки, использованные в теле функции, локальны в ней. Область действия метки - функция. Поэтому в разных функциях можно использовать одинаковые метки.

Примеры меток: vvod, vivod, vhod

ВЫРАЖЕНИЯ

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

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

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

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

Все операции в Си разделены по приоритетам на 16 категорий. Операции одной категории имеют один и тот же приоритет.

Приоритет Операция Комментарий Порядок выполнения
(), [] Скобки
Все унарные (кроме постфиксных)
++ Увеличение операнда на единицу
- - Уменьшение операнда на единицу
- Изменение знака
* Определение значения по адресу
& Определение адреса
! Логическое отрицание
Поразрядное логическое отрицание**
(тип) Явное преобразование типа
sizeof Определение размера
Бинарные арифметические
* Умножение
/ Деление
% Остаток от деления**
+ Сложение
- Вычитание
> Сдвиг вправо**
Больше
> = Больше или равно
== != Равно Не равно → →
Поразрядные операции
& Поразрядное И**
^ Поразрядное исключающее ИЛИ**
| Поразрядное ИЛИ**
Логические
&& Логическое И
|| Логическое ИЛИ
Условная
? Условная
Присваивание и постиксные бинарные
= Простое присваивание
* = Умножение с присваиванием
/ = Деление с присваиванием
% = Остаток от деления с присваиванием**
+ = Суммирование с присваиванием
- = Вычитание с присваиванием
> = Сдвиг вправо с присваиванием
Постфиксные унарные
++ Увеличение операнда на единицу
- - Уменьшение операнда на единицу
Операция Запятая
, Последовательность выполнения

П р и м е ч а н и я:

** -операция определена только для данных целого типа;

- порядок выполнения операции одного приоритета слева направо;

-порядок выполнения одного приоритета справа налево

Последовательность вычисления функций в выражениях Си не задана.

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

При рассмотрении выражений различных типов определяют по каждому типу:

- стандартные функции, допустимые для данных определенного типа.

Арифметические выражения

Арифметические выражения – аналог алгебраических выражений математики. Они используются:

1) в операторах присваивания;

2) в качестве фактических параметров функций;

3) в операторах заголовка цикла.

Операция Назначение Тип операнда Тип результата
+ Плюс (сложение) Целый, вещественный Совпадает с типом операнда
- Унарный минус (вычитание) Целый, вещественный Совпадает с типом операнда
* Умножение Целый, вещественный В соответствии с иерархией типов
/ Деление Целый, вещественный В соответствии с иерархией типов
% Остаток от деления (деление по модулю) Целый Целый
++ Инкремент (увеличение на 1) Целый, вещественный Совпадает с типом операнда
-- Декремент (уменьшение на 1) Целый, вещественный Совпадает с типом операнда

Приоритет: 1) ++, -- 2) – 3) *, /, % 4) +, -

Сложение, вычитание и умножение значений выполняются как обычно.

Операция «деление по модулю» применяется только к данным целого типа, знак совпадает со знаком первого из операндов.

Операция деления дает результат с плавающей точкой, если хотя бы один из операндов является вещественным, если оба операнда целые, то результат тоже будет целым - произойдет отбрасывание дробной части (аналогично функции DIV в Паскале).

5/2= 2 5/2.0=2.5 5.0/2=2.5 (13+6)/2*3=27

Пусть дан фрагмент программы. Определить значения переменных.

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

В арифметических выражениях операндами могут быть переменные целого типа и типа char, так как на машинном уровне и те, и другие имеют одинаковое целое представление. Поэтому для данных типа char применимы все операции, операндами которых могут быть целые int.

Double float

Long int char, short, enum

Функция вывода printf( )

Функция printf() (print format – форматная печать) осуществляет преобразование и печать данных в соответствии с указанным форматом представления данных.

printf ("формат", аргумент1,…,аргумент n);

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

формат - символьная строка, содержащая объекты трех типов:

обычные символы, которые просто копируются в выходной поток;

управляющие символы – влияют на расположение на экране выводимых знаков. Признаком управляющего символа является значок \ .

Функция ввода scanf()

Функция scanf( ) (форматный ввод) осуществляет ввод данных с клавиатуры и преобразование их во внутреннее представление в соответствии с типом величин.

scanf ("формат", аргумент 1. аргумент n);

Форматная строка и список аргументов присутствуют обязательно.

где аргументы – это перечень вводимых переменных, причем перед именеми каждой переменной обязательно указывается символ &(это знак операции «взятия адреса переменной»).

формат - символьная строка, которая может содержать символы трех типов:

1. Пробелы, символы табуляции, символ перехода на новую строку, которые компилятором игнорируются.

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

3. Спецификации преобразования – указывают необходимые преобразования вводимых данных для очередного аргумента.

В спецификации преобразования указывается символ % и символ преобразования.

Составить программу для ввода и вывода своего любимого числа и своего возраста.


рrintf ("Введите Ваше любимое число =>");

рrintf ("\nСколько Вам лет? ");

рrintf("\nВам %4.1f лет",vоz);

printf("\nВаше любимое число %d",chislo);

ОПЕРАТОРЫ ЯЗЫКА СИ

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

ПУСТОЙ ОПЕРАТОР

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

Он может использоваться:

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

2) после ключевого слова else оператора if, вложенного в другой if, внешний по отношению к данному;

3) после оператора заголовка цикла, когда действия, которые должны быть выполнены в теле цикла, помещаются в заголовок цикла.

СОСТАВНОЙ ОПЕРАТОР

Составной оператор – это блок, ограниченный фигурными скобками. Он может содержать объявление переменных, которые будут действовать только в пределах этого блока, и выполняемые операторы любого типа (перед закрывающейся фигурной скобкой точку с запятой надо ставить обязательно).

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

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

ОПЕРАТОР ПЕРЕХОДА

Оператор goto передает управление непосредственно на оператор, помеченный меткой.

где метка – это идентификатор.

Область действия метки ограничивается функцией, которой она определена. Из этого следует: 1) каждая метка должна быть отлична от других меток той же самой функции; 2) нельзя передавать управление оператором goto в другую функцию.

Помеченный оператор выполняется сразу после выполнения оператора goto. Если оператор с данной меткой отсутствует или существует более одного оператора, помеченных одной и той же меткой, то это приводит к ошибочному результату.

Не рекомендуется переход внутрь операторов: составного, условного, варианта, цикла.

Для программирования ветвлений в языке Си используют условную
операцию ?, условный оператор if и оператор выбора варианта switch.

УСЛОВНЫЙ ОПЕРАТОР

if (выражение) оператор1;

где выражение – это некоторое условие, содержащее операции отношения и логические операции. Значение выражения приводится к целому и интерпретируется в соответствии с правилом: =0 – ложь, ¹0– истина. Выражение обязательно записывается в круглых скобках.

Перед ключевым словом else точка с запятой:

- ставится, если оператор 1 простой;

- не ставится, если оператор 1 – составной оператор.

Условный оператор служит для вычислений по одной из двух ветвей в зависимости от значения выражения. Сначала вычисляется выражение, и если оно истинно, то выполняется , если ложно, то выполняется , непосредственно следующий за ключевым словом else.

Если значение выражения ложно, а конструкция else отсутствует, то управление передается на оператор, следующий в программе за оператором if.

На месте оператора 1 и 2 может быть оператор if. В этом случае имеет место вложенный условный оператор.

if (x j) if (j>0) x=1; else x=2; /* else ассоциируется с внутренним if */ Пример 2. if (i>j) < if (j>0) x=1; > else x=2; /* else ассоциируется с внешним if */

ОПЕРАТОР ВЫБОРА ВАРИАНТА

switch (выражение целого или символьного типа)

case константное выражение 2: оператор 2;[break;]

case константное выражение N: оператор N;[break;]

Оператор switchслужит для выбора одного из нескольких вариантов; он проверяет совпадения значения выражения в скобках с одним из заданных константных выражений во всех вариантах caseи передаёт управление оператору, который соответствует значению какого-либо из выражений. Каждый вариант caseможет быть помечен целой или символьной постоянной или выражением. Операторы, связанные с меткой defaultвыполняются в том случае, если ни одна из констант не равна значению выражения. Вариант defaultнеобязательно указывать в операторе и не обязательно последним. Если ни одно из константных выражений не соответствует значению выражения и вариант defaultотсутствует, то оператор switch не выполняет никаких действий.

Ключевое слово caseвместе с константным выражением служит просто меткой. Если будут выполнены операторы для некоторого варианта, то далее выполняются операторы всех последующих вариантов до тех пор, пока не встретится оператор break. Это позволяет связать одну последовательность операторов с несколькими.

Если действия двух вариантов совпадают, то эти варианты могут быть объединены, в этом случае оператор switch имеет вид:

switch (выражение целого символьного типа)

case константное выражение 2: оператор1;[break;]

ОПЕРАТОР РАЗРЫВА

break;

Оператор разрыва прерывает выполнение операторов цикла или оператора switch, в которых он появляется. Управление передаётся оператору, следующему за прерванным оператором. Если операторы цикла вложены, то break вызывает непременное прекращение того цикла в котором он находится. Появление break вне перечисленных операторах приводит к ошибке.

ОПЕРАТОРЫ ЦИКЛА

Циклы используют в случае, если некоторые действия надо выполнить многократно, каждый раз с новыми данными. В Си имеются операторы цикла while, do while и for.

ОПЕРАТОР ПРОДОЛЖЕНИЯ

Оператор продолжения может использоваться в операторах цикла, предназначен для перехода за последний оператор тела цикла, т.е. на корректировку параметров цикла for (вычисляется выражение приращения) или на анализ конца циклов while и dowhile(происходит вычисление условного выражения).

ОПЕРАТОР ЦИКЛА С ПАРАМЕТРОМ (С ШАГОМ)

Оператор цикла с параметром используется, если заранее известно начальное значение параметра (параметров) цикла, условие его завершения и коррекция – выражение (выражения) для изменения параметра (параметров) цикла.

for ([начальное значение];[условное выражение];[выражение приращения])

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

- условное выражение – список операторов и выражений для проверки конца цикла; конец цикла обычно определяется на основе анализа значения параметра цикла; выполняется перед каждым выполнением тела цикла; если значение последнего выражения истинно (≠0), тело цикла выполняется, а если ложно (=0) – завершается;

- выражение приращения – список операторов и (или) выражений для корректировки параметров цикла; выполняется после каждого выполнения тела цикла;

- оператор – простой или составной оператор тела цикла.

Правила выполнения цикла for:

1) производится вычисление начального выражения;

2) выполняется условное выражение и производится его оценка:

а) если оно истинно (≠0), то выполняется тело цикла;

б) если оно ложно (=0), то выполнение цикла завершается;

если оно ложно до первого выполнения тела цикла, то тело цикла не выполняется ни разу;

3) после выполнения тела цикла выполняется выражение приращения и осуществляется переход к п.2;

4) появление в любом месте тела цикла оператора continue дает переход к выполнению выражения приращения, т.е. к п.3;

5) появление в любом месте тела цикла оператора break вызывает переход к оператору, следующему после оператора цикла;

после выхода из цикла по оператору break или goto параметр цикла сохраняет значение, при котором произошло завершение выполнения цикла;

6) после нормального завершения цикла значение параметра цикла равно значению, которое привело к завершению выполнения цикла.

7) если начальное значение и (или) условное выражение отсутствуют, их ; должны остаться в операторе заголовка цикла;

for( ; ; ) for(i=1; ;i++) - бесконечныециклы, которые могут завершиться только при выполнении в его теле операторов break, goto, return.

Пример оператора цикла for:

МАССИВЫ В ЯЗЫКЕ СИ

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

Синтаксис объявления массива:

тип_элементов имя_массива [константное_выражение];

константное выражение определяет размер массива, т.е количество элементов массива. Например:

объявлен одномерный массив с именем a, содержащий 10 элементов целого типа.

объявлен двумерный массив b, состоящий из 3 строк и 3 столбцов, т.е.
из 9 элементов вещественного типа.

В отличие от языка Паскаль в языке Си нельзя определять произвольные диапазоны для индексов. Размер массива, указанный в объявлении, всегда на единицу больше максимального значения индекса.

Для обращения к элементам массива используется имя массива, после которого в квадратных скобках стоит индекс в виде выражения определяющего значение индекса - порядкового номера элементов массива. Индексов в квадратных скобках может быть столько, сколько измерений в массиве. Каждый индекс указывается в своих скобках. Значения индекса должны лежать в диапазоне от 0 до величины на единицу меньшей, чем размер массива указанный при его объявлении.

Например: a[0], а[1], а[2], а[3], а[4], а[5], а[6], а[7], а[8], а[9].

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

Пример 1. Ввод с клавиатуры и вывод на экран одномерного массива.

while(1)//открытие беск.цикла для проверки размерности

printf("Ведите размерность вектора:");

> //конец ввода вектора

printf("\n\t Исходный вектор \n");//вывод вектора

printf("\n\t Исходная матрица \n");

for(i=0;i “Hello, Friends”

Такой механизм предоставляет удобное средство для записи длинных строковых констант.

Строка описывается, как символьный массив.

char имя строки [максимальный размер];

например: char str[20];

Одновременно с описанием строка может инициализироваться. Возможны два способа инициализации строки:

1) с помощью строковой константы (последовательности символов, заключенной в кавычки):

При таком способе инициализации в конце строки компилятор сам ставит символ \0.

char s[10] = “строка”; - начальное значение задавать необязательно

под строку s будет выделено 10 байт памяти, из них первые 7 получат значения при инициализации (седьмой – нулевой символ).

char s[] = ”строка”; -начальное значение обязательно

будет сформирована строка s из 7 символов.

2) в виде списка символов (элементов массива):

char s[10] = <‘c’,‘т’,’р’,’о’,’к’,’а’,‘\0’>; -описание равнозначно первому.

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

но это приведет к проблемам с обработкой такой строки, так как будет отсутствовать ориентир на ее окончание.

Отдельные символы строки идентифицируются индексированными именами, например: s[0]=’c’, s[5]=’a’

Инициализация двумерного символьного массива строками:

char b[4][30]=Тексты в кавычках эквивалентны

“Результаты сессии”,<<…>,<…>,…>;

“_________________”,

“ ФИО | Оценки “,

“ ________________”

Ввод-вывод символьных строк

Прототипы функций находятся в библиотеке стандартного ввода-вывода stdio.h.

gets(); Аргументом функций указывается имя строки. чтение строки, введенной с клавиатуры Функция gets() читает строку из стандартного потока ввода stdin (высокоуровневый ввод-вывод) и помещает её по адресу, задаваемому параметром функции. Ввод строки заканчивается при обнаружении символа новой строки \n, который заменяется на нулевой символ \0.
puts(); Аргументом функций указывается имя строки. вывод строки на экран Функцияputs() записывает строку, адрес которой определяется значением параметра в круглых скобках, в стандартный поток вывода stdout. При выводе, завершающий \0 заменяется символом новой строки, то есть после вывода строки на экран курсор переходит в начало следующей строки.
getchar(); getc(); ввод одного символа с клавиатуры Например: ch = getchar (); присваивание переменной символьного типа ch очередного символа, полученного из стандартного ввода.
putchar(); putc(); вывод одного символа на экран Например: putchar (ch); выводит символ ch на экран.
getch(); ввод одного символа с клавиатуры без отображения его на экране

Пример: Ввести с клавиатуры строку символов и вывести ее на печать.

1 способ. 2 способ.
# include void main() # include void main()

Ввод и вывод строки можно оформить в цикле:

3 способ. 4 способ.
# include vo ); for(i=0; i vo ); for(i=0; i 1 234Следующая ⇒

Организация стока поверхностных вод: Наибольшее количество влаги на земном шаре испаряется с поверхности морей и океанов (88‰).

Папиллярные узоры пальцев рук - маркер спортивных способностей: дерматоглифические признаки формируются на 3-5 месяце беременности, не изменяются в течение жизни.

Опора деревянной одностоечной и способы укрепление угловых опор: Опоры ВЛ - конструкции, предназначен­ные для поддерживания проводов на необходимой высоте над землей, водой.

Описание языка си

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

Язык Си представляет собой удачный компромисс между желанием располагать теми возможностями, которые обычно предоставляют программисту столь понятные и удобные языки высокого уровня, и стремлением эффективно использовать особенности компьютера. Кроме набора средств, присущих современным языкам программирования высокого уровня (структурность, модульность, определяемые типы данных) в него включены средства для программирования «почти» на уровне ассемблера (использование указателей, побитовые операции, операции сдвига). Большой набор операторов позволяет писать компактные и эффективные программы. Однако, такие мощные средства требуют от программиста осторожности, аккуратности и хорошего знания языка со всеми его преимуществами и недостатками. В отличие от языков программирования типа Паскаль, требующих жесткой дисциплины программирования, ограничивающих свободу программиста, содействующих устранению многих ошибок в программах еще на стадии их трансляции, язык Си предоставляет программисту наибольшую свободу. Однако, ответственность за корректность программ при этом полностью ложится на программиста.


Си был создан Денисом Ритчи как инструмент для разработки операционной системы UNIX и реализован в рамках этой операционной системы. Название языка имеет случайное происхождение: «С» - третья буква английского алфавита. Это наименование говорит о чувстве юмора у создателей языка - его предшественником был язык В («В» - вторая буква английского алфавита).

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

Язык Си - компилирующего типа. Текст программы на Си, подготовленный с помощью текстового редактора, для получения объектного модуля обрабатывается компилятором, причем компиляция выполняется в два прохода. При первом проходе (претрансляцни) обрабатываются строки-директивы, начинающиеся со знака #, при втором - транслируется текст программы и создается объектный (машинный) код. Для получения загрузочного (исполняемого) модуля теперь необходимо отредактировать внешние связи объектного модуля - подсоединить к нему соответствующие библиотечные модули.

Рассмотрим простую программу на языке Си. Такой пример позволит нам выявить некоторые основные черты любой программы, написанной на языке Си.

main () /* Простая программа */

printf ("Это моя 1-я программа на языке Си.\ n ",num);)

Результат выполнения программы:

Это моя 1-я программа на языке Си.

Поясним строки этой программы.

# include stdio . h > - подключение файла stdio . h , который является частью пакета, имеющегося в любом компиляторе языка Си и описывающего функции ввода-вывода (например, средства взаимодействия программы с терминалом). В качестве имени файла используется аббревиатура английских слов: STandard Input / Output header - стандартный заголовок ввода-вывода. Данная строка не является оператором языка Си. Символ # оказывает, что она должна быть обработана «препроцессором» компилятора. Препроцессор осуществляет некоторую предварительную обработку текста программы перед началом компиляции.

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

/* Простая программа */-комментарий. Использование комментариев облегчает процесс понимания программы любым программистом. Большим удобством при написании комментариев является возможность располагать их в той же строке, что и операции, которые они объясняют. Длинный комментарий может помещаться в отдельной строке или даже занимать несколько строк. Все, что находится между символом, указывающим начало комментария /*, и символом, указывающим его

конец */, игнорируется компилятором, поскольку он не в состоянии интерпретировать язык, отличный от Си.

< >- фигурные скобки отмечают начало и конец тела функции. Фигурные скобки применяются также для того, чтобы объединить несколько операторов программы в сегмент или «блок».

int num ; - оператор описания. С помощью такого оператора мы объявляем, что будем использовать в программе переменную num, которая принимает целые (int) значения.

Точка с запятой в конце строки является частью оператора языка Си, а не разделителем операторов, как в Паскале.

Слово int служит «ключевым словом», определяющим один из основных типов данных языка Си. Ключевыми словами называются специальные зарезервированные слова, используемые для построения фраз языка.

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

num = l ; - оператор присваивания. Служит для присваивания переменной num значения 1.

printf ("эт o моя %d-я программа на языке Си.\ n ",num); - вызов функции printf() форматного вывода на печать. Этот оператор выводит на печать значение переменной num в формате, содержащемся в строке в кавычках (в данном случае печатается приведенная в кавычках фраза. Символы % d указывают компилятору, где в выводимой строке и в какой форме необходимо вставить значение переменной num . Символ % сигнализирует программе, что, начиная с этой позиции, необходимо вывести значение переменной, a d указывает, что число надо печатать в десятичном формате.

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

В общем случае обращение к этой функции имеет вид:

где , , . Управляющая строка «формат» содержит объекты двух типов: обычные символы, которые просто копируются в выходной поток (печатаются), и спецификации преобразования значений из внутреннего машинного представления в текстовое для вывода на печатающем устройстве.

Каждая спецификация преобразования начинается с символа % и заканчивается символом преобразования. Между % и символом преобразования могут (не обязательно) находиться

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

2) целое число, задающее минимальный размер поля: преобразованный параметр будет напечатан в поле-минимум оказанного размера; если в преобразованном параметре символов меньше, чем размещается в указанном поле, то слева будут добавлены пробелы (или справа, если указано выравнивание влево);

3) строка цифр с начальным нулем - лишние позиции поля заполняются нулями, а не пробелами:

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

d - десятичное число со знаком;

u - десятичное число без знака;

О - восьмеричное число без знака (и без ведущего 0);

х - шестнадцатеричное число без знака (и без ведущего 0);

s - символьная строка;

с - одиночный символ;

f - действительное число в представлении с фиксированной точкой;

е-действительное число в экспоненциальном представлении;

g - наиболее короткое представление действительного числа; и др.

Функцией ввода, аналогичной функции вывода printf() , является scanf() - стандартная функция форматного ввода.

Обращение к этой функции имеет вид

где , , . - имена переменных, значения которых надо ввести. Наличие символа "&" перед каждым именем обязательно (кроме переменных строкового типа), его смысл будет пояснен ниже.

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

В качестве спецификаций в формате можно использовать те же символы, что и в функции printf(). Спецификации формата должны соответствовать количеству и типу вводимых переменных. В управляющей строке функции scanf нет промежуточных управляющих символов между % и символом преобразования, за исключением "*". Если присутствует символ *, входное поле просто пропускается и никакого присваивания не производится. Обычные символы (кроме °о) сопоставляются с очередными (отличными от символов промежутков) символами входного потока, и в случае отличия дальнейший ввод игнорируется.

int data,month,year; char name[15],town[15];

printf (" Ka к вас зовут? "); scanf ("% s ", name ); printf ("Укажите дату, месяц и год вашего рождения.\пДата:");

printf (" Месяц ( числом ) :") ; scanf ("%d", Sinonth) ;

printf ("А в каком городе? "); scanf("%s",town);

printf ("Вот мы и познакомились. \ n ");

printf(" Вас зовут %s ",name); printf(" Вы родились в городе %s (%d.%d.%d)",town,data, month,year);)

Результат работы программы:

Укажите дату, месяц и год вашего рождения.

Язык программирования Си# : критическая оценка

В июне 2000 года стало известно о новом языке программирования, родившемся в недрах компании Microsoft . Он стал частью новой технологии Microsoft , названной . NET (читается « Dot Net »). В рамках этой технологии предусмотрена единая среда выполнения программ ( Common Language Runtime , CLR ), написанных на разных языках программирования. Одним из таких языков, основным в этой среде, и является Си# ( C #, читается « C sharp », «Си шарп»). Названием языка, конечно же, хотели подчеркнуть его родство с Си++, ведь # — это два пересекшихся плюса [1] . Но больше всего новый язык похож на Яву. И нет сомнений, что одной из причин его появления стало стремление Microsoft ответить на вызов компании Sun .

Хотя официально авторы Си# не называются, но на титульном листе одной из предварительных редакций справочника по языку обозначены Андерс Хейльсберг ( Anders Hejlsberg ) — создатель Турбо Паскаля и Дельфи, перешедший в 1996 году в Microsoft , и Скотт Вилтамут ( Scott Wiltamuth ).

Единая среда выполнения программ основана на использовании промежуточного языка IL ( Intermediate Language — промежуточный язык) [2] , исполняющего почти ту же роль, что и байт-код виртуальной машины языка Ява. Используемые в рамках технологии . NET компиляторы с различных языков транслируют программы в IL -код. Так же как и байт-код Явы, IL -код представляет собой команды гипотетической стековой вычислительной машины. Но есть и разница в устройстве и использовании IL .

Во-первых, в отличие от JVM , IL не привязан к одному языку программирования. В составе, предварительных версий Microsoft . NET имеются компиляторы с языков Си++, Си#, Visual Basic . Независимые разработчики могут добавлять другие языки, создавая компиляторы с этих языков в IL -код.

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

Основные черты Си#

«Си# — простой, современный, объектно-ориентированный язык с безопасной системой типов, происходящий от Си и Си++. Си# будет удобен и понятен для программистов, знающих Си и Си++. Си# сочетает продуктивность Visual Basic и мощность Си++.» Такими словами начинается описание Си#. Мы же рассмотрим технические особенности языка.

l Единицей компиляции является файл (как в Си, Си++, Яве). Файл может содержать одно или несколько описаний типов: классов ( class ), интерфейсов ( interface ), структур ( struct ), перечислений ( enum ), типов-делегатов ( delegate ) с указанием (или без указания) об их распределении по пространствам имен.

l Пространства имен ( namespace ) регулируют видимость объектов программы (как в Си++). Пространства имен могут быть вложенными. Разрешено употребление объектов программы без явного указания пространства имен, которому этот объект принадлежит. Достаточно лишь общего упоминания об использовании этого пространства имен в директиве using (как в Турбо Паскале). Предусмотрены псевдонимы для названий пространств имен в директиве using (как в языке Оберон).

l Элементарные типы данных: 8-разрядные ( sbyte , byte ), 16-разрядные ( short , ushort ), 32-разрядные ( int , uint ) и 64-разрядные ( long , ulong ) целые со знаком и без знака, вещественные одиночной ( float ) и двойной ( double ) точности, символы Unicode ( char ), логический тип ( bool , не совместим с целыми), десятичный тип, обеспечивающий точность 28 значащих цифр ( decimal ).

l Структурированные типы : классы и интерфейсы (как в Яве), одномерные и многомерные (в отличие от Явы) массивы, строки ( string ), структуры (почти то же, что и классы, но размещаемые не куче и без наследования), перечисления, несовместимые с целыми (как в Паскале).

l Типы-делегаты или просто «делегаты» (подобны процедурным типам в Модуле‑2 и Обероне, указателям на функции в Си и Си++).

l Типы подразделяются на ссылочные (классы, интерфейсы, массивы, делегаты) и типы-значения (элементарные типы, перечисления, структуры). Объекты ссылочных типов размещаются в динамической памяти (куче), а переменные ссылочных типов являются, по сути, указателями на эти объекты. В случае типов-значений переменные представляют собой не указатели, а сами значения. Неявные преобразования типов разрешены только для случаев, когда они не нарушают систему безопасности типов и не приводят к потере информации. Все типы, включая элементарные, совместимы с типом object , который является базовым классом всех прочих типов. Предусмотрено неявное преобразование типов-значений к типу object , называемое упаковкой ( boxing ), и явное обратное преобразование — распаковка ( unboxing ).

l Автоматическая сборка мусора (как в Обероне и Яве).

l Обширный набор операций с 14 уровнями приоритета. Переопределение операций (как в Алголе-68, Аде, Си++). С помощью операторов checked и unchecked можно управлять контролем переполнения при выполнении операций с целыми.

l Методы с параметрами значениями, параметрами-ссылками ( ref ) и выходными параметрами ( out ). Слова ref и out нужно записывать перед параметром не только в описании метода, но и при вызове. Наличие выходных параметров позволяет контролировать выполнение определяющих присваиваний. По правилам языка любая переменная должна гарантированно получить значение до того, как будет предпринята попытка ее использования.

l Управляющие операторы : if , switch , while , do , for, break, continue (как в Си, Си++ и Яве). Оператор foreach , выполняющий цикл для каждого элемента «коллекции», несколько разновидностей оператора перехода goto .

l Обработка исключений (как в Яве).

l Свойства — элементы классов (объектов), доступ к которым осуществляется так же, как и к полям (можно присвоить или получить значение), но реализуется неявно вызываемыми подпрограммами get и set (как в Объектном Паскале — входном языке системы Delphi ).

l Индексаторы — элементы классов (объектов), позволяющие обращаться к объектам так же, как к массивам (указанием индекса в квадратных скобках). Реализуются неявно вызываемыми подпрограммами get и set [3] . Например, доступ (для чтения) к символам строки может выполняться как к элементам массива благодаря тому, что для стандартного класса string реализован индексатор.

l События — элементы классов (поля или свойства) процедурного типа (делегаты), к которым вне класса, где они определены, применимы только операции += и –=, позволяющие добавить или удалить методы-обработчики событий для объектов данного класса.

l Небезопасный ( unsafe ) код , использующий указатели и адресную арифметику, локализуется в частях программы, помеченных модификатором unsafe.

l Препроцессор, предусматривающий, в отличие от Си и Си++, только средства условной компиляции.

Примеры программ на Си#

Рассмотрим вначале простейшую законченную программу, процесс ее компиляции и выполнения. Разместим текст программы в файле Hello.cs :

/* Простейшая программа на языке Си# */

static void Main() <

Для компиляции программы можно воспользоваться компилятором csc , который входит в состав Microsoft .NET Framework SDK — комплект разработчика для среды Microsoft .NET и запускается из командной строки:

После компиляции будет получен исполнимый файл Hello.exe. Но запустить его на компьютере, работающим под управлением ОС Windows , можно, только если на этом компьютере установлена поддержка Microsoft .NET. Дело в том, что полученный после компиляции файл (несмотря на свое название) содержит не обычные машинные команды, а IL -код, который будет преобразован в код процессора при загрузке и запуске программы.

Но, если .NET Framework SDK установлен, значит, соответствующая поддержка имеется. Запустив Hello.exe , получим:

Теперь обратимся к тексту программы. В ней определен единственный класс Hello , в котором содержится описание статического метода (метода класса) Main. Статический метод с названием Main (большие и малые буквы в Си# различаются) является точкой входа в программу, написанную на Си#. С выполнения этого метода начинается работа программы. В отличие от языка Ява, метод Main в Си# может быть без параметров или с параметрами, не важно также, возвращает ли он значение (являясь функцией) или нет. В нашем примере Main не имеет ни параметров, ни возвращаемого значения ( void ).

Единственный оператор в методе Main — вызов статического метода WriteLine . Это метод класса Console , предоставляющего доступ к стандартным выходному и входному потокам. Класс Console принадлежит (предопределенному) пространству имен System .

Для ссылки на класс Console использовано его полное название System.Console (квалифицированный идентификатор), включающее обозначение пространства имен System . Используя директиву using , можно сокращать запись, применяя не квалифицированные названием пространства имен обозначения:

/* Простейшая программа на языке Си# */

using System; // разрешается неквалифицированный доступ

static void Main() <

Сортировка на Си#: найдите отличия

В качестве другого примера рассмотрим метод, выполняющий сортировку простыми вставками вещественного массива. Если сравнить этот текст с соответствующей программой на Яве, то отличия совершенно незаметны.

// Сортировка простыми вставками на Си#

public static void InsSort( float[] a ) <

Отличия одно. Слово «длина» пишется с большой буквы: Length (в Яве — length) . Length — это свойство ( property ) стандартного класса System.Array , который является родоначальником массивов в Си#.

Первые впечатления

К моменту написания этих строк существуют лишь предварительное описание языка Си# и предварительная версия средств разработки программ на этом языке. Поэтому делать какие-либо обобщающие выводы еще рано. Но некоторые суждения высказать можно.

В Си# сохранены и даже приведены в порядок некоторые традиционные конструкции: перечисления, структуры, многомерные массивы. Ява в этом отношении демонстрирует более экстремистский подход: объекты и ничего кроме объектов. Устранены явные несуразности Явы, вроде отсутствия передачи параметров по ссылке при отсутствии же указателей. Механизм передачи параметров в Си# хорошо продуман, предусматривая передачу, как по значению, так и по ссылке.

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

Пространства имен

В Си#, как и в других языках, происходящих от Си, так и не получила воплощения простая и ясная концепция модуля. Вместо этого использованы пространства имен — средство, появившееся на поздних стадиях стандартизации Си++. Пространства имен — это весьма общий механизм, поглощающий, в частности, и возможности, предоставляемые модулями. Но здесь налицо чрезмерное обобщение [4] , не обусловленное насущными потребностями, предоставляющее программисту избыточные средства, а с ними и возможности для злоупотреблений. Вложенность пространств имен, их длинные составные обозначения служат препятствием к тому, чтобы потребовать обязательного явного (квалифицированного) использования имен, взятых из этих пространств [5] , как это сделано в Обероне для импортируемых модулем идентификаторов. Неявный же импорт, разрешаемый директивой using [6] , — источник ошибок, связанных с коллизией имен. Вот тому пример.

Рассмотрим программу, в которой определено пространство имен Hello , а внутри этого пространства имен — вложенные друг в друга классы A и B . Класс B содержит единственное статическое поле C , которое проинициализировано значением «Привет!».

// Эта программа хранится в файле Hello.cs

public static string C = " Привет !";

Содержимое файла Hello.cs не является независимой программой, но может быть отдельной единицей компиляции, оттранслировав которую можно получить динамически компонуемую библиотеку [7] (файл с расширением dll ). Для этого при запуске компилятора csc нужно использовать параметр /target :

csc /target:library Hello.cs

В результате компиляции будет получена библиотека Hello.dll .

Теперь напишем основную программу, которая сможет воспользоваться ресурсами нашей библиотеки. А ресурс, собственно, один — строка, содержащая «Привет!». Ее и напечатаем:

// Эта программа хранится в файле Print.cs

static void Main() <

Поместим эту программу в файл Print.cs и откомпилируем ее. Чтобы при компиляции Print.cs была доступна библиотека Hello.dll, упомянем ее в команде, вызывающей компилятор, с помощью параметра /reference :

csc /reference:Hello.dll Print.cs

В результате компиляции получается исполнимый файл Print.exe , который можно запустить и увидеть напечатанное слово «Привет!»:

Теперь модифицируем программу Print.cs , воспользовавшись директивами using для указания пространств имен System и Hello , из которых импортируются нашей программой классы Console и A :

static void Main() <

// Console – из пространства имен System ;

// A – из пространства имен Hello .

Компилируем заново Print.cs , запускаем, получаем тот же результат (а как же иначе):

csc /reference:Hello.dll Print.cs

Теперь, ничего не меняя в уже написанном коде Print.cs и Hello.cs , подключаем к трансляции Print.cs еще одну библиотеку ( A.dll ). В реальной задаче это могло потребоваться, когда программе Print стали нужны какие-то средства, имеющиеся в библиотеке A.dll . Компилируем и запускаем Print :

csc /reference:Hello.dll,A.dll Print.cs

Но что это? Вместо "Привета" (ведь мы ничего не меняли в программе, по-прежнему при компиляции упомянули библиотеку Hello.dll , которая оставалась на том же месте) выведено какое-то число [8] !

Дело в том, что, к нашему несчастью, во вновь подключенной библиотеке оказалось определено пространство имен A , а в нем класс B , а в нем — доступное статическое поле C . В реальной ситуации в библиотеке A.dll могли быть определены также и другие классы и пространства имен. В нашем же примере A.dll была получена компиляцией файла A.cs :

// Эта программа хранится в файле A.cs

public static double C = 2.71828182845904523;

Теперь в операторе Console.WriteLine(A.B.C); программы Print идентификатор A воспринимается как обозначающий пространство имен A , а не класс A пространства имен Hello ! Язык Си# подвел нас. Причем, дважды. В первый раз, когда была допущена коллизия имени класса A пространства имен Hello и названия пространства имен A . Эта коллизия была почему-то разрешена в пользу названия пространства имен, в то время как директива using Hello создала в пределах своего действия локальную область, в которой локальные имена должны были бы иметь преимущество. Во-вторых, возникшее несоответствие не было обнаружено даже при том, что два разных поля с именем С были разных типов. Если бы метод WriteLine [9] не был столь либерален к типу своих параметров, был шанс обнаружить ошибку.


Может показаться, что продемонстрированная ситуация создана искусственно, а на практике такие совпадения маловероятны. Но речь может идти о программах в сотни тысяч и миллионы строк, и тогда роковые совпадения отнюдь не исключены. Более того, при тех правилах относительно пространств имен, которые действуют в Си#, получается, что программист обязан знать перечень всех доступных его программе пространств имен, иначе он рискует исказить работу уже написанных и правильно работавших частей своей программы. Опасность возникает даже в случае, если ваша собственная программа невелика, но использует что-то из библиотек, написанных другими. Складывающееся с названиями пространств имен Си# положение подобно ситуации в примитивнейших версиях Бейсика, когда все переменные глобальны, и надо их всех помнить, чтоб не запутаться.

В чем причина таких прорех и как они могли бы быть устранены? Дело в том, что в отношении названий пространств имен в Си# не действует общее, признанное еще со времен Алгола-60, правило, согласно которому любой идентификатор в программе не может быть использован без (предварительного) описания. Для исключения рассмотренных коллизий необходимо, чтобы директивы using были обязательными, вместе с обязательной квалификацией идентификаторов названием пространства имен. То есть, следовало бы потребовать, чтобы программа Print могла быть записана только в таком виде:

// Безопасное использование пространств имен

using System ; // Описание названия пространства имен

using Hello ; // Описание названия пространства имен

static void Main() <

// Использование названий пространств имен:

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

Пространство программы

В Си#, как и в языке Ява вне определений классов (а также интерфейсов и структур) нельзя размещать ни описания полей, ни описания методов. Это довольно странное правило, особенно для такого языка, как Си#, в котором границы пространств имен оформляются явно — скобками.

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

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

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

Наконец, класс перестает противоестественно совмещать две различные роли — описания типа и пространства для статических полей и методов. Такое совмещение, кстати, затрудняет понимание и изучение языков Ява и Си#.

Рассмотрим перечисленные возможности на уже обсуждавшемся примере. Пространство имен А , содержащее (статическое) поле С, можно было бы определить так:

// Это программа на модифицированном Си#

public double C = 2.71828182845904523;

Класс B для определения поля C больше не нужен. Устраним класс B и в программе из файла Hello :

// Это программа на модифицированном Си#

public string C = " Привет !";

При этом класс A превращен во вложенное пространство имен A , поскольку статические (такого слова в программе нет) поля теперь не должны располагаться внутри классов. Теперь при условии обязательного применения using и при обязательной квалификации импортируемых идентификаторов не может возникнуть никаких опасных коллизий, подобных тем, что рассмотрены выше.

Вот как могла бы выглядеть программа Print , которая печатает значения обоих полей, названных C :

// Безопасное использование пространств имен
// в модернизированном Си#

Прежний класс Print превратился в пространство имен, поскольку классы теперь не являются контейнерами для статических методов, в данном случае, для метода Main . Использованы директивы using , определяющие псевдонимы ( Cons и HA ) для импортируемых пространств имен. Такая форма using предусмотрена в Си#. Стандартный класс Console пространства имен System теперь следует считать (вложенным в System ) пространством имен, поскольку он содержит только статические поля и методы (кроме унаследованных от класса Object ).

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

Избыточность

Некоторые средства языка Си# являются избыточными. Избыточными в том смысле, что не добавляют языку каких-либо функциональных возможностей, а лишь позволяют в иной форме записать то, что и так может быть выражено достаточно просто. К числу таких средств можно отнести свойства ( properties ) и индексаторы ( indexers ).

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

Пусть строковое (типа string ) свойство Title экранных объектов класса Element задает надпись-заголовок таких объектов. Объявление этого свойства может выглядеть так:

public class Element <

string title; // Это поле

// Далее определяются другие поля и методы

Сама надпись хранится в поле title , а свойство Title организует доступ к нему c помощью подпрограмм get (получить) и set (установить). Если e — переменная, обозначающая объект класса Element , то для изменения надписи и перерисовки объекта на экране достаточно записать:

e . Title = "Привет";

Такое присваивание приводит к выполнению подпрограммы set . А, например, при выполнении оператора, добавляющего к заголовку восклицательный знак:

неявно вызываются и get и set .

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

public class Element <

public string getTitle() <

public void setTitle( string value ) <

// Далее определяются другие поля и методы

В этом случае вместо e.Title = "Привет"; нужно записать

а взамен e.Title += "!"; необходимо использовать

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

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

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

Наконец, следует рассмотреть использование языка, оснащенного свойствами, вне контекста визуальной среды [10] . В этой ситуации, то есть в общем случае, когда свойства применяются не только для визуальных элементов, неотличимость вызова подпрограмм ( get и set ) от обращения к полю может сослужить плохую службу, ухудшая возможности понимания программы. По тексту программы уже нельзя понять, сводится ли действие лишь к изменению или получению значения поля, либо оно сопряжено с выполнением и других операций.

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

Например, обращение к отдельным битам 32-разрядного целого значения ( bits ) можно замаскировать под обращение к логическому массиву. Для этого создается такое описание (в данном случае структуры):

public struct BitSet <

int bits ; // Поле целого типа

public bool this[int i] < // Индексатор

if(value) bits |= 1 else bits &=

Теперь использование переменной типа BitSet может выглядеть, например, так:

BitSet b = new BitSet (); // Описание переменной

// Все биты устанавливаются в единичное значение

if ( b [ i ]) ¼ // Проверка i -го бита

В описании типа BitSet использовано много специфических обозначений. && — условное «И»; & — поразрядное логическое «И»; — сдвиг влево; != — не равно ; ¼ ? ¼ : ¼ — условная операция — если истинно логическое выражение перед «?», то результат операции вычисляется по выражению, записанному перед двоеточием, иначе — по записанному после двоеточия; || — поразрядное логическое «ИЛИ»; |= — присваивание с поразрядным «ИЛИ» (обратите внимание на схожесть с != ); &= — присваивание с поразрядным «И»;

— поразрядное «НЕ». Идентификатор value представляет значение, переданное подпрограмме set .

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

public struct BitSet <

public bool get(int i) < // Получить

public vo > Установить

if(value) bits |= 1 else bits &=

Обратите внимание, что содержание подпрограмм get и set нисколько не изменилось. Только теперь это — обычные методы. Для доступа к отдельным битам квадратные скобки уже не применить:

BitSet b = new BitSet (); // Описание переменной

// Все биты устанавливаются в единичное значение

if(b.get(i)) ¼ // Проверка i- го бита

По-видимому, одной из причин появления индексаторов в Си# было желание создателей языка естественным образом оформить обращение к символам строк, которые в Си# являются объектами, но не массивами. Если s — строка ( string ), то, только благодаря наличию индексатора в классе string , i -й символ строки s можно обозначить s[i]. В языке Ява, где строки — тоже объекты, к отдельному символу приходится обращаться, вызвав специальный метод: s.charAt(i).

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

Преимущество обнаруживается одно:

· Упрощение нотации. Действия по доступу к объекту и его изменению записываются как обращение к полю в случае свойств, и обращение к элементу массива при использовании индексаторов.

Недостатков можно назвать больше:

· Усложняются язык и компилятор. С появлением свойств и индексаторов возникает много новых правил. В справочнике по Си# свойствам посвящено целых 8 страниц.

· При использовании свойств и индексаторов от программиста скрываются затраты, которые происходят при работе подпрограмм доступа get и set , что провоцирует к употреблению неадекватных и неэффективных приемов. Например, использование индексатора для доступа к элементам линейного списка по их номеру [11] при значительной длине списка намного менее эффектно, чем обращение к элементу массива, хотя и выглядит так же. Применение такого индексатора для последовательного просмотра списка и вовсе абсурд, к которому, тем не менее, вас подталкивают.

· Ухудшаются возможности понимания программы. По ее тексту становится невозможно узнать, имеем ли мы дело с полем или свойством, массивом или индексатором. Это при том, что обращение к полю всегда связано лишь с получением или установкой его значения, а обращение к свойству может предусматривать выполнение любых действий. Аналогично обстоит дело в случае массивов и индексаторов.

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

Ну и что тут такого? Вначале элементам a поочередно присваиваются значения первых ста чисел натурального ряда 1, 2, 3, ¼ , 99, 100. Затем вычисляется и выводится сумма этих чисел, которая обязана быть равна 5050. Ничего подобного! Напечатанное этой программой значение может оказаться каким угодно. Например, равным 338350, если a — это индексируемый объект такого типа:

public int this[int i]<

get < return i*i; >// i- е значение равно i*i

set <> // Никаких действий

Побочный эффект

Побочным эффектом называют изменение значений переменных при вычислении выражения. Во многих языках (в том числе в Паскале и Обероне) могут быть определены функции, которые имеют изменяемые параметры или могут менять значения глобальных переменных. Такие функции обладают побочным эффектом. Их использование считается плохой практикой, поскольку порождает ряд проблем. Значение выражения с побочным эффектом может зависеть от порядка вычисления операндов.

Рассмотрим пример. Интерпретаторы при вычислении выражений часто используют стек. Пусть Pop(S) — функция, возвращающая значение, извлекаемое из стека S , а Push(S, V) — процедура, помещающая значение V в стек S . При вызове Pop(S) стек меняется, эта функция обладает побочным эффектом. Для замены двух верхних значений в стеке их разностью (от значения, находящегося под вершиной надо отнять значение, расположенное на вершине) можно попробовать записать Push(S, –Pop(S)+Pop(S)). Программист при этом рассчитывает, что первый из двух записанных вызовов Pop(S) и выполнен будет первым. При этом значение, взятое с вершины стека, будет участвовать в вычислении со знаком минус. На самом деле, если язык не устанавливает порядка вычисления операндов (так обстоит дело, например, в Паскале и Си), компилятор может поменять местами слагаемые и запрограммировать это действие как Push(S, Pop(S)–Pop(S)) [12] , что приведет к неверному результату.

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

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

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

Обратимся к примеру. Приведенный ниже цикл достаточно типичен для программ на Си.

Выражение, записанное в круглых скобках, обладает в Си двойным побочным эффектом. Во-первых, каждое его вычисление присваивает переменной b значение n‑ го элемента массива a , во-вторых, увеличивает значение переменной n . При отсутствии привычки к стилю языка Си понять такую конструкцию непросто, но возможно. Такая же запись допустима и в программе на Си#. Но, глядя на нее, уже нельзя сказать, что происходит. Ведь a может быть индексируемым объектом, а b — свойством, и «внутри» как одного, так и другого может быть что угодно.

Тяжеловесность

Такие конструкции языка Си#, как пространства имен, свойства, индексаторы приводят к ситуациям, когда по тексту программы (программной единицы) бывает невозможно понять природу используемых в программе объектов. Имена классов могут быть приняты за названия пространств имен и наоборот, свойства неотличимы от полей, индексируемые объекты — от массивов. При употреблении директивы using возникает неоднозначность определения принадлежности идентификаторов тому или иному пространству имен.

Отмеченные проблемы с однозначной идентификацией объектов программы могут быть частично решены с помощью встроенной в среду программирования системы подсказок. Язык Си# как раз и предназначается в первую очередь для использования в составе мощной среды программирования Microsoft Visual Studio . Она оснащена развитой системой помощи и средствами, позволяющими в ходе диалога с системой определить характеристики и принадлежность объектов программы.

Сказанное, означает, что язык Си# предполагает «тяжеловесную» реализацию, когда в составе системы программирования должны быть сложные вспомогательные инструменты, без которых разработка программ на C и# осложняется. Значительные затраты на создание систем программирования для языка Си#, кроме достаточно высокой сложности самого языка, обусловлены и тем, что неотъемлемой его частью является обширная системная библиотека (пространство имен System ).

Читать или писать

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

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

Как же соотносятся легкость чтения и написания программы в языке Си#, который происходит от Си и Си++? Большинство средств компактной записи, имевшиеся в Си и Си++, сохранены. Кое-что упрощено. Не применяются, например, такие обозначения как –> и :: . Требование использовать лишь логические выражения в роли условий в операторах if и while делает практически бесполезной запись присваивания с его побочным эффектом в таком условии. Но в целом, возможности для побочных эффектов даже расширены (свойства, индексаторы). Переопределение операций [13] и совместное использование методов (имя метода не определяет его однозначно), делая запись более компактной и внешне простой, ухудшают возможности однозначного понимания программы. Неявный импорт с помощью using позволяет не выписывать длинные составные обозначения, но создает опасные коллизии имен.

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

protected internal const verbosity @const = null;

protected internal static readonly verbosity field

protected internal virtual verbosity method()

protected internal static verbosity property

protected internal virtual verbosity this[int i]

Она перегружена прилагательными. Конечно, описание класса verbosity умышленно сделано таким многословным ( verbosity — многословие). Но язык это позволяет. Синтаксис Си# устроен так, что использование многочисленных модификаторов и описателей является нормой. Их следование друг за другом без всяких разделителей затрудняет восприятие программы.

Поясню использованные в примере обозначения. Все определенные элементы класса имеют тип verbosity . Модификаторы protected internal означают, что доступ к элементам класса ограничен пределами данного проекта ( internal ) или классов, производных от verbosity . readonly означает доступ только для чтения; virtual — возможность переопределения метода в производных классах. Символ @ в имени константы позволяет использовать зарезервированное слово const в роли идентификатора. Слово this , обозначая данный экземпляр класса, является обязательным элементом описания индексатора.

В то же время отсутствие в языке Си# специальных слов, обозначающих метод и свойство (подобно словам procedure , function в паскалеподобных языках) заставляет отличать их описания друг от друга и от описания полей и индексаторов по косвенным признакам. В описании метода после его имени есть круглые скобки; в описании свойства — фигурные; у индексатора — квадратные; в описании поля нет скобок, но может присутствовать знак равенства ¼ Просто тест на внимательность получается.

Многословие Си# (как, впрочем, и Явы) выглядит непривлекательно и стилистически ущербно. Заимствованные из Си правила позволяют очень компактно записывать выражения и операторы, используя разнообразные специальные знаки. В то же время объектные нововведения оформлены громоздко и, наоборот, игнорируют возможности знаков препинания. В итоге получается, что и писать трудно, и читать не легко.

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

Перспективы Си#

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

Сравнивая Си# с Явой, можно увидеть много общих черт. Правда, если Ява-системы многоплатформны, то реализация Си# существует пока только для операционной системы Windows и только одна. Но, несмотря на тяжеловесность, можно ожидать, что язык будет реализован и для других систем. Кроме того, сама платформа Microsoft . NET с единой средой выполнения программ может быть продвинута на альтернативные архитектуры, в первую очередь на UNIX -системы.

Си# представляется более реалистичным языком, чем Ява. В отличие от Явы, он самодостаточен. То есть на Си# можно написать любую программу, не прибегая к другим языкам. Это возможно благодаря наличию «небезопасных» блоков кода, которые открывают доступ непосредственно к аппаратуре. В языке Ява для доступа к средствам низкого уровня должны использоваться «родные методы» ( native methods ), которые необходимо программировать на других языках.

И, разумеется, перспективы Си# в первую очередь связаны с теми усилиями, которые, конечно же, приложит компания Microsoft для его продвижения. Можно не сомневаться.

[1] В первых дискуссиях о новом языке, возникших в русском Интернете, было предложение называть язык по-русски «Си-диез». Очень симпатично. Ведь си — это еще и название ноты, а диез — изменение ноты на полтона.

[2] Идея применения единого промежуточного языка для построения многоязыковой системы программирования не нова. Еще в 60-е годы такие системы на основе общего машинно-ориентированного языка АЛМО были созданы в СССР для многих типов машин.

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

[4] Стремление к обобщению всего и вся можно заметить, например, в Алголе-68. Судьба его печальна.

[5] Предпосылки для обязательной квалификации имен в Си#, тем не менее, есть — предусмотрены псевдонимы пространств имен, которые могут быть короче их полных обозначений. Но создатели языка, видимо, не рискнули потребовать обязательной квалификации, опасаясь перенапрячь программистов, привыкших к вольностям Си++.

[6] Наследие Турбо Паскаля версии 4.0. И по слову (в Турбо Паскале — uses ), и по создаваемым проблемам, и, видимо, по автору (А. Хейльсберг).

[7] Компиляция и выполнение программ рассматриваемого примера производились с помощью компилятора Microsoft (R) Visual C# Compiler Version 7.00.9030 и единой языковой среды исполнения (CLR version 1.00.2204.21) под управлением ОС Windows 95.

[8] Вы узнали приближенное значение числа e — основания натуральных логарифмов?

[9] На самом деле здесь мы имеем дело не с одним методом WriteLine , а с совокупностью совместно используемых (перекрытых) методов с одинаковыми названиями, но разными типами параметров.

[10] Для языка Объектный Паскаль, который используется в Delphi , такое применение не слишком актуально. Это специфический язык конкретной системы визуального программирования. Однако, язык Си#, по-видимому, претендует на всеобщность, на существование не только в рамках конкретной среды.

[11] Именно этот пример иллюстрирует использование индексаторов в документации по Си#.

[12] Турбо Паскаль именно так и поступает.

[13] Переопределение операций имеется и в языке Ада.

Илон Маск рекомендует:  Что такое код setclasslong
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL