Язык c — Почему этот код вообще работает


Содержание

Советы по языку программирования Си: 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) . Во всех случаях значения будут одинаковыми. Это наглядно демонстрирует, что массив и адрес — это одно и то же.

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

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

Записки программиста

Почему не лишено смысла писать код на C, а не на C++

11 февраля 2020

Сегодня мы поговорим на тему «C против C++». Некоторые читатели данного блога уже знакомы с моей точкой зрения по этому поводу. Когда встает вопрос с формулировкой вроде «похоже, мы решаем задачу, где очень важна скорость выполнения кода, и нужно выбрать между языками C и C++», в последнее время я склонен рекомендовать C. Многие программисты при этом недоумевают, мол «как же так, ведь С++ новее и имеет больше фичей, и вообще C входит в него как подмножество». Поэтому я хотел бы подробно объяснить свою точку зрения один раз в данном посте, так как каждый раз объяснять ее заново занимает ощутимое количество времени.

Хотелось бы начать с небольшого дисклеймера. Всегда найдутся люди, которые пишут на C++ последние лет 20, и потому (1) они искренне считают язык простым и понятным, (2) им не хочется учить что-то новое, ведь их и здесь неплохо кормят. Это, собственно, и есть так называемый C++ головного мозга. Далее я предполагаю, что читатель не страдает от этого недуга и не утратил трезвость восприятия и открытость мышления по каким-либо иным причинам. Также стоит отметить, что в вопросах «какой язык лучше» нет правых и неправых. Одни и те же объективные преимущества и недостатки воспринимаются разными людьми с разными весовыми коэффициентами, поэтому на выходе получаются разные значения функции fitness. В этой заметке мне хотелось бы пояснить причины, по которым у меня весовые коэффициенты выставлены так, как они выставлены, а не доказать, что у кого-то они выставленные неверно.

Сразу отмечу, что я не считаю, что C++ умер, что это ужасный и совершенно ни на что не годный язык или что-то в этом роде. Трудно ругать язык, на котором написаны Chromium, Skype, Sublime Text и множество других программ, которые я использую каждый день. Не говоря уже о великом множестве хороших библиотек на С++. Тут мне сразу вспоминаются, например, Assimp и wxWidgets. Более того, вы можете помнить, что в заметке Критика языка Rust и почему C/C++ никогда не умрет я отстаивал C++ и говорил, что в обозримом будущем он никуда не денется. Нельзя исключать и культурный фактор. Так игры AAA класса принято писать на C++, потому что в индустрии уже много лет все так делают. Если вы работаете в этой индустрии, то особого выбора у вас может и не быть.

Несмотря на все это, я считаю, что в третьем тысячелетии лучше не писать нового кода на C++, если, конечно, (!) у вас есть такая возможность. И далее я постараюсь более подробно объяснить эту точку зрения.

Примечание: Специально для читателей, считающих, что на C давно никто не пишет, спешу сообщить, что это не так. В первую очередь на C, конечно же, ведется разработка всех современных операционных систем, драйверов, и подобных вещей, например, систем виртуализации. Многие десктоп приложения все так же пишутся на C, например, Claws Mail, Liferea, XChat, Transmission, Gimp, Pidgin, Tox, а также оконные менеджеры и десктоп окружения — Xfce, Lxde, Awesome, i3, и другие. Серверные приложения также часто разрабатываются на C. Тут вспоминается HAProxy, lighttpd, Nginx, Nagios, Memcached, Redis и PostgreSQL. Еще можно вспомнить, например, виртуальную машину языка Erlang и интерпретатор языка Python. Все эти приложения объединяет то, что они должны использовать доступные им ресурсы максимально эффективно — десктоп приложения должны хорошо работать на бюджетных компьютерах, даже таких, как Raspberry Pi, серверные приложения должны обрабатывать как можно больше запросов в секунду, и так далее.

Итак, основная идея, пожалуй, состоит в следующем. Если вы решаете задачу, где действительно очень важна скорость (определение см далее), вы все равно не сможете использовать C++. Вы, вероятно, сможете писать на так называемом «C с классами» или «C с шаблонами». Эти диалекты языка C, бесспорно, имеют право на жизнь. И если вы называете «языком C++» эти диалекты, то я, пожалуй, с вами даже соглашусь — для задачи надо брать «язык C++», срочно! Только нужно при этом быть очень уверенным, что через год вы не выйдите за рамки «C с шаблонами». Эта действительно большая проблема на практике и она более детально описана далее.

Однако большинство людей под С++ понимают так называемый «современный C++», со счетчиками ссылок, классами, исключениями, шаблонами, лямбдами, STL, Boost, и так далее. То есть, тот C++, на котором вы пишите, почти как на Java, в котором никогда не встречаются обычные указатели, и вот это все. Если вам очень важна скорость, то писать на таком C++ вы не сможете. Если же он вам подходит, то лучше взять Java, Go или любой другой высокоуровневый язык по вкусу. В них все те же возможности реализованы намного лучше. А узкие места при необходимости, которой, впрочем, может и не возникнуть, вы всегда сможете переписать на C.

