¿En qué se diferencia C de C ++?

21

Muchas personas han dicho que C ++ es un lenguaje completamente diferente que C, pero el propio Bjarne ha dicho que C ++ es un lenguaje que se extiende desde C, de ahí que es de donde ++proviene. Entonces, ¿por qué todos dicen que C y C ++ son lenguajes completamente diferentes? ¿En qué se diferencia C de C ++ de las características extendidas en C ++?

Joshua Partogi
fuente
3
Debido a cómo se usan. Ciertamente puedes escribir C en C ++ ... pero no deberías.
Ed S.

Respuestas:

18

Durante la década de 1980, cuando el desarrollo de C ++ apenas comenzaba, C ++ era casi un superconjunto adecuado de C. Así es como comenzó todo.
Sin embargo, con el tiempo, tanto C como C ++ evolucionaron y se han separado entre sí, aunque la compatibilidad entre los lenguajes siempre se ha considerado importante.

Además, las diferencias técnicas entre C y C ++ han hecho que las expresiones idiomáticas típicas en esos lenguajes y lo que se considera 'buena práctica' diverjan aún más.

Este es el factor determinante detrás de las personas que dicen cosas como "no existe un lenguaje como C / C ++" o "C y C ++ son dos lenguajes diferentes". Aunque es posible escribir programas que sean aceptables tanto para un compilador C como para C ++, el código generalmente no se considera un ejemplo de buen código C ni un buen código C ++.

Bart van Ingen Schenau
fuente
No creo que el primer párrafo sea correcto. Creo que C siempre tuvo un casting implícito, void *pero C ++ nunca lo hizo. (No votó a favor)
alternativa
55
@mathepic: Definir "siempre". C tomó el tipo void * de C ++. En K&R C, Malloc regresaba char *
Nemanja Trifunovic
19

Stroustrup mismo responde eso en sus preguntas frecuentes :

C ++ es un descendiente directo de C que retiene casi todo C como un subconjunto. C ++ proporciona una verificación de tipo más fuerte que C y admite directamente una gama más amplia de estilos de programación que C. C ++ es "una mejor C" en el sentido de que admite los estilos de programación realizados usando C con una mejor verificación de tipo y más soporte de notación (sin pérdida de eficiencia). En el mismo sentido, ANSI C es una mejor C que K&R C. Además, C ++ admite abstracción de datos, programación orientada a objetos y programación genérica.

Es compatible con la programación orientada a objetos y la programación genérica que hacen que C ++ sea "completamente diferente" a C. Casi puede escribir C puro y luego compilarlo con un compilador de C ++ (siempre y cuando se encargue de la verificación de tipos más estricta). Pero aún estás escribiendo C, no estás escribiendo C ++.

Si está escribiendo C ++, está haciendo uso de sus características orientadas a objetos y de plantilla, y eso no se parece en nada a lo que vería en C.

Dean Harding
fuente
"Eso no se parece en nada a lo que verías en C." ¡Esta frase suena divertida! "ver en C". C en C! Este es un tipo de poesía.
Galaxy
13

En pocas palabras, lo que se considera idiomático en C definitivamente no lo es en C ++.

C y C ++ son lenguajes muy diferentes en la práctica, debido a la forma en que las personas los usan. C apunta al minimalismo, donde C ++ es un lenguaje muy complejo, con muchas características.

También hay algunas diferencias prácticas: se puede llamar fácilmente a C desde prácticamente cualquier lenguaje, y a menudo define el ABI de una plataforma, mientras que C ++ es bastante difícil de usar desde otras bibliotecas. La mayoría de los lenguajes tienen una FFI o interfaz en C, incluso los lenguajes implementados en C ++ (Java, por ejemplo).

David Cournapeau
fuente
44
No es solo que el código de estilo C no sea idiomático en C ++. La codificación de estilo C es realmente problemática en C ++, debido a la falta de seguridad de excepción.
dan04
4

Además del hecho obvio de que C ++ admite programación orientada a objetos, creo que tiene su respuesta aquí: http://en.wikipedia.org/wiki/Compatibility_of_C_and_C++

Ese artículo contiene ejemplos de código que muestran cosas que están bien en C pero no en C ++. Por ejemplo:

int *j = malloc(sizeof(int) * 5); /* Implicit conversion from void* to int* */

Portar un programa C a C ++ a menudo es sencillo y consiste principalmente en corregir errores de compilación (agregar conversiones, nuevas palabras clave, etc.).

