2012-11-20 23 views
14

मेरे पास फ़ंक्शन 1 द्वारा मूल्य बढ़ाने के लिए है, मैं इसे सामान्य बनाना चाहता हूं, क्योंकि मैं समान कार्यक्षमता के लिए अलग-अलग फ़ंक्शन नहीं बनाना चाहता हूं।सी में शून्य * का उपयोग कर सामान्य कार्य कैसे करें?

मान लीजिए मैं 1

void incr(void *vp) 
{ 
     (*vp)++; 
} 

द्वारा int, float, char बढ़ाने के लिए चाहते हैं लेकिन समस्या यह है मैं जानता हूँ कि Dereferencing a void pointer is undefined behaviour है। कभी-कभी यह त्रुटि दे सकता है: Invalid use of void expression

मेरे main funciton है:

int main() 
{ 

int i=5; 
float f=5.6f; 
char c='a'; 

incr(&i); 
incr(&f); 
incr(&c); 

return 0; 
} 

समस्या यह कैसे हल करने के लिए है? वहाँ C में इसे हल करने का कोई तरीका है केवल

या

मैं हर डेटाटाइप्स के लिए incr() को परिभाषित करना होगा? यदि हाँ, तो swap() और sort() .मैं साथ void *

एक ही समस्या के उपयोग क्या स्वैप करने के लिए चाहते हैं और तरह सभी एक ही समारोह के साथ डेटा प्रकार के प्रकार के।

+0

क्या सी ++ में अभिन्न प्रकारों के लिए पोस्टफिक्स वृद्धि है? आप इसे फिर से परिभाषित क्यों करना चाहते हैं? – Yola

+2

सी 11 में आप '_Generic' का उपयोग कर सकते हैं: http://www.robertgamble.net/2012/01/c11-generic-selections.html –

+0

@Yola: यह सिर्फ एक डेमो है .. असल में मुझे बहुत कुछ करना पड़ सकता है मेरी परियोजना जो कि structs पर भी डेटाटाइप पर काम करनी चाहिए, और मैं उन कार्यों को बनाना नहीं चाहता – Omkant

उत्तर

17

आप मैक्रो के रूप में पहली बार लागू कर सकते हैं:

#define incr(x) (++(x)) 
बेशक

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

void * आमतौर पर तब उपयोग किया जाता है जब प्रश्न में कार्य को वास्तव में शामिल डेटा के सटीक प्रकार को जानने की आवश्यकता नहीं होती है। कुछ मामलों में (उदा।, qsort) यह डेटा के किसी भी विवरण को जानने से बचने के लिए कॉलबैक फ़ंक्शन का उपयोग करता है।

चूंकि यह दोनों प्रकार और स्वैप करता है, चलिए थोड़ी अधिक विस्तार से qsort देखें। इसके हस्ताक्षर है:

void qsort(void *base, size_t nmemb, size_t size, 
      int(*cmp)(void const *, void const *)); 

तो, पहले void * आप के बारे में पूछा है - डेटा के लिए सूचक क्रमबद्ध करना। दूसरा सरणी में तत्वों की संख्या qsort बताता है। तीसरा, सरणी में प्रत्येक तत्व का आकार। अंतिम एक फ़ंक्शन के लिए एक सूचक है जो अलग-अलग वस्तुओं की तुलना कर सकता है, इसलिए qsort को यह जानने की आवश्यकता नहीं है कि यह कैसे करें। उदाहरण के लिए, कहीं qsort अंदर जैसे कुछ कोड कुछ हो जाएगा: दो आइटम स्वैप करने के लिए,

// if (base[j] < base[i]) ... 
if (cmp((char *)base+i, (char *)base+j) == -1) 

इसी तरह, यह सामान्य रूप से अस्थायी भंडारण के लिए एक स्थानीय सरणी होगा। यह तो array[j] से array[i] को और अंत में temp से array[j] करने के लिए अपने अस्थायी करने array[i] से बाइट्स कॉपी कर देंगे, तो:

char temp[size]; 