Позвольте пояснить, что я имею ввиду под задачами, где очень важна скорость. Вы едете на машине. Вдруг в нескольких метрах впереди выбегает человек. На принятие решения водителю в среднем требуется около одной секунды. Нога мелено перемещается с педали газа на педаль тормоза. Затем медленно вдавливает тормоз в пол. Расстояние между автомобилем и человеком в это время сокращается. Наконец, сигнал от педали тормоза летит в бортовой компьютер автомобиля. И вот тут ни в коем случае программа не может сказать «о, счетчик ссылок обнулился, пойду-ка я собирать мусор по всему дереву» или даже «секундочку, я только схожу в vtable… как, ее нет в L1? ой…». Программа должна обработать сигнал как можно быстрее, тут же ударив по тормозным дискам. Ни о каких смартпоинтерах и прочих видах автоматического управления памятью, ровно как и о развесистых иерархиях классов, в таких задачах и речи быть не может.

Из менее драматичных примеров можно привести любую систему, где не работает правило «90% времени выполняется 10% кода, эти 10% и будем оптимизировать». Если код, который выполняется всего лишь 10% времени, ускорить на 1/20, суммарная производительность вырастит на жалкие 0.5%. Но в масштабах компании вроде Google или широко используемого приложения вроде PostgreSQL или Nginx эти 0.5% ускорения могут означать миллионы долларов экономии. То есть, несколько месяцев работы небольшой группы программистов в этом направлении окупаются с лихвой. А раз так, почему бы, например, не отказаться от STL совсем и сразу не использовать алгоритмы и структуры данных, заточенные под конкретный случай (примеры есть далее по тексту)?

