C में ऑब्जेक्ट-ओरिएंटेशन


157

निफ्टी प्रीप्रोसेसर हैक्स (ANSI C89 / ISO C90 संगत) का एक सेट क्या होगा जो C में किसी प्रकार का बदसूरत (लेकिन उपयोग करने योग्य) ऑब्जेक्ट-ओरिएंटेशन सक्षम करता है?

मैं कुछ अलग-अलग ऑब्जेक्ट-ओरिएंटेड भाषाओं से परिचित हूं, इसलिए कृपया "C ++ जानें" जैसे उत्तरों के साथ जवाब न दें। मैंने " ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग विद एएनएसआई सी " (सावधान: पीडीएफ प्रारूप ) और कई अन्य दिलचस्प समाधान पढ़े हैं, लेकिन मैं ज्यादातर आपकी दिलचस्पी में हूं :-)!


यह भी देखें कि क्या आप C में ऑब्जेक्ट ओरिएंटेड कोड लिख सकते हैं?


1
क्या मैं D सीखने के लिए प्रतिक्रिया कर सकता हूँ और जहाँ आप वास्तव में C. digitalmars.com/d की
टिम मैथ्यूज

2
@ दीना: "देखें" के लिए भी धन्यवाद। वह पोस्ट रोचक थी।

1
दिलचस्प सवाल यह है कि आप सी पर OOP का प्री-प्रोसेसर हैक क्यों चाहते हैं
Calyth

3
@ पता: मुझे लगता है कि ओओपी उपयोगी है और "मैं कुछ एम्बेडेड सिस्टम के साथ काम करता हूं जो केवल वास्तव में सी कंपाइलर उपलब्ध हैं" (ऊपर से)। इसके अलावा, आप निफ्टी प्रीप्रोसेसर हैक्स को देखने के लिए दिलचस्प नहीं पाते हैं?

जवाबों:


31

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

C में अपवाद अन्य OO भाषाओं में पाए जाने वाले TRY-CATCH-FINALLY का C89 कार्यान्वयन है। यह एक परीक्षण और कुछ उदाहरणों के साथ आता है।

लॉरेंट डेनियू द्वारा दोनों, जो सी में ओओपी पर बहुत काम कर रहा है


@vonbrand COS ने गितुब में प्रवास किया जहां अंतिम प्रतिबद्ध अंतिम ग्रीष्म ऋतु है। परिपक्वता कमिटमेंट की कमी बता सकती है।
दार्शनिक

185

मैं प्री -प्रोसेसर (ab) उपयोग के खिलाफ सलाह दूंगा कि वह C सिंटैक्स को एक और अधिक ऑब्जेक्ट-ओरिएंटेड भाषा की तरह बनाने की कोशिश करे। सबसे बुनियादी स्तर पर, आप बस वस्तुओं के रूप में सादे संरचना का उपयोग करते हैं और उन्हें संकेत द्वारा पास करते हैं:

struct monkey
{
    float age;
    bool is_male;
    int happiness;
};

void monkey_dance(struct monkey *monkey)
{
    /* do a little dance */
}

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

struct base
{
    /* base class members */
};

struct derived
{
    struct base super;
    /* derived class members */
};

struct derived d;
struct base *base_ptr = (struct base *)&d;  // upcast
struct derived *derived_ptr = (struct derived *)base_ptr;  // downcast

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

struct base;
struct base_vtable
{
    void (*dance)(struct base *);
    void (*jump)(struct base *, int how_high);
};

struct base
{
    struct base_vtable *vtable;
    /* base members */
};

void base_dance(struct base *b)
{
    b->vtable->dance(b);
}

void base_jump(struct base *b, int how_high)
{
    b->vtable->jump(b, how_high);
}

struct derived1
{
    struct base super;
    /* derived1 members */
};

void derived1_dance(struct derived1 *d)
{
    /* implementation of derived1's dance function */
}

void derived1_jump(struct derived1 *d, int how_high)
{
    /* implementation of derived 1's jump function */
}

/* global vtable for derived1 */
struct base_vtable derived1_vtable =
{
    &derived1_dance, /* you might get a warning here about incompatible pointer types */
    &derived1_jump   /* you can ignore it, or perform a cast to get rid of it */
};

