Mengapa menggunakan #ifndef CLASS_H dan #define CLASS_H dalam file .h tetapi tidak dalam .cpp?


140

Saya selalu melihat orang menulis

class.h

#ifndef CLASS_H
#define CLASS_H

//blah blah blah

#endif

Pertanyaannya adalah, mengapa mereka tidak melakukannya juga untuk file .cpp yang berisi definisi untuk fungsi kelas?

Katakanlah saya punya main.cpp, dan main.cpptermasuk class.h. The class.hfile tidak includeapa-apa, jadi bagaimana main.cpptahu apa yang ada dalam class.cpp?


5
"impor" mungkin bukan kata yang ingin Anda gunakan di sini. Sertakan.
Kate Gregory

5
Di C ++, tidak ada korelasi 1-ke-1 antara file dan kelas. Anda dapat menempatkan sebanyak mungkin kelas ke dalam satu file sesuai keinginan (atau bahkan membagi satu kelas ke beberapa file, meskipun ini jarang membantu). Oleh karena itu makro seharusnya FILE_Htidak CLASS_H.
sbi

1
Lihat saran saya termasuk penjaga .

Jawaban:


309

Pertama, untuk menjawab pertanyaan pertama Anda:

Jika Anda melihat ini di file .h :

#ifndef FILE_H
#define FILE_H

/* ... Declarations etc here ... */

#endif

Ini adalah teknik preprocessor untuk mencegah file header dimasukkan beberapa kali, yang dapat menjadi masalah karena berbagai alasan. Selama kompilasi proyek Anda, setiap file .cpp (biasanya) dikompilasi. Secara sederhana, ini berarti kompilator akan mengambil file .cpp Anda , membuka file apa pun #includeddengannya, menggabungkan semuanya menjadi satu file teks besar, dan kemudian melakukan analisis sintaks dan akhirnya akan mengubahnya menjadi beberapa kode perantara, mengoptimalkan / melakukan yang lain tugas, dan akhirnya menghasilkan keluaran perakitan untuk arsitektur target. Karenanya, jika sebuah file #includedbeberapa kali berada di bawah satu .cppfile, kompilator akan menambahkan konten file dua kali, jadi jika ada definisi di dalam file itu, Anda akan mendapatkan kesalahan kompilator yang memberi tahu Anda bahwa Anda mendefinisikan ulang variabel. Ketika file diproses oleh langkah preprocessor dalam proses kompilasi, pertama kali isinya tercapai, dua baris pertama akan memeriksa apakah FILE_Hpreprocessor telah ditentukan. Jika tidak, itu akan mendefinisikan FILE_Hdan melanjutkan pemrosesan kode antara itu dan #endifdirektif. Saat konten file tersebut dilihat di lain waktu oleh preprocessor, pemeriksaan terhadapnya FILE_Hakan salah, jadi itu akan segera memindai ke bawah #endifdan melanjutkan setelahnya. Ini mencegah kesalahan definisi ulang.

Dan untuk mengatasi kekhawatiran kedua Anda:

Dalam pemrograman C ++ sebagai praktik umum, kami memisahkan pengembangan menjadi dua jenis file. Salah satunya adalah dengan ekstensi .h dan kami menyebutnya "file header". Mereka biasanya menyediakan deklarasi fungsi, kelas, struct, variabel global, typedefs, makro dan definisi preprocessing, dll. Pada dasarnya, mereka hanya memberi Anda informasi tentang kode Anda. Kemudian kami memiliki ekstensi .cpp yang kami sebut "file kode." Ini akan memberikan definisi untuk fungsi tersebut, anggota kelas, anggota struct apa pun yang membutuhkan definisi, variabel global, dll. Jadi file .h mendeklarasikan kode, dan file .cpp mengimplementasikan deklarasi itu. Untuk alasan ini, kami biasanya selama kompilasi mengkompilasi setiap .cppfile ke dalam suatu objek dan kemudian tautkan objek tersebut (karena Anda hampir tidak pernah melihat satu file .cpp menyertakan file .cpp lainnya ).