Я могу привести еще много примеров такого рода. Но идея, надеюсь, ясна. Если решаемая вами задача такова, что написать 90% кода на высокоуровневом языке и 10% на C никак нельзя, то ни на каком «C++, который почти как Java, только компилируемый в машинный код» вы писать не сможете. Если же в вашей задаче можно не париться по поводу 0.5% производительности, то скорость вам нужна не так сильно, как вы думали.

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

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

  • Согласно книге The Design and Evolution of C++, язык C++ был создан Страустропом, как «C для крупных проектов». Однако практика показывает, что на C вполне успешно разрабатываются очень даже крупные проекты. А от многих возможностей языка C++ отказываются даже в проектах, которые не являются такими уж крупными. Потому что возможности эти зачастую не упрощают разработку, а лишь усложняют ее. То есть, C++ не только плохо решает изначальную проблему, но и усложняет ее решение, да и проблемы то, оказывается, не было вовсе.
  • Лямбды и прочие ништяки не всегда получается использовать на работе, так как о C++11 там только мечтают. К сожалению, многие реальные проекты на C++ в наше время — это страшный легаси с C++98, самописным STL, форкнутым Boost, Visual Studio 6 и CVS. Может быть, я тут немного и преувеличиваю, но идея, надеюсь, ясна.
  • Сложность кода. Если вы видели код на C++, реальный, а не из учебника, то знаете, что он часто он оказывается действительно очень непрост для восприятия. Из недавних примеров мне вспоминается GLM. Так выглядит его исходный код (только один из файлов, их там еще очень много), а так выглядит код на Си, который делает вообще все, что мне было нужно на самом деле. Бесспорно, после 20 лет программирования на C++, код GLM любому покажется простым, понятным и элегантным. Но у меня нет 20 лет, чтобы постигать это темное искусство, мне нужно решать задачи сегодня. Проблема еще в том, что на C++ так пишут довольно часто и, например, чтобы понять, как работает реализация алгоритма сжатия, тебе еще нужно очень хорошо знать, как в C++ работают стримы. А как ты без стримов будешь использовать свой алгоритм сжатия повторно? По теме сложности кода на C++ еще можно привести пример c chrono из статьи Продолжаем изучение OpenGL: простой вывод текста.
  • Пример со стримами хорошо иллюстрирует, что C++ — словно вирус. Все начинается с использования какой-то одной маленькой его возможности, и через какое-то время весь проект кишит лямбдами, шаблонами и вот этим всем, и в этом уже никто не может нормально разобраться. Судите сами. Допустим, вы решили использовать классы. Но вот проблема — все private поля объявляются в .hpp файле и при изменении приводят к перекомпиляции половины проекта (в C такой проблемы с инкапсуляцией нет совсем). И вот в дополнение к простому и понятному классу вам уже приходится использовать не такую уж понятную и простую в реализации идиому pImpl (или фабричный метод, что еще менее производительно). А затем еще правильно перегрузить оператор присваивания, реализовать конструктор перемещения, и чтобы при этом все это добро правильно работало с STL… Мы всего лишь хотели классов, помните? Аналогично вы не можете использовать исключения, не обернув все в умные указатели, которые, напомню, являются классами и используют шаблоны, а чтобы кода было поменьше, придется еще использовать и auto. Аналогично вы не можете использовать классы и конструкторы, не используя исключения, так как нормально вернуть ошибку из конструктора можно только бросив исключение. Таким образом, не платить за то, что не используешь, вот как-то не получается — приходится использовать сразу все.
  • ООП. Кажется, сегодня уже вся индустрия осознала, что объединение кода и данных — идея так себе, не говоря уже про повсеместное использование наследования. В теории это, конечно, здорово, когда есть класс животное и от него наследуется кошка и лошадь. Но на практике реальная необходимость (ООП головного мозга — тоже тяжелый недуг!) строить такие иерархии возникает очень редко, и если возникает, то дело обычно ограничивается одним интерфейсом и многими классами, реализующими этот интерфейс. Последнее, если что, очень просто делается на С. И возникает вопрос, а какой вообще смысл использовать язык, одна из главных фишек которого — возможность легко писать код так, как это делать не надо?
  • STL часто преподносится так, словно в мире С нет библиотек с готовыми алгоритмами и контейнерами, что, разумеется, не так. При этом STL предлагает только одну из многих возможных реализаций (даже для простого vector их можно придумать десятки) конкретного алгоритма или контейнера. Почему кто-то за меня решил, что замедление скорости компиляции и разбухание секции кода лучше, чем, например, хранение всего по ссылке и, соответственно, быстрая компиляция и разбухание кучи? Или, например, хранение всего по значению, но с небольшим замедлением скорости выполнения кода? Следует также отметить, что контейнер, написанный с нуля и заточенный под данный конкретный случай, позволит вам повысить производительность на те самые «жалкие 0.5%», которые так важны в задачах, на решение которых претендует С++. Например, если вы знаете что-то о природе данных, которые хранятся в контейнере, то можете опустить некоторые проверки. Или, возможно, вам известно, что данные удаляются из хэш-таблицы только в порядке обратном тому, в котором они были добавлены — это тоже можно использовать.
  • Из-за повсеместного использования шаблонов скорость компиляции кода на С++ просто ни на что не годится. Не говоря уже о том, что при компиляции крупных проектов нередко может потребоваться, скажем, гигов 10 оперативной памяти. Для сравнения, язык C позволяет мне компилировать по много раз на дню миллионы строк кода, используя только Raspberry Pi. Ну и если я вдруг решу, что в моем проекте имеет смысл использовать кодогенерацию, ничто не мешает ее использовать. Притом, с нормальным кэшированием результата. Понятное дело, так как шаблоны объявляются в .hpp файлах, их изменение приводит к перекомпиляции половины проекта. См также Десять причин избегать метапрограммирования.
  • Как уже отмечалось, если вы берете исключения, то будьте готовы использовать для всего RAII и смартпоинтеры, а следовательно и тормозить, когда счетчики ссылок обнуляются. Иначе одно неудачно брошенное исключение приведет к тому, что все ваши ресурсы утекут. Следует также отметить, что исключения добавляют коду неявного поведения, и далеко не всем программистам это нравится. Как по мне, в задачах, где используется С и/или С++, лучше использовать старые-добрые коды возврата. Пожалуй, придется написать чуть больше кода и завести привычку всегда проверять возвращаемые значения. Зато вы будете точно знать, что и как именно делает ваш код, безо всякой магии. Не удивительно, что в том же Google в коде на С++ исключения не используются, и что в новых языках, таких, как Go и Rust, исключений не предусмотрено.
  • По своему опыту могу сказать, что отлаживать код C++ — мягко говоря, удовольствие ниже среднего. Продраться через тонны смартпоинтеров и виртуальных методов, или, например, посмотреть, что же происходит внутри STL, в gdb подчас сложно настолько, что проще прибегнуть к обычному отладочному выводу. В языке C все просто и понятно. Даже весьма непростые баги можно легко поймать за пару минут. Я вам даже больше скажу, код на C можно довольно комфортно отлаживать вообще без отладочных символов. Когда-то очень давно я так и делал, просто брал OllyDbg и дебажил. Попробуйте, это правда не сложно.
  • Код на C прекрасно пишется без каких-либо тяжеловесных >для C++ существуют. CLion, например, довольно неплох. Но не все программисты согласны платить за него деньги и попрощаться с 2 Гб оперативной памяти. К тому же, CLion не все и не всегда подсвечивает правильно, и если открыть в нем сразу два проекта, то даже довольно мощный компьютер начнет тормозить. Есть и другие IDE, но у них свои проблемы, например, привязка к Windows или отсутствие важных возможностей, таких, как вывод типов.
  • Нельзя упускать из виду и кадровый вопрос. Язык С сравнительно прост. По крайней мере, его реально уместить целиком в голову среднего программиста. Стандарт С11 [PDF] занимает 700 страниц со всеми приложениями и предметным указателем, а полноценный компилятор C умещается в 15-20 тысяч строк кода. Многие (не все, но многие) студенты уже на первом курсе в состоянии писать вполне сносный боевой код на C. Язык C++ в десятки раз сложнее C. Не удивительно, что его толком не знает никто. В лучшем случае, есть люди, которые знают небольшую его часть. Что намного хуже, с выходом каждого нового стандарта С++ становится еще более сложным и запутанным. Туда тянут еще какие-то концепты, корутины и прочие модные игрушки, как будто без них язык не был уже достаточно распухшим. Но хуже всего то, что правила языка часто далеко не очевидны (например) и имеют кучу исключений. Чтобы писать что-то серьезное на языке, про который неизвестно точно, как работают его компоненты и как они друг с другом взаимодействуют, нужно быть либо очень смелым, либо очень глупым.
  • Ну и до кучи. (1) Далеко не везде есть компилятор C++, особенно если это какой-нибудь C++11/14/17. Так что, если вы хотите настоящей переносимости кода, пишите на С. Язык C есть реально везде. (2) Раз взаимодействие между разными языками программирования или, например, вызов процедур из динамических библиотек, все равно происходит через C API, может лучше просто писать на C? (3) Я уже говорил про совершенно нечитаемые сообщения об ошибках в C++? Попробуйте использовать тот же chrono, например. (4) Александреску в итоге ушел заниматься языком D. Мейерс тоже завязал с C++. Mozilla и Google сделали свои языки для замены C++. Наводит на размышления, согласны?

