Czym C różni się od C ++?

21

Wiele osób powiedziało, że C ++ jest zupełnie innym językiem niż C, ale sam Bjarne powiedział, że C ++ jest językiem rozszerzonym od C, stąd też ++pochodzi. Dlaczego więc wszyscy mówią, że C i C ++ to zupełnie inne języki? W jaki sposób C różni się od C ++ poza rozszerzonymi funkcjami w C ++?

Joshua Partogi
źródło
3
Ze względu na sposób ich użycia. Na pewno możesz napisać C w C ++ ... ale nie powinieneś.
Ed S.

Odpowiedzi:

18

W latach 80., gdy dopiero zaczynało się tworzenie C ++, C ++ był prawie właściwym nadzbiorem C. Tak to się wszystko zaczęło.
Jednak z biegiem czasu zarówno C, jak i C ++ ewoluowały i odbiegały od siebie, mimo że zgodność między językami zawsze była uważana za ważną.

Ponadto różnice techniczne między C i C ++ sprawiły, że typowe idiomy w tych językach są jeszcze bardziej rozbieżne w tym, co uważa się za „dobrą praktykę”.

Jest to czynnik napędzający ludzi mówiących na przykład: „nie ma takiego języka jak C / C ++” lub „C i C ++ to dwa różne języki”. Chociaż możliwe jest pisanie programów, które są akceptowalne zarówno dla kompilatora C, jak i C ++, kod ogólnie nie jest uważany za przykład dobrego kodu C ani dobrego kodu C ++.

Bart van Ingen Schenau
źródło
Nie sądzę, aby pierwszy akapit był poprawny. Wierzę, że C zawsze miało domyślny rzut, void *ale C ++ nigdy nie miał. (Nie przegłosował)
alternatywny
5
@mathepic: Zdefiniuj „zawsze”. C wziął void * type od C ++. W K&R C malloc wracał char *
Nemanja Trifunovic
19

Sam Stroustrup odpowiada na to w swoim FAQ :

C ++ jest bezpośrednim potomkiem C, który zachowuje prawie cały C jako podzbiór. C ++ zapewnia silniejsze sprawdzanie typów niż C i bezpośrednio obsługuje szerszy zakres stylów programowania niż C. C ++ jest „lepszym C” w tym sensie, że obsługuje style programowania wykonywane przy użyciu C z lepszym sprawdzaniem typów i większym wsparciem notacyjnym (bez strat wydajności). W tym samym sensie ANSI C jest lepszym C niż K&R C. Ponadto C ++ obsługuje abstrakcję danych, programowanie obiektowe i programowanie ogólne.

To wsparcie dla programowania obiektowego i programowania generycznego, że make C ++ „zupełnie inna” do C. Ty może prawie napisać czystym C, a następnie skompilować go z C ++ (tak długo, jak dbać o typie kontroli dosłownym). Ale nadal piszesz C - nie piszesz C ++.

Jeśli piszesz w C ++, to korzystasz z funkcji obiektowych i szablonów, a to nie przypomina tego, co zobaczysz w C.

Dean Harding
źródło
„to nic takiego, jak w C.” To zdanie brzmi zabawnie! „patrz w C”. C in C! To jest rodzaj poezji.
Galaxy
13

Mówiąc prosto, to, co jest uważane za idiomatyczne w C, zdecydowanie nie jest idiomatyczne w C ++.

C i C ++ są w praktyce bardzo różnymi językami, ze względu na sposób ich używania. C dąży do minimalizmu, gdzie C ++ jest bardzo złożonym językiem, z wieloma funkcjami.

Istnieją także pewne praktyczne różnice: C można łatwo wywołać z dowolnego języka i często definiuje ABI platformy, podczas gdy C ++ jest dość trudny w użyciu z innych bibliotek. Większość języków ma interfejs FFI lub interfejs w C, nawet języki zaimplementowane w C ++ (na przykład java).

David Cournapeau
źródło
4
Nie chodzi tylko o to, że kod w stylu C nie jest idiomatyczny w C ++. Kodowanie w stylu C jest w rzeczywistości problematyczne w C ++ ze względu na brak bezpieczeństwa wyjątków.
dan04
4

