Anteriormente, solo he usado lenguajes de programación orientados a objetos (C ++, Ruby, Python, PHP), y ahora estoy aprendiendo C. Me resulta difícil encontrar la manera correcta de hacer las cosas en un lenguaje sin el concepto de un 'Objeto'. Me doy cuenta de que es posible usar paradigmas OOP en C, pero me gustaría aprender la forma idiomática C.
Al resolver un problema de programación, lo primero que hago es imaginar un objeto que resuelva el problema. ¿Con qué pasos reemplazo esto cuando uso un paradigma de programación imperativa que no es OOP?
object-oriented
c
Mas Bagol
fuente
fuente
qux = foo.bar(baz)
vuelvenqux = Foo_bar(foo, baz)
.Respuestas:
struct
.Eso es.
¿Cómo escribiste una clase? Así es como escribes un archivo .C. De acuerdo, no obtienes cosas como el método de polimorfismo y herencia, pero de todos modos puedes simular aquellos con diferentes nombres de funciones y composiciones .
Para allanar el camino, estudie la Programación Funcional. Es realmente sorprendente lo que puedes hacer sin clases, y algunas cosas funcionan mejor sin la sobrecarga de clases.
Lectura adicional
Orientación a objetos en ANSI C
fuente
typedef
esostruct
y hacer algo parecido a una clase . Lostypedef
tipos yed se pueden incluir en otrosstruct
s que pueden ser ellos mismostypedef
. lo que no se obtiene con C es la sobrecarga del operador y la herencia superficialmente simple de las clases y los miembros que se encuentran en C ++. y no obtienes mucha sintaxis extraña y antinatural que obtienes con C ++. Realmente me encanta el concepto de OOP, pero creo que C ++ es una realización fea de OOP. Me gusta C porque es un lenguaje más pequeño y deja de lado la sintaxis del lenguaje que mejor se deja a las funciones.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
. Fuente: TypeScript, Dart, CoffeeScript y todas las otras formas en que la industria está tratando de alejarse de un lenguaje OOP funcional / prototipo.Lea el SICP y aprenda el Esquema y la idea práctica de los tipos de datos abstractos . Luego, codificar en C es fácil (ya que con SICP, un poco de C y un poco de PHP, Ruby, etc., su pensamiento sería lo suficientemente amplio, y comprendería que la programación orientada a objetos podría no ser el mejor estilo en todos los casos, pero solo para algún tipo de programa). Tenga cuidado con la asignación de memoria dinámica C , que probablemente sea la parte más difícil. El estándar de lenguaje de programación C99 o C11 y su biblioteca estándar C es bastante pobre (¡no sabe sobre TCP o directorios!), Y a menudo necesitará algunas bibliotecas o interfaces externas (p. Ej.POSIX , libcurl para la biblioteca del cliente HTTP, libonion para la biblioteca del servidor HTTP, GMPlib para bignums, alguna biblioteca como libunistring para UTF-8, etc.).
Sus "objetos" a menudo están en C algunos
struct
-s relacionados , y usted define el conjunto de funciones que operan en ellos. Para funciones cortas o muy simples, considere definirlas, con la correspondientestruct
, comostatic inline
en algún archivo de encabezadofoo.h
para ser#include
-d en otro lugar.Tenga en cuenta que la programación orientada a objetos no es el único paradigma de programación . En algunas ocasiones, otros paradigmas valen la pena ( programación funcional a la Ocaml o Haskell o incluso Scheme o Commmon Lisp, programación lógica a la Prolog, etc. etc ... Lea también el blog de J.Pitrat sobre inteligencia artificial declarativa). Ver el libro de Scott: Pragmática del lenguaje de programación
En realidad, un programador en C, o en Ocaml, generalmente no quiere codificar en un estilo de programación orientado a objetos. No hay razón para obligarse a pensar en objetos cuando eso no es útil.
Definirá algunos
struct
y las funciones que operan en ellos (a menudo a través de punteros). Es posible que necesite algunas uniones etiquetadas (a menudo,struct
con un miembro de etiqueta, a menudo algunasenum
y algunasunion
dentro), y puede resultarle útil tener un miembro de matriz flexible al final de algunos de susstruct
-s.Mire dentro del código fuente de algún software libre existente en C (vea github & sourceforge para encontrar algunos). Probablemente, instalar y usar una distribución de Linux sería útil: está hecho casi exclusivamente de software libre, tiene excelentes compiladores de software libre C ( GCC , Clang / LLVM ) y herramientas de desarrollo. Consulte también Programación avanzada de Linux si desea desarrollar para Linux.
No olvide compilar todas las advertencias e información de depuración, por ejemplo,
gcc -Wall -Wextra -g
notablemente durante las fases de desarrollo y depuración, y aprenda a usar algunas herramientas, por ejemplo, valgrind para buscar fugas de memoria , elgdb
depurador, etc. Tenga cuidado de comprender bien lo que no está definido. comportamiento y evítelo (recuerde que un programa podría tener algo de UB y que a veces parece "funcionar").Cuando realmente necesita construcciones orientadas a objetos (en particular herencia ) puede usar punteros a estructuras relacionadas y funciones. Puede tener su propia maquinaria vtable , hacer que cada "objeto" comience con un puntero a una
struct
función que contiene punteros. Aprovecha la capacidad de emitir un tipo de puntero a otro tipo de puntero (y el hecho de que puede emitir desde un questruct super_st
contenga los mismos tipos de campo que aquellos que comienzanstruct sub_st
a emular la herencia). Tenga en cuenta que C es suficiente para implementar sistemas de objetos bastante sofisticados, en particular siguiendo algunas convenciones , como lo demuestra GObject (de GTK / Gnome).Cuando realmente necesite cierres , a menudo los emulará con devoluciones de llamada , con la convención de que cada función que utiliza una devolución de llamada pasa tanto un puntero de función como algunos datos del cliente (consumidos por el puntero de función cuando llama a eso). También podría tener (convencionalmente) sus propios cierres como
struct
(que contienen algún puntero de función y los valores cerrados).Dado que C es un lenguaje de muy bajo nivel, es importante definir y documentar sus propias convenciones (inspiradas en la práctica en otros programas de C), en particular sobre la administración de memoria, y probablemente también algunas convenciones de nomenclatura. Es útil tener alguna idea sobre la arquitectura del conjunto de instrucciones . No olvide que un compilador de C puede hacer muchas optimizaciones en su código (si lo solicita), así que no se preocupe demasiado por hacer micro-optimizaciones a mano, déjelo a su compilador (
gcc -Wall -O2
para una compilación optimizada de software). Si le interesan las evaluaciones comparativas y el rendimiento sin procesar, debe habilitar las optimizaciones (una vez que se haya depurado el programa).No olvides que a veces la metaprogramación es útil . Muy a menudo, el software grande escrito en C contiene algunos scripts o programas ad-hoc para generar algún código C utilizado en otro lugar (y también puede jugar algunos trucos sucios del preprocesador C , por ejemplo , macros X ). Existen algunos generadores de programas C útiles (por ejemplo, yacc o gnu bison para generar analizadores, gperf para generar funciones hash perfectas, etc.). En algunos sistemas (especialmente Linux y POSIX), incluso podría generar algún código C en tiempo de ejecución en el
generated-001.c
archivo, compilarlo en un objeto compartido ejecutando algún comando (comogcc -O -Wall -shared -fPIC generated-001.c -o generated-001.so
) en tiempo de ejecución, cargar dinámicamente ese objeto compartido usando dlopen& obtener un puntero de función de un nombre usando dlsym . Estoy haciendo tales trucos en MELT (un lenguaje específico de dominio similar a Lisp que podría serle útil, ya que permite la personalización del compilador GCC ).Tenga en cuenta los conceptos y técnicas de recolección de basura (el conteo de referencias es a menudo una técnica para administrar la memoria en C, y en mi humilde opinión, es una forma pobre de recolección de basura que no trata bien con referencias circulares ; podría tener punteros débiles para ayudar con eso, pero puede ser complicado) En algunas ocasiones, puede considerar usar el recolector de basura conservador de Boehm .
fuente
La forma en que se construye el programa es básicamente definir qué acciones (funciones) se deben realizar para resolver el problema (es por eso que se llama lenguaje de procedimiento). Cada acción corresponderá a una función. Luego, debe definir qué tipo de información recibirá cada función y qué información necesita devolver.
El programa normalmente está separado en archivos (módulos), cada archivo normalmente tendrá un grupo de funciones relacionadas. Al comienzo de cada archivo, declara (fuera de cualquier función) las variables que serán utilizadas por todas las funciones en ese archivo. Si utiliza el calificador "estático", esas variables solo serán visibles dentro de ese archivo (pero no desde otros archivos). Si no utiliza el calificador "estático" en las variables definidas fuera de las funciones, también será accesible desde otros archivos y estos otros archivos deberían declarar la variable como "externa" (pero no definirla) para que el compilador las busque en otros archivos
En resumen, primero piensa en los procedimientos (funciones) y luego se asegura de que todas las funciones tengan acceso a la información que necesitan.
fuente
Las API de C a menudo, tal vez incluso, por lo general , tienen una interfaz esencialmente orientada a objetos si las mira de la manera correcta.
En C ++:
Cía:
Como ya sabrá, en C ++ y en varios otros lenguajes formales de OO, bajo el capó, un método de objeto toma un primer argumento que es un puntero al objeto, al igual que la versión C
bar()
anterior. Para ver un ejemplo de dónde sale esto a la superficie en C ++, considere cómostd::bind
se puede usar para ajustar los métodos de objeto a las firmas funcionales:Como otros han señalado, la verdadera diferencia es que los lenguajes formales de OO pueden implementar polimorfismo, control de acceso y varias otras características ingeniosas. Pero la esencia de la programación orientada a objetos, la creación y manipulación de estructuras de datos discretas y complejas, ya es una práctica fundamental en C.
fuente
Una de las principales razones por las que se alienta a las personas a aprender C es que es uno de los lenguajes de programación de nivel más bajo. Los lenguajes OOP hacen que sea más fácil pensar en los modelos de datos y el código de plantillas y el paso de mensajes, pero al final del día, un microprocesador ejecuta el código paso a paso, entrando y saliendo de bloques de código (funciones en C) y moviéndose referencias a variables (punteros en C) alrededor para que diferentes partes de un programa puedan compartir datos. Piense en C como lenguaje ensamblador en inglés, que proporciona instrucciones paso a paso para el microprocesador de su computadora, y no se equivocará. Como beneficio adicional, la mayoría de las interfaces del sistema operativo funcionan como llamadas de función C en lugar de paradigmas OOP,
fuente
uint16_t blah(uint16_t x) {return x*x;}
funcionará de manera idéntica en máquinas conunsigned int
16 bits o con 33 bits o más.unsigned int
Sin embargo, algunos compiladores para máquinas de 17 a 32 bits pueden considerar una llamada a ese método ...uint16_t
, arrojaría 9, el Estándar no exige tales comportamientos al multiplicar valores de tipouint16_t
en plataformas de 17 a 32 bits.También soy un nativo de OO (C ++ en general) que a veces tiene que sobrevivir en un mundo de C. Para mí, el obstáculo más importante es tratar con el manejo de errores y la gestión de recursos.
En C ++ tenemos que pasar un error desde donde ocurre hasta el nivel superior donde podemos lidiar con él y tenemos destructores para liberar automáticamente nuestra memoria y otros recursos.
Puede notar que muchas API de C incluyen una función init que le proporciona un typedef'd void * que es realmente un puntero a una estructura. Luego, pasa esto como el primer argumento para cada llamada a la API. Esencialmente, eso se convierte en su "este" puntero de C ++. Se utiliza para todas las estructuras de datos internas que están ocultas (un concepto muy OO). También puede usarlo para administrar la memoria, por ejemplo, tener una función llamada myapiMalloc que malloquea su memoria y registra el malloc en su versión C de este puntero para que pueda asegurarse de que esté libre cuando regrese su API. Además, como descubrí recientemente, puede usarlo para almacenar códigos de error y usar setjmp y longjmp para brindarle un comportamiento muy similar al lanzamiento de capturas. La combinación de ambos conceptos le brinda mucha funcionalidad de un programa C ++.
Ahora dijiste que no querías aprender a forzar C a C ++. Eso no es realmente lo que estoy describiendo (al menos no deliberadamente). Este es simplemente un método (con suerte) bien diseñado para explotar la funcionalidad de C. Resulta que tiene algunos sabores OO, tal vez por eso se desarrollaron los lenguajes OO, fueron una forma de formalizar / hacer cumplir / facilitar conceptos que algunas personas consideraron la mejor práctica.
Si cree que esto es algo que OO siente por usted, entonces la alternativa es que casi todas las funciones devuelvan un código de error que debe asegurarse religiosamente de verificar después de cada llamada de función y propagar la pila de llamadas. Debe asegurarse de que todos los recursos se liberen no solo al final de cada función, sino en cada punto de retorno (lo que podría suceder después de cualquier llamada de función que pueda devolver un error que indique que no puede continuar). Puede volverse muy tedioso y tiende a hacerte pensar que probablemente no necesito lidiar con esa falla potencial de asignación de memoria (o lectura de archivo o conexión de puerto ...), solo asumiré que funcionará o ' Escribiré el código "interesante" ahora y volveré y me ocuparé del manejo de errores, lo que nunca sucede.
fuente