Не удивительно, что и сегодня даже для новых проектов многие программисты (Линус Торвальдс, пожалуй, является самым известным примером) выбирают язык C, а не C++. Стремление писать код на C — это стремление к максимально простому и понятному коду, стремление использовать ресурсы как можно более эффективным образом, и не в последнюю очередь это стремление к красоте. Технологии появляются и исчезают. Подходы, которые еще вчера считались общепринятой практикой, сегодня уже причисляют к антипаттернам. И только C прекрасен и вечен. На чем еще писать, если с 1972 года люди так и не придумали ничего лучше?

Русский язык в консоли

Учу C++ по книжке Страуструпа, не выводятся русские символы. Вот код:

«Повторяющееся слово: » — отображается нормально благодаря setlocale. То что после — крякозяблы, хотя повторяющееся слова находит. setlocale пробовал разные (0, «»), «», «Rus» и пр.

В Code::Blocks всё работает и без крякозяблов. Даже без setlocale.

4 ответа 4

Для данной задачи существует множество решений. Если вам нужно быстрое и не обязательно универсальное решение, чтобы сильно не разбираться, прокручивайте к разделу «Менее правильные, но пригодные решения».

Правильное, но сложное решение

Для начала, проблема у консоли Windows состоит в том, что её шрифты, которые стоят «по умолчанию», показывают не все символы. Вам следует сменить шрифт консоли на юникодный, это позволит работать даже на английской Windows. Если вы хотите поменять шрифт только для вашей программы, в её консоли нажмите на иконку в левом верхнем углу → Свойства → Шрифт. Если хотите поменять для всех будущих программ, то же самое, только заходите в Умолчания, а не Свойства.

Lucida Console и Consolas справляются со всем, кроме иероглифов. Если ваши консольные шрифты позволят, вы сможете вывести и 猫 , если нет, то лишь те символы, которые поддерживаются.

Дальнейшее рассмотрение касается лишь Microsoft Visual Studio. Если у вас другой компилятор, пользуйтесь предложенными на свой страх и риск, никакой гарантии нету.

Теперь, кодировка входных файлов компилятора. Компилятор Microsoft Visual Studio (по крайней мере, версии 2012 и 2013) компилирует исходники в однобайтных кодировках так, как будто бы они на самом деле в ANSI-кодировке, то есть для случая русской системы — CP1251. Это означает, что кодировка исходников в CP866 — неправильна. (Это важно, если вы используете L». » -строки.) С другой стороны, если вы храните исходники в CP1251, то эти же исходники не будут нормально собираться на нерусской Windows. Поэтому стоит хранить исходники в Unicode (например, UTF-8).

Настроив среду, перейдём к решению собственно задачи.

Правильным решением является уйти от однобайтных кодировок, и использовать Unicode в программе. При этом вы получите правильный вывод не только кириллицы, но и поддержку всех языков (изображение отсутствующих в шрифтах символов будет отсутствовать, но вы сможете с ними работать). Для Windows это означает переход с узких строк ( char* , std::string ) на широкие ( wchar_t* , std::wstring ), и использование кодировки UTF-16 для строк.

(Ещё одна проблема, которую решает использование широких строк: узкие строки при компиляции кодируются в однобайтную кодировку используя текущую системную кодовую страницу, то есть, ANSI-кодировку. Если вы компилируете вашу программу на английской Windows, это приведёт к очевидным проблемам.)

Вам нужно _setmode(_fileno(. ), _O_U16TEXT); для переключения режима консоли:

Такой способ должен работать правильно с вводом и выводом, с именами файлов и перенаправлением потоков.

Важное замечание: потоки ввода-вывода находятся либо в «широком», либо в «узком» состоянии — то есть, в них выводится либо только char* , либо только wchar_t* . После первого вывода переключение не всегда возможно. Поэтому такой код:

вполне может не сработать. Используйте только wprintf / wcout .

Если очень не хочется переходить на Unicode, и использовать однобайтную кодировку, будут возникать проблемы. Для начала, символы, не входящие в выбранную кодировку (например, для случая CP1251 — базовый английский и кириллица), работать не будут, вместо них будет вводиться и выводиться абракадабра. Кроме того, узкие строковые константы имеют ANSI-кодировку, а это значит, что кириллические строковые литералы на нерусской системе не сработают (в них будет зависимая от системной локали абракадабра). Держа в голове эти проблемы, переходим к изложению следующей серии решений.

Менее правильные, но пригодные решения

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