Martin Wickman
fuente
44
Portar un programa como ese no te da un programa C ++. Le da C que se puede compilar en el compilador de C ++. Eso no lo convierte en C ++ (todavía llamaría al código resultante C (ni siquiera C con clases)).
Martin York
@MartinYork Llámelo malo C, señale que la semántica podría ser diferente, y estoy de acuerdo. El resultado es obviamente C, sin embargo.
Deduplicador
2

C ++ agrega no solo nuevas características, sino también nuevos conceptos y nuevas expresiones idiomáticas a C. Aunque C ++ y C están estrechamente relacionados, el hecho es que para escribir efectivamente en un lenguaje, debe pensar en el estilo de ese lenguaje. Incluso el mejor código C no puede aprovechar las diferentes fortalezas y modismos de C ++, por lo que es más probable que no sea un código C ++ bastante malo .

Jon Purdy
fuente
2

Las "características extendidas", hacen que parezca que en C ++ agregaron macros variadic o algo así, y eso es todo. Las "características extendidas" en C ++ son una revisión completa del lenguaje y reemplazan totalmente las mejores prácticas de C porque las nuevas características de C ++ son mucho mejores que las características de C originales que las características de C originales son completamente y totalmente redundantes en la gran mayoría de los casos . Sugerir que C ++ simplemente extiende C sugiere que un tanque de batalla moderno extiende un cuchillo de mantequilla con el propósito de librar una guerra.

DeadMG
fuente
¿Puedes enumerar un ejemplo de una de las características más útiles que tiene C ++ y que C no tiene?
Dark Templar
2
@DarkTemplar: ¿Qué tal la administración de recursos en modo fácil con RAII? ¿O agradables estructuras de datos genéricos usando plantillas? Solo para empezar.
DeadMG
1

La diferencia es que en C piensa de manera procesal y en C ++ piensa de forma orientada a objetos. Los idiomas son bastante similares, pero el enfoque es muy diferente.

Hormiga
fuente
¿C tampoco tiene estructuras? ¿Los archivos C no son módulos separados (básicamente, objetos)? No estoy seguro de cuál es la diferencia exacta entre el procedimiento y la orientación a objetos ...
Dark Templar
1
Oscuro has ilustrado mi punto. No es una limitación del lenguaje que le permite escribir de manera procesal o orientada a objetos, es un ethos o una forma de pensar.
Ant
0

Si bien C ++ puede ser un superconjunto de C en términos sintácticos, es decir, cualquier compilación del programa C puede ser compilada por el compilador de C ++.

Sin embargo, casi nunca escribe un programa C ++ de la misma manera que lo hubiera hecho con el programa C. La lista puede ser interminable o puede ser que alguien deba investigar más para ponerla como informes exhaustivos. Sin embargo, pongo algunos indicadores que marcan las diferencias clave.

El punto de la publicación actual es que C ++ tiene las siguientes características que los buenos programadores de C ++ deben usar como mejores prácticas de programación, aunque es posible compilar C equivalente.

¿Cómo se debe hacer en C ++ sobre C

  1. Clases y herencia. Esas son las diferencias más importantes que permiten la orientación sistemática de objetos que hace que la expresión de programación sea muy poderosa. Supongo que este punto no necesita una mejor explicación. Si está en C ++, casi siempre, es mejor hacer uso de las clases.

  2. Privatización: las clases e incluso las estructuras tienen miembros privados. Esto hace posible la encapsulación de una clase. El equivalente en C es encasillando el objeto como vacío * a la aplicación para que la aplicación no tenga acceso a variables internas. Sin embargo, en C ++ puede tener elementos con clases públicas y privadas.

  3. Pase por referencia. C ++ permite la modificación basada en la referencia, para lo cual se requieren punteros de paso. El paso por referencia mantiene el código muy limpio y más seguro contra los riesgos de puntero. También puede pasar el puntero de estilo C y eso funciona, pero si está en C ++, estará mejor siempre que

  4. nuevo y eliminar vs malloc y gratis. Las declaraciones new () y delete () no solo asignan y desasignan memoria, sino que también permiten que el código se ejecute como parte de destruct-er para ser llamado en una cadena. Si está usando C ++, en realidad es MALO usar malloc y gratis.

  5. Tipos de E / S y sobrecarga del operador La sobrecarga del operador hace que el código sea legible o más intuitivo si se hace bien. Lo mismo para los operadores << y >> io. La forma en C de hacer esto sería usar punteros de función, pero es complicado y solo para programadores avanzados.

  6. Usando "cadena". El char * de C funciona en todas partes. Entonces, C y C ++ son más o menos lo mismo. Sin embargo, si está en C ++, siempre es mucho mejor (y más seguro) usar las clases de String, lo que lo salva de los peligros de las matrices sobre la ejecución, que son casi todas las cosas.

