c++ - मैं सी++ में सरणी का उपयोग कैसे करूं?




arrays pointers (4)

5. सरणी का उपयोग करते समय आम नुकसान।

5.1 Pitfall: भरोसेमंद प्रकार असुरक्षित लिंकिंग।

ठीक है, आपको बताया गया है, या खुद को पता चला है, कि ग्लोबल्स (नेमस्पेस स्कोप वैरिएबल जिन्हें अनुवाद इकाई के बाहर एक्सेस किया जा सकता है) ईविल ™ हैं। लेकिन क्या आप जानते थे कि वे वास्तव में कितने ईविल ™ हैं? नीचे दिए गए कार्यक्रम पर विचार करें, जिसमें दो फाइलें हैं [main.cpp] और [numbers.cpp]:

// [main.cpp]
#include <iostream>

extern int* numbers;

int main()
{
    using namespace std;
    for( int i = 0;  i < 42;  ++i )
    {
        cout << (i > 0? ", " : "") << numbers[i];
    }
    cout << endl;
}

// [numbers.cpp]
int numbers[42] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

विंडोज 7 में यह मिनीगू जी ++ 4.4.1 और विजुअल सी ++ 10.0 दोनों के साथ ठीक से संकलित और लिंक करता है।

चूंकि प्रकार मेल नहीं खाते हैं, इसलिए जब आप इसे चलाते हैं तो प्रोग्राम क्रैश हो जाता है।

औपचारिक स्पष्टीकरण: कार्यक्रम में अनिर्धारित व्यवहार (यूबी) है, और इसे दुर्घटनाग्रस्त करने की बजाय, बस लटका सकता है, या शायद कुछ भी नहीं कर सकता है, या यह संयुक्त राज्य अमेरिका, रूस, भारत के राष्ट्रपतियों को धमकी देने वाले ई-मेल भेज सकता है, चीन और स्विट्जरलैंड, और नाक डेमन्स को अपनी नाक से बाहर निकाल दें।

इन-प्रैक्टिस स्पष्टीकरण: main.cpp में सरणी को एक सूचक के रूप में माना जाता है, जो सरणी के समान पते पर रखा जाता है। 32-बिट निष्पादन योग्य के लिए इसका मतलब है कि सरणी में पहला int मान को पॉइंटर के रूप में माना जाता है। Ie, main.cpp में numbers चर शामिल है, या इसमें शामिल है, (int*)1 । यह प्रोग्राम को पता स्थान के बहुत नीचे मेमोरी तक पहुंचने का कारण बनता है, जो परंपरागत रूप से आरक्षित और जाल-कारण है। परिणाम: आपको एक दुर्घटना मिलती है।

संकलक इस त्रुटि का निदान न करने के अपने अधिकारों के भीतर पूरी तरह से हैं, क्योंकि सी ++ 11 §3.5 / 10 कहता है, घोषणाओं के लिए संगत प्रकार की आवश्यकता के बारे में,

[एन 32 9 0 §3.5 / 10]
इस नियम के उल्लंघन पर इस नियम का उल्लंघन नैदानिक ​​की आवश्यकता नहीं है।

वही पैराग्राफ उस भिन्नता का विवरण देता है जिसकी अनुमति है:

... किसी सरणी ऑब्जेक्ट के लिए घोषणाएं सरणी प्रकार निर्दिष्ट कर सकती हैं जो किसी प्रमुख सरणी (8.3.4) की उपस्थिति या अनुपस्थिति से भिन्न होती हैं।

इस अनुमति में भिन्नता में एक अनुवाद इकाई में एक सरणी के रूप में नाम घोषित करना शामिल नहीं है, और एक अन्य अनुवाद इकाई में सूचक के रूप में शामिल नहीं है।

5.2 memset : समयपूर्व अनुकूलन ( memset और दोस्तों) करना।

अभी तक लिखा नहीं है

5.3 Pitfall: तत्वों की संख्या प्राप्त करने के लिए सी मुहावरे का उपयोग करना।

गहरे सी अनुभव के साथ लिखना स्वाभाविक है ...

#define N_ITEMS( array )   (sizeof( array )/sizeof( array[0] ))

चूंकि एक array आवश्यक तत्व पर पॉइंटर करने का sizeof(a)/sizeof(a[0]) , जहां अभिव्यक्ति का sizeof(a)/sizeof(a[0]) sizeof(a)/sizeof(*a) रूप में भी लिखा जा सकता है। इसका मतलब यह है, और इससे कोई फर्क नहीं पड़ता कि यह कैसे लिखा गया है, यह सरणी के संख्या तत्वों को खोजने के लिए सी idiom है