void derived1_init(struct derived1 *d)
{
    d->super.vtable = &derived1_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

struct derived2
{
    struct base super;
    /* derived2 members */
};

void derived2_dance(struct derived2 *d)
{
    /* implementation of derived2's dance function */
}

void derived2_jump(struct derived2 *d, int how_high)
{
    /* implementation of derived2's jump function */
}

struct base_vtable derived2_vtable =
{
   &derived2_dance,
   &derived2_jump
};

void derived2_init(struct derived2 *d)
{
    d->super.vtable = &derived2_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

int main(void)
{
    /* OK!  We're done with our declarations, now we can finally do some
       polymorphism in C */
    struct derived1 d1;
    derived1_init(&d1);

    struct derived2 d2;
    derived2_init(&d2);

    struct base *b1_ptr = (struct base *)&d1;
    struct base *b2_ptr = (struct base *)&d2;

    base_dance(b1_ptr);  /* calls derived1_dance */
    base_dance(b2_ptr);  /* calls derived2_dance */

    base_jump(b1_ptr, 42);  /* calls derived1_jump */
    base_jump(b2_ptr, 42);  /* calls derived2_jump */

    return 0;
}

और यह है कि आप सी में बहुरूपता कैसे करते हैं। यह सुंदर नहीं है, लेकिन यह काम करता है। आधार और व्युत्पन्न वर्गों के बीच सूचक कलाकारों को शामिल करने वाले कुछ चिपचिपा मुद्दे हैं, जो तब तक सुरक्षित हैं जब तक कि आधार वर्ग व्युत्पन्न वर्ग का पहला सदस्य है। मल्टीपल इनहेरिटेंस बहुत कठिन है - उस मामले में, पहले के अलावा बेस कक्षाओं के बीच केस करने के लिए, आपको उचित ऑफसेट के आधार पर अपने पॉइंटर्स को मैन्युअल रूप से समायोजित करने की आवश्यकता है, जो वास्तव में मुश्किल और त्रुटि-प्रवण है।

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


6
एडम, एक प्रकार की वैश्विक व्यवहार्यता को बदलने का मज़ा सी-में डक-टाइपिंग का अनुकरण करना है :)
jmucchiello

अब मुझे C ++ पर दया आती है ... बेशक C ++ सिंटैक्स स्पष्ट है, लेकिन चूंकि यह एक मामूली वाक्यविन्यास नहीं है, इसलिए मैं छोटा हूं। मुझे आश्चर्य है कि अगर C ++ और C के बीच कुछ हाइब्रिड हासिल किया जा सकता है, तो शून्य * अभी भी मान्य कास्टेबल प्रकार होगा। इसके साथ struct derived {struct base super;};का भाग यह अनुमान लगाने के लिए स्पष्ट है कि यह कैसे काम करता है, क्योंकि बाइट्स क्रम से यह सही है।
जोकून

2
सुरुचिपूर्ण कोड के लिए +1, अच्छी तरह से लिखा गया। यही वह है जिसकी तलाश में मैं हूं!
होम्यनकुलस रेटिकुल्ली

3
बहुत बढ़िया। यह ठीक यही है कि मैं इसे कैसे कर रहा हूं और यह सही तरीका भी है। मन में संरचना / वस्तु के लिए एक पॉइंटर की आवश्यकता के बजाय आपको एक पॉइंटर को पूर्णांक (पता) में पास करना चाहिए। यह आपको असीमित पॉलीमॉर्फिक विधि कॉल के लिए किसी भी प्रकार की वस्तु में पारित करने की अनुमति देगा। इसके अलावा, केवल एक चीज गायब है जो आपकी संरचना (वस्तुओं / वर्गों) को शुरू करने के लिए एक फ़ंक्शन है। इसमें एक मॉलॉक फ़ंक्शन शामिल होगा और एक पॉइंटर लौटाएगा। शायद मैं सी में संदेश पासिंग (वस्तुनिष्ठ) करने के लिए एक टुकड़ा जोड़

1
यह पुआल मुझे सी ++ का टूटा हुआ है, और सी का अधिक उपयोग करने के लिए (इससे पहले कि मैं केवल विरासत के लिए सी ++ का उपयोग करता हूं) धन्यवाद
ऐनी क्विन

31

मैंने एक बार एक सी लाइब्रेरी के साथ काम किया था जिसे इस तरह से लागू किया गया था, जिसने मुझे काफी सुरुचिपूर्ण बना दिया। उन्होंने लिखा था, सी में, वस्तुओं को परिभाषित करने का एक तरीका, फिर उनसे विरासत में मिला ताकि वे सी ++ ऑब्जेक्ट के रूप में एक्स्टेंसिबल थे। मूल विचार यह था:

  • प्रत्येक ऑब्जेक्ट की अपनी फ़ाइल थी
  • सार्वजनिक कार्यों और चर को किसी वस्तु के लिए .h फ़ाइल में परिभाषित किया गया है
  • निजी चर और फ़ंक्शन केवल .c फ़ाइल में स्थित थे
  • "इनहेरिट" करने के लिए एक नई संरचना बनाई जाती है जिसमें संरचना के पहले सदस्य को विरासत से वस्तु मिलती है

इनहेरिटिंग का वर्णन करना मुश्किल है, लेकिन मूल रूप से यह यह था:

struct vehicle {
   int power;
   int weight;
}

फिर दूसरी फाइल में:

struct van {
   struct vehicle base;
   int cubic_size;
}

तब आपके पास मेमोरी में निर्मित एक वैन हो सकती है, और कोड द्वारा उपयोग किया जा रहा है जो केवल वाहनों के बारे में जानता था:

struct van my_van;
struct vehicle *something = &my_van;
vehicle_function( something );

यह खूबसूरती से काम करता है, और .h फाइलें परिभाषित करती हैं कि आपको प्रत्येक वस्तु के साथ क्या करने में सक्षम होना चाहिए।


मैं वास्तव में इस समाधान को पसंद करता हूं, सिवाय इसके कि "ऑब्जेक्ट" के सभी इंटर्नल सार्वजनिक हैं।
लॉरेंस Dol

6
@ सुरक्षा बंदर: सी का कोई पहुंच नियंत्रण नहीं है। कार्यान्वयन विवरण को छिपाने का एकमात्र तरीका अपारदर्शी बिंदुओं के माध्यम से बातचीत करना है, जो बहुत दर्दनाक हो सकता है, क्योंकि सभी क्षेत्रों को एक्सेसर विधियों के माध्यम से एक्सेस करने की आवश्यकता होगी जो शायद इनलेट नहीं हो सकते।
एडम रोसेनफील्ड

1
@ एडम: लिंक-टाइम ऑप्टिमाइज़ेशन का समर्थन करने वाले कंपाइलर उन्हें ठीक-ठीक
क्रिस्टोफ़

9
यदि आप ऐसा करते हैं, तो आपको यह भी सुनिश्चित करना चाहिए कि सार्वजनिक फ़ाइल के रूप में परिभाषित नहीं होने वाली .c फ़ाइल में सभी फ़ंक्शन स्थिर के रूप में परिभाषित किए गए हैं ताकि वे आपकी ऑब्जेक्ट फ़ाइलों में नामित फ़ंक्शन के रूप में समाप्त न हों। यह सुनिश्चित करता है कि लिंक चरण में कोई भी उनके नाम नहीं पा सकता है।
jmucchiello

2
@ मार्सेल: C का उपयोग किया गया था क्योंकि कोड को निम्न स्तर के बोर्डों पर तैनात किया गया था जो स्वायत्त प्रणालियों के लिए विभिन्न प्रकार के प्रोसेसर चला रहे थे। वे सभी C से उनके संबंधित देशी बायनेरिज़ के संकलन का समर्थन करते थे। दृष्टिकोण ने कोड को पढ़ने में बहुत आसान बना दिया जब आप महसूस करते हैं कि वे क्या करने की कोशिश कर रहे थे।
कीवेली

18

लिनक्स के लिए गनोम डेस्कटॉप को ऑब्जेक्ट ओरिएंटेड सी में लिखा गया है, और इसमें " गोबजेक्ट " नामक एक ऑब्जेक्ट मॉडल है , जो गुण, वंशानुक्रम, बहुरूपता का समर्थन करता है, साथ ही संदर्भ, ईवेंट हैंडलिंग (जिसे "सिग्नल" कहा जाता है), रनटाइम जैसे कुछ अन्य उपहार भी हैं टाइपिंग, निजी डेटा, आदि।

इसमें क्लास की पदानुक्रम के आसपास टाइपकास्टिंग जैसे काम करने के लिए प्रीप्रोसेसर हैक्स शामिल हैं, यहाँ एक उदाहरण वर्ग है जो मैंने GNOME के ​​लिए लिखा है (gchar की तरह चीजें टाइप की जाती हैं):

कक्षा स्रोत

क्लास हैडर

GObject संरचना के अंदर एक GType पूर्णांक है जिसे GLib के डायनामिक टाइपिंग सिस्टम के लिए एक जादुई संख्या के रूप में उपयोग किया जाता है (आप इसे टाइप करने के लिए संपूर्ण संरचना को "GType" में डाल सकते हैं)।


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

लिंक तय हो गए हैं।
जेम्स केप

4
लिंक फिर से टूट गए हैं।
सीनियरमे

6

मैं सी में इस तरह का काम करता था, इससे पहले कि मुझे पता था कि ओओपी क्या था।

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

विचार यह है कि वस्तु को xxx_crt () का उपयोग करके तुरंत हटा दिया जाता है और xxx_dlt () का उपयोग करके हटा दिया जाता है। "सदस्य" विधियों में से प्रत्येक को संचालित करने के लिए एक विशेष रूप से टाइप किए गए पॉइंटर को लेता है।

मैंने इस तरह से एक लिंक्ड सूची, चक्रीय बफर और कई अन्य चीजों को लागू किया।

मुझे स्वीकार करना चाहिए, मैंने इस दृष्टिकोण के साथ विरासत को कैसे लागू किया जाए, इस पर कभी कोई विचार नहीं किया है। मुझे लगता है कि केवली द्वारा प्रस्तुत कुछ मिश्रण एक अच्छा रास्ता हो सकता है।

dtb.c:

#include <limits.h>
#include <string.h>
#include <stdlib.h>

static void dtb_xlt(void *dst, const void *src, vint len, const byte *tbl);

DTABUF *dtb_crt(vint minsiz,vint incsiz,vint maxsiz) {
    DTABUF          *dbp;

    if(!minsiz) { return NULL; }
    if(!incsiz)                  { incsiz=minsiz;        }
    if(!maxsiz || maxsiz<minsiz) { maxsiz=minsiz;        }
    if(minsiz+incsiz>maxsiz)     { incsiz=maxsiz-minsiz; }
    if((dbp=(DTABUF*)malloc(sizeof(*dbp))) == NULL) { return NULL; }
    memset(dbp,0,sizeof(*dbp));
    dbp->min=minsiz;
    dbp->inc=incsiz;
    dbp->max=maxsiz;
    dbp->siz=minsiz;
    dbp->cur=0;
    if((dbp->dta=(byte*)malloc((vuns)minsiz)) == NULL) { free(dbp); return NULL; }
    return dbp;
    }

DTABUF *dtb_dlt(DTABUF *dbp) {
    if(dbp) {
        free(dbp->dta);
        free(dbp);
        }
    return NULL;
    }

vint dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen) {
    if(!dbp) { errno=EINVAL; return -1; }
    if(dtalen==-1) { dtalen=(vint)strlen((byte*)dtaptr); }
    if((dbp->cur + dtalen) > dbp->siz) {
        void        *newdta;
        vint        newsiz;

        if((dbp->siz+dbp->inc)>=(dbp->cur+dtalen)) { newsiz=dbp->siz+dbp->inc; }
        else                                       { newsiz=dbp->cur+dtalen;   }
        if(newsiz>dbp->max) { errno=ETRUNC; return -1; }
        if((newdta=realloc(dbp->dta,(vuns)newsiz))==NULL) { return -1; }
        dbp->dta=newdta; dbp->siz=newsiz;
        }
    if(dtalen) {
        if(xlt256) { dtb_xlt(((byte*)dbp->dta+dbp->cur),dtaptr,dtalen,xlt256); }
        else       { memcpy(((byte*)dbp->dta+dbp->cur),dtaptr,(vuns)dtalen);   }
        dbp->cur+=dtalen;
        }
    return 0;
    }

