अगले चरण

प्रोग्रामिंग और C++ के बारे में जानकारी

यह ऑनलाइन ट्यूटोरियल ज़्यादा बेहतर कॉन्सेप्ट के साथ जारी है - कृपया भाग III पढ़ें. इस मॉड्यूल में हमारा फ़ोकस, पॉइंटर के इस्तेमाल और ऑब्जेक्ट के साथ शुरुआत करने पर होगा.

उदाहरण #2 से जानें

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

उदाहरण #1: डिकंपोज़िशन इस्तेमाल करने का ज़्यादा तरीका

किसी आसान गेम से मिले आउटपुट के बारे में सोचें:

Welcome to Artillery.
You are in the middle of a war and being charged by thousands of enemies.
You have one cannon, which you can shoot at any angle.
You only have 10 cannonballs for this target..
Let's begin...

The enemy is 507 feet away!!!
What angle? 25<
You over shot by 445
What angle? 15
You over shot by 114
What angle? 10
You under shot by 82
What angle? 12
You under shot by 2
What angle? 12.01
You hit him!!!
It took you 4 shots.
You have killed 1 enemy.
I see another one, are you ready? (Y/N) n

You killed 1 of the enemy.

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

StartUp(); // This displays the introductory script.
killed = 0;
do {
  killed = Fire(); // Fire() contains the main loop of each round.
  cout << "I see another one, care to shoot again? (Y/N) " << endl;
  cin >> done;
} while (done != 'n');
cout << "You killed " << killed << " of the enemy." << endl;

फ़ायर प्रोसेस में, गेम खेलने की प्रोसेस को मैनेज किया जाता है. इस फ़ंक्शन में, हम दुश्मन की दूरी का पता लगाने के लिए एक रैंडम नंबर जनरेटर को कॉल करते हैं. इसके बाद, हम लूप को सेट करते हैं, ताकि खिलाड़ी का इनपुट मिल सके और यह हिसाब लगाया जा सके कि उन्होंने दुश्मन को टक्कर मारी है या नहीं. लूप पर गार्ड की स्थिति का मतलब है कि हम दुश्मन को मारने के कितने करीब पहुंच गए.

In case you are a little rusty on physics, here are the calculations:

Velocity = 200.0; // initial velocity of 200 ft/sec Gravity = 32.2; // gravity for distance calculation // in_angle is the angle the player has entered, converted to radians. time_in_air = (2.0 * Velocity * sin(in_angle)) / Gravity; distance = round((Velocity * cos(in_angle)) * time_in_air);

cos() और sin() को कॉल करने की वजह से, आपको मैथ.h को शामिल करना होगा. इस प्रोग्राम को लिखने की कोशिश करें - यह समस्या को कम करने और मूल C++ की अच्छी समीक्षा करने के लिए एक अच्छी प्रक्रिया है. हर फ़ंक्शन में सिर्फ़ एक टास्क करना न भूलें. यह हमारा अब तक का सबसे बेहतरीन प्रोग्राम है. इसलिए, इसे करने में आपको थोड़ा समय लग सकता है.यहां हमारा तरीका बताया गया है. 

उदाहरण #2: पॉइंटर की प्रैक्टिस करें

पॉइंटर का इस्तेमाल करते समय इन चार बातों का ध्यान रखें:
  1. पॉइंटर ऐसे वैरिएबल होते हैं जिनमें मेमोरी के पते होते हैं. जब कोई प्रोग्राम एक्ज़ीक्यूट होता है, तब सभी वैरिएबल मेमोरी में स्टोर होते हैं. हर वैरिएबल का अपना यूनीक पता या जगह होती है. पॉइंटर एक खास तरह का वैरिएबल होता है. इसमें डेटा वैल्यू के बजाय, मेमोरी का पता होता है. जिस तरह किसी सामान्य वैरिएबल का इस्तेमाल करने पर डेटा में बदलाव किया जाता है, उसी तरह पॉइंटर में सेव किए गए पते की वैल्यू में भी, पॉइंटर वैरिएबल के तौर पर बदलाव किया जाता है. यहां एक उदाहरण दिया गया है:
    int *intptr; // Declare a pointer that holds the address
                 // of a memory location that can store an integer.
                 // Note the use of * to indicate this is a pointer variable.
    
    intptr = new int; // Allocate memory for the integer.
    *intptr = 5; // Store 5 in the memory address stored in intptr.
          
  2. आम तौर पर, हम कहते हैं कि पॉइंटर उस जगह की जानकारी "पॉइंट" पर ले जाता है जिसे वह स्टोर कर रहा है ("पॉइंट"). इसलिए, ऊपर दिए गए उदाहरण में, intptr पॉइंट 5 को पॉइंट करता है.

    हमारे पूर्णांक पॉइंटी के लिए मेमोरी असाइन करने के लिए, "नया" ऑपरेटर के इस्तेमाल पर ध्यान दें. पॉइंटी को ऐक्सेस करने से पहले हमें यह काम करना पड़ता है.

    int *ptr; // Declare integer pointer.
    ptr = new int; // Allocate some memory for the integer.
    *ptr = 5; // Dereference to initialize the pointee.
    *ptr = *ptr + 1; // We are dereferencing ptr in order
                     // to add one to the value stored
                     // at the ptr address.
          

    C में, * ऑपरेटर का इस्तेमाल डीरेफ़रेंस करने के लिए किया जाता है. पॉइंटर के साथ काम करते समय, C/C++ प्रोग्रामर से होने वाली सबसे आम गड़बड़ियों में से एक है, पॉइंटी को शुरू करना भूल जाना. इस वजह से कभी-कभी रनटाइम क्रैश हो सकता है, क्योंकि हम मेमोरी में मौजूद उस जगह को ऐक्सेस कर रहे हैं जिसमें अज्ञात डेटा होता है. अगर हम इस डेटा में बदलाव करने की कोशिश करते हैं, तो मेमोरी में काफ़ी गड़बड़ी हो सकती है. इसकी वजह से, इसे ट्रैक करना मुश्किल हो सकता है.

  3. दो पॉइंटर के बीच होने वाले पॉइंटर असाइनमेंट, उन्हें एक ही पॉइंटी पॉइंट पर ले जाते हैं. इसलिए, असाइनमेंट y = x; y पॉइंट को उसी पॉइंटी ओर ले जाता है जिस पर x का निशान है. पॉइंटर असाइनमेंट ने पॉइंट पाने वाले व्यक्ति को नहीं छुआ. यह सिर्फ़ एक पॉइंटर को बदलकर, दूसरी पॉइंटर की जगह के बारे में बताता है. पॉइंटर असाइनमेंट के बाद, दो पॉइंटर, पॉइंटी को "शेयर" करते हैं. 
  4. void main() {
     int* x; // Allocate the pointers x and y
     int* y; // (but not the pointees).
    
     x = new int; // Allocate an int pointee and set x to point to it.
    
     *x = 42; // Dereference x and store 42 in its pointee
    
     *y = 13; // CRASH -- y does not have a pointee yet
    
     y = x; // Pointer assignment sets y to point to x's pointee
    
     *y = 13; // Dereference y to store 13 in its (shared) pointee
    }
      

यहां इस कोड का ट्रेस दिया गया है:

1. दो पॉइंटर x और y असाइन करें. पॉइंटर तय करने से, पॉइंट हासिल नहीं होते.
2. पॉइंट असाइन करें और उस पर पॉइंट करने के लिए x को सेट करें.
3. 42 को उसके पॉइंटर में स्टोर करने के लिए, x को अलग करें. यह डेटा हटाने की कार्रवाई का एक बुनियादी उदाहरण है. x से शुरू करते हुए, ऐरो के निशान को ऐक्सेस करने के लिए, उसके ऊपर बने तीर के निशान को फ़ॉलो करें.
4. 13 को इसके पॉइंटर में बदलने की कोशिश करें. यह क्रैश हो जाता है, क्योंकि y के पास कोई पॉइंटी नहीं है -- इसे कभी असाइन नहीं किया गया था.
5. y = x; असाइन करें, ताकि y पॉइंट, x के पॉइंटी हो जाएं. अब x और y एक ही पॉइंट की ओर इशारा करते हैं -- वे "शेयर कर रहे हैं".
6. 13 को इसके पॉइंटर में बदलने की कोशिश करें. इस बार यह काम करता है, क्योंकि पिछले असाइनमेंट में एक पॉइंट हासिल किया गया था.

जैसा कि आपने देखा, पॉइंटर के इस्तेमाल को समझने में तस्वीरें बहुत मददगार होती हैं. यहां एक और उदाहरण दिया गया है.

int my_int = 46; // Declare a normal integer variable.
                 // Set it to equal 46.

// Declare a pointer and make it point to the variable my_int
// by using the address-of operator.
int *my_pointer = &my_int;

cout << my_int << endl; // Displays 46.

*my_pointer = 107; // Derefence and modify the variable.