Características de las que aún no sería fanático en C ++ 1. Plantillas: aunque no uso plantillas pesadas en muchos códigos, puede resultar muy potente para las bibliotecas. Casi no hay un equivalente en C. Pero en un día normal, especialmente si te falta matemáticamente.

  1. Punteros inteligentes: ¡sí, son muy inteligentes! Y como la mayoría de las cosas inteligentes, ¡comienzan bien y se vuelven desordenadas después! No me gusta usar

Cosas que me gustan de C y extraño en C ++

  1. Algoritmos polimórficos mediante el uso de punteros de función. En C, cuando ejecuta algoritmos complejos, algunas veces puede usar un conjunto de punteros de función. Esto hace verdadero polimorfismo de una manera poderosa. Cuando está en C ++, PUEDE usar punteros de función, pero eso es malo. Debería usar solo métodos; de lo contrario, prepárese para ponerse desordenado. La única forma de polimorfismo en las clases de C ++ es la sobrecarga de funciones y operadores, pero eso es bastante limitante.

  2. Hilos simples Cuando creas hilos eran pthreads, es bastante simple y manejable. Se convierte en cuando necesita crear hilos que se supone que son "privados" para las clases (para que tengan acceso a miembros privados). Hay marcos de tipo impulso , pero nada en C ++ básico.

Dipan

Dipan Mehta
fuente
0

Ciertas características del lenguaje, incluso si son adiciones, pueden cambiar la forma en que el lenguaje prácticamente necesita ser utilizado. Como un ejemplo, considere este caso:

lock_mutex(&mutex);

// call some functions
...

unlock_mutex(&mutex);

Si el código anterior involucraba funciones de llamada implementadas en C ++, podríamos estar en un mundo de problemas, ya que cualquiera de esas llamadas de función puede lanzarse y nunca desbloquearemos el mutex en esas rutas excepcionales.

Los destructores ya no son convenientes para ayudar a los programadores a evitar olvidar liberar / liberar recursos en ese momento. RAII se convierte en un requisito práctico porque no es humanamente factible anticipar cada línea de código que pueda incluir ejemplos no triviales (sin mencionar que esas líneas pueden no arrojarse ahora, pero pueden cambiar más adelante). Toma otro ejemplo:

void f(const Foo* f1)
{
    Foo f2;
    memcpy(&f2, f1, sizeof f2);
    ...
}

Tal código, aunque generalmente es inocuo en C, es como el caos del infierno que reina en C ++, porque las memcpyexcavaciones sobre los bits y bytes de estos objetos y evita cosas como los constructores de copias. Tales funciones como memset, realloc, memcpy, etc, mientras que las herramientas diarias entre los desarrolladores de C utilizado para ver las cosas de una manera bastante homogénea de bits y bytes en la memoria, no están en armonía con el sistema de tipo más complejo y más rico de C ++. C ++ fomenta una visión mucho más abstracta de los tipos definidos por el usuario.

Entonces, este tipo de cosas ya no permiten que C ++, para cualquiera que quiera usarlo correctamente, lo vea como un mero "superconjunto" de C. Estos lenguajes requieren una mentalidad, disciplina y forma de pensar muy diferentes para usarlo de manera más efectiva .

No estoy en el campo que ve a C ++ como absolutamente mejor en todos los sentidos, y en realidad la mayoría de mis bibliotecas de terceros favoritas son bibliotecas de C por alguna razón. No sé exactamente por qué, pero las bibliotecas C tienden a ser de naturaleza más minimalista (tal vez porque la ausencia de un sistema de tipo tan rico hace que los desarrolladores se centren más en proporcionar la funcionalidad mínima requerida sin crear un conjunto de abstracciones grandes y en capas), aunque a menudo termino simplemente colocando envoltorios de C ++ a su alrededor para simplificar y adaptar su uso para mis propósitos, pero esa naturaleza minimalista es preferible para mí incluso cuando lo hago. Realmente amo el minimalismo como una característica atractiva de una biblioteca para aquellos que se toman el tiempo extra para buscar tales cualidades, y tal vez C tiende a alentar eso,

