C और C ++ संरचना के भीतर सरणियों के सदस्‍यता प्रदान करने का समर्थन क्यों करते हैं, लेकिन आम तौर पर नहीं?


87

मैं समझता हूं कि सरणियों का सदस्यवार कार्य समर्थित नहीं है, जैसे कि निम्नलिखित काम नहीं करेंगे:

int num1[3] = {1,2,3};
int num2[3];
num2 = num1; // "error: invalid array assignment"

मैंने इसे केवल इस तथ्य के रूप में स्वीकार किया है, यह अनुमान लगाते हुए कि भाषा का उद्देश्य एक ओपन-एंडेड फ्रेमवर्क प्रदान करना है, और उपयोगकर्ता को यह तय करने दें कि किसी चीज़ को कैसे लागू किया जाए जैसे कि किसी ऐरे की कॉपी।

हालाँकि, निम्नलिखित काम करता है:

struct myStruct { int num[3]; };
struct myStruct struct1 = {{1,2,3}};
struct myStruct struct2;
struct2 = struct1;

सरणी num[3]को इसके उदाहरण में इसके उदाहरण में से सदस्य-वार सौंपा गया struct1है struct2

सदस्य-वार असाइनमेंट को स्ट्रक्चर के लिए समर्थित क्यों है, लेकिन सामान्य रूप से नहीं?

संपादित करें : थ्रेड स्टैड में रोजर पाटे की टिप्पणी :: संरचना में स्ट्रिंग - कॉपी / असाइनमेंट मुद्दों? उत्तर की सामान्य दिशा में इंगित करने के लिए लगता है, लेकिन मैं खुद इसकी पुष्टि करने के लिए पर्याप्त नहीं जानता।

संपादन 2 : कई उत्कृष्ट प्रतिक्रियाएँ। मैं लूथर ब्लिसटेट को चुनता हूं क्योंकि मैं ज्यादातर व्यवहार के पीछे दार्शनिक या ऐतिहासिक औचित्य के बारे में सोच रहा था, लेकिन जेम्स मैकनेलिस का संबंधित संबंधित दस्तावेज के संदर्भ में भी उपयोगी था।


6
मैं इसे टैग के रूप में C और C ++ दोनों बना रहा हूं, क्योंकि यह C से उत्पन्न होता है। साथ ही, अच्छा प्रश्न।
GManNickG

4
यह ध्यान देने योग्य हो सकता है कि सी में एक लंबे समय से पहले, संरचना असाइनमेंट आम तौर पर संभव नहीं था और आपको इसका उपयोग करना था memcpy()या समान।
ggg

बस थोड़ा सा FYI करें ... boost::array( boost.org/doc/libs/release/doc/html/array.html ) और अब std::array( en.cppreference.com/w/cpp/container/array ) एसटीएल-संगत विकल्प हैं गड़बड़ पुराने सी सरणियों। वे कॉपी-असाइनमेंट का समर्थन करते हैं।
एमिल कॉर्मियर

@EmileCormier और वे हैं - टाडा! - सरणियों के आसपास संरचनाएं।
पीटर - मोनिका

जवाबों:


46

यहाँ मेरा उस पर ले लो:

C भाषा का विकास C में सरणी प्रकार के विकास में कुछ अंतर्दृष्टि प्रदान करता है:

मैं सरणी चीज़ को रेखांकित करने का प्रयास करूँगा:

C के अग्रदूतों B और BCPL का कोई अलग प्रकार नहीं था, जैसे घोषणा:

auto V[10] (B)
or 
let V = vec 10 (BCPL)

V को एक (अनकैप्ड) पॉइंटर घोषित करेगा जो कि स्मृति के 10 "शब्दों" के अप्रयुक्त क्षेत्र को इंगित करने के लिए आरंभिक है। B पहले से ही *पॉइंटर डेरेफ़रिंग के लिए उपयोग किया जाता है और [] शॉर्ट हैंड नोटेशन का *(V+i)मतलब है V[i], जैसे कि आज C / C ++ में। हालांकि, Vयह एक सरणी नहीं है, यह अभी भी एक संकेतक है जिसे कुछ मेमोरी को इंगित करना है। इससे परेशानी हुई जब डेनिस रिची ने बी को संरचनात्मक प्रकारों के साथ विस्तारित करने का प्रयास किया। वह चाहता था कि सरणियों का हिस्सा हो, जैसे कि C में आज:

struct {
    int inumber;
    char name[14];
};