cout << my_int << endl; // Displays 107.
cout << *my_pointer << endl; // Also 107.

इस उदाहरण में ध्यान दें कि हमने कभी भी "नए" ऑपरेटर के साथ मेमोरी असाइन नहीं की है. हमने एक सामान्य पूर्णांक वैरिएबल की जानकारी दी और उसमें पॉइंटर की मदद से बदलाव किए.

इस उदाहरण में, हम डिलीट ऑपरेटर के उस इस्तेमाल के बारे में बताते हैं जो हीप मेमोरी को बांटता है. साथ ही, यह भी दिखाया गया है कि ज़्यादा कॉम्प्लेक्स स्ट्रक्चर को असाइन कैसे किया जा सकता है. हम दूसरे लेसन में, मेमोरी व्यवस्थित करने की सुविधा (हीप और रनटाइम स्टैक) के बारे में बताएंगे. फ़िलहाल, हीप को मेमोरी के एक मुफ़्त स्टोर के तौर पर देखा जा सकता है. यह मेमोरी, प्रोग्राम चलाने के लिए उपलब्ध होती है.

int *ptr1; // Declare a pointer to int.
ptr1 = new int; // Reserve storage and point to it.

float *ptr2 = new float; // Do it all in one statement.

delete ptr1; // Free the storage.
delete ptr2;

इस आखिरी उदाहरण में, हम दिखाते हैं कि फ़ंक्शन के रेफ़रंस से वैल्यू पास करने के लिए, पॉइंटर का इस्तेमाल कैसे किया जाता है. हम किसी फ़ंक्शन में वैरिएबल की वैल्यू को इस तरह बदलते हैं.

// Passing parameters by reference.
#include <iostream>
using namespace std;

void Duplicate(int& a, int& b, int& c) {
  a *= 2;
  b *= 2;
  c *= 2;
}

int main() {
  int x = 1, y = 3, z = 7;
  Duplicate(x, y, z);
  // The following outputs: x=2, y=6, z=14.
  cout << "x="<< x << ", y="<< y << ", z="<< z;
  return 0;
}

अगर डुप्लीकेट फ़ंक्शन की परिभाषा में, & के आर्ग्युमेंट बाहर रखे जाते हैं, तो हम "वैल्यू के हिसाब से" वैरिएबल को पास करते हैं. इसका मतलब है कि वैरिएबल की वैल्यू से एक कॉपी बनती है. फ़ंक्शन में वैरिएबल में किया जाने वाला कोई भी बदलाव, कॉपी में बदलाव कर देता है. वे ओरिजनल वैरिएबल में बदलाव नहीं करते.

जब किसी वैरिएबल को रेफ़रंस से पास किया जाता है, तब हम उसकी वैल्यू की कॉपी को पास नहीं करते हैं. इसका मतलब है कि हम फ़ंक्शन में वैरिएबल का पता भेज रहे हैं. लोकल वैरिएबल में किया जाने वाला कोई भी बदलाव, असल में पास किए गए ओरिजनल वैरिएबल में भी बदलाव कर देता है. 

अगर आप C प्रोग्रामर हैं, तो यह आपके लिए नया तरीका है. हम C में भी ऐसा कर सकते हैं. इसके लिए, हम C में डुप्लीकेट (int *x) के तौर पर बताते हैं. इस मामले में x किसी इंंट का पॉइंटर होता है, फिर &x (पता x) के साथ डुप्लीकेट() को कॉल करता है और डुप्लीकेट() में x का रेफ़रंस हटाकर ऐसा करता है (नीचे देखें). हालांकि, C++ रेफ़रंस के हिसाब से फ़ंक्शन में वैल्यू पास करने का आसान तरीका उपलब्ध कराता है. हालांकि, ऐसा करने का पुराना "C" तरीका अब भी काम करता है.

void Duplicate(int *a, int *b, int *c) {
  *a *= 2;
  *b *= 2;
  *c *= 2;
}

int main() {
  int x = 1, y = 3, z = 7;
  Duplicate(&x, &y, &z);
  // The following outputs: x=2, y=6, z=14.
  cout << "x=" << x << ", y=" << y << ", z=" << z;
  return 0;
}

C++ रेफ़रंस के साथ ध्यान दें, हमें न तो वैरिएबल का पता भेजना ज़रूरी है और न ही हमें कॉल किए गए फ़ंक्शन के अंदर वैरिएबल का रेफ़रंस देने की ज़रूरत है.

इस प्रोग्राम से क्या होता है? इसे समझने के लिए, मेमोरी की एक तस्वीर बनाएं.

void DoIt(int &foo, int goo);

int main() {
  int *foo, *goo;
  foo = new int;
  *foo = 1;
  goo = new int;
  *goo = 3;
  *foo = *goo + 3;
  foo = goo;
  *goo = 5;
  *foo = *goo + *foo;
  DoIt(*foo, *goo);
  cout << (*foo) << endl;
}

void DoIt(int &foo, int goo) {
  foo = goo + 3;
  goo = foo + 4;
  foo = goo + 3;
  goo = foo;
} 

सही जवाब मिला या नहीं, यह देखने के लिए प्रोग्राम चलाएं.

उदाहरण #3: रेफ़रंस के हिसाब से वैल्यू पास करना

तेज़ी से कॉल करने वाला फ़ंक्शन लिखें, जो वाहन की गति और मात्रा को इनपुट के रूप में लेता है. इस सुविधा से, गाड़ी की रफ़्तार बढ़ाने के लिए आवाज़ को और तेज़ कर दिया जाता है. स्पीड पैरामीटर को रेफ़रंस और रकम के हिसाब से पास किया जाना चाहिए. यहां हमारा समाधान है.

उदाहरण #4: क्लास और ऑब्जेक्ट

नीचे दी गई क्लास आज़माएं:

// time.cpp, Maggie Johnson
// Description: A simple time class.

#include <iostream>
using namespace std;

class Time {
 private:
  int hours_;
  int minutes_;
  int seconds_;
 public:
  void set(int h, int m, int s) {hours_ = h; minutes_ = m; seconds_ = s; return;}
  void increment();
  void display();
};

void Time::increment() {
  seconds_++;
  minutes_ += seconds_/60;
  hours_ += minutes_/60;
  seconds_ %= 60;
  minutes_ %= 60;
  hours_ %= 24;
  return;
}

void Time::display() {
  cout << (hours_ % 12 ? hours_ % 12:12) << ':'
       << (minutes_ < 10 ? "0" :"") << minutes_ << ':'
       << (seconds_ < 10 ? "0" :"") << seconds_
       << (hours_ < 12 ? " AM" : " PM") << endl;
}

int main() {
  Time timer;
  timer.set(23,59,58);
  for (int i = 0; i < 5; i++) {
    timer.increment();
    timer.display();
    cout << endl;
  }
}

ध्यान दें कि क्लास में शामिल सदस्य के वैरिएबल में ट्रेलिंग अंडरस्कोर है. ऐसा लोकल वैरिएबल और क्लास वैरिएबल के बीच अंतर करने के लिए किया जाता है.

इस क्लास में, कम करने का तरीका जोड़ें. यहां हमारा समाधान है.

विज्ञान के चमत्कार: कंप्यूटर साइंस

अभ्यास

इस कोर्स के पहले मॉड्यूल की तरह, हम कसरतों और प्रोजेक्ट से जुड़ी समस्याएं हल नहीं करते.

याद रखें, एक अच्छा प्रोग्राम...

... को लॉजिक के हिसाब से, उन फ़ंक्शन में बांटा जाता है जिनमें कोई एक फ़ंक्शन, एक ही टास्क को पूरा करता है.

... का एक मुख्य प्रोग्राम है जो बताता है कि प्रोग्राम क्या करेगा.

... इसमें ब्यौरा देने वाला फ़ंक्शन, कॉन्सटेंट और वैरिएबल नाम हैं.

... प्रोग्राम में किसी "जादू" संख्या से बचने के लिए कॉन्सटेंट का इस्तेमाल किया जाता है.

... का यूज़र इंटरफ़ेस आसान है.

