संकेत बनाम मान पैरामीटर और वापसी मान


327

गो में structवैल्यू या स्लाइस को वापस करने के विभिन्न तरीके हैं। मैंने जिन व्यक्तियों को देखा है, उनके लिए:

type MyStruct struct {
    Val int
}

func myfunc() MyStruct {
    return MyStruct{Val: 1}
}

func myfunc() *MyStruct {
    return &MyStruct{}
}

func myfunc(s *MyStruct) {
    s.Val = 1
}

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

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

इसी तरह, स्लाइस के बारे में एक ही सवाल:

func myfunc() []MyStruct {
    return []MyStruct{ MyStruct{Val: 1} }
}

func myfunc() []*MyStruct {
    return []MyStruct{ &MyStruct{Val: 1} }
}

func myfunc(s *[]MyStruct) {
    *s = []MyStruct{ MyStruct{Val: 1} }
}

func myfunc(s *[]*MyStruct) {
    *s = []MyStruct{ &MyStruct{Val: 1} }
}

फिर से: यहां सबसे अच्छी प्रथाएं क्या हैं। मुझे पता है कि स्लाइस हमेशा संकेत होते हैं, इसलिए एक सूचक को एक स्लाइस में वापस करना उपयोगी नहीं होता है। हालांकि, क्या मुझे संरचनात्मक मूल्यों का एक टुकड़ा, संरक्षकों को संकेत का एक टुकड़ा लौटना चाहिए, क्या मुझे एक सूचक को तर्क के रूप में एक टुकड़ा ( गो ऐप इंजन एपीआई में प्रयुक्त पैटर्न ) में पास करना चाहिए?


1
जैसा कि आप कहते हैं, यह वास्तव में उपयोग के मामले पर निर्भर करता है। सभी स्थिति के आधार पर मान्य हैं - क्या यह एक परिवर्तनशील वस्तु है? क्या हम एक प्रति या सूचक चाहते हैं? आदि। BTW आप का उपयोग कर उल्लेख नहीं किया new(MyStruct):) लेकिन संकेत आवंटित करने और उन्हें वापस करने के विभिन्न तरीकों के बीच वास्तव में कोई अंतर नहीं है।
Not_a_Golfer

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

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

3
BTW जिज्ञासा से बाहर बस मैं इस benchamrked। रिटर्निंग स्ट्रक्चर्स बनाम पॉइंटर्स लगभग एक ही गति के प्रतीत होते हैं, लेकिन लाइनों के नीचे फंक्शंस के लिए पॉइंटर्स पास करना बहुत तेजी से होता है। हालांकि एक स्तर पर यह बात नहीं होगी
Not_a_Golfer

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

जवाबों:


392

tl; डॉआर :

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

एक मामला जहां आपको अक्सर एक सूचक का उपयोग करना चाहिए:

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

कुछ परिस्थितियाँ जहाँ आपको पॉइंटर्स की आवश्यकता नहीं होती है:

  • कोड समीक्षा दिशानिर्देशों से पता चलता है कि छोटी संरचनाएं जैसे कि type Point struct { latitude, longitude float64 }, और शायद यहां तक ​​कि चीजें थोड़ी बड़ी हैं, मूल्यों के रूप में, जब तक कि जिस फ़ंक्शन को आप कॉल कर रहे हैं, उन्हें जगह में संशोधित करने में सक्षम होना चाहिए।

    • मूल्य शब्दार्थ, उन स्थितियों से बचने से बचते हैं जहाँ पर एक असाइनमेंट आश्चर्य से वहाँ पर एक मूल्य बदल देता है।
    • थोड़ी गति के लिए स्वच्छ शब्दार्थ का त्याग करना गो-वाई नहीं है, और कभी-कभी मूल्य द्वारा छोटी संरचनाओं को पारित करना वास्तव में अधिक कुशल होता है, क्योंकि यह कैश मिसेज या हीप आवंटन से बचा जाता है ।
    • तो, गो विकी के कोड रिव्यू कमेंट पेज से पता चलता है कि जब मूल्य छोटे होते हैं और उस तरह से रहने की संभावना होती है।
    • यदि "बड़ी" कटऑफ अस्पष्ट लगती है, तो यह है; यकीनन कई संरचनाएं एक सीमा में होती हैं जहां एक सूचक या एक मूल्य ठीक है। एक निचली सीमा के रूप में, कोड समीक्षा टिप्पणियां मानती हैं कि स्लाइस (तीन मशीन शब्द) मूल्य रिसीवर के रूप में उपयोग करने के लिए उचित हैं। जैसा कि एक ऊपरी सीमा के पास कुछ है, bytes.Replace10 शब्दों के मूल्य के आर्ग्स (तीन स्लाइस और ए) लेता है int
  • के लिए स्लाइस , आप सरणी के परिवर्तन तत्वों के लिए एक सूचक पारित करने के लिए की जरूरत नहीं है। उदाहरण के लिए, io.Reader.Read(p []byte)बाइट्स बदलता है p। यकीनन यह "मूल्यों की तरह छोटी संरचनाओं का इलाज" का एक विशेष मामला है, क्योंकि आंतरिक रूप से आप एक छोटी संरचना के चारों ओर से गुजर रहे हैं जिसे एक स्लाइस हेडर कहा जाता है (देखें रस कॉक्स (आरएससी) का स्पष्टीकरण )। इसी तरह, आपको एक नक्शे को संशोधित करने या किसी चैनल पर संवाद करने के लिए एक संकेतक की आवश्यकता नहीं है ।

  • के लिए स्लाइस आप reslice देंगे (की शुरुआत / लंबाई / क्षमता बदल), बिल्ट-इन कार्य की तरह appendएक टुकड़ा मूल्य को स्वीकार करने और एक नया एक वापस जाएँ। मैं उसका अनुकरण करूँगा; यह अलियासिंग से बचा जाता है, एक नया टुकड़ा वापस करने से इस तथ्य पर ध्यान देने में मदद मिलती है कि एक नया सरणी आवंटित किया जा सकता है, और यह कॉलर्स से परिचित है।

    • यह हमेशा उस पैटर्न का पालन करना व्यावहारिक नहीं है। डेटाबेस इंटरफेस या धारावाहिक जैसे कुछ उपकरण एक ऐसे स्लाइस के लिए संलग्न करने की आवश्यकता होती है जिसका प्रकार संकलन समय पर ज्ञात नहीं है। वे कभी-कभी एक interface{}पैरामीटर में एक स्लाइस को पॉइंटर स्वीकार करते हैं ।
  • स्लाइस की तरह नक्शे, चैनल, तार, और फ़ंक्शन और इंटरफ़ेस मान आंतरिक रूप से संदर्भ या संरचनाएं हैं, जिनमें पहले से ही संदर्भ हैं, इसलिए यदि आप अंतर्निहित डेटा की प्रतिलिपि बनाने से बचने की कोशिश कर रहे हैं, तो आपको उन्हें पास करने की आवश्यकता नहीं है। । (rsc ने एक अलग पोस्ट पर लिखा है कि कैसे इंटरफ़ेस मान संग्रहीत किए जाते हैं )।

    • आपको अभी भी रेयर मामले में पॉइंटर्स पास करने की आवश्यकता हो सकती है जिसे आप कॉलर के स्ट्रक्चर को संशोधित करना चाहते हैं : उदाहरण के लिए, उस कारण से flag.StringVarलेता है *string

जहाँ आप पॉइंटर्स का उपयोग करते हैं:

  • इस बात पर विचार करें कि क्या आपके फ़ंक्शन को एक विधि होना चाहिए, जिसमें आपको एक पॉइंटर की आवश्यकता है। लोग xसंशोधित करने के लिए कई तरीकों की उम्मीद करते हैं x, इसलिए संशोधित संरचना को रिसीवर बनाने से आश्चर्य को कम करने में मदद मिल सकती है। जब रिसीवर पॉइंटर्स होना चाहिए, उस पर दिशानिर्देश हैं ।

  • ऐसे कार्य जिनके गैर-रिसीवर पार्म्स पर प्रभाव होते हैं, उन्हें गोडोक में स्पष्ट करना चाहिए, या बेहतर अभी तक, गोडोक और नाम (जैसे reader.WriteTo(writer))।

  • आप पुन: उपयोग की अनुमति देकर आवंटन से बचने के लिए एक सूचक को स्वीकार करने का उल्लेख करते हैं; मेमोरी के पुन: उपयोग के लिए एपीआई बदलना एक अनुकूलन है जब तक यह स्पष्ट नहीं हो जाता है कि आवंटन में एक nontrivial लागत है, और फिर मैं एक तरीका खोजूंगा जो सभी उपयोगकर्ताओं पर ट्रिकियर एपीआई को मजबूर न करे:

    1. आवंटन से बचने के लिए, गो का बचना विश्लेषण आपका मित्र है। आप कभी-कभी ऐसे प्रकारों को बनाने से बचने में मदद कर सकते हैं जिन्हें एक तुच्छ कंस्ट्रक्टर, एक सादे शाब्दिक या एक उपयोगी शून्य मान के साथ आरंभ किया जा सकता है bytes.Buffer
    2. Reset()एक वस्तु को खाली स्थिति में वापस रखने की विधि पर विचार करें , जैसे कुछ stdlib प्रकार की पेशकश। जो उपयोगकर्ता परवाह नहीं करते हैं या आवंटन नहीं बचा सकते हैं उन्हें इसे कॉल करने की आवश्यकता नहीं है।
    3. सुविधा के लिए, संशोधित जोड़े के रूप में लिखने की जगह और स्क्रैच-फ़ंक्शंस को जोड़े के रूप में लिखने पर विचार करें: existingUser.LoadFromJSON(json []byte) errorद्वारा लपेटा जा सकता है NewUserFromJSON(json []byte) (*User, error)। फिर से, यह आलसी और व्यक्तिगत कॉल करने वाले के लिए चुटकी आवंटन के बीच विकल्प को धक्का देता है।
    4. मेमोरी को रीसायकल करने के इच्छुक कॉलर्स sync.Poolकुछ विवरणों को संभाल सकते हैं । यदि कोई विशेष आवंटन बहुत अधिक मेमोरी दबाव बनाता है, तो आप आश्वस्त हैं कि आपको पता है कि जब आवंटन का उपयोग नहीं किया जाता है, और आपके पास बेहतर अनुकूलन उपलब्ध नहीं है, तो sync.Poolआप मदद कर सकते हैं। (CloudFlare ने रीसाइक्लिंग के बारे में एक उपयोगी (पूर्व- sync.Pool) ब्लॉग पोस्ट प्रकाशित की ।)

अंत में, इस बात पर कि क्या आपके स्लाइस पॉइंटर्स के होने चाहिए: मानों के स्लाइस उपयोगी हो सकते हैं, और आपको आवंटन और कैश मिस को बचा सकते हैं। ब्लॉकर्स हो सकते हैं:

  • आपके आइटम बनाने के लिए API आपको संकेत करने के लिए मजबूर कर सकता है, उदाहरण के लिए आपको शून्य मान केNewFoo() *Foo साथ प्रारंभ जाने के बजाय कॉल करना होगा ।
  • वस्तुओं के वांछित जीवनकाल सभी समान नहीं हो सकते हैं। पूरे स्लाइस को एक बार में मुक्त कर दिया जाता है; यदि 99% आइटम अब उपयोगी नहीं हैं, लेकिन आपके पास अन्य 1% के लिए संकेत हैं, तो सभी सरणी आवंटित की जाती है।
  • आसपास की वस्तुओं को ले जाने से आपको समस्या हो सकती है। विशेष रूप से, appendजब यह अंतर्निहित सरणी बढ़ता है तो आइटम की प्रतिलिपि बनाता है । appendगलत स्थान के बाद के बिंदु से पहले आपके द्वारा प्राप्त किए गए पॉइंटर्स , कॉपी करना बहुत बड़ी संरचना के लिए धीमा हो सकता है, और उदाहरण के लिए sync.Mutexकॉपी करने की अनुमति नहीं है। बीच में डालें / हटाएं और इसी तरह वस्तुओं को इधर-उधर करें।

मोटे तौर पर, मूल्य स्लाइस समझ में आ सकते हैं यदि या तो आप अपनी सभी वस्तुओं को सामने रखते हैं और उन्हें स्थानांतरित नहीं करते हैं (उदाहरण के लिए, appendप्रारंभिक सेटअप के बाद और अधिक नहीं ), या यदि आप उन्हें इधर-उधर घुमाते रहते हैं, लेकिन आपको यकीन है कि ठीक है (आइटम के लिए संकेत का कोई सावधान / उपयोग, आइटम कुशलता से कॉपी करने के लिए पर्याप्त छोटे हैं, आदि)। कभी-कभी आपको अपनी स्थिति की बारीकियों के बारे में सोचना या मापना पड़ता है, लेकिन यह एक कठिन मार्गदर्शिका है।


12
बड़ी संरचनाओं का क्या मतलब है? क्या एक बड़ी संरचना और एक छोटी संरचना का उदाहरण है?
बिना हैट वाला यूजर

1
आप बाइट्स को कैसे बता सकते हैं। Rplace amd64 पर 80 बाइट्स की कीमत लेता है?
टिम वू

2
हस्ताक्षर है Replace(s, old, new []byte, n int) []byte; s, पुराना और नया तीन शब्द हैं ( स्लाईस हेडर हैं(ptr, len, cap) ) n intएक शब्द है और एक शब्द है, इसलिए 10 शब्द, जो आठ बाइट्स पर है / शब्द 80 बाइट्स है।
ट्वोटवोटव

6
आप बड़ी संरचनाओं को कैसे परिभाषित करते हैं? कितना बड़ा है?
एंडी एल्डो

3
@AndyAldo मेरे स्रोतों में से कोई भी (कोड समीक्षा टिप्पणियां, आदि) एक सीमा को परिभाषित करता है, इसलिए मैंने यह कहने का फैसला किया कि यह एक सीमा बनाने के बजाय एक निर्णय कॉल है। तीन शब्दों (एक स्लाइस की तरह) को लगातार stdlib में एक मूल्य के रूप में व्यवहार किया जाता है। मुझे अभी (टेक्स्ट / स्कैनर.पोज़िशन) पर पाँच-शब्द मूल्य रिसीवर का एक उदाहरण मिला, लेकिन मैं इसमें ज्यादा नहीं पढ़ूंगा (यह एक पॉइंटर के रूप में भी पारित हो गया है!)। अनुपस्थित बेंचमार्क, आदि, मैं सिर्फ वही करूँगा जो पठनीयता के लिए सबसे सुविधाजनक लगता है।
twotwotwo 21

10

तीन मुख्य कारण जब आप बिंदु के रूप में विधि रिसीवर का उपयोग करना चाहेंगे:

  1. "पहला और सबसे महत्वपूर्ण, क्या विधि को रिसीवर को संशोधित करने की आवश्यकता है? यदि ऐसा होता है, तो रिसीवर को एक संकेतक होना चाहिए।"

  2. "दूसरा दक्षता का विचार है। यदि रिसीवर बड़ा है, उदाहरण के लिए एक बड़ी संरचना, तो एक पॉइंटर रिसीवर का उपयोग करना बहुत सस्ता होगा।"

  3. "अगला है संगति। यदि प्रकार के कुछ तरीकों में सूचक रिसीवर होने चाहिए, तो बाकी को भी होना चाहिए, इसलिए विधि सेट इस बात के अनुरूप है कि प्रकार का उपयोग कैसे किया जाता है"

संदर्भ: https://golang.org/doc/faq#methods_on_values_or_pointers