static void dtb_xlt(void *dst,const void *src,vint len,const byte *tbl) {
    byte            *sp,*dp;

    for(sp=(byte*)src,dp=(byte*)dst; len; len--,sp++,dp++) { *dp=tbl[*sp]; }
    }

vint dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...) {
    byte            textÝ501¨;
    va_list         ap;
    vint            len;

    va_start(ap,format); len=sprintf_len(format,ap)-1; va_end(ap);
    if(len<0 || len>=sizeof(text)) { sprintf_safe(text,sizeof(text),"STRTOOLNG: %s",format); len=(int)strlen(text); }
    else                           { va_start(ap,format); vsprintf(text,format,ap); va_end(ap);                     }
    return dtb_adddta(dbp,xlt256,text,len);
    }

vint dtb_rmvdta(DTABUF *dbp,vint len) {
    if(!dbp) { errno=EINVAL; return -1; }
    if(len > dbp->cur) { len=dbp->cur; }
    dbp->cur-=len;
    return 0;
    }

vint dtb_reset(DTABUF *dbp) {
    if(!dbp) { errno=EINVAL; return -1; }
    dbp->cur=0;
    if(dbp->siz > dbp->min) {
        byte *newdta;
        if((newdta=(byte*)realloc(dbp->dta,(vuns)dbp->min))==NULL) {
            free(dbp->dta); dbp->dta=null; dbp->siz=0;
            return -1;
            }
        dbp->dta=newdta; dbp->siz=dbp->min;
        }
    return 0;
    }

void *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen) {
    if(!elmlen || (elmidx*elmlen)>=dbp->cur) { return NULL; }
    return ((byte*)dbp->dta+(elmidx*elmlen));
    }

dtb.h

typedef _Packed struct {
    vint            min;                /* initial size                       */
    vint            inc;                /* increment size                     */
    vint            max;                /* maximum size                       */
    vint            siz;                /* current size                       */
    vint            cur;                /* current data length                */
    void            *dta;               /* data pointer                       */
    } DTABUF;

#define dtb_dtaptr(mDBP)                (mDBP->dta)
#define dtb_dtalen(mDBP)                (mDBP->cur)

DTABUF              *dtb_crt(vint minsiz,vint incsiz,vint maxsiz);
DTABUF              *dtb_dlt(DTABUF *dbp);
vint                dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen);
vint                dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...);
vint                dtb_rmvdta(DTABUF *dbp,vint len);
vint                dtb_reset(DTABUF *dbp);
void                *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen);

पुनश्च: एक प्रकार की शीशी बस int का एक प्रकार था - मैंने इसका उपयोग यह याद दिलाने के लिए किया कि यह लंबाई एक प्लेटफ़ॉर्म से प्लेटफ़ॉर्म (पोर्ट्रेट के लिए) में परिवर्तनशील थी।


7
पवित्र मोली, यह एक सी प्रतियोगिता जीत सकता है! मुझें यह पसंद है! :)
घोड़ा जुई

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

6

थोड़ा ऑफ-टॉपिक, लेकिन मूल C ++ कंपाइलर, Cfront , C ++ को C और फिर कोडांतरक को संकलित करता है।

यहां संरक्षित है


मैंने वास्तव में इसे पहले देखा है। मेरा मानना ​​है कि यह एक अच्छा काम था।

@Anthony Cuozzo: स्टेन लिपमैन ने 'C ++ - इनसाइड द ऑब्जेक्ट मॉडल' नाम की एक महान पुस्तक लिखी, जहाँ उन्होंने सी-फ्रंट को लिखने और बनाए रखने में अपने अनुभवों और डिज़ाइन के निर्णयों से बहुत संबंधित थे। यह अभी भी एक अच्छा पढ़ा है और सी से सी + + कई साल पहले संक्रमण करते समय मेरी बहुत मदद की
zebrabox

5

यदि आप वस्तुओं को स्टैटिक विधियों के रूप thisमें कहते हैं जो फ़ंक्शन में एक अंतर्निहित ' ' पास करते हैं तो यह सोच को O C में आसान बना सकता है।

उदाहरण के लिए:

String s = "hi";
System.out.println(s.length());

हो जाता है:

string s = "hi";
printf(length(s)); // pass in s, as an implicit this

या कुछ इस तरह का।


6
@Artelius: यकीन है, लेकिन कभी-कभी स्पष्ट नहीं होता है, जब तक कि यह कहा नहीं जाता है। इसके लिए +1।
लॉरेंस डोल

1
बेहतर अभी तक होगाstring->length(s);
OozeMeister

4

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


मैं इसमें (ffmpeg) कोई भी कारखाना कार्य नहीं देखता, बल्कि यह बहुरूपता / वंशानुक्रम (ऊपर सुझाए गए तुच्छ तरीके) का उपयोग करता प्रतीत होता है।
FL4SOF

avcodec_open एक कारखाने का कार्य है। यह फंक्शन पॉइंटर्स को AVCodecContext स्ट्रक्चर (जैसे draw_horiz_band) में भरता है। यदि आप FF_COMMON_FRAME मैक्रो का उपयोग avcodec.h में देखते हैं, तो आपको डेटा सदस्यों के उत्तराधिकार के लिए कुछ समान दिखाई देगा। IMHO, ffmpeg मुझे साबित करता है कि OOP C ++ में सबसे अच्छा किया जाता है, न कि C.
Mr Fooz

3

क्या तुम सच में catefully सोचता है, तो भी मानक सी पुस्तकालय उपयोग OOP - पर विचार FILE *के लिए एक उदाहरण के रूप में: fopen()एक initializes FILE *वस्तु, और आप इसे सदस्य तरीकों का उपयोग का उपयोग fscanf(), fprintf(), fread(), fwrite()और दूसरों को, और अंततः के साथ इसे अंतिम रूप देने fclose()

