第五章 數組和字符串
5.1 概述
在程序設計中,爲了方便處理數據把具有相同類型的若干變量按有序形式組織起來——稱爲數組。
數組就是在內存中連續的相同類型的變量空間。同一個數組所有的成員都是相同的數據類型,同時所有的成員在內存中的地址是連續的。
數組屬於構造數據類型:
- 一個數組可以分解爲多個數組元素:這些數組元素可以是基本數據類型或構造類型。
int a[10];
struct Stu boy[10];
- 按數組元素類型的不同,數組可分爲:數值數組、字符數組、指針數組、結構數組等類別。
int a[10];
char s[10];
char *p[10];
通常情況下,數組元素下標的個數也稱爲維數,根據維數的不同,可將數組分爲一維數組、二維數組、三維數組、四維數組等。通常情況下,我們將二維及以上的數組稱爲多維數組。
5.2 一維數組
5.2.1 一維數組的定義和使用
- 數組名字符合標識符的書寫規定(數字、英文字母、下劃線)
- 數組名不能與其它變量名相同,同一作用域內是唯一的
- 方括號[]中常量表達式表示數組元素的個數
int a[3]表示數組a有3個元素
其下標從0開始計算,因此3個元素分別爲a[0],a[1],a[2]
- 定義數組時[]內最好是常量,使用數組時[]內即可是常量,也可以是變量
#include <stdio.h>
int main()
{
int a[10];//定義了一個數組,名字叫a,有10個成員,每個成員都是int類型
//a[0]…… a[9],沒有a[10]
//沒有a這個變量,a是數組的名字,但不是變量名,它是常量
a[0] = 0;
//……
a[9] = 9;
int i = 0;
for (i = 0; i < 10; i++)
{
a[i] = i; //給數組賦值
}
//遍歷數組,並輸出每個成員的值
for (i = 0; i < 10; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
5.2.2 一維數組的初始化
在定義數組的同時進行賦值,稱爲初始化。全局數組若不初始化,編譯器將其初始化爲零。局部數組若不初始化,內容爲隨機值。
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//定義一個數組,同時初始化所有成員變量
int a[10] = { 1, 2, 3 };//初始化前三個成員,後面所有元素都設置爲0
int a[10] = { 0 };//所有的成員都設置爲0
//[]中不定義元素個數,定義時必須初始化
int a[] = { 1, 2, 3, 4, 5 };//定義了一個數組,有5個成員
5.2.3 數組名
數組名是一個地址的常量,代表數組中首元素的地址。
#include <stdio.h>
int main()
{
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//定義一個數組,同時初始化所有成員變量
printf("a = %p\n", a);
printf("&a[0] = %p\n", &a[0]);
int n = sizeof(a); //數組佔用內存的大小,10個int類型,10 * 4 = 40
int n0 = sizeof(a[0]);//數組第0個元素佔用內存大小,第0個元素爲int,4
int i = 0;
for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
5.2.4 強化訓練
1.一維數組的最值
#include <stdio.h>
int main()
{
int a[] = { 1, -2, 3,- 4, 5, -6, 7, -8, -9, 10 };//定義一個數組,同時初始化所有成員變量
int i = 0;
int max = a[0];
for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
if (a[i] > max)
{
max = a[i];
}
}
printf("數組中最大值爲:%d\n", max);
return 0;
}
2.一維數組的逆置
#include <stdio.h>
int main()
{
int a[] = { 1, -2, 3,- 4, 5, -6, 7, -8, -9, 10 };//定義一個數組,同時初始化所有成員變量
int i = 0;
int j = sizeof(a) / sizeof(a[0]) -1;
int tmp;
while (i < j)
{
tmp = a[i];
a[i] = a[j];
a[j] = tmp;
i++;
j--;
}
for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
3.冒泡法排序
#include <stdio.h>
int main()
{
int a[] = { 1, -2, 3,- 4, 5, -6, 7, -8, -9, 10 };//定義一個數組,同時初始化所有成員變量
int i = 0;
int j = 0;
int n = sizeof(a) / sizeof(a[0]);
int tmp;
//1、流程
//2、試數
for (i = 0; i < n-1; i++)
{
for (j = 0; j < n - i -1 ; j++)//內循環的目的是比較相鄰的元素,把大的放到後面
{
if (a[j] > a[j + 1])
{
tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
}
}
}
for (i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
5.3二維數組
5.3.1 二維數組的定義和使用
二維數組定義的一般形式是:
類型說明符 數組名[常量表達式1][常量表達式2]
其中常量表達式1表示第一維下標的長度,常量表達式2 表示第二維下標的長度。
int a[3][4];
-
命名規則同一維數組
-
定義了一個三行四列的數組,數組名爲a其元素類型爲整型,該數組的元素個數爲3×4個,即:
二維數組a是按行進行存放的,先存放a[0]行,再存放a[1]行、a[2]行,並且每行有四個元素,也是依次存放的。
- 二維數組在概念上是二維的:其下標在兩個方向上變化,對其訪問一般需要兩個下標。
- 在內存中並並存在二維數組,二維數組實際的硬件存儲器是連續編址的,也就是說內存中只有一維數組,即放完一行之後順次放入第二行,和一維數組存放方式是一樣的。
#include <stdio.h>
int main()
{
//定義了一個二維數組,名字叫a
//由3個一維數組組成,這個一維數組是int [4]
//這3個一維數組的數組名分別爲a[0],a[1],a[2]
int a[3][4];
a[0][0] = 0;
//……
a[2][3] = 12;
//給數組每個元素賦值
int i = 0;
int j = 0;
int num = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
a[i][j] = num++;
}
}
//遍歷數組,並輸出每個成員的值
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("%d, ", a[i][j]);
}
printf("\n");
}
return 0;
}
5.3.2 二維數組的初始化
//分段賦值 int a[3][4] = {{ 1, 2, 3, 4 },{ 5, 6, 7, 8, },{ 9, 10, 11, 12 }};
int a[3][4] =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8, },
{ 9, 10, 11, 12 }
};
//連續賦值
int a[3][4] = { 1, 2, 3, 4 , 5, 6, 7, 8, 9, 10, 11, 12 };
//可以只給部分元素賦初值,未初始化則爲0
int a[3][4] = { 1, 2, 3, 4 };
//所有的成員都設置爲0
int a[3][4] = {0};
//[]中不定義元素個數,定義時必須初始化
int a[][4] = { 1, 2, 3, 4, 5, 6, 7, 8};
5.3.3 數組名
數組名是一個地址的常量,代表數組中首元素的地址。
#include <stdio.h>
int main()
{
//定義了一個二維數組,名字叫a
//二維數組是本質上還是一維數組,此一維數組有3個元素
//每個元素又是一個一維數組int[4]
int a[3][4] = { 1, 2, 3, 4 , 5, 6, 7, 8, 9, 10, 11, 12 };
//數組名爲數組首元素地址,二維數組的第0個元素爲一維數組
//第0個一維數組的數組名爲a[0]
printf("a = %p\n", a);
printf("a[0] = %p\n", a[0]);
//測二維數組所佔內存空間,有3個一維數組,每個一維數組的空間爲4*4
//sizeof(a) = 3 * 4 * 4 = 48
printf("sizeof(a) = %d\n", sizeof(a));
//測第0個元素所佔內存空間,a[0]爲第0個一維數組int[4]的數組名,4*4=16
printf("sizeof(a[0]) = %d\n", sizeof(a[0]) );
//測第0行0列元素所佔內存空間,第0行0列元素爲一個int類型,4字節
printf("sizeof(a[0][0]) = %d\n", sizeof(a[0][0]));
//求二維數組行數
printf("i = %d\n", sizeof(a) / sizeof(a[0]));
// 求二維數組列數
printf("j = %d\n", sizeof(a[0]) / sizeof(a[0][0]));
//求二維數組行*列總數
printf("n = %d\n", sizeof(a) / sizeof(a[0][0]));
return 0;
}
5.3.4 強化訓練
#include <stdio.h>
int main()
{
//二維數組: 五行、三列
//行代表人: 老大到老五
//列代表科目:語、數、外
float a[5][3] = { { 80, 75, 56 }, { 59, 65, 71 }, { 59, 63, 70 }, { 85, 45, 90 }, { 76, 77, 45 } };
int i, j, person_low[3] = { 0 };
float s = 0, lesson_aver[3] = { 0 };
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
s = s + a[j][i];
if (a[j][i] < 60)
{
person_low[i]++;
}
}
lesson_aver[i] = s / 5;
s = 0;
}
printf("各科的平均成績:\n");
for (i = 0; i < 3; i++)
{
printf("%.2f\n", lesson_aver[i]);
}
printf("各科不及格的人數:\n");
for (i = 0; i < 3; i++)
{
printf("%d\n", person_low[i]);
}
return 0;
}
5.4多維數組(瞭解)
多維數組的定義與二維數組類似,其語法格式具體如下:
數組類型修飾符 數組名 [n1][n2]…[nn];
int a[3][4][5];
定義了一個三維數組,數組的名字是a,數組的長度爲3,每個數組的元素又是一個二維數組,這個二維數組的長度是4,並且這個二維數組中的每個元素又是一個一維數組,這個一維數組的長度是5,元素類型是int。
#include <stdio.h>
int main()
{
//int a[3][4][5] ;//定義了一個三維數組,有3個二維數組int[4][5]
int a[3][4][5] = { { { 1, 2, 3, 4, 5 }, { 6, 7, 8, 9, 10 }, { 0 }, { 0 } }, { { 0 }, { 0 }, { 0 }, { 0 } }, { { 0 }, { 0 }, { 0 }, { 0 } } };
int i, j, k;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
for (k = 0; k < 5; k++)
{
//添加訪問元素代碼
printf("%d, ", a[i][j][k]);
}
printf("\n");
}
}
return 0;
}
5.5 字符數組與字符串
5.5.1 字符數組與字符串區別
- C語言中沒有字符串這種數據類型,可以通過char的數組來替代;
- 字符串一定是一個char的數組,但char的數組未必是字符串;
- 數字0(和字符‘\0’等價)結尾的char數組就是一個字符串,但如果char數組沒有以數字0結尾,那麼就不是一個字符串,只是普通字符數組,所以字符串是一種特殊的char的數組。
#include <stdio.h>
int main()
{
char c1[] = { 'c', ' ', 'p', 'r', 'o', 'g' }; //普通字符數組
printf("c1 = %s\n", c1); //亂碼,因爲沒有’\0’結束符
//以‘\0’(‘\0’就是數字0)結尾的字符數組是字符串
char c2[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0'};
printf("c2 = %s\n", c2);
//字符串處理以‘\0’(數字0)作爲結束符,後面的'h', 'l', 'l', 'e', 'o'不會輸出
char c3[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0', 'h', 'l', 'l', 'e', 'o', '\0'};
printf("c3 = %s\n", c3);
return 0;
}
5.5.2 字符串的初始化
#include <stdio.h>
// C語言沒有字符串類型,通過字符數組模擬
// C語言字符串,以字符‘\0’, 數字0
int main()
{
//不指定長度, 沒有0結束符,有多少個元素就有多長
char buf[] = { 'a', 'b', 'c' };
printf("buf = %s\n", buf); //亂碼
//指定長度,後面沒有賦值的元素,自動補0
char buf2[100] = { 'a', 'b', 'c' };
printf("buf2 = %s\n", buf2);
//所有元素賦值爲0
char buf3[100] = { 0 };
//char buf4[2] = { '1', '2', '3' };//數組越界
char buf5[50] = { '1', 'a', 'b', '0', '7' };
printf("buf5 = %s\n", buf5);
char buf6[50] = { '1', 'a', 'b', 0, '7' };
printf("buf6 = %s\n", buf6);
char buf7[50] = { '1', 'a', 'b', '\0', '7' };
printf("buf7 = %s\n", buf7);
//使用字符串初始化,編譯器自動在後面補0,常用
char buf8[] = "agjdslgjlsdjg";
//'\0'後面最好不要連着數字,有可能幾個數字連起來剛好是一個轉義字符
//'\ddd'八進制字義字符,'\xdd'十六進制轉移字符
// \012相當於\n
char str[] = "\012abc";
printf("str == %s\n", str);
return 0;
}
5.5.3 字符串的輸入輸出
由於字符串採用了’\0’標誌,字符串的輸入輸出將變得簡單方便。
#include <stdio.h>
int main()
{
char str[100];
printf("input string1 : \n");
scanf("%s", str);//scanf(“%s”,str)默認以空格分隔
printf("output:%s\n", str);
return 0;
}
5.5.4 強化訓練:字符串追加
#include <stdio.h>
int main()
{
char str1[] = "abcdef";
char str2[] = "123456";
char dst[100];
int i = 0;
while (str1[i] != 0)
{
dst[i] = str1[i];
i++;
}
int j = 0;
while (str2[j] != 0)
{
dst[i + j] = str2[j];
j++;
}
dst[i + j] = 0; //字符串結束符
printf("dst = %s\n", dst);
return 0;
}
5.5.5 函數的調用:產生隨機數
當調用函數時,需要關心5要素:
- 頭文件:包含指定的頭文件
- 函數名字:函數名字必須和頭文件聲明的名字一樣
- 功能:需要知道此函數能幹嘛後才調用
- 參數:參數類型要匹配
- 返回值:根據需要接收返回值
#include <time.h>
time_t time(time_t *t);
功能:獲取當前系統時間
參數:常設置爲NULL
返回值:當前系統時間, time_t 相當於long類型,單位爲毫秒
#include <stdlib.h>
void srand(unsigned int seed);
功能:用來設置rand()產生隨機數時的隨機種子
參數:如果每次seed相等,rand()產生隨機數相等
返回值:無
#include <stdlib.h>
int rand(void);
功能:返回一個隨機數值
參數:無
返回值:隨機數
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main()
{
time_t tm = time(NULL);//得到系統時間
srand((unsigned int)tm);//隨機種子只需要設置一次即可
int r = rand();
printf("r = %d\n", r);
return 0;
}
5.5.6 字符串處理函數
1.gets()
#include <stdio.h>
char *gets(char *s);
功能:從標準輸入讀入字符,並保存到s指定的內存空間,直到出現換行符或讀到文件結尾爲止。
參數:
s:字符串首地址
返回值:
成功:讀入的字符串
失敗:NULL
gets(str)與scanf(“%s”,str)的區別:
gets(str)
允許輸入的字符串含有空格scanf(“%s”,str)
不允許含有空格
注意:由於scanf()
和gets()
無法知道字符串s大小,必須遇到換行符或讀到文件結尾爲止才接收輸入,因此容易導致字符數組越界(緩衝區溢出)的情況。
char str[100];
printf("請輸入str: ");
gets(str);
printf("str = %s\n", str);
2.fgets()
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
功能:從stream指定的文件內讀入字符,保存到s所指定的內存空間,直到出現換行字符、讀到文件結尾或是已讀了size - 1個字符爲止,最後會自動加上字符 '\0' 作爲字符串結束。
參數:
s:字符串
size:指定最大讀取字符串的長度(size - 1)
stream:文件指針,如果讀鍵盤輸入的字符串,固定寫爲stdin
返回值:
成功:成功讀取的字符串
讀到文件尾或出錯: NULL
fgets()在讀取一個用戶通過鍵盤輸入的字符串的時候,同時把用戶輸入的回車也做爲字符串的一部分。通過scanf和gets輸入一個字符串的時候,不包含結尾的“\n”,但通過fgets結尾多了“\n”。fgets()函數是安全的,不存在緩衝區溢出的問題。
char str[100];
printf("請輸入str: ");
fgets(str, sizeof(str), stdin);
printf("str = \"%s\"\n", str);
3.puts()
#include <stdio.h>
int puts(const char *s);
功能:標準設備輸出s字符串,在輸出完成後自動輸出一個'\n'。
參數:
s:字符串首地址
返回值:
成功:非負數
失敗:-1
#include <stdio.h>
int main()
{
printf("hello world");
puts("hello world");
return 0;
}
4.fputs()
#include <stdio.h>
int fputs(const char * str, FILE * stream);
功能:將str所指定的字符串寫入到stream指定的文件中, 字符串結束符 '\0' 不寫入文件。
參數:
str:字符串
stream:文件指針,如果把字符串輸出到屏幕,固定寫爲stdout
返回值:
成功:0
失敗:-1
fputs()
是puts()
的文件操作版本,但fputs()
不會自動輸出一個’\n’。
printf("hello world");
puts("hello world");
fputs("hello world", stdout);
5.strlen()
#include <string.h>
size_t strlen(const char *s);
功能:計算指定指定字符串s的長度,不包含字符串結束符‘\0’
參數:
s:字符串首地址
返回值:字符串s的長度,size_t爲unsigned int類型
char str[] = "abcdefg";
int n = strlen(str);
printf("n = %d\n", n);
6.strcpy()
#include <string.h>
char *strcpy(char *dest, const char *src);
功能:把src所指向的字符串複製到dest所指向的空間中,'\0'也會拷貝過去
參數:
dest:目的字符串首地址
src:源字符首地址
返回值:
成功:返回dest字符串的首地址
失敗:NULL
注意:如果參數
dest
所指的內存空間不夠大,可能會造成緩衝溢出的錯誤情況。
char dest[20] = "123456789";
char src[] = "hello world";
strcpy(dest, src);
printf("%s\n", dest);
7.strncpy()
#include <string.h>
char *strncpy(char *dest, const char *src, size_t n);
功能:把src指向字符串的前n個字符複製到dest所指向的空間中,是否拷貝結束符看指定的長度是否包含'\0'。
參數:
dest:目的字符串首地址
src:源字符首地址
n:指定需要拷貝字符串個數
返回值:
成功:返回dest字符串的首地址
失敗:NULL
char dest[20] ;
char src[] = "hello world";
strncpy(dest, src, 5);
printf("%s\n", dest);
dest[5] = '\0';
printf("%s\n", dest);
8.strcat()
#include <string.h>
char *strcat(char *dest, const char *src);
功能:將src字符串連接到dest的尾部,‘\0’也會追加過去
參數:
dest:目的字符串首地址
src:源字符首地址
返回值:
成功:返回dest字符串的首地址
失敗:NULL
char str[20] = "123";
char *src = "hello world";
printf("%s\n", strcat(str, src));
9.strncat()
#include <string.h>
char *strncat(char *dest, const char *src, size_t n);
功能:將src字符串前n個字符連接到dest的尾部,‘\0’也會追加過去
參數:
dest:目的字符串首地址
src:源字符首地址
n:指定需要追加字符串個數
返回值:
成功:返回dest字符串的首地址
失敗:NULL
char str[20] = "123";
char *src = "hello world";
printf("%s\n", strncat(str, src, 5));
10.strcmp()
#include <string.h>
int strcmp(const char *s1, const char *s2);
功能:比較 s1 和 s2 的大小,比較的是字符ASCII碼大小。
參數:
s1:字符串1首地址
s2:字符串2首地址
返回值:
相等:0
大於:>0
小於:<0
char *str1 = "hello world";
char *str2 = "hello mike";
if (strcmp(str1, str2) == 0)
{
printf("str1==str2\n");
}
else if (strcmp(str1, str2) > 0)
{
printf("str1>str2\n");
}
else
{
printf("str1<str2\n");
}
11.strncmp()
#include <string.h>
int strncmp(const char *s1, const char *s2, size_t n);
功能:比較 s1 和 s2 前n個字符的大小,比較的是字符ASCII碼大小。
參數:
s1:字符串1首地址
s2:字符串2首地址
n:指定比較字符串的數量
返回值:
相等:0
大於: > 0
小於: < 0
char *str1 = "hello world";
char *str2 = "hello mike";
if (strncmp(str1, str2, 5) == 0)
{
printf("str1==str2\n");
}
else if (strcmp(str1, "hello world") > 0)
{
printf("str1>str2\n");
}
else
{
printf("str1<str2\n");
}
12.sprintf()
#include <stdio.h>
int sprintf(char *_CRT_SECURE_NO_WARNINGS, const char *format, ...);
功能:根據參數format字符串來轉換並格式化數據,然後將結果輸出到str指定的空間中,直到出現字符串結束符 '\0' 爲止。
參數:
str:字符串首地址
format:字符串格式,用法和printf()一樣
返回值:
成功:實際格式化的字符個數
失敗: - 1
char dst[100] = { 0 };
int a = 10;
char src[] = "hello world";
printf("a = %d, src = %s", a, src);
printf("\n");
int len = sprintf(dst, "a = %d, src = %s", a, src);
printf("dst = \" %s\"\n", dst);
printf("len = %d\n", len);
13.sscanf()
#include <stdio.h>
int sscanf(const char *str, const char *format, ...);
功能:從str指定的字符串讀取數據,並根據參數format字符串來轉換並格式化數據。
參數:
str:指定的字符串首地址
format:字符串格式,用法和scanf()一樣
返回值:
成功:參數數目,成功轉換的值的個數
失敗: - 1
char src[] = "a=10, b=20";
int a;
int b;
sscanf(src, "a=%d, b=%d", &a, &b);
printf("a:%d, b:%d\n", a, b);
14.strchr()
#include <string.h>
char *strchr(const char *s, int c);
功能:在字符串s中查找字母c出現的位置
參數:
s:字符串首地址
c:匹配字母(字符)
返回值:
成功:返回第一次出現的c地址
失敗:NULL
char src[] = "ddda123abcd";
char *p = strchr(src, 'a');
printf("p = %s\n", p);
15.strstr()
#include <string.h>
char *strstr(const char *haystack, const char *needle);
功能:在字符串haystack中查找字符串needle出現的位置
參數:
haystack:源字符串首地址
needle:匹配字符串首地址
返回值:
成功:返回第一次出現的needle地址
失敗:NULL
char src[] = "ddddabcd123abcd333abcd";
char *p = strstr(src, "abcd");
printf("p = %s\n", p);
16.strtok()
#include <string.h>
char *strtok(char *str, const char *delim);
功能:來將字符串分割成一個個片段。當strtok()在參數s的字符串中發現參數delim中包含的分割字符時, 則會將該字符改爲\0 字符,當連續出現多個時只替換第一個爲\0。
參數:
str:指向欲分割的字符串
delim:爲分割字符串中包含的所有字符
返回值:
成功:分割後字符串首地址
失敗:NULL
- 在第一次調用時:
strtok()
必需給予參數s
字符串 - 往後的調用則將
參數s
設置成NULL
,每次調用成功則返回指向被分割出片段的指針
char a[100] = "adc*fvcv*ebcy*hghbdfg*casdert";
char *s = strtok(a, "*");//將"*"分割的子串取出
while (s != NULL)
{
printf("%s\n", s);
s = strtok(NULL, "*");
}
17.atoi()
#include <stdlib.h>
int atoi(const char *nptr);
功能:atoi()會掃描nptr字符串,跳過前面的空格字符,直到遇到數字或正負號纔開始做轉換,而遇到非數字或字符串結束符('\0')才結束轉換,並將結果返回返回值。
參數:
nptr:待轉換的字符串
返回值:成功轉換後整數
類似的函數有:
atof()
:把一個小數形式的字符串轉化爲一個浮點數。atol()
:將一個字符串轉化爲long
類型
char str1[] = "-10";
int num1 = atoi(str1);
printf("num1 = %d\n", num1);
char str2[] = "0.123";
double num2 = atof(str2);
printf("num2 = %lf\n", num2);