【C++】C++中的數組


目錄結構:

contents structure [-]

數組是存放相同類型的容器,數組的大小確定不變,不能隨意向數組中添加元素。因為數組的大小固定,因此對某些特殊應用來說程序的運行時性能較好,但相應的損失了一些靈活性。

1. 一維數組

1.1 數組的定義和初始化

一維數組的聲明形如:a[b],其中a是數組的名字,b是數組的維度(數組中元素的個數)。其實這里說成數組的定義更加准確,因為a[b];是會被分配內存的。
比如:

int arr[10]; //arr是含有10個整型的數組
int arr[10]={}; //arr是含有10個整型的數組,進行了初始化,每個元素的值都是10
int *arr[10]; //arr是含有10個整型指針的數組
int arr[] = {1,2,3}; //arr是含有3個整型的數組
int arr[5] = {1,2,3}; //等價於int arr[5] = {1,2,3,0,0}
string arr[3] = {"hello","world"}; //等價於 string arr[3] = {"hello","world",""};
char arr[6] = "hello"; //這里不能聲明維度為5,因為字符串末尾還有一個空字符('\0'),所以應該是"hello\0"
char arr[6] = {'h','e','l','l','o','\0'}; //等價於 char arr[] = "hello";
char arr[6] = {'h','e','l','l','o'}; //這條語句和上面一條語句是一樣的。默認字符初始化值是'\0'

如果在定義了整型類型的數組后,並沒有對內存進行初始化的話,那么里面會存放以前的垃圾值。用字符串字面值初始化字符數組時,一定要注意字符串字面值的末尾還有一個空字符。

 

上面介紹一些簡單的一維數組的定義,下面結合引用和指針來介紹一些復雜的數組聲明,理解數組聲明格式的步驟:默認順序從右到左,如果有括號,要先看括號里的。

int *arr1[10];//1
int (*arr2)[10];//2

第1條語句和第2條語句的含義完全不同,第1條語句表示:“arr1是含有10個整型指針的數組”;第2條語句的含義是:“arr2是指向一個10個整型大小的指針”。也就是說,arr1是一個數組,數組的大小是10,數組中每個元素類型是int類型的指針;arr2首先是一個指針,指向了大小為10的數組,數組中每個元素是int類型。

 

int &arr3[10];//3,錯誤
int (&arr4)[10];//4,錯誤
int a[10] = {};
int (&arr5)[10] = a;//5

arr3的本意是想聲明了一個數組,數組的大小是10,數組中的每個元素的類型是一個int類型的引用,但是c++並不存在這種數組的聲明格式,語法錯誤。
arr4的本意是想聲明一個引用,引用的是一個數組,數組的大小是10,數組中每個元素的類型是int類型,但arr4既然是一個引用,所以必需要初始化,所以arr4錯誤,arr5正確。

 

還可以結合指針和引用來共同聲明更復雜的數組,理解的方式和上面都是一樣的(從右到左,有括號先看括號),例如:

int *(&arr)[10] = ...;//必須要進行正確的初始化

上面一條語句的含義:arr首先是一個引用,引用的類型是一個大小為10的數組,數組的每個元素是int*(整型指針)類型。因為arr是一個引用,所以這條語句必須要初始化,否則會報錯。

 

1.2 數組元素的訪問

數組的下標是從0開始的,在使用數組下標的時候,通常將其定義為size_t類型(一般不要使用int類型),size_t位於cstddef頭文件(該文件是C標准庫stddef.h頭文件的C++版本)中,是無符號整型的別名,可以代表任意字節對象的大小。
例如:

#include <cstddef>
#include <iostream>
using namespace std;

int main(){
    size_t size = 20;
    int arr[size] = {};
    for(size_t index=0; index < size; index++){
        cout << arr[index] << " ";
    }
    cout << endl;
    return 0;
}

 

C++11標准提供了auto關鍵字:

for(auto a : arr)
    cout << a << " ";

1.3 數組和指針

在c/c++語言中,數組和指針有着非常緊密的聯系,在使用數組編譯的時候通常會把它轉化為它的第一個元素的指針。

例如:

string nums[] = {"one","two","three"};
string *p = &nums[0];
string *p2 = nums;

p和p2其實等價的,都是表示指向nums首元素的指針。

int ia[] = {0,1,2,3,4,5};
auto ia2(ia);//ia2是一個整型指針,指向ia的第一個元素

ia是含有6個整數的數組,但當使用ia作為ia2的初始值時,編譯器實際的編譯類型是:

auto ia2(&ia[0]);

可以看出,ia2是一個指針,指向了ia數組的第一個元素。


使用auto會發生上述的轉化,但當使用decltype關鍵字時上述轉化不會發生

decltype(ia) ia3 = {0,1,2,3,4,5};//ia3是一個含有6個整數的數組

其中ia是一個含有6個整數的數組int[6]類型,decltype(ia)得到的類型也是int[6],所以ia3的類型也是int[6]。

 

上面提到了如何獲取數組第一個元素的指針,接下來討論一下如何獲取數組尾元素下一元素的指針

string *p = &nums[3];//nums的大小為3,p指向數組尾元素的下一個元素。


對於獲取首元素和尾元素下一元素的指針,c++11提供了兩個新方法,begin()和end()方法