memcpy(temp, (char *)base+i, size);    // temp = base[i] 
memcpy((char *)base+i, (char *)base+j, size); // base[i] = base[j] 
memcpy((char *)base+j, temp, size);    // base[j] = temp 
+0

@PaulR: अच्छा बिंदु। –

+0

धन्यवाद, मैं एक घंटे के लिए बाहर था, ऊपर स्पष्टीकरण के बारे में थोड़ा सा संदेह। मेरे अनुसार मुझे लगता है कि हर बार जब मैं शून्य प्रकार के लिए शून्य पॉइंटर टाइप करता हूं और अपने आकार के आधार पर संचालन करता हूं, है ना? – Omkant

+0

ये फ़ंक्शन 'मेमसेट, मेममोव, मेम्पी' सभी को 'शून्य *' तर्क के रूप में लिया जाता है, इसका मतलब है कि वे 'char *' – Omkant

1

"जेनेरिक" स्वैप का उपयोग करने के लिए उदाहरण।

यह कोड स्मृति के दो ब्लॉक को स्वैप करता है।

void memswap_arr(void* p1, void* p2, size_t size) 
{  
     size_t   i; 
     char* pc1= (char*)p1; 
     char* pc2= (char*)p2; 
     char ch; 

     for (i= 0; i<size; ++i) { 
     ch=  pc1[i]; 
     pc1[i]= pc2[i]; 
     pc2[i]= ch; 
     } 
} 

और आप इसे इस तरह कहते हैं:

int main() { 
    int i1,i2; 
    double d1,d2; 
    i1= 10; i2= 20; 
    d1= 1.12; d2= 2.23; 
    memswap_arr(&i1,&i2,sizeof(int));  //I use memswap_arr to swap two integers 
    printf("i1==%d i2==%d \n",i1,i2);  //I use the SAME function to swap two doubles 
    memswap_arr(&d1,&d2,sizeof(double));  
    printf("d1==%f d2==%f \n",d1,d2); 
    return 0; 
} 

मुझे लगता है कि यह आप कैसे विभिन्न डेटा प्रकार के लिए एक समारोह का उपयोग करने के की एक विचार देना चाहिए।

+0

का उपयोग करने पर विचार किया है, यह ओपी की वृद्धि आवश्यकता के साथ कैसे मदद करता है? –

+0

उसने पूछा: * "मैं एक ही फ़ंक्शन के साथ सभी प्रकार के डेटा प्रकारों को स्वैप करना और सॉर्ट करना चाहता हूं।" * और यही मैंने जवाब दिया :) मैंने सोचा कि एक अच्छा उदाहरण एक अच्छा स्पष्टीकरण दे सकता है। मैं मदद करने की कोशिश कर रहा था, भ्रमित नहीं करने के लिए :) – Maroun

+0

एक मैक्रो, '# परिभाषित स्वैप (एक्स, वाई) swap_impl (& x, और y, sizeof x)' और बूम के साथ संयुक्त, हमेशा आकार के ऑपरेटर का उपयोग करने की आवश्यकता नहीं है। अच्छा उदाहरण! –

0

आपको अपने पॉइंटर को इसे संदर्भित करने से पहले ठोस प्रकार में डालना चाहिए। इसलिए आपको पॉइंटर वैरिएबल के प्रकार को पास करने के लिए कोड भी जोड़ना चाहिए।

12

void * का उपयोग करके आप पॉलिमॉर्फिक व्यवहार नहीं देंगे, जो मुझे लगता है कि आप क्या देख रहे हैं। void * बस आपको ढेर चर के प्रकार की जांच को बाईपास करने की अनुमति देता है। वास्तविक बहुलक व्यवहार को प्राप्त करने के लिए, आपको प्रकार की जानकारी को दूसरे चर के रूप में पास करना होगा और इसके incr फ़ंक्शन में इसकी जांच करनी होगी, फिर पॉइंटर को वांछित प्रकार पर कास्टिंग करना होगा या फ़ंक्शन पॉइंटर्स के रूप में आपके डेटा पर किसी भी ऑपरेशन में गुज़रना होगा (दूसरों के पास उदाहरण के रूप में qsort का उल्लेख किया गया है)। सी में भाषा में निर्मित स्वचालित बहुरूपता नहीं है, इसलिए यह आपके अनुकरण के लिए होगा। दृश्यों के पीछे, पॉलीमोर्फिज्म में निर्मित भाषाएं दृश्यों के पीछे इस तरह कुछ कर रही हैं।