लेकिन बी, बीसीपीएल अवधारणा के साथ बिंदुओं के रूप में, यह nameक्षेत्र को एक पॉइंटर को समाहित करने की आवश्यकता होती है जिसे संरचना के भीतर 14 बाइट्स के मेमोरी क्षेत्र में रनटाइम पर आरंभीकृत किया जाना था । आरंभिक / लेआउट समस्या को अंततः एक विशेष उपचार देकर सरणियों को हल किया गया था: संकलक संरचनाओं में सरणियों के स्थान को ट्रैक करेगा, स्टैक आदि पर वास्तव में सूचक को डेटा को भौतिक बनाने की आवश्यकता के बिना, अभिव्यक्तियों को छोड़कर, जिसमें सरणियाँ शामिल हैं। इस उपचार ने लगभग सभी बी कोड को अभी भी चलाने की अनुमति दी और "एरे को पॉइंटर में कनवर्ट करें यदि आप उन्हें देखते हैं" नियम। यह एक कॉम्पेटिबिलिटी हैक है, जो बहुत उपयोगी साबित हुई, क्योंकि इसमें खुले आकार आदि के सरणियों की अनुमति थी।

और यहाँ मेरा अनुमान है कि सरणी को क्यों नहीं सौंपा जा सकता है: चूंकि सरणियाँ बी में संकेत थे, आप बस लिख सकते हैं:

auto V[10];
V=V+5;

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

/* Example how array assignment void make things even weirder in C/C++, 
   if we don't want to break existing code.
   It's actually better to leave things as they are...
*/
typedef int vec[3];

void f(vec a, vec b) 
{
    vec x,y; 
    a=b; // pointer assignment
    x=y; // NEW! element-wise assignment
    a=x; // pointer assignment
    x=a; // NEW! element-wise assignment
}

1978 में C के एक संशोधन ने संरचना असाइनमेंट ( http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf ) को बदल दिया। भले ही रिकॉर्ड सी में अलग-अलग प्रकार के थे , लेकिन उन्हें शुरुआती के एंड आर सी में असाइन करना संभव नहीं था। आपको उन्हें मेम्स्की के साथ सदस्य-वार कॉपी करना था और आप फ़ंक्शन मापदंडों के रूप में केवल पॉइंटर्स पास कर सकते थे। एसिगमेंट (और पैरामीटर पासिंग) को अब केवल संरचना की कच्ची मेमोरी के रूप में परिभाषित किया गया था और चूंकि यह मौजूदा कोड को तोड़ नहीं सकता था, यह आसानी से स्थगित हो गया था। एक अनपेक्षित साइड इफेक्ट के रूप में, इस निहित रूप से कुछ प्रकार के सरणी असाइनमेंट पेश किए गए थे, लेकिन यह एक संरचना के अंदर कहीं खुश था, इसलिए यह वास्तव में उस तरह की समस्याओं को पेश नहीं कर सका जिस तरह से सरणियों का उपयोग किया गया था।


यह बहुत बुरा है C ने एक सिंटैक्स को परिभाषित नहीं किया है। उदाहरण int[10] c;के लिए, लवल्यू cको दस आइटमों के एक सरणी के बजाय सूचक के रूप में दस वस्तुओं की एक सरणी के रूप में व्यवहार करना है। कुछ स्थितियाँ ऐसी होती हैं जहाँ यह टाइपपैड बनाने में सक्षम होने के लिए उपयोगी होता है जो चर के लिए उपयोग किए जाने पर स्थान आवंटित करता है, लेकिन फ़ंक्शन तर्क के रूप में उपयोग किए जाने पर एक पॉइंटर पास करता है, लेकिन सरणी प्रकार का मान रखने में असमर्थता एक महत्वपूर्ण अर्थगत कमजोरी है भाषा में।
सुपरकैट

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

इतिहास के एक अच्छे सारांश के लिए सरणियों के लिए सी का फैलाव देखें ।
पीटर कॉर्डेस

31

असाइनमेंट ऑपरेटरों के संबंध में, C ++ मानक निम्नलिखित कहता है (C ++ 03 .15.17 / 1):

कई असाइनमेंट ऑपरेटर हैं ... सभी को अपने बाएं ऑपरेंड के रूप में एक परिवर्तनीय अंतराल की आवश्यकता होती है

एक सरणी एक परिवर्तनीय अंतराल नहीं है।

हालाँकि, एक वर्ग प्रकार की वस्तु को असाइन करना विशेष रूप से परिभाषित किया गया है (75.17 / 4):

कॉपी असाइनमेंट ऑपरेटर द्वारा एक वर्ग की वस्तुओं को असाइन किया गया है।