Bagaimana eksternal ini diselesaikan adalah pekerjaan untuk linker. Ketika kompilator Anda memproses main.cpp , ia mendapatkan deklarasi untuk kode di class.cpp dengan menyertakan class.h . Hanya perlu mengetahui seperti apa fungsi atau variabel ini (yang diberikan deklarasi kepada Anda). Jadi itu mengkompilasi file main.cpp Anda menjadi beberapa file objek (sebut saja main.obj ). Demikian pula, class.cpp dikompilasi menjadi class.objmengajukan. Untuk menghasilkan eksekusi akhir, linker dipanggil untuk menghubungkan kedua file objek tersebut bersama-sama. Untuk variabel atau fungsi eksternal yang belum terselesaikan, kompilator akan menempatkan stub di mana akses terjadi. Linker kemudian akan mengambil stub ini dan mencari kode atau variabel di file objek terdaftar lainnya, dan jika ditemukan, linker akan menggabungkan kode dari dua file objek menjadi file output dan menggantikan stub dengan lokasi akhir fungsi atau variabel. Dengan cara ini, kode Anda di main.cpp dapat memanggil fungsi dan menggunakan variabel di class.cpp JIKA DAN HANYA JIKA MEREKA DITERAPKAN DI class.h .

Saya harap ini membantu.


Saya mencoba memahami .h dan .cpp selama beberapa hari terakhir. Jawaban ini menghemat waktu & minat saya untuk belajar C ++. Ditulis dengan baik. Terima kasih Justin!
Rajkumar R

Anda benar-benar menjelaskan dengan hebat! Mungkin jawabannya akan cukup baik jika dengan gambar
alamin

13

Ini CLASS_Hadalah termasuk penjaga ; ini digunakan untuk menghindari file header yang sama disertakan beberapa kali (melalui rute berbeda) dalam file CPP yang sama (atau, lebih tepatnya, unit terjemahan yang sama ), yang akan menyebabkan kesalahan definisi ganda.

Sertakan penjaga tidak diperlukan pada file CPP karena, menurut definisi, konten file CPP hanya dibaca sekali.

Anda tampaknya telah menafsirkan penjaga include memiliki fungsi yang sama dengan importpernyataan dalam bahasa lain (seperti Java); Namun, bukan itu masalahnya. Bahasa #includeitu sendiri kira-kira setara dengan importbahasa lain.


2
"dalam file CPP yang sama" harus dibaca "dalam unit terjemahan yang sama".
dreamlax

@dreamlax: Poin yang bagus - itulah yang awalnya akan saya tulis, tetapi kemudian saya pikir seseorang yang tidak mengerti termasuk penjaga hanya akan dibingungkan oleh istilah "unit terjemahan". Saya akan mengedit jawaban untuk menambahkan "unit terjemahan" dalam tanda kurung - itu seharusnya menjadi yang terbaik dari kedua dunia.
Martin B

7

Tidak - setidaknya selama fase kompilasi.

Terjemahan program c ++ dari kode sumber ke kode mesin dilakukan dalam tiga tahap:

  1. Preprocessing - Preprocessor mem-parsing semua kode sumber untuk baris yang diawali dengan # dan menjalankan arahan. Dalam kasus Anda, konten file Anda class.hdisisipkan di tempat baris #include "class.h. Karena Anda mungkin disertakan dalam file header di beberapa tempat, #ifndefklausul menghindari kesalahan deklarasi duplikat, karena direktif preprocessor tidak ditentukan hanya saat pertama kali file header disertakan.
  2. Kompilasi - Sekarang Compiler menerjemahkan semua file kode sumber yang telah diproses sebelumnya ke file objek biner.
  3. Menghubungkan - Linker menautkan (karena itu namanya) bersama-sama file objek. Referensi ke kelas Anda atau salah satu metodenya (yang harus dideklarasikan di class.h dan didefinisikan di class.cpp) diselesaikan ke offset masing-masing di salah satu file objek. Saya menulis 'salah satu file objek Anda' karena kelas Anda tidak perlu didefinisikan dalam file bernama class.cpp, mungkin di perpustakaan yang ditautkan ke proyek Anda.

Singkatnya, deklarasi dapat dibagikan melalui file header, sedangkan pemetaan deklarasi ke definisi dilakukan oleh linker.


4

Itulah perbedaan antara deklarasi dan definisi. File header biasanya hanya menyertakan deklarasi, dan file sumber berisi definisi.

Untuk menggunakan sesuatu Anda hanya perlu mengetahui deklarasi itu bukan definisinya. Hanya penaut yang perlu mengetahui definisinya.

Jadi inilah mengapa Anda akan menyertakan file header di dalam satu atau lebih file sumber tetapi Anda tidak akan menyertakan file sumber di dalam file lain.

Juga maksud Anda #includedan bukan impor.


3

Itu dilakukan untuk file header sehingga isinya hanya muncul sekali di setiap file sumber yang diproses sebelumnya, meskipun itu disertakan lebih dari sekali (biasanya karena disertakan dari file header lain). Pertama kali disertakan, simbol CLASS_H(dikenal sebagai penjaga penyertaan ) belum ditentukan, jadi semua konten file disertakan. Melakukan ini mendefinisikan simbol, jadi jika itu dimasukkan lagi, konten file (di dalam blok #ifndef/ #endif) akan dilewati.

Tidak perlu melakukan ini untuk file sumber itu sendiri karena (biasanya) itu tidak disertakan oleh file lain.

Untuk pertanyaan terakhir Anda, class.hharus berisi definisi kelas, dan deklarasi semua anggotanya, fungsi terkait, dan apa pun, sehingga file apa pun yang menyertakannya memiliki informasi yang cukup untuk menggunakan kelas. Implementasi fungsi bisa masuk dalam file sumber terpisah; Anda hanya perlu deklarasi untuk memanggilnya.


2

main.cpp tidak harus tahu apa yang ada di class.cpp . Itu hanya harus mengetahui deklarasi fungsi / kelas yang digunakannya, dan deklarasi ini ada di class.h .

Linker menghubungkan antara tempat-tempat di mana fungsi / kelas yang dideklarasikan di class.h digunakan dan implementasinya di class.cpp


1

.cppfile tidak disertakan (menggunakan #include) ke file lain. Oleh karena itu mereka tidak perlu menyertakan penjagaan. Main.cppakan mengetahui nama dan tanda tangan kelas yang telah Anda terapkan class.cpphanya karena Anda telah menentukan semua itu di class.h- ini adalah tujuan dari file header. (Terserah Anda untuk memastikan bahwa class.hsecara akurat mendeskripsikan kode yang Anda terapkan class.cpp.) Kode yang dapat dieksekusi di class.cppakan tersedia untuk kode yang dapat dieksekusi main.cppberkat upaya linker.


1

Secara umum diharapkan bahwa modul kode seperti .cppfile dikompilasi satu kali dan ditautkan ke dalam beberapa proyek, untuk menghindari kompilasi logika berulang yang tidak perlu. Misalnya, g++ -o class.cppakan menghasilkan class.oyang kemudian dapat Anda tautkan dari beberapa proyek untuk digunakan g++ main.cpp class.o.

Kami dapat menggunakan #includesebagai penaut kami, seperti yang Anda maksudkan, tetapi itu akan menjadi konyol ketika kami tahu cara menautkan dengan benar menggunakan kompiler kami dengan lebih sedikit penekanan tombol dan pengulangan kompilasi yang tidak terlalu boros, daripada kode kami dengan lebih banyak penekanan tombol dan lebih boros pengulangan kompilasi ...

File header masih perlu dimasukkan ke dalam setiap proyek, karena ini menyediakan antarmuka untuk setiap modul. Tanpa header ini, kompilator tidak akan tahu tentang simbol apa pun yang diperkenalkan oleh.o file.

Penting untuk disadari bahwa file header adalah yang memperkenalkan definisi simbol untuk modul tersebut; setelah itu direalisasikan maka masuk akal bahwa banyak inklusi dapat menyebabkan redefinisi simbol (yang menyebabkan kesalahan), jadi kami menggunakan include penjaga untuk mencegah redefinisi tersebut.


0

itu karena Headerfiles mendefinisikan apa yang berisi kelas (Anggota, struktur data) dan file cpp mengimplementasikannya.

Dan tentu saja, alasan utamanya adalah Anda dapat menyertakan satu File .h beberapa kali dalam file .h lainnya, tetapi ini akan menghasilkan beberapa definisi kelas, yang tidak valid.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.