हेडर फाइलें और .cpp फाइलें क्यों हैं? [बन्द है]


484

C ++ में हेडर फाइलें और .cpp फाइलें क्यों होती हैं?


3
संबंधित प्रश्न: stackoverflow.com/questions/1945846/…
Spoike

यह एक सामान्य OOP प्रतिमान है। यह एक वर्ग की घोषणा है और परिभाषा होने के कारण cpp। किसी को यह जानने की जरूरत नहीं है कि इसे कैसे लागू किया जाता है, उसे केवल इंटरफ़ेस जानना चाहिए।
मनीष काकती

यह c ++ का सबसे अच्छा हिस्सा है जो कार्यान्वयन से इंटरफ़ेस को अलग करता है। यह हमेशा एकल फ़ाइल में सभी कोड रखने के बजाय अच्छा है, हमारे पास इंटरफ़ेस अलग है। कोड की कुछ मात्रा हमेशा इनलाइन फ़ंक्शन की तरह होती है जो हेडर फ़ाइलों का हिस्सा होती है। जब कोई शीर्ष लेख फ़ाइल देखी जाती है, तो घोषित कार्यों और वर्ग चर की सूची प्रदर्शित होती है।
मियांक

ऐसे समय होते हैं जहां हेडर फाइलें संकलन के लिए आवश्यक होती हैं - न कि केवल एक संगठन वरीयता या पूर्व-संकलित पुस्तकालयों को वितरित करने का तरीका। कहते हैं कि आपके पास एक संरचना है जहाँ game.c BOTH Phys.c और math.c पर निर्भर करता है; Phys.c.c भी math.c पर निर्भर करता है। यदि आप .c फ़ाइलों को शामिल करते हैं और .h फ़ाइलों को हमेशा के लिए भूल जाते हैं, तो आपके पास math.c से नकली घोषणाएँ होंगी और संकलन की कोई उम्मीद नहीं होगी। यह वही है जो मुझे सबसे अधिक समझ में आता है कि हेडर फाइलें क्यों महत्वपूर्ण हैं। आशा है कि यह किसी और की मदद करता है।
सैमी बेनचेरीफ 19

मुझे लगता है कि यह इस तथ्य के साथ करना है कि एक्सटेंशन में केवल अल्फ़ान्यूमेरिकल वर्णों की अनुमति है। मैं यह भी नहीं जानता कि क्या यह सच है, बस अनुमान लगा रहा है
user12211554

जवाबों:


201

खैर, इसका मुख्य कारण इंटरफ़ेस को कार्यान्वयन से अलग करना होगा। शीर्ष लेख "क्या" को एक वर्ग घोषित करता है (या जो कुछ भी कार्यान्वित किया जा रहा है) करेगा, जबकि सीपीपी फ़ाइल उन विशेषताओं को "कैसे" परिभाषित करेगी।

यह निर्भरता कम करता है ताकि कोड का उपयोग करने वाले कोड को कार्यान्वयन के सभी विवरणों और किसी अन्य वर्ग / हेडर को केवल उसी के बारे में जानने की आवश्यकता न हो। यह संकलन समय को कम करेगा और कार्यान्वयन में कुछ बदलाव होने पर आवश्यक पुन: प्राप्ति की मात्रा भी।

यह बिल्कुल सही नहीं है, और आप आमतौर पर इंटरफ़ेस और कार्यान्वयन को ठीक से अलग करने के लिए Pimpl Idiom जैसी तकनीकों का सहारा लेंगे , लेकिन यह एक अच्छी शुरुआत है।


177
वास्तव में सच नहीं है। शीर्षलेख में अभी भी कार्यान्वयन का एक प्रमुख हिस्सा है। कब से निजी उदाहरण चर एक वर्ग के इंटरफ़ेस का हिस्सा थे? निजी सदस्य कार्य? फिर वे सार्वजनिक रूप से दिखाई देने वाले हेडर में क्या कर रहे हैं? और यह टेम्प्लेट के साथ और अलग हो जाता है।
jalf

13
इसलिए मैंने कहा कि यह बिल्कुल सही नहीं है, और अधिक अलगाव के लिए पिंपल मुहावरे की आवश्यकता है। टेम्प्लेट कीड़े की एक पूरी तरह से अलग कर सकते हैं - भले ही "निर्यात" कीवर्ड पूरी तरह से अधिकांश संकलक में समर्थित था, फिर भी यह वास्तविक पृथक्करण के बजाय मुझे सिंटैक्टिक चीनी होगा।
जोरिस टिम्मरमन्स

