Bagaimana berpikir sebagai seorang programmer C setelah bias dengan bahasa OOP? [Tutup]

38

Sebelumnya, saya hanya menggunakan bahasa Pemrograman Berorientasi Objek (C ++, Ruby, Python, PHP), dan sekarang saya sedang belajar C. Saya merasa sulit untuk menemukan cara yang tepat untuk melakukan sesuatu dalam suatu bahasa tanpa konsep suatu 'Obyek'. Saya menyadari bahwa mungkin untuk menggunakan paradigma OOP dalam C, tapi saya ingin belajar cara C-idiomatik.

Saat memecahkan masalah pemrograman, hal pertama yang saya lakukan adalah membayangkan sebuah objek yang akan menyelesaikan masalah. Langkah apa yang saya ganti dengan ini, ketika menggunakan paradigma Pemrograman Imperatif non-OOP?

Mas Bagol
sumber
15
Saya belum menemukan bahasa yang cocok dengan cara berpikir saya, jadi saya harus "menyusun" pikiran saya untuk bahasa apa pun yang saya gunakan. Satu konsep yang menurut saya berguna adalah "unit kode", apakah ini label, subrutin, fungsi, objek, modul, atau kerangka kerja: setiap konsep harus dienkapsulasi dan mengekspos antarmuka yang terdefinisi dengan baik. Jika Anda menggunakan pendekatan tingkat objek top-down, di C Anda mungkin mulai dengan menggambar serangkaian fungsi yang berperilaku seolah-olah masalah telah dipecahkan. Seringkali, API C yang dirancang dengan baik terlihat seperti OOP, tetapi qux = foo.bar(baz)menjadi qux = Foo_bar(foo, baz).
Amon
Untuk echo amon , fokuslah pada hal-hal berikut: struktur data seperti grafik, pointer, algoritma, eksekusi (kontrol aliran) kode (fungsi), pointer fungsi.
rwong
1
LibTiff (kode sumber di github) adalah contoh bagaimana mengatur program C yang besar.
rwong
1
Sebagai seorang programmer C # saya akan kehilangan delegasi (fungsi pointer dengan satu parameter terikat) jauh lebih banyak daripada saya akan kehilangan objek.
CodesInChaos
Saya pribadi menemukan sebagian besar C mudah dan langsung, dengan pengecualian pra-prosesor. Jika saya harus belajar ulang C, itu akan menjadi satu bidang yang saya fokuskan pada banyak usaha saya.
biziclop

Jawaban:

53
  • Program AC adalah kumpulan fungsi.
  • Fungsi adalah kumpulan pernyataan.
  • Anda dapat merangkum data dengan a struct.

Itu dia.

Bagaimana Anda menulis kelas? Itu cukup banyak bagaimana Anda menulis file .C. Memang, Anda tidak mendapatkan hal-hal seperti metode polimorfisme dan pewarisan, tetapi Anda dapat mensimulasikan mereka dengan nama fungsi dan komposisi yang berbeda pula.

Untuk membuka jalan, pelajari Pemrograman Fungsional. Sungguh menakjubkan apa yang dapat Anda lakukan tanpa kelas, dan beberapa hal benar-benar berfungsi lebih baik tanpa overhead kelas.

Orientasi Objek Bacaan Lebih Lanjut
dalam ANSI C

Robert Harvey
sumber
9
Anda juga bisa melakukan typedefitu structdan membuat sesuatu seperti kelas . dan typedeftipe -ed dapat dimasukkan ke dalam tipe-tipe lain structyang dapat digunakan sendiri typedef. apa yang tidak Anda dapatkan dengan C adalah kelebihan operator dan warisan kelas yang dangkal dan anggota di dalamnya Anda dapatkan di C ++. dan Anda tidak mendapatkan banyak sintaks aneh dan tidak wajar yang Anda dapatkan dengan C ++. Saya sangat menyukai konsep OOP, tetapi saya pikir C ++ adalah realisasi OOP yang jelek. Saya suka C karena itu adalah bahasa yang lebih kecil dan tidak menggunakan sintaks dari bahasa yang lebih baik dibiarkan berfungsi.
robert bristow-johnson
22
Sebagai seseorang yang bahasa pertamanya adalah C, saya berani mengatakan itu . a lot of things actually work better without the overhead of classes
haneefmubarak
1
Untuk memperluas, banyak hal telah dikembangkan tanpa OOP: sistem operasi, server protokol, boot loader, browser, dan sebagainya. Komputer tidak berpikir dalam hal objek dan mereka juga tidak perlu. Memang, seringkali sangat lambat bagi mereka untuk memaksakan itu.
edmz
Counterpoint: a lot of things actually work better with addition of class-based OOP. Sumber: TypeScript, Dart, CoffeeScript dan semua cara lain industri berusaha untuk menjauh dari bahasa OOP fungsional / prototipe.
Den
Untuk berkembang, banyak hal telah dikembangkan dengan OOP: segalanya. Manusia secara alami berpikir dalam hal objek dan program ditulis untuk dibaca dan dipahami manusia lain.
Den
18