वार्म-अप व्यायाम

  • व्यायाम 1

    पूर्णांक 36 की एक खास प्रॉपर्टी है: यह एक परफ़ेक्ट स्क्वेयर है. साथ ही, यह 1 से 8 तक के पूर्णांकों का योग भी है. अगली ऐसी संख्या 1225 है, जो 352 है और 1 से लेकर 49 तक के पूर्णांकों का योग है. ऐसी अगली संख्या पता करें जो एक पूर्ण वर्ग हो और साथ ही किसी शृंखला 1...n का योग भी हो. यह अगली संख्या, 32767 से बड़ी हो सकती है. अपने प्रोग्राम को ज़्यादा तेज़ी से चलाने के लिए, लाइब्रेरी के उन फ़ंक्शन का इस्तेमाल किया जा सकता है जिनके बारे में आपको जानकारी है या गणित के फ़ॉर्मूले इस्तेमाल किए जा सकते हैं. लूप-लूप का इस्तेमाल करके भी इस प्रोग्राम को लिखा जा सकता है. इससे यह पता लगाया जा सकता है कि कोई संख्या, परफ़ेक्ट स्क्वेयर है या सीरीज़ का कुल योग. (ध्यान दें: आपकी मशीन और प्रोग्राम के हिसाब से, इस संख्या को ढूंढने में काफ़ी समय लग सकता है.)

  • दूसरी कसरत

    आपको कॉलेज की बुक स्टोर से अगले साल के कारोबार का अनुमान लगाने में मदद चाहिए. कई अनुभवों से पता चला है कि बिक्री इस बात पर निर्भर करती है कि किताब को किसी कोर्स के लिए ज़रूरी है या सिर्फ़ वैकल्पिक है. साथ ही, यह इस बात पर भी निर्भर करता है कि क्लास में पहले कभी इस किताब का इस्तेमाल किया गया है या नहीं. एक नई और ज़रूरी टेक्स्टबुक, रजिस्टर करने वाले 90% संभावित लोगों को बेची जाएगी. हालांकि, अगर इसका इस्तेमाल क्लास में पहले किया जा चुका है, तो सिर्फ़ 65% लोग ही इसे खरीदेंगे. इसी तरह, रजिस्ट्रेशन करने वाले 40% लोग एक नई और वैकल्पिक टेक्स्टबुक खरीदेंगे. हालांकि, अगर इसका इस्तेमाल क्लास में किया जा चुका है, तो सिर्फ़ 20% लोग ही इसे खरीदेंगे. (ध्यान दें कि यहां "इस्तेमाल की गई" का मतलब पुरानी किताबें नहीं है.)

  • एक ऐसा प्रोग्राम लिखें जो इनपुट के तौर पर किताबों की सीरीज़ को स्वीकार करता हो (जब तक कि उपयोगकर्ता किसी प्रहरी में नहीं पहुंच जाता). हर किताब के लिए ये बातें ज़रूरी होती हैं: किताब का एक कोड, किताब की एक कॉपी की कीमत, उपलब्ध किताबों की मौजूदा संख्या, संभावित क्लास के रजिस्ट्रेशन की जानकारी, और वह डेटा जिससे पता चलता है कि किताब ज़रूरी है/ज़रूरी नहीं है, पहले नई/इस्तेमाल की गई है या नहीं. आउटपुट के तौर पर, अच्छी तरह फ़ॉर्मैट की गई स्क्रीन पर इनपुट की पूरी जानकारी दिखाएं. साथ ही, यह भी दिखाएं कि कितनी किताबों का ऑर्डर देना ज़रूरी है (अगर कोई है, तो ध्यान दें कि सिर्फ़ नई किताबों को ऑर्डर किया जाता है), हर ऑर्डर की कुल कीमत.

    सभी इनपुट के पूरा होने के बाद, बुक के सभी ऑर्डर की कुल कीमत दिखाएं. साथ ही, अगर स्टोर, किताब की कीमत का 80% चुकाता है, तो कितना मुनाफ़ा होगा. हमने अभी तक प्रोग्राम में शामिल होने वाले डेटा के बड़े सेट को मैनेज करने के किसी भी तरीके के बारे में नहीं बताया है (हमारे साथ बने रहें!), इसलिए एक बार में सिर्फ़ एक किताब को प्रोसेस करें और उस किताब की आउटपुट स्क्रीन दिखाएं. इसके बाद, जब उपयोगकर्ता सारा डेटा डाल देगा, तब आपके प्रोग्राम में कुल और मुनाफ़े की वैल्यू दिखेंगी.

    कोड लिखना शुरू करने से पहले, कुछ समय निकालकर इस प्रोग्राम की डिज़ाइन के बारे में सोचें. फ़ंक्शन के सेट को डिकंपोज़ करें. इसके बाद, एक min() फ़ंक्शन बनाएं जो समस्या को हल करने के आपके तरीके के बारे में बताता हो. पक्का करें कि हर फ़ंक्शन एक ही काम करे.

    यहां आउटपुट का नमूना दिया गया है:

    Please enter the book code: 1221
     single copy price: 69.95
     number on hand: 30
     prospective enrollment: 150
     1 for reqd/0 for optional: 1
     1 for new/0 for used: 0
    ***************************************************
    Book: 1221
    Price: $69.95
    Inventory: 30
    Enrollment: 150
    
    This book is required and used.
    ***************************************************
    Need to order: 67
    Total Cost: $4686.65
    ***************************************************
    
    Enter 1 to do another book, 0 to stop. 0
    ***************************************************
    Total for all orders: $4686.65
    Profit: $937.33
    ***************************************************

डेटाबेस प्रोजेक्ट

इस प्रोजेक्ट में, हम पूरी तरह से काम करने वाला C++ प्रोग्राम बनाते हैं, जो एक आसान डेटाबेस ऐप्लिकेशन लागू करता है.

हमारे प्रोग्राम की मदद से, हम संगीतकारों का डेटाबेस और उनसे जुड़ी काम की जानकारी मैनेज कर पाएंगे. प्रोग्राम की सुविधाओं में शामिल हैं:

  • नए संगीतकार को जोड़ने की सुविधा
  • किसी संगीतकार को रैंक देने की क्षमता (जैसे, यह बताना कि हमें उसके संगीत को कितना पसंद या नापसंद है)
  • डेटाबेस में मौजूद सभी कंपोज़र को देखने की सुविधा
  • रैंक के हिसाब से सभी कंपोज़र को देखने की सुविधा

"सॉफ़्टवेयर का डिज़ाइन बनाने के दो तरीके हैं: एक तरीका है इसे इतना आसान बनाना कि उसमें साफ़ तौर पर कोई कमी न हो, और दूसरा तरीका है इसे इतना जटिल बनाना कि कोई साफ़ तौर पर कोई कमियां न हों. पहला तरीका ज़्यादा मुश्किल है." - सी॰ए॰आर॰ होआर

हम में से कई लोगों ने एक "प्रोसेसल" अप्रोच का इस्तेमाल करके, डिज़ाइन और कोड करना सीखा. हम सबसे पहले मुख्य सवाल करते हैं कि "इस प्रोग्राम को क्या करना चाहिए?". हम किसी समस्या के समाधान को टास्क में बांट देते हैं. यह तरीका उस समस्या का एक हिस्सा हल करता है. ये टास्क, हमारे प्रोग्राम में मौजूद उन फ़ंक्शन से मैप होते हैं जिन्हें मुख्य फ़ंक्शन या अन्य फ़ंक्शन से क्रम में कॉल किया जाता है. यह सिलसिलेवार तरीका ऐसी कुछ समस्याओं को हल करने का सबसे अच्छा तरीका है जिन्हें हमें हल करना होगा. हालांकि, हमारे प्रोग्राम अक्सर किसी टास्क या इवेंट का क्रम नहीं होते.

ऑब्जेक्ट-ओरिएंटेड (OO) अप्रोच वाले तरीके से, हम इस सवाल से शुरू करते हैं कि "मैं असल दुनिया के किन ऑब्जेक्ट को मॉडल कर रहा/रही हूं?" किसी प्रोग्राम को ऊपर बताए गए टास्क में बांटने के बजाय, हम इसे फ़िज़िकल ऑब्जेक्ट के मॉडल में बांट देते हैं. इन फ़िज़िकल ऑब्जेक्ट की एक स्थिति होती है. यह उनकी विशेषताओं और उनके किए जा सकने वाले व्यवहार या कार्रवाइयों के आधार पर तय होती है. इन कार्रवाइयों की वजह से, ऑब्जेक्ट की स्थिति बदल सकती है या वे दूसरे ऑब्जेक्ट की कार्रवाइयों को शुरू कर सकती हैं. इसका बुनियादी सिद्धांत यह है कि कोई ऑब्जेक्ट यह "जानता" है कि चीज़ों को अपने-आप कैसे किया जाए. 

OO डिज़ाइन में, हम फ़िज़िकल ऑब्जेक्ट को क्लास और ऑब्जेक्ट, एट्रिब्यूट, और व्यवहार के आधार पर परिभाषित करते हैं. किसी OO प्रोग्राम में, आम तौर पर बड़ी संख्या में ऑब्जेक्ट होते हैं. हालांकि, इनमें से कई ऑब्जेक्ट एक जैसे हैं. नीचे दी गई बातों पर ध्यान दें.

क्लास, किसी ऑब्जेक्ट के लिए सामान्य विशेषताओं और व्यवहार का एक सेट है, जो असल दुनिया में मौजूद हो सकता है. ऊपर दिए गए उदाहरण में, हमारी Apple क्लास है. हर तरह के सेब के रंग और स्वाद की विशेषताएं अलग-अलग होती हैं. हमने एक ऐसा तरीका भी बताया है जिसमें Apple अपने एट्रिब्यूट दिखाता है.

इस डायग्राम में, हमने Apple क्लास के दो ऑब्जेक्ट बताए हैं. हर ऑब्जेक्ट में वही एट्रिब्यूट और कार्रवाइयां होती हैं जो क्लास में मौजूद होती हैं. हालांकि, ऑब्जेक्ट किसी खास तरह के सेब के एट्रिब्यूट के बारे में बताता है. इसके अलावा, डिसप्ले ऐक्शन किसी खास ऑब्जेक्ट के लिए एट्रिब्यूट दिखाता है, जैसे कि "हरा" और "खरना".

OO डिज़ाइन में क्लास का एक सेट, इन क्लास से जुड़ा डेटा, और क्लास के ज़रिए की जा सकने वाली कार्रवाइयों का सेट होता है. हमें यह भी बताना होगा कि अलग-अलग क्लास, कैसे इंटरैक्ट करती हैं. यह इंटरैक्शन किसी क्लास के ऑब्जेक्ट से किया जा सकता है, जो दूसरी क्लास के ऑब्जेक्ट की कार्रवाइयों का अनुरोध करता है. उदाहरण के लिए, हमारे पास एक Apple व्यक्तियों की क्लास हो सकती है, जो Apple के हर ऑब्जेक्ट के Display() तरीके को कॉल करके, Apple ऑब्जेक्ट के किसी ऐरे का रंग और स्वाद दिखाती है.

OO डिज़ाइन बनाने के लिए, हम ये तरीके अपनाते हैं:

  1. क्लास की पहचान करें और सामान्य तौर पर यह तय करें कि हर क्लास का ऑब्जेक्ट, किस डेटा को डेटा के तौर पर स्टोर करता है और कोई ऑब्जेक्ट क्या कर सकता है.
  2. हर क्लास के डेटा एलिमेंट तय करें
  3. हर क्लास की कार्रवाइयों के बारे में बताएं. साथ ही, यह भी बताएं कि मिलती-जुलती दूसरी क्लास की कार्रवाइयों का इस्तेमाल करके, एक क्लास की कुछ कार्रवाइयों को कैसे लागू किया जा सकता है.

एक बड़े सिस्टम के लिए, ये चरण जानकारी के अलग-अलग लेवल पर बार-बार आते हैं.

कंपोज़र के डेटाबेस सिस्टम के लिए, हमें कंपोज़र क्लास की ज़रूरत है. यह क्लास वह सारा डेटा इकट्ठा करती है जिसे हम किसी कंपोज़र पर सेव करना चाहते हैं. इस क्लास का कोई ऑब्जेक्ट, खुद को प्रमोट कर सकता है या उसका दर्जा घटा सकता है (उसकी रैंक बदल सकता है), और अपने एट्रिब्यूट दिखा सकता है.

हमें कंपोज़र ऑब्जेक्ट के संग्रह की भी ज़रूरत है. इसके लिए, हम एक डेटाबेस क्लास तय करते हैं, जो अलग-अलग रिकॉर्ड को मैनेज करती है. इस क्लास का कोई ऑब्जेक्ट, कंपोज़र ऑब्जेक्ट को जोड़ सकता है या उसे वापस ला सकता है. साथ ही, कंपोज़र ऑब्जेक्ट के डिसप्ले ऐक्शन की मदद से अलग-अलग ऑब्जेक्ट दिखा सकता है.

आखिर में, हमें डेटाबेस पर इंटरैक्टिव ऑपरेशन की सुविधा देने के लिए, एक तरह के यूज़र इंटरफ़ेस की ज़रूरत है. यह एक प्लेसहोल्डर क्लास है.इसका मतलब है कि हमें अभी तक नहीं पता कि यूज़र इंटरफ़ेस कैसा दिखेगा. हालांकि, हमें पता है कि हमें इसकी ज़रूरत होगी. यह ग्राफ़िकल या टेक्स्ट पर आधारित हो सकता है. फ़िलहाल, हम एक प्लेसहोल्डर तय करते हैं, जिसे बाद में भरा जा सकता है.

हमने कंपोज़र डेटाबेस ऐप्लिकेशन के लिए क्लास की पहचान कर ली है. इसके बाद, हम क्लास के लिए एट्रिब्यूट और कार्रवाइयां तय करते हैं. ज़्यादा मुश्किल ऐप्लिकेशन में, हम क्लास के क्रम और ऑब्जेक्ट के इंटरैक्ट करने के तरीके को मैप करने के लिए, पेंसिल और काग़ज़ या यूएमएल, सीआरसी कार्ड या OOD का इस्तेमाल करते हैं.

अपने कंपोज़र डेटाबेस के लिए, हम कंपोज़र क्लास तय करते हैं. इसमें वह काम का डेटा शामिल होता है जिसे हम हर कंपोज़र पर सेव करना चाहते हैं. इसमें रैंकिंग में हेर-फेर करने और डेटा दिखाने के तरीके भी शामिल हैं.

डेटाबेस क्लास को कंपोज़र ऑब्जेक्ट को होल्ड करने के लिए किसी तरह की संरचना की ज़रूरत होती है. हमें स्ट्रक्चर में नया कंपोज़र ऑब्जेक्ट जोड़ने के साथ-साथ, खास कंपोज़र ऑब्जेक्ट को वापस लाने की ज़रूरत है. हम सभी ऑब्जेक्ट को या तो एंट्री के क्रम में या रैंकिंग के हिसाब से दिखाना चाहते हैं.

यूज़र इंटरफ़ेस क्लास, मेन्यू-ड्रिवन इंटरफ़ेस लागू करती है. इसमें हैंडलर की मदद से डेटाबेस क्लास में ऐक्शन को कॉल किया जाता है.

अगर कंपोज़र ऐप्लिकेशन की तरह ही क्लास को आसानी से समझा जा सकता है और उनकी विशेषताएं और कार्रवाइयां साफ़ तौर पर समझ आती हैं, तो क्लास को डिज़ाइन करना आसान है. हालांकि, क्लास से जुड़े और इंटरैक्ट करने के तरीके के बारे में अगर आपके मन में कोई सवाल है, तो सबसे पहले उसका ड्रॉ करें और कोड करना शुरू करने से पहले पूरी जानकारी इकट्ठा करें.

डिज़ाइन की पूरी जानकारी मिलने और इसकी समीक्षा करने के बाद, हम हर क्लास के लिए इंटरफ़ेस तय करते हैं. इस बारे में ज़्यादा जानकारी जल्द ही दी जाएगी. अब हमें, टैग लागू करने की जानकारी नहीं चाहिए - इसमें सिर्फ़ एट्रिब्यूट और कार्रवाइयां क्या हैं, और क्लास की स्थिति के कौनसे हिस्से और अन्य क्लास के लिए कार्रवाइयां उपलब्ध हैं.

C++ में, आम तौर पर हम हर क्लास के लिए एक हेडर फ़ाइल तय करके ऐसा करते हैं. कंपोज़र क्लास के पास, उस डेटा के लिए निजी डेटा सदस्य होते हैं जिसे हम किसी कंपोज़र पर सेव करना चाहते हैं. हमें ऐक्सेसर (“पाएं” के तरीके) और म्यूटेटर (“सेट” करने के तरीके) के साथ-साथ क्लास के लिए मुख्य कार्रवाइयों की ज़रूरत होगी.

// composer.h, Maggie Johnson
// Description: The class for a Composer record.
// The default ranking is 10 which is the lowest possible.
// Notice we use const in C++ instead of #define.
const int kDefaultRanking = 10;

class Composer {
 public:
  // Constructor
  Composer();
  // Here is the destructor which has the same name as the class
  // and is preceded by ~. It is called when an object is destroyed
  // either by deletion, or when the object is on the stack and
  // the method ends.
  ~Composer();

  // Accessors and Mutators
  void set_first_name(string in_first_name);
  string first_name();
  void set_last_name(string in_last_name);
  string last_name();
  void set_composer_yob(int in_composer_yob);
  int composer_yob();
  void set_composer_genre(string in_composer_genre);
  string composer_genre();
  void set_ranking(int in_ranking);
  int ranking();
  void set_fact(string in_fact);
  string fact();

  // Methods
  // This method increases a composer's rank by increment.
  void Promote(int increment);
  // This method decreases a composer's rank by decrement.
  void Demote(int decrement);
  // This method displays all the attributes of a composer.
  void Display();

 private:
  string first_name_;
  string last_name_;
  int composer_yob_; // year of birth
  string composer_genre_; // baroque, classical, romantic, etc.
  string fact_;
  int ranking_;
};

डेटाबेस क्लास भी सीधी और सटीक है.

// database.h, Maggie Johnson
// Description: Class for a database of Composer records.
#include  <iostream>
#include "Composer.h"

// Our database holds 100 composers, and no more.
const int kMaxComposers = 100;