Убедитесь, что ваши исходники в кодировке CP 1251 (это не само собой разумеется, особенно если у вас не русская локаль Windows). Если при добавлении русских букв и сохранении Visual Studio ругается на то, что не может сохранить символы в нужной кодировке, выбирайте CP 1251.

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

  1. Запустите Regedit.
  2. На всякий пожарный экспортируйте куда-нибудь реестр (этот шаг все почему-то пропускают, так что когда всё сломается, мы вас предупреждали).
  3. В разделе HKEY_CURRENT_USER\Console найдите ключ CodePage (если нету, создайте ключ с таким названием и типом DWORD ).
  4. Установите значение по ключу (левая клавиша/изменить/Система счисления = десятичная) на 1251.
  5. Не забудьте перегрузиться после изменений в реестре.

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

Примечание. Установка глобальной кодовой страницы консоли через параметр реестра HKEY_CURRENT_USER\Console\CodePage не работает в Windows 10, вместо него будет использована кодовая страница OEM — предположительно баг в conhost. При этом установка кодовой страницы консоли на уровне конкретного приложения ( HKEY_CURRENT_USER\Console\(путь к приложению)\CodePage ) работает.

(2) Вы можете поменять кодировку только вашей программы. Для этого нужно сменить кодировку консоли программным путём. Из вежливости к другим программам не забудьте потом вернуть кодировку на место!

Это делается либо при помощи вызова функций

в начале программы, либо про помощи вызова внешней утилиты

(То есть, у вас должно получиться что-то вроде

и дальше обыкновенный код программы.)

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

(если выполняете задание из Страуструпа можно вставить в конец заголовочного файла std_lib_facilities.h )

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

Остались методы, которые тоже часто встречаются, приведём их для полноты.

Методы, которые работают плохо (но могут помочь вам)

Метод, который часто рекомендуют — использование конструкции setlocale(LC_ALL, «Russian»); У этого варианта (по крайней мере в Visual Studio 2012) гора проблем. Во-первых, проблема с вводом русского текста: введённый текст передаётся в программу неправильно! Нерусский текст (например, греческий) при этом вовсе не вводится с консоли. Ну и общие для всех неюникодных решений проблемы.

Ещё один метод, не использующий Unicode — использование функций CharToOem и OemToChar . Этот метод требует перекодировки каждой из строк при выводе, и (кажется) слабо поддаётся автоматизации. Он также страдает от общих для неюникодных решений недостатков. Кроме того, этот метод не будет работать (не только с константами, но и с runtime-строками!) на нерусской Windows, т. к. там OEM-кодировка не будет совпадать с CP866. В дополнение можно так же сказать что эти функции поставляются не со всеми версиями Visual Studio — например в некоторых версиях VS Express их просто нет.

Поэтому стоит хранить исходники в Unicode (например, UTF-8).

Причем сохранить следует с сигнатурой

Ситуацию частично спасает пересохранение исходников в кодировке UTF-8 с обязательным символом BOM, без него Visual Studio начинает интерпретировать «широкие» строки с кириллицей весьма своеобразно. Однако, указав BOM (Byte Order Mark — метка порядка байтов) кодировки UTF-8 — символ, кодируемый тремя байтами 0xEF, 0xBB и 0xBF, мы получаем узнавание кодировки UTF-8 в любой системе

Cygwin — при установке в выборе пакетов нужно найти и отметить всякие cmake , GDB и прочие, кем-нибудь рекомендуемые к установке.

Сlion — File — Settings — Editor — File Encodings: IDE Encoding, Project Encoding, main.cpp (Ваш исполняемый файл) — UTF-8, Default encoding for properties files — IBM866

В окне редактора внизу — UTF-8.

Включить в проект заголовочный файл Windows.h

Стоит пояснить кое-что для тех, кто ищет правильный ответ по поводу функции setlocale:

Метод, который часто рекомендуют — использование конструкции setlocale(LC_ALL, «Russian»); У этого варианта (по крайней мере в Visual Studio 2012) гора проблем. Во-первых, проблема с вводом русского текста: введённый текст передаётся в программу неправильно! Нерусский текст (например, греческий) при этом вовсе не вводится с консоли. Ну и общие для всех неюникодных решений проблемы.

Я добавлю по этому методу побольше информации: Его вообще не правильно рекомендуют!

Начнём с первого: Во втором параметре функция принимает не название страны или языка, хотя в некоторых случаях она сработает, а языковый идентификатор, согласно ISO 3166-1. Поэтому правильно и корректно указывать: «ru-RU». Теперь второе: в документации к этой функции написано чёрным по белому: «If execution is allowed to continue, the function sets errno to EINVAL and returns NULL.» Что буквально толкуется: при возникновении ошибки, функция устанавливает значение переменной errno в EINVAL и возвращает NULL.

В случае возникновения ошибки, errno всегда будет равен EINVAL, что означает: не верный аргумент. Поэтому её проверять нет смысла, а вот исполнение функции должно быть проверено. Поэтому правильный вызов функции setlocale выглядит следующим образом:

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

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

И да, не стоит устанавливать языковый указатель виде «ru», так как в зависимости от сборки самой ось и имеющихся языковых пакетов, может установиться ru-BY, ru-UA, ru-MO и другие языковые стандарты, значительно отличающиеся от ru-RU. И категорично нельзя указывать «Russia», «Russian», «Russian Federation» (да, такую вакханалию уже встречал пару раз). Хотя функция производит проверку и по названию региона, не всегда в таблице локализации это указано, или может быть указано «Россия» или «Русский» уже на нашей раскладке. Это и есть основная ошибка, из-за которой функция setlocale зачастую отказывается работать.

