In che modo C differisce da C ++?


21

Molte persone hanno affermato che C ++ è un linguaggio completamente diverso rispetto a C, ma Bjarne stesso ha affermato che C ++ è un linguaggio che si estende da C, da qui ++deriva la sua origine. Quindi perché tutti continuano a dire che C e C ++ sono lingue completamente diverse? In che modo C differisce da C ++ oltre alle funzionalità estese in C ++?


3
A causa di come vengono utilizzati. Puoi sicuramente scrivere C in C ++ ... ma non dovresti.
Ed S.

Risposte:


18

Durante gli anni '80, quando lo sviluppo del C ++ era appena agli inizi, il C ++ era quasi un vero e proprio superset di C. Ecco come è iniziato tutto.
Tuttavia, nel tempo, sia il C che il C ++ si sono evoluti e si sono differenziati l'uno dall'altro, anche se la compatibilità tra le lingue è sempre stata considerata importante.

Inoltre, le differenze tecniche tra C e C ++ hanno reso i linguaggi tipici in quelle lingue e ciò che è considerato "buona pratica" diverge ancora di più.

Questo è il fattore trainante dietro le persone che dicono cose come "non esiste un linguaggio come C / C ++" o "C e C ++ sono due lingue diverse". Sebbene sia possibile scrivere programmi accettabili sia per un compilatore C che per un C ++, il codice è generalmente considerato né un esempio di buon codice C né un esempio di buon codice C ++.


Non penso che il primo paragrafo sia corretto. Credo che C abbia sempre avuto il casting implicito, void *ma C ++ non l'ha mai fatto. (Non sottovalutato)
alternativa il

5
@mathepic: definisce "sempre". C ha preso il tipo void * da C ++. In K&R C, malloc stava tornando char *
Nemanja Trifunovic il

19

Lo stesso Stroustrup risponde che nelle sue FAQ :

C ++ è un discendente diretto di C che mantiene quasi tutta la C come sottoinsieme. C ++ fornisce un controllo del tipo più forte di C e supporta direttamente una gamma più ampia di stili di programmazione rispetto a C. C ++ è "una C migliore", nel senso che supporta gli stili di programmazione eseguiti usando C con un migliore controllo del tipo e un maggiore supporto notazionale (senza perdita di efficienza). Allo stesso modo, ANSI C è un C migliore di K&R C. Inoltre, C ++ supporta l'astrazione dei dati, la programmazione orientata agli oggetti e la programmazione generica.

E 'di supporto per la programmazione orientata agli oggetti e la programmazione generica che fanno C ++ "completamente diverso" per C. Si può quasi scrivere puro C e poi compilarlo con un compilatore C ++ (a patto che si prende cura del tipo di controllo più severe). Ma poi stai ancora scrivendo C - non stai scrivendo C ++.

Se stai scrivendo C ++, stai facendo uso delle sue funzionalità orientate agli oggetti e dei modelli e non è niente di simile a quello che vedresti in C.


"non è niente di simile a quello che vedresti in C." Questa frase sembra divertente! "vedi in C". C in C! Questa è una specie di poesia.
Galaxy,

13

In parole povere, ciò che è considerato idiomatico in C non è assolutamente idiomatico in C ++.

C e C ++ sono in pratica linguaggi molto diversi, a causa del modo in cui le persone li usano. C punta al minimalismo, in cui C ++ è un linguaggio molto complesso, con molte funzionalità.

Ci sono anche alcune differenze pratiche: C può essere facilmente chiamato praticamente da qualsiasi linguaggio e spesso definisce l'ABI di una piattaforma, mentre C ++ è piuttosto difficile da usare da altre librerie. La maggior parte delle lingue ha un FFI o un'interfaccia in C, anche le lingue implementate in C ++ (java, per esempio).


4
Non è solo che il codice in stile C non è idiomatico in C ++. La codifica in stile C è effettivamente problematica in C ++, a causa della mancanza di sicurezza delle eccezioni.
dan04,

4

A parte l'ovvio fatto che C ++ supporta la programmazione orientata agli oggetti, penso che tu abbia la tua risposta qui: http://it.wikipedia.org/wiki/Compatibility_of_C_and_C++

Quell'articolo contiene esempi di codice che mostrano cose che vanno bene in C ma non in C ++. Per esempio:

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

Il porting di un programma C in C ++ è spesso semplice e consiste principalmente nel correggere errori di compilazione (aggiunta di cast, nuove parole chiave ecc.).