आप छद्म उद्देश्य-सी के साथ भी जा सकते हैं जो कठिन नहीं है:

typedef void *Class;

typedef struct __class_Foo
{
    Class isa;
    int ivar;
} Foo;

typedef struct __meta_Foo
{
    Foo *(*alloc)(void);
    Foo *(*init)(Foo *self);
    int (*ivar)(Foo *self);
    void (*setIvar)(Foo *self);
} meta_Foo;

meta_Foo *class_Foo;

void __meta_Foo_init(void) __attribute__((constructor));
void __meta_Foo_init(void)
{
    class_Foo = malloc(sizeof(meta_Foo));
    if (class_Foo)
    {
        class_Foo = {__imp_Foo_alloc, __imp_Foo_init, __imp_Foo_ivar, __imp_Foo_setIvar};
    }
}

Foo *__imp_Foo_alloc(void)
{
    Foo *foo = malloc(sizeof(Foo));
    if (foo)
    {
        memset(foo, 0, sizeof(Foo));
        foo->isa = class_Foo;
    }
    return foo;
}

Foo *__imp_Foo_init(Foo *self)
{
    if (self)
    {
        self->ivar = 42;
    }
    return self;
}
// ...

काम में लाना:

int main(void)
{
    Foo *foo = (class_Foo->init)((class_Foo->alloc)());
    printf("%d\n", (foo->isa->ivar)(foo)); // 42
    foo->isa->setIvar(foo, 60);
    printf("%d\n", (foo->isa->ivar)(foo)); // 60
    free(foo);
}

कुछ ऑब्जेक्टिव-सी कोड के परिणामस्वरूप ऐसा हो सकता है, यदि कोई बहुत पुराना ऑब्जेक्टिव-सी-टू-सी अनुवादक उपयोग किया जाता है:

@interface Foo : NSObject
{
    int ivar;
}
- (int)ivar;
- (void)setIvar:(int)ivar;
@end

@implementation Foo
- (id)init
{
    if (self = [super init])
    {
        ivar = 42;
    }
    return self;
}
@end

int main(void)
{
    Foo *foo = [[Foo alloc] init];
    printf("%d\n", [foo ivar]);
    [foo setIvar:60];
    printf("%d\n", [foo ivar]);
    [foo release];
}

में क्या करता __attribute__((constructor))है void __meta_Foo_init(void) __attribute__((constructor))?
एई ड्रू

1
यह एक जीसीसी विस्तार है जो यह सुनिश्चित करेगा कि बाइनरी को मेमोरी में लोड होने पर चिह्नित फ़ंक्शन कहा जाता है। @AEDrew
मैक्सथन चान

popen(3)एक FILE *अन्य उदाहरण के लिए भी लौटता है ।
प्रियेफ़टन

3

मुझे लगता है कि एडम रोसेनफील्ड ने जो पोस्ट किया है, वह ओओपी को सी में करने का सही तरीका है। मैं जोड़ना चाहूंगा कि वह जो दिखाता है वह वस्तु का कार्यान्वयन है। दूसरे शब्दों में वास्तविक कार्यान्वयन .cफ़ाइल में डाला जाएगा , जबकि इंटरफ़ेस हेडर .hफ़ाइल में डाला जाएगा । उदाहरण के लिए, ऊपर दिए गए बंदर उदाहरण का उपयोग करते हुए:

इंटरफ़ेस की तरह दिखेगा:

//monkey.h

    struct _monkey;

    typedef struct _monkey monkey;

    //memory management
    monkey * monkey_new();
    int monkey_delete(monkey *thisobj);
    //methods
    void monkey_dance(monkey *thisobj);

आप इंटरफ़ेस .hफ़ाइल में देख सकते हैं कि आप केवल प्रोटोटाइप को परिभाषित कर रहे हैं। फिर आप कार्यान्वयन भाग " .cफ़ाइल" को एक स्थिर या गतिशील लाइब्रेरी में संकलित कर सकते हैं । यह एनकैप्सुलेशन बनाता है और आप चाहें तो कार्यान्वयन को बदल भी सकते हैं। आपके ऑब्जेक्ट के उपयोगकर्ता को इसके कार्यान्वयन के बारे में लगभग कुछ भी जानने की जरूरत है। यह ऑब्जेक्ट के समग्र डिजाइन पर भी ध्यान केंद्रित करता है।

यह मेरा व्यक्तिगत विश्वास है कि ऊप आपके कोड संरचना और पुन: प्रयोज्य की अवधारणा का एक तरीका है और वास्तव में उन अन्य चीजों से कोई लेना-देना नहीं है जिन्हें c ++ में जोड़ा जाता है जैसे ओवरलोडिंग या टेम्पलेट। हाँ, वे बहुत अच्छी उपयोगी विशेषताएं हैं लेकिन वे वास्तव में क्या वस्तु उन्मुख प्रोग्रामिंग के प्रतिनिधि नहीं हैं।


आप एक संरचना की घोषणा कर सकते हैं कि typedef struct Monkey {} Monkey; इसे बनाने के बाद इसे टाइप करने वाली बात क्या है?
मार्कसज