И да, для приложения, работающего в режиме юникогда, стоит использовать функцию _wsetlocale. Она идентична, и также устанавливает базовые настройки для локализации. Кроме того, если проект приложения в Visual Studio настроен в режим юникода, то и будет работать только _wsetlocale, так как setlocale, по документации, не приспособлена к работе с юникодом вообще никак.

UPD.

Совсем забыл указать, что функция setlocale и _wsetlocale, в случае успеха вернёт именно идентификатор региона. То есть, в нашем случае строку «ru_RU\0».

Что означают эти функции?

Добавлено через 40 минут
ещё сразу вопрос, почему в этой программе нет функции memset() для обнуления памяти

03.06.2020, 07:34

Объяснить, что означают строки кода
float res = 0; res += f(a); res += f(b); res += f((a + b)/2); res /=3; объясните.

Что означают символы *»»
Что означает символ » *»» » в С? Пример: while (str!=*»»)

что делают вот эти две строки?
не понимаю какую функцию они выполняют тут опишите подробно printf («%s\n»,pch); pch = strtok.

03.06.2020, 08:07 2

3)Зачем обнулять? Вы выделили диапазон памяти для массива и сразу же его заполнили.

Добавлено через 1 минуту
Это работает?

03.06.2020, 09:04 [ТС] 3

Adrian_One, всё работает, я просто спросил. Это язык Си.

Добавлено через 8 минут
Adrian_One, на счёт memset().. у нас в методичке просто сказано:
после использования функции malloc всегда проверять, успешно ли выделилась память! (проверять указатель на NULL).
После выделения память всегда нужно инициализировать! (memset())

03.06.2020, 09:38 4
03.06.2020, 09:38
05.06.2020, 11:40 5
05.06.2020, 14:00 6

вообще-то это одномерный массив, а двумерным он «становится» из-за магии указателей (a + i * m + j) и *(a + i * m + j) в циклах.

2 гетчара из-за кривизны функции scanf(), которая любит оставлять после себя символ ‘\n’ в потоке ввода.

05.06.2020, 14:22 7
05.06.2020, 14:53 8

я не это имел в виду, к двумерному массиву int ** a; вы можете обращаться стандартным способом: a[i][k], однако к массиву выделенному таким образом данная «магия» не применима, поскольку он является одномерным, т.е. int * a; и обращаться к нему получится только как a[i] без доступа ко «второму измерению» штатными средствами. Для чего используется пляска с бубном и указателями.

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

Думаю тут вы мне скажете, что int * a — это не массив, а указатель.

Преимущества и недостатки C# — Учим Шарп #1

Опубликовано shwan в 06.11.2020

Я хочу обсудить с тобой один очень важный вопрос, который достаточно часто упускают из внимания: действительно ли тебе нужно учить язык C#?

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

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

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

История языка C#

Я постараюсь долго не задерживаться на этом пункте, но основную информацию расскажу.

Данный язык создан всеми так горячо любимой корпорацией Зла Microsoft в 2000 году. Он очень многое унаследовал от своих родителей (С++ и Java), но и привнес нового.