#include <iostream>
using namespace std;
int main(){
    int ia[] = {0,1,2,3,4,5,6,7,8};
    int *p1 = begin(ia);//獲取首元素的指針
    int *p2 = end(ia);//獲取尾元素下一元素的指針
    for(; p1 != p2 ; p1++)
        cout << *p1 << " ";
    cout << endl;
}

指向數組元素的兩個指針可以進行解運算、比較、遞增、遞減、指針相減等操作。指針的比較就是比較地址的大小。遞增/遞減就是內存地址往后/往前移動1,除此之外,還可以在指針上加上/減去某整數n,表示往后/往前移動n位的新指針。指針相減就是內存地址相減,差值表示間隔的元素個數。

 

兩個指針相減是一種ptrdiff_t的標准庫類型,和size_t一樣,ptrdiff_t也是定義在cstddef頭文件中的。因為差值可能為負,所以ptrdiff_t是一種有符號整型。

auto n = end(ia) - begin(ia);//等價於:ptrdiff_t n = end(ia) - begin(ia);

 

例如:

#include <iostream>
#include <cstddef>
using namespace std;
int main(){
    int ia[] = {1,2,3,4,5,6,7,8,9};
    auto p1 = begin(ia);//首元素的指針
    auto p2 = end(ia);//尾元素下一元素的指針
    while(p1 < p2){
        *p1 = (*p1) * 2;
        p1++;
    }
    ptrdiff_t length = end(ia) - begin(ia);//得到ia數組的長度,ptrdiff_t是指針相減的類型(一種有符號整數的別名)
    for(size_t index=0; index<length; index++)
        cout << ia[index] << " ";
    cout << "\n";
    int *p3 = &ia[4];//得到第5個元素
    cout << "第7個元素 : "  <<  *(p3 + 2) << "\n";
    cout << "第3個元素 : "  <<  *(p3 - 2) << endl;
    return 0;
}

結果:

2 4 6 8 10 12 14 16 18
第7個元素 : 14
第3個元素 : 6

 

當一個指針指向一個數組后,使用指針可以直接通過下標訪問數組中的值:

int a[]={1,2,3,4,5,6};
int *p=a;//指向a的首元素

for(int index=0; index<6; index++)
  cout << p[index] << " ";//等同於 *(p+index)

cout << endl;

注意:p[index]是未移動指針p的位置的,指針p始終都未改變(p一直都指向數組a的首元素)。

 

C++數組的初始化長度不能是變量, 如果需要變量長度來初始化數組,那么這時候可以new一個數組,然后用指針指向數組來解決這個問題:

    std::string s = "Hello World!";
    char *cstr = new char[s.size() + 1];//不能寫成char cstr[s.size()+1]
#注意:在不使用cstr時,一定要手動delete cstr.

 

 

2. 多維數組

嚴格的說,在C++語言中是沒有多維數組的,通常所說的多維數組其實就是數組的數組。

 

當一個數組中的元素依然是一個數組時,通常使用兩個維度來定義它。

int arr1[m][n];//二維數組,大小為m的數組,每個數組的元素都是大小為n的數組。
int arr2[m][n][q];//三維數組,大小為m的數組,每個數組的元素都是大小為n的數組,然后大小為n的數組中的每個元素又是大小為q的數組。

 

對二維數組來說,通常把第一維度稱為行,把第二維度稱為列,因此上面的arr1是一個含有m行n列的二維數組。

 

int ia1[3][4]={
{0,1,2,3},
{4,5,6,7},
{8,9,10,11},
}
int ia2[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
//顯式初始化每行的首元素
int ia3[3][4]={{0},{4},{8}};
//顯式初始化第一行元素
int ia4[3][4]={0,1,2,3};

上面的ia1和ia2是等價的,ia1分配了12個整型內存空間,由於這些空間都是連續的(因為數組就是一塊連續的內存空間),所以換成ia2的聲明方式完全一樣。

 

可以使用下標運算符來訪問多維數組的元素,此時數組的每個維度對應一個下標運算符。

// 用arr的首元素為ia的最后一行的最后一列元素賦值
ia[2][3] = arr[0][0][0];//ia是一個3行4列的數組,arr是一個三維數組
int (&row)[4] = ia[1];//把ia的第二行綁定到引用a上

 

例如:

#include <iostream>
using namespace std;
int main(){
    int ia[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
   //使用下標運算符
    for(size_t row=0; row < 3; row++)
        for(size_t col=0; col<4; col++)
            cout << ia[row][col]<< " ";
    cout << "\n";

   //使用指針
    for(int (*row)[4] = ia; row != ia + 3; row++)
        for(int *col = *row; col != *row + 4; col++)
            cout << *col << " ";
    cout << "\n";

   //使用指針
    for(int *val : ia)
        cout << *val << " ";
    cout << "\n";

   //使用引用
    for(auto &row : ia)
        for(auto &col : row)
            cout << col << " ";
    cout << endl;
    return 0;
}

注意,上面的auto循環不能寫成

for(auto row : ia){
    for(auto col : row){//錯誤,因為row是int*類型
    }
}

因為數組名在使用的時候,默認是會被編譯成指向首元素的指針。
換句話說,循環語句for(auto row : ia);其中的 row 是int *類型,對於for(auto col : row)的話,顯然錯誤。使用auto &row 讓編譯器知道自己要使用的是引用,編譯器就不會再把它轉化為指針。

對於獲取首元素和尾元素下一元素的指針,可以通過標准庫中的begin()和end()方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM