सारांश
अब तक पाठ्यक्रम का हर प्रोग्राम एक ही फ़ाइल में समा जाता था। हज़ार पंक्तियों का प्रोग्राम एक बार में नहीं पढ़ा जा सकता, और उसके फ़ंक्शन दूसरे प्रोग्रामों के काम आने योग्य होते हैं। यह पाठ उसे बाँटना सिखाता है: एक हेडर, जो घोषित करता है कि मॉड्यूल क्या देता है; एक कार्यान्वयन फ़ाइल, जो उसे परिभाषित करती है; और एक main.cpp, जो उसका उपयोग करती है। यह इन्क्लूड गार्ड प्रस्तुत करता है; एक परिभाषा नियम, जो लिंकर की दो क्लासिक त्रुटियों और inline के आधुनिक अर्थ को समझाता है; पृथक कंपाइलेशन, जो केवल वही दोबारा कंपाइल करता है जो बदला है; और अंत में पहले के पाठों के फ़ंक्शनों से बनी अपनी लाइब्रेरी पर समाप्त होता है।
अधिगम उद्देश्य
- प्रोग्राम को घोषणाओं वाले हेडर, एक कार्यान्वयन फ़ाइल और एक
main.cppमें बाँटना, और उसे कंपाइल करना। - इन्क्लूड गार्ड लिखना और समझाना कि वे कौन-सी समस्या हल करते हैं;
#pragma onceको ऐसे एक्सटेंशन के रूप में पहचानना जिसे सभी कंपाइलर स्वीकार करते हैं, पर जो मानक में शामिल नहीं है। - एक परिभाषा नियम को समझाना और लिंकर के संदेश (“multiple definition”, “undefined reference”) से उसके उल्लंघनों का निदान करना।
-cके साथ अलग-अलग कंपाइल करना और ऑब्जेक्ट फ़ाइलों को लिंक करना, यह समझाते हुए कि हर फ़ाइल बदलने पर क्या दोबारा कंपाइल होता है।
इंटरफ़ेस और कार्यान्वयन
मॉड्यूलरता
अनुबंध वाला फ़ंक्शन, जैसे पाठ फ़ंक्शनों से डिज़ाइन के फ़ंक्शन, पहले से ही अलग रखता है कि वह क्या वादा करता है (जो उसकी घोषणा और अनुबंध में लिखा है) और उसे कैसे निभाता है (जो उसके बॉडी में लिखा है); कॉल करने वाले को केवल पहली बात चाहिए। यदि ऐसा है, तो दोनों भागों को अलग-अलग फ़ाइलों में रखने से कुछ नहीं रोकता। मॉड्यूल साझा उद्देश्य वाले फ़ंक्शनों (और उनके उपयोग किए प्रकारों) का समूह है; उसका इंटरफ़ेस वह है जो वह उपयोग करने वाले को देता है, और उसका कार्यान्वयन वह तरीका है जिससे वह यह करता है। C++ में इंटरफ़ेस एक हेडर में लिखा जाता है (एक्सटेंशन .hpp, या C से विरासत में मिला .h) और कार्यान्वयन उसी नाम की .cpp फ़ाइल में।
इससे तीन लाभ निकलते हैं। इसे भागों में समझा जा सकता है: मॉड्यूल का उपयोग करने के लिए उसका हेडर पढ़ना काफ़ी है। इसे भागों में कंपाइल किया जा सकता है: कार्यान्वयन में बदलाव केवल इंटरफ़ेस का उपयोग करने वालों को दोबारा कंपाइल करने पर मजबूर नहीं करता। और इसका भागों में, हर अनुबंध के सामने, परीक्षण किया जा सकता है, जैसा पाठ प्रोग्रामों का परीक्षण सिखाएगा। “शैक्षणिक लेखन के लिए LaTeX” पाठ्यक्रम भी अपने पाठ “लंबे दस्तावेज़” में किसी लंबे दस्तावेज़ को इसी तरह बाँटता है।
तीन फ़ाइलें
इस उदाहरण का समतल ज्यामिति मॉड्यूल तीन फ़ंक्शन देता है; उनमें से दो पाठ 5.3 से आते हैं। हेडर उन्हें केवल घोषित करता है, हर एक को उसके अनुबंध के साथ। # से शुरू होने वाली तीन पंक्तियाँ आगे आने वाले अनुभाग में समझाई जाएँगी; अभी इतना जानना काफ़ी है कि हर हेडर में वे होती हैं।
// geometry.hpp: समतल ज्यामिति मॉड्यूल का इंटरफ़ेस।#ifndef GEOMETRY_HPP#define GEOMETRY_HPP// त्रिज्या radius वाले वृत्त का क्षेत्रफल।// पूर्व-शर्त: radius >= 0।// पश्च-शर्त: त्रिज्या radius वाले वृत्त का क्षेत्रफल लौटाता है।double circleArea(double radius);// क्या तीन लंबाइयाँ किसी त्रिभुज की भुजाएँ हैं।// पूर्व-शर्त: कोई नहीं।// पश्च-शर्त: true लौटाता है यदि और केवल यदि भुजाओं a, b और c वाला त्रिभुज मौजूद है।bool isTriangle(double a, double b, double c);// भुजाओं a, b और c वाले त्रिभुज का क्षेत्रफल।// पूर्व-शर्त: isTriangle(a, b, c)।// पश्च-शर्त: भुजाओं a, b और c वाले त्रिभुज का क्षेत्रफल लौटाता है (हीरोन का सूत्र)।double triangleArea(double a, double b, double c);#endifकार्यान्वयन फ़ाइल अपने ही हेडर को इन्क्लूड करती है और फ़ंक्शनों को परिभाषित करती है। उसे जिन मानक लाइब्रेरी हेडरों की ज़रूरत है (वर्गमूल के लिए <cmath>, के लिए <numbers>) उन्हें यहीं इन्क्लूड किया जाता है, हेडर में नहीं, क्योंकि मॉड्यूल का उपयोग करने वाले को उनकी ज़रूरत नहीं होती।
// geometry.cpp: समतल ज्यामिति मॉड्यूल का कार्यान्वयन।#include "geometry.hpp"#include <cmath>#include <numbers>double circleArea(double radius){ return std::numbers::pi * radius * radius;}bool isTriangle(double a, double b, double c){ return a > 0 && b > 0 && c > 0 && a < b + c && b < a + c && c < a + b;}double triangleArea(double a, double b, double c){ const double s{(a + b + c) / 2}; return std::sqrt(s * (s - a) * (s - b) * (s - c));}अंत में, main.cpp हेडर को इन्क्लूड करती है और मॉड्यूल का उपयोग करती है, उसके कार्यान्वयन की एक भी पंक्ति देखे बिना:
#include <print>#include "geometry.hpp"int main(){ std::println("त्रिज्या 2 वाला वृत्त: {:.4f}", circleArea(2.0)); std::println("क्या भुजाओं 3, 4, 5 वाला त्रिभुज है? {}", isTriangle(3.0, 4.0, 5.0)); std::println("उसका क्षेत्रफल: {}", triangleArea(3.0, 4.0, 5.0)); std::println("और भुजाओं 1, 2, 3 वाला? {}", isTriangle(1.0, 2.0, 3.0));}त्रिज्या 2 वाला वृत्त: 12.5664क्या भुजाओं 3, 4, 5 वाला त्रिभुज है? trueउसका क्षेत्रफल: 6और भुजाओं 1, 2, 3 वाला? false#include "geometry.hpp" के उद्धरण-चिह्न फ़ाइल को सबसे पहले उस फ़ाइल के फ़ोल्डर में खोजते हैं जो उसे इन्क्लूड करती है, और #include <print> के कोण-कोष्ठक सिस्टम के फ़ोल्डरों में: उद्धरण-चिह्न अपनी फ़ाइलों के लिए हैं। फ़ाइल geometry.cpp अपना हेडर सबसे पहले इन्क्लूड करती है, ताकि यदि हेडर में कोई ऐसा इन्क्लूड छूट गया हो जिसकी उसे स्वयं ज़रूरत है, तो त्रुटि मॉड्यूल को कंपाइल करते समय दिखे, किसी और की फ़ाइल में नहीं। और भुजाओं 1, 2 और 3 वाला त्रिभुज मौजूद नहीं है (), इसलिए triangleArea की पूर्व-शर्त पहले यह पूछने को बाध्य करती है।
कंपाइल करने के लिए कमांड दोनों .cpp फ़ाइलें लेती है; हेडर का नाम नहीं दिया जाता, क्योंकि वह #include के ज़रिए हर फ़ाइल में पहुँच जाता है। पाठ्यक्रम के विकल्प टर्मिनल के एक वेरिएबल, FLAGS, में रखे जाते हैं, ताकि हर कमांड में उन्हें दोहराना न पड़े; $FLAGS उन्हें वापस लाता है। Linux और macOS में ऐसे ही लिखा जाता है; PowerShell में वेरिएबल दूसरे ढंग से लिखे जाते हैं, और वहाँ विकल्प पूरे लिखना या संपादक से कंपाइल करना बेहतर है (अनुभाग “पृथक कंपाइलेशन”)।
$ FLAGS="-std=c++23 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -g -fsanitize=address,undefined -fno-sanitize-recover=undefined"$ g++ $FLAGS main.cpp geometry.cpp -o main$ ./mainत्रिज्या 2 वाला वृत्त: 12.5664क्या भुजाओं 3, 4, 5 वाला त्रिभुज है? trueउसका क्षेत्रफल: 6और भुजाओं 1, 2, 3 वाला? falseयह चित्र पाठ प्रोग्राम, कंपाइलर और C++ के चित्र को इस परियोजना पर लागू करता है।
प्रमाण
प्रोग्रामर तीन फ़ाइलें लिखते हैं: एक हेडर और दो
.cppफ़ाइलें। कंपाइलर को केवल.cppफ़ाइलें दी जाती हैं।प्रीप्रोसेसर
geometry.hppको हर उस फ़ाइल में कॉपी करता है जो उसे इन्क्लूड करती है। इसलिए घोषणाओं की दो प्रतियाँ होती हैं, हर ट्रांसलेशन यूनिट में एक, और यह सही है: किसी फ़ंक्शन को जितनी बार चाहें घोषित किया जा सकता है।हर यूनिट अलग से अपनी ऑब्जेक्ट फ़ाइल में कंपाइल होती है।
main.cppको कंपाइल करते समय कंपाइलरgeometry.cppको नहीं देखता: हर कॉल के प्रकार जाँचने के लिए घोषणा उसके लिए काफ़ी है।लिंकर दोनों ऑब्जेक्ट फ़ाइलों और मानक लाइब्रेरी को जोड़ता है, और
main.oकीcircleAreaके प्रति हर कॉल को उस परिभाषा से मिलाता है जोgeometry.oमें है।
एक आम त्रुटि है कार्यान्वयन फ़ाइल को इन्क्लूड करना, #include "geometry.cpp"। यदि इसके अलावा उसका नाम कमांड में भी हो, तो उसके फ़ंक्शन दो बार परिभाषित हो जाते हैं और लिंकिंग विफल होती है, उस नियम के कारण जिसे अनुभाग “एक परिभाषा नियम” समझाएगा; यदि नाम न हो, तो main.cpp फिर से एक अकेला टेक्स्ट बन जाती है जो हर बदलाव पर पूरी की पूरी दोबारा कंपाइल होती है। .cpp फ़ाइलें कंपाइल की जाती हैं; हेडर इन्क्लूड किए जाते हैं।
सामग्री लोड हो रही है…