Возможной версией использования символа хештега принято считать наследование плюсиков от предков, примерно следующим образом: C → C++ → C++++(C#), потому что символ «#» при хорошем воображении можно получить путем объединения 4-х знаков «+».

Язык активно развивается. Регулярно выходят новые версии C#, которые добавляют новые синтаксические конструкции в язык, а также увеличивают его быстродействие и надежность.

Преимущества C#

Данный язык использует объектно-ориентированный подход к программированию во всем. Это означает, что тебе нужно будет описывать абстрактные конструкции на основе предметной области, а потом реализовывать между ними взаимодействие. Данный подход пользуется большой популярностью, потому что позволяет не держать в голове всю информацию, а работать по принципу черного ящика: подал входные данные -> МАГИЯ -> PROFIT.

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

Еще стоит упомянуть, что все это работает на базе платформы .NET Framework. Что это означает? Для многих непосвященных, это просто какая-то приблуда, которую нужно установить на комп, чтобы программа запустилась, но дело обстоит значительно глубже. Написанный тобой код на языке C# транслируется в промежуточный язык (IL), который в свою очередь уже преобразуется в машинный код на твоем компьютере прямо во время выполнения приложения (JIT). Спрашивается, зачем это все? А суть в том, что ты можешь пилить со своим другом Васей на разных языках один и тот же проект и ни одному из вас не придется переучиваться. Но я никогда не видел, чтобы это реально использовали на практике. Но это еще не все. Так как окончательная компиляция из промежуточного кода выполняется в живую на твоей конкретной машине, то возможно увеличение производительности за счет использования специфических команд именно твоего процессора.

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

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

Немаловажно наличие хороших инструментов разработки, и здесь все очень хорошо. Абсолютное большинство разработчиков используют интегрированную среду разработки Visual Studio, которая предоставляет over9000 возможностей, использовать которые ты конечно же не будешь.

Но IDE действительно хороша, к тому же имеется ее полнофункциональная бесплатная версия Community.

Еще к плюсам можно отнести строгую типизацию, которая позволяет защититься от дурака, и не так давно появившаяся кросспратформенность в .NET Core (да-да, мелкомягкие потихоньку захватывают линукс).

Недостатки C#

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

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

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

C# не является повсеместно распространенным языком. Большинство программистов сосредоточены в коммерческой Enterprise сфере, что накладывает весьма серьезные ограничения на поиск работы в небольших городах, где кроме Delphi или PHP ничего жизни не видели. К тому же, как бы то ни было, C# в первую очередь ассоциируется с Windows. Вряд ли в обозримом будущем что-то изменится и Винда все также будет продолжать доминировать на рынке, но все же небольшой риск остается, особенно учитывая недавние фейлы с обновлениями в Windows 10.

Перспективы развития C#

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

Компания Microsoft остается одной из крупнейших IT компаний мира, а C# ее флагманский язык программирования, который постоянно развивается и впитывает в себя все новые возможности. Поэтому в обозримом будущем проблем у данного языка возникнуть не должно.

Сферы применения языка C#

В этой области C#, наверное, впереди планеты всей. Хочешь разрабатывать обычные приложения для компьютера – пожалуйста, стандартные WinForms Application и консоль тебе в помощь. Хочешь такие же, но покрасивее? – используй WPF. И специальные приложения для магазина в Windows Store тоже. Веб-приложения? – Легко ASP.NET всегда придет на помощь. На Linux? – тоже не вопрос, .NET Core уже здесь. Мобильное приложение? – Xamarin сделает сразу под все платформы. Хочешь написать игру? – движок Unity показывает себя очень даже неплохо, и при этом также адаптирует игру под различные платформы. Хочешь приблизить апокалипсис с восстанием машин и создаешь искусственный интеллект? – есть целая платформа с кучей инструментов для этого Microsoft AI Platform. Также и для компьютерного зрения и ботов. Я вообще с трудом могу придумать пример того, что невозможно реализовать на C#. Я где-то встречал даже операционную систему написанную на шарпе. Поэтому в этой области все хорошо.

Зарплаты разработчиков C#

Здесь все в целом неплохо. По данным на 2020 год, C# явно не является самым высокооплачиваемым языком, но и не самый низкооплачиваемый тоже. Среднее значение зарплаты для данного языка в России около 90к рублей. Это весьма неплохой результат, но бесспорно есть и более дорогие языки. В целом, уровень заработной платы намного больше зависит от прямоты рук и уровня знаний, поэтому не так важно на каком языке писать, главное делать это хорошо. Но данная цифра задает примерный уровень для Middle разработчика в среднем по России. От этого уже можно отталкиваться.

Итоги

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

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

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

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

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

На этом мы заканчиваем наш первый урок. Подписывайтесь на мои социальные сети: Вконтакте, Телеграм, YouTube и Дзен. Ну а еще есть специальный закрытый чат, для изучающих C# по моему курсу. С вами был Вадим. Пока!

elizarov

Блог Романа Елизарова

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

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

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

Зачем же нужно знание языка Си программисту, который пишет прикладное программное обеспечение на других языках? Во первых, все первые пять наиболее популярных сейчас языков программирования так или иначе основаны на языке Си. В дополнение к самому Си на первом месте, C++ и Objective-C являются его расширениями в том или ином виде, а Java и C# используют синтаксис основанный на языке Си.

Во вторых, если программист пишет на каком-нибудь узкоспециализированном или универсальном динамическом языке (PHP, JavaScript, Python, Perl и т.п.), то его среда исполнения, интерпретатор, и основные библиотеки в большинстве случаев написаны на Си (ну или иногда на C++, что не отменяет необходимость знать Си). А значит для того, чтобы понять что же именно происходит в том или ином куске кода, почему та или иная операция занимает какое-то определенное время или потребляет какие-то ресурсы нужно иметь представление о языке Си. Любая нестандартная ситуация или неожиданное поведение — и вот уже приходится изучать исходные тексты библиотеки на Си. Любой выход за рамки задач, которые были предусмотрены разработчиками языка — и вот уже приходится самому писать расширение на языке Си. Собственно, все универсальные языки программирования поддерживают тот или иной способ взаимодействия с кодом написанным на Си, что подчеркивает особый статус языка Си в современном мире.

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

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

Код C, который не будет работать в C++?

Вырезка из Википедии:

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

И у Лафоре встречал такое заявление, что аки не каждый код на C будет скомпилирован компилятором C++. Спорить-то и нечего, но сколько помню, то все функции языка Си поддерживаются в C++. Обратная совместимость же!

Но можно ли узнать конкретно какой код не скомпилируется?

  • Вопрос задан более трёх лет назад
  • 1057 просмотров

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

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

Массивы переменной длинны (VLA).

Но всё зависит от компилятора и строгости следования стандарту. GNU компилятор, например, вводит такие массивы для C++ в расширениях.

Ещё в C есть restrict переменные, регистровые переменные, которых нет с C++.

Программирование на C, C# и Java

Уроки программирования, алгоритмы, статьи, исходники, примеры программ и полезные советы

ОСТОРОЖНО МОШЕННИКИ! В последнее время в социальных сетях участились случаи предложения помощи в написании программ от лиц, прикрывающихся сайтом vscode.ru. Мы никогда не пишем первыми и не размещаем никакие материалы в посторонних группах ВК. Для связи с нами используйте исключительно эти контакты: vscoderu@yandex.ru, https://vk.com/vscode

Как включить русский язык в Си

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

Например, при запуске вот этой программы:

В консоли будет отображено следующее:

Всё дело в том, что мы не подключили русскую локализацию.

Исправить эту ошибку очень легко!

Для начала надо добавить следующую библиотеку:

Она отвечает за локализацию.

А затем нам надо просто написать в начале тела кода вот эту строку:

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

“Rus”, как легко догадаться говорит о том, что локализация произойдёт на русский язык.

Вот и всё! Мы включили русский язык в Си. Наша программа модернизирована и обогащена на две строки. Теперь она будет выглядеть вот так:

А консоль вот так:

Поделиться в соц. сетях:

14 комментария(ев) к статье “ Как включить русский язык в Си ”

Что именно? Какая ошибка?

Можете попробовать такой код:

#include
#include
#include

int main()
<
setlocale(LC_ALL, “Rus”);
printf(“Всем привет! Как дела?”);
getch(); //В Visual Studio _getch();
return 0;
>

Этот вариант работает, но он не включает русский язык в стандартном потоке ошибок.

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

Здравствуйте, Евгений. Существует ещё один способ включения русского языка в Си. Попробуйте использовать следующий код:

#include
#include
#include

int main()
<
SetConsoleCP(1251); //установка кодовой страницы win-cp 1251 в поток ввода
SetConsoleOutputCP(1251); //установка кодовой страницы win-cp 1251 в поток вывода
printf(“Всем привет! Как дела?”);
_getch();
return 0;
>

Здравствуйте, использовала такой метод как в статье.
Все работает при выводе текста функцией printf.
Но, когда я считываю слово функцией scanf, а потом вывожу его функцией printf появляются снова непонятные символы.
Подскажите пожалуйста как это можно исправить.

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

У меня та же проблема, с printf всё работает, но вот если ввести при scanf, то в выводе printf будут кракозябры

Попробуйте способы, указанные в комментариях выше. Один из них точно сработает.

Спасибо за инфу! Все работает.

Всё работает! Спасибо большое!

Правильно “Ru”, а не “Rus”.
setlocale(LC_ALL, “Ru”);

у меня получилось так:

#include
#include
#include

void main()
<
setlocale(LC_ALL, “Rus”);
wprintf(L”Спасибо”);
>

Если кто-то пишет в NotePad++ или чём-то подобном, не забудьте поменять кодировку самого файла с UTF-8 на, например, Windows-1251.
Спасибо за статью.

Передача строки как значения в языке Си: почему этот код работает?

Мне пришлось написать функцию, которая изменила строку. Поскольку мне нужно изменить строку, я сразу подумал, что мне нужно передать строку как ссылку:

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

Я проверил свою программу, используя

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

Может кто-нибудь помочь мне прояснить это?

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

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

Во второй версии вы разыменовываете указатель и работаете с отдельными символами.

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

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

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

С не имеет понятия «передача по ссылке». Указатели имитируют передачу по ссылке, но все равно ВСЕГДА передают по значению в C. То, что вы делаете, передает значение указателя.

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

C++, с другой стороны, имеет истинный проход по ссылке. Эта простая программа выведет 42:

Это не может быть сделано на C. Эквивалентная программа на C будет выглядеть так:

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

вы действительно можете переписать свою первую функцию:

Чтобы заставить его работать в простом C, вам придется переименовать один из них.

Почему этот код C ++ работает? (наверное просто)

Я изучал очень простой C ++, когда наткнулся на код, который не должен работать, но … оно делает!

Вот очень простая версия того, что я нашел:

Почему это работает ?? Я печатаю что-то, что еще не объявлено. Он не должен ничего печатать.

Решение

Это абсолютно правильно.

Здесь вы говорите компилятору: «Эй, эта функция называется HelloWorld существует где-то. Он не принимает параметров и возвращает строку. Это называется функцией декларация. Вы заявляете, что это существует. Это конкретно называется предварительная декларация .

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

Здесь вы на самом деле определяющий функция. Вы говорите компилятору: «Это то, что я имел в виду, когда сказал HelloWorld существовал где-то.

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

Возможно, вы программист на Python или PHP?

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

Другие решения

На сайте вызовов компилятору нужно только объявление. В этом случае HelloWorld() передается объявлено перед вызовом.

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

Вы вызываете функцию, которая имеет было объявлено, и печать его возвращаемого значения.

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

Этот код имеет только один дефект. Должен иметь заголовок В противном случае другие компиляторы могут выдать ошибку. ��

Так что программа должна содержать директиву

Вы печатаете то, что уже было определено. Вы печатаете возвращаемое значение функции HelloWorld который объявлен перед его вызовом

В своем определении функция имеет оператор return, который возвращает строковый литерал «Hello World»

Как класс std::string имеет конструктор, который принимает строковый литерал в качестве аргумента, а затем объект типа std::string создается как возвращаемое значение, и вы его печатаете.

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