Baca SICP dan pelajari Skema, dan ide praktis tipe data abstrak . Maka coding dalam C itu mudah (karena dengan SICP, sedikit C, dan sedikit PHP, Ruby, dll ... pemikiran Anda akan cukup luas, dan Anda akan mengerti bahwa pemrograman berorientasi objek mungkin bukan gaya terbaik di semua kasus, tetapi hanya untuk beberapa jenis program). Hati-hati dengan alokasi memori dinamis C , yang mungkin merupakan bagian tersulit. The C99 atau C11 standar bahasa pemrograman dan yang C standar perpustakaan sebenarnya sangat miskin (tidak tahu tentang TCP atau direktori!), Dan Anda akan sering perlu beberapa perpustakaan eksternal atau antarmuka (misalnyaPOSIX , libcurl untuk perpustakaan klien HTTP, libonion untuk perpustakaan server HTTP, GMPlib untuk bignum, beberapa perpustakaan seperti libunistring untuk UTF-8, dll ...).

"Objek" Anda sering kali dalam C struct-s terkait , dan Anda menentukan set fungsi yang beroperasi pada mereka. Untuk fungsi pendek atau sangat sederhana, pertimbangkan untuk mendefinisikannya, dengan yang relevan struct, seperti static inlinepada beberapa file header foo.hmenjadi #include-d di tempat lain.

Perhatikan bahwa pemrograman berorientasi objek bukan satu-satunya paradigma pemrograman . Dalam beberapa kesempatan, paradigma lain bermanfaat ( pemrograman fungsional à la Ocaml atau Haskell atau bahkan Skema atau Commmon Lisp, pemrograman logika à la Prolog, dll. ... Baca juga blog J.Pitrat tentang deklarasi kecerdasan buatan). Lihat buku Scott: Bahasa Pemrograman Pragmatik

Sebenarnya, seorang programmer di C, atau di Ocaml, biasanya tidak ingin kode dalam gaya pemrograman berorientasi objek. Tidak ada alasan untuk memaksa diri Anda memikirkan benda-benda saat itu tidak berguna.

Anda akan mendefinisikan beberapa structdan fungsi-fungsi yang beroperasi pada mereka (seringkali melalui pointer). Anda bisa memerlukan beberapa serikat yang ditandai (sering, a structdengan anggota tag, sering beberapa enum, dan beberapa uniondi dalam), dan Anda mungkin menemukan berguna untuk memiliki anggota array yang fleksibel di akhir beberapa struct-s Anda .

Lihat di dalam kode sumber dari beberapa perangkat lunak gratis yang ada di C (lihat github & sourceforge untuk menemukan beberapa). Mungkin, menginstal dan menggunakan distribusi Linux akan berguna: dibuat hampir hanya dari perangkat lunak bebas, ia memiliki kompiler perangkat lunak C gratis yang hebat ( GCC , Dentang / LLVM ) dan alat pengembangan. Lihat juga Pemrograman Linux Lanjut jika Anda ingin mengembangkan untuk Linux.

Jangan lupa untuk mengompilasi dengan semua peringatan dan info debug, misalnya gcc -Wall -Wextra -g-terutama selama fase pengembangan & debugging- dan belajar menggunakan beberapa alat, misalnya valgrind untuk memburu kebocoran memori , gdbdebugger, dll. Berhati-hatilah untuk memahami dengan baik apa yang tidak terdefinisi perilaku dan sangat menghindarinya (ingat bahwa suatu program dapat memiliki beberapa UB dan kadang-kadang tampaknya "bekerja").