class Database {
 public:
  Database();
  ~Database();

  // Add a new composer using operations in the Composer class.
  // For convenience, we return a reference (pointer) to the new record.
  Composer& AddComposer(string in_first_name, string in_last_name,
                        string in_genre, int in_yob, string in_fact);
  // Search for a composer based on last name. Return a reference to the
  // found record.
  Composer& GetComposer(string in_last_name);
  // Display all composers in the database.
  void DisplayAll();
  // Sort database records by rank and then display all.
  void DisplayByRank();

 private:
  // Store the individual records in an array.
  Composer composers_[kMaxComposers];
  // Track the next slot in the array to place a new record.
  int next_slot_;
};

यह देखें कि हमने कंपोज़र से जुड़े डेटा को, अलग क्लास में कैसे ध्यान से इकट्ठा किया है. हम कंपोज़र रिकॉर्ड को दिखाने के लिए, डेटाबेस क्लास में स्ट्रक्चर्ड या क्लास रख सकते थे और उसे सीधे वहीं ऐक्सेस कर सकते थे. हालांकि, यह "किसी मकसद से पूरा नहीं होना चाहिए". इसका मतलब है कि हम ऑब्जेक्ट के साथ उतना ज़्यादा मॉडल नहीं कर रहे हैं जितना हम कर सकते हैं.

कंपोज़र और डेटाबेस क्लास को लागू करने की शुरुआत करते ही, आपको लगेगा कि यह एक अलग कंपोज़र क्लास बनाने से ज़्यादा आसान है. खास तौर पर, कंपोज़र ऑब्जेक्ट पर अलग-अलग ऐटमिक ऑपरेशन होने से, डेटाबेस क्लास में Display() के तरीके लागू करना काफ़ी आसान हो जाता है.

बेशक, एक चीज़ "ज़्यादा मकसद" भी है, जिसमें हम हर चीज़ को एक क्लास बनाने की कोशिश करते हैं या हमारे पास ज़रूरत से ज़्यादा क्लास होती हैं. सही संतुलन बनाने के लिए प्रैक्टिस करने की ज़रूरत होती है और आपको यह लगेगा कि अलग-अलग प्रोग्रामर की राय अलग-अलग हो सकती है. 

अपनी क्लास का डायग्राम बनाकर, अक्सर यह पता लगाया जा सकता है कि आपका मकसद ज़रूरत से ज़्यादा है या कम. जैसा कि पहले बताया गया है, कोडिंग शुरू करने से पहले क्लास का डिज़ाइन बनाना ज़रूरी है. इससे आपको अपने तरीके का विश्लेषण करने में मदद मिल सकती है. इसके लिए इस्तेमाल किया जाने वाला एक सामान्य नोटेशन है यूएमएल (यूनिफ़ाइड मॉडलिंग लैंग्वेज) अब हमारे पास कंपोज़र और डेटाबेस ऑब्जेक्ट के लिए क्लास तय हैं, इसलिए हमें एक ऐसे इंटरफ़ेस की ज़रूरत है जो उपयोगकर्ता को डेटाबेस के साथ इंटरैक्ट करने दे. एक आसान मेन्यू से यह काम हो जाएगा:

Composer Database
---------------------------------------------
1) Add a new composer
2) Retrieve a composer's data
3) Promote/demote a composer's rank
4) List all composers
5) List all composers by rank
0) Quit

हम यूज़र इंटरफ़ेस को एक क्लास या प्रोसेसल प्रोग्राम के तौर पर लागू कर सकते हैं. यह ज़रूरी नहीं है कि C++ प्रोग्राम में सब कुछ एक क्लास हो. असल में, अगर प्रोसेसिंग क्रम में या टास्क के लिए होती है, जैसा कि इस मेन्यू प्रोग्राम में होता है, तो इसे क्रम में लागू करना ठीक है. यह ज़रूरी है कि इसे इस तरह से लागू किया जाए कि यह एक "प्लेसहोल्डर" ही बना रहे. इसका मतलब है कि अगर हमें कभी ग्राफ़िकल यूज़र इंटरफ़ेस बनाना है, तो हमें सिस्टम में यूज़र इंटरफ़ेस के अलावा कुछ और बदलने की ज़रूरत नहीं है.

आवेदन को पूरा करने के लिए, आखिर में हमें क्लास का टेस्ट करने वाला प्रोग्राम बनाना है. कंपोज़र क्लास के लिए, हमें वह मुख्य() प्रोग्राम चाहिए जो इनपुट लेता है, कंपोज़र ऑब्जेक्ट को पॉप्युलेट करता है, और फिर उसे दिखाता है, ताकि यह पक्का किया जा सके कि क्लास ठीक से काम कर रही है. हम यह भी चाहते हैं कि कंपोज़र क्लास के सभी तरीकों को कॉल किया जाए.

// test_composer.cpp, Maggie Johnson
//
// This program tests the Composer class.

#include <iostream>
#include "Composer.h"
using namespace std;

int main()
{
  cout << endl << "Testing the Composer class." << endl << endl;

  Composer composer;

  composer.set_first_name("Ludwig van");
  composer.set_last_name("Beethoven");
  composer.set_composer_yob(1770);
  composer.set_composer_genre("Romantic");
  composer.set_fact("Beethoven was completely deaf during the latter part of "
    "his life - he never heard a performance of his 9th symphony.");
  composer.Promote(2);
  composer.Demote(1);
  composer.Display();
}

हमें डेटाबेस क्लास के लिए मिलते-जुलते टेस्ट प्रोग्राम की ज़रूरत है.

// test_database.cpp, Maggie Johnson
//
// Description: Test driver for a database of Composer records.
#include <iostream>
#include "Database.h"
using namespace std;

int main() {
  Database myDB;

  // Remember that AddComposer returns a reference to the new record.
  Composer& comp1 = myDB.AddComposer("Ludwig van", "Beethoven", "Romantic", 1770,
    "Beethoven was completely deaf during the latter part of his life - he never "
    "heard a performance of his 9th symphony.");
  comp1.Promote(7);

  Composer& comp2 = myDB.AddComposer("Johann Sebastian", "Bach", "Baroque", 1685,
    "Bach had 20 children, several of whom became famous musicians as well.");
  comp2.Promote(5);

  Composer& comp3 = myDB.AddComposer("Wolfgang Amadeus", "Mozart", "Classical", 1756,
    "Mozart feared for his life during his last year - there is some evidence "
    "that he was poisoned.");
  comp3.Promote(2);

  cout << endl << "all Composers: " << endl << endl;
  myDB.DisplayAll();
}

ध्यान दें कि ये आसान टेस्ट प्रोग्राम, पहला अच्छा कदम है. हालांकि, इनके लिए हमें मैन्युअल रूप से आउटपुट की जांच करनी होती है, ताकि यह पक्का किया जा सके कि प्रोग्राम सही तरीके से काम कर रहा है. जैसे-जैसे सिस्टम बड़ा होता जाता है, आउटपुट की मैन्युअल तरीके से जांच करना ठीक से काम नहीं करता. हम अगले लेसन में, यूनिट टेस्ट के तौर पर खुद से जांच करने वाले टेस्ट प्रोग्राम के बारे में बताएंगे.

हमारे ऐप्लिकेशन का डिज़ाइन अब पूरा हो गया है. इसके बाद, क्लास और यूज़र इंटरफ़ेस के लिए .cpp फ़ाइलें लागू करें.शुरू करने के लिए, ऊपर दिए गए .h और टेस्ट ड्राइवर कोड को कॉपी करके, फ़ाइलों में चिपकाएं और उन्हें कंपाइल करें.अपनी क्लास को टेस्ट करने के लिए, टेस्ट ड्राइवर इस्तेमाल करें. इसके बाद, यह इंटरफ़ेस लागू करें:

Composer Database
---------------------------------------------
1) Add a new composer
2) Retrieve a composer's data
3) Promote/demote a composer's rank
4) List all composers
5) List all composers by rank
0) Quit

यूज़र इंटरफ़ेस लागू करने के लिए, डेटाबेस क्लास में तय किए गए तरीकों का इस्तेमाल करें. अपने तरीकों को गड़बड़ी-प्रूफ़ बनाएं. उदाहरण के लिए, कोई रैंकिंग हमेशा 1 से 10 के बीच होनी चाहिए. किसी को भी 101 कंपोज़र जोड़ने की अनुमति न दें, जब तक कि आप डेटाबेस क्लास में डेटा स्ट्रक्चर नहीं बदलना चाहते.

