OOP dili ile önyargılı olduktan sonra bir C programcısı olarak nasıl düşünülür? [kapalı]


38

Önceden, yalnızca Nesne Yönelimli Programlama dillerini kullandım (C ++, Ruby, Python, PHP) ve şimdi C öğreniyorum. Konsepti olmayan bir dilde işleri yapmanın doğru yolunu bulmakta zorlanıyorum. 'Nesne, cisim'. C'deki OOP paradigmalarını kullanmanın mümkün olduğunu anladım, ancak C-deyimsel yolu öğrenmek istiyorum.

Bir programlama problemini çözerken, ilk yaptığım problemi çözecek bir nesne hayal etmektir. OOP olmayan bir Zorunlu Programlama paradigması kullanırken bununla hangi adımları değiştiririm?


15
Düşünce tarzımla yakından eşleşen bir dil bulamadım, bu yüzden kullandığım herhangi bir dil için düşüncelerimi “derlemeliyim”. Yararlı bulduğum bir kavram, bunun bir etiket, alt yordam, işlev, nesne, modül veya çerçeve olup olmadığı bir “kod birimi” dir: her biri enkapsüle edilmeli ve iyi tanımlanmış bir arabirim göstermelidir. Yukarıdan aşağıya nesne seviyesindeki bir yaklaşımdan faydalanıyorsanız, C'de, problem çözülmüş gibi davranan bir dizi işlev çizerek başlayabilirsiniz. Genellikle, iyi tasarlanmış C API'leri OOP'a benzer, ancak qux = foo.bar(baz)olur qux = Foo_bar(foo, baz).
15.00

Yankı için amon aşağıdakilerden odaklanan: Grafik-gibi bir kod veri yapısı, işaretçiler, algoritma, uygulama (kontrol akış) (fonksiyonlar), işlev işaretçileri.
rwong

1
LibTiff (github kaynak kodu) büyük C programlarının nasıl düzenlendiğine bir örnektir.
rwong

1
Bir C # programcısı olarak delegeleri (bir sınır parametresi olan işlev göstergeleri) nesneleri kaçırdığımdan çok daha fazla özlüyorum.
CodesInChaos

Şahsen C işlemcisinin kayda değer istisnası dışında çoğu C'yi kolay ve anlaşılır buldum. C'yi tekrar öğrenmek zorunda olsaydım, bu çabalarımın çoğuna konsantre olacağım bir alan olurdu.
biziclop

Yanıtlar:


53
  • AC programı, fonksiyonların bir koleksiyonudur.
  • Bir işlev ifadeler topluluğudur.
  • Verileri a ile kapsülleyebilirsiniz struct.

Bu kadar.

Nasıl bir sınıf yazdın? Bir .C dosyasını nasıl yazdığınız çok fazla. Verilen, yöntem polimorfizmi ve kalıtım gibi şeyleri alamazsınız, ancak yine de farklı fonksiyon isimleri ve kompozisyonları olanları simüle edebilirsiniz .

Yolu açmak için, İşlevsel Programlamayı inceleyin. Dersler olmadan yapabilecekleriniz gerçekten oldukça şaşırtıcı ve bazı şeyler aslında derslerin ek yükü olmadan daha iyi çalışıyor.

ANSI C’de Daha Fazla Okuma
Nesnesi-Oryantasyonu


9
aşağıdakileri de yapabilirsiniz typedefo structve makyaj şey sınıfı benzeri . ve typedef-ed türleri, structkendi typedef-ed olabilen başka lara dahil edilebilir . C ile elde edemediğiniz şey operatör aşırı yüklemesi ve yüzeysel olarak basit sınıfların ve içindeki C ++ 'daki üyelerin kalıtımıdır. ve C ++ ile aldığınız çok garip ve doğal olmayan sözdizimi elde edemezsiniz. OOP kavramını gerçekten çok seviyorum, ama C ++ 'nın OOP' nin çirkin bir realizasyonu olduğunu düşünüyorum. C gibi ben çünkü olduğunu iyi fonksiyonlara bırakılır dilden sözdizimi dışarı küçük dil ve yaprakları.
robert bristow-johnson

22
Anadili C olan biri olarak, bunu söylemeye teşebbüs ediyorum . a lot of things actually work better without the overhead of classes
haneefmubarak