4
Il porting di un programma del genere non ti dà un programma C ++. Ti dà C che può essere compilato sul compilatore C ++. Ciò non lo rende C ++ (chiamerei comunque il codice risultante C (nemmeno C con classi)).
Martin York,

@MartinYork Chiamalo cattivo C, fai notare che la semantica potrebbe essere diversa e concordo. Il risultato è ovviamente C però.
Deduplicatore,

2

C ++ aggiunge non solo nuove funzionalità, ma nuovi concetti e nuovi modi di dire a C. Anche se C ++ e C sono strettamente correlati, resta il fatto che per scrivere efficacemente in una lingua, è necessario pensare nello stile di quella lingua. Anche il miglior codice C non può trarre vantaggio dai diversi punti di forza e idiomi del C ++, quindi è più probabile che non effettivamente un codice C ++ piuttosto scadente .


2

Le "funzionalità estese", lo fai sembrare come in C ++ hanno aggiunto come, macro variadiche o qualcosa del genere e basta. Le "funzionalità estese" in C ++ sono una revisione completa del linguaggio e sostituiscono totalmente le migliori pratiche C poiché le nuove funzionalità C ++ sono molto migliori delle funzionalità C originali che le funzionalità C originali sono completamente e totalmente ridondanti nella stragrande maggioranza dei casi . Suggerire che C ++ estende semplicemente C suggerisce che un moderno carro armato estende un coltellino per scopi di guerra.


Puoi elencare un esempio di una delle funzionalità più utili di C ++ che C non ha?
Templare oscuro,

2
@DarkTemplar: che ne dici della gestione delle risorse in modalità facile con RAII? O belle strutture di dati generici che utilizzano modelli? Tanto per cominciare.
DeadMG

1

La differenza è che in C pensi in modo procedurale e in C ++ pensi in modo orientato agli oggetti. Le lingue sono abbastanza simili ma l'approccio è molto diverso.


C non ha anche strutture? I file C non sono essi stessi moduli separati (fondamentalmente, oggetti)? Non sono sicuro di quale sia la differenza esatta tra procedurale e orientato agli oggetti ...
Dark Templar,

1
Dark hai illustrato il mio punto. Non è una limitazione del linguaggio che ti permette di scrivere in modo procedurale o orientato agli oggetti, è un ethos o un modo di pensare.
Formica

0

Mentre C ++ può essere un super set di C in termini sintattici, vale a dire qualsiasi costrutto di programma C può essere compilato dal compilatore C ++.

Tuttavia, non scrivi quasi mai un programma C ++ come faresti con il programma C. L'elenco può essere infinito o potrebbe essere qualcuno dovrebbe semplicemente fare ulteriori ricerche per metterlo come rapporti esaustivi. Tuttavia, sto mettendo alcuni suggerimenti che fanno le differenze chiave.

Il punto del post corrente è che C ++ ha le seguenti caratteristiche che un buon programmatore C ++ deve usare come buone pratiche di programmazione anche se è possibile compilare C equivalente.

Come dovrebbe essere fatto in C ++ su C

  1. Classi ed ereditarietà. Queste sono le differenze più importanti che consentono l'orientamento sistematico degli oggetti che rende l'espressione della programmazione molto potente. Immagino che questo punto non abbia bisogno di spiegazioni migliori. Se sei in C ++, quasi sempre, è meglio usare le classi.

  2. Privatizzazione: le classi e persino le strutture hanno membri privati. Ciò rende possibile l'incapsulamento di una classe. L'equivalente in C consiste nel digitare l'oggetto come void * nell'applicazione in modo che l'applicazione non abbia accesso alle variabili interne. Tuttavia, in C ++ puoi avere elementi con classi sia pubbliche che private.

  3. Passa per riferimento. C ++ consente la modifica in base al riferimento, per il quale è richiesto il passaggio di puntatori. Il pass-by-reference mantiene il codice molto pulito e molto più sicuro contro i rischi dei puntatori. Anche tu passi il puntatore in stile C e funziona, ma se sei in C ++ stai meglio finché

  4. nuovo ed elimina vs. malloc e gratuito. Le nuove istruzioni () e delete () non solo allocano e de-allocano la memoria, ma consentono anche l'esecuzione del codice come parte di destruct-er da chiamare in una catena. Se stai usando C ++, in realtà è MALE usare malloc e free.

  5. Tipi di I / O e sovraccarico dell'operatore Il sovraccarico dell'operatore rende il codice leggibile o più intuitivo se eseguito correttamente. Lo stesso vale per gli operatori << e >> io. Il modo C per farlo sarebbe usare i puntatori a funzione - ma è disordinato e solo per programmatori avanzati.

  6. Usando "stringa". Il carattere * di C funziona ovunque. Quindi C e C ++ sono praticamente gli stessi. Tuttavia, se sei in C ++, è sempre molto meglio (e più sicuro) usare le classi String che ti salvano dai pericoli delle matrici durante l'esecuzione che sono quasi tutte le cose.

