Раньше я использовал только языки объектно-ориентированного программирования (C ++, Ruby, Python, PHP) и сейчас изучаю C. Мне трудно найти правильный способ сделать что-то на языке без понятия «Объект». Я понимаю, что можно использовать ООП-парадигмы в C, но я бы хотел изучить C-идиоматический способ.
При решении программной задачи первое, что я делаю, это представляю объект, который решит проблему. Какими шагами я могу заменить это, когда использую не-ООП парадигму императивного программирования?
object-oriented
c
Мас Баголь
источник
источник
qux = foo.bar(baz)
становятсяqux = Foo_bar(foo, baz)
.Ответы:
struct
.Вот и все.
Как ты написал класс? Это почти то, как вы пишете .C файл. Конечно, вы не получите таких вещей, как полиморфизм и наследование методов, но вы все равно можете смоделировать их с разными именами функций и составом .
Чтобы проложить путь, изучите функциональное программирование. Это действительно удивительно, что вы можете делать без занятий, а некоторые вещи работают лучше без затрат на занятия.
Дальнейшее чтение
объектно-ориентации в ANSI C
источник
typedef
этоstruct
и сделать что - то классное . иtypedef
-ed типы могут быть включены в другиеstruct
s, которые сами могут бытьtypedef
-ed. то, что вы не получаете с C, это перегрузка операторов и поверхностно простое наследование классов и членов внутри, которые вы получаете в C ++. и вы не получите много странного и неестественного синтаксиса, который вы получаете с C ++. Я действительно люблю концепцию ООП, но я думаю, что C ++ - ужасная реализация ООП. я как C , так как это является меньшим языком и выходит из синтаксиса из языка , который лучше оставить функции.a lot
of things actually work better without the overhead of classes
a lot of things actually work better with addition of class-based OOP
. Источник: TypeScript, Dart, CoffeeScript и все остальные способы, которыми индустрия пытается уйти от функционального / прототипного языка ООП.Прочитайте SICP и изучите Scheme и практическую идею абстрактных типов данных . Тогда кодирование на C легко (поскольку с SICP, немного C и немного PHP, Ruby и т. Д.) Ваше мышление будет достаточно широким, и вы поймете, что объектно-ориентированное программирование может быть не лучшим стилем в все дела, но только для каких-то программ). Будьте осторожны с динамическим распределением памяти на C , что, вероятно, является самой сложной частью. Стандарт языка программирования C99 или C11 и его стандартная библиотека C на самом деле довольно бедны (он не знает о TCP или каталогах!), И вам часто понадобятся некоторые внешние библиотеки или интерфейсы (например,POSIX , библиотека libcurl для HTTP-клиента, библиотека libonion для HTTP-сервера, GMPlib для bignums, некоторые библиотеки, такие как libunistring для UTF-8 и т. Д.).
Ваши «объекты» часто находятся в некоторых связанных-C
struct
, и вы определяете набор функций, работающих с ними. Для коротких или очень простых функций рассмотрите возможность их определения с соответствующими значениямиstruct
, какstatic inline
в некотором заголовочном файле,foo.h
который будет#include
-d в другом месте.Обратите внимание, что объектно-ориентированное программирование - не единственная парадигма программирования . В некоторых случаях целесообразны и другие парадигмы ( функциональное программирование в стиле Ocaml или Haskell или даже Scheme или Commmon Lisp, логическое программирование в стиле Prolog и т. Д. И т. Д. ... Читайте также блог Дж. Питрата о декларативном искусственном интеллекте). См. Книгу Скотта: Прагматика языка программирования.
На самом деле, программист на C или в Ocaml, как правило, не хочет кодировать в стиле объектно-ориентированного программирования. Нет причин заставлять себя думать об объектах, когда это бесполезно.
Вы определите некоторые
struct
и функции, действующие на них (часто через указатели). Вам могут понадобиться несколько теговых объединений (частоstruct
с элементом тега, часто с некоторымиenum
, а некоторыеunion
внутри), и вам может быть полезно иметь гибкий член массива в конце некоторых из вашихstruct
-s.Загляните в исходный код некоторых существующих свободных программ на C (смотрите github & sourceforge, чтобы найти их). Вероятно, было бы полезно установить и использовать дистрибутив Linux: он сделан почти только из свободного программного обеспечения, у него есть отличные компиляторы бесплатного программного обеспечения C ( GCC , Clang / LLVM ) и инструменты разработки. Смотрите также Advanced Linux Programming, если вы хотите разрабатывать для Linux.
Не забудьте собрать все предупреждения и информацию об отладке, например, особенно на
gcc -Wall -Wextra -g
этапах разработки и отладки, и научиться использовать некоторые инструменты, например, valgrind для поиска утечек памяти ,gdb
отладчик и т. Д. Постарайтесь хорошо понять, что не определено поведение и строго избегать его (помните, что программа может иметь некоторое UB и иногда, кажется, "работает").Когда вам действительно нужны объектно-ориентированные конструкции (в частности, наследование ), вы можете использовать указатели на связанные структуры и функции. Вы можете иметь свой собственный механизм vtable , каждый «объект» должен начинаться с указателя на указатели на
struct
содержащую функцию. Вы используете возможность приведения типа указателя к другому типу указателя (и тот факт, что вы можете приводитьstruct super_st
типы, содержащие те же типы полей, что и те, которые начинаютstruct sub_st
эмулировать наследование). Обратите внимание, что C достаточно для реализации довольно сложных объектных систем, в частности, следуя некоторым соглашениям , как демонстрирует GObject (из GTK / Gnome).Когда вы действительно нужны затворы , вы часто эмулировать их обратные вызовы , с конвенцией , что каждая функция обратного вызова с использованием передаются как указатель на функцию и некоторые данные клиента (потребляемый указатель на функции , когда она называет это). Вы также можете иметь (условно) свои собственные закрывающие
struct
-s (содержащие некоторый указатель на функцию и закрытые значения).Поскольку C - это язык очень низкого уровня, важно определить и задокументировать ваши собственные соглашения (вдохновленные практикой в других программах на C), в частности, об управлении памятью, и, возможно, также некоторые соглашения об именах. Полезно иметь представление об архитектуре набора команд . Не забывайте , что C компилятор может сделать много оптимизаций на свой код (если вы попросите его), так что не все равно слишком много о выполнении микро-оптимизаций вручную, отпуск , что ваш компилятор ( для оптимизации компиляции выпущен програмное обеспечение). Если вы заботитесь о тестировании производительности и производительности, вам следует включить оптимизацию (после отладки вашей программы).
gcc -Wall -O2
Не забывайте, что иногда метапрограммирование полезно . Довольно часто большое программное обеспечение, написанное на C, содержит некоторые сценарии или специальные программы для генерации некоторого кода C, используемого где-либо еще (и вы также можете использовать некоторые грязные трюки препроцессора C , например, X-макросы ). Существует несколько полезных генераторов программ на Си (например, yacc или gnu bison для генерации парсеров, gperf для генерации совершенных хеш-функций и т. Д.). В некоторых системах (особенно в Linux и POSIX) вы даже можете сгенерировать некоторый код C во время выполнения в
generated-001.c
файле, скомпилировать его в общий объект, выполнив некоторую команду (напримерgcc -O -Wall -shared -fPIC generated-001.c -o generated-001.so
) во время выполнения, динамически загрузить этот общий объект, используя dlopen& получить указатель на функцию из имени, используя dlsym . Я делаю такие трюки в MELT (Lisp-подобном доменном языке, который может быть полезен для вас, так как он позволяет настраивать компилятор GCC ).Помните о концепциях и методах сбора мусора ( подсчет ссылок часто является методом управления памятью в C, и это, ИМХО, плохая форма сборки мусора, которая плохо справляется с циклическими ссылками ; у вас могут быть слабые указатели, чтобы помочь в этом, но это может быть сложно). В некоторых случаях вы могли бы рассмотреть возможность использования консервативного сборщика мусора Бема .
источник
Способ построения программы в основном определяет, какие действия (функции) должны выполняться для решения проблемы (именно поэтому она называется процедурным языком). Каждое действие будет соответствовать функции. Затем вам нужно определить, какую информацию будет получать каждая функция и какую информацию они должны возвращать.
Программа обычно разделяется на файлы (модули), каждый файл обычно имеет группу связанных функций. В начале каждого файла вы объявляете (вне любой функции) переменные, которые будут использоваться всеми функциями в этом файле. Если вы используете «статический» квалификатор, эти переменные будут видны только внутри этого файла (но не из других файлов). Если вы не используете «статический» спецификатор для переменных, определенных вне функций, они будут доступны и из других файлов, и эти другие файлы должны объявить переменную как «внешнюю» (но не определять ее), так что компилятор будет искать их в других файлах.
Короче говоря, вы сначала думаете о процедурах (функциях), а затем убедитесь, что все функции имеют доступ к необходимой информации.
источник
API C часто - возможно, даже обычно - имеют по существу объектно-ориентированный интерфейс, если вы посмотрите на них правильно.
В C ++:
В С:
Как вы, возможно, знаете, в C ++ и различных других формальных ОО-языках внутри объекта метод объекта принимает первый аргумент, который является указателем на объект, во многом как версия C
bar()
выше. В качестве примера того, как это выходит на поверхность в C ++, рассмотрим, какstd::bind
можно использовать, чтобы приспособить методы объекта к сигнатурам функций:Как уже отмечали другие, реальное различие заключается в том, что формальные ОО-языки могут реализовывать полиморфизм, управление доступом и другие отличные функции. Но сущность объектно-ориентированного программирования, создание и управление дискретными, сложными структурами данных, уже является фундаментальной практикой в C.
источник
Одна из главных причин, по которой людям предлагается изучать C, это то, что это один из самых низких языков программирования высокого уровня. Языки ООП позволяют легче думать о моделях данных и шаблонном коде и передаче сообщений, но в конце концов микропроцессор выполняет код шаг за шагом, перепрыгивая через блоки кода (функции в C) и перемещаясь ссылки на переменные (указатели на C) вокруг, чтобы разные части программы могли обмениваться данными. Думайте о C как о языке ассемблера на английском языке - предоставляя пошаговые инструкции для микропроцессора вашего компьютера - и вы не ошибетесь. В качестве бонуса, большинство интерфейсов операционной системы работают как вызовы функций C, а не как парадигмы ООП,
источник
uint16_t blah(uint16_t x) {return x*x;}
будет работать одинаково на машинах сunsigned int
16 битами или с 33 или более битами. Однако некоторые компиляторы для машин сunsigned int
длиной от 17 до 32 бит могут рассматривать вызов этого метода ...uint16_t
получит 9, стандарт не предписывает такое поведение при умножении значений типаuint16_t
на 17–32-битных платформах.Я также являюсь уроженцем ОО (в целом C ++), которому иногда приходится выживать в мире C. Для меня принципиально большим препятствием является обработка ошибок и управление ресурсами.
В C ++ у нас есть throw, чтобы передать ошибку от того места, где оно происходит, до самого верхнего уровня, где мы можем с ним справиться, и у нас есть деструкторы для автоматического освобождения нашей памяти и других ресурсов.
Вы можете заметить, что многие C API включают функцию init, которая предоставляет вам typedef'd void *, который на самом деле является указателем на структуру. Затем вы передаете это в качестве первого аргумента для каждого вызова API. По сути, это становится вашим указателем «это» из C ++. Он используется для всех внутренних структур данных, которые скрыты (очень концепция ОО). Вы также можете использовать его для управления памятью, например, иметь функцию myapiMalloc, которая распределяет память по памяти и записывает malloc в вашу версию C указателя this, чтобы вы могли быть уверены, что она освободится, когда ваш API вернется. Также, как я недавно обнаружил, вы можете использовать его для хранения кодов ошибок и использовать setjmp и longjmp, чтобы дать вам поведение, очень похожее на throw catch. Объединение обеих концепций дает вам большую функциональность программы на C ++.
Теперь вы сказали, что не хотите учить форсировать C в C ++. Это не совсем то, что я описываю (по крайней мере, не намеренно). Это просто (надеюсь) хорошо разработанный метод для использования функциональности Си. Оказывается, у некоторых ОО-разновидностей - возможно, именно поэтому ОО-языки развивались, они были способом формализовать / внедрить / упростить концепции, которые некоторые люди сочли наилучшей практикой.
Если вы думаете, что это для ОО чувства, то альтернатива состоит в том, чтобы почти каждая функция возвращала код ошибки, который вы должны неукоснительно проверять после каждого вызова функции и распространять его по стеку вызовов. Вы должны убедиться, что все ресурсы освобождаются не только в конце каждой функции, но и в каждой точке возврата (что может быть после любого вызова функции, который может вернуть ошибку, указывающую на невозможность продолжения). Это может быть очень утомительно и может привести вас к мысли, что мне, вероятно, не нужно иметь дело с этим потенциальным отказом выделения памяти (или чтением файла, или соединением порта ...), я просто предположу, что это будет работать, или я ' Я напишу «интересный» код сейчас и вернусь, чтобы разобраться с обработкой ошибок, чего никогда не происходит.
источник