前言
嗨嗨,这里是枫枫。
没想到我们的专栏能迎来第三期,先来庆祝一下。
本期的内容主要是设计到一维数组以及字符串数组,其中会夹杂些其他的有关内容,例如一维数组作为函数参数等等。
下面,先给大家讲讲什么是数组。
数组是一种高效的数据组织方式,几乎所有的高级程序设计语言都支持数组。
所谓数组,就是指相同类型数据的一个有序集合。
这些数据有一个共同的名字——数组名(必须是合法的标识符)。
集合中的元素称为——数组元素。
集合中元素的位置信息被称为——下标,在引用某个元素时只要给出数组名和该元素的下标即可。
数组元素类型任意,可以是基本类型中的字符型、整型、浮点型等,
也可以是指针,或结构体、共用体等构造类型。
因为每个元素的类型均相同,所以它们占用内存中连续的存储单元,其中第一个数组元素的内存地址是数组所占内存块的首地址,数组名就代表数组的首地址。
它是一个常量。可表示为:&a[0]=a。
数组在使用原则上与普通变量一致,即先定义后使用,先定义的目的是先让计算机准备好内存空间,将来存放数据。数组的定义就是给出数组名、元素类型和元素个数3个方面的信息。
好了,基本的介绍就到这里,下面让我们进入正题吧。
那么,新的“游戏”,开始吧!
Link Start!
目录
浮游塔第21层:一维数组的定义
(最基本、最简单的数组)
一维数组是最基本也是最简单的数组,在平常的学习种,也是碰得最多的数组。
其定义的一般格式为:元素类型标识符 数组名[长度(即元素个数)];
例如:int a[10];或char ch[9];
注:长度必须是整型常量或整型常量表达式,因为在C语言中,数组不能动态定义。
其实数组的定义,就是开辟出内存空间用来存放数组元素,至于开辟出多大的空间,则是由数组长度和数组类型决定的。
数组长度就是再定义时,数组元素的个数;
数组类型决定了每个数据元素所占内存的字节数。
二者的乘积等于数组占内存的总字节数,
总字节数=数组长度(或元素个数)*sizeof(数组类型)
浮游塔第22层:一维数组的初始化
(数组的初始化=给数组元素赋值)
一维数组未进行初始化的数组如果是局部的,其各个元素的值是不确定的;
如果是全局的或者静态的,各元素的值默认为零值。
其一般格式为:元素类型标识符 数组名[元素个数]={数据1,数据2,···,数据n};
例:
int a[6]={1,2,3,4,5,6};
在初始化的过程中,可以省略元素个数的多少,
例如:int a[ ]={1,2,3,4,5,6};
若整个数组都没有进行初始化,则每个元素都会被赋为乱码。
也可以部分元素连续初始化:
例如:int a[6]={1,2,3};数组a的前三个元素被分别赋值为1,2,3,而后三个元素则自动赋值为0,即初始化时给出的数据不足,其余的元素会被自动赋值为0。
如果是字符数组,则会被赋值到字符’\0’。
①:全部赋初值
例如:int arr[5] = {90, 80, 95, 85, 75};
arr[0] | arr[1] | arr[2] | arr[3] | arr[4] |
90 | 80 | 95 | 85 | 75 |
注意:
1、初值的个数不能大于数组的长度,否则会产生运行时错误。
例如:int a[2]={1,2,3,4};
2、每个初值的类型最好与数组的类型一致,精度高的数组可以赋以精度低的元素。
例如:double型的数组可以包含Int型的元素,但精度低的数组不能赋以精度高的元素,例如int型的数组不能包含double型的元素。
②:部分赋初值
例如:int arr[5] = {90, 80};
arr[0] | arr[1] | arr[2] | arr[3] | arr[4] |
90 | 80 | 0 | 0 | 0 |
注意:
- 未赋值的元素都为0。
- 实际开发中经常使用部分赋值的方式进行
#define ARR_SIZE 5
int c_score[ARR_SIZE]={0}; //可以表示元素全为0,因为只有第一个元素赋以0 ,而后面的元素自动赋为0
③:省略长度赋初值
int arr[] = {90, 80, 95, 85, 75};
arr[0] | arr[1] | arr[2] | arr[3] | arr[4] |
90 | 80 | 95 | 85 | 75 |
注意:
数组的长度(数组的元素个数),由初值的个数确定。
浮游塔第23层:一维数组元素的引用
数组元素的引用就是使用数组数据元素。
与普通常量引用不同的是,由于数组储存了多个数据,所以在引用时除了给出数组名外,还要具体指出被引用的元素的下标。
具体的引用格式为: 数组名[下标];
注:在引用时,下标可以包含变量。
例如:
int k=0; //初始化普通常量
a[k]=12; //第一个元素赋值为12
a[5]=a[0]+10;//先计算第一个元素与数值10的和,在复制给第个各元素
scanf("%d",&a[2]);//将键盘输入的整数存入第三个元素中
a['B'-'A']=8;//第二个元素赋值为8
a[3]=a[1]+a[2*2] //将第二个元素和第五个元素的和存入第四个元素中
下面用一个例题来加强一下大家的印象。
题目:从键盘输入5个圆的半径,计算并输出它们的面积。(两个数据都保存在数组中)
3
2
1
解析如下:
#include<stdio.h>
#define Π 3.14//宏定义一个常量Π来表示圆周率,并赋以3.14
#define N 5 //宏定义一个常量N来表示圆的个数,并赋以5
int main( )
{
int i;
float r[N]; //N个圆的半径存放在数组r中
float s[N]; //N个园的面积存放在数组s中
for(i=0;i<N;i++) //循环5次
{
printf("请输入第%d个圆的半径:",i+1);
scanf("%f",&r[i]); //从键盘接受半径的值,并按顺序保存到数组r种
s[i]=Π*r[i]*r[i]; //由半径得到面积,并按顺序保存到数组s中
}
for(i=0;i<N;i++)
{
printf("第%d个圆的面积:%.2f\n",i+1,s[i]);
}
}
浮游塔第24层:二维数组基础
二维数组定义的一般形式是:
类型说明符 数组名[常量表达式1][常量表达式2]; |
即:数据类型 数组名[行数][列数]; |
其中,常量表达式1表示第一维下标的长度,表达式2表示第二维下标的长度。例如:int a[3][4];
注意:若第一维的长度没有指定,则在定义的同时需要对其完成初始化。
二维数组的初始化也有多种方式,
①int arr[2][3]={{1,2,3},{4,5,6}};
②int arr[2][3]=
{
{1,2,3},
{4,5,6}
};
③或者把二者间的括号省略,即int arr[2][3]={1,2,3,4,5,6};
④又或者把行数省略,即int arr[][3]={1,2,3,4,5,6};但列数是绝对不能省略的!
有关二维数组的元素大小以及行列数问题,可以参考以下代码。
#include <stdio.h>
int main()
{
int arr[2][3]={{1,2,3},{4,5,6}};
int i,j;
for(i=0;i<2;i++)
{
for(j=0;j<3;j++)
{
printf("%d",arr[i][j]);
}
printf("\n");
}
printf("二维数组大小:%d\n",sizeof(arr));
printf("二维数组一行大小:%d\n",sizeof(arr[0]));
printf("二维数组元素大小:%d\n",sizeof(arr[0][0]));
printf("二维数组行数:%d\n",sizeof(arr)/sizeof(arr[0]));
printf("二维数组列数:%d\n",sizeof(arr[0])/sizeof(arr[0][0]));
return 0;
}
二维数组的元素和一维数组的元素一样的,即元素的位置是相邻的,我们可以通过以下代码去验证它。
#include <stdio.h>
int main()
{
int arr[2][3]={{1,2,3},{4,5,6}};
printf("%p\n",arr[0][0]);
printf("%p\n",arr[0][1]);
printf("%p\n",arr[0][2]);
printf("%p\n",arr[1][0]);
return 0;
}
其中的%p是打印地址(指针地址)的,并且是以十六进制的形式打印。
但是会全部打完,即有多少位打印多少位,但这个长度受编译器影响。
32位编译器的指针变量为4个字节(32位),64位编译器的指针变量为8个字节(64位)。
二维数组各元素的地址关系:
int a[3][4];
若0<=i<3,0<=j<4,则二维数组的元素a[i][j]的地址可以有以下五种表达式:
&a[i][j] a[i]+j *(a+i)+j &a[0][0]+4*i+j a[0]+4*i+j
&a 表示二维数组地址。 a 表示首行地址。
&a[0] 表示首行地址。 a[0] 表示首行首元素地址。
a[0][0] 表示首行首元素的值。 &a[0][0] 表示首行首元素地址。
浮游塔第25层:多维数组基础
(不断套娃)
多维数组的定义与二维数组类似,其语法格式具体如下:
数组类型修饰符 数组名[n1][n2]···[nn]; |
即:数据类型 数组名[层][行][列]; |
例如:int arr[3][4][5];
定义了一个三维数组,数组名是arr,数组的长度是3,每个数组的元素又是一个二维数组,
这个二维数组的长度是4,并且这个二维数组中的每个元素又是一个一维数组,
这个一维数组的长度是5,元素类型是int。
下面我会用最直观的初始化方式,来加强大家对于多维数组的印象。
#include<stdio.h>
int main()
{
int i,j,k,arr[2][3][4]=
{
{
{1,2,3,4},
{2,3,4,5},
{3,4,5,6}
},
{
{4,5,6,7},
{5,6,7,8},
{6,7,8,9}
}
};
for(i=0;i<2;i++)
{
for(j=0;j<3;j++)
{
for(k=0;k<4;k++)
{
printf("%d\t",arr[i][j][k]);
}
printf("\n");
}
}
printf("三维数组大小:%d\n",sizeof(arr));
printf("三维数组一层大小:%d\n",sizeof(arr[0]));
printf("三维数组一行大小:%d\n",sizeof(arr[0][0]));
printf("三维数组元素大小:%d\n",sizeof(arr[0][0][0]));
printf("层:%d\n",sizeof(arr)/sizeof(arr[0]));
printf("行:%d\n",sizeof(arr[0])/sizeof(arr[0][0]));
printf("列:%d\n",sizeof(arr[0][0])/sizeof(arr[0][0][0]));
return 0;
}
浮游塔第26层:一维数组作为函数参数
函数的语法格式:
数据类型 函数名(形参列表) //多个参数之间用逗号分隔
{
函数体;
}
如何在一个函数中使用另一个函数中定义的数组?
我们将数组作为参数传递到被调函数中的时候,需要传递什么过去?
因为数组元素的内存空间是连续的,所以我们大可不必将数组中所有的元素值都作为参数传递,只需要把数组的这块连续的内存空间首地址传递到被调函数即可。那么被调函数得到数组地址后,就可以对该数组内容进行访问。
其中,被调函数也存在多种情况。
- 无参无返回值
- 无参有返回值
- 有参无返回值
- 有参有返回值
一维数组作为函数参数:
形参:定义数据类型相同的数组或者定义指针
实参:数组名(数组首元素的地址)
若使用一维数组名作函数参数,则在被调用函数中,不需要考虑形式参数数组的大小
需要注意的是,函数的参数传递有两种,一种是数值传递(单向),一种是地址传递(双向)。
地址传递会在学习了指针之后在进行讲解。
下面用一段代码来介绍数值传递。
#include <stdio.h>
#define SIZE 5
int fun(int x[])
{
int i=0; //数组元素下标
int sum=0; //和
for(i=0;i<SIZE;i++)
{
sum+=x[i];//计算arr数组元素之和
}
return sum;//返回最终结果
}
int main( )
{
int arr[SIZE]={1,2,3,4,5};
int sum=fun(arr);//调用fun函数,
printf("数组元素之和:%d\n",sum);
return 0;
}
我们可以发现:
fun函数的形参是int x[]或者int x[5]
main函数的调用函数为 fun(arr)
但不论形参数组的长度定义多少,计算后得到的形参数组的大小都是4
原因如下:
因为实际参数是数组名,数组名就是数组首元素的地址,即&arr[0],因此实际参数的本质就是地址。
那么形式参数用一个指针变量就可以接受保存实际参数,无需额外开辟多余的内存空间。
虽然形式参数定义的是数组,但是形式参数的本质仍然是指针。
提示:不要在自定义函数中计算形式参数数组的长度,需要知道长度可以从主调函数中传递
浮游塔第27层:字符数组的定义
字符串的存储方式有字符数组和字符指针,我们先来看看字符数组。
与定义其他类型的数组类似,字符数组定义的一般格式为:
char 数组名[元素个数1] [元素个数2] … [元素个数n] ;
存储字符时实际存储的是其对应的ASCII码值,虽然可以作为整数使用,但并不是真正的整形数据,其类型仍然为char型,即存储时占一个字节。
注:以下两个语句初始化的结果占用内存的情况是不同。
short c1[3]={65,66,67}; | 在内存中占3*sizeof(short)个字节 |
char c2[3]={}65,66,67] | 再内存中占3*sizeof(char)个字节 |
因为字符串是由多个字符组成的序列,所以要想存储一个字符串,可以先把它拆成一个个字符,然后分别对这些字符进行存储,即通过字符数组存储。
例如:以下两个数组的定义是等价的。
char str1[30] = "Let's go"; // 字符串长度:8;数组长度:30
char str1[30] = { 'L', 'e', 't', '\'', 's',' ', 'g', 'o', '\0' };
存储字符串的数组一定比字符串长度多一个元素,以容纳下字符串终止符(空字符'\0')。
下列字符数组里存放的一定是字符串嘛?
char ch1[5]={‘a’,’b’,’c’,’d’,’e’}; 没有使用’\0’结尾
char ch2[]=”Hello”; 是字符串
char ch3[5]={‘a’,’b’}; 是字符串,未初始化的数组元素默认为0,也就是’\0’
char ch4[5]=”Hello”; 不是字符串,数组只存放了’H’,’e’,’l’,’l’,’o’,没有存放’\0’
浮游塔第28层:字符数组的初始化
字符数组只有在初始化的时候才能整体赋值,否则只能对字符数组逐个赋值。
字符数组的初始化与数组的初始化一样,要么定义时初始化,要么定义后初始化。
如果在定义字符数组时不进行初始化,则数组中各元素的值是不可预料的。
下面介绍初始化的两种方式:
①用字符初始化/逐个字符赋值
如果用字符型数据初始化字符数组,那么其格式与其他类型的数组初始化格式无区别。
char ch[5]={‘a’,’a’,’a’,’a’,’a’}; //逐个全部初始化一维数组
则ch[0]~ch[4]的值均为’a’,也可以省略元素个数,
即”char ch[ ]={‘a’,’a’,’a’,’a’,’a’};元素个数自动确定为5。
也可以部分初始化,特点与前面所讲的普通一维数组相同。
但需要注意的是,没有得到数据的元素,会被自动赋以字符’\0’,即ASCII码值为0的字符。
再如:
char ch[3][4]={{‘a’,’a’,’a’,’a’},{‘b’,’b’,’b’,’b’},{‘c’,’c’,’c’,’c’}}; //逐个全部初始化二维数组
可以按行分段对全部或部分元素进行初始化,也可以按行连续对全部或部分元素进行初始化,并且也可以省略行下标,这些特点与其他类型的二维数组相同。
②用字符串初始化
char str[7]={“string”};
或
char str[ ]={“string”};
注:用字符串对字符数组初始化时,如果不省略元素个数,那么元素个数应至少比字符串包含的可显示字符个数多一个。
因为系统会自动在字符串的最后加上字符’\0’,它也要占一个存储位置,算作一个数组元素。
浮游塔第29层:字符数组的输入输出
逐个字符的形式:
采用"%c"格式符与循环结构配合,
以逐个字符的方式实现“字符串”的输入和输出
整串输入和输出
#include<stdio.h>
int main( )
{
char ch[6];
printf("输入一个字符串:");
scanf("%s",&ch);
printf("字符串:%s\n",ch);
return 0;
}
注意:在整串输入时,一定要空出一个位置给'\0',否则会出现以下情况。
run-time check failure #2-stack around the variable'ch'was corrupted
(运行时检查失败#2 -围绕变量“ch”的堆栈已损坏)
浮游塔第30层:字符数组的引用
字符数组的引用方式与其他类型数组引用方式相同。
#include <stdio.h>
#define N 5 //宏定义常量N,表示数组长度
int main( )
{
int i; //循环控制变量
char str[N];
printf("请输入%d个字符:",N); //输入的字符个数,不能超过N个
for(i=0;i<N;i++)
{
scanf("%c",&str[i]);
}
printf("第1、3、5个字符:");
printf("%c",str[0]);
printf("%c",str[2]);
printf("%c",str[4]);
printf("\n");
return 0;
}
浮游塔隐藏层:数组的应用
数组在日后制作首个C语言项目[学生成绩管理系统]的时候会发挥很大的作用。
在其中的功能有,
- 数据的添加和删除
- 数据查询
- 数据修改
- 数据的排序
感兴趣的可以看看这篇由数组和结构体制作的学生成绩管理系统:
后记
okay,这就是浮游塔第21至30层的内容,依旧希望各位能够坚持下去。
突然发现写博客能让人上瘾,今天光是写博客查资料就花了五六个小时。
下期的内容是函数的天下,预计在1~2天后发布,敬请期待,欸嘿。
第④期——函数链接: