C語言之數(shù)組
——TechZone(Harris)
學完了前面幾種基礎語法之后,你可能會漸漸發(fā)現(xiàn),現(xiàn)有的數(shù)據(jù)的記錄方式龟再,已經(jīng)無法很好地解決我們接下來要解決的問題。比如有一天尼变,老師找你計算一下全班同學的平均成績利凑。那么你就會開始思考如何存儲全班的成績。按照之前學習的知識嫌术,我們可以定義a1
a2
a3
……
哀澈。但是,這樣未免也太麻煩了度气,如果要記錄全省人民的身高數(shù)據(jù)呢割按?如果下個學期班里學生人數(shù)有變化呢?
你會發(fā)現(xiàn)磷籍,種種原因适荣,導致了我們編寫的程序很繁雜现柠,不夠靈活。那么這樣束凑,編程也就沒有太大的必要了晒旅。好在,C語言提供了一種存儲數(shù)據(jù)的方式汪诉,叫做數(shù)組废恋。
數(shù)組,就是存放一堆同類型的數(shù)據(jù)的容器扒寄。比如剛剛的例子鱼鼓,我們要存儲學生的成績,這時候數(shù)組就可以大顯神威了该编。
定義一維數(shù)組
定義一維數(shù)組的方法很簡單迄本,只需要指定元素的類型和存放的數(shù)量即可。
類型 數(shù)組名[元素個數(shù)]
比如:
int Score1[50];//定義一個叫Score1的整型數(shù)組课竣,有50個元素
float Score2[30];//定義一個叫Score2的浮點型數(shù)組嘉赎,有30個元素
double Score3[20];//定義一個叫Score3的雙精度浮點型數(shù)組,有20個元素
char Str[10];//定義一個叫Str的字符型數(shù)組于樟,有10個元素
數(shù)組一旦被定義公条,在其生命周期內,就不可能被改變(其實是在內存中開辟了一段連續(xù)的空間了)迂曲。
訪問數(shù)組
訪問數(shù)組的方法和定義有點類似靶橱,但是如果混淆了的話,那可就不是什么好事了路捧。
數(shù)組名[下標]
方括號里面的关霸,實際上是指的數(shù)組的下標,也可以叫索引杰扫。需要注意的是队寇,下標的計數(shù)是從0
開始的,最大的下標是(元素個數(shù)-1)
涉波。也就是說英上,如果我int Score[10]
之后,那么我想訪問第一個元素啤覆,就是這樣Score[0]
苍日,如果要訪問最后一個元素,就是Score[9]
窗声。
之前看到過一個段子相恃,大概意思是說程序員數(shù)數(shù)都喜歡從0開始數(shù)。如果你是剛剛接觸編程笨觅,那么你也要開始習慣從0開始計數(shù)的這種思路拦耐。
其實耕腾,并不是C語言才開始有數(shù)組,F(xiàn)ORTRAN語言就有數(shù)組了杀糯。但是下標從0開始計數(shù)這種方式扫俺,是從C語言才開始有的。當時開發(fā)C語言編譯器的人們就想讓編譯器能夠更加簡單固翰,如果從0開始狼纬,那么編譯器實際上能夠少做很多事情,于是就多了這么一個設定骂际。隨著計算機科學的發(fā)展疗琉,后面出現(xiàn)的優(yōu)秀的語言也越來越多。但是我們所說的"C-Like”語言歉铝,也就是參照C語言來開發(fā)的語言盈简,也都繼承了C語言這一“優(yōu)良傳統(tǒng)”,因此就有了程序員數(shù)數(shù)是從0開始的這么一種說法太示。
講到這里柠贤,想必大家也就會明白之前為什么我們在循環(huán)的時候,初始值都是設定為0的了类缤。像這樣:
for (i = 0; i < 10; i++)
{
...
}
而不是像這樣寫(當然也沒錯):
for (i = 1; i <= 10; i++)
{
...
}
這就是因為种吸,我們在使用循環(huán)的時候,經(jīng)常會配合數(shù)組一起來使用呀非,那么我們循環(huán)設置成和數(shù)組下標的計數(shù)方法一樣,有利于我們使用數(shù)組镜盯。
還是回到我們最初的那個問題岸裙,存儲班里面學生的成績,然后計算出平均值:
//Example 01
#include <stdio.h>
int main(void)
{
int s[10];//假定我們班上有10個人
int i;
double sum = 0;
for (i = 0; i < 10; i++)
{
printf("請輸入第 %d 位同學的成績:", i + 1);
scanf("%d", &s[i]);
sum += s[i];
}
printf("成績錄入完畢速缆,該次考試的平均分是:%.2f\n", sum/10);
return 0;
}
程序實現(xiàn)如下:
//Consequence 01
請輸入第 1 位同學的成績:80
請輸入第 2 位同學的成績:90
請輸入第 3 位同學的成績:70
請輸入第 4 位同學的成績:66
請輸入第 5 位同學的成績:77
請輸入第 6 位同學的成績:54
請輸入第 7 位同學的成績:67
請輸入第 8 位同學的成績:86
請輸入第 9 位同學的成績:78
請輸入第 10 位同學的成績:65
成績錄入完畢降允,該次考試的平均分是:73.30
數(shù)組的初始化
在定義數(shù)組的時候同時對其各個元素進行賦值,稱為數(shù)組的初始化艺糜。在剛剛的代碼中剧董,我們定義了數(shù)組,但卻沒有在定義的時候就初始化破停,而是在循環(huán)中進行賦值翅楼。那么初始化數(shù)組一般有下面幾種方法:
-
將數(shù)組中所有的元素初始化為0,可以這么寫:
int a[10] = {0};
-
如果要賦予不同的值真慢,用逗號分開即可:
int a[5] = {1, 2, 3, 4, 5};
-
給部分元素賦值毅臊,剩下的自動初始化為0:
int a[10] = {1, 2 ,3};//剩下的全部為0
-
也可以偷懶只給出每個元素的值,讓編譯器自己判斷數(shù)組長度:
int a[] = {1, 2, 3, 4, 5};
-
C99中增加了一種特性黑界,指定元素進行賦值管嬉,剩下的自動初始化為0皂林。也就是說,可以針對不連續(xù)的幾個元素賦值:
int a[10] = { [3] = 3, [5] = 5, [8] = 8 };//編譯的時候記得加上-std=c99選項
那可能你會說了蚯撩,”你剛剛提到的一個問題還沒解決呢础倍!要是班里的人數(shù)變了怎么辦呢?“
沒錯胎挎,我們現(xiàn)在就來解決下這個問題沟启。
可變長數(shù)組
在C99標準推出之前,要求定義數(shù)組的時候呀癣,數(shù)組的維度必須是常量表達式或者const常量美浦,但是C99標準中,支持了變量定義數(shù)組项栏,那么浦辨,我們就可以將第一次的代碼改成這樣:
//Example 02
#include <stdio.h>
int main(void)
{
int Member;
printf("請輸入班級人數(shù):");
scanf("%d", &Member);
int s[Member];//使用用戶輸入的值來確定數(shù)組的大小
int i;
float sum = 0;
for (i = 0; i < Member; i++)
{
printf("請輸入第 %d 位同學的成績:", i + 1);
scanf("%d", &s[i]);
sum += s[i];
}
printf("成績錄入完畢,該次考試的平均分是:%.2f\n", sum/Menber);
return 0;
}
這樣沼沈,在開始存儲成績之前流酬,先讓使用者告訴程序班里有多少學生,該開辟多大的數(shù)組列另,然后就完美解決了人數(shù)變動的問題芽腾。
注意,這里的”可變長數(shù)組“是指的數(shù)組在程序運行的時候才確定長度页衙,也就是說每一次運行都不一定一樣摊滔。但是數(shù)組一旦被創(chuàng)建,在其生命周期內就不會再改變了店乐,這是數(shù)組的根本特性艰躺。
但是,如果有的同學使用的是Visual Studio的話眨八,是不支持C99的這個特性的(我也不知道為什么巨硬不支持腺兴,明明這么好的特性),那么就只能使用動態(tài)分配的方法來創(chuàng)建數(shù)組廉侧。放在這里來講的話有些超綱页响,后面會講到。
字符型數(shù)組
還記得之前說過段誊,C語言是沒有字符串這種類型的闰蚕。那么C語言處理字符串有兩種方法:字符串常量和字符型數(shù)組。字符串常量是指用雙引號括起來的字符串连舍,一旦確定下來就無法改變陪腌。一般我們會更多地傾向于使用更加靈活的字符型數(shù)組。這樣,數(shù)組中的每一個元素表示一個字符诗鸭,當然還要多一位來表示\0
染簇。
那么接下來就講講字符串的一些方法,因為字符串實在是太重要了强岸。
獲取字符串的長度
計算字符串的長度使用strlen
函數(shù)(這是長度锻弓,不是尺寸),這個函數(shù)包含在string.h
中
#include <string.h>
...
size_t strlen ( const char * str );
這個方法是不包含字符串末尾的\0
的。且看下面的例子:
//Example 03
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[] = "I love Clang!";
printf("sizeof str = %d\n", sizeof(str));
printf("strlen str = %u\n", strlen(str));
return 0;
}
運行結果如下:
//Consequence 03
sizeof str = 14
strlen str = 13
除了驗證不包含\0
以外蝌箍,我們還可以看到青灼,strlen
函數(shù)返回的是size_t
而不是int
。size_t
被定義在stddef.h
中妓盲,實際上就是無符號整型杂拨。
復制字符串
估計在第一次見到這個詞的時候,你的大腦浮現(xiàn)出來的就是使用賦值符號=
悯衬,但是弹沽,這是錯的……
字符串的復制應該使用strcpy
和strncpy
來實現(xiàn)。
#include <string.h>
...
char *strcpy (char *dest, const char *src);
char *strncpy (char *dest, const char *src, size_t n);
不多廢話筋粗,且看下面的例子:
//Example 04
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[] = "Original String";
char str2[] = "New String";
char str3[100];
strcpy(str1, str2);
strcpy(str3, "Successfully Copied");
printf("\
str1: %s\n\
str2: %s\n\
str3: %s\n", \
str1, str2, str3);
return 0;
}
運行結果如下:
//Consequence 04
str1: New String
str2: New String
str3: Successfully Copied
但是其實這個程序是有缺陷的策橘。
我們可以看到,兩個數(shù)組的長度其實不一樣娜亿,我們現(xiàn)在是把短的復制到長的里面丽已,那么不會有問題。如果上面的str1
和str2
對調一下买决,那么就極有可能出問題沛婴,這就是我們等會兒要講的數(shù)組越界問題。
那么如何解決復制時的這個隱式bug呢督赤?
使用strncpy方法來復制
如果超出的字符不是很多瘸味,那么程序有可能能夠成功地運行。但是如果兩者懸殊的話够挂,那編譯運行之后,程序會報Segmentation fault
藕夫。
因此在復制的時候孽糖,我們應該確保不越界,在復制之后不溢出毅贮。那么使用strncpy
函數(shù)办悟,由于增加了一個參數(shù)來指定復制的字符個數(shù),我們在編寫代碼的時候就可以規(guī)避這樣的問題滩褥。
舉個例子:
//Example 05
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[] = "TechZone was made by HarrisWilde";
char str2[40];
strncpy(str2, str1, 8);
str2[8] = '\0';
printf("%s\n", str2);
return 0;
}
結果如下:
//Consequence 05
TechZone
有一個地方要格外小心病蛉,strncpy
函數(shù)并不會在字符串的末尾添加\0
,因此在使用的時候要注意加上。
連接字符串
如果你想把一個字符串拼接到另一個后面的話铺然,就可以使用strcat
和strncat
兩個函數(shù)來實現(xiàn)俗孝。
#include <string.h>
...
char *strcat (char *dest, const char *src);
char *strncat (char *dest, const char *src, size_t n);
可以看到,這個函數(shù)的用法和上面復制字符串的用法完全相同魄健,strncat
也就是比strcat
多了一個指定復制長度的參數(shù)罷了赋铝。
需要注意的是,這個函數(shù)會自動在末尾追加一個\0
沽瘦,這和復制不一樣革骨,要特別注意區(qū)分。
比較字符串
比較兩個字符串析恋,也和上面的一樣良哲,有兩個類似的函數(shù),strcmp
和strncmp
助隧。
#include <string.h>
...
char *strcmp (char *dest, const char *src);
char *strncmp (char *dest, const char *src, size_t n);
采用這套函數(shù)來比較兩個字符串是否相同的時候筑凫,如果兩個字符串完全一致,那么返回的值為0
喇颁。這個函數(shù)的原理是漏健,從第一個字符開始,依次對比兩個字符串中每個字符的ASCII橘霎,如果第一個字符串的ASCII小于第二個字符串對應的字符蔫浆,那么返回一個小于0
的數(shù)值(通常是-1
),如果大于姐叁,那就會返回一個大于0
的值(通常是1
)瓦盛。
strncmp
則是增加了一個參數(shù),可以用來僅比較前面n個元素外潜。
//Example 06
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[10] = "TechZone";
char str2[20] = "TechZone";
if (!strcmp(str1, str2))
{
printf("Same!\n");
}
else
{
printf("Different!\n");
}
return 0;
}
運行結果為:
//Consequence 06
Same!
多維數(shù)組
有時候原环,使用數(shù)組來存儲還是不夠方便,比如处窥,老師讓你做一個全班全部科目的成績的分析嘱吗。如果利用我們剛剛所學習的數(shù)組知識,你可能會這么寫:
//Example 07
#include <stdio.h>
int main(void)
{
int chinese[50];
int math[50];
int English[50];
int science[50];
...
}
但是如果我們使用二維數(shù)組的話滔驾,那么只需要定義一次就行了谒麦。
假設我們有6科。
那么就這樣:
//Example 07 V2
#include <stdio.h>
int main(void)
{
int score[6][50];
...
}
這其實就像一個表格一樣哆致,二維數(shù)組通常也被稱為矩陣(matrix)绕德,將二維數(shù)組寫成行和列的表示形式,可以形象地幫我們解決一些問題摊阀。
訪問二維數(shù)組也和普通的數(shù)組一樣耻蛇,也是從0
開始計數(shù)的踪蹬,只不過下標隨著維度的變化會增加罷了(比如二維數(shù)組就有2個下標)。
二維數(shù)組的初始化
-
二維數(shù)組在內存中是線性存放的臣咖,因此可以將所有的數(shù)據(jù)寫在一個大括號內:
int a[2][3] = {1, 2, 3, 4, 5, 6};
這樣就是先將第一行的三個元素初始化跃捣,然后再初始化第二行的元素。
-
為了更直觀地表達我們可以這么寫:
int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
-
二維數(shù)組也可以僅對部分元素賦值:
int a[2][3] = {{1}, {4}};
這樣寫只是對各行的第一列元素賦值亡哄,其余的全部為0.
-
如果希望全部為0枝缔,那么可以這么寫:
int a[2][3] = {0};
-
C99中增加的指定賦值的特性,這里也可以適用蚊惯。其余未被操作的元素為
0
愿卸。int a[2][3] = {[0][0] = 1, [1][2] = 6};
-
二維數(shù)組也可以偷懶,但是只有第一維度的元素個數(shù)可以不寫截型,其他的都要寫上:
int a[][3] = { {1, 2, 3}, {4, 5, 6} };
數(shù)組越界
我們剛剛說過了趴荸,我們在寫程序的時候,盡量要把越界的情況通過代碼的努力來規(guī)避宦焦。那么发钝,可能有的小伙伴比較感興趣,如果越界了波闹,會發(fā)生什么呢酝豪?
那好,咱們就來試試精堕。
//Example 08
#include <stdio.h>
void f();
int main(void)
{
f();
return 0;
}
void f()
{
int a[10];
a[10] = 0;//這里我們寫到了一個不存在的下標里面
}
我們來跑一下這個程序孵淘。
筆者使用的Visual Studio 2019給出了以下的錯誤提示:
//Consequence 08
Run-Time Check Failure #2 - Stack around the variable 'a' was corrupted.
它發(fā)現(xiàn)了我在寫入一個錯誤的地址。并且還給了我兩個warning:
警告 C6201 索引“10”超出了“0”至“9”的有效范圍(對于可能在堆棧中分配的緩沖區(qū)“a”)歹篓。
警告 C6386 寫入到“a”時緩沖區(qū)溢出: 可寫大小為“40”個字節(jié)瘫证,但可能寫入了“44”個字節(jié)。
如果我們像普通程序員一樣庄撮,不管代碼warning背捌,直接強制執(zhí)行,試試會發(fā)生什么洞斯。
為了更直觀體現(xiàn)毡庆,我們把代碼改成這樣:
//Example 08
#include <stdio.h>
void f();
int main(void)
{
f();
printf("Here\n");//我們加了這句,如果函數(shù)正常執(zhí)行完畢了烙如,就可以看到這個語句的輸出
return 0;
}
void f()
{
int a[10];
a[10] = 0;
}
還是出現(xiàn)了這句:
Run-Time Check Failure #2 - Stack around the variable 'a' was corrupted.
控制臺上面沒有看到Here
的輸出么抗,說明函數(shù)還沒有執(zhí)行完,程序就已經(jīng)崩潰了厅翔,根本沒辦法執(zhí)行到輸出。
但是搀突,為什么編譯器沒有給我error刀闷,而是給了我warning呢?
有的編譯器可能連warning都沒有。
實際上甸昏,我們在對a[10]
寫入的時候顽分,其實是成功了的。只不過我們把a[10]
寫在了一個不該寫的地方(實際上就是這段數(shù)組內存的后面)施蜜,干擾到了其他東西的運行卒蘸,程序就有可能會崩潰。如果后面的內存為空或者是沒有被回收的垃圾內存翻默,那么就沒關系缸沃,但是如果是有用的內存,出問題就很正常了修械。
有時候我們寫了一個程序趾牧,可能這次運行沒問題,下一次運行就出錯肯污,或者是在我的電腦上可以翘单,在你的電腦上就不行了等等,都有可能是數(shù)組越界蹦渣,或者是我們后面要學的指針出錯了哄芜。我們作為創(chuàng)造代碼的人,有責任通過代碼上的設計柬唯,來規(guī)避這樣的問題认臊,避免程序的崩潰。
長度為0的數(shù)組权逗?
有的同學可能會異想天開美尸,說,我可不可以定義一個長度為0的數(shù)組呢斟薇?
類似于這樣:
int a[0];
答案是师坎,完全沒問題!
不信的話可以去試試堪滨,編譯可以通過的胯陋,只不過這樣的數(shù)組不存在任何意義,因為沒有符合要求的下標袱箱。我們說遏乔,最大的下標就是元素個數(shù)-1,那么发笔,-1顯然不是一個合法的下標盟萨。所以這樣的操作可行,但是沒有任何意義了讨。
好了捻激,本節(jié)內容就到這里了制轰,希望你能夠從中有所收獲哦!