इसलिए, हम देखते हैं कि किसी वर्ग के लिए अनुमानित रूप से घोषित कॉपी असाइनमेंट ऑपरेटर क्या करता है (.812.8 / 13):

दसवीं कक्षा के लिए अनुमानित रूप से परिभाषित कॉपी असाइनमेंट ऑपरेटर अपने सबोबिज के सदस्यवार असाइनमेंट करता है। ... प्रत्येक उप-विषय को उसके प्रकार के लिए उपयुक्त तरीके से असाइन किया गया है:
...
- यदि उप-विषय एक सरणी है, तो प्रत्येक तत्व को तत्व प्रकार के लिए उपयुक्त तरीके से असाइन किया गया है
...

इसलिए, एक वर्ग प्रकार की वस्तु के लिए, सरणियों को सही तरीके से कॉपी किया जाता है। ध्यान दें कि यदि आप एक उपयोगकर्ता द्वारा घोषित कॉपी असाइनमेंट ऑपरेटर प्रदान करते हैं, तो आप इसका लाभ नहीं उठा सकते हैं, और आपको एरे तत्व एलिमेंट को कॉपी करना होगा।


तर्क C (C99 ing6.5.16 / 2) के समान है:

एक असाइनमेंट ऑपरेटर के पास अपने बाएं ऑपरेंड के रूप में एक modi सक्षम लैवल्यू होगा।

और .26.3.2.1 / 1:

एक modi mod सक्षम लैवल्यू एक लवल्यू है जिसमें सरणी प्रकार नहीं होता है ... [अन्य बाधाओं का पालन करते हैं]

C में, C ++ की तुलना में असाइनमेंट बहुत सरल है (.16.5.16.1 / 2):

सरल असाइनमेंट (=) में, सही ऑपरेंड का मान असाइनमेंट एक्सप्रेशन के प्रकार में बदल जाता है और लेफ्ट ऑपरेंड द्वारा निर्दिष्ट ऑब्जेक्ट में स्टोर किए गए वैल्यू को बदल देता है।

संरचना-प्रकार की वस्तुओं के असाइनमेंट के लिए, बाएं और दाएं ऑपरेंड में एक ही प्रकार होना चाहिए, इसलिए सही ऑपरेंड के मूल्य को बस बाएं ऑपरेंड में कॉपी किया जाता है।


1
सरणियाँ अपरिवर्तनीय क्यों हैं? या इसके बजाय, असाइनमेंट को विशेष रूप से सरणियों के लिए परिभाषित क्यों नहीं किया जाता है, जब यह एक वर्ग-प्रकार में है?
GManNickG

1
@ मेन: यह अधिक दिलचस्प सवाल है, है ना। C ++ के लिए उत्तर शायद "है, क्योंकि यह सी में कैसे है," और C के लिए, मुझे लगता है कि यह सिर्फ इस वजह से है कि भाषा कैसे विकसित हुई (यानी, कारण ऐतिहासिक है, तकनीकी नहीं), लेकिन मैं जीवित नहीं था जब वह सबसे अधिक जगह ले चुका था, तो मैं उस हिस्से का जवाब देने के लिए किसी और जानकार को छोड़ दूंगा :-P (FWIW, मुझे C90 या C99 औचित्य दस्तावेजों में कुछ भी नहीं मिल सकता है)।
जेम्स मैकनेलिस

2
क्या किसी को पता है कि "modifiable lvalue" की परिभाषा C ++ 03 मानक में कहां है? यह should3.10 में होना चाहिए । सूचकांक कहता है कि यह उस पृष्ठ पर परिभाषित है, लेकिन ऐसा नहीं है। Non8.3.4 / 5 पर (गैर-मानक) नोट कहता है "सरणी प्रकार की वस्तुओं को संशोधित नहीं किया जा सकता है, 3.10 देखें," लेकिन does3.10 एक बार शब्द "सरणी" का उपयोग नहीं करता है।
जेम्स मैकनेलिस

@ जेम्स: मैं बस यही कर रहा था। यह एक हटाई गई परिभाषा को संदर्भित करता है। और हाँ, मैं हमेशा इसके पीछे का असली कारण जानना चाहता हूं, लेकिन यह एक रहस्य है। मैंने ऐसी बातें सुनी हैं, "लोगों को गलती से सरण देने से अयोग्य होने से रोकें", लेकिन यह हास्यास्पद है।
GManNickG

