Neden başlık dosyaları ve .cpp dosyaları var? [kapalı]


484

C ++ 'ın neden başlık dosyaları ve .cpp dosyaları var?



Bu yaygın bir OOP paradigmasıdır, .h bir sınıf beyanıdır ve cpp'nin tanımıdır.
Manish Kakati

Bu c ++ arabirimi uygulamadan ayıran en iyi parçasıdır. Tüm kodu tek bir dosyada tutmak yerine her zaman iyi, arayüz ayrıldı. Bir miktar kod her zaman başlık dosyalarının bir parçası olan satır içi işlev gibi vardır. Bir başlık dosyası görüldüğünde bildirilen işlevlerin ve sınıf değişkenlerinin listesini görüntüler.
Haziran'da Miank

Başlık dosyalarının derleme için önemli olduğu zamanlar vardır - yalnızca bir kuruluş tercihi veya önceden derlenmiş kitaplıkları dağıtmanın yolu değil. Game.c'nin İKİ fizik ve matematik.c'ye bağlı olduğu bir yapınız olduğunu varsayalım. physics.c ayrıca matematiğe de bağlıdır. c. .C dosyalarını eklediyseniz ve sonsuza dek .h dosyalarını unuttuysanız, math.c'den yinelenen bildirimler alırsınız ve derleme umudu olmazdı. Başlık dosyalarının neden önemli olduğu bana en çok mantıklı gelen şey bu. Umarım başka birine yardımcı olur.
Samy Bencherif

Bence uzantılarda sadece alfasayısal karakterlere izin verilmesi gerekiyor. Bunun doğru olup olmadığını bile bilmiyorum, sadece tahmin
user12211554 13:19

Yanıtlar:


201

Temel sebep, arayüzü uygulamadan ayırmak olacaktır. Üstbilgi, bir sınıfın (ya da uygulanmakta olan her şeyin) "ne" yapacağını, cpp dosyası ise bu özellikleri nasıl "gerçekleştireceğini" tanımlar.

Bu, bağımlılıkları azaltır, böylece üstbilgiyi kullanan kodun uygulamanın tüm ayrıntılarını ve yalnızca bunun için gereken diğer sınıfları / başlıkları bilmesi gerekmez. Bu, derleme sürelerini ve ayrıca uygulamadaki bir şey değiştiğinde gereken yeniden derleme miktarını azaltacaktır.

Mükemmel değil ve genellikle arayüzü ve uygulamayı düzgün bir şekilde ayırmak için Pimpl Deyim gibi tekniklere başvurursunuz , ancak iyi bir başlangıçtır.


178
Gerçekten doğru değil. Başlık hala uygulamanın önemli bir bölümünü içerir. Özel örnek değişkenleri ne zamandan beri bir sınıfın arayüzünün parçasıydı? Özel üye işlevleri? O halde, herkesin görebileceği üstbilgide ne yapıyorlar? Ve şablonlarla daha da ayrılıyor.
jalf

13
Bu yüzden mükemmel olmadığını ve daha fazla ayrılık için Pimpl deyiminin gerekli olduğunu söyledim. Şablonlar tamamen farklı solucanlar olabilir - "export" anahtar kelimesinin çoğu derleyicide tam olarak desteklenmiş olsa bile, hala gerçek ayrılma yerine sözdizimsel şeker olurdu.
Joris Timmermans

4
Diğer diller bunu nasıl ele alır? örneğin - Java? Java'da başlık dosyası kavramı yoktur.
Lazer

8
@Lazer: Java'nın ayrıştırılması daha kolaydır. Java derleyicisi, diğer dosyalardaki tüm sınıfları bilmeden bir dosyayı ayrıştırabilir ve daha sonra türleri kontrol edebilir. C ++ 'da birçok yapı tür bilgisi olmadan belirsizdir, bu nedenle C ++ derleyicisi bir dosyayı ayrıştırmak için başvurulan türler hakkında bilgiye ihtiyaç duyar. Bu yüzden başlıklara ihtiyacı var.
Niki

15
@nikie: Ayrıştırmanın "kolaylığı" bununla ne ilgisi var? Java'nın en azından C ++ kadar karmaşık bir dilbilgisi varsa, yine de java dosyalarını kullanabilir. Her iki durumda da, C ne olacak? C'nin ayrıştırılması kolaydır, ancak hem üstbilgileri hem de c dosyalarını kullanır.
Thomas Eding

609