Oprócz oczywistego faktu, że C ++ obsługuje programowanie obiektowe, myślę, że masz tutaj swoją odpowiedź: http://en.wikipedia.org/wiki/Compatibility_of_C_and_C++

Ten artykuł zawiera przykłady kodu pokazujące rzeczy, które są w porządku w C, ale nie w C ++. Na przykład:

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

Przeniesienie programu C do C ++ jest często proste i polega głównie na naprawie błędów kompilacji (dodawanie rzutowań, nowych słów kluczowych itp.).

Martin Wickman
źródło
4
Przeniesienie takiego programu nie daje programu w C ++. Daje ci C, które można skompilować na kompilatorze C ++. To nie oznacza, że ​​jest to C ++ (nadal nazwałbym wynikowy kod C (nawet C z klasami)).
Martin York
@MartinYork Nazwij to złym C, zaznacz, że semantyka może być inna i zgadzam się. Rezultatem jest oczywiście C.
Deduplicator,
2

C ++ dodaje nie tylko nowe funkcje, ale także nowe koncepcje i nowe idiomy do C. Mimo że C ++ i C są ze sobą blisko powiązane, faktem jest, że aby pisać skutecznie w języku, musisz myśleć w stylu tego języka. Nawet najlepszy kod C nie może korzystać z różnych mocnych stron i idiomów języka C ++, dlatego jest bardziej prawdopodobne niż zły kod C ++.

Jon Purdy
źródło
2

„Rozszerzone funkcje” sprawiają, że brzmi to tak, jak w C ++, które dodali, variadic macros lub coś w tym stylu. „Rozszerzone funkcje” w C ++ są kompletnym przeglądem języka i całkowicie zastępują najlepsze praktyki C, ponieważ nowe funkcje C ++ są o wiele lepsze niż oryginalne funkcje C, że oryginalne funkcje C są całkowicie i całkowicie zbędne w zdecydowanej większości przypadków . Sugerowanie, że C ++ jedynie rozszerza C, sugeruje, że nowoczesny czołg bojowy wysuwa maślankę do celów prowadzenia wojny.

DeadMG
źródło
Czy potrafisz wymienić przykład jednej z bardziej użytecznych funkcji C ++, których C nie ma?
Dark Templar,
2
@DarkTemplar: Co powiesz na zarządzanie zasobami w trybie łatwym za pomocą RAII? Lub ładne ogólne struktury danych przy użyciu szablonów? Na początek.
DeadMG,
1

Różnica polega na tym, że w C myślisz proceduralnie, aw C ++ myślisz obiektowo. Języki są dość podobne, ale podejście jest zupełnie inne.

Mrówka
źródło
Czy C nie ma również struktur? Czy same pliki C nie są osobnymi modułami (w zasadzie obiektami)? Nie jestem pewien, jaka jest dokładna różnica między proceduralnym a zorientowanym obiektowo ...
Dark Templar,
1
Ciemno zilustrowałeś mój punkt widzenia. To nie jest ograniczenie języka, który pozwala pisać proceduralnie lub obiektowo, to etos lub sposób myślenia.
Ant
0

Podczas gdy C ++ może być super zestawem C pod względem składniowym - tzn. Dowolna konstrukcja programu C może być kompilowana przez kompilator C ++.

Jednak prawie nigdy nie piszesz programów w C ++ tak, jak zrobiłbyś to z programem C. Lista może być nieskończona lub ktoś powinien po prostu przeprowadzić więcej badań, aby przedstawić ją jako wyczerpujące raporty. Jednak zamieszczam kilka wskazówek, które powodują kluczowe różnice.

Chodzi o to, że C ++ ma następujące funkcje, których dobrzy programiści C ++ muszą używać jako najlepszych praktyk programistycznych, nawet jeśli kompilacja C jest możliwa.

Jak należy to zrobić w C ++ w stosunku do C

  1. Klasy i dziedziczenie. To najważniejsze różnice, które pozwalają na systematyczną orientację obiektową, dzięki czemu ekspresja programowania jest bardzo silna. Chyba - ten punkt nie wymaga lepszego wyjaśnienia. Jeśli jesteś w C ++ - prawie zawsze lepiej jest korzystać z klas.

  2. Prywatyzacja - klasy, a nawet struktury mają członków prywatnych. Umożliwia to enkapsulację klasy. Odpowiednikiem w C jest rzutowanie obiektu jako void * do aplikacji, aby aplikacja nie miała dostępu do zmiennych wewnętrznych. Jednak w C ++ możesz mieć elementy z klasami publicznymi i prywatnymi.

  3. Przekaż przez odniesienie. C ++ pozwala na modyfikację w oparciu o referencję, dla której wymagane są przekazywanie wskaźników. Przekazywanie przez odniesienie utrzymuje kod bardzo czysty i bardziej bezpieczny przed zagrożeniami związanymi ze wskaźnikiem. Masz równie dobrze wskaźnik w stylu C i to działa - ale jeśli jesteś w C ++, to lepiej, jeśli tylko

  4. nowe i usuń vs. malloc i za darmo. Instrukcje new () i delete () nie tylko przydziału i cofnięcia przydziału pamięci, ale także pozwalają na wykonywanie kodu w ramach funkcji destruktora w celu wywołania w łańcuchu. Jeśli używasz C ++ - w rzeczywistości BAD jest używanie malloc i za darmo.

  5. Typy IO i przeciążenie operatora Przeciążenie operatora sprawia, że ​​kod jest czytelny lub bardziej intuicyjny, jeśli jest dobrze wykonany. To samo dotyczy operatorów << i >> io. Sposobem na wykonanie tego w C byłoby użycie wskaźników funkcji - ale jest to bałagan i tylko dla zaawansowanych programistów.

  6. Używanie „łańcucha”. Char * z C działa wszędzie. Więc C i C ++ są prawie takie same. Jednakże, jeśli jesteś w C ++ - zawsze jest o wiele lepiej (i bezpieczniej) używać klas String, które oszczędzają Ci niebezpieczeństw związanych z uruchomieniem tablic, które są prawie wszystkim.

Funkcje Wciąż nie byłbym fanem w C ++ 1. Szablony - Chociaż nie używam ciężkich szablonów w wielu kodach - może okazać się bardzo wydajny dla bibliotek. Prawie nie ma odpowiednika w C. Ale w normalny dzień - szczególnie jeśli robisz matematyczne braki.

  1. Inteligentne wskaźniki - Tak, są bardzo inteligentne! I jak większość inteligentnych rzeczy - zaczynają dobrze, a później stają się nieporządne! Nie bardzo lubię używać

Rzeczy, które lubię w C i których brakuje w C ++

  1. Algorytmy polimorficzne przy użyciu wskaźników funkcji. W C, gdy uruchamiasz złożone algorytmy - czasami możesz użyć zestawu wskaźników funkcji. To czyni prawdziwy polimorfizm w potężny sposób. Kiedy jesteś w C ++ masz CAN użyciu wskaźników funkcji - ale to jest złe. Powinieneś używać tylko metod - w przeciwnym razie bądź przygotowany na bałagan. Jedyną formą polimorfizmu w klasach C ++ jest przeciążenie funkcji i operatora, ale jest to dość ograniczone.

  2. Proste wątki. Podczas tworzenia wątków były wątki - jest to dość proste i łatwe do zarządzania. Dzieje się tak, gdy trzeba tworzyć wątki, które mają być „prywatne” dla klas (aby miały dostęp do prywatnych członków). Istnieją frameworki typu boost - ale nic w podstawowym C ++.

Dipan.

Dipan Mehta
źródło
0

Niektóre funkcje językowe, nawet jeśli są dodatkami, mogą zmienić cały sposób, w jaki język musi być praktycznie używany. Jako jeden przykład rozważ ten przypadek:

lock_mutex(&mutex);

// call some functions
...

unlock_mutex(&mutex);

Jeśli powyższy kod wymagałby wywoływania funkcji zaimplementowanych w C ++, moglibyśmy mieć kłopoty, ponieważ każde z tych wywołań funkcji może rzucić i nigdy nie odblokujemy muteksu w tych wyjątkowych ścieżkach.

Niszczyciele nie są już wygodniejsze, aby pomóc programistom uniknąć zapomnienia o uwolnieniu / uwolnieniu zasobów w tym momencie. RAII staje się wymogiem praktycznym, ponieważ nie jest możliwe z ludzkiego punktu widzenia przewidywanie każdego wiersza kodu, który może generować nietrywialne przykłady (nie wspominając, że te linie nie mogą teraz rzucać, ale mogą później ze zmianami). Weź inny przykład:

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

Taki kod, choć ogólnie nieszkodliwy w C, jest jak spustoszenie piekła piekielnego w C ++, ponieważ memcpybuldożery przecinają bity i bajty tych obiektów i omijają rzeczy takie jak konstruktory kopii. Funkcje takie jak memset, realloc, memcpyitp, podczas codziennych narzędzi wśród programistów C używany do patrzenia na rzeczy w dość jednorodnej formie bitów i bajtów w pamięci, nie są harmonijne z bardziej złożonych i bogatszych systemów typu C ++. C ++ zachęca do bardziej abstrakcyjnego widzenia typów zdefiniowanych przez użytkownika.

Zatem tego typu rzeczy nie pozwalają już C ++, dla każdego, kto chce używać go poprawnie, traktować go jako zwykły „nadzbiór” C. Języki te wymagają zupełnie innego sposobu myślenia, dyscypliny i sposobu myślenia, aby używać najbardziej efektywnie .

Nie jestem w obozie, który uważa C ++ za zdecydowanie lepszy pod każdym względem, a właściwie większość moich ulubionych bibliotek stron trzecich to biblioteki C z jakiegoś powodu. Nie wiem, dlaczego dokładnie, ale biblioteki C mają z natury bardziej minimalistyczny charakter (być może dlatego, że brak tak bogatego systemu typów sprawia, że ​​programiści bardziej skupiają się na zapewnieniu minimalnej wymaganej funkcjonalności bez budowania dużego i warstwowego zestawu abstrakcji), chociaż często po prostu umieszczam wokół nich owijarki C ++, aby uprościć i dostosować ich użycie do moich celów, ale ta minimalistyczna natura jest dla mnie lepsza, nawet gdy to robię. Naprawdę uwielbiam minimalizm jako atrakcyjną cechę biblioteki dla tych, którzy poświęcają dodatkowy czas na poszukiwanie takich cech, i być może C zazwyczaj to zachęca,

Preferuję C ++ znacznie częściej niż nie, ale tak naprawdę muszę raczej używać C API dla najszerszej kompatybilności binarnej (i dla FFI), chociaż często implementuję je w C ++, mimo że używam C do nagłówków. Ale czasami, gdy wchodzisz na naprawdę niski poziom, na przykład poziom alokatora pamięci lub bardzo niskopoziomową strukturę danych (i jestem pewien, że istnieją dalsze przykłady wśród tych, którzy wykonują programowanie osadzone), czasem pomocne może być możemy założyć, że typy i dane, z którymi pracujesz, są nieobecne w niektórych funkcjach, takich jak vtables, costructors i destructors, dzięki czemu możemy traktować je jako bity i bajty, które można przetasować, skopiować, zwolnić, przenieść ponownie. W przypadku problemów szczególnie niskiego poziomu czasem pomocna może być praca z dużo prostszym systemem typów, który zapewnia C,

Wyjaśnienie

Jeden ciekawy komentarz tutaj chciałem odpowiedzieć na trochę bardziej dogłębnie (uważam, że komentarze tutaj są tak surowe w odniesieniu do limitu znaków):

memcpy(&f2, f1, sizeof f2); jest także „spustoszeniem piekielnego ognia” w C, jeśli Foo ma jakieś wskazówki, lub jest jeszcze gorszy, ponieważ brakuje ci też narzędzi, aby sobie z tym poradzić.

To słuszna kwestia, ale wszystko, na czym się skupiam, skupia się głównie na systemie typów C ++, a także w odniesieniu do RAII. Jednym z powodów, dla których takie rentgenowskie kopiowanie bajtów memcpylub qsortrodzaje funkcji stanowią mniej praktyczne zagrożenie w C, jest to, że zniszczenie f1i f2powyżej jest jawne (jeśli nawet potrzebują one nietrywialnego zniszczenia), podczas gdy gdy niszczyciele przenoszą się na obraz stają się niejawne i zautomatyzowane (często o dużej wartości dla programistów). Nie wspominając nawet o stanie ukrytym, takim jak vptrs i tak dalej, które takie funkcje zostałyby natychmiast usunięte. Jeśli f1posiada wskaźniki if2płytkie kopiuje je w jakimś tymczasowym kontekście, to nie stanowi problemu, jeśli nie spróbujemy wyraźnie uwolnić posiadaczy wskaźników po raz drugi. W przypadku C ++ kompilator będzie chciał to zrobić automatycznie.

A staje się to tym większe, że zwykle w języku C, „ Jeśli Foo ma własne wskaźniki”, ponieważ jawność wymagana przy zarządzaniu zasobami często sprawia, że ​​coś zwykle trudniej przeoczyć, podczas gdy w C ++ możemy uczynić UDT nie trywialnym konstruowalny / destrukowalny poprzez zwykłe przechowywanie w nim dowolnej zmiennej składowej, która nie jest trywialnie konstruowalna / destrukowalna (w sposób, który jest na ogół bardzo pomocny, znowu, ale nie, jeśli mamy ochotę używać funkcji takich jak memcpylub realloc).

Moim głównym celem nie jest próba argumentowania jakiejkolwiek korzyści z tej jawności (powiedziałbym, że jeśli istnieją, prawie zawsze są one obciążone wadami zwiększonego prawdopodobieństwa błędu ludzkiego, który się z tym wiąże), a jedynie stwierdzenie, że funkcje jak memcpyi memmovei qsorti memsetirealloci tak dalej nie ma miejsca w języku z UDT tak bogatym w funkcje i możliwości jak C ++. Chociaż istnieją niezależnie, myślę, że nie byłoby zbyt trudne twierdzenie, że ogromna większość programistów C ++ rozsądnie byłoby unikać takich funkcji, jak zaraza, podczas gdy są to bardzo codzienne funkcje w C, a ja ' d twierdzą, że stwarzają mniej problemów w C z tego prostego powodu, że jego system typów jest o wiele bardziej podstawowy i, być może, „głupszy”. Prześwietlanie typów C i traktowanie ich jako bitów i bajtów jest podatne na błędy. Robienie tego w C ++ jest prawdopodobnie całkowicie błędne, ponieważ takie funkcje walczą z bardzo podstawowymi cechami języka i tym, co zachęca system czcionek.

Jest to w rzeczywistości największy apel do mnie ze strony C, szczególnie w odniesieniu do tego, w jaki sposób odnosi się do interoperacyjności językowej. O wiele, wiele trudniej byłoby sprawić, aby FFI C # zrozumiał w pełni funkcjonalny system typów i funkcje językowe C ++ aż do konstruktorów, destruktorów, wyjątków, funkcji wirtualnych, przeciążenia funkcji / metody, przeciążenia operatora, wszystkich różnych typów dziedziczenie itp. W przypadku C jest to stosunkowo głupszy język, który stał się raczej standardowy, jeśli chodzi o interfejsy API, ponieważ wiele różnych języków może importować bezpośrednio przez FFI lub pośrednio przez niektóre funkcje eksportujące API C w pożądanej formie (np. Java Native Interface ). I właśnie tam w większości nie mam wyboru, jak korzystać z C, ponieważ interoperacyjność tego języka jest w naszym przypadku praktycznym wymogiem (choć często „

Ale wiesz, jestem pragmatykiem (a przynajmniej staram się być). Jeśli C byłby tym najbardziej plugawym i cholernym, podatnym na błędy, nieprzyjemnym językiem, niektórzy z moich entuzjastów C ++ twierdzili, że jest (i uważałbym się za entuzjastę C ++, z wyjątkiem tego, że jakoś nie doprowadziło to do nienawiści do C z mojej strony ; wręcz przeciwnie, wywarło na mnie odwrotny skutek, sprawiając, że lepiej doceniam oba języki pod ich względami i różnicami), wtedy spodziewałbym się, że pojawią się w realnym świecie w postaci niektórych z najbardziej wadliwych i nieszczelnych i niewiarygodne produkty i biblioteki pisane w C. I tego nie znajduję. Lubię Linuksa, lubię Apache, Lua, Zlib, uważam, że OpenGL jest tolerowany ze względu na długą tradycję w stosunku do takich zmieniających się wymagań sprzętowych, Gimp, Libpng, Kair itp. Przynajmniej jakiekolwiek przeszkody, jakie stawia język, nie wydają się stanowić impasu, jeśli chodzi o pisanie fajnych bibliotek i produktów we właściwych rękach, i to naprawdę wszystko, czym jestem zainteresowany. Więc nigdy nie byłem typem tak zainteresowanym najbardziej namiętnymi wojny językowe, z wyjątkiem pragmatycznego odwołania się i powiedzenia: „Hej, są tam fajne rzeczy! Nauczmy się, jak to zrobili i może są fajne lekcje, nie tak specyficzne dla idiomatycznej natury języka, które możemy przywrócić na dowolny używany przez nas język. ” :-RE

Dragon Energy
źródło
2
memcpy(&f2, f1, sizeof f2);jest także „spustoszeniem piekielnego ognia” w C, jeśli Fooma jakieś wskaźniki własności, lub jest jeszcze gorszy, ponieważ brakuje ci też narzędzi, aby sobie z tym poradzić. Więc ludzie piszący C nie robią takich rzeczy tak bardzo
Caleth,
@Caleth Jedną z rzeczy, które upraszczają to, że nawet przy posiadaniu wskaźników jest wyraźne uwolnienie wskaźników, więc w takich przypadkach skutecznie zamienia się w płytką kopię, a tylko jedno miejsce wciąż zwalnia oryginalną pamięć, na przykład (w niektórych przypadkach w zależności od Projektowanie). Podczas gdy z udziałem niszczycieli, obie Fooinstancje mogą teraz chcieć zniszczyć tablicę dynamiczną, wektor lub coś w tym celu. Często zdarza się, że w niektórych szczególnych przypadkach pomocne jest pisanie pewnych struktur danych, aby oprzeć się na fakcie, że zniszczenie jest jawne, a nie ukryte.
Dragon Energy
@Caleth To z pewnością lenistwo z mojej strony, ponieważ jeśli Fooma jakieś własne wskaźniki, moglibyśmy nadać mu poprawne kopiowanie / przenoszenie ctor, dtor, stosowanie semantyki wartości itd., I mieć znacznie bogatszy i bezpieczniejszy kod. Ale czasami zdarza mi się sięgać po C w przypadkach, gdy chcę uogólnić strukturę danych lub alokator na jednorodnym poziomie bitów i bajtów, z pewnymi założeniami, które mogę poczynić na temat typów, które w swoich wąskich przypadkach użycia są uproszczone trochę przy takich założeniach czuję się bardziej pewnie w C
Dragon Energy
@Caleth W moim przypadku jednak czasami jest kamień węgielny architektury, w którym nie ma sensu patrzeć na rzeczy jako na więcej niż bitów i bajtów w pamięci do aranżacji i dostępu (i zwykle te przypadki nie dotyczą niczego poza POD). To tylko kilka szczególnych przypadków, w których nadal wolę C. Jeśli wyobrażasz sobie alokator pamięci, to nie ma tak naprawdę nic więcej do roboty niż bity i bajty, puste wskaźniki, rzeczy tego rodzaju, z całym naciskiem na wyrównanie i pogrupowanie i rozdawanie bitów i bajtów, aw tych bardzo szczególnych przypadkach uważam, że C oferuje mniej przeszkód niż C ++.
Dragon Energy
Innym przypadkiem, w którym czasami sięgam po C, są przypadki, w których kod może stać się bardziej uogólniony i można go ponownie wykorzystać, ponieważ jest mniej abstrakcyjny i koncentruje się na prymitywach, jak API void filter_image(byte* pixels, int w, int h);prosty przykład w przeciwieństwie do podobnego API void filter_image(ImageInterface& img);(który łączyłby nasz kod z takim interfejsem obrazu, zawężenie jego zastosowania). W takich przypadkach czasami po prostu implementuję takie funkcje w C, ponieważ w C ++ niewiele można zyskać, a to zmniejsza prawdopodobieństwo, że taki kod będzie wymagał przyszłych zmian.
Dragon Energy,