विस्तृत करने के लिए, void * स्मृति के सामान्य ब्लॉक के लिए एक सूचक है, जो कुछ भी हो सकता है: एक int, float, string, आदि। स्मृति के ब्लॉक की लंबाई भी सूचक में संग्रहीत नहीं है, अकेले रहने दो डेटा का प्रकार। याद रखें कि आंतरिक रूप से, सभी डेटा बिट्स और बाइट्स हैं, और प्रकार वास्तव में केवल मार्कर हैं कि लॉजिकल डेटा को शारीरिक रूप से एन्कोड किया गया है, क्योंकि आंतरिक रूप से, बिट्स और बाइट्स टाइपलेस हैं।सी में, यह जानकारी चर के साथ संग्रहीत नहीं है, इसलिए आपको इसे स्वयं संकलक को प्रदान करना होगा, ताकि यह पता चल सके कि बिट अनुक्रमों के इलाज के लिए 2 पूरक पूरक पूर्णांक, आईईईई 754 डबल-प्रेसिजन फ्लोटिंग पॉइंट, एएससीआईआईआई चरित्र डेटा, कार्य, आदि; ये विभिन्न प्रकार के डेटा के लिए प्रारूपों और संचालन के सभी विशिष्ट मानक हैं। जब आप एक विशेष प्रकार, आप प्रोग्रामर के रूप में के सूचक के लिए एक void * डाली जोर देते हुए कर रहे हैं डेटा बताया कि वास्तव में आप किस प्रकार के लिए यह कास्टिंग कर रहे हैं की है। अन्यथा, आप शायद अजीब व्यवहार के लिए हैं।

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

आप अधिक जानकारी के लिए check out the Wikipedia explanation कर सकते हैं।

+0

+1 ग्रेट उत्तर! –

4

आप वास्तव में ऐसा नहीं कर सकते कि तुम क्या कह रहे हैं - वेतन वृद्धि जैसे ऑपरेटरों एक विशिष्ट प्रकार के साथ काम करने की जरूरत है।

enum type { 
    TYPE_CHAR, 
    TYPE_INT, 
    TYPE_FLOAT 
}; 

void incr(enum type t, void *vp) 
{ 
    switch (t) { 
     case TYPE_CHAR: 
     (*(char *)vp)++; 
     break; 

     case TYPE_INT: 
     (*(int *)vp)++; 
     break; 

     case TYPE_FLOAT: 
     (*(float *)vp)++; 
     break; 
    } 
} 

तो फिर तुम फोन था इसे पसंद:: तो, आप कुछ इस तरह कर सकता है

int i=5; 
float f=5.6f; 
char c='a'; 

incr(TYPE_INT, &i); 
incr(TYPE_FLOAT, &f); 
incr(TYPE_CHAR, &c); 

बेशक, यह वास्तव में आप कुछ भी बस अलग incr_int() को परिभाषित करने से अधिक नहीं देता है, incr_float() और incr_char() फ़ंक्शंस - यह void * का उद्देश्य नहीं है।

void * के प्रयोजन महसूस किया है जब एल्गोरिथ्म आप लिख रहे हैं वस्तुओं की वास्तविक प्रकार के बारे में परवाह नहीं है। एक अच्छा उदाहरण मानक सॉर्टिंग समारोह qsort() है, जो के रूप में घोषित किया जाता है:

void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *)); 

इस वस्तु के किसी भी प्रकार की सरणियों सॉर्ट करने के लिए इस्तेमाल किया जा सकता - फोन करने वाले सिर्फ एक तुलना समारोह है कि दो वस्तुओं की तुलना कर सकते हैं की आपूर्ति करने की जरूरत है।

