Preprocessor को समझने का सबसे अच्छा तरीका यही होगा, की आप इसे एक बिलकुल अलग program के रूप में देखें, जो आपके program के compile होते वक़्त आपके compiler से पहले run करता है । Preprocessor का तात्पर्य directives को process करने से है । Directives विशेष तरह के instructions होते हैं, जो # symbol से शुरू होकर एक new line पे ख़त्म होते हैं (ध्यान दे, new line, ना की semicolon) । Directives के कई अलग-अलग प्रकार हैं, जिसके बारे में हम अभी चर्चा करने वाले हैं । Preprocessor बहुत ज्यादा smart नहीं होता -- इसलिए ये C++ के syntax को समझने में असमर्थ है; फिर भी, ये compiler के run होने से पहले texts को बेहतर ढंग से प्रस्तुत करने के लिए उपयोगी है ।
Includes
आप #include directive को पहले ही देख चुके हैं । जब आप किसी file को #include करते हैं, preprocessor included file (जिसे include करना है, उदाहरण के लिए #include
#include command को दो तरह से लिखा जा सकता है:
#include
, ये style preprocessor को headers ढूढने के लिए उन directories पर जाने के लिए कहता है जिसे विशेष रूप से operating system ने, C++ runtime library के header file store करने के लिए define किया है ।
#include "filename"
, ये style preprocessor को header files ढूंढने के लिए उन directories में जाने की आज्ञा देता है, जहाँ आपका source file (जिसमेआपने #include directive का प्रयोग किया है) मौजूद है । यदि इसे वहाँ ऐसा कोई header file नहीं मिलता है, तो इसके बाद इसे खोजने के लिए ये उन include paths में चला जाता है, जिसे आपने अपने compiler/IDE की सेटिंग के रूप में specify किया था । यदि यहाँ भी कुछ नहीं मिला, तो ये उस header को बिलकुल angled bracket में बंद header files की तरह treat करेगा ।
Macro defines
#define directive का प्रयोग macros define करने के लिए किया जाता है ।Macro एक ऐसी पद्धति है, जो ये सुनिश्चित करता है की कोई input sequence (जैसे की, कोई identifier) किसी output sequence (जैसे की, कोई text) में कैसे convert किया जाता है । परेशान मत होइए, आप उदाहरण के द्वारा इसे और बेहतर तरीके से समझ पाएंगे ।
Macros मुख्यतः दो प्रकार के होते हैं: object-like macros, और function-like macros ।
Function-like macros बिलकुल functions की ही तरह होते हैं, और उनका इस्तेमाल भी function की ही तरह होता है । हम इसके बारे में यहाँ और ज्यादा चर्चा नहीं करेंगे क्यूंकि साधारणतः, function like macros programming के दृष्टिकोण से खतरनाक माने जाते हैं, अर्थात इनका उपयोग करने से आपको मुश्किलों का सामना करना पड़ सकता है । इसके द्वारा किये जाने वाले लगभग सभी काम (inline) functions की सहायता से किया जा सकता है, वो भी बिना किसी परेशानी के ।
अब बात करते हैं, object-like macros की । Object-like macros को दो भागों में बाँटा जा सकता है:
#define identifier #define identifier substitution_text
ऊपर वाले definition में किसी तरह के “substitution text” का इस्तेमाल नहीं किया गया है, लेकिन ध्यान दीजिये, दुसरे definition में हमने इसका उपयोग किया है । क्यूंकि ये preprocessor declarations हैं, ध्यान दीजिये की इनमे से कोई भी semicolon के साथ ख़त्म नहीं होता ।
Substitution text के साथ Object like macros
जब भी preprocessor को ये directive दिखाई देता है, ये इसके बाद हर जगह ‘identifier’ को इसके ‘substitution_text’ से replace कर देता है । पहले से ही identifiers को capital letter (जैसे A) में लिखने की पद्धति चली आ रही है, इसलिए हमने भी यहाँ उसी style को अपनाया है ।
इस code snippet पर एक नज़र डालें:
1 2 3 |
#define MY_FAVORITE_NUMBER 9 std::cout << "My favorite number is: " << MY_FAVORITE_NUMBER << std::endl; |
Preprocessor इसे कुछ इस तरह बदल देगा:
1 |
std::cout << "My favorite number is: " << 9 << std::endl; |
और जब आप इसे run करोगे, आपको My favorite number is: 9
print हुआ मिलेगा ।
हम इस घटना पर और अधिक चर्चा करेंगे (और ये भी बताएँगे की आपको इस तरीके का प्रयोग क्यूँ नहीं करना चाहिए), लेकिन वो सब section 2.8 -- Literals, symbolic constants, और const variables में ।
Substitution text के बिना Object-like macros
Object-like macros को बिना किसी substitution text के भी define किया जा सकता है ।
उदाहरण के लिए:
1 |
#define USE_YEN |
इस तरह के macros बिलकुल वैसा ही behave करेंगे जैसा आप सोच रहे हो: जब preprocessor इस identifier को देखेगा, ये इसे किसी के साथ replace या remove नहीं करेगा ।
यहाँ तक आपको ये style (Object-like macros, बिना substitution text) बिलकुल बेकार लगा होगा, लेकिन असल में होता यूँ है, की इस style का प्रयोग, substitution text वाले style से ज्यादा किया जाता है । हम अगले section (conditional compilation) में इसका कारण जानेंगे ।
Object-like macros जो substitution text के साथ होते हैं, उनसे अलग बिना substitution text वाले macros उपयोग के लिए बेहतर माने जाते हैं ।
Conditional compilation
Conditional compilation preprocessor directives आपको ये निश्चित करने की अनुमति देता है की किस condition (अवस्था/हालात) में लिखा गया code compile करना है, और कब नहीं करना है । हम इस section में केवल तीन conditional compilation directives के बारे में जानेंगे, जो हैं: #ifdef, #ifndef, और #endif ।
#ifdef preprocessor directive preprocessor को, ये check करने की आज्ञा देता है, की क्या कोई value पहले define किया गया है । यदि value पहले से #defined है, तो #ifdef और इसके संगत #endif के बीच का code compile होगा । यदि value पहले से #defined नहीं है, तो इन दोनों के बीच लिखा गया code ignore कर दिया जायेगा ।
Code के इस snippet पे नज़र डाले:
1 2 3 4 5 6 7 8 9 |
#define PRINT_JOE #ifdef PRINT_JOE cout << "Joe" << endl; #endif #ifdef PRINT_BOB cout << "Bob" << endl; #endif |
यहाँ, क्यूंकि PRINT_JOE पहले #define किया गया है, line cout << "Joe" << endl;
compile हो जायेगा । ठीक इसके विपरीत, क्यूंकि PRINT_BOB कही भी #define नहीं किया गया है, line cout << "Bob" << endl;
compile नहीं होगा ।
#ifndef, #ifdef का opposite (उल्टा) है, ये preprocessor को ये check करने की आज्ञा देता है, की क्या कोई value पहले से #define ' नहीं ' किया गया है ।
1 2 3 |
#ifndef PRINT_BOB cout << "Bob" << endl; #endif |
ये program "Bob" print करेगा, क्यूंकि PRINT_BOB कहीं भी #defined नहीं है ।
Header guards
Header files, दुसरे header files को भी #include कर सकते हैं, और इसी कारण से ये संभव है की कोई header file किसी source file में एक से ज्यादा बार #include हो जाये । उदाहरण के लिए, ज़रा इसे देखें:
mymath.h:
1 2 3 4 5 |
// note: इस function का definition निश्चित रूप से mymath.cpp में लिखा जाना चाहिए, पर उदाहरण के लिए हम इसे header file में रख रहे हैं int cardsInDeck() { return 52; } |
add.h:
1 2 |
#include "mymath.h" int add(int x, int y); // add.cpp में define किया गया है |
subtract.h:
1 2 |
#include "mymath.h" int subtract(int x, int y); // subtract.cpp में define किया गया है |
main.cpp:
1 2 3 4 |
#include "add.h" #include "subtract.h" // यहाँ main.cpp का बाकी बचा हिस्सा लिखा जायेगा, जिसमे main() function भी शामिल है |
यहाँ, जब हम add.h को include करते हैं, ये mymath.h और add के prototype, दोनों को including code file में शामिल कर लेता है । अब जब हम subtract.h को include करते हैं, ये mymath.h (फिर से) और subtract() के prototype को code file में शामिल कर लेता है । फलस्वरूप, mymath.h के contents code file (main.cpp) में दो बार include हो जाते हैं । अब यहाँ ध्यान दीजिये, यदि mymath.h के सारे contents including file में दो बार include हो जायेंगे, तो इसका मतलब है की function cardsInDeck() भी including file में दो बार define हो जायेगा, और इसी के कारण compiler इस function के एक से ज्यादा बार define होने का error देगा ।
इस परेशानी से बचने के लिए हम header guards का प्रयोग करते हैं, जो असल में conditional compilation directives हैं और इन्हें कुछ इस प्रकार लिखा जाता है:
1 2 3 4 5 6 |
#ifndef SOME_UNIQUE_NAME_HERE #define SOME_UNIQUE_NAME_HERE // यहाँ declarations #endif |
अब जब ये header include किया जायेगा, तो सबसे पहले preprocessor ये check करेगा की क्या SOME_UNIQUE_NAME_HERE पहले कहीं define किया गया है ! यदि हमने पहली बार SOME_UNIQUE_NAME_HERE को include किया है, तो preprocessor इसे समझ जायेगा और SOME_UNIQUE_NAME_HERE define कर लिया जायेगा । फलस्वरूप, इस file के सारे contents को including file में include कर लिया जायेगा । इसके विपरीत, यदि SOME_UNIQUE_NAME_HERE पहले define किया गया है, तो इसका मतलब है की इसके सारे contents code file में पहले से ही included हैं । Preprocessor इसे समझ लेगा और SOME_UNIQUE_NAME_HERE के सारे contents इस मर्तबा ignore कर दिए जायेंगे ।
आपके हर header file में header guard होना चाहिए । SOME_UNIQUE_NAME_HERE आपके पसंद का कोई भी नाम हो सकता है, लेकिन जो भी हो, .h लगा नाम (अर्थात आपके header file का नाम) इस्तेमाल के लिए बेहतर माना जाता है। उदाहरण के लिए, add.h का header file कुछ इस तरह से लिखा जायेगा:
1 2 3 4 5 6 |
#ifndef ADD_H #define ADD_H // यहाँ declarations #endif |
Standard library भी अपने header files के लिए header guards का प्रयोग करता है । यदि आप Visual Studio 2005 Express में iostream header file को देखोगे, तो वो कुछ ऐसा दिखाई देगा:
1 2 3 4 5 6 |
#ifndef _IOSTREAM_ #define _IOSTREAM_ // बाकी के contents यहाँ पे #endif |
हमारे mymath.h example को header guards लगाकर update करना
आइये हमारे mymath.h example पर वापस लौटते हैं:
mymath.h:
1 2 3 4 5 |
// note: इस function का definition निश्चित रूप से mymath.cpp में लिखा जाना चाहिए, पर उदाहरण के लिए हम इसे header file में रख रहे हैं int cardsInDeck() { return 52; } |
add.h:
1 2 |
#include "mymath.h" int add(int x, int y); // defined in add.cpp (not shown here) |
subtract.h:
1 2 |
#include "mymath.h" int subtract(int x, int y); // subtract.cpp में define किया गया है (यहाँ वो definition नही दिखाया जा रहा है) |
main.cpp:
1 2 3 4 5 6 7 |
#include "add.h" #include "subtract.h" int main() { return 0; } |
cardsInDeck() को दो बार include होने से रोकने के लिए, हम अब mymath.h में header guards का प्रयोग करेंगे:
mymath.h, header guards के साथ:
1 2 3 4 5 6 7 8 9 |
#ifndef MYMATH_H #define MYMATH_H // note: इस function का definition निश्चित रूप से mymath.cpp में लिखा जाना चाहिए, पर उदाहरण के लिए हम इसे header file में रख रहे हैं { return 52; } #endif |
अब, main.cpp, add.h को #include करेगा, जिसके साथ-साथ mymath.h भी #include हो जायेगा । क्यूंकि MYMATH_H पहले कहीं define नहीं किया गया है, mymath.h के contents (जिसमे cardsInDeck() भी शामिल है) code file में define कर लिए जायेंगे और अब MYMATH_H define हो चूका होगा । अब इसके बाद main.cpp subtract.h को #include करेगा और इसमें भी mymath.h include किया गया है, लेकिन क्यूंकि MYMATH_H पहले ही define कर लिया जा चूका है, इस बार mymath.h का सारा code ignore कर दिया जायेगा ।
Header guard को जोड़कर, अब हमने ये पक्का कर लिया है की mymath.h, main.cpp में केवल और केवल एक ही बार #include किया जायेगा ।
Code को और ज्यादा बेहतर बनाने के लिए, add.h और subtract.h में भी header guards का इस्तेमाल किया जा सकता है ।
#pragma once
बहुत सारे modern compilers header guards से बेहतर और आसान तरीका पेश करते हैं, जो बिलकुल header guards की ही तरह काम करता है: -- #pragma directive:
1 2 3 |
#pragma once // यहाँ आपका code |
यहाँ #pragma once
बिलकुल header guards की तरह काम करेगा, और इसके इस्तेमाल से एक फायदा ये भी है की ये लिखने में आसान है जिसके चलते गलतियाँ होने की सम्भावना घट जाती है । stdafx.h file, जिसे Visual Studio उन projects में include करता है जिनमे precompiled headers इस्तेमाल किये जाते हैं, में pragma directive का प्रयोग देखने को मिल सकता है ।
फिर भी, #pragma once
के इस्तेमाल करने या ना करने पर C++ (इसके standards) कोई officially सलाह नहीं देते, और सारे compilers इसे support भी नहीं करते (फिर भी, कई modern compilers इसके लिए supportive हैं) ।
हर compiler में compatibility के लिए, हमारी सलाह है कि आप header guards का ही उपयोग करें ।
सारांश
Header guards, कोई header file किसी source file में दोबारा include ना हो जाये, ये पक्का करने के लिए design किये गए है (इससे code के duplication में कमी आती है) । फिर भी, header guards किसी header file के contents को अलग-अलग project या program में include होने से नहीं रोकता (जैसे की add.h और subtract.h का उपयोग ऊपर दिए गए example के अलावा किसी दूसरे project में भी किया जा सकता है) । ये एक अच्छी चीज़ है क्योंकि कभी-कभी हमें अलग-अलग projects में एक ही तरह की functionalities (या एक ही तरह के contents) की ज़रूरत होती है ।
Quiz
1) ऊपर दिए गए mymath.h example का प्रयोग करते हुए, add.h और subtract.h header files में header guards जोड़ें ।
Quiz Answers
![]() |
![]() |
![]() |
Leave a Comment