\(aP3Q(^*m>g\(aP3Q(^<fmtf!m,,aHC%QN1!N1\nJ#Qqsjoug)#&e]o# - aP1Q*aHb%#Qqvut)\
aP1Q*aHb%FN1\nQm>::::aHC%VP3Q>bupj)hfut)c**aHb%JD12JON1!Qjg)a%LN1UP1D12JIQUa\
P1HL%IQ*m>aN2!N2\nP2Q<fmtf!m,,aHC%MN1!N2>P2Q>aN2\nP2Hbdd!b/d";int k;char R[4][99]
;main(c,v)char**v;{char*p,*r,*q;for(q=s;*q;q++)*q>' '&&(*q) - - ;{FILE*i=fopen(v
[1],"r"),*o=fopen(q - 3,"w");for(p=s;;p++)switch(*p++){B'M':Q(k=fgetc(i))!=EOF
&&k!=*p)*r++=k;if(k==EOF){fputs("}}\n",o);fclose(o);return system(q - 6);}*r=0
B'P':while(*p!='`')fputc(*p++,o)B'O':Q*r)fputc(*r++,o);p - - B'C':k=0;Q k<*p - '0'
)(*r++=fgetc(i),k++);*r=0 B'I':k= *p;if(**R==k)goto G B'G':k= *p;G:p=s;while(
*p!='$'||p[1]!= k)p++;p++B'N':R[*p - '0'][0]++;}}}
Эта программа всего в 17 строчках текстового режима VGA(25X80) представляет собой полнофункциональный интерпретатор языка Basic, который поддерживает:
Переменные(имена от A до Z), которые инициализируются нулевыми значениями при запуске.
Цикл FOR var = exp TO exp ..NEXT var
Подпрограммы GOSUB exp и RETURN
Естественно, оператор GOTO(какой же Basic без GOTO)
Условия IF exp THEN exp
Комментарий REM any text
Оператор конец программы END
Присвоение var = exp
Ввод INPUT variable
Ивывод PRINT string PRINT exp
Есть ли читатели, которые по - прежнему считают, что краткость - это всегда очень хорошо. А читабельность конструкций языка факт второстепенный ?
В части ясности синтаксиса антиподом C/С++ является язык АДА. Вместо бесконечных "}" пишется END LOOP, END IF, END CASE или END ИМЯ_ПОДПРОГРАММЫ/ПАКЕТА. Я уже не говорю, насколько "Переменная ТИПА целый" читабельней чем "цел Переменная". Логично предположить, что платой за краткость является читабельность программ, или платой за читабельность программ является отсутствие краткости.
Хотя, что краткость в синтаксисе является сестрой таланта является крайне сомнительным. Отсутствие избыточности на уровне синтаксиса приходится компенсировать обильными комментариями (если, конечно, разработчик хочет иметь возможность разобраться в этом коде через пару месяцев).
Отмечу, что подобный синтаксис Microsoft положила в основу языка Visual Basic.
Ну и, конечно, отсутствие строгого контроля типов(хотя в этом есть и обратная сторона - в Pascal'е Вам придется для того, чтобы иметь возможность "прострелить себе ногу", явно указать на свое желание компилятору.
Распространенности C послужила:
Распространенность Unix.
Хорошее качество генерируемого кода. Особенно на PDP.
Достаточная "мощь" языка.
Сравнительная простота реализации компиляторов.
Краткость синтаксиса и его "красота" любима многими С/C++ - программистами, многие считают их чуть ли не основными преимуществами языка (то есть возможность написать ar[c++] вместо ar[c];inc(c) оказала большое влияние на популярность языка).
Широкие возможности разработки системного ПО(к примеру, адресная арифметика , битовые поля ). В этой низкоуровневости для многих разработчиков даже прикладного ПО есть некая прелесть, привлекающая к языку.
Справедливости ради скажу, что именно такие низкоуровневые средства часто делают C/C++ более предпочтительным для задач системного программирования по сравнению с Паскалем(Правда, уже в Modula 2 появилась адресная арифметика).Позже сам Керниган советовал разработчикам не использовать битовые поля, так как они "слишком платформозависимы".
Итог - C хороший язык системного программирования, кроссассемблер, позволяющий очень эффективно использовать PDP, не очень хорошо подходит для разработки прикладного ПО. Замечу также, что некоторые разработчики "забавы ради" занижают уровень выбираемого для разработки языка(смотрите главу о человеческом факторе).
Перед разработчиками C стояла задача создать язык, обладающий преимуществами и ассемблера и ЯПВУ. С задачей они, в принципе, справились хорошо: правда, с плюсами оных были добавлены и их минусы.
C да и C++ не были и не будут безопасными языками, поскольку один из приоритетов этих языков - эффективность. Поэтому Страуструп не включил в C++ динамическую проверку типов. Как гласит базовый принцип ЯП, эффективность и безопасность несовместимы. Да и потери в эффективности не столь значительны для большинства задач.
Да и потеря не столь велика. Я провел небольшой эксперимент со следующей программой и её аналогом на Pacal.
#include<stdlib.h>
#include<stdio.h>
#include<time.h>
void
main()
{ unsigned long int i;
int
ar[10000];
time_t t,t2;
t = time(NULL);
randomize();
//Многократное присвоение случайному элементу случайного значения
for (i=1;i<429496;i++)
ar[random(1000)]=random (32767);
//Многократное заполнение
массива числом 2
for (i=1;i<429496;i++)
ar[i%(1000)]=2;
t2 = time(NULL);
printf("Время выполнения= %d",t2 - t);
}
Как видно, программа очень интенсивно работает с массивом и контроль за выход границ массива должен, вероятно, замедлить работу. Ведь на каждое присвоение приходится проверка индекса массива.
Выводы могут оказаться для кого - то неожиданными:
Версия на C не показала более высокого быстродействия по сравнению с Pascal версией.
Быстродействия Pascal версии при отключении всех проверок увеличивалось на 7 - 8%.
Что вполне логично, т.к. проверка у процессоров 80x86 реализуются на аппаратном уровне. А прирост в 7 - 8% может дать использование компилятора с хорошей оптимизацией.
По данным Дмитрия Беленко, основанным на проводимом им эксперименте, разница между Delphi и C++ Builder даже вычислительных задачах составляет 4 - 5% .
Замечу что существует и с специальный проект Cyclone. Его разработчики - Корнельский университет и AT&T. Язык фактически представляет Си с проверкой потенциально опасных ситуаций, таких как переполнение буфера. В дистрибутив входит программа преобразования программ из Си в Cyclone которая может отыскать и найти потенциально опасные места программы. Основная цель разработчиков - создать язык пригодный для программирования безопасных приложений.
После появления и распространения ООП львиная доля С программистов перешли на С++ . В этом(и большом обьеме ПО уже написанном на С) я вижу одну из основных причин его распространенности.
Отмечу что и C++ повлиял на C. К примеру enum С получил от С++, и при программировании на С он практически не используется, однако было отмечено, что С содержит такую конструкцию, поскольку она есть в ныне действующем стандарте языка.В языке появились:
классы (classes);
шаблоны (templates);
пространства имен (namespaces);
перегрузка (overload);
потоки (streams);
исключения (exception)
ООП и современные компиляторы частично сгладили недостатки С. Появились классы для работы с такими типами данных, как строки и множества. В сомнительных(смотрите пример выше) ситуациях компилятор(далеко не любой) может выдать подсказку. Для лучшей читабельности IDE автоматически выровняет исходный текст(Первоначально этим занималась команда indent, которая существует и сейчас во многих клонах UNIX(в т.ч. Linux)). Специальные средства, к примеру, NuMega BoundsChecker позволяют отслеживать наиболее вероятные ошибки, такие, как выход за границы массива или утечка памяти. Программа lint(входит в Unix'ы) занимается исключительно ревизией исходного текста программы по поводу контроля типов.
Низкая скорость компиляции в результате отсутствие полноценного механизма модульности часто компенсируется механизмом предварительной компиляции заголовочных файлов.
"Software engineering - искусство программирования без умения это делать"
Эдсгар Дейкстра (Edsger Dijkstra)
"Использования C++ для разработки прикладного ПО и вспомогательных средств для нейтрализации недостатков языка подобно использованию решета для переноски воды и специальных средств для замыкания дыр в решете."
Автор
Хотя С++ частично сгладил недостатки(просто прикрыв их своими средствами, сами недостатки никуда не делись) к нему он прибавил и свои:
Главный - черезмерная сложность и размер языка(это роднит его с Адой, хотя сложность Ады вызвана разнообразием средств, а не их дублированием). Даже реализация ООП подвергается интенсивной критике. Я для примера рассмотрю множественное наследование. Сам вопрос о необходимости включения множественного наследования в язык является достаточно спорным. В частности стоит проблема наличия одноименных методов у родителей класса. Возникает вопрос: что вызывать? Первый попавшийся ? Или все по очереди ? На практике это решается написанием у нового класса метода, который непосредственно реализует нужную логику, правда тогда возникает вопрос о адекватности самого множественного наследования. Ведь его смысл в автоматическом вызове методов (и доступе к полям) классов предков. Если же разработчику приходится указывать конкретный класс - предок, то множественное наследование в значительной степени теряет смысл. Остаются лишь такие частности как возможность обращения к protected элементов классов предков, но соответствующая модификация программы это не проблема. Реализацию множественного наследования тоже нельзя назвать тривиальным делом, она значительно сложней реализации "единичного наследования".
Большая избыточность средств - т.е. для решения одной задачи я могу использовать большое количество идентичных средств.
Кстати, многие средства языка, которые, по мнению его поклонников, появились в C++, были заимствованы из других языков. К примеру, аналог шаблонов - пакеты - были в АДЕ до создания C++. Обработка исключительных ситуаций присутствует в АДЕ и даже в PL/1.Перезагрузка операций также присутствовала в АДЕ.
Огромное разнообразие средств, которые дублируют друг друга , нравится многим поклонникам C++.Они считают, что это делает язык более удобным для использования. Считать так- это их право. Вероятно, для кого-то это действительно так.Приведу авторитетное мнение Кена Томпсона (одного из разработчиков C и Unix):
"При работе с языками C++ и Java у меня возникало определенное беспокойство, когда я просил сделать что - либо, а в ответ получал: "Хорошо, вы делаете это так - то или можете сделать так - то". Совершенно очевидно, если вы в состоянии сделать что - то столь разнообразными способами и все эти способы более не менее эквивалентны, значит в системе заложено слишком много возможностей."