दोनों अपने swap() और sort() कार्यों इस श्रेणी में आते। swap() और भी आसान है - एल्गोरिथ्म उन्हें स्वैप करने के लिए वस्तुओं के आकार के अलावा और कुछ जानने की जरूरत नहीं है:

void swap(void *a, void *b, size_t size) 
{ 
    unsigned char *ap = a; 
    unsigned char *bp = b; 
    size_t i; 

    for (i = 0; i < size; i++) { 
     unsigned char tmp = ap[i]; 

     ap[i] = bp[i]; 
     bp[i] = tmp; 
    } 
} 

अब किसी भी सरणी आपको लगता है कि सरणी में दो आइटम स्वैप कर सकते हैं दी:

int ai[]; 
double ad[]; 

swap(&ai[x], &ai[y], sizeof(int)); 
swap(&di[x], &di[y], sizeof(double)); 
0

क्षमा करें यदि इस व्यापक सवाल "करने के लिए एक गैर-जवाब के रूप में बाहर आ सकता है कैसे करें सी * में शून्य * का उपयोग करके जेनेरिक फ़ंक्शन बनाएं ".. लेकिन आपको लगता है कि समस्याएं हैं (मनमानी प्रकार के चर को बढ़ाकर, और अज्ञात प्रकारों के 2 चरों को स्वैप करना) कार्यों और पॉइंटर्स को शून्य से मैक्रोज़ के साथ बहुत आसान किया जा सकता है ।

बढ़ाने के काफी सरल:

#define increment(x) ((x)++) 

अदला-बदली के लिए, मैं कुछ इस तरह करते हैं:

#define swap(x, y)     \ 
({         \ 
     typeof(x) tmp = (x);  \ 
     (x) = (y);     \ 
     (y) = tmp;     \ 
}) 

... जो ints, डबल्स और चार संकेत (तार) के लिए काम करता है, के आधार मेरे परीक्षण पर

जबकि incrementing मैक्रो बहुत सुरक्षित होना चाहिए, स्वैप मैक्रो typeof() ऑपरेटर है, जो एक जीसीसी/बजना विस्तार, मानक सी (का हिस्सा नहीं है पर निर्भर करता है यद्यपि अगर आप केवल वास्तव में कभी जीसीसी या बजना, इस shouldn साथ संकलन एक समस्या का बहुत अधिक नहीं है)।

मुझे पता है कि इस तरह के मूल प्रश्न को डोड किया गया है; लेकिन उम्मीद है कि यह अभी भी आपकी मूल समस्याओं को हल करता है।

0

आप प्रकार-जेनेरिक सुविधाओं (सी 11 मानक) का उपयोग कर सकते हैं। यदि आप अधिक उन्नत गणित कार्यों का उपयोग करना चाहते हैं (++ ऑपरेटर से अधिक उन्नत), तो आप <tgmath.h> पर जा सकते हैं, जो <math.h> और <complex.h> में फ़ंक्शंस की प्रकार-सामान्य परिभाषा है।

आप मैक्रो के रूप में एक प्रकार-जेनेरिक फ़ंक्शन को परिभाषित करने के लिए _Generic कीवर्ड का भी उपयोग कर सकते हैं। एक उदाहरण के नीचे:

#include <stdio.h> 

#define add1(x) _Generic((x), int: ++(x), float: ++(x), char: ++(x), default: ++(x)) 

int main(){ 
    int i = 0; 
    float f = 0; 
    char c = 0; 

    add1(i); 
    add1(f); 
    add1(c); 

    printf("i = %d\tf = %g\tc = %d", i, f, c); 
} 

आप Rob's programming blog से इस पोस्ट में language standard और अधिक soffisticated उदाहरण बारे में अधिक जानकारी पा सकते हैं।

* void के लिए, स्वैप और सॉर्ट करें, बेहतर Jerry Coffin का उत्तर देखें।