一. 为什么需要数组
为了解决大量同类型数据的存储和使用问题
为了模拟现实世界:
一维模拟:直线
二维模拟:平面
三维模拟:空间
四维模拟:空间+时间
...
后来都会使用以数组为内核,别人造的工具
数组的概念:
- 数组是一组有序数据的集合。数组中各数据的排列是有一定规律的,下标代表数据在数组中的序号
- 用一个数组名和一个下标来唯一确定数组中的元素
- 数组中的每一个元素都属于同一个数据类型
二. 数组的分类
2.1 一维数组
2.1.1 一位数组定义
指:数组中每一个元素只带一个下标的数组。
定义方式:类型说明符 数组名 [常量表达式]
eg:int a[10]; 定义一个整型数组,数组名为a,数组包含10个整型元素(这个10个元素是a[0]、a[1]、a[2]、a[3]、a[4]、a[5]、a[6]、a[7]、a[8]、a[9])
2.1.2 定义一维数组的要求
为n个变量连续分配存储空间
所有的变量的数据类型必须相同
所有变量所占的字节大小必须相同
2.1.3 一维数组元素的引用
引用形式:数组名[下标表达式]
一个数组元素实质上是一个变量名,代表一个内存中的一个存储单元,一个数组占据的是一连串连续的存储单元
引用数组元素时,数组的下标可以是整型常量,也可以是整型表达式
数组元素的引用,数组的起始元素下标为0
只能逐个引用数组元素而不能一次引用整个数组全部元素的值
举例1:一维数组元素的存储和打印
#include<stdio.h>
#define N 9
int main(void) {
int arr[N];
int i;
for (i = 0; i < N; i++) //注意数组下标最多为N-1,数组下标不能越界
{
arr[i] = i + 1; //一次只为一个数组元素赋值
printf("arr[%d]=%d\t", i, arr[i]); //赋值完一个元素就进行打印
if (0 == (i+1)%3) //每三个元素为一行
{
printf("\n");
}
}
return 0;
}
结果:
举例2:一维数组在内存中的存放形式
#include<stdio.h>
#define N 4
int main(void) {
int arr[N];
int i;
for (i = 0; i < N; i++)
{
arr[i] = i;
printf("&arr[%d]=%d\n", i, &arr[i]); //打印一维数组每个元素的地址
}
return 0;
}
结果:
在内存中存储:
2.1.4 一维数组的初始化
当数组定义后,系统会为该数组在内存中开辟一串连续的存储单元,但这些存储单元中并没有确定的值。可以在定义数组时为所包含的数组元素赋初值。
初始化:
完全初始化: int a[5] = {1,2,3,4,5};
不完全初始化: int a[5] = {1,2,3}; 只初始化前3个元素
不初始化: int a[5]; 所有元素都是垃圾值
清零: int a[5] = 0;
注:
只有在定义数组的时候才可以整体赋值
一维数组名a代表 a[]数组元素首地址
定义一维数组时的一对方括号中可以不指定数组大小,而通过赋初值来定义大小
举例1.使用数组计算平均数
# include <stdio.h>
int main(void)
{
int x;
double sum = 0;
int cnt;
int i=0;
scanf("%d", &cnt);
if(cnt>0)
{
int number[cnt]; //C99规定,一旦创建了数组,就不能改变他
scanf("%d", &x);
while(x!=-1)
{
cnt--;
number[cnt] = x;
sum += number[cnt];
i++;
scanf("%d",&x);
}
printf("%f\n",(sum*1.0)/i);;
}
return 0;
}
2.1.5 数组的注意事项
数组的大小
求所有数组所占字节:sizeof(a)
求一个数组所占字节数:sizeof(a[0])
求数组有多少个数组元素:sizeof(a)/sizeof(a[0])
数组的赋值
数组变量本身不能赋值
要把一个数组的所有元素交给另一个数组,必须采用遍历
遍历数组来赋值
for(i=0;i<length;i++)
b[i]=a[i];
2.2 二维数组
2.2.1 二维数组的定义
二维数组中元素排列的顺序是:按行存放,即在内存中先顺序存放第一行的元素,在存放第二行的元素。二维数组元素的存储总是占用一块连续的内存单元。
一般形式:类型说明符 数组名[常量表达式1][常量表达式2];
eg: float pay[3][6]; 定义一个float类型的二维数组,第一维有3个元素,第二维有6个元素
常量表达式1为第一维的长度,常量表达式2为第二维的长度。
通常在处理二维数组的时候,为了便于理解,都将数组视为一个矩阵,常量表达式1表示矩阵的行数,而常量表达式2表示矩阵的列数。
与一维数组一样,在定义二维数组时,常量表达式同样不能为变量。
2.2.2 二维数组元素的引用
表示形式:数组名[下标表达式1][下标表达式2];
数组元素可以出现在表达中,也可以被赋值。
举例1:二维数组的引用
#include<stdio.h>
#define M 4
#define N 3
int main() {
int arr[M][N]; //二维数组的定义
for (int i = 0; i < M; i++) //通过两层for循环来输出二维数组中每个元素的地址
{
for (int j = 0; j < N; j++)
{
printf("&arr[%d][%d]=%d\t", i, j, &arr[i][j]);
}
printf("\n");
}
return 0;
}
结果:
将二维数组arr视为一个矩阵,下图显示了数组中每个元素在矩阵中的存放位置。
二维数组在内存中的存储结构:
2.2.3 二维数组的初始化
在定义二维数组的同时给二维数组的各元素赋值
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}, {5,6,7}, {8,9}};
注:对于二维数组,只可以省略第一个方括号中的常量表达式,而不能省略第二个方括号中的常量表达式
为全部元素赋值的时候可以省去第一个常量
int[][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
2.3 多维数组
2.3.1 是否存在多维数组
不存在
因为内存是线性一维的
n维数组可以当做每个元素是n-1维数组的一维数组
例如:
int a[3][4]
该数组是含有3个元素的一维数组,只不过每个元素都可以再分成4个小元素
2.4 字符数组
字符型数据是以字符的ASCII代码存储在存储单元中的,一般占一个字节。由于ASCII代码也属于整数形式,因此在C99标准中,把字符类型归纳为整型类型中的一种
C语言中没有字符串类型(String),也没有字符串变量,字符串是存放在字符型数组中的
2.4.1 字符数组定义
字符数组就是数组的元素类型为字符型的数组。
特殊之处在于它是数组元素为字符的数组。其定义的一般形式和注意事项与之前讲解的一般数组类似,只是其中的类型说明符是char。当然,并不是说类型说明符只能是char,也可以是long、int等,但是由于char型只占用一个字节的大小,使用long型和int型来定义字符数组会造成资源的浪费,因此一般选择使用char型来定义字符数组。
举例1,不同数据类型字符数组的大小差异
#include<stdio.h>
#define SIZE 20
int main() {
long arr1[SIZE] = {'h','e','l','l','o',' ','w','o','r','l','d','!'};
char arr2[SIZE] = {'h','e','l','l','o',' ','w','o','r','l','d','!'};
printf("long型字符数组占用的内存大小为:%d\n", sizeof(arr1));
printf("char型字符数组占用的内存大小为:%d\n", sizeof(arr2));
return 0;
}
结果:
long数据类型定义字符数组会出现数据空间的浪费
2.4.2 字符数组的引用
引用形式:数组名[下标];
#include <stdio.h>
int main(void){
char c[15] = {'I',' ','a','m',' ','s','t','u','d','e','n','t'};
int i;
for(i=0;i<15;i++){
printf("%c",c[i]);
}
printf("\n");
return 0;
}
2.4.3 字符数组初始化
对字符数组的初始化,通过字符常量列表可逐个元素的赋值,即把 ‘字符’ 逐个赋给数组元素。
或者
在C语言中,将字符串常量作为字符数组来处理,所以也可以用 “字符串” 给字符数组来赋初值
对于第一种赋值方式
-
如果花括号中提供的初值个数(即字符个数)大于数组长度,则编译时会按语法错误处理
-
如果初值个数小于数组长度,则将这些字符赋值给数组前面的那些元素,其余的元素定为空字符(‘\0’)
举例1.
#include<stdio.h>
#define SIZE 20
//定义了20个字符类型的元素,只用了前12个来存放arr字符,之后8个元素用来存放空字符('\0')
int main(void) {
int i;
char arr[SIZE] = { 'h','e','l','l','o',' ','w','o','r','l','d','!' };
for (i = 0; i < SIZE; i++)
{
printf("%c", arr[i]);
}
return 0;
}
结果:
虽然不显示空字符,但是实际存在还有8个空字符,我们可以利用这一点:在打印的时候将数组中的元素‘\0’视为数组结束的标志
举例2.
#include<stdio.h>
#define SIZE 20
int main() {
int i;
long arr[SIZE] = { 'h','e','l','l','o',' ','w','o','r','l','d','!' };
for (i = 0; arr[i]!='\0'; i++) //这里通过for循环里面的语句arr[i]!='\0',这时的输出结果中就不含有任何空字符了
{
printf("%c", arr[i]);
}
return 0;
}
结果:
这个时候输出的字符就不包括空字符了。
第二种赋值方式
-
为了测定字符串的实际长度,C语言规定了一个字符串结束标志,以字符’\0’代表。
-
也就是说,在遇到字符’\0’时,表示字符串结束,由它前面的字符组成字符串。
举例1:采用字符串常量对一维数组进行初始化
#include<stdio.h>
#define SIZE 20
int main() {
int i;
char arr[SIZE] = { "hello world!" };
for (i = 0; arr[i] != '\0'; i++)
{
printf("%c", arr[i]);
}
return 0;
}
或者
#include<stdio.h>
int main() {
int i;
char arr[] = "hello world"; //可以省略花括号
//此时字符串的长度为12,因为字符串常量最后由系统加上了一个'\0'
//等价于:
//char arr[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0'};
for (i = 0; arr[i] != '\0'; i++)
{
printf("%c", arr[i]);
}
return 0;
}
2.4.4 字符串
C语言中,将字符串作为字符数组来处理,用一个一维字符数组来存放字符串“I am student”,字符串中的字符是逐个存放到数组元素中的
- 定义一个字符长度100,而实际有效字符只有40个,为了测定字符串的实际长度,C语言规定一个“字符串结束标志”,以字符‘\0’作为结束标志
1. 初始化字符串
方式一:
如果声明字符数组的同时初始化数组,则可以不规定数组的长度,系统在存储字符串常量时,会在串尾自动加上一个结束标识‘\0’。
结束标识在字符数组中也要占用一个元素的存储空间,因此在声明字符数组的长度时,要预留出该字符的位置
字符串常量来使字符数组初始化
char c[] = {"I am happy"};
等价于
char c[] = "I am happy"; //此时数组c的长度为11,因为字符串常量最后由系统加上一个'\0'
等价于
char c[] = {'I',' ','a','m',' ','h','a','p','p','y','\0'}; //数组c长度为11
char c[10] = "China";
等价于
char c[10] = {'C','h','i','n','a','\0','\0','\0','\0','\0'};
方式二:
采用循环语句进行输入输出,程序如下
#include <stdio.h>
int main(void)
{
char a[3];
scanf("%s", a); /*或者用gets(a)进行输入操作*/
printf("%s\n", a); /*或者用puts(a)进行输出操作*/
}
2. 字符串结束标识’\0’
判断是否到达字符串的结尾,即判断当前字符是否为'\0'
遍历字符串s,使用整型变量n存放下标,那么判断当前字符是否为'\0'
使用while(s[n]!='\0')
或
while(*p!='\0')
或
for(i=0;str[i]!='\0';i++)
对字符串操作结束后,添加'\0'
下标n为字符串中最后一个字符的下标,要添加结束标识,
表示为:s[n++]='\0'
或
指针p指向最后一个字符,*(p++)='\0'
3. 指向字符串的指针
定义格式:
char * 指针变量
初始化方法:
char * p = “abc”;
使用方法:
while(* p)
{
. . .
}
字符串指针的使用:
注:只有字符数组才可以应用数组名称直接将整个数组中的元素输出,其他类型的数组不具有这种特性
使用指针对字符串进行操作
字符串s=“hello”,指针p指向s,将字符串中字母l转换为字符a
while(p)
{
if(* p=='l')
*p = 'a';
p++;
}
字符串处理函数:
strcpy()字符串复制函数
strcat()字符串连接函数
strlen()字符串长度函数
strcmp()字符串比较函数
strlwr()转换为小写的函数
strupr()转换为大写的函数
2.4.5 单个字符和字符串的比较
1. 字符数组的输入输出
C语言没有字符串(String)这种类型,但是可以用字符数组来代替
用"%c"格式符,将字符逐个输入或输出
用"%s"格式符,将整个字符串一次输入或输出
-
输入方法
如果知道输入字符的长度
char s[10]; for(int i=0;i<5;++i) //输入5个字符 scanf("%c", &s[i]);
如果不知道输入字符的长度
char s[10]; //输入方法一:遇到空格和回车就会停止 scanf("%s", s); //注意不需要用&,因为数组名就是数组首元素的地址 //输入方法二:遇到回车才会停止 gets(s);
注: 1. 输入字符串的时候不需要在字符串后面加'\0',系统会自动添加 2. 利用scanf输入多个字符串时,应在输入时以空格分隔
举例: #include<stdio.h> int main() { char str1[5], str2[5], str3[5]; scanf("%s%s%s", str1,str2,str3); printf("%s %s %s", str1,str2,str3); return 0; } 注: 输入时应该使用空格字符分隔,作为3个字符串输入 例如:how are you 因为系统把空格字符作为了输入字符串之间的分隔符,遇到空格表示字符串输入结束 接着就输入了第二个字符串了
-
输出方法
如果知道输出字符串的长度
使用for循环直接printf输出每个字符
#include <stdio.h> int main(void) { char s[10]; for(int i=0;i<5;i++) scanf("%c", &s[i]); for(int i=0;i<5;++i) printf("%c", s[i]); return 0; }
如果不知道输出字符串的长度
char s[10]; gets(s); //输出方法一 printf("%s", s); //使用printf函数中的输出项是字符数组名 //输出方法二 puts(s);
注:
1. 对于printf("%s", s);中控制符%s发挥作用是: 按字符数组名s找到s第一个元素地址,然后一个接一个的把字符打印到屏幕上,直到碰到'\0'为止,且不输出'\0'. 2. 字符数组包含一个以上的'\0',则遇到第一个'\0'是输出就结束了
举例:
#include<stdio.h> int main() { char arr[] = "hello\0world"; printf("%s", arr); return 0; } 注: 输出字符数组中的字符串时,遇到'\0'就停止输出了, 因此只输出了字符串"hello",而不会输出"hello world"
gets函数原型
#include <stdio.h> char *gets(char *str); 参数类型为 char* 型,即 str 可以是一个字符指针变量名,也可以是一个字符数组名。 功能是从输入缓冲区中读取一个字符串存储到字符指针变量 str 所指向的内存空间。
puts函数原型
#include <stdio.h> int puts(const char *s); 只有一个参数。s可以是字符指针变量名、字符数组名,或者直接是一个字符串常量 功能是将字符串输出到屏幕。输出时只有遇到 '\0' 也就是字符串结束标志符才会停止。
2. 字符串长度
字符串长度:不包括字符串最后的’\0’
也就是说在遇到字符’\0’时,表示字符串结束,把它前面的字符组成一个字符串
-
注:C系统在用字符数组存储字符串常量时会自动加一个’\0’作为结束符
例如:"C program"共9个字符,字符串是存放在字符数组中的,在数组中它占10个字节,最后一个字节’\0’由系统自动加上的 -
'\0’代表ASCII码为0的字符,从ASCII码表中可以查到,ASCII码为0的字符不是一个可显示的字符,而是一个空操作符,即他什么也不做。
用它来作为字符串结束标志不会产生附加的操作或增加有效字符,只是起到一个辨别的作用
3. 对一维字符数组进行定义和初始化的过程中,不指定其长度
举例1:使用字符常量列表和字符串常量的进行初始化的比较
#include<stdio.h>
int main() {
int i;
char arr1[] = { "hello world!" };
char arr2[] = {'h','e','l','l','o',' ','w','o','r','l','d','!'};
printf("采用字符串常量进行初始化的arr1数组的长度为:%d\n", sizeof(arr1));
printf("采用字符常量列表进行初始化的arr2数组的长度为:%d\n", sizeof(arr2));
return 0;
}
结果:
总结:
在采用字符串常量对字符数组进行初始化的过程中,在内存中进行存储时会自动在字符串的后面添加一个结束符‘\0’,所以得到的字符数组长度是字符串常量的长度加1;而采用字符常量列表的方式对字符数组进行初始化就不会在最后添加一个结束符。