Ketika Anda benar-benar membutuhkan konstruksi berorientasi objek (khususnya warisan ), Anda dapat menggunakan pointer ke struktur terkait dan fungsi. Anda bisa memiliki mesin vtable Anda sendiri , masing-masing memiliki "objek" dimulai dengan pointer ke pointer structfungsi yang mengandung. Anda mengambil keuntungan dari kemampuan untuk melemparkan tipe pointer ke tipe pointer lain (dan fakta bahwa Anda bisa melemparkan dari yang struct super_stberisi jenis bidang yang sama dengan yang memulai a struct sub_stuntuk meniru warisan). Perhatikan bahwa C cukup untuk mengimplementasikan sistem objek yang cukup canggih - khususnya dengan mengikuti beberapa konvensi -, seperti diperlihatkan GObject (dari GTK / Gnome).

Ketika Anda benar-benar membutuhkan penutupan , Anda akan sering meniru mereka dengan panggilan balik , dengan konvensi bahwa setiap fungsi yang menggunakan panggilan balik dilewatkan baik oleh pointer fungsi dan beberapa data klien (dikonsumsi oleh pointer fungsi ketika memanggil itu). Anda juga dapat memiliki (secara konvensional) closure-like struct-s Anda sendiri (mengandung beberapa pointer fungsi dan nilai-nilai tertutup).

Karena C adalah bahasa tingkat yang sangat rendah, penting untuk mendefinisikan dan mendokumentasikan konvensi Anda sendiri (terinspirasi oleh praktik dalam program C lainnya), khususnya tentang manajemen memori, dan mungkin beberapa konvensi penamaan juga. Sangat berguna untuk memiliki beberapa gagasan tentang arsitektur set instruksi . Jangan lupa bahwa sebuah C compiler dapat melakukan banyak optimasi pada kode Anda (jika Anda bertanya kepada), sehingga tidak peduli terlalu banyak tentang melakukan mikro-optimasi dengan tangan, cuti bahwa untuk compiler Anda (gcc -Wall -O2 untuk kompilasi dioptimalkan dirilis perangkat lunak). Jika Anda peduli tentang tolok ukur dan kinerja mentah, Anda harus mengaktifkan pengoptimalan (setelah program Anda didebug).

Jangan lupa bahwa kadang - kadang metaprogramming berguna . Cukup sering, perangkat lunak besar yang ditulis dalam C berisi beberapa skrip atau program ad-hoc untuk menghasilkan beberapa kode C yang digunakan di tempat lain (dan Anda mungkin juga memainkan beberapa trik preprosesor C yang kotor , misalnya X-macro ). Ada beberapa generator program C yang berguna (mis. Yacc atau gnu bison untuk menghasilkan parser, gperf untuk menghasilkan fungsi hash yang sempurna, dll ...). Pada beberapa sistem (terutama Linux & POSIX) Anda bahkan dapat menghasilkan beberapa kode C saat runtime dalam generated-001.cfile, kompilasi ke objek bersama dengan menjalankan beberapa perintah (sepertigcc -O -Wall -shared -fPIC generated-001.c -o generated-001.so ) saat runtime, secara dinamis memuat objek yang dibagikan menggunakan dlopen& dapatkan penunjuk fungsi dari nama menggunakan dlsym . Saya melakukan trik-trik semacam itu dalam MELT (bahasa khusus domain mirip Lisp yang mungkin berguna bagi Anda, karena memungkinkan penyesuaian dari kompiler GCC ).

Waspadai konsep dan teknik pengumpulan sampah ( penghitungan referensi sering kali merupakan teknik untuk mengelola memori dalam C, dan itu adalah bentuk buruk pengumpulan sampah yang tidak menangani dengan baik dengan referensi melingkar ; Anda bisa memiliki petunjuk lemah untuk membantu tentang itu, tapi mungkin rumit). Pada beberapa kesempatan, Anda mungkin mempertimbangkan untuk menggunakan pengumpul sampah Boehm yang konservatif .