याद रखें - आपके सभी कोड को हमारी कोडिंग से जुड़ी शर्तों का पालन करना होगा. आपकी सुविधा के लिए, यहां इन नियमों को दोहराया गया है:

  • हम जो भी प्रोग्राम लिखते हैं उसकी शुरुआत एक हेडर टिप्पणी से होती है. इसमें लेखक का नाम, उनकी संपर्क जानकारी, कम शब्दों में जानकारी, और इस्तेमाल (अगर ज़रूरी हो) की जानकारी होती है. हर फ़ंक्शन/तरीका, कार्रवाई और उसके इस्तेमाल पर टिप्पणी से शुरू होता है.
  • जब कोड अपने-आप दस्तावेज़ नहीं बनाता, तो हम पूरे वाक्यों का इस्तेमाल करके टिप्पणियां जोड़ते हैं. उदाहरण के लिए, जब प्रोसेसिंग मुश्किल, समझ में न आने वाली, दिलचस्प या ज़रूरी हो.
  • हमेशा जानकारी देने वाले नाम का इस्तेमाल करें: वैरिएबल छोटे अक्षरों वाले ऐसे शब्द होते हैं जिन्हें _ से अलग किया जाता है, जैसे कि my_variable. फ़ंक्शन/मेथड के नाम में, MyExatingFunction() जैसी शब्दों को मार्क करने के लिए अंग्रेज़ी के बड़े अक्षरों का इस्तेमाल किया जाता है. कॉन्सटैंट की शुरुआत "k" से होती है और शब्दों को मार्क करने के लिए, अपर-केस के अक्षरों का इस्तेमाल किया जाता है, जैसे कि kDaysInWeek.
  • इंडेंट दो के गुणज में होता है. पहला लेवल दो स्पेस है. अगर और इंडेंट की ज़रूरत है, तो हम चार स्पेस, छह स्पेस वगैरह का इस्तेमाल करते हैं.

रीयल वर्ल्ड में आपका स्वागत है!

इस मॉड्यूल में, हमने दो बेहद अहम टूल के बारे में बताया है. इनका इस्तेमाल ज़्यादातर सॉफ़्टवेयर इंजीनियरिंग संगठनों में किया जाता है. पहला है बिल्ड टूल और दूसरा, कॉन्फ़िगरेशन मैनेजमेंट सिस्टम. ये दोनों टूल औद्योगिक सॉफ़्टवेयर इंजीनियरिंग में ज़रूरी हैं, जहां कई इंजीनियर अक्सर एक ही बड़े सिस्टम पर काम करते हैं. ये टूल, कोड बेस में बदलावों को सिंक और कंट्रोल करने में मदद करते हैं. साथ ही, ये कई प्रोग्राम और हेडर फ़ाइलों से सिस्टम को कंपाइल और लिंक करने के लिए असरदार तरीके उपलब्ध कराते हैं.

मेकफ़ाइलें

प्रोग्राम बनाने की प्रोसेस को आम तौर पर, एक बिल्ड टूल से मैनेज किया जाता है. यह टूल ज़रूरी फ़ाइलों को सही क्रम में कंपाइल करता है और उन्हें लिंक करता है. आम तौर पर, C++ फ़ाइलों पर डिपेंडेंसी होती हैं. उदाहरण के लिए, किसी एक प्रोग्राम में कॉल किया गया फ़ंक्शन, दूसरे प्रोग्राम में मौजूद होता है. इसके अलावा, हो सकता है कि कई अलग-अलग .cpp फ़ाइलों को हेडर फ़ाइल की ज़रूरत पड़े. एक बिल्ड टूल इन डिपेंडेंसी के हिसाब से सही कंपाइलेशन क्रम तय करता है. साथ ही, इससे सिर्फ़ उन फ़ाइलों को कंपाइल किया जाएगा जिनमें पिछले बिल्ड के बाद बदलाव किया गया है. इससे, लाखों फ़ाइलों वाले सिस्टम में काफ़ी समय बच सकता है.

बनाएं नाम का एक ओपन सोर्स बिल्ड टूल आम तौर पर इस्तेमाल किया जाता है. इसके बारे में जानने के लिए, यह लेख पढ़ें. देखें कि क्या आप कंपोज़र डेटाबेस ऐप्लिकेशन के लिए डिपेंडेंसी ग्राफ़ बना सकते हैं और फिर इसे मेकफ़ाइल में बदल सकते हैं.यहां हमारा समाधान दिया गया है.

कॉन्फ़िगरेशन मैनेजमेंट सिस्टम

औद्योगिक सॉफ़्टवेयर इंजीनियरिंग में इस्तेमाल किया जाने वाला दूसरा टूल, कॉन्फ़िगरेशन मैनेजमेंट (सीएम) है. इसका इस्तेमाल, बदलाव को मैनेज करने के लिए किया जाता है. मान लें कि बॉब और सुमन, दोनों टेक राइटर हैं और दोनों ही तकनीकी मैन्युअल को अपडेट करने पर काम कर रहे हैं. मीटिंग के दौरान, उसका मैनेजर उसे अपडेट करने के लिए एक ही दस्तावेज़ का हर सेक्शन असाइन करता है.

तकनीकी मैन्युअल को ऐसे कंप्यूटर पर सेव किया जाता है जिस पर बॉब और सुज़ैन दोनों ऐक्सेस कर सकते हैं. किसी भी CM टूल या प्रक्रिया के बिना, कई समस्याएं पैदा हो सकती हैं. एक स्थिति यह भी हो सकती है कि दस्तावेज़ को स्टोर करने वाले कंप्यूटर को सेट अप किया गया हो, ताकि बॉब और सुज़ैन दोनों एक ही समय पर मैन्युअल पर काम न कर सकें. इससे उनकी रफ़्तार काफ़ी कम हो जाएगी.

एक ज़्यादा खतरनाक स्थिति तब पैदा होती है, जब स्टोरेज कंप्यूटर एक ही समय पर बॉब और सुज़ैन दोनों को एक ही समय पर दस्तावेज़ खोलने की अनुमति मिल जाती है. ऐसा इन वजहों से हो सकता है:

  1. बॉब अपने कंप्यूटर पर दस्तावेज़ खोलता है और अपने सेक्शन पर काम करता है.
  2. सुमन अपने कंप्यूटर पर दस्तावेज़ खोलती हैं और अपने सेक्शन पर काम करती हैं.
  3. बॉब अपने बदलावों को पूरा करता है और दस्तावेज़ को स्टोरेज कंप्यूटर पर सेव करता है.
  4. सुज़ैन अपने बदलावों को पूरा करती है और दस्तावेज़ को स्टोरेज कंप्यूटर पर सेव करती है.

इस उदाहरण में वह समस्या दिखाई गई है जो तकनीकी मैन्युअल की एक कॉपी पर कोई कंट्रोल न होने पर हो सकती है. जब सुज़ैन अपने बदलावों को सेव करती है, तो वे बॉब के बनाए गए बदलावों को ओवरराइट कर देती हैं.

ठीक इस तरह की स्थिति को CM सिस्टम कंट्रोल कर सकता है. CM सिस्टम की मदद से बॉब और सुज़ैन, दोनों ही तकनीकी मैन्युअल की अपनी कॉपी "देखें" और उन पर काम करते हैं. जब बॉब वापस अपने बदलावों की जांच करता है, तो सिस्टम को पता चलता है कि सुज़ेन के पास अपनी खुद की कॉपी है. जब सुज़ैन अपनी कॉपी की जांच करती है, तो सिस्टम बॉब और सुज़ैन दोनों के बदलावों का विश्लेषण करके एक नया वर्शन बनाता है. इस वर्शन में, दोनों तरह के बदलावों को मर्ज कर दिया जाता है.

सीएम सिस्टम में एक साथ किए जाने वाले बदलावों को मैनेज करने के अलावा भी कई सुविधाएं होती हैं, जैसा कि ऊपर बताया गया है. कई सिस्टम, किसी दस्तावेज़ को पहली बार बनाए जाने के बाद से उसके सभी वर्शन के संग्रह को सेव करते हैं. तकनीकी मैन्युअल के मामले में, यह तब बहुत मददगार साबित हो सकता है, जब उपयोगकर्ता के पास मैन्युअल का पुराना वर्शन हो और वह किसी टेक राइटर से सवाल पूछ रहा हो. CM सिस्टम, टेक राइटर को पुराना वर्शन ऐक्सेस करने की अनुमति देता है. साथ ही, इससे पता चलता है कि उपयोगकर्ता को क्या दिख रहा है.

सॉफ़्टवेयर में किए गए बदलावों को कंट्रोल करने में, CM सिस्टम खास तौर पर काम के होते हैं. ऐसे सिस्टम को सॉफ़्टवेयर कॉन्फ़िगरेशन मैनेजमेंट (एससीएम) सिस्टम कहा जाता है. अगर आपको लगता है कि एक बड़े सॉफ़्टवेयर इंजीनियरिंग संगठन में बड़ी संख्या में अलग-अलग सोर्स कोड फ़ाइलें हैं और उनमें बदलाव करने के लिए बड़ी संख्या में इंजीनियर हैं, तो यह साफ़ है कि एससीएम सिस्टम बहुत ज़रूरी है.

सॉफ़्टवेयर कॉन्फ़िगरेशन मैनेजमेंट

एससीएम सिस्टम सामान्य आइडिया पर आधारित होते हैं: आपकी फ़ाइलों की तय कॉपी एक सेंट्रल रिपॉज़िटरी में रखी जाती हैं. लोग डेटा स्टोर करने की जगह में मौजूद फ़ाइलों की कॉपी देखते हैं, उन कॉपी पर काम करते हैं, और काम पूरा हो जाने के बाद, उन्हें फिर से देखते हैं. एससीएम सिस्टम एक ही मास्टर सेट में, कई लोगों के बदलावों को मैनेज और ट्रैक करते हैं. 

सभी एससीएम सिस्टम में ये ज़रूरी सुविधाएं मिलती हैं:

  • एक साथ काम करने के लिए बनाई गई प्रोसेस का मैनेजमेंट
  • वर्शन
  • सिंक्रनाइज़ेशन के अधिकार

आइए, इन सभी सुविधाओं के बारे में ज़्यादा जानकारी देखें.

एक साथ काम करने के लिए बनाई गई प्रोसेस का मैनेजमेंट

एक साथ कई लोगों को फ़ाइल में बदलाव करने का मतलब है कि उस फ़ाइल में एक साथ कई लोग बदलाव कर सकते हैं. डेटा स्टोर करने की जगह बहुत बड़ी है, इसलिए हम चाहते हैं कि लोग ऐसा कर सकें. हालांकि, इससे कुछ समस्याएं आ सकती हैं.

इंजीनियरिंग डोमेन का एक आसान उदाहरण देखें: मान लीजिए कि हम इंजीनियर को सोर्स कोड के सेंट्रल रिपॉज़िटरी (डेटा स्टोर करने की जगह) में, एक ही फ़ाइल में एक साथ बदलाव करने की अनुमति देते हैं. Client1 और Client2, दोनों को एक ही समय पर फ़ाइल में बदलाव करने होते हैं:

  1. Client1, bar.cpp खोलता है.
  2. Client2, bar.cpp खोलता है.
  3. Client1 फ़ाइल में बदलाव करता है और उसे सेव करता है.
  4. Client2 फ़ाइल में बदलाव करता है और उसे Client1 के बदलावों के ऊपर बदलकर सेव करता है.

ज़ाहिर है, हम ऐसा नहीं चाहते. भले ही, हमने दोनों इंजीनियरों को सीधे मास्टर सेट के बजाय, अलग-अलग कॉपी पर काम करने के लिए कहा जाए (जैसा कि नीचे उदाहरण में दिखाया गया है), तो भी इन कॉपी का मिलान होना ज़रूरी है. ज़्यादातर एससीएम सिस्टम इस समस्या से निपटने के लिए एक से ज़्यादा इंजीनियर को फ़ाइल की जांच करने ("सिंक करने" या "अपडेट करने") की अनुमति देते हैं और ज़रूरत के मुताबिक बदलाव करने की अनुमति देते हैं. इसके बाद, एससीएम सिस्टम एल्गोरिदम चलाकर बदलावों को मर्ज करता है. ऐसा इसलिए होता है, क्योंकि फ़ाइलों को डेटा स्टोर करने की जगह में वापस चेक इन ("सबमिट" या "कमिट") किया जाता है.

ये एल्गोरिदम आसान हो सकते हैं (इंजीनियर से कॉन्फ़्लिक्टिंग चेंज को हल करने के लिए कहें) या इतने आसान नहीं (यह तय करें कि कॉन्फ़्लिक्टिंग बदलावों को समझदारी से कैसे मर्ज किया जाए और इंजीनियर से ही पूछें कि क्या सिस्टम सच में अटक जाता है). 

वर्शन

वर्शन बनाने का मतलब है, फ़ाइल में हुए बदलावों को ट्रैक करना. इससे, फ़ाइल के पिछले वर्शन को फिर से बनाया या रोल बैक किया जा सकता है. ऐसा, डेटा स्टोर करने की जगह में हर फ़ाइल की जांच करते समय, संग्रह की गई फ़ाइल के तौर पर किया जा सकता है या फ़ाइल में किए गए हर बदलाव को सेव किया जा सकता है. हम किसी भी समय संग्रह का इस्तेमाल कर सकते हैं या पुराना वर्शन बनाने के लिए जानकारी में बदलाव कर सकते हैं. वर्शन तय करने वाले सिस्टम, इन लॉग रिपोर्ट भी बना सकते हैं. इनमें यह जानकारी होती है कि बदलावों की जांच किसने की, उन्होंने कब चेक इन किया, और बदलाव क्या थे.

सिंक्रनाइज़ेशन के अधिकार

कुछ एससीएम सिस्टम में, रिपॉज़िटरी में अलग-अलग फ़ाइलों को चेक इन और आउट किया जाता है. ज़्यादा बेहतर सिस्टम की मदद से, एक बार में एक से ज़्यादा फ़ाइलें देखी जा सकती हैं. इंजीनियर डेटा स्टोर करने की अपनी पूरी जानकारी (डेटा स्टोर करने की जगह) या इसके किसी हिस्से की कॉपी देखते हैं और ज़रूरत के मुताबिक फ़ाइलों पर काम करते हैं. इसके बाद, वे समय-समय पर अपने डेटा को मास्टर डेटा स्टोर करने की जगह पर भेजते हैं और अपनी निजी कॉपी अपडेट करते हैं, ताकि वे अन्य लोगों की ओर से किए गए बदलावों के बारे में भी अप-टू-डेट रहें. इस प्रोसेस को सिंक करना या अपडेट करना कहा जाता है.

सबवर्शन

सबवर्ज़न (एसवीएन) एक ओपन सोर्स वर्शन कंट्रोल सिस्टम है. इसमें ऊपर बताई गई सभी सुविधाएं मौजूद हैं.

विवाद होने पर, एसवीएन एक आसान तरीका अपनाता है. जब दो या दो से ज़्यादा इंजीनियर, कोड बेस की एक ही जगह पर अलग-अलग बदलाव करते हैं और फिर दोनों अपने-अपने बदलाव सबमिट करते हैं, तो इसे टकराव की स्थिति माना जाता है. SVN सिर्फ़ इंजीनियर को सूचना देता है कि कोई विवाद है - इसे ठीक करना इंजीनियर पर निर्भर करता है.

कॉन्फ़िगरेशन मैनेजमेंट के बारे में जानने में आपकी मदद करने के लिए, हम इस पूरे कोर्स में एसवीएन का इस्तेमाल करेंगे. इंडस्ट्री में ऐसे सिस्टम काफ़ी आम हैं.

सबसे पहले, अपने सिस्टम पर एसवीएन इंस्टॉल करें. निर्देशों के लिए यहां क्लिक करें. अपना ऑपरेटिंग सिस्टम ढूंढें और सही बाइनरी डाउनलोड करें.

SVN की कुछ शब्दावली

  • बदलाव: किसी फ़ाइल या फ़ाइलों के सेट में होने वाले बदलाव. लगातार बदलने वाले प्रोजेक्ट में बदलाव, एक "स्नैपशॉट" होता है.
  • डेटा स्टोर करने की जगह: ऐसी मास्टर कॉपी जहां एसवीएन, प्रोजेक्ट के बदलावों का पूरा इतिहास सेव करता है. हर प्रोजेक्ट में एक डेटा स्टोर करने की जगह होती है.
  • वर्किंग कॉपी: वह कॉपी जिसमें इंजीनियर किसी प्रोजेक्ट में बदलाव करता है. किसी प्रोजेक्ट की कई ऐसी कॉपी हो सकती हैं जिनका मालिकाना हक अलग-अलग इंजीनियर के पास हो.
  • चेक आउट करें: डेटा स्टोर करने की जगह से काम करने वाली कॉपी का अनुरोध करने के लिए. काम करने वाली कॉपी उस प्रोजेक्ट की स्थिति के बराबर होती है जिसे प्रोजेक्ट के लिए पैसे चुकाए गए थे.
  • असाइन करें: अपनी वर्क कॉपी में मौजूद बदलावों को सेंट्रल रिपॉज़िटरी में भेजने के लिए. इसे चेक-इन या सबमिट करें के नाम से भी जाना जाता है.
  • अपडेट करना: डेटा स्टोर करने की जगह से दूसरों के किए गए बदलावों को डेटा की कॉपी में लाने के लिए या यह बताने के लिए कि आपकी वर्किंग कॉपी में ऐसा कोई बदलाव है या नहीं जिसे मंज़ूरी नहीं दी गई है. यह सिंक जैसा ही है, जैसा कि ऊपर बताया गया है. इसलिए, अपडेट/सिंक करें, ताकि आपकी कॉपी को डेटा स्टोर करने की जगह पर मौजूद कॉपी के साथ अप-टू-डेट रखा जा सके.
  • टकराव: वह स्थिति जब दो इंजीनियर, फ़ाइल के एक ही हिस्से में बदलाव करने की कोशिश करते हैं. SVN समस्याओं को दिखाता है, लेकिन इंजीनियरों को इन्हें ठीक करना होगा.
  • लॉग मैसेज: इस बदलाव के बाद आपकी ओर से जोड़ी जाने वाली टिप्पणी, आपके बदलावों के बारे में बताती है. लॉग से पता चलता है कि किसी प्रोजेक्ट में क्या चल रहा है.

