C++ में घोषणाएँ और परिभाषाएँ अक्सर अलग-अलग रखी जाती हैं। घोषणाएँ तथाकथित हेडर फाइलों में समूहित की जाती हैं, और उनसे जुड़े इम्प्लीमेंटेशन सोर्स फाइलों में रखे जाते हैं। हेडर फाइलों को आप एक API की तरह समझ सकते हैं। हेडर फाइल आपको बताती है कि किसी कोडबेस में क्या उपलब्ध है, बिना इस बारीकी में जाए कि वह कैसे काम करता है।
हेडर फाइलों का सबसे आम फाइल एक्सटेंशन .h है।
कुछ प्रोजेक्ट्स .hpp इस्तेमाल करते हैं, और कुछ एक्सटेंशन बिल्कुल छोड़ देते हैं।
परिभाषाएँ अलग .cpp फाइल में रखी जाती हैं।
इन हिस्सों को फिर से जोड़ने के लिए सोर्स फाइल की शुरुआत में संबंधित हेडर फाइल को इन्क्लूड किया जाता है।
मान लीजिए आप "quick_math" नाम की एक लाइब्रेरी लिखना चाहते हैं, जो "super_root" नाम का फंक्शन देती है और जिसे आप बार-बार इस्तेमाल करना चाहते हैं। तब फाइलें ऐसी दिखेंगी:
// A file named quick_math.h
#pragma once
namespace quick_math {
double super_root(double x, int n);
}
// A file named quick_math.cpp
#include "quick_math.h"
#include <cmath>
double quick_math::super_root(double x, int n) {
while(n) { x = std::sqrt(x), --n;}
return x;
}
अगर कोई हेडर सिर्फ इम्प्लीमेंटेशन के लिए ज़रूरी है, तो उसकी #include लाइन सिर्फ सोर्स फाइल में लिखनी होती है।
हेडर में इन्क्लूड की गई हर चीज़ .cpp फाइल में भी उपलब्ध रहती है, जैसे नीचे के उदाहरण में string लाइब्रेरी।
ध्यान दें: हेडर फाइल में घोषणा के बाद ; लगाना ज़रूरी है, लेकिन सोर्स फाइल में परिभाषा के बाद नहीं।
Exercism पर कई C++ अभ्यास दो लगभग खाली फाइलों से शुरू होते हैं: हेडर और सोर्स।
अभ्यास हल करने के लिए आपको *_test.cpp फाइल देखनी होगी, ताकि पता चले कि अपेक्षित फंक्शनों के नाम और नेमस्पेस क्या हैं।
क्लास बहुत जटिल हो सकती हैं, और हेडर तथा सोर्स के इस बँटवारे से उनका रिश्ता उलझा हुआ लग सकता है। एक तरीका यह है कि इम्प्लीमेंटेशन की सारी बारीकियाँ सोर्स फाइल में रहें, और सारी घोषणाएँ तथा मेंबर वेरिएबल हेडर में:
// A file named robot_flower.h
#if !defined(ROBOT_FLOWER_H)
#define ROBOT_FLOWER_H
#include <string>
namespace robots {
class Flower {
private:
bool needs_water{};
int size{};
std::string name{};
public:
Flower(std::string name, int size = 0);
void give_water();
std::string get_name();
int get_size();
void start_next_day();
};
}
#endif
// A file named robot_flower.cpp
#include "robot_flower.h"
robots::Flower::Flower(std::string name, int size) {this->name = "Robotica " + name; this->size = size;}
void robots::Flower::start_next_day() {if (!needs_water) ++size; needs_water = true;}
std::string robots::Flower::get_name() {return name;}
int robots::Flower::get_size() {return size;}
जब हेडर को API के अवलोकन की तरह इस्तेमाल किया जाता है, तो डिफॉल्ट वैल्यू जैसी जानकारी लोग वहीं ढूँढ़ते हैं।
इसीलिए कंस्ट्रक्टर के size पैरामीटर का डिफॉल्ट हेडर में संभाला जाता है, इम्प्लीमेंटेशन में नहीं।
सोर्स फाइल में हर परिभाषा से पहले नेमस्पेस robots और क्लास टाइप Flower लिखा जाता है।
दूसरा तरीका हेडर ओनली लाइब्रेरी का है, जिसमें .cpp फाइल बिल्कुल नहीं होती:
// A file named robot_flower.h
#pragma once
#include <string>
namespace robots {
class Flower {
private:
bool needs_water{};
int size{};
std::string name{};
public:
Flower(std::string name, int size = 0) {this->name = "Robotica " + name; this->size = size;}
void give_water() {needs_water = false;}
std::string get_name() {return name;}
int get_size() {return size;}
void start_next_day() {if (!needs_water) ++size; needs_water = true;}
};
}
प्रोजेक्ट्स इन तरीकों का मेल भी इस्तेमाल कर सकते हैं, और इस पर खूब चर्चा होती है कि किस काम के लिए कौन-सा तरीका सबसे अच्छा बैठता है।
ऊपर दिए गए उदाहरण हेडर फाइल में आपने #pragma once लाइन देखी होगी।
इसे इन्क्लूड गार्ड कहते हैं। यह सुनिश्चित करता है कि कंपाइलेशन के दौरान फाइल की सामग्री सिर्फ एक बार इन्क्लूड हो, ताकि एरर न हों।
इन्क्लूड गार्ड का एक और रूप भी है, जो ज़्यादा जटिल है और #ifndef से शुरू होकर #endif पर खत्म होता है।
यह भी वही काम करता है, और इसका इस्तेमाल ऊपर दिए गए Flower क्लास के उदाहरण में दिखाया गया है।
आज ऑस्ट्रेलिया की "Doctor Data" नाम की कंपनी में आपका पहला दिन है, जो सूचना वापस पाने के काम में विशेषज्ञता रखती है। नौकरी के इंटरव्यू में आपने C++ और von Neumann probes की अपनी जानकारी के दम पर शानदार प्रदर्शन किया। चूँकि आपने बहुत सारी टेस्ट फाइलें देखी हैं, इसलिए आपके नए बॉस चाहते हैं कि आप कंपनी द्वारा हाल ही में वापस पाए गए कुछ टेस्ट कोड से संबंधित सोर्स और हेडर फाइलें फिर से बनाएँ।
इस अभ्यास में आप खोई हुई फाइलें फिर से बनाएँगे। फिलहाल इन फाइलों में जो सामग्री है, वह बेकार डेटा है और किसी काम की नहीं।
इस कॉन्सेप्ट अभ्यास का वर्कफ्लो Exercism के प्रैक्टिस अभ्यासों के ढाँचे से बहुत मिलता-जुलता है। अभ्यास का परिचय स्पेसिफिकेशन का बस एक हिस्सा भर है। किसी दी गई समस्या को हल करने के लिए टेस्ट फाइल ही आपकी निर्णायक मार्गदर्शिका है। C++ में कंपाइलेशन जिस तरह से होता है, उसकी वजह से टेस्ट के नतीजे वेब इंटरफेस पर तब तक दिखाई नहीं दे सकते, जब तक आप टेस्ट में मांगी गई हर क्लास, हर फंक्शन और हर एन्यूमरेशन का एक छोटा-सा रूप लागू न कर दें। अगर आपको कंपाइलेशन एरर मिलती हैं, तो सारे टास्क और टेस्ट पूरे कर लेने के बाद वे अपने आप गायब हो सकती हैं।
आपके सामने चार टास्क हैं, और सबका संबंध हेडर तथा सोर्स फाइलों में खोए हुए कोड को वापस पाने से है।
आप टेस्ट फाइलों से वापस पाई गई दो लाइनें देखते हैं:
heaven::Vessel bob{"Robert Johansson", 1};
heaven::Vessel will{"Riker", 2, star_map::System::BetaHydri};
आपकी तेज़ नज़र ने तुरंत एक नेमस्पेस heaven पहचान लिया।
आपको यह भी दिखता है कि यहाँ Vessel नाम की एक क्लास होनी चाहिए।
ऐसा लगता है कि कंस्ट्रक्टर को दो या तीन आर्गुमेंट के साथ कॉल किया जा सकता है।
पहला आर्गुमेंट string टाइप का लगता है और दूसरा एक संख्या है।
Vessel क्लास को तीसरे आर्गुमेंट के साथ भी इनिशियलाइज़ किया जा सकता है।
तीसरा आर्गुमेंट star_map नेमस्पेस से आता है।
यह System टाइप का एक एन्यूमरेटर है।
एक एन्यूमरेशन तो आपको मिल ही गया है: BetaHydri.
अब मिली हुई जानकारी के आधार पर सोर्स और हेडर फाइलें तैयार कीजिए।
आपको दो नेमस्पेस चाहिए: heaven और star_map।
heaven नेमस्पेस में Vessel नाम की एक क्लास होनी चाहिए, जिसे दो या तीन आर्गुमेंट के साथ कॉल किया जा सके।
System एनम को star_map नेमस्पेस में रखना है, और उसमें BetaHydri एन्यूमरेशन होना चाहिए।
दूसरे एन्यूमरेशन पर भी नज़र रखिए, ताकि आप System एनम को समय-समय पर अपडेट करते रहें।
आपको टेस्ट की कुछ और लाइनें मिलती हैं:
heaven::Vessel bob{"Robert Johansson", 1};
REQUIRE(bob.current_system == star_map::System::Sol);
REQUIRE(bob.generation == 1);
heaven::Vessel bob5 = bob.replicate("Mario");
REQUIRE(bob5.current_system == star_map::System::Sol);
REQUIRE(bob5.generation == 2);
नई मिली ये टेस्ट लाइनें Vessel क्लास का एक और मेंबर फंक्शन सामने लाती हैं: replicate.
यह फंक्शन एक स्ट्रिंग लेता है और एक और Vessel इंस्टेंस लौटाता है।
पिछले टास्क से आपको कंस्ट्रक्टर के तीसरे आर्गुमेंट की डिफॉल्ट वैल्यू का अंदाज़ा मिल जाता है।
साथ ही आपको Vessel क्लास के दो पब्लिक मेंबर वेरिएबल और replicate मेंबर फंक्शन का स्पेसिफिकेशन भी दिख जाता है।
हेडर और सोर्स फाइलों में replicate फंक्शन तथा पब्लिक मेंबर वेरिएबल current_system और generation जोड़िए।
वापस पाई गई टेस्ट फाइलों में आपको कुछ और दिलचस्प लाइनें मिलती हैं:
heaven::Vessel bob6{"Homer", 3, star_map::System::EpsilonEridani};
REQUIRE(bob6.busters == 0);
bob6.make_buster();
REQUIRE(bob6.busters == 1);
bool success = bob6.shoot_buster();
REQUIRE(success);
REQUIRE(bob6.busters == 0);
success = bob6.shoot_buster();
REQUIRE_FALSE(success);
लगता है कि Vessel क्लास में एक मेंबर वेरिएबल busters है, जिसे क्लास के दो मेंबर फंक्शन make_buster और shoot_buster बदल सकते हैं।
जब तक कोई और जानकारी सामने न आए, आप अंदाज़ा लगाते हैं कि make_buster फंक्शन void लौटाता है।
चूँकि shoot_buster की रिटर्न वैल्यू के लिए एक टेस्ट मौजूद है, आप मान लेते हैं कि यह फंक्शन bool लौटाता है।
Vessel क्लास में ये दोनों फंक्शन और यह मेंबर वेरिएबल जोड़िए।
दूसरे System एन्यूमरेटर भी ढूँढते रहिए।
टेस्ट फाइलों को छानते समय आपको कोड के सिर्फ दो हिस्से ही ऐसे मिलते हैं जो अब तक सामने नहीं आए थे:
heaven::Vessel bob1{"Bob", 1, star_map::System::AlphaCentauri};
heaven::Vessel marv{"Marvin", 2, star_map::System::DeltaEridani};
heaven::Vessel milo{"Milo", 3, star_map::System::DeltaEridani};
heaven::Vessel howie{"Howard", 4, star_map::System::Omicron2Eridani};
REQUIRE("Bob" == heaven::get_older_bob(bob1, marv));
REQUIRE(heaven::in_the_same_system(marv, milo));
REQUIRE_FALSE(heaven::in_the_same_system(marv, howie));
आपको दो ऐसे फंक्शन दिखाई देते हैं जो Vessel क्लास के मेंबर नहीं हैं, क्योंकि इन्हें किसी इंस्टेंस के साथ कॉल नहीं किया जाता।
get_older_bob दो Vessel इंस्टेंस की तुलना करता है और एक स्ट्रिंग लौटाता है।
in_the_same_system दो Vessel इंस्टेंस की तुलना करता है और एक bool लौटाता है।
ऊपर वापस पाई गई लाइनों से बाकी बचे फंक्शन लागू कीजिए।
Exercism पर साइन अप कीजिए और C++ को 19 कॉन्सेप्ट100 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।