मुख्य pitfall: सी idiom टाइपएफ़ नहीं है। उदाहरण के लिए, कोड ...

#include <stdio.h>

#define N_ITEMS( array ) (sizeof( array )/sizeof( *array ))

void display( int const a[7] )
{
    int const   n = N_ITEMS( a );          // Oops.
    printf( "%d elements.\n", n );
}

int main()
{
    int const   moohaha[]   = {1, 2, 3, 4, 5, 6, 7};

    printf( "%d elements, calling display...\n", N_ITEMS( moohaha ) );
    display( moohaha );
}

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

7 तत्व, प्रदर्शन कॉलिंग ...
1 तत्व

  1. कंपाइलर int const a[7] फिर से int const a[7] को लिखता है।
  2. कंपाइलर int const a[] int const* a को फिर से लिखता int const* a
  3. इसलिए N_ITEMS को एक सूचक के साथ बुलाया जाता है।
  4. 32-बिट निष्पादन योग्य sizeof(array) (पॉइंटर का आकार) के लिए 4 है।
  5. sizeof(*array) sizeof(int) बराबर है, जो 32-बिट निष्पादन योग्य के लिए भी 4 है।

रन त्रुटि पर इस त्रुटि का पता लगाने के लिए आप कर सकते हैं ...

#include <assert.h>
#include <typeinfo>

#define N_ITEMS( array )       (                               \
    assert((                                                    \
        "N_ITEMS requires an actual array as argument",        \
        typeid( array ) != typeid( &*array )                    \
        )),                                                     \
    sizeof( array )/sizeof( *array )                            \
    )

7 तत्व, प्रदर्शन कॉलिंग ...
दावा विफल हुआ: ("N_ITEMS को तर्क के रूप में एक वास्तविक सरणी की आवश्यकता होती है", टाइपिड (ए)! = टाइपिड (& * ए)), फ़ाइल runtime_detect ion.cpp, लाइन 16

इस एप्लिकेशन ने रनटाइम से असामान्य तरीके से इसे समाप्त करने का अनुरोध किया है।
अधिक जानकारी के लिए कृपया एप्लिकेशन की सहायता टीम से संपर्क करें।

रनटाइम त्रुटि का पता लगाने से कोई पहचान नहीं है, लेकिन यह थोड़ा प्रोसेसर समय बर्बाद कर देता है, और शायद अधिक प्रोग्रामर समय बर्बाद करता है। संकलन समय पर पहचान के साथ बेहतर! और यदि आप C ++ 98 के साथ स्थानीय प्रकार के सरणी का समर्थन नहीं करते हैं, तो आप यह कर सकते हैं:

#include <stddef.h>

typedef ptrdiff_t   Size;

template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }

#define N_ITEMS( array )       n_items( array )

इस परिभाषा को संकलित करने के पहले पूर्ण कार्यक्रम में g ++ के साथ प्रतिस्थापित किया गया, मुझे मिला ...

एम: \ count> g ++ compile_time_detection.cpp
compile_time_detection.cpp: फ़ंक्शन 'शून्य प्रदर्शन (कॉन्स int *)' में:
compile_time_detection.cpp: 14: त्रुटि: 'n_items (const int * &)' पर कॉल के लिए कोई मिलान करने वाला फ़ंक्शन नहीं

एम: \ count> _

यह कैसे काम करता है: सरणी n_items संदर्भ में पारित n_items , और इसलिए यह पहले तत्व को पॉइंटर को क्षीण नहीं करता है, और फ़ंक्शन केवल प्रकार द्वारा निर्दिष्ट तत्वों की संख्या को वापस कर सकता है।

सी ++ 11 के साथ आप इसे स्थानीय प्रकार के सरणी के लिए भी उपयोग कर सकते हैं, और यह सरणी के तत्वों की संख्या को खोजने के लिए सुरक्षित C ++ idiom प्रकार है।

5.4 सी ++ 11 और सी ++ 14 constexpr : एक constexpr सरणी आकार समारोह का उपयोग कर।

C ++ 11 के साथ और बाद में यह प्राकृतिक है, लेकिन जैसा कि आप खतरनाक देखेंगे !, C ++ 03 फ़ंक्शन को प्रतिस्थापित करने के लिए