अब आपने SVN इंस्टॉल कर लिया है, इसलिए हम कुछ बुनियादी निर्देशों का पालन करेंगे. सबसे पहले, किसी खास डायरेक्ट्री में डेटा स्टोर करने की जगह सेट अप करें. ये निर्देश हैं:

$ svnadmin create /usr/local/svn/newrepos
$ svn import mytree file:///usr/local/svn/newrepos/project -m "Initial import"
Adding         mytree/foo.c
Adding         mytree/bar.c
Adding         mytree/subdir
Adding         mytree/subdir/foobar.h

Committed revision 1.

import कमांड, डायरेक्ट्री myT का कॉन्टेंट, रिपॉज़िटरी में डायरेक्ट्री प्रोजेक्ट में कॉपी करता है. हम list कमांड की मदद से, डेटा स्टोर करने की जगह में डायरेक्ट्री को देख सकते हैं

$ svn list file:///usr/local/svn/newrepos/project
bar.c
foo.c
subdir/

इंपोर्ट करने से काम करने वाली कॉपी नहीं बनती. ऐसा करने के लिए, आपको svn checkout कमांड का इस्तेमाल करना होगा. इससे डायरेक्ट्री ट्री की एक वर्किंग कॉपी बन जाती है. चलिए, अब इसे करते हैं:

$ svn checkout file:///usr/local/svn/newrepos/project
A    foo.c
A    bar.c
A    subdir
A    subdir/foobar.h
…
Checked out revision 215.

अब आपके पास काम करने वाली कॉपी है, इसलिए वहां फ़ाइलों और डायरेक्ट्री में बदलाव किए जा सकते हैं. आपकी वर्क कॉपी, फ़ाइलों और डायरेक्ट्री के किसी भी दूसरे कलेक्शन की तरह ही होती है - इसमें नई कॉपी जोड़ी जा सकती हैं या उनमें बदलाव किया जा सकता है, उन्हें एक जगह से दूसरी जगह ले जाया जा सकता है, यहां तक की पूरी कॉपी को मिटाया भी जा सकता है. ध्यान दें कि अगर आप अपनी वर्किंग कॉपी में फ़ाइलों को कॉपी करके दूसरी जगह ले जाते हैं, तो अपने ऑपरेटिंग सिस्टम के निर्देशों के बजाय svn कॉपी और svn shift का इस्तेमाल करें. नई फ़ाइल जोड़ने के लिए, svn add का इस्तेमाल करें और फ़ाइल मिटाने के लिए svn delete का इस्तेमाल करें. अगर आपको सिर्फ़ बदलाव करना है, तो एडिटर की मदद से फ़ाइल खोलें और उसमें बदलाव करें!

कुछ स्टैंडर्ड डायरेक्ट्री के नाम अक्सर सबवर्शन के साथ इस्तेमाल किए जाते हैं. "ट्रंक" डायरेक्ट्री में आपके प्रोजेक्ट का डेवलपमेंट का मुख्य हिस्सा होता है. "ब्रांच" डायरेक्ट्री में ब्रांच का ऐसा कोई भी वर्शन शामिल होता है जिस पर शायद आप काम कर रहे हों.

$ svn list file:///usr/local/svn/repos
/trunk
/branches

मान लें कि आपने अपनी वर्किंग कॉपी में सभी ज़रूरी बदलाव कर लिए हैं और आपको इसे डेटा स्टोर करने की जगह के साथ सिंक करना है. अगर रिपॉज़िटरी (डेटा स्टोर करने की जगह) के इस क्षेत्र में कई अन्य इंजीनियर काम कर रहे हैं, तो अपनी काम की कॉपी को अप-टू-डेट रखना ज़रूरी है. आपने जो बदलाव किए हैं उन्हें देखने के लिए, svn status कमांड का इस्तेमाल किया जा सकता है.

A       subdir/new.h      # file is scheduled for addition
D       subdir/old.c        # file is scheduled for deletion
M       bar.c                  # the content in bar.c has local modifications

ध्यान दें कि इस आउटपुट को कंट्रोल करने के लिए, स्टेटस कमांड में कई फ़्लैग मौजूद हैं. अगर आपको किसी फ़ाइल में किए गए खास बदलाव देखने हैं, तो svn diff का इस्तेमाल करें.

$ svn diff bar.c
Index: bar.c
===================================================================
--- bar.c	(revision 5)
+++ bar.c	(working copy)
## -1,18 +1,19 ##
+#include
+#include

 int main(void) {
-  int temp_var;
+ int new_var;
...

आखिर में, रिपॉज़िटरी से अपनी वर्किंग कॉपी अपडेट करने के लिए, svn अपडेट कमांड का इस्तेमाल करें.

$ svn update
U  foo.c
U  bar.c
G  subdir/foobar.h
C  subdir/new.h
Updated to revision 2.

इसी जगह पर विवाद हो सकता है. ऊपर दिए गए आउटपुट में, "U" से पता चलता है कि इन फ़ाइलों के डेटा स्टोर करने की जगह वाले वर्शन में कोई बदलाव नहीं किया गया है और अपडेट पूरा किया गया है. "G" का मतलब है कि मर्ज हो गया है. रिपॉज़िटरी वर्शन को बदल दिया गया था, लेकिन इन बदलावों का आपके डेटा पर कोई असर नहीं था. "C" एक टकराव को दिखाता है. इसका मतलब है कि डेटा स्टोर करने की जगह में सेव किए गए बदलाव, आपके डेटा से ओवरलैप कर रहे हैं. अब आपको उनमें से कोई एक चुनना होगा.

टकराव वाली हर फ़ाइल के लिए, सबवर्शन आपकी काम कर रही कॉपी में तीन फ़ाइलें रखता है:

  • file.mine: यह आपकी फ़ाइल है, जो आपकी काम करने वाली कॉपी को अपडेट करने से पहले आपकी कॉपी में मौजूद थी.
  • file.rOLDREV: यह वह फ़ाइल है जिसे आपने बदलाव करने से पहले डेटा स्टोर करने की जगह से चेक आउट किया था.
  • file.rNEWREV: यह फ़ाइल, डेटा स्टोर करने की जगह में मौजूदा वर्शन है.

विवाद सुलझाने के लिए, आप तीन में से कोई एक काम कर सकते हैं:

  • फ़ाइलों को देखें और मैन्युअल तरीके से मर्ज करें.
  • आपके मौजूदा कॉपी वर्शन पर, SVN की बनाई गई अस्थायी फ़ाइलों में से किसी एक को कॉपी करें.
  • अपने सभी बदलावों को हटाने के लिए, svn वापस चलाएं.

समस्या हल हो जाने के बाद, svn समाधान चलाकर एसवीएन को इसकी जानकारी दें. इससे तीन अस्थायी फ़ाइलें हट जाएंगी और SVN, फ़ाइल को कॉन्फ़्लिक्ट की स्थिति में नहीं देखता.

आखिर में, अपने फ़ाइनल वर्शन को डेटा स्टोर करने की जगह पर सेव करें. ऐसा करने के लिए, svnसुरक्षा कमांड का इस्तेमाल किया जाता है. कोई बदलाव करने पर, आपको एक लॉग मैसेज देना होगा. इस मैसेज से आपके बदलावों की जानकारी मिलती है. यह लॉग मैसेज, आपके बनाए गए बदलाव से अटैच होता है.

svn commit -m "Update files to include new headers."  

SVN के बारे में जानने के लिए और भी बहुत कुछ है. साथ ही, यह भी जानें कि यह बड़े सॉफ़्टवेयर इंजीनियरिंग प्रोजेक्ट में कैसे मदद कर सकता है. वेब पर कई तरह के संसाधन उपलब्ध हैं - बस "सबवर्शन" पर Google सर्च करें.

प्रैक्टिस करने के लिए, अपने कंपोज़र डेटाबेस सिस्टम के लिए डेटा स्टोर करने की जगह बनाएं और अपनी सभी फ़ाइलें इंपोर्ट करें. इसके बाद, काम करने वाली कॉपी चेकआउट करें और ऊपर बताए गए निर्देशों का पालन करें.

References

ऑनलाइन उल्लंघन की किताब

SVN के बारे में Wikipedia का लेख

सबवर्शन वेबसाइट

ऐप्लिकेशन: शरीर की रचना पर एक स्टडी

द यूनिवर्सिटी ऑफ़ टेक्सस, ऑस्टिन के eSkeletons देखें