संपादित करें: वास्तविक "प्रकार" को जानने के लिए एक और महत्वपूर्ण बात यह है कि आप कार्य करने के लिए भेज रहे हैं। प्रकार या तो 'मान प्रकार' या 'संदर्भ प्रकार' हो सकता है।

यहां तक ​​कि जैसे ही स्लाइस और नक्शे संदर्भ के रूप में कार्य करते हैं, हम उन्हें फ़ंक्शन में स्लाइस की लंबाई बदलने जैसे परिदृश्य में संकेत के रूप में पारित करना चाहते हैं।


1
2 के लिए, कटऑफ क्या है? मुझे कैसे पता चलेगा कि मेरी संरचना बड़ी या छोटी है? इसके अलावा, क्या ऐसी कोई संरचना है जो इतनी छोटी है कि यह सूचक के बजाय एक मूल्य का उपयोग करने के लिए अधिक कुशल है (ताकि इसे ढेर से संदर्भित न करना पड़े)?
zlotnika

मैं कहूंगा कि जितने अधिक क्षेत्र और / या नेस्टेड संरचनाएं अंदर होती हैं, उतना ही बड़ा ढांचा होता है। मुझे यकीन नहीं है कि जब किसी संरचना को "बड़ा" या "बड़ा" कहा जा सकता है, तो विशिष्ट कटऑफ या मानक तरीका है। यदि मैं एक संरचना का उपयोग कर रहा हूं या बना रहा हूं, तो मुझे पता होगा कि इसके बड़े या छोटे के आधार पर जो मैंने ऊपर कहा था। लेकिन यह सिर्फ मुझे है!
संतोष पिल्लई

2

एक मामला जहां आपको आमतौर पर एक पॉइंटर को वापस करने की आवश्यकता होती है, जब कुछ स्टेटफुल या साझा करने योग्य संसाधन की आवृत्ति का निर्माण होता है । इस बार के साथ उपसर्ग कार्यों द्वारा किया जाता है New

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

कुछ उदाहरण:

अन्य मामलों में, पॉइंटर्स सिर्फ इसलिए लौटाए जाते हैं क्योंकि संरचना डिफ़ॉल्ट रूप से कॉपी करने के लिए बहुत बड़ी हो सकती है:


वैकल्पिक रूप से, सीधे लौटने वाले पॉइंटर्स को एक संरचना की कॉपी को वापस करने से बचाया जा सकता है जिसमें आंतरिक रूप से पॉइंटर शामिल है, लेकिन शायद यह मुहावरेदार नहीं माना जाता है:


इस विश्लेषण में निहित यह है कि, डिफ़ॉल्ट रूप से, मूल्य द्वारा प्रतिरूपों की नकल की जाती है (लेकिन जरूरी नहीं कि उनके अप्रत्यक्ष सदस्य)।
नोबार

2

यदि आप कर सकते हैं (जैसे एक गैर-साझा संसाधन जिसे संदर्भ के रूप में पारित करने की आवश्यकता नहीं है), तो एक मूल्य का उपयोग करें। निम्नलिखित कारणों से:

  1. सूचक संचालकों और अशक्त जांच से बचने के लिए आपका कोड अच्छे और अधिक पठनीय होगा।
  2. आपका कोड नल पॉइंटर पैनिक के खिलाफ सुरक्षित होगा।
  3. आपका कोड अक्सर तेज़ होगा: हाँ, तेज़! क्यों?

कारण 1 : आप स्टैक में कम आइटम आवंटित करेंगे। स्टैक से आवंटन / निस्तारण तत्काल है, लेकिन हीप पर आवंटन / डीलिंग करना बहुत महंगा हो सकता है (आवंटन समय + कचरा संग्रह)। आप यहां कुछ बुनियादी नंबर देख सकते हैं: http://www.macias.info/entry/201802102230_go_values_vs_references.md

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

मिथक ब्रेकर : एक सामान्य x86 कैश लाइन 64 बाइट्स हैं। अधिकांश संरचनाएं इससे छोटी हैं। मेमोरी में कैशे लाइन को कॉपी करने का समय एक पॉइंटर को कॉपी करने के समान है।

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

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