Basile Starynkevitch
sumber
7
Sejujurnya, secara indepen dari pertanyaan ini, membaca SICP tidak diragukan lagi merupakan saran yang bagus, tetapi bagi OP ini mungkin akan mengarah pada pertanyaan berikutnya "Bagaimana berpikir sebagai seorang programmer C setelah bias dengan SICP".
Doc Brown
1
Tidak, karena Skema dari SICP & PHP (atau Ruby atau Python) sangat berbeda sehingga OP akan mendapatkan pemikiran yang jauh lebih luas; dan SICP menjelaskan dengan sangat baik apa tipe data abstrak dalam praktiknya, dan itu sangat berguna untuk dipahami, khususnya untuk pengkodean dalam C.
Basile Starynkevitch
1
SICP adalah saran aneh. Skema ini sangat berbeda dari C.
Brian Gordon
Tetapi SICP mengajarkan banyak kebiasaan yang baik, dan mengetahui Skema memang membantu ketika melakukan pengkodean dalam C (untuk konsep penutupan, tipe data abstrak, dll ...)
Basile Starynkevitch
5

Cara program dibangun pada dasarnya mendefinisikan tindakan (fungsi) mana yang harus dilakukan untuk menyelesaikan masalah (itulah sebabnya itu disebut bahasa prosedural). Setiap tindakan akan sesuai dengan suatu fungsi. Kemudian Anda perlu menentukan jenis informasi apa yang masing-masing fungsi akan terima dan informasi apa yang mereka butuhkan untuk kembali.

Program biasanya dipisahkan dalam file (modul) setiap file biasanya akan memiliki sekelompok fungsi yang terkait. Di awal setiap file Anda mendeklarasikan (di luar fungsi apa pun) variabel yang akan digunakan oleh semua fungsi dalam file itu. Jika Anda menggunakan kualifikasi "statis" variabel-variabel itu hanya akan terlihat di dalam file itu (tetapi tidak dari file lain). Jika Anda tidak menggunakan kualifikasi "statis" pada variabel yang ditentukan di luar fungsi, mereka akan dapat diakses dari file lain juga dan file-file lain ini harus mendeklarasikan variabel sebagai "extern" (tetapi tidak mendefinisikannya) sehingga kompiler akan mencari mereka dalam file lain.

Jadi singkatnya Anda pertama kali berpikir tentang prosedur (fungsi) maka Anda memastikan semua fungsi memiliki akses ke informasi yang mereka butuhkan.

Mandrill
sumber
3

API C sering - mungkin bahkan, biasanya - memang memiliki antarmuka berorientasi objek jika Anda melihatnya dengan cara yang benar.

Dalam C ++:

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

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

Dalam C:

typedef struct {
    int x;
} foo;

void bar (foo*, int param);

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

Seperti yang Anda ketahui, dalam C ++ dan berbagai bahasa OO formal lainnya, di bawah tenda metode objek mengambil argumen pertama yang merupakan pointer ke objek, seperti versi C di bar()atas. Untuk contoh di mana ini muncul ke permukaan dalam C ++, pertimbangkan bagaimana std::binddapat digunakan agar sesuai dengan metode objek untuk berfungsi tanda tangan:

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

Seperti yang telah ditunjukkan orang lain, perbedaan nyata adalah bahwa bahasa OO formal dapat menerapkan polimorfisme, kontrol akses, dan berbagai fitur bagus lainnya. Tetapi esensi pemrograman berorientasi objek, penciptaan dan manipulasi struktur data yang kompleks dan diskrit, sudah merupakan praktik mendasar dalam C.

goldilocks
sumber
2

Salah satu alasan utama orang didorong untuk belajar C adalah itu salah satu bahasa pemrograman tingkat tinggi yang paling rendah. Bahasa OOP membuatnya lebih mudah untuk berpikir tentang model data dan templating code dan message passing, tetapi pada akhirnya, mikroprosesor mengeksekusi kode langkah-demi-langkah, melompat masuk dan keluar dari blok kode (fungsi dalam C) dan memindahkan referensi ke variabel (pointer di C) sekitar sehingga bagian yang berbeda dari suatu program dapat berbagi data. Anggaplah C sebagai bahasa rakitan dalam bahasa Inggris - memberikan instruksi langkah demi langkah ke mikroprosesor komputer Anda - dan Anda tidak akan salah. Sebagai bonus, sebagian besar antarmuka sistem operasi bekerja seperti panggilan fungsi C daripada paradigma OOP,