Estoy a favor de C ++ con mucha frecuencia, pero en realidad se me exige que use las API de C con bastante frecuencia para la compatibilidad binaria más amplia (y para FFI), aunque a menudo las implemento en C ++ a pesar de usar C para los encabezados. Pero a veces, cuando se pasa a un nivel realmente bajo, como el nivel de un asignador de memoria o una estructura de datos de muy bajo nivel (y estoy seguro de que hay más ejemplos entre los que hacen programación integrada), a veces puede ser útil ser capaz de asumir que los tipos y datos con los que está trabajando están ausentes de ciertas características como vtables, costructores y destructores, por lo que podemos tratarlos como bits y bytes para barajar, copiar, liberar, reasignar. Para problemas de muy bajo nivel, a veces puede ser útil trabajar con un sistema de tipo mucho más simple que proporciona C,

Una clarificación

Un comentario interesante aquí que quería responder un poco más en profundidad (los comentarios aquí son muy estrictos en cuanto al límite de caracteres):

memcpy(&f2, f1, sizeof f2); también es "el caos reinante del fuego del infierno" en C si Foo tiene punteros propios, o es aún peor, ya que también te faltan las herramientas para lidiar con eso.

Ese es un punto justo, pero todo en lo que me estoy centrando es predominantemente con un enfoque en el sistema de tipos de C ++ y también con respecto a RAII. Una de las razones por las cuales la copia de bytes en rayos X memcpyo los qsorttipos de funciones representan un peligro menos práctico en C es que la destrucción de f1y por f2encima es explícita (si incluso necesitan una destrucción no trivial), mientras que cuando los destructores entran en escena , se vuelven implícitos y automatizados (a menudo con un gran valor para los desarrolladores). Eso ni siquiera menciona el estado oculto como vptrs, etc., sobre el cual tales funciones se demorarían. Si f1posee punteros yf2Shallow los copia en algún contexto temporal, entonces no plantea ningún problema si no intentamos liberar explícitamente a los propietarios de punteros por segunda vez. Con C ++ eso es algo que el compilador querrá hacer automáticamente.

Y eso se vuelve más grande si normalmente está en C, " Si Foo tiene punteros propietarios", porque la explicitación requerida con la gestión de recursos a menudo hará que algo sea típicamente más difícil de pasar por alto, mientras que en C ++, podemos hacer que un UDT ya no sea trivial constructible / destructible simplemente haciendo que almacene cualquier variable miembro que no sea trivialmente constructible / destructible (de una manera que generalmente es muy útil, de nuevo, pero no si estamos tentados a usar funciones como memcpyo realloc).

Mi punto principal no es tratar de argumentar ningún beneficio de esta explicidad (yo diría que si hay alguna, casi siempre están agobiadas por las desventajas de la mayor probabilidad de error humano que conlleva), sino simplemente decir que funciones como memcpyy memmovey qsorty memsetyreallocy así sucesivamente no tienen cabida en un lenguaje con UDT tan rico en características y capacidades como C ++. Si bien existen independientemente, creo que no sería demasiado discutible decir que la gran mayoría de los desarrolladores de C ++ sería prudente evitar tales funciones como la peste, mientras que estas son funciones muy cotidianas en C, y yo ' Argumentaría que plantean menos problemas en C por la simple razón de que su sistema de tipos es mucho más básico y, quizás, "más tonto". Hacer rayos X con los tipos C y tratarlos como bits y bytes es propenso a errores. Hacer eso en C ++ podría decirse que es completamente erróneo porque tales funciones están luchando contra características muy fundamentales del lenguaje y lo que alienta del sistema de tipos.

Sin embargo, ese es realmente el mayor atractivo para mí de C, específicamente con la forma en que se relaciona con la interoperabilidad del lenguaje. Sería mucho, mucho más difícil hacer que algo como FFI de C # entienda el sistema de tipos completo y las características del lenguaje de C ++ hasta constructores, destructores, excepciones, funciones virtuales, sobrecarga de funciones / métodos, sobrecarga de operadores, todos los diversos tipos de herencia, etc. Con C es un lenguaje relativamente más tonto que se ha vuelto bastante estándar en cuanto a las API de manera que muchos lenguajes diferentes pueden importar directamente a través de FFI, o indirectamente a través de algunas funciones de exportación de API de C en la forma deseada (por ejemplo: Java Native Interface ) Y ahí es donde principalmente no me queda más remedio que usar C, ya que la interoperabilidad del lenguaje es un requisito práctico en nuestro caso (aunque a menudo yo '