4
अन्य भाषाएं इसे कैसे संभालती हैं? उदाहरण के लिए - जावा? जावा में हेडर फाइल कांसेप्ट नहीं है।
लेज़र

8
@ लेज़र: जावा पार्स करने के लिए सरल है। जावा कंपाइलर अन्य फ़ाइलों में सभी वर्गों को जाने बिना किसी फ़ाइल को पार्स कर सकता है, और बाद में प्रकारों की जांच कर सकता है। C ++ में बहुत सारे निर्माण बिना प्रकार की जानकारी के अस्पष्ट हैं, इसलिए C ++ कंपाइलर को किसी फ़ाइल को पार्स करने के लिए संदर्भित प्रकारों की जानकारी चाहिए। इसलिए इसे हेडर की जरूरत है।
निकी

15
@ मिक्की: पार्सिंग के "सहज" से क्या लेना-देना है? यदि जावा में एक व्याकरण था जो कम से कम सी ++ के रूप में जटिल था, तो यह अभी भी जावा फाइलों का उपयोग कर सकता है। किसी भी मामले में, सी के बारे में क्या? सी को पार्स करना आसान है, फिर भी हेडर और सी दोनों फ़ाइलों का उपयोग करता है।
थॉमस एडिंग

609

सी ++ संकलन

C ++ में एक संकलन 2 प्रमुख चरणों में किया जाता है:

  1. पहला "स्रोत" टेक्स्ट फ़ाइलों को बाइनरी "ऑब्जेक्ट" फ़ाइलों में संकलित करना है: सीपीपी फ़ाइल संकलित फ़ाइल है और अन्य सीपीपी फ़ाइलों (या यहां तक ​​कि पुस्तकालयों) के बारे में किसी भी ज्ञान के बिना संकलित की जाती है, जब तक कि इसे कच्ची घोषणा के माध्यम से नहीं खिलाया जाता है या हेडर समावेश। CPP फ़ाइल को आमतौर पर .OBJ या a .O "ऑब्जेक्ट" फ़ाइल में संकलित किया जाता है।

  2. दूसरी सभी "ऑब्जेक्ट" फ़ाइलों को एक साथ जोड़ना है, और इस प्रकार, अंतिम बाइनरी फ़ाइल (या तो एक पुस्तकालय या एक निष्पादन योग्य) का निर्माण।

इस सारी प्रक्रिया में एचपीपी कहाँ फिट बैठता है?

एक गरीब अकेला CPP फ़ाइल ...

प्रत्येक CPP फ़ाइल का संकलन अन्य सभी CPP फ़ाइलों से स्वतंत्र है, जिसका अर्थ है कि यदि A.CPP को B.CPP में परिभाषित प्रतीक की आवश्यकता है, जैसे:

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

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

यह संकलित नहीं करेगा क्योंकि A.CPP के पास "doSomethingElse" को जानने का कोई तरीका नहीं है ... जब तक A.CPP में कोई घोषणा नहीं होती, जैसे:

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

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

फिर, यदि आपके पास C.CPP है जो समान प्रतीक का उपयोग करता है, तो आप घोषणा को कॉपी / पेस्ट करते हैं ...

COPY / PASTE ALERT!

हां, एक समस्या है। कॉपी / पेस्ट खतरनाक हैं, और बनाए रखना मुश्किल है। जिसका अर्थ है कि अगर हम कॉपी / पेस्ट न करने और अभी भी प्रतीक घोषित करने का कोई तरीका है तो यह अच्छा होगा ... हम इसे कैसे कर सकते हैं? कुछ टेक्स्ट फ़ाइल को शामिल करके, जो आमतौर पर .h, .hxx, .h ++ या, मेरी पसंदीदा है, C ++ फ़ाइलों के लिए, .hpp:

// 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
}

कैसे includeकाम करता है ?

एक फ़ाइल सहित, संक्षेप में, पार्स और फिर सीपीपी फ़ाइल में इसकी सामग्री को कॉपी-पेस्ट करें।

उदाहरण के लिए, A.HPP हेडर के साथ निम्नलिखित कोड में:

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

... स्रोत B.CPP:

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

void doSomething()
{
   // Etc.
}

... समावेश के बाद बन जाएगा:

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