Gaurav
sumber
2
IMHO C adalah bahasa tingkat rendah, tetapi jauh lebih tinggi daripada kode assembler atau mesin, karena kompiler C dapat melakukan banyak optimasi tingkat rendah.
Basile Starynkevitch
Kompiler C juga, atas nama "optimisasi", bergerak menuju model mesin abstrak yang dapat meniadakan hukum waktu dan hubungan sebab akibat ketika diberi input yang akan menyebabkan Perilaku Tidak Terdefinisi, bahkan jika perilaku alami kode pada mesin tempat mesin itu dijalankan akan memenuhi persyaratan jika tidak memenuhi. Misalnya, fungsi tersebut uint16_t blah(uint16_t x) {return x*x;}akan bekerja secara identik pada mesin unsigned intyang 16 bit, atau 33 bit atau lebih besar. Beberapa kompiler untuk mesin dengan unsigned int17 hingga 32 bit, bagaimanapun, mungkin menganggap panggilan ke metode itu ...
supercat
... sebagai pemberian izin bagi kompiler untuk menyimpulkan bahwa tidak ada rantai peristiwa yang akan menyebabkan metode diberi nilai lebih dari 46340 mungkin terjadi. Meskipun mengalikan 65533u * 65533u pada platform apa pun akan menghasilkan nilai yang, ketika dilemparkan ke uint16_t, akan menghasilkan 9, Standar tidak mengamanatkan perilaku seperti itu ketika mengalikan nilai tipe uint16_tpada platform 17 hingga 32-bit.
supercat
-1

Saya juga I OO asli (C ++ umumnya) yang kadang-kadang harus bertahan hidup di dunia C. Bagi saya rintangan terbesar pada dasarnya adalah berurusan dengan penanganan kesalahan dan manajemen sumber daya.

Dalam C ++ kita harus melempar untuk melewati kesalahan dari tempat itu terjadi sepanjang perjalanan kembali ke tingkat atas di mana kita dapat menghadapinya dan kita memiliki destruktor untuk secara otomatis membebaskan memori kita dan sumber daya lainnya.

Anda mungkin memperhatikan bahwa banyak API C menyertakan fungsi init yang memberi Anda kekosongan yang sudah diketik * yang benar-benar merupakan pointer ke struct. Lalu Anda meneruskan ini sebagai argumen pertama untuk setiap panggilan API. Intinya itu menjadi pointer "ini" Anda dari C ++. Ini akan digunakan untuk semua struktur data internal yang disembunyikan (konsep yang sangat OO). Anda juga dapat menggunakannya untuk mengelola memori, mis. Memiliki fungsi yang disebut myapiMalloc yang mengosongkan memori Anda dan mencatat malloc dalam versi C dari penunjuk ini sehingga Anda dapat memastikannya dibebaskan ketika API Anda kembali. Juga seperti yang saya temukan baru-baru ini Anda dapat menggunakannya untuk menyimpan kode kesalahan dan menggunakan setjmp dan longjmp untuk memberi Anda perilaku yang sangat mirip dengan melempar tangkapan. Menggabungkan kedua konsep memberi Anda banyak fungsi dari program C ++.

Sekarang Anda memang mengatakan bahwa Anda tidak ingin belajar memaksa C menjadi C ++. Itu bukan yang saya jelaskan (setidaknya tidak sengaja). Ini hanyalah metode (mudah-mudahan) dirancang dengan baik untuk mengeksploitasi fungsionalitas C. Ternyata memiliki beberapa rasa OO - mungkin itu sebabnya bahasa OO dikembangkan, mereka adalah cara untuk memformalkan / menegakkan / memfasilitasi konsep yang beberapa orang temukan sebagai praktik terbaik.

Jika Anda pikir ini untuk OO merasa untuk Anda maka alternatifnya adalah memiliki hampir setiap fungsi mengembalikan kode kesalahan yang Anda harus memastikan Anda memeriksa setiap fungsi panggilan dan menyebarkan tumpukan panggilan. Anda harus memastikan bahwa semua sumber daya dibebaskan tidak hanya di akhir setiap fungsi, tetapi di setiap titik pengembalian (yang berpotensi terjadi setelah setiap panggilan fungsi yang dapat mengembalikan kesalahan yang menunjukkan Anda tidak dapat melanjutkan). Ini bisa menjadi sangat membosankan dan cenderung membuat Anda berpikir saya mungkin tidak perlu berurusan dengan kegagalan alokasi memori potensial (atau file read atau port connect ...), saya hanya akan berasumsi bahwa itu akan berfungsi atau saya ' Saya akan menulis kode "menarik" sekarang dan kembali dan berurusan dengan penanganan kesalahan - yang tidak pernah terjadi.

Phil Rosenberg
sumber