1
@MarcusJ struct _monkeyकेवल एक प्रोटोटाइप है। वास्तविक प्रकार की परिभाषा कार्यान्वयन फ़ाइल (.c फ़ाइल) में परिभाषित की गई है। यह एनकैप्सुलेशन प्रभाव बनाता है और एपीआई डेवलपर को एपीआई को संशोधित किए बिना भविष्य में बंदर संरचना को फिर से परिभाषित करने की अनुमति देता है। एपीआई के उपयोगकर्ताओं को केवल वास्तविक तरीकों से संबंधित होना चाहिए। एपीआई डिजाइनर कार्यान्वयन का ख्याल रखता है जिसमें ऑब्जेक्ट / संरचना कैसे रखी जाती है। तो ऑब्जेक्ट / संरचना का विवरण उपयोगकर्ता (एक अपारदर्शी प्रकार) से छिपा हुआ है।

मैं हेडर में अपनी संरचना को परिभाषित करता हूं, क्या यह मानक नहीं है? ठीक है, मैं इसे इस तरह से करता हूं क्योंकि मुझे कभी-कभी उस पुस्तकालय के बाहर संरचना के सदस्यों तक पहुंचने की आवश्यकता होती है।
मार्कस जे

1
@MarcusJ यदि आप चाहें (कोई मानक नहीं है) आप हेडर में संरचना को परिभाषित कर सकते हैं। लेकिन अगर आप इसे बदलना चाहते हैं तो यह आंतरिक संरचना है कि आप अपने कोड को तोड़ सकते हैं। एनकैप्सुलेशन केवल कोडिंग की एक शैली है जो आपके कोड को तोड़ने के बिना कार्यान्वयन को बदलना आसान बनाता है। int getCount(ObjectType obj)यदि आप कार्यान्वयन फ़ाइल में संरचना को परिभाषित करने के लिए चुनते हैं, तो आप हमेशा अपने सदस्यों को एक्सेसर विधियों आदि के माध्यम से एक्सेस कर सकते हैं ।

2

मेरी सिफारिश: इसे सरल रखें। मेरे पास सबसे बड़े मुद्दों में से एक पुराने सॉफ्टवेयर को बनाए रखना है (कभी-कभी 10 साल से अधिक पुराना)। यदि कोड सरल नहीं है, तो यह मुश्किल हो सकता है। हां, कोई C में बहुरूपता के साथ बहुत उपयोगी OOP लिख सकता है, लेकिन इसे पढ़ना मुश्किल हो सकता है।

मैं सरल वस्तुओं को पसंद करता हूं जो कुछ अच्छी तरह से परिभाषित कार्यक्षमता को बाधित करते हैं। इसका एक शानदार उदाहरण GLIB2 है , उदाहरण के लिए एक हैश तालिका:

GHastTable* my_hash = g_hash_table_new(g_str_hash, g_str_equal);
int size = g_hash_table_size(my_hash);
...

g_hash_table_remove(my_hash, some_key);

चाबियाँ हैं:

  1. सरल वास्तुकला और डिजाइन पैटर्न
  2. बेसिक OOP इनकैप्सुलेशन प्राप्त करता है।
  3. लागू करने, पढ़ने, समझने और बनाए रखने में आसान

1

अगर मैं CI में OOP लिखने जा रहा था तो शायद एक छद्म- Pimpl डिज़ाइन के साथ जाऊंगा । पॉइंटर्स टू स्ट्रक्चर्स के बजाय, आप पाइंट्स टू पाइंट टू पॉइंट टू पॉइंट टू स्ट्रॉफ़्स। यह सामग्री को अपारदर्शी बनाता है और बहुरूपता और वंशानुक्रम को सुविधाजनक बनाता है।

सी में ओओपी के साथ वास्तविक समस्या तब होती है जब चर दायरे से बाहर निकलते हैं। कोई संकलक-जनित विध्वंसक नहीं हैं और जो मुद्दों का कारण बन सकते हैं। मैक्रोज़ संभवतः मदद कर सकते हैं, लेकिन यह हमेशा देखने में बदसूरत होने वाला है।


1
सी में प्रोग्रामिंग करते समय, मैं ifबयानों का उपयोग करके और अंत में उन्हें जारी करके गुंजाइश से निपटता हूं । उदाहरण के लिएif ( (obj = new_myObject()) ) { /* code using myObject */ free_myObject(obj); }

1

C के साथ ऑब्जेक्ट ओरिएंटेड स्टाइल में प्रोग्राम करने का एक और तरीका कोड जनरेटर का उपयोग करना है जो एक डोमेन विशिष्ट भाषा को C. में बदल देता है। जैसा कि टाइपोस्क्रिप्ट और जावास्क्रिप्ट के साथ किया जाता है ताकि OOP को js में लाया जा सके।


0
#include "triangle.h"
#include "rectangle.h"
#include "polygon.h"

#include <stdio.h>

int main()
{
    Triangle tr1= CTriangle->new();
    Rectangle rc1= CRectangle->new();

    tr1->width= rc1->width= 3.2;
    tr1->height= rc1->height= 4.1;

    CPolygon->printArea((Polygon)tr1);

    printf("\n");

    CPolygon->printArea((Polygon)rc1);
}

आउटपुट:

6.56
13.12

यहाँ C के साथ OO प्रोग्रामिंग क्या है का एक शो है।

यह वास्तविक, शुद्ध सी, कोई प्रीप्रोसेसर मैक्रोज़ नहीं है। हमारे पास विरासत, बहुरूपता और डेटा एनकैप्सुलेशन (डेटा निजी से कक्षाओं या वस्तुओं सहित) है। संरक्षित क्वालिफायर समतुल्य के लिए कोई मौका नहीं है, अर्थात्, निजी डेटा असंख्य श्रृंखला भी निजी है। लेकिन यह एक असुविधा नहीं है क्योंकि मुझे नहीं लगता कि यह आवश्यक है।