Pero ya sabes, soy un pragmático (o al menos me esfuerzo por serlo). Si C fuera este lenguaje desagradable y propenso, propenso a errores y desagradable, algunos de mis pares entusiastas de C ++ afirmaron que era (y me consideraría un entusiasta de C ++, excepto que de alguna manera no ha llevado a un odio hacia C por mi parte ; por el contrario, tuvo el efecto opuesto en mí al hacerme apreciar ambos idiomas mejor en sus propios aspectos y diferencias), entonces esperaría que se mostrara en el mundo real en la forma de algunos de los errores y goteos productos y bibliotecas poco confiables que se escriben en C. Y no encuentro eso. Me gusta Linux, me gusta Apache, Lua, zlib, encuentro OpenGL tolerable por su largo legado frente a los requisitos de hardware tan cambiantes, Gimp, libpng, Cairo, etc. Al menos cualquier obstáculo que el lenguaje plantea no parece representar un punto muerto en cuanto a escribir algunas bibliotecas y productos geniales en manos competentes, y eso es realmente todo lo que me interesa. Así que nunca he sido del tipo tan interesado en los más apasionados guerras lingüísticas, excepto para hacer un llamamiento pragmático y decir: "¡Oye, hay cosas geniales ahí fuera! Aprendamos cómo lo hicieron y tal vez hay lecciones interesantes, no tan específicas de la naturaleza idiomática del idioma, que podemos traer de vuelta a cualquier idioma que estemos usando ". :-RE

Dragon Energy
fuente
2
memcpy(&f2, f1, sizeof f2);también es "caos reinante del infierno" en C si Footiene algún puntero propio, o es aún peor, ya que también carece de las herramientas para lidiar con eso. Entonces, las personas que escriben C no hacen tantas cosas así
Caleth
@Caleth Algo de lo que simplifica que incluso con los punteros propietarios es la liberación explícita de los punteros propietarios, por lo que efectivamente se convierte en una copia superficial en tales casos con solo un lugar que todavía libera la memoria original, por ejemplo (en algunos casos, dependiendo de el diseño). Mientras que con la participación de destructores, ambas Fooinstancias ahora pueden querer destruir la matriz dinámica, o vector, o algo por el estilo. A menudo encuentro para algunos casos peculiares que es útil poder escribir ciertas estructuras de datos para apoyar el hecho de que la destrucción es explícita en lugar de implícita.
Dragon Energy
@Caleth Es algo de flojera de mi parte, ya que si Footiene punteros propietarios, podríamos darle una copia / movimiento apropiado, dtor, usar semántica de valor, etc., y tener un código mucho más rico y seguro. Pero ocasionalmente me encuentro buscando C en los casos en que quiero generalizar una estructura de datos o un asignador en el nivel homogéneo de bits y bytes, con ciertas suposiciones que puedo hacer sobre los tipos que tienden a simplificarse en sus casos de uso estrechos. un poco por tales suposiciones me siento un poco más seguro de hacer en C.
Dragon Energy
@Caleth Sin embargo, en mi caso, ocasionalmente hay una piedra angular de la arquitectura en la que no me sirve mirar las cosas como algo más que bits y bytes en la memoria para organizar y acceder (y, por lo general, esos casos no involucran nada más que POD). Esos son los pocos casos peculiares que aún prefiero C. Si imagina el asignador de memoria, en realidad no hay nada más con lo que trabajar que bits y bytes, punteros nulos, cosas de este tipo, con todo el enfoque en la alineación y la agrupación y repartiendo bits y bytes, y en esos casos muy peculiares, encuentro que C ofrece menos obstáculos que C ++.
Dragon Energy
El otro caso en el que a veces busco C es en los casos en que el código podría volverse más generalizado y reutilizable al ser menos abstracto y centrarse en las primitivas, API void filter_image(byte* pixels, int w, int h);como un simple ejemplo en lugar de me gusta API void filter_image(ImageInterface& img);(que acoplaría nuestro código a dicha interfaz de imagen, estrechando su aplicabilidad). En tales casos, a veces solo implemento tales funciones en C, ya que hay poco que ganar en C ++, y reduce la probabilidad de que dicho código necesite cambios futuros.
Dragon Energy