Funzionalità di cui non sarei ancora fan di C ++ 1. Modelli - Anche se non uso modelli pesanti in molti codici - può rivelarsi molto potente per le librerie. Non c'è quasi nessun equivalente in C. Ma in un giorno normale - specialmente se stai perdendo matematicamente.

  1. Puntatori intelligenti - Sì, sono molto intelligenti! E come la maggior parte delle cose intelligenti, iniziano bene e diventano più disordinati in seguito! Non mi piace usare

Cose che mi piacciono di C e che mi mancano in C ++

  1. Algoritmi polimorfici usando i puntatori a funzione. In C, quando si eseguono algoritmi complessi, a volte è possibile utilizzare un set di puntatori a funzione. Questo rende il vero polimorfismo in modo potente. Quando si è in C ++ si POTETE utilizzare puntatori a funzione - ma che è male. Dovresti usare solo metodi - altrimenti preparati a diventare disordinato. L'unica forma di polimorfismo nelle classi C ++ è il sovraccarico di funzioni e operatori, ma è piuttosto limitante.

  2. Discussioni semplici Quando si creano thread sono stati pthreads - è abbastanza semplice e gestibile. Diventa quando è necessario creare thread che dovrebbero essere "privati" per le classi (in modo che abbiano accesso ai membri privati). Esiste un tipo di framework di boost , ma nulla nel C ++ di base.

Dipan.


0

Alcune funzionalità linguistiche, anche se sono aggiunte, possono cambiare completamente il modo in cui la lingua deve essere praticamente utilizzata. Ad esempio, considera questo caso:

lock_mutex(&mutex);

// call some functions
...

unlock_mutex(&mutex);

Se quel codice sopra riguardava le funzioni di chiamata implementate in C ++, potremmo trovarci in un mondo di problemi, dato che una di quelle chiamate di funzione potrebbe lanciare e non sbloccheremo mai il mutex in quei percorsi eccezionali.

I distruttori non sono più nel regno della convenienza per aiutare i programmatori a evitare di dimenticare di liberare / liberare risorse a quel punto. RAII diventa un requisito pratico perché non è umanamente possibile anticipare ogni singola riga di codice che può essere lanciata in esempi non banali (per non parlare del fatto che quelle linee potrebbero non essere lanciate ora ma potrebbero in seguito con modifiche). Prendi un altro esempio:

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

Tale codice, sebbene generalmente innocuo in C, è come il caos infernale che regna in C ++, perché il memcpybulldoz su bit e byte di questi oggetti e ignora cose come i costruttori di copie. Tali funzioni gradiscono memset, realloc, memcpy, ecc, mentre gli strumenti giornalieri tra gli sviluppatori C usati per guardare le cose in modo piuttosto omogeneo di bit e byte di memoria, non sono in armonia con il più complesso e più ricco sistema di tipo C ++. Il C ++ incoraggia una visione molto più astratta dei tipi definiti dall'utente.

Quindi questi tipi di cose non consentono più al C ++, da parte di chiunque cerchi di usarlo correttamente, di considerarlo come un semplice "superset" di C. Questi linguaggi richiedono una mentalità, una disciplina e un modo di pensare molto diversi per usare nel modo più efficace .