CPolygon इसका तत्काल मूल्यांकन नहीं किया गया है क्योंकि हम इसका उपयोग केवल उन असंख्य श्रृंखलाओं की वस्तुओं में हेरफेर करने के लिए करते हैं जिनके सामान्य पहलू हैं लेकिन उनका अलग-अलग क्रियान्वयन (बहुरूपता) है।


0

@Adam Rosenfield को C के साथ OOP प्राप्त करने के तरीके की बहुत अच्छी व्याख्या है

इसके अलावा, मैं आपको पढ़ने की सलाह दूंगा

1) pjsip

वीओआईपी के लिए एक बहुत अच्छा सी लाइब्रेरी। आप सीख सकते हैं कि यह कैसे ओओपी प्राप्त करता है हालांकि संरचनाएं और सूचक तालिकाएं कार्य करती हैं

2) iOS रनटाइम

जानें कैसे iOS रनटाइम शक्तियां उद्देश्य सी। यह isa पॉइंटर, मेटा क्लास के माध्यम से OOP प्राप्त करता है


0

मेरे लिए C में ऑब्जेक्ट ओरिएंटेशन में ये विशेषताएं होनी चाहिए:

  1. एनकैप्सुलेशन और डेटा छिपाना (संरचना / अपारदर्शी बिंदुओं का उपयोग करके प्राप्त किया जा सकता है)

  2. बहुरूपता के लिए वंशानुक्रम और समर्थन (एकल वंशानुक्रम का उपयोग करके प्राप्त किया जा सकता है - सुनिश्चित करें कि सार आधार तत्काल नहीं है)

  3. निर्माता और विध्वंसक कार्यक्षमता (प्राप्त करने के लिए आसान नहीं)

  4. टाइप चेकिंग (कम से कम उपयोगकर्ता द्वारा परिभाषित प्रकारों के लिए सी किसी भी लागू नहीं करता है)

  5. संदर्भ गिनती (या RAII लागू करने के लिए कुछ )

  6. अपवाद से निपटने के लिए सीमित समर्थन (सेटजम्प और लॉन्गजम्प)

उपरोक्त के शीर्ष पर यह एएनएसआई / आईएसओ विनिर्देशों पर निर्भर होना चाहिए और संकलक-विशिष्ट कार्यक्षमता पर भरोसा नहीं करना चाहिए।


संख्या (5) के लिए - आप बिना किसी विध्वंसक के भाषा में RAII को लागू नहीं कर सकते (जिसका अर्थ है कि RAII C या Java में संकलक समर्थित तकनीक नहीं है)।
टॉम

कंस्ट्रक्टर और डिस्ट्रक्टर्स को c आधारित ऑब्जेक्ट के लिए लिखा जा सकता है - मुझे लगता है कि GObject इसे करता है। और आरकेएआई (यह सीधे आगे नहीं है, बदसूरत हो सकता है और बिल्कुल भी व्यावहारिक होने की आवश्यकता नहीं है) - सब मैं देख रहा था कि उपरोक्त को खोजने के लिए सी आधारित शब्दार्थ को पहचानना है।
FL4SOF

सी विध्वंसक का समर्थन नहीं करता है। उन्हें काम करने के लिए आपको कुछ लिखना होगा । इसका मतलब है कि वे खुद को साफ नहीं करते हैं। GObject भाषा को बदलता नहीं है।
टॉम

0

को देखो http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html । अगर प्रलेखन के माध्यम से और कुछ नहीं पढ़ना एक ज्ञानवर्धक अनुभव है।


3
कृपया आपके द्वारा साझा किए जा रहे लिंक के लिए संदर्भ प्रदान करें। हालाँकि आपके द्वारा साझा किया गया लिंक वास्तव में बहुत मददगार हो सकता है, लेकिन यह इस सवाल का जवाब देने वाले साझा लेख के प्रमुख पहलुओं पर कब्जा करना उचित है। इस तरह, भले ही लिंक हटा दिया गया हो फिर भी आपका उत्तर प्रासंगिक और सहायक होगा।
ishmelMakitla

0

मैं यहाँ पार्टी के लिए थोड़ा लेट हो गया हूँ, लेकिन मैं दोनों मैक्रो चरम सीमाओं से बचना पसंद करता हूँ - बहुत अधिक या बहुत अधिक ओफ़्सेट्स कोड, लेकिन एक जोड़ी स्पष्ट मैक्रोज़ ओओपी कोड को विकसित करने और पढ़ने में आसान बना सकते हैं:

/*
 * OOP in C
 *
 * gcc -o oop oop.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

struct obj2d {
    float x;                            // object center x
    float y;                            // object center y
    float (* area)(void *);
};

#define X(obj)          (obj)->b1.x
#define Y(obj)          (obj)->b1.y
#define AREA(obj)       (obj)->b1.area(obj)

void *
_new_obj2d(int size, void * areafn)
{
    struct obj2d * x = calloc(1, size);
    x->area = areafn;
    // obj2d constructor code ...
    return x;
}

// --------------------------------------------------------

struct rectangle {
    struct obj2d b1;        // base class
    float width;
    float height;
    float rotation;
};

#define WIDTH(obj)      (obj)->width
#define HEIGHT(obj)     (obj)->height

float rectangle_area(struct rectangle * self)
{
    return self->width * self->height;
}

#define NEW_rectangle()  _new_obj2d(sizeof(struct rectangle), rectangle_area)

// --------------------------------------------------------

struct triangle {
    struct obj2d b1;
    // deliberately unfinished to test error messages
};

#define NEW_triangle()  _new_obj2d(sizeof(struct triangle), triangle_area)

// --------------------------------------------------------

struct circle {
    struct obj2d b1;
    float radius;
};

#define RADIUS(obj)     (obj)->radius

float circle_area(struct circle * self)
{
    return M_PI * self->radius * self->radius;
}

#define NEW_circle()     _new_obj2d(sizeof(struct circle), circle_area)

// --------------------------------------------------------

#define NEW(objname)            (struct objname *) NEW_##objname()


int
main(int ac, char * av[])
{
    struct rectangle * obj1 = NEW(rectangle);
    struct circle    * obj2 = NEW(circle);

    X(obj1) = 1;
    Y(obj1) = 1;

    // your decision as to which of these is clearer, but note above that
    // macros also hide the fact that a member is in the base class

    WIDTH(obj1)  = 2;
    obj1->height = 3;

    printf("obj1 position (%f,%f) area %f\n", X(obj1), Y(obj1), AREA(obj1));

    X(obj2) = 10;
    Y(obj2) = 10;
    RADIUS(obj2) = 1.5;
    printf("obj2 position (%f,%f) area %f\n", X(obj2), Y(obj2), AREA(obj2));

    // WIDTH(obj2)  = 2;                                // error: struct circle has no member named width
    // struct triangle  * obj3 = NEW(triangle);         // error: triangle_area undefined
}

मुझे लगता है कि यह एक अच्छा संतुलन है, और कुछ अधिक संभावित गलतियों के लिए यह त्रुटियां उत्पन्न करता है (कम से कम डिफ़ॉल्ट जीसीसी 6.3 विकल्प के साथ) भ्रमित करने के बजाय मददगार है। पूरे बिंदु प्रोग्रामर उत्पादकता में सुधार नहीं है?


0

अगर आपको थोड़ा कोड लिखने की कोशिश करनी है तो यह कोशिश करें: https://github.com/fulminati/class-framework

#include "class-framework.h"

CLASS (People) {
    int age;
};

int main()
{
    People *p = NEW (People);

    p->age = 10;

    printf("%d\n", p->age);
}

2
कृपया उत्तर के रूप में कुछ टूल या लाइब्रेरी पोस्ट न करें। कम से कम यह प्रदर्शित करें कि यह उत्तर में समस्या को कैसे हल करता है।
बॉम मिट ऑगेन

0

मैं भी एक मैक्रो समाधान पर आधारित इस पर काम कर रहा हूँ। तो यह केवल सबसे बहादुर के लिए है, मुझे लगता है;; लेकिन यह पहले से ही काफी अच्छा है, और मैं पहले से ही कुछ परियोजनाओं पर काम कर रहा हूं। यह काम करता है ताकि आप पहले प्रत्येक वर्ग के लिए एक अलग हेडर फ़ाइल को परिभाषित करें। ऐशे ही:

#define CLASS Point
#define BUILD_JSON

#define Point__define                            \
    METHOD(Point,public,int,move_up,(int steps)) \
    METHOD(Point,public,void,draw)               \
                                                 \
    VAR(read,int,x,JSON(json_int))               \
    VAR(read,int,y,JSON(json_int))               \

कक्षा को लागू करने के लिए, आप इसके लिए एक हेडर फ़ाइल बनाते हैं और एक सी फ़ाइल जहाँ आप विधियों को लागू करते हैं:

METHOD(Point,public,void,draw)
{
    printf("point at %d,%d\n", self->x, self->y);
}

वर्ग के लिए आपके द्वारा बनाए गए शीर्ष लेख में, आप अन्य प्रमुखों को शामिल करते हैं जिनकी आपको आवश्यकता होती है और वर्ग से संबंधित प्रकार आदि को परिभाषित करते हैं। क्लास हेडर और सी फाइल दोनों में आप क्लास स्पेसिफिकेशन फाइल (पहला कोड उदाहरण देखें) और एक एक्स-मैक्रो शामिल करते हैं। ये एक्स-मैक्रोज़ ( 1 , 2 , 3) आदि) कोड को वास्तविक वर्ग संरचनाओं और अन्य घोषणाओं तक विस्तारित करेंगे।

एक वर्ग वारिस करने के लिए, #define SUPER supernameऔर जोड़ेंsupername__define \ कक्षा परिभाषा में पहली पंक्ति के रूप में । दोनों को होना चाहिए। JSON समर्थन, सिग्नल, अमूर्त वर्ग इत्यादि भी है।

एक वस्तु बनाने के लिए, बस का उपयोग करें W_NEW(classname, .x=1, .y=2,...)। आरंभीकरण C11 में शुरू किए गए संरचनात्मक आरंभीकरण पर आधारित है। यह अच्छी तरह से काम करता है और सूचीबद्ध नहीं की गई हर चीज शून्य पर सेट है।

एक विधि को कॉल करने के लिए, का उपयोग करें W_CALL(o,method)(1,2,3)। यह एक उच्च क्रम फ़ंक्शन कॉल की तरह दिखता है, लेकिन यह सिर्फ एक मैक्रो है। इसका विस्तार होता है((o)->klass->method(o,1,2,3)) जो वास्तव में एक अच्छा हैक है।

दस्तावेज़ीकरण और कोड स्वयं देखें

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

class Point
    public int move_up(int steps)
    public void draw()
    read int x
    read int y

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



0

आप सीओओपी , सी में ओओपी के लिए एक प्रोग्रामर-फ्रेंडली फ्रेमवर्क की कोशिश कर सकते हैं , क्लास, एक्सेप्शन, पॉलीमॉर्फिज़्म, और मेमोरी मैनेजमेंट (एंबेडेड कोड के लिए महत्वपूर्ण) सुविधाएँ। यह अपेक्षाकृत हल्का वाक्यविन्यास है, विकी में ट्यूटोरियल देखें ।

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