C ++ derlemesi

C ++ 'da bir derleme 2 ana aşamada yapılır:

  1. Birincisi, "kaynak" metin dosyalarının ikili "nesne" dosyalarına derlenmesidir: CPP dosyası derlenmiş dosyadır ve ham bildirim veya üstbilgi dahil etme. CPP dosyası genellikle bir .OBJ veya .O "nesne" dosyasına derlenir.

  2. İkincisi, tüm "nesne" dosyalarının birbirine bağlanması ve böylece nihai ikili dosyanın (bir kütüphane veya yürütülebilir bir dosya) oluşturulmasıdır.

HES tüm bu süreçlere nerede uyar?

Kötü bir CPP dosyası ...

Her CPP dosyasının derlenmesi diğer tüm CPP dosyalarından bağımsızdır, yani A.CPP'nin B.CPP'de tanımlanan bir sembole ihtiyacı varsa, örneğin:

// A.CPP
void doSomething()
{
   doSomethingElse(); // Defined in B.CPP
}

// B.CPP
void doSomethingElse()
{
   // Etc.
}

Derlenmeyecektir, çünkü A.CPP'nin "doSomethingElse" varlığını bilmenin bir yolu yoktur ... A.CPP'de aşağıdaki gibi bir bildirim olmadığı sürece:

// A.CPP
void doSomethingElse() ; // From B.CPP

void doSomething()
{
   doSomethingElse() ; // Defined in B.CPP
}

Ardından, aynı sembolü kullanan C.CPP'niz varsa, bildirimi kopyalayıp yapıştırın ...

KOPYA / MACUN UYARISI!

Evet, bir sorun var. Kopyalama / yapıştırma işlemleri tehlikelidir ve bakımı zordur. Bu da, kopyalamak / yapıştırmak için bir yolumuz olmasaydı ve yine de sembolü ilan etmenin harika olacağı anlamına gelir ... Nasıl yapabiliriz? .H, .hxx, .h ++ veya C ++ dosyaları için tercih ettiğim, .hpp tarafından eklenmiş bazı metin dosyalarının eklenmesiyle:

// B.HPP (here, we decided to declare every symbol defined in B.CPP)
void doSomethingElse() ;

// A.CPP
#include "B.HPP"

void doSomething()
{
   doSomethingElse() ; // Defined in B.CPP
}

// B.CPP
#include "B.HPP"

void doSomethingElse()
{
   // Etc.
}

// C.CPP
#include "B.HPP"

void doSomethingAgain()
{
   doSomethingElse() ; // Defined in B.CPP
}

Nasıl includeçalışır?

Bir dosya eklemek, özünde ayrıştırılır ve ardından içeriğini CPP dosyasına kopyalayıp yapıştırır.

Örneğin, aşağıdaki kodda, A.HPP üstbilgisiyle:

// A.HPP
void someFunction();
void someOtherFunction();

... kaynak B.CPP:

// B.CPP
#include "A.HPP"

void doSomething()
{
   // Etc.
}

... dahil edildikten sonra olacak:

// B.CPP
void someFunction();
void someOtherFunction();

void doSomething()
{
   // Etc.
}

Küçük bir şey - neden B.CPP'ye B.HPP dahil?

Mevcut durumda, bu gerekli değildir ve B.HPP doSomethingElseişlev bildirimine sahiptir ve B.CPP doSomethingElseişlev tanımına sahiptir (kendi başına bir bildirimdir). Ancak, B.HPP'nin bildirimler (ve satır içi kod) için kullanıldığı daha genel bir durumda, karşılık gelen bir tanım olmayabilir (örneğin, numaralandırmalar, düz yapılar vb.), Bu nedenle B.CPP ise içerme gerekebilir B.HPP'den gelen bu beyanı kullanır. Sonuçta, bir kaynağın varsayılan olarak başlığını içermesi "iyi bir tattır".

Sonuç

Bu nedenle başlık dosyası gereklidir, çünkü C ++ derleyicisi yalnızca sembol bildirimlerini arayamaz ve bu nedenle bu bildirimleri ekleyerek yardımcı olmanız gerekir.

Son bir kelime: Birden fazla kapanmanın hiçbir şeyi bozmayacağından emin olmak için HPP dosyalarınızın içeriğine başlık korumaları koymalısınız, ama sonuçta, HPP dosyalarının varlığının ana nedeninin yukarıda açıklandığına inanıyorum.

#ifndef B_HPP_
#define B_HPP_

// The declarations in the B.hpp file

#endif // B_HPP_

hatta daha basit

#pragma once

// The declarations in the B.hpp file

2
@ nimcap:: You still have to copy paste the signature from header file to cpp file, don't you?Gerek yok. CPP HPP'yi "içerdiği sürece", ön derleyici otomatik olarak HPP dosyasının içeriğini CPP dosyasına kopyalayıp yapıştıracaktır. Cevabı açıklığa kavuşturmak için güncelledim.
paercebal

7
@Bob: While compiling A.cpp, compiler knows the types of arguments and return value of doSomethingElse from the call itself. Hayır. Sadece kullanıcı tarafından sağlanan türleri bilir, bu da yarı zamanın geri dönüş değerini okumak için bile uğraşmaz. Ardından, örtük dönüşümler gerçekleşir. Ve sonra, koda sahip olduğunuzda: bir işlev foo(bar)olduğundan bile emin olamazsınız foo. Bu yüzden derleyici, kaynağın doğru bir şekilde derlenip derlenmeyeceğine karar vermek için başlıklardaki bilgilere erişmelidir ... Daha sonra, kod derlendiğinde, bağlayıcı işlev çağrılarını birbirine bağlayacaktır.
paercebal

3
@Bob: [devam ediyor] ... Şimdi, linker derleyici tarafından yapılan işi yapabilirdi, sanırım, o zaman seçeneğinizi mümkün kılacaktı. (Sanırım bu bir sonraki standart için "modüller" önerisinin konusudur). Seems, they're just a pretty ugly arbitrary design.: C ++ 2012'de oluşturulmuş olsaydı gerçekten. Ancak, C ++ 'ın 1980'lerde C üzerine kurulduğunu ve o sırada kısıtlamaların o zamanlar oldukça farklı olduğunu unutmayın (IIRC, C'lerden aynı bağlayıcıları tutmaya karar verilmesi).
paercebal

1
@paercebal Açıklama ve notlar için teşekkürler, paercebal! Neden emin değilim, bu foo(bar)bir işlev - eğer bir işaretçi olarak elde edilirse? Aslında, kötü tasarımdan bahsetmişken, C ++ değil C'yi suçluyorum. Girişte birden fazla argüman alırken, başlık dosyalarına sahip olmak veya işlevlerin bir ve sadece bir değer döndürmesi gibi saf C'nin bazı kısıtlamalarını gerçekten sevmiyorum (giriş ve çıkışın benzer şekilde davranması doğal hissetmiyor mu? ; neden birden fazla argüman, ama tek çıktı?) :)
Boris Burkov

1
@Bobo:: Why can't I be sure, that foo(bar) is a functionfoo bir tür olabilir, bu yüzden adlı bir sınıf oluşturucunuz olur. In fact, speaking of bad design, I blame C, not C++: Bir çok şey için C'yi suçlayabilirim, ancak 70'lerde tasarlanmış olmak bunlardan biri olmayacak. Yine, o zamanın kısıtlamaları ... such as having header files or having functions return one and only one value: Tuples bunu hafifletmeye ve argümanları referans olarak geçirmeye yardımcı olabilir. Şimdi, döndürülen birden çok değeri almak için sözdizimi ne olurdu ve dili değiştirmeye değer mi?
paercebal

93

Kavramın ortaya çıktığı C 30 yaşında olduğu ve o zamandan beri, birden fazla dosyadan kodu birbirine bağlamanın tek geçerli yolu oldu.

Bugün, C ++ 'da derleme süresini tamamen yok eden, sayısız gereksiz bağımlılığa neden olan korkunç bir hack'tir (çünkü bir başlık dosyasındaki sınıf tanımları uygulama hakkında çok fazla bilgi ortaya çıkarır, vb.)


3
Neden başlık dosyaları (ya da aslında derleme / bağlantı için gerekli ne olursa olsun) sadece "otomatik oluşturulan" değildi merak ediyorum?
Mateen Ulhaq

54

C ++ 'da, son yürütülebilir kod herhangi bir sembol bilgisi taşımadığından, az çok saf makine kodu olur.

Bu nedenle, kodun kendisinden ayrı olan bir kod parçasının arayüzünü tanımlamanın bir yoluna ihtiyacınız vardır. Bu açıklama başlık dosyasındadır.


16

Çünkü C ++ onları C'den miras aldı. Ne yazık ki.


4
C ++ 'ın C'den miras alması talihsizdir?
Lokesh

3
@Lokesh Bagajından dolayı :(
陳 力

1
Bu nasıl bir cevap olabilir?
Shuvo Sarker

14
@ShuvoSarker, binlerce dilin gösterdiği gibi, C ++ 'ın programcılara fonksiyon imzalarını iki kez yazmasını sağlayan teknik bir açıklama yoktur. "Neden?" "tarih" tir.
Boris

15

Çünkü kütüphane formatını tasarlayanlar, C önişlemci makroları ve fonksiyon bildirimleri gibi nadiren kullanılan bilgiler için yer kaybetmek istemiyorlardı.

Derleyicinize "bu işlev daha sonra bağlayıcı işi yaparken kullanılabilir" demek için bu bilgilere ihtiyaç duyduğunuzdan, bu paylaşılan bilgilerin depolanabileceği ikinci bir dosya bulmaları gerekiyordu.

C / C ++ 'dan sonraki çoğu dil bu bilgileri çıktıda depolar (örneğin Java bayt kodu) veya önceden derlenmiş bir format kullanmazlar, her zaman kaynak formda dağıtılır ve anında derlenirler (Python, Perl).


Çalışmaz, döngüsel referanslar. B.cpp'den b.lib oluşturmadan önce a.cpp'den a.lib oluşturamazsınız, ancak a.lib'den önce b.lib oluşturamazsınız.
MSalters

20
Java bunu çözdü, Python bunu yapabilir, herhangi bir modern dil bunu yapabilir. Ancak C'nin icat edildiği sırada RAM çok pahalı ve kıttı, sadece bir seçenek değildi.
Aaron Digulla

6

Arabirimleri bildirmenin önişlemci yolu. Arabirimi (yöntem bildirimleri) başlık dosyasına ve uygulamayı cpp'ye koydunuz. Kitaplığınızı kullanan uygulamaların yalnızca #include aracılığıyla erişebilecekleri arayüzü bilmeleri gerekir.


4

Genellikle tüm kodu göndermeden bir arayüz tanımına sahip olmak istersiniz. Örneğin, paylaşılan bir kitaplığınız varsa, onunla paylaşılan kitaplıkta kullanılan tüm işlevleri ve sembolleri tanımlayan bir başlık dosyası gönderirsiniz. Başlık dosyaları olmadan, kaynağı göndermeniz gerekir.

Tek bir projede, IMHO başlık dosyaları en az iki amaç için kullanılır:

  • Netlik, yani arayüzleri uygulamadan ayrı tutarak kodu okumak daha kolaydır
  • Derleme zamanı. Tam uygulama yerine yalnızca mümkün olan arabirimi kullanarak derleme süresi azaltılabilir, çünkü derleyici gerçek kodu ayrıştırmak yerine arabirime bir başvuru yapabilir (bu, yalnızca tek bir kez).

3
Kütüphane satıcıları neden oluşturulmuş bir "başlık" dosyası gönderemedi? İşlemci öncesi ücretsiz bir "başlık" dosyası çok daha iyi performans vermelidir (uygulama gerçekten bozuk olmadıkça).
Tom Hawtin - taktik

Başlık dosyası oluşturulursa veya elle yazılırsa bunun önemsiz olduğunu düşünüyorum, soru "insanlar neden başlık dosyalarını kendileri yazıyor?", "Neden başlık dosyalarımız var?" Aynı şey önişlemci içermeyen üstbilgiler için de geçerlidir. Elbette, bu daha hızlı olurdu.

-5

Yanıt MadKeithV cevabı ,

Bu, bağımlılıkları azaltır, böylece üstbilgiyi kullanan kodun uygulamanın tüm ayrıntılarını ve yalnızca bunun için gereken diğer sınıfları / başlıkları bilmesi gerekmez. Bu, derleme sürelerini ve ayrıca uygulamadaki bir şey değiştiğinde gereken yeniden derleme miktarını azaltacaktır.

Başka bir neden, bir başlığın her sınıfa benzersiz bir kimlik vermesidir.

Yani bizim gibi bir şeyimiz varsa

class A {..};
class B : public A {...};

class C {
    include A.cpp;
    include B.cpp;
    .....
};

Projeyi inşa etmeye çalıştığımızda hatalarımız olacak, A B'nin bir parçası olduğundan, başlıklar ile bu tür baş ağrısından kaçınırız ...

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.