typedef ptrdiff_t   Size;

template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }

साथ में

using Size = ptrdiff_t;

template< class Type, Size n >
constexpr auto n_items( Type (&)[n] ) -> Size { return n; }

जहां महत्वपूर्ण परिवर्तन constexpr का उपयोग है, जो इस फ़ंक्शन को संकलन समय निरंतर उत्पन्न करने की अनुमति देता है

उदाहरण के लिए, सी ++ 03 फ़ंक्शन के विपरीत, इस तरह के संकलन समय निरंतर का उपयोग उसी आकार की सरणी को दूसरे के रूप में घोषित करने के लिए किया जा सकता है:

// Example 1
void foo()
{
    int const x[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
    constexpr Size n = n_items( x );
    int y[n] = {};
    // Using y here.
}

लेकिन constexpr संस्करण का उपयोग कर इस कोड पर विचार करें:

// Example 2
template< class Collection >
void foo( Collection const& c )
{
    constexpr int n = n_items( c );     // Not in C++14!
    // Use c here
}

auto main() -> int
{
    int x[42];
    foo( x );
}

गड़बड़ी: जुलाई 2015 तक उपरोक्त -pedantic-errors साथ -pedantic-errors -64 5.1.0 के साथ -pedantic-errors , और gcc.godbolt.org/ पर ऑनलाइन gcc.godbolt.org/ साथ परीक्षण, gcc.godbolt.org/ 3.0 और gcc.godbolt.org/ 3.2 के साथ भी, लेकिन gcc.godbolt.org/ साथ नहीं 3.3, 3.4.1, 3.5.0, 3.5.1, 3.6 (आरसी 1) या 3.7 (प्रयोगात्मक)। और विंडोज प्लेटफार्म के लिए महत्वपूर्ण है, यह विजुअल सी ++ 2015 के साथ संकलित नहीं है। कारण constexpr अभिव्यक्तियों में संदर्भों के उपयोग के बारे में सी ++ 11 / सी ++ 14 कथन है:

सी ++ 11 सी ++ 14 $ 5.1 9/2 नौ वें डैश

एक सशर्त अभिव्यक्ति e कोर निरंतर अभिव्यक्ति है जब तक कि अमूर्त मशीन (1.9) के नियमों का पालन करने के बाद e का मूल्यांकन निम्न अभिव्यक्तियों में से एक का मूल्यांकन करेगा:

  • एक आईडी-अभिव्यक्ति जो संदर्भ प्रकार के चर या डेटा सदस्य को संदर्भित करती है जब तक संदर्भ में प्रारंभिक प्रारंभिकता न हो और न हो
    • यह निरंतर अभिव्यक्ति के साथ शुरू किया गया है या
    • यह किसी ऑब्जेक्ट का एक गैर स्थैतिक डेटा सदस्य है जिसका जीवनकाल ई के मूल्यांकन के भीतर शुरू हुआ;

कोई भी अधिक verbose लिख सकते हैं

// Example 3  --  limited

using Size = ptrdiff_t;

template< class Collection >
void foo( Collection const& c )
{
    constexpr Size n = std::extent< decltype( c ) >::value;
    // Use c here
}

... लेकिन यह विफल रहता है जब Collection कच्ची सरणी नहीं है।

To deal with collections that can be non-arrays one needs the overloadability of an n_items function, but also, for compile time use one needs a compile time representation of the array size. And the classic C++03 solution, which works fine also in C++11 and C++14, is to let the function report its result not as a value but via its function result type . For example like this:

// Example 4 - OK (not ideal, but portable and safe)

#include <array>
#include <stddef.h>

using Size = ptrdiff_t;

template< Size n >
struct Size_carrier
{
    char sizer[n];
};

template< class Type, Size n >
auto static_n_items( Type (&)[n] )
    -> Size_carrier<n>;
// No implementation, is used only at compile time.

template< class Type, size_t n >        // size_t for g++
auto static_n_items( std::array<Type, n> const& )
    -> Size_carrier<n>;
// No implementation, is used only at compile time.

#define STATIC_N_ITEMS( c ) \
    static_cast<Size>( sizeof( static_n_items( c ).sizer ) )

template< class Collection >
void foo( Collection const& c )
{
    constexpr Size n = STATIC_N_ITEMS( c );
    // Use c here
    (void) c;
}

auto main() -> int
{
    int x[42];
    std::array<int, 43> y;
    foo( x );
    foo( y );
}

About the choice of return type for static_n_items : this code doesn't use std::integral_constant because with std::integral_constant the result is represented directly as a constexpr value, reintroducing the original problem. Instead of a Size_carrier class one can let the function directly return a reference to an array. However, not everybody is familiar with that syntax.

About the naming: part of this solution to the constexpr -invalid-due-to-reference problem is to make the choice of compile time constant explicit.

Hopefully the oops-there-was-a-reference-involved-in-your- constexpr issue will be fixed with C++17, but until then a macro like the STATIC_N_ITEMS above yields portability, eg to the clang and Visual C++ compilers, retaining type safety.

Related: macros do not respect scopes, so to avoid name collisions it can be a good idea to use a name prefix, eg MYLIB_STATIC_N_ITEMS .

सी ++ सी से वंचित सरणी जहां वे लगभग हर जगह उपयोग किया जाता है। सी ++ उन अवशेषों को प्रदान करता है जो उपयोग में आसान हैं और कम त्रुटि-प्रवण ( std::vector<T> सी ++ 98 और std::array<T, n> C++11 बाद से), इसलिए सरणी की आवश्यकता नहीं है सी में जितनी बार होती है उतनी बार उठती है। हालांकि, जब आप विरासत कोड पढ़ते हैं या सी में लिखी गई लाइब्रेरी से बातचीत करते हैं, तो आपको एरे काम करने के तरीके पर दृढ़ समझ होनी चाहिए।

यह FAQ पांच भागों में विभाजित है:

  1. प्रकार के स्तर पर सरणी और तत्वों तक पहुंच
  2. सरणी निर्माण और प्रारंभिकरण
  3. असाइनमेंट और पैरामीटर गुजर रहा है
  4. पॉइंटर्स के बहुआयामी सरणी और सरणी
  5. सरणी का उपयोग करते समय आम नुकसान

यदि आपको इस FAQ में कुछ महत्वपूर्ण याद आ रही है, तो एक उत्तर लिखें और इसे एक अतिरिक्त भाग के रूप में यहां लिंक करें।

निम्न पाठ में, "सरणी" का अर्थ है "सी सरणी", वर्ग टेम्पलेट std::array । सी घोषणाकर्ता वाक्यविन्यास का मूल ज्ञान माना जाता है। ध्यान दें कि नीचे दिखाए गए अनुसार मैन्युअल उपयोग और delete गए अपवादों के मुकाबले बेहद खतरनाक है, लेकिन यह एक और एफएक्यू का विषय है।

(नोट: यह स्टैक ओवरफ्लो के सी ++ एफएक्यू में प्रवेश करने के लिए है । यदि आप इस फॉर्म में एक एफएक्यू प्रदान करने के विचार की आलोचना करना चाहते हैं, तो मेटा पर पोस्ट करना जो यह सब शुरू कर देगा, ऐसा करने का स्थान होगा। उस प्रश्न की निगरानी सी ++ चैटरूम में की जाती है, जहां एफएक्यू विचार पहली जगह शुरू हुआ था, इसलिए आपके उत्तर को उन लोगों द्वारा पढ़ा जाने की संभावना है जो इस विचार के साथ आए थे।)


असाइनमेंट

किसी विशेष कारण के लिए, अरण एक दूसरे को सौंपा नहीं जा सकता है। इसके बजाय std::copy उपयोग करें:

#include <algorithm>

// ...

int a[8] = {2, 3, 5, 7, 11, 13, 17, 19};
int b[8];
std::copy(a + 0, a + 8, b);

यह सच सरणी असाइनमेंट प्रदान करने से अधिक लचीला है क्योंकि बड़े सरणी के स्लाइस को छोटे सरणी में कॉपी करना संभव है। std::copy आमतौर पर आदिम प्रकार के लिए अधिकतम प्रदर्शन देने के लिए विशिष्ट है। यह असंभव है कि std::memcpy बेहतर प्रदर्शन करता है। यदि संदेह में, उपाय करें।

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

पैरामीटर गुजर रहा है

Arrays मूल्य से पारित नहीं किया जा सकता है। आप या तो पॉइंटर या संदर्भ द्वारा उन्हें पास कर सकते हैं।

सूचक द्वारा पास करें

चूंकि सरणी स्वयं मूल्य से पारित नहीं हो सकती हैं, आमतौर पर उनके पहले तत्व के लिए एक सूचक मूल्य के आधार पर पारित किया जाता है। इसे अक्सर "पॉइंटर द्वारा पास" कहा जाता है। चूंकि सरणी का आकार उस सूचक के माध्यम से पुनर्प्राप्त करने योग्य नहीं है, इसलिए आपको सरणी के आकार (क्लासिक सी समाधान) या सरणी के अंतिम तत्व (सी ++ इटरेटर समाधान) के बाद इंगित करने वाला दूसरा पॉइंटर इंगित करने वाला दूसरा पैरामीटर पारित करना होगा। :

#include <numeric>
#include <cstddef>

int sum(const int* p, std::size_t n)
{
    return std::accumulate(p, p + n, 0);
}

int sum(const int* p, const int* q)
{
    return std::accumulate(p, q, 0);
}

एक वाक्य रचनात्मक विकल्प के रूप में, आप पैरामीटर को T p[] रूप में भी घोषित कर सकते हैं, और इसका मतलब केवल पैरामीटर सूचियों के संदर्भ में T* p जैसा ही है :

int sum(const int p[], std::size_t n)
{
    return std::accumulate(p, p + n, 0);
}

आप केवल पैरामीटर सूचियों के संदर्भ में T p[] से T *p को पुनः लिखने के रूप में संकलक के बारे में सोच सकते हैं। यह विशेष नियम आंशिक रूप से सरणी और पॉइंटर्स के बारे में पूरी भ्रम के लिए ज़िम्मेदार है। हर दूसरे संदर्भ में, किसी सरणी के रूप में या पॉइंटर के रूप में कुछ घोषित करना एक बड़ा अंतर बनाता है।

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

int sum(const int* p, std::size_t n)

// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[], std::size_t n)

// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[8], std::size_t n)   // the 8 has no meaning here

संदर्भ द्वारा पास करें

संदर्भ द्वारा Arrays भी पारित किया जा सकता है:

int sum(const int (&a)[8])
{
    return std::accumulate(a + 0, a + 8, 0);
}

इस मामले में, सरणी का आकार महत्वपूर्ण है। एक फ़ंक्शन लिखने के बाद से केवल 8 तत्वों के सरणी को स्वीकार करने के लिए थोड़ा उपयोग होता है, प्रोग्रामर आमतौर पर टेम्पलेट्स के रूप में ऐसे फ़ंक्शन लिखते हैं:

template <std::size_t n>
int sum(const int (&a)[n])
{
    return std::accumulate(a + 0, a + n, 0);
}

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


प्रकार के स्तर पर Arrays

एक सरणी प्रकार T[n] रूप में दर्शाया गया है जहां T तत्व प्रकार है और n एक सकारात्मक आकार है , सरणी में तत्वों की संख्या। सरणी प्रकार तत्व प्रकार और आकार का एक उत्पाद प्रकार है। यदि उनमें से एक या दोनों अवयव भिन्न हैं, तो आपको एक अलग प्रकार मिलता है:

#include <type_traits>

static_assert(!std::is_same<int[8], float[8]>::value, "distinct element type");
static_assert(!std::is_same<int[8],   int[9]>::value, "distinct size");

ध्यान दें कि आकार प्रकार का हिस्सा है, यानी, विभिन्न आकार के सरणी प्रकार असंगत प्रकार हैं जिनके पास एक दूसरे के साथ बिल्कुल कुछ नहीं है। sizeof(T[n]) n * sizeof(T) बराबर है।

ऐरे-टू-पॉइंटर क्षय

T[n] और T[m] बीच एकमात्र "कनेक्शन" यह है कि दोनों प्रकार निहित रूप से T* परिवर्तित हो सकते हैं, और इस रूपांतरण का परिणाम सरणी के पहले तत्व के लिए एक सूचक है। यही है, कहीं भी एक T* की आवश्यकता है, आप एक T[n] प्रदान कर सकते हैं, और संकलक चुपचाप उस सूचक प्रदान करेगा:

                  +---+---+---+---+---+---+---+---+
the_actual_array: |   |   |   |   |   |   |   |   |   int[8]
                  +---+---+---+---+---+---+---+---+
                    ^
                    |
                    |
                    |
                    |  pointer_to_the_first_element   int*

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

Arrays पॉइंटर्स नहीं हैं

संकलक चुपचाप किसी भी सरणी के पहले तत्व को पॉइंटर जेनरेट करेगा जब भी इसे समझा जा सके, यानी, जब भी कोई ऑपरेशन किसी सरणी पर असफल हो जाता है लेकिन पॉइंटर पर सफल होता है। सरणी से पॉइंटर तक यह रूपांतरण छोटा है, क्योंकि परिणामस्वरूप सूचक मूल्य केवल सरणी का पता है। ध्यान दें कि पॉइंटर सरणी के हिस्से के रूप में संग्रहीत नहीं है (या स्मृति में कहीं और)। एक सरणी एक सूचक नहीं है।

static_assert(!std::is_same<int[8], int*>::value, "an array is not a pointer");

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

static_assert(!std::is_same<int*, int(*)[8]>::value, "distinct element type");

निम्नलिखित ASCII कला इस भेद को बताती है:

      +-----------------------------------+
      | +---+---+---+---+---+---+---+---+ |
+---> | |   |   |   |   |   |   |   |   | | int[8]
|     | +---+---+---+---+---+---+---+---+ |
|     +---^-------------------------------+
|         |
|         |
|         |
|         |  pointer_to_the_first_element   int*
|
|  pointer_to_the_entire_array              int(*)[8]

ध्यान दें कि पहले तत्व के सूचक केवल एक पूर्णांक को इंगित करते हैं (एक छोटे बॉक्स के रूप में चित्रित), जबकि पूरे सरणी के सूचक को 8 पूर्णांक (एक बड़े बॉक्स के रूप में चित्रित) की एक सरणी को इंगित करता है।

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

यदि आप सी घोषणाकर्ता वाक्यविन्यास से अपरिचित हैं, तो प्रकार int(*)[8] में कोष्ठक आवश्यक हैं:

  • int(*)[8] 8 पूर्णांक की सरणी के लिए एक सूचक है।
  • int*[8] 8 पॉइंटर्स की एक सरणी है, प्रकार int* प्रत्येक तत्व।

तत्वों तक पहुंच

सी ++ सरणी के अलग-अलग तत्वों तक पहुंचने के लिए दो वाक्य रचनात्मक विविधता प्रदान करता है। उनमें से कोई भी दूसरे से बेहतर नहीं है, और आपको दोनों को स्वयं से परिचित होना चाहिए।

सूचक अंकगणित

किसी सरणी के पहले तत्व को पॉइंटर p देखते हुए, अभिव्यक्ति p+i सरणी के i-th तत्व में एक सूचक उत्पन्न करता है। उस पॉइंटर को बाद में संदर्भित करके, कोई व्यक्ति अलग-अलग तत्वों तक पहुंच सकता है:

std::cout << *(x+3) << ", " << *(x+7) << std::endl;

यदि x एक सरणी को इंगित करता है, तो सरणी-से-पॉइंटर क्षय में लाया जाएगा, क्योंकि एक सरणी जोड़ना और एक पूर्णांक अर्थहीन है (सरणी पर कोई प्लस ऑपरेशन नहीं है), लेकिन एक पॉइंटर और एक पूर्णांक जोड़ना समझ में आता है:

   +---+---+---+---+---+---+---+---+
x: |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
     |           |               |
x+0  |      x+3  |          x+7  |     int*

(ध्यान दें कि अंतर्निहित जेनरेटर पॉइंटर का कोई नाम नहीं है, इसलिए मैंने इसे पहचानने के लिए x+0 लिखा था।)

यदि, दूसरी तरफ, x एक सरणी के पहले (या किसी अन्य) तत्व को पॉइंटर इंगित करता है, तो सरणी-से-पॉइंटर क्षय जरूरी नहीं है, क्योंकि जिस सूचक पर i जोड़ा जा रहा i वह पहले से मौजूद है:

   +---+---+---+---+---+---+---+---+
   |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
   +-|-+         |               |
x: | | |    x+3  |          x+7  |     int*
   +---+

ध्यान दें कि चित्रित मामले में, x एक सूचक परिवर्तक ( x बगल में छोटे बॉक्स द्वारा स्पष्ट) है, लेकिन यह एक सूचक (या प्रकार T* की किसी भी अन्य अभिव्यक्ति) को वापस करने वाले फ़ंक्शन का परिणाम भी हो सकता है।

इंडेक्सिंग ऑपरेटर

चूंकि वाक्यविन्यास *(x+i) थोड़ा बेकार है, सी ++ वैकल्पिक वाक्यविन्यास x[i] प्रदान करता है x[i] :

std::cout << x[3] << ", " << x[7] << std::endl;

इस तथ्य के कारण कि अतिरिक्त कम्यूटिव है, निम्न कोड बिल्कुल वही करता है:

std::cout << 3[x] << ", " << 7[x] << std::endl;

इंडेक्सिंग ऑपरेटर की परिभाषा निम्नलिखित दिलचस्प समकक्षता की ओर ले जाती है:

&x[i]  ==  &*(x+i)  ==  x+i

हालांकि, &x[0] आम तौर पर x बराबर नहीं है । पूर्व एक सूचक है, बाद वाला एक सरणी है। केवल जब संदर्भ सरणी-से-पॉइंटर क्षय को ट्रिगर करता है तो x और &x[0] दूसरे के लिए उपयोग किया जा सकता है। उदाहरण के लिए:

T* p = &array[0];  // rewritten as &*(array+0), decay happens due to the addition
T* q = array;      // decay happens due to the assignment

पहली पंक्ति पर, कंपाइलर एक पॉइंटर से एक पॉइंटर तक असाइनमेंट का पता लगाता है, जो आसानी से सफल होता है। दूसरी पंक्ति पर, यह एक सरणी से एक सूचक को असाइनमेंट का पता लगाता है। चूंकि यह व्यर्थ है (लेकिन पॉइंटर असाइनमेंट के सूचक को समझ में आता है), सरणी-से-पॉइंटर क्षय सामान्य रूप से किक करता है।

सीमाओं

प्रकार की एक सरणी T[n] में n तत्व हैं, जो 0 से n-1 तक अनुक्रमित हैं; कोई तत्व n नहीं है। और फिर भी, आधा खुली श्रेणियों का समर्थन करने के लिए (जहां शुरुआत समावेशी है और अंत अनन्य है ), सी ++ एक सूचक की गणना (अस्तित्वहीन) एन-वें तत्व को अनुमति देता है, लेकिन यह संकेतक है कि पॉइंटर:

   +---+---+---+---+---+---+---+---+....
x: |   |   |   |   |   |   |   |   |   .   int[8]
   +---+---+---+---+---+---+---+---+....
     ^                               ^
     |                               |
     |                               |
     |                               |
x+0  |                          x+8  |     int*

उदाहरण के लिए, यदि आप किसी सरणी को सॉर्ट करना चाहते हैं, तो निम्न दोनों समान रूप से अच्छी तरह से काम करेंगे:

std::sort(x + 0, x + n);
std::sort(&x[0], &x[0] + n);

ध्यान दें कि यह प्रदान करना अवैध है &x[n] दूसरे तर्क के रूप में है क्योंकि यह &*(x+n) बराबर है, और उप-अभिव्यक्ति *(x+n) तकनीकी रूप से C ++ में अपरिभाषित व्यवहार का आह्वान करता है (लेकिन C99 में नहीं )।

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


प्रोग्रामर अक्सर पॉइंटर्स के सरणी के साथ बहुआयामी सरणी को भ्रमित करते हैं।

बहुआयामी सरणी

अधिकांश प्रोग्रामर नामित बहुआयामी सरणी से परिचित हैं, लेकिन कई इस तथ्य से अनजान हैं कि बहुआयामी सरणी भी गुमनाम रूप से बनाई जा सकती है। बहुआयामी सरणी को अक्सर "सरणी के सरणी" या " सच्चे बहुआयामी सरणी" के रूप में जाना जाता है।

बहुआयामी सरणी नामित

नामित बहुआयामी सरणी का उपयोग करते समय, सभी आयामों को संकलन समय पर जाना जाना चाहिए:

int H = read_int();
int W = read_int();

int connect_four[6][7];   // okay

int connect_four[H][7];   // ISO C++ forbids variable length array
int connect_four[6][W];   // ISO C++ forbids variable length array
int connect_four[H][W];   // ISO C++ forbids variable length array

इस प्रकार एक नामित बहुआयामी सरणी स्मृति में दिखती है:

              +---+---+---+---+---+---+---+
connect_four: |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+

ध्यान दें कि उपरोक्त 2 डी ग्रिड केवल सहायक विज़ुअलाइज़ेशन हैं। सी ++ के दृष्टिकोण से, स्मृति बाइट्स का एक "फ्लैट" अनुक्रम है। बहु-आयामी सरणी के तत्व पंक्ति-प्रमुख क्रम में संग्रहीत होते हैं। यही है, connect_four[0][6] और connect_four[1][0] स्मृति में पड़ोसियों हैं। वास्तव में, connect_four[0][7] और connect_four[1][0] एक ही तत्व को दर्शाता है! इसका मतलब है कि आप बहु-आयामी सरणी ले सकते हैं और उन्हें बड़े, एक-आयामी सरणी के रूप में व्यवहार कर सकते हैं:

int* p = &connect_four[0][0];
int* q = p + 42;
some_int_sequence_algorithm(p, q);

बेनामी बहुआयामी सरणी

अज्ञात बहुआयामी सरणी के साथ, पहले को छोड़कर सभी आयाम संकलन समय पर जाना जाना चाहिए:

int (*p)[7] = new int[6][7];   // okay
int (*p)[7] = new int[H][7];   // okay

int (*p)[W] = new int[6][W];   // ISO C++ forbids variable length array
int (*p)[W] = new int[H][W];   // ISO C++ forbids variable length array

इस प्रकार एक अज्ञात बहुआयामी सरणी स्मृति में दिखती है:

              +---+---+---+---+---+---+---+
        +---> |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |
      +-|-+
   p: | | |
      +---+

ध्यान दें कि सरणी को अभी भी स्मृति में एक ब्लॉक के रूप में आवंटित किया गया है।

पॉइंटर्स की Arrays

आप संकेत के दूसरे स्तर को पेश करके निश्चित चौड़ाई के प्रतिबंध को दूर कर सकते हैं।

पॉइंटर्स के नामांकित सरणी

यहां पांच पॉइंटर्स का नामित सरणी है जो विभिन्न लंबाई के अज्ञात सरणी के साथ प्रारंभ की जाती हैं:

int* triangle[5];
for (int i = 0; i < 5; ++i)
{
    triangle[i] = new int[5 - i];
}

// ...

for (int i = 0; i < 5; ++i)
{
    delete[] triangle[i];
}

और यहां यह स्मृति में कैसा दिखता है:

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
triangle: | | | | | | | | | | |
          +---+---+---+---+---+

चूंकि प्रत्येक पंक्ति को अलग-अलग आवंटित किया जाता है, इसलिए 1 डी सरणी के रूप में 2 डी सरणी देखना अब और काम नहीं करता है।

पॉइंटर्स के बेनामी सरणी

यहां 5 (या किसी अन्य संख्या) पॉइंटर्स की अज्ञात सरणी है जो विभिन्न लंबाई के अज्ञात सरणी के साथ प्रारंभ की जाती है:

int n = calculate_five();   // or any other number
int** p = new int*[n];
for (int i = 0; i < n; ++i)
{
    p[i] = new int[n - i];
}

// ...

for (int i = 0; i < n; ++i)
{
    delete[] p[i];
}
delete[] p;   // note the extra delete[] !

और यहां यह स्मृति में कैसा दिखता है:

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
          | | | | | | | | | | |
          +---+---+---+---+---+
            ^
            |
            |
          +-|-+
       p: | | |
          +---+

रूपांतरण

ऐरे-टू-पॉइंटर क्षय प्राकृतिक रूप से सरणी के सरणी और तीर के सरणी तक फैला हुआ है:

int array_of_arrays[6][7];
int (*pointer_to_array)[7] = array_of_arrays;

int* array_of_pointers[6];
int** pointer_to_pointer = array_of_pointers;

हालांकि, T[h][w] से T** तक कोई अंतर्निहित रूपांतरण नहीं है। यदि ऐसा निहित रूपांतरण मौजूद था, तो परिणाम T लिए h पॉइंटर्स की सरणी के पहले तत्व के लिए सूचक होगा (प्रत्येक मूल 2 डी सरणी में किसी पंक्ति के पहले तत्व को इंगित करता है), लेकिन वह सूचक सरणी मौजूद नहीं है अभी तक स्मृति में कहीं भी। यदि आप ऐसा रूपांतरण चाहते हैं, तो आपको आवश्यक पॉइंटर सरणी मैन्युअल रूप से बनाना और भरना होगा:

int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
    p[i] = connect_four[i];
}

// ...

delete[] p;

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

int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
    p[i] = new int[7];
    std::copy(connect_four[i], connect_four[i + 1], p[i]);
}

// ...

for (int i = 0; i < 6; ++i)
{
    delete[] p[i];
}
delete[] p;






c++-faq