Non sono nel campo che vede C ++ come decisamente migliore in ogni modo, e in realtà la maggior parte delle mie librerie di terze parti preferite sono librerie C per qualche motivo. Non so perché esattamente, ma le librerie C tendono ad essere di natura più minimalista (forse perché l'assenza di un sistema di tipo così ricco rende gli sviluppatori più concentrati nel fornire le funzionalità minime richieste senza costruire un insieme ampio e stratificato di astrazioni), anche se spesso finisco semplicemente per mettere i wrapper C ++ attorno a loro per semplificare e personalizzare il loro utilizzo per i miei scopi, ma quella natura minimalista è preferibile per me anche quando lo faccio. Adoro il minimalismo come una caratteristica interessante di una biblioteca per coloro che si prendono il tempo extra per cercare tali qualità, e forse C tende a incoraggiarlo,

Preferisco il C ++ molto più spesso, ma in realtà mi viene richiesto di utilizzare le API C piuttosto spesso per la più ampia compatibilità binaria (e per le FFI), anche se spesso le implemento in C ++ nonostante l'uso di C per le intestazioni. Ma a volte quando si passa a un livello molto basso, come il livello di un allocatore di memoria o una struttura di dati di livello molto basso (e sono sicuro che ci sono ulteriori esempi tra coloro che eseguono la programmazione integrata), a volte può essere utile essere in grado di presumere che i tipi e i dati con cui stai lavorando siano assenti alcune funzionalità come vtables, costruttori e distruttori, in modo che possiamo trattarli come bit e byte da mescolare, copiare, liberare, riallocare. Per problemi di livello particolarmente basso, a volte può essere utile lavorare con un sistema di tipo molto più semplice fornito da C,

Un chiarimento

Un commento interessante qui ho voluto rispondere un po 'più in profondità (trovo che i commenti qui siano così severi sul limite di caratteri):

memcpy(&f2, f1, sizeof f2); è anche "il caos infernale che regna" in C se Foo ha qualche indicatore proprietario, o è anche peggio, poiché mancano anche gli strumenti per affrontarlo.

Questo è un punto giusto, ma tutto ciò su cui mi sto concentrando è principalmente focalizzato sul sistema di tipi di C ++ e anche rispetto a RAII. Uno dei motivi per cui la copia a byte di raggi X memcpyo qsorttipi di funzioni rappresentano un pericolo pratico in C è che la distruzione di f1e f2sopra è esplicita (se hanno bisogno anche di una distruzione non banale), mentre quando i distruttori entrano in scena , diventano impliciti e automatizzati (spesso con un grande valore per gli sviluppatori). Questo non vuol dire nemmeno lo stato nascosto come vptrs e così via che tali funzioni dovrebbero abbattere proprio sopra. Se f1possiede puntatori ef2shallow li copia in un contesto temporaneo, quindi non crea problemi se non proviamo a liberare esplicitamente quei puntatori proprietari una seconda volta. Con C ++ questo è qualcosa che il compilatore vorrà fare automaticamente.

E questo diventa più grande se in genere in C, " Se Foo ha i propri puntatori", perché l'esplicitazione richiesta con la gestione delle risorse renderà spesso qualcosa che in genere è più difficile da trascurare mentre in C ++, possiamo fare un UDT non più banalmente costruibile / distruttibile semplicemente facendo in modo che memorizzi qualsiasi variabile membro che non sia banalmente costruibile / distruttibile (in un modo che è generalmente molto utile, ancora una volta, ma non se siamo tentati di usare funzioni come memcpyo realloc).

Il mio punto principale non è quello di cercare di argomentare alcun beneficio di questa esplicitazione (direi che se ce ne sono, sono quasi sempre appesantiti dai contro della maggiore probabilità di errore umano che ne deriva), ma semplicemente per dire che funziona come memcpye memmovee qsorte memsete erealloce così via non hanno spazio in un linguaggio con UDT ricchi di caratteristiche e capacità come C ++. Sebbene esistano a prescindere, penso che non sarebbe troppo contestato affermare che la stragrande maggioranza degli sviluppatori di C ++ sarebbe saggia per evitare tali funzioni come la peste, mentre queste sono una specie di funzioni quotidiane in C, e io ' d sostengono che pongono meno problemi in C per la semplice ragione che il suo sistema di tipi è molto più semplice e, forse, "più stupido". La radiografia dei tipi C e il loro trattamento come bit e byte è soggetto a errori. Farlo in C ++ è senza dubbio del tutto errato perché tali funzioni stanno combattendo contro caratteristiche fondamentali del linguaggio e ciò che incoraggia il sistema dei tipi.

Questo è in realtà il più grande appello a me di C, tuttavia, in particolare con il modo in cui si riferisce all'interoperabilità del linguaggio. Sarebbe molto, molto più difficile far capire a qualcosa come FFI di C # il sistema di tipo completo e le caratteristiche del linguaggio di C ++ fino a costruttori, distruttori, eccezioni, funzioni virtuali, sovraccarico di funzione / metodo, sovraccarico dell'operatore, tutti i vari tipi di ereditarietà, ecc. Con C è un linguaggio relativamente più stupido che è diventato piuttosto standard per quanto riguarda le API in modi che molte lingue diverse possono importare direttamente tramite FFI o indirettamente tramite alcune funzioni di esportazione dell'API C nella forma desiderata (es: Java Native Interface ). Ed è qui che per la maggior parte mi rimane solo la possibilità di usare C, dal momento che l'interoperabilità del linguaggio è un requisito pratico nel nostro caso (anche se spesso

Ma sai, sono un pragmatico (o almeno mi sforzo di esserlo). Se C fosse il linguaggio più disgustoso e sfrontato, soggetto a errori e brutto che alcuni dei miei coetanei appassionati di C ++ sostenevano che fosse (e mi considererei un appassionato di C ++ solo che in qualche modo non ha portato a un odio di C da parte mia ; al contrario, ha avuto l'effetto opposto su di me di farmi apprezzare meglio entrambe le lingue nei loro aspetti e differenze), quindi mi aspetto che si presenti nel mondo reale sotto forma di alcuni dei più attivi e leali e prodotti e biblioteche inaffidabili scritti in C. E non lo trovo. Mi piace Linux, mi piace Apache, Lua, zlib, trovo OpenGL tollerabile per la sua lunga eredità contro tali requisiti hardware mutevoli, Gimp, libpng, Cairo, ecc. Almeno qualunque ostacolo la lingua sembri non sembra rappresentare un problema per quanto riguarda la scrittura di biblioteche e prodotti interessanti in mani competenti, ed è davvero tutto ciò a cui sono interessato. Quindi non sono mai stato il tipo così interessato ai più appassionati guerre linguistiche, tranne per fare un appello pragmatico e dire: "Ehi, ci sono cose interessanti là fuori! Impariamo come ce l'hanno fatta e forse ci sono lezioni interessanti, non così specifiche per la natura idiomatica della lingua, che possiamo riportare indietro in qualsiasi lingua (e) che stiamo usando. " :-D


2
memcpy(&f2, f1, sizeof f2);è anche "il caos infernale che regna" in C se Fooha qualche indicatore proprietario, o è anche peggio, poiché mancano anche gli strumenti per affrontarlo. Quindi le persone che scrivono in C non fanno cose del genere
Caleth,

@Caleth Una delle cose che semplifica il fatto che anche con i puntatori proprietari sia la liberazione esplicita dei puntatori proprietari, quindi in questi casi si trasforma effettivamente in una copia superficiale con un solo posto che libera la memoria originale, ad esempio (in alcuni casi, a seconda di il design). Considerando che con il coinvolgimento dei distruttori, entrambe le Fooistanze potrebbero ora voler distruggere l'array dinamico, o il vettore, o qualcosa in tal senso. Trovo spesso, per alcuni casi particolari, che sia utile poter scrivere determinate strutture di dati per appoggiarsi al fatto che la distruzione sia esplicita piuttosto che implicita.
Dragon Energy il

@Caleth È certamente una cosa da pigrizia da parte mia dal momento che se Fooha dei puntatori proprietari, potremmo dargli una copia corretta / spostare ctor, dtor, usare la semantica del valore, ecc. E avere un codice molto più ricco e sicuro. Ma a volte mi ritrovo a cercare C nei casi in cui voglio generalizzare una struttura di dati o un allocatore a livello omogeneo di bit e byte, con alcune ipotesi che posso fare sui tipi che tendono, nei loro casi d'uso ristretti, a essere semplificati un po 'da tali ipotesi mi sento un po' più sicuro di fare in C.
Dragon Energy il

@Caleth Nel mio caso, tuttavia, a volte c'è qualche pietra angolare dell'architettura in cui non trovo utile guardare le cose come non più di bit e byte in memoria per organizzare e accedere (e in genere quei casi non coinvolgono altro che POD). Questi sono i pochi casi particolari rimasti in cui preferisco ancora C. Se immagini l'allocatore di memoria, non c'è davvero niente di più su cui lavorare se non bit e byte, puntatori vuoti, cose di questo tipo, con tutta l'attenzione per l'allineamento e il pooling e distribuendo bit e byte, e in quei casi molto particolari trovo che C offra meno ostacoli di C ++.
Dragon Energy il

L'altro caso in cui talvolta raggiungo C sono casi in cui il codice potrebbe effettivamente diventare più generalizzato e riutilizzabile essendo meno astratto e focalizzandosi sui primitivi, come API void filter_image(byte* pixels, int w, int h);un semplice esempio anziché un simile API void filter_image(ImageInterface& img);(che accoppierebbe il nostro codice a tale interfaccia di immagine, restringendo la sua applicabilità). In questi casi a volte implemento tali funzioni in C poiché c'è poco da guadagnare in C ++ e riduce la probabilità che tale codice abbia bisogno di cambiamenti futuri.
Dragon Energy
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.