1
@GMan, जेम्स: वहाँ हाल ही में comp.lang.c ++ पर एक चर्चा था groups.google.com/group/comp.lang.c++/browse_frm/thread/... यदि आपने उन्हें अभी और अभी भी रुचि कर रहे हैं। जाहिर है यह क्योंकि एक सरणी नहीं एक परिवर्तनीय lvalue है नहीं है (एक सरणी निश्चित रूप से एक lvalue और सभी गैर स्थिरांक lvalues परिवर्तनीय हैं), लेकिन क्योंकि =एक की आवश्यकता है rvalue पर आरएचएस और एक सरणी एक नहीं हो सकता rvalue ! अंतराल के लिए लेवल-टू-रैवल्यू रूपांतरण निषिद्ध है, जिसे लैवल-टू-पॉइंटर के साथ बदल दिया गया है। static_castइसे बनाने में कोई बेहतर नहीं है क्योंकि यह समान शब्दों में परिभाषित है।
पोटाटोज़वाटर

2

इस लिंक में: http://www2.research.att.com/~bs/bs_faq2.html सरणी असाइनमेंट पर एक अनुभाग है:

सरणियों के साथ दो मूलभूत समस्याएं हैं

  • एक सरणी अपने स्वयं के आकार को नहीं जानती है
  • किसी सरणी का नाम सूचक को उसके पहले तत्व में थोड़ी सी भी उत्तेजना में परिवर्तित कर देता है

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

इसलिए, कंपाइलर int [10] और int b [20] के बीच अंतर नहीं बता सकता है।

संरचनाएं, हालांकि, समान अस्पष्टता नहीं है।


3
वह पृष्ठ फ़ंक्शंस में पास होने की बात करता है (जो नहीं किया जा सकता है, इसलिए यह सिर्फ एक पॉइंटर है, जो कि जब वह कहता है कि इसका मतलब है कि यह अपना आकार खो देता है)। यह सरणियों को असाइन करने से कोई लेना-देना नहीं है। और नहीं, एक सरणी चर पहले तत्व के लिए केवल "वास्तव में" सूचक नहीं है, यह एक सरणी है। ऐरे पॉइंटर्स नहीं हैं।
GMANNICKG

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

3
संकलक दो अलग-अलग आकार के सरणियों के बीच का अंतर बता सकता है - मुद्रण sizeof(a)बनाम sizeof(b)या पास aकरने का प्रयास करें void f(int (&)[20]);
जॉर्ज फ्रिट्ज़शे

यह समझना महत्वपूर्ण है कि प्रत्येक सरणी आकार अपने प्रकार का गठन करता है। पैरामीटर पासिंग के नियम यह सुनिश्चित करते हैं कि आप गरीब आदमी के "जेनेरिक" कार्यों को लिख सकते हैं जो किसी भी आकार के सरणी तर्क लेते हैं, आकार को अलग से पारित करने की आवश्यकता की कीमत पर। यदि ऐसा नहीं होता (और C ++ में आप - और -! - विशिष्ट आकार के सरणियों के लिए संदर्भ मापदंडों को परिभाषित करते हैं), तो आपको प्रत्येक अलग आकार, स्पष्ट रूप से बकवास के लिए एक विशिष्ट फ़ंक्शन की आवश्यकता होगी। मैंने इसके बारे में एक अन्य पोस्ट में लिखा था ।
पीटर -

0

मुझे पता है, उत्तर देने वाले सभी लोग C / C ++ के विशेषज्ञ हैं। लेकिन मैंने सोचा, यह प्राथमिक कारण है।

num2 = num1;

यहां आप सरणी के आधार पते को बदलने की कोशिश कर रहे हैं, जो अनुमेय नहीं है।

और हां, स्ट्रक्चर 2 = स्ट्रक्चर 1;

यहां, ऑब्जेक्ट struct1 को किसी अन्य ऑब्जेक्ट को सौंपा गया है।


और संरचना को निर्दिष्ट करते हुए अंत में सरणी सदस्य को असाइन किया जाएगा, जो सटीक एक ही प्रश्न पूछता है। जब दोनों स्थितियों में यह एक सरणी है, तो किसी को अनुमति क्यों नहीं है?
GManNickG

1
माना। लेकिन पहले वाले को कंपाइलर (num2 = num1) द्वारा रोका जाता है। दूसरे को संकलक द्वारा रोका नहीं जाता है। इससे एक बड़ा फर्क पड़ता है।
nivivakr

यदि सरणियों को असाइन किया गया था, num2 = num1तो पूरी तरह से व्यवहार किया जाएगा। के तत्वों का num2तत्सम तत्व के समान मूल्य होगा num1
जुआनकोपंजा

0

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

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

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