void doSomething()
{
   // Etc.
}

एक छोटी सी बात - B.CP में B.HPP क्यों शामिल है?

वर्तमान मामले में, इसकी आवश्यकता नहीं है, और B.HPP में doSomethingElseफ़ंक्शन डिक्लेरेशन है, और B.CPP के पास doSomethingElseफंक्शन डेफिनेशन है (जो कि स्वयं एक डिक्लेरेशन है)। लेकिन एक अधिक सामान्य मामले में, जहां B.HPP का उपयोग घोषणाओं (और इनलाइन कोड) के लिए किया जाता है, कोई संगत परिभाषा नहीं हो सकती है (उदाहरण के लिए, enums, सादे संरचनाएं, आदि), इसलिए यदि बीसीपीपी शामिल हो तो इसकी आवश्यकता हो सकती है। B.HPP से उन घोषणाओं का उपयोग करता है। सभी में, यह एक स्रोत के लिए "अच्छा स्वाद" है जो डिफ़ॉल्ट रूप से अपने हेडर को शामिल करता है।

निष्कर्ष

हेडर फ़ाइल इस प्रकार आवश्यक है, क्योंकि C ++ कंपाइलर अकेले प्रतीक घोषणाओं की खोज करने में असमर्थ है, और इस प्रकार, आपको उन घोषणाओं को शामिल करके इसकी मदद करनी चाहिए।

एक अंतिम शब्द: आपको अपनी एचपीपी फ़ाइलों की सामग्री के चारों ओर हेडर गार्ड्स लगाने चाहिए, यह सुनिश्चित करने के लिए कि कई समावेश कुछ भी नहीं तोड़ेंगे, लेकिन सभी में, मेरा मानना ​​है कि एचपीपी फाइलों के अस्तित्व का मुख्य कारण ऊपर बताया गया है।

#ifndef B_HPP_
#define B_HPP_

// The declarations in the B.hpp file

#endif // B_HPP_

या सरल भी

#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?कोई ज़रूरत नहीं है। जब तक CPP "HPP" को शामिल करता है, तब तक Precompiler स्वचालित रूप से HPP फ़ाइल की सामग्री की कॉपी-पेस्ट CPP फ़ाइल में करेगा। मैंने स्पष्ट करने के लिए उत्तर को अद्यतन किया।
पियरसबल

7
@Bob: While compiling A.cpp, compiler knows the types of arguments and return value of doSomethingElse from the call itself। नहीं, यह नहीं है। यह केवल उपयोगकर्ता द्वारा प्रदान किए गए प्रकारों को जानता है, जो कि आधा समय, वापसी मूल्य को पढ़ने के लिए परेशान नहीं करेगा। फिर, निहितार्थ रूपांतरण होते हैं। और फिर, जब आपके पास कोड होगा: foo(bar)आप यह भी सुनिश्चित नहीं कर सकते हैं कि fooयह एक फ़ंक्शन है। इसलिए संकलक को हेडर में जानकारी तक पहुंचना होगा ताकि यह तय किया जा सके कि स्रोत सही तरीके से संकलित करता है या नहीं ... फिर, कोड संकलित होने के बाद, लिंकर सिर्फ एक साथ फ़ंक्शन कॉल करेगा।
पियरसबल

3
@ याकूब: [जारी] ... अब, लिंकर कंपाइलर द्वारा किए गए काम को कर सकता है, मुझे लगता है, जो तब आपके विकल्प को संभव बना देगा। (मुझे लगता है कि यह अगले मानक के लिए "मॉड्यूल" प्रस्ताव का विषय है)। Seems, they're just a pretty ugly arbitrary design.: यदि C ++ 2012 में बनाया गया था, तो वास्तव में। लेकिन याद रखें कि C ++ को 1980 के दशक में C पर बनाया गया था, और उस समय, बाधाएं उस समय (IIRC) से काफी अलग थीं, इसे सी के बजाय उसी लिंकर्स को रखने के लिए गोद लेने के उद्देश्यों के लिए तय किया गया था।
पीरसेबल

