概覽
陣列在C語言中有著特殊的地位,它有很多特性,例如它的儲存是連續的,陣列的名稱就是陣列的地址等。而在C語言中是沒有String型別的,那麼如果要表示一個字串,就必須使用字元陣列。今天主要就介紹如下三個方面:
一維陣列
一維陣列操作比較簡單,但是需要注意,陣列長度必須是固定的,長度不能使用變數進行初始化;如果宣告的同時進行賦值則陣列長度可以省略,編譯器會自動計算陣列長度;同時陣列不能先宣告再一次性賦值(當然可以對每個元素一一賦值)。
#include <stdio.h> int main(){ int len = 2; //int a[len] = { 1, 2};//錯誤,不能使變數 int a[2];//正確 a[0] = 1; a[1] = 2; //a[2] = 3;//超過陣列長度,但是編譯器並不會檢查,執行報錯 int b['a'] = {1,2,3};//'a'=97,所以可以作為陣列長度,但是後面的元素沒有初始化,其值預設為0 for (int i = 0; i < 97; ++i){ printf("b[%d]=%d\n",i,b[i]); } int c[2 * 3];//2*3是固定值可以作為陣列長度 int d[] = { 1, 2, 3 };//如果初始化的同時賦值則陣列長度可以省略,當前個數為3 }
擴充套件--陣列的儲存
陣列在記憶體中儲存在一塊連續的空間中,如果知道陣列型別(int、float等)和初始地址就可以知道其他元素的地址,同時由於陣列名等於陣列第一個元素的地址,所以當陣列作為引數(作為引數時形參可以省略)其實是引用傳遞。
#include <stdio.h> int main(){ int const l = 3; int a[l] = { 1, 2,3 }; for (int i = 0; i < l; ++i){ //由於當前在32位編譯器下,int型長度為4個位元組,可以判斷出三個地址兩兩相差都是4 printf("a[%d]=%d,address=%x\n", i, a[i], &a[i]); } /*當前輸出結果: a[0] = 1, address = c9f95c a[1] = 2, address = c9f960 a[2] = 3, address = c9f964*/ }
我們看一下上面定義的陣列在記憶體中儲存結構
再來看一下陣列作為引數傳遞的情況,陣列作為引數傳遞的是陣列的地址
#include <stdio.h> void changeValue(int a[]){ a[0] = 10; } int main(){ int a[2] = {1,2}; changeValue(a); for (int i = 0; i < 2; ++i){ printf("a[%d]=%d\n",i,a[i]); } /*列印結果 a[0]=10 a[1]=2 */ }
多維陣列
多維陣列其實可以看成是一個特殊的一維陣列,只是每個元素又是一個一維陣列,下面簡單看一下多維陣列的初始化和賦值
#include <stdio.h> int main(){ int a[2][3];//2行3列,二維陣列可以看成是一個特殊的一維陣列,只是它的每一個元素又是一個一維陣列 a[0][0] = 1; a[0][1] = 2; a[0][2] = 3; a[1][0] = 4; a[1][1] = 5; a[1][2] = 6; for (int i = 0; i < 2; ++i){ for (int j = 0; j < 3; ++j){ printf("a[%d][%d]=%d,address=%x\n", i, j, a[i][j], &a[i][j]); } } /*列印結果 a[0][0]=1,address=f8fb24 a[0][1]=2,address=f8fb28 a[0][2]=3,address=f8fb2c a[1][0]=4,address=f8fb30 a[1][1]=5,address=f8fb34 a[1][2]=6,address=f8fb38 */ //初始化並直接賦值 int b[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; //由於陣列的賦值順序是先從第一行第一列,再第一行第二列...然後第二行第一列...,所以我們也可以寫成如下形式 int c[2][3] = { 1, 2, 3, 4, 5, 6 }; //也可以只初始化部分資料,其餘元素預設為0 int d[2][3] = { 1, 2, 3, 4 }; for (int i = 0; i < 2; ++i){ for (int j = 0; j < 3; ++j){ printf("d[%d][%d]=%d\n", i, j, d[i][j]); } } /*列印結果 d[0][0]=1 d[0][1]=2 d[0][2]=3 d[1][0]=4 d[1][1]=0 d[1][2]=0 */ //當然下面賦值也可以 int e[2][3] = { {}, { 4, 5, 6 } }; //可以省略行號,但是絕對不可以省略列號,因為按照上面說的賦值順序,它無法判斷有多少行 int f[][3] = { {1,2,3},{4,5,6} }; }
擴充套件--多維陣列的儲存
以上面a陣列為例,它在記憶體中的結構如下圖
根據上圖和一維陣列的儲存,對於二維陣列可以得出如下結論:陣列名就是整個二維陣列的地址,也等於第一行陣列名的地址,還等於第一個元素的地址;第二行陣列名等於第二行第一個元素的地址。用表示式表示:
- a=a[0]=&a[0][0]
- a[1]=&a[1][0]
同樣可以得出a[i][j]=a[i]+j。關於三維陣列、四維陣列等多維陣列,其實可以以此類推,在此不再贅述。
字串
在C語言中是沒有字串型別的,如果要表示字串需要使用char型別的陣列,因為字串本身就是多個字元的組合。但是需要注意的是字串是一個特殊的陣列,在它的結束位置必須要加一個”\0”(ASCII中0是空操作符,表示什麼也不做)來表示字串結束,否則編譯器是不知道什麼時候字串已經結束的。當直接使用字串賦值的時候程式會自動加上”\0”作為結束符。
// // main.c // ArrayAndString // // Created by KenshinCui on 14-7-06. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #include <stdio.h> int main(int argc, const char * argv[]) { char a[] = {'K','e','n','s','h','i','n','\0'}; printf("%s",a); //結果:Kenshin,注意使用%s輸出字串內容,如果換成整形輸出格式其實輸出的是a的地址 printf("\n"); printf("address=%x", a); //結果:address=5fbff890 printf("\n"); //後面的\0絕對不能省略,如果沒有\0則會出現如下情況 char b[] = { 'I', 'a', 'm'}; printf("%s",b); //沒有按照期望輸出,多了一些垃圾資料,在當前環境列印結果:IamKenshin printf("\n"); printf("address=%x",b); //結果:address=5fbff88d printf("\n"); //直接賦值為字串,此時不需要手動新增\0,編譯器會自動新增 char c[] = "Kenshin"; printf("c=%s",c); //結果:c=Kenshin printf("\n"); //二維陣列儲存多個字串 char d[2][3]={"Kenshin","Kaoru","Rose","Jack","Tom","Jerry"}; return 0; }
從上面程式碼註釋中可以看到列印b的時候不是直接列印出來“Iam”而是列印出了“IamKenshin”,原因就是編譯器無法判斷字串是否結束,要解釋為什麼列印出“IamKenshin”我們需要了解a和b在記憶體中的儲存。
從圖中我們不難發現由於a佔用8個位元組,而定義完a後直接定義了b,此時分配的空間連續,b佔用3個位元組,這樣當輸出b的時候由於輸出完“Iam”之後並未遇到”\0”標記,程式繼續輸出直到遇到陣列a中的“\0”才結束,因此輸出內容為“IamKenshin”。
擴充套件--字串操作常用函式
下面簡單看一下和字元和字串相關的常用的幾個函式
// // main.c // ArrayAndString // // Created by Kenshin Cui on 14-7-04. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #include <stdio.h> int main(int argc, const char * argv[]) { /*字元操作*/ putchar('a'); //結果:a,putchar一次只能輸出一個字元 printf("\n"); putchar(97);//結果:a printf("\n"); char a; a=getchar();//getchar()一次只能接收一個字元,可以接收空格、tab、回車 printf("a=%c",a); printf("\n"); /*字串操作*/ char b[]="Kenshin"; printf("b=%s",b); printf("\n"); puts(b); //puts用於輸出單個字串,不能像printf格式化輸出,會自動新增換行 printf("\n"); char c[10]; scanf("%s",c);//注意c沒必要寫成&c,因為c本身就代表了陣列的地址 printf("c=%s\n",c);//注意即使你輸入的內容大於10,也能正確輸出,但是下面的gets()函式卻不行 printf("\n"); //gets()函式,注意它是不安全的,因為接收的時候不知道它的大小容易造成溢位,建議不要使用 char d[10]; gets(d); //gets一次只能接收一個字串,但是scanf可接收多個;scanf不能接收空格、tab,gets則可以 printf("d=%s",d); printf("\n"); char e[]={'K','s','\0'}; printf("%lu",strlen(e)); //結果是:2,不是3,因為\0不計入長度 printf("\n"); char f[]={"Kenshin"}; printf("%lu",strlen(f)); //結果是:7 printf("\n"); char g[5]; strcpy(g,"hello,world!"); printf("%s",g); //結果是:hello,即使定義的g長度為5,但是也能完全拷貝進去 printf("\n"); char h[5]; char i[]={'a','b','c','\0','d','e','f','\0'}; strcpy(h,i); printf("%s",h); //結果是:abc,遇到第一個\0則結束 printf("\n"); strcat(i,"ghi"); printf("%s",i); //結果是:abcghi,注意不是abcdefghi,strcat,從i第一\0開始使用“ghi”覆蓋,覆蓋完之後加上一個\0,在記憶體中目前應該是:{'a','b','c','g','h','i','\0','f','\0'} printf("\n"); char j[]="abc"; char k[]="aBc"; char l[]="acb"; char m[]={'a','\0'}; printf("%d,%d,%d",strcmp(j,k),strcmp(k,l),strcmp(l,m));//遇到第一個不相同的字元或\0則返回兩者前後之差,結果:32,-33,99 printf("\n"); return 0; }
注意:
1.在Xcode中會提示gets是不安全的,推薦使用fgets()。
2.strlen()只用於計算字串長度,由於在C語言中字串使用字元陣列長度表示,所以它可以計算帶有’\0’結尾的字元陣列長度,但是它並不能計算其他型別的陣列長度。