1
Genişletmek için, OOP olmadan birçok şey geliştirilmiştir: işletim sistemleri, protokol sunucuları, önyükleyiciler, tarayıcılar vb. Bilgisayarlar nesneler olarak düşünmez ve ne de ihtiyaçları yoktur. Gerçekten de, bunu zorlamaları genellikle oldukça yavaştır .
edmz

Konturpuan: a lot of things actually work better with addition of class-based OOP. Kaynak: TypeScript, Dart, CoffeeScript ve endüstrinin işlevsel / prototip bir OOP dilinden uzaklaşmaya çalıştığı diğer tüm yollar.
Den

Genişlemek için, OOP ile bir çok şey geliştirildi : her şey. İnsanlar doğal olarak nesneler açısından düşünürler ve programlar diğer insanların okuması ve anlaması için yazılır.
Den

18

SICP'yi okuyun ve Şema ve soyut veri türleri hakkında pratik fikir edinin . O zaman C'ye kodlama kolaydır (SICP, biraz C ve biraz PHP, Ruby, vb.) Çünkü düşünceleriniz yeterince genişleyecektir ve nesne yönelimli programlamanın en iyi stil olmayabileceğini anlarsınız. tüm davalar, ancak sadece bazı programlar için). Muhtemelen en zor kısım olan C dinamik bellek ayırma konusunda dikkatli olun . C99 veya C11 programlama dili standart ve C standart kütüphanesi (o! TCP veya dizinleri bilmediği) ve aslında oldukça zayıf sık sık bazı dış kütüphaneleri gerekecektir veya arabirimleri (örnPOSIX , libcurl HTTP istemci kütüphanesi için, libonion HTTP sunucusu kütüphane için, GMPlib bignums için, gibi bazı kütüphane libunistring ) ... vb UTF-8 için.

"Nesnelerin" çoğu kez C ile ilişkilidir structve üzerinde çalışan fonksiyonlar kümesini tanımlarsınız. Kısa ya da çok basit işlevler için, bunları başka yerde -d'de kullanılacak bazı başlık dosyalarında olduğu structgibi , ilgili olarak tanımlamayı düşünün .static inlinefoo.h#include

Nesne yönelimli programlamanın tek programlama paradigması olmadığına dikkat edin . Bazı durumlarda ise, diğer paradigmalar değerli (olan fonksiyonel programlama la ocaml veya Haskell hatta Programı veya commmon Lisp, à mantık programlama vs vs la Prolog à ... ayrıca Oku J.Pitrat blog bildirim yapay zeka konusunda). Scott'ın kitabına bakınız: Programlama Dili Pragmatik

Aslında, C veya Ocaml'daki bir programcı genellikle nesne yönelimli programlama tarzında kodlama yapmak istemez. İşe yaramazsa nesneleri düşünmeye zorlamak için hiçbir neden yoktur.

Bazılarını structve üzerinde çalışan fonksiyonları tanımlayacaksınız (genellikle işaretçilerden). Bazı etiketli sendikalara gereksinim duyabilirsiniz (genellikle, bir structetiket üyeli, çoğu bazıları enumve bazıları unioniçeride) ve bazılarınızın sonunda esnek bir dizi üyesine sahip olmakta yararlı olabilirsiniz struct.

Bazı mevcut kaynak koduna içine bak özgür yazılım içinde C (bkz github & sourceforge bazı bulmak için). Muhtemelen, bir Linux dağıtımını kurmak ve kullanmak faydalı olacaktır: hemen hemen sadece özgür yazılımdan yapılmış, harika özgür yazılım C derleyicileri ( GCC , Clang / LLVM ) ve geliştirme araçlarına sahip. Ayrıca Linux için geliştirmek istiyorsanız İleri Linux Programcılığına bakınız .

Örneğin tüm uyarı ve hata ayıklama bilgi ile derlemeye unutmayın gcc -Wall -Wextra -g-notably geliştirme ve hata ayıklama bölecek sırasında ve bazı araçlar, örneğin kullanmayı öğrenmek valgrind avı için hafıza sızıntıları , gdbözen vb ayıklayıcıya, bir de ne olduğunu anlamak tanımsız Davranış ve şiddetle kaçının (bir programın bazı UB'lere sahip olabileceğini ve bazen "çalışıyor" göründüğünü unutmayın).

Nesneye yönelik yapılara (özellikle kalıtımsal ) gerçekten ihtiyaç duyduğunuzda , ilgili yapılar ve işlevler için işaretçileri kullanabilirsiniz. Kendi kararsız makinenize sahip olabilirsiniz , her bir "nesne" ye sahip bir işaretçi ile başlayan bir işaretçilere sahip olabilirsiniz struct. Bir işaretçi tipini başka bir işaretçi tipine çevirme kabiliyetinden (ve kalıtım taklit etmeye struct super_stbaşlayanlarla aynı alan tiplerini içeren bir içerikten kullanabilme gerçeğinden struct sub_st) faydalanırsınız. GObject'in (GTK / Gnome'dan) gösterdiği gibi, özellikle bazı kuralları izleyerek oldukça karmaşık nesne sistemlerini uygulamak için C'nin yeterli olduğuna dikkat edin .

Eğer gerçekten gerektiğinde kapanışları , sen edeceğiz genellikle onları taklit geri aramaları ile, kongre (o çağıran işlev işaretçisi tarafından tüketilen) bir geri arama kullanarak her işlevi bir işlev işaretçisi ve bazı müşteri verilerini hem geçirilir söyledi. Ayrıca (geleneksel olarak) kendi kapanış-benzeri- structlerinize (bazı fonksiyon göstergeleri ve kapalı değerler içeren) sahip olabilirsiniz.

C çok düşük seviyeli bir dil olduğundan, kendi sözleşmelerinizi (diğer C programlarındaki uygulamalardan ilham alarak), özellikle bellek yönetimi ve muhtemelen bazı adlandırma kurallarıyla ilgili tanımlamak ve belgelemek önemlidir . Komut kümesi mimarisi hakkında biraz fikir sahibi olmak faydalıdır . Bir C derleyicisinin kodunuzda birçok optimizasyon yapabileceğini unutmayın (isterseniz), bu nedenle el ile mikro optimizasyonlar yapmaya çok fazla önem vermeyin, bunu derleyicinize bırakın ( gcc -Wall -O2sürümün optimize edilmiş bir şekilde derlenmesi için) yazılım). Kıyaslama ve ham performansla ilgileniyorsanız, optimizasyonları etkinleştirmelisiniz (programınız hata ayıklandıktan sonra).

Unutmayın ki bazen metaprogramming faydalı olabilir . Oldukça sık, C ile yazılmış büyük yazılım, başka yerlerde kullanılan bazı C kodlarını oluşturmak için bazı komut dosyaları veya geçici programlar içerir (ayrıca bazı kirli C ön işlem püf noktalarını da oynayabilirsiniz , örneğin X makroları ). Bazı yararlı C program jeneratörleri bulunmaktadır (örneğin , ayrıştırıcı üretmek için yacc veya gnu bizonu , mükemmel hash fonksiyonları üretmek için gperf , vb ...). Bazı sistemlerde (özellikle Linux ve POSIX) generated-001.cdosya zamanında çalışma sırasında bazı C kodları bile oluşturabilir, çalışma zamanında bir komut (gibi gcc -O -Wall -shared -fPIC generated-001.c -o generated-001.so) çalıştırarak onu paylaşılan bir nesneye derleyebilir , dlopen kullanarak bu paylaşılan nesneyi dinamik olarak yükleyebilirsiniz.& dlsym kullanarak bir ismin fonksiyon göstergesini alın . MELT'de ( GCC derleyicisinin özelleştirilmesini sağladığından sizin için yararlı olabilecek Lisp benzeri bir alana özgü dil) bu tür numaralar yapıyorum .

Çöp toplama konseptleri ve tekniklerinin farkında olun ( referans sayma , genellikle C'deki belleği yönetmek için kullanılan bir tekniktir ve IMHO, dairesel referanslarla iyi ilgilenmeyen zayıf bir çöp toplama biçimidir ; bu konuda yardımcı olabilecek zayıf işaretçilere sahip olabilirsiniz. ama zor olabilir). Bazı durumlarda, Boehm'in muhafazakar çöp toplayıcısını kullanmayı düşünebilirsiniz .


7
Açıkçası, bu sorudan bağımsız olarak, SICP'yi okumak şüphesiz iyi bir tavsiyedir, ancak OP için bu muhtemelen “SICP ile önyargılı olduktan sonra bir C programcısı olarak nasıl düşünülür” sorusuna yol açacaktır.
Doktor Brown,

1
Hayır, çünkü SICP ve PHP'den (ya da Ruby ya da Python'dan) gelen Şema, OP'nin daha geniş bir düşünce yapacağı kadar farklıdır; ve SICP, pratikte soyut veri tipinin ne olduğunu oldukça iyi açıklıyor ve bu, özellikle de C'yi kodlamak için anlaşılması çok yararlı
Basile Starynkevitch

1
SICP tuhaf bir öneri. Şema C'den çok farklıdır.
Brian Gordon

Fakat SICP birçok iyi alışkanlık öğretiyor ve
Scheme'nin

5

Programın inşa edilme şekli temel olarak problemi çözmek için hangi işlemlerin (fonksiyonların) yapılması gerektiğini (bu nedenle prosedür dili olarak adlandırılır) tanımlamaktadır. Her eylem bir işleve karşılık gelir. O zaman, her bir fonksiyonun ne tür bilgiler alacağını ve hangi bilgileri geri vermeleri gerektiğini tanımlamanız gerekir.

Program normalde dosyalara (modüllere) ayrılır, her dosya normalde ilgili bir fonksiyon grubuna sahip olacaktır. Her dosyanın başında, o dosyadaki tüm fonksiyonlar tarafından kullanılacak (herhangi bir fonksiyonun dışında) değişkenleri beyan ediyorsunuz. Eğer "statik" niteleyiciyi kullanırsanız, bu değişkenler sadece o dosyanın içinde görünür (ama diğer dosyalardan değil). İşlevlerin dışında tanımlanan değişkenlerde "statik" niteleyicisini kullanmazsanız, diğer dosyalardan da erişilebilir olacaklar ve bu diğer dosyalar değişkeni "extern" (açıklayıcı) olarak bildirmeli (ancak tanımlamamalıdır), böylece derleyici bunları arayacaktır diğer dosyalarda.

Kısacası, önce prosedürleri (fonksiyonlar) düşünür, sonra tüm fonksiyonların ihtiyaç duydukları bilgiye ulaşmasını sağlar.


3

C API'leri sık sık - belki de genellikle - genellikle onlara doğru şekilde bakarsanız temelde nesne yönelimli bir arayüze sahiptir.

C ++ dilinde:

class foo {
    public:
        foo (int x);
        void bar (int param);
    private:
        int x;
};

// Example use:
foo f(42);
f.bar(23);

C’de:

typedef struct {
    int x;
} foo;

void bar (foo*, int param);

// Example use:
foo f = { .x = 42 };
bar(&f, 23);

Bildiğiniz gibi, C ++ ve diğer çeşitli resmi OO dillerinde, başlık altında bir nesne yöntemi, bar()yukarıdaki C sürümünde olduğu gibi, nesneye bir işaretçi olan ilk argümanı alır . Bunun C ++ 'da yüzeye nereden geldiğinin bir örneği için, std::bindimzaları işlemek için nesne yöntemlerine nasıl uyduğunu düşünün :

new function<void(int)> (
    bind(&foo::bar, this, placeholders::_1)
//                  ^^^^ object pointer as first arg
);

Diğerlerinin de belirttiği gibi, gerçek fark, resmi OO dillerinin polimorfizm, erişim kontrolü ve diğer çeşitli şık özellikleri uygulayabilmesidir. Ancak, nesne yönelimli programlamanın özü, ayrık, karmaşık veri yapılarının yaratılması ve manipülasyonu, C’de temel bir uygulamadır.


2

İnsanların C öğrenmeye teşvik edilmesinin en büyük nedenlerinden biri, üst seviye programlama dillerinin en düşüklerinden biri olmasıdır. OOP dilleri veri modelleri ve şablon kod ve mesaj geçişi hakkında düşünmeyi kolaylaştırır, ancak günün sonunda bir mikroişlemci kodu adım adım yürütür, kod bloklarına girip çıkar (C'deki işlevler) ve hareket eder programın farklı bölümlerinin verileri paylaşabilmesi için değişkenlere (C'deki işaretçiler) atıfta bulunma. C'yi İngilizce dilinde bir montaj dili olarak düşünün - bilgisayarınızın mikro işlemcisine adım adım talimatlar verin - ve çok yanlış gitmezsiniz. Bir bonus olarak, çoğu işletim sistemi arayüzleri, OOP paradigmalarından ziyade C işlevi çağrıları gibi çalışır,


2
IMHO C, düşük seviye bir dildir, ancak C derleyicisi birçok düşük seviye optimizasyonu yapabileceğinden montajcı veya makine kodundan çok daha yüksektir.
Basile Starynkevitch

C derleyicileri ayrıca, "optimizasyon" adına, makinedeki kodun doğal davranışı olsa bile, Tanımsız Davranışa neden olabilecek girdi verildiğinde zaman ve nedensellik yasalarını ihmal edebilecek soyut bir makine modeline doğru ilerlemektedir. çalıştırılması aksi takdirde gereksinimleri karşılayacaktı. Örneğin, işlev 16 bit veya 33 bit veya daha büyük uint16_t blah(uint16_t x) {return x*x;}olan makinelerde aynı şekilde çalışacaktır unsigned int. unsigned intBununla birlikte, 17 ila 32 bit olan makinelerin bazı derleyicileri , bu yönteme yapılan bir çağrıyı dikkate alabilir ...
supercat

... derleyiciye, yöntemin 46340'ı aşan bir değer verilmesine neden olacak hiçbir olay zincirinin gerçekleşemeyeceği sonucuna varmasına izin verilmesi gibi. Her ne kadar herhangi bir platformda 65533u * 65533u'yu çarpmak, döküm yapıldığında 9'u verecek olan bir değer verecek olsa da uint16_t, Standart, uint16_t17 - 32 bitlik platformlarda türlerin değerlerini çarparken bu tür davranışları zorunlu kılmaz .
supercat

-1

Ben de bazen bir C dünyasında hayatta kalmak zorunda olan OO yerlisi (genellikle C ++) Ben temelde en büyük engel hata yönetimi ve kaynak yönetimi ile uğraşmaktır.

C ++ 'da en üst seviyeye geri döndüğü bir hatayı üstesinden gelebileceğimiz bir hata iletmek için attık ve hafızamızı ve diğer kaynakları otomatik olarak serbest bırakmak için yıkıcılarımız var.

Pek çok C API'sinin, size yapıya gerçekten bir işaretçi olan typedef'd void * sağlayan bir init işlevi içerdiğini fark edebilirsiniz. Sonra bunu her API çağrısı için ilk argüman olarak iletirsiniz. Esasen bu, C ++ 'dan "this" imleciniz olur. Gizlenen tüm dahili veri yapıları için kullanılır (çok OO konsepti). Belleği yönetmek için de kullanabilirsiniz; örneğin, belleğinizi mallocs yapan ve bu işaretçinin C sürümündeki malloc'u kaydeden myapiMalloc adlı bir işleve sahip olursunuz, böylece API'niz döndüğünde serbest kaldığından emin olabilirsiniz. Ayrıca son zamanlarda keşfettiğim gibi, hata kodlarını saklamak için kullanabilirsiniz ve yakalamada size çok benzer davranışlar vermek için setjmp ve longjmp kullanabilirsiniz. Her iki kavramı birleştirmek size bir C ++ programının işlevselliğini sağlar.

Şimdi C'yi C ++ 'a zorlamayı öğrenmek istemediğini söylemiştin. Bu gerçekten tarif ettiğim şey değil (en azından kasten değil). Bu, C işlevselliğinden yararlanmak için (umarım) iyi tasarlanmış bir yöntemdir. Bazı OO tatlarına sahip olduğu ortaya çıkıyor - belki de bu nedenle OO dilleri geliştirildi, bazı insanların en iyi uygulama olarak buldukları kavramları biçimlendirmenin / uygulamanın / kolaylaştırmanın bir yoluydu.

Bunun sizin için hissetmesi OO olduğunu düşünüyorsanız, alternatif hemen hemen her fonksiyonun, her fonksiyon çağrısından sonra kontrol etmeniz ve çağrı yığınını yaymanız için dini olarak güvence altına almanız gereken bir hata kodu döndürmesidir. Tüm kaynakların yalnızca her fonksiyonun sonunda değil, her dönüş noktasında da serbest bırakıldığından emin olmalısınız (potansiyel olarak devam edemeyeceğinizi belirten bir hata döndürebilecek herhangi bir işlev çağrısından sonra olabilir). Çok sıkıcı olabilir ve muhtemelen bu olası bellek ayırma arızası (veya dosya okuma ya da bağlantı noktası bağlantısı ...) ile uğraşmaya ihtiyacım olmadığını düşünerek size yol açma eğilimindedir. Şimdi "ilginç" kodunu yazacağım ve geri dönüp hata işlemesiyle başa çıkacağız - bu asla gerçekleşmez.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.