1
@paercebal स्पष्टीकरण और नोट के लिए धन्यवाद, paercebal! मुझे यकीन नहीं हो रहा है, कि foo(bar)यह एक फ़ंक्शन है - अगर यह एक पॉइंटर के रूप में प्राप्त किया गया है? वास्तव में, खराब डिज़ाइन की बात करते हुए, मैं C को दोष देता हूं, C ++ को नहीं। मुझे वास्तव में शुद्ध C की कुछ अड़चनें पसंद नहीं हैं, जैसे कि हेडर फाइल्स होना या फ़ंक्शंस का एक और केवल एक मान होना, जबकि इनपुट पर कई तर्क देना (इनपुट और आउटपुट का एक समान व्यवहार करना स्वाभाविक नहीं लगता है) ; कई तर्क क्यों, लेकिन एकल आउटपुट?) :)
बोरिस बुर्कोव

1
@ गोबो:: Why can't I be sure, that foo(bar) is a functionफू एक प्रकार हो सकता है, इसलिए आपके पास एक क्लास कंस्ट्रक्टर होगा। In fact, speaking of bad design, I blame C, not C++: मैं बहुत सी चीजों के लिए सी को दोष दे सकता हूं, लेकिन 70 के दशक में डिजाइन किया गया है, उनमें से एक नहीं होगा। फिर से, उस समय की अड़चनें ... such as having header files or having functions return one and only one value: ट्यूपल्स को कम करने में मदद मिल सकती है, साथ ही संदर्भ द्वारा तर्क पारित कर सकते हैं। अब, कई मानों को वापस लाने के लिए सिंटैक्स क्या होगा, और क्या यह भाषा को बदलने के लिए इसके लायक होगा?
पियरसबल

93

क्योंकि C, जहां अवधारणा उत्पन्न हुई, 30 साल पुरानी है, और फिर, यह कई फ़ाइलों से कोड को एक साथ जोड़ने का एकमात्र व्यवहार्य तरीका था।

आज, यह एक भयानक हैक है जो पूरी तरह से C ++ में संकलन समय को नष्ट कर देता है, अनगिनत अनावश्यक निर्भरता का कारण बनता है (क्योंकि हेडर फ़ाइल में वर्ग परिभाषाएं कार्यान्वयन के बारे में बहुत अधिक जानकारी उजागर करती हैं), और इसी तरह।


3
मुझे आश्चर्य है कि हेडर फाइलें (या जो कुछ भी वास्तव में संकलन / लिंकिंग के लिए आवश्यक थी) केवल "ऑटो-जेनरेट" नहीं थीं?
मतीन उलहाक

54

क्योंकि C ++ में, अंतिम निष्पादन योग्य कोड किसी भी प्रतीक की जानकारी नहीं रखता है, यह अधिक या कम शुद्ध मशीन कोड है।

इस प्रकार, आपको कोड के एक टुकड़े के इंटरफ़ेस का वर्णन करने का एक तरीका चाहिए, जो कोड से ही अलग है। यह विवरण हेडर फ़ाइल में है।


16

क्योंकि सी ++ ने उन्हें सी। दुर्भाग्य से विरासत में मिला।


4
C से C ++ का उत्तराधिकार दुर्भाग्यपूर्ण क्यों है?
लोकेश

3
@ लोकेश अपने सामान की वजह से :(

1
इसका उत्तर कैसे हो सकता है?
शुवो सरकार

14
@ShuvoSarker क्योंकि हजारों भाषाओं ने प्रदर्शन किया है, इसलिए सी ++ बनाने वाले प्रोग्रामर के लिए दो बार फ़ंक्शन हस्ताक्षर लिखने के लिए कोई तकनीकी स्पष्टीकरण नहीं है। का जवाब "क्यों?" "इतिहास" है।
बोरिस

15

क्योंकि पुस्तकालय के प्रारूप को डिजाइन करने वाले लोग C प्रीप्रोसेसर मैक्रोज़ और फ़ंक्शन घोषणाओं जैसी शायद ही कभी उपयोग की जाने वाली जानकारी के लिए "बेकार" स्थान नहीं चाहते थे।

चूंकि आपको अपने संकलक को यह बताने के लिए उस जानकारी की आवश्यकता है "यह फ़ंक्शन बाद में उपलब्ध है जब लिंकर अपना काम कर रहा है", उन्हें दूसरी फाइल के साथ आना पड़ा जहां इस साझा जानकारी को संग्रहीत किया जा सकता है।

C / C ++ के बाद अधिकांश भाषाएं इस जानकारी को आउटपुट (उदाहरण के लिए जावा बाइटकोड) में संग्रहीत करती हैं, या वे पहले से तैयार किए गए प्रारूप का उपयोग नहीं करते हैं, हमेशा स्रोत रूप में वितरित किए जाते हैं और मक्खी (पायथन, पर्ल) पर संकलित करते हैं।


काम नहीं करेंगे, चक्रीय संदर्भ। Ieyou b.lib से b.lib बनाने से पहले a.cpp से a.lib का निर्माण नहीं कर सकता, लेकिन आप a.lib से पहले b.lib का निर्माण नहीं कर सकते।
मसलक 14:

20
जावा ने हल किया कि, पायथन यह कर सकता है, कोई भी आधुनिक भाषा इसे कर सकती है। लेकिन जिस समय C का आविष्कार हुआ था, उस समय RAM इतनी महंगी और दुर्लभ थी, यह सिर्फ एक विकल्प नहीं था।
आरोन दिगुल्ला

6

यह इंटरफेस घोषित करने का पूर्व तरीका है। आपने हेडर फ़ाइल में इंटरफ़ेस (विधि घोषणाओं), और कार्यान्वयन को सीपीपी में डाल दिया। आपके पुस्तकालय का उपयोग करने वाले अनुप्रयोगों को केवल इंटरफ़ेस को जानना होगा, जिसे वे #include के माध्यम से एक्सेस कर सकते हैं।


4

अक्सर आप पूरे कोड को शिप किए बिना एक इंटरफ़ेस की परिभाषा रखना चाहेंगे। उदाहरण के लिए, यदि आपके पास एक साझा पुस्तकालय है, तो आप इसके साथ एक हेडर फ़ाइल को शिप करेंगे, जो साझा लाइब्रेरी में उपयोग किए जाने वाले सभी कार्यों और प्रतीकों को परिभाषित करता है। हेडर फ़ाइलों के बिना, आपको स्रोत को शिप करना होगा।

एक ही परियोजना के भीतर, हेडर फ़ाइलों का उपयोग किया जाता है, कम से कम दो उद्देश्यों के लिए IMHO:

  • स्पष्टता, अर्थात् इंटरफेस को कार्यान्वयन से अलग रखने से, कोड को पढ़ना आसान होता है
  • संकलन का समय। पूर्ण कार्यान्वयन के बजाय जहां संभव हो केवल इंटरफ़ेस का उपयोग करके, संकलन समय को कम किया जा सकता है क्योंकि कंपाइलर केवल वास्तविक कोड को पार्स करने के बजाय इंटरफ़ेस का संदर्भ बना सकता है (जो, विचारपूर्वक, केवल करने की आवश्यकता होगी एक बार)।

3
पुस्तकालय विक्रेता सिर्फ एक उत्पन्न "हेडर" फ़ाइल क्यों नहीं भेज सकते हैं? पूर्व-प्रोसेसर मुक्त "हेडर" फ़ाइल को बहुत बेहतर प्रदर्शन देना चाहिए (जब तक कि कार्यान्वयन वास्तव में टूट नहीं गया था)।
टॉम हॉल्टिन -

मुझे लगता है कि इसकी अप्रासंगिक अगर हेडर फ़ाइल जेनरेट होती है या हाथ से लिखी जाती है, तो सवाल यह नहीं था कि "लोग हेडर फाइलें खुद क्यों लिखते हैं?", यह "हमारे पास हेडर फाइलें क्यों होती हैं"। वही प्रीप्रोसेसर मुक्त हेडर के लिए जाता है। यकीन है, यह तेजी से होगा।

-5

MadKeithV के उत्तर का जवाब देते हुए ,

यह निर्भरता कम करता है ताकि कोड का उपयोग करने वाले कोड को कार्यान्वयन के सभी विवरणों और किसी अन्य वर्ग / हेडर को केवल उसी के बारे में जानने की आवश्यकता न हो। यह संकलन समय को कम करेगा, और कार्यान्वयन में कुछ बदलाव होने पर आवश्यक पुन: प्राप्ति की मात्रा भी।

एक और कारण यह है कि एक हेडर प्रत्येक वर्ग को एक अद्वितीय आईडी देता है।

तो अगर हमारे पास ऐसा कुछ है

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

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

हम त्रुटि करेंगे, जब हम प्रोजेक्ट बनाने की कोशिश करते हैं, क्योंकि ए बी का हिस्सा है, हेडर के साथ हम इस तरह के सिरदर्द से बचेंगे ...

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.