文章目录
一、初探程序中的数组
-
数组的概念
-
数组是相同数据类型变量的有序集合
-
数组作为整体需要一个合法的命名(数组名)
-
数组中的变量没有独立命名,只有在数组中的编号
-
数组中的变量数量是固定不变的(数组大小固定)
-
-
-
数组的定义
-
语法:type Name [ size ];
-
示例:
int arrl[10]; //定义名为 arrl 的数组 //数组中一共有 10 个元素(变量) //每个元素的类型为 int double farr[5]; //定义名为 farr 的数组 //数组中一共有 5 个元素(变量) //每个元素的类型为 double
-
- 数组元素的访问
- 通过数组名 [下标] 的方式访问数组元素
- 数组是一个有序集合,每个元素有固定下标
- 数组元素下标固定从0开始(可以使用变量作为下标)
- 示例:
int arrl[3]; arrl[-1] = 0; //ERROR arrl[0] = 1; arrl[1] = 2; arrl[2] = 3; arrl[3] = 4; //ERROR
- 注意事项
- 只有整型数才能作为下标访问数组元素
- 下标越界是一种非常严重的编程错误(工业级BUG来源)
- 下标越界带来的严重后果可能不会立即表现
下面来编写程序求出0到100之间任意一个数的累加和,并对程序进行改进。首先看最常规的:
#include <stdio.h>
int main()
{
int n = 0;
int i = 0;
int flag = 0;
int sum = 0;
do
{
printf("Input: ");
scanf("%d", &n);
flag = ((0 <= n) && (n <= 100));
if( flag )
{
sum = 0;
for(i=0; i<=n; i++)
{
sum += i;
}
printf("Sum = %d\n", sum);
}
} while( flag );
return 0;
}
下面为输出结果:
上面的程序每求一次和,就得重新通过 for 循环求一次,造成资源的浪费,有没有更好的方法呢?下面看第二种方法:
#include <stdio.h>
int main()
{
int n = 0;
int i = 0;
int flag = 0;
int sum[101];
for(n=0; n<=100; n++)
{
sum[n] = 0;
for(i=0; i<=n; i++)
{
sum[n] += i;
}
}
do
{
printf("Input: ");
scanf("%d", &n);
flag = ((0 <= n) && (n <= 100));
if( flag )
{
printf("Sum = %d\n", sum[n]);
}
} while( flag );
return 0;
}
下面输出结果:
这个程序相对于第一个程序改进的地方就是把所求的和提前算好,存在数组中,所以输入数后,只用查询就行。但是这种方法在提前算好的时候也存在重复计算,所以也不是最好的方法。下面看第3种方法:
#include <stdio.h>
int main()
{
int n = 0;
int flag = 0;
int sum[101];
sum[0] = 0;
for(n=1; n<=100; n++)
{
sum[n] = sum[n-1] + n; // sum[1] = sum[0] + 1;
// sum[2] = sum[1] + 2;
// ......
}
do
{
printf("Input: ");
scanf("%d", &n);
flag = ((0 <= n) && (n <= 100));
if( flag )
{
printf("Sum = %d\n", sum[n]);
}
} while( flag );
return 0;
}
下面为输出结果:
第三种方法采用递推的方法求和,充分利用资源,是最佳的方法。
- 小结
- 数组是相同数据类型变量的有序集合
- 数组元素通过数组名 [下标] 的方式访问
- 只有整型数才能作为下标访问数组元素
- 下标越界是一种非常严重的编程错误
二、数组特性深入剖析
- 数组的初始化
- 语法:type Name [N] = { v0, v1, ... , vN-1}
- 意义:将数组中的元素分别初始化为 v0, v1, ... , vN-1
- 示例:
int main() { int arr[5] = {2, 4, 6, 8, 10}; int i = 0; for(i = 0; i < 5; i++) { printf("arr[%d] = %d\n", i, arr[i]); } return 0; }
- 数组初始化技巧
- 自动确定数组大小
- type Name [ ] = {v0, v1, ... , vn-1}
- 将部分数组元素初始化为0
- type Name [ N ] = {v0, v1, ... , vs} 注:S < N,未指定初始值的元素默认为0
- 将所有数组元素初始化为0
- type Name [N] = {0};
- 自动确定数组大小
- 数组的内存分布
- 数组在计算机底层是一片连续的内存,用于存储数组元素
- 数组的大小字节数可以用 sizeof 获取(单位:字节)
- 计算数组的大小
- type Name [ ] = {v0, v1, ..., vn-1}; //共 n 个元素
- sizeof (Name) / sizeof ( Name[0] ); //计算结果为n
- 注意事项
- 数组名只能当作左值使用(可看作常量)
- 只能使用整型常量对数组大小进行定义
- 只能使用整型值对作为下标访问数组元素
- 示例
int size; int arr[size]; //错误,只能使用整型常量定义数组 int a[5] = {0}; arr[1.5] = 1; //错误:小数不能用来定义数组下标 arr = a; //错误 :常量不能做左值使用
下面进行一段代码:从键盘输入10个整数,完成之后输入目标整数 x 进行查找
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
int len = sizeof(arr)/sizeof(arr[0]);
int x = 0;
for(i=0; i<len; i++)
{
printf("Input NO.%d: ", i + 1);
scanf("%d", &arr[i]);
}
while( 1 )
{
printf("Target Number: ");
scanf("%d", &x);
for(i=0; i<len; i++)
{
if( x == arr[i] )
{
break;
}
}
if( i < len )
{
printf("Index: %d\n", i + 1);
}
else
{
printf("No Target Number!\n");
}
}
return 0;
}
下面为输出结果:
接下来再看一段代码:已知整型数组中的元素都是 0 - 9 之间的数,编写程序统计各个数字的个数
#include <stdio.h>
int main()
{
int arr[] = { 9, 1, 2, 1, 1, 4, 5, 5, 5, 9, 9, 9 };
int i = 0;
int j = 0;
int len = sizeof(arr)/sizeof(arr[0]);
int n = 0;
for(i=0; i<10; i++)
{
n = 0;
for(j=0; j<len; j++)
{
if( i == arr[j] )
{
n++;
}
}
printf("%d: %d\n", i, n);
}
return 0;
}
下面为输出结果:
上面的程序采用了两段 for 循环,造成资源的浪费,下面看一种好的方法:
#include <stdio.h>
int main()
{
int arr[] = { 9, 1, 2, 1, 1, 4, 5, 5, 5, 9, 9, 9 };
int i = 0;
int j = 0;
int len = sizeof(arr)/sizeof(arr[0]);
int cnt[10] = {0};
int num = 0;
for(j=0; j<len; j++)
{
num = arr[j];
cnt[num] += 1;
}
for(i=0; i<10; i++)
{
printf("%d. %d\n", i, cnt[i]);
}
return 0;
}
下面为输出结果:
- 小结
- 数组在计算机底层是一片连续的内存
- 数组名只能当作左值使用(可看作常量)
- 只能使用整型常量对数组大小进行定义
- 只能使用整型值对作为下标访问数组元素
三、多维数组的概念与示例
- 数组类型
- 数组类型由 元素类型 和 数组大小 共同决定
- 数组类型的具体表现形式为:type [ N ]
- 示例:
int a[10] = {0}; //类型:int[10] float b[5] = {0}; //类型:float[5] int aa[5] = 0; //类型:int[5] float bb[8] = {0}; //类型:float[8] int c[10]; //类型:int[10] int i; for(i = 0; i < 10; i++) { c[i] = a[i]; //相同类型数组的“赋值操作” }
- 小知识
- 数组中的元素可以是变量,也可以是其它程序元素
- 当数组中的元素是另一个数组时,就构成了多维数组
- C语言中的二维数组定义: type Name[ N1 ][ N2 ];
- 二维数组可看作数学中的矩阵
- 注意
- 二维数组的本质还是就是数组,即:数组中的每一个元素是数组!!
- 示例:
int a[3][4]; //定义一个数组,数组中有 3 个元素,每个元素的类型为 int[4] (a[1])[2] = 2; //对第 1 个数组中的第 2 个变量赋值 a[2][3] = 2; 对第 2 个数组中的第 3 个变量赋值 a[3][0] = 0; //越界 a[0][4] = 0; //越界
- 二维数组的初始化
- 方式一:int a[2][3]= { {1,2],{4,5] };
- 方式二:int a[2][3] = { 1,2,3,4 };
- 方式三:int a[2][3] = { 0 };
- 方式四:int a[ ][3] = { {1,2,3},{4,5,6} };
- 方式五: int a[ ][3] = { 1,2,3,4 };
方式一示例:
方式二示例:
- 注意事项
- 二维数组能且仅能让编译器自动确定第一维的大小
- 第二维大小必须显示给定,即:数组元素的类型必须正确合法
- 第一维大小自动确定的方法:(初始值个数 除以 第二维大小) 向上取整
- 示例:
int a[][3] = {1, 2, 3, 4}; int s1 = sizeof(a) / sizeof(a[0]); int i = 0; int j = 0; for(i = 0; i < s1 ; i++) { for(j = 0; j < 3; j++) { printf("a[%d][%d] = %d\n", i, j, a[i][j]); } }
下面先看一段代码, 弄清数组占用内存和赋值问题:
#include <stdio.h>
int main()
{
int a[10] = {0}; // int[10]
int b[5] = {1, 2, 3, 4, 5}; // int[5]
int i = 0;
printf("sizeof(int[5]) = %d\n", sizeof(int[5]));
printf("sizeof(int[10]) = %d\n", sizeof(int[10]));
printf("sizeof(a) = %d\n", sizeof a);
for(i=0; i<5; i++)
{
a[i] = b[i];
}
for(i=0; i<10; i++)
{
printf("a[%d] = %d\n", i, a[i]);
}
return 0;
}
下面为输出结果:
注意这里赋值只能将数组 b 中的元素赋值给 数组 a,如果反过来 数组b 会发生越界,后面5个值会变成垃圾值。
再来看一个二维数组的内存占用空间的代码:
#include <stdio.h>
int main()
{
int a[][3] = { {1, 2, 3}, {4, 5} };
int s1 = sizeof(a) / sizeof(a[0]); // 2
int i = 0;
int j = 0;
printf("s1 = %d\n", s1);
printf("sizeof(a) = %d\n", sizeof(a));
printf("sizeof(a[0]) = %d\n", sizeof(a[0])); // int[3]
for(i=0; i<s1; i++)
{
for(j=0; j<3; j++)
{
printf("a[%d][%d] = %d\n", i, j, a[i][j]); // 数组的数组可以看作矩阵
// 因此,可以使用 2 个下标访问矩阵中的值
}
}
return 0;
}
下面为输出结果:
这里注意两个问题:
第一个问题就是第一维数组大小的计算,有对应的公式
第二个问题就是对二维数组的理解,数组中的每一个元素是数组,所以 a 数组有 2 个数组,每个数组的类型为 int[3] 。所以对 a[0] 取字节后看到占用 12 个字节,即3个元素,每个元素为int 类型,占用 4 个字节,总的就为 12字节
下面看一下对数组转置的代码 :
#include <stdio.h>
int main()
{
int a[3][3];
int i = 0;
int j = 0;
for(i=0; i<3; i++)
{
for(j=0; j<3; j++)
{
printf("Input a[%d][%d]: ", i, j);
scanf("%d", &a[i][j]);
}
}
printf("Matrix:\n");
for(i=0; i<3; i++)
{
for(j=0; j<3; j++)
{
printf("%d ", a[i][j]);
}
printf("\n");
}
for(i=0; i<3; i++)
{
for(j=0; j<3; j++)
{
if( i < j )
{
int t = a[i][j];
a[i][j] = a[j][i];
a[j][i] = t;
}
}
}
printf("Matrix-T:\n");
for(i=0; i<3; i++)
{
for(j=0; j<3; j++)
{
printf("%d ", a[i][j]);
}
printf("\n");
}
return 0;
}
下面为输出结果:
- 多维数组
- 三维数组定义:一个数组,每个元素为一个二维数组
- 语法:type Name[ N1 ][ N2 ][ N3 ];
- 四维数组定义:一个数组,每个元素为一个三维数组
- 语法:type Name[ N1 ][ N2 ][ N3 ][ N4 ];
- 三维数组定义:一个数组,每个元素为一个二维数组
- 小结
- 数组类型由元素类型和数组大小共同决定(type [ N ])
- 数组中的元素可以是变量,也可以是其它程序元素
- 当数组中的元素是另一个数组时,就构成了多维数组
- 二维数组能且仅能让编译器自动确定第一维的大小
四、字符数组与字符串(上)
- 字符数组是特殊的整数有序集合
- 每个整数占用一个字节(-128 --127)
- 可以用字符字面量对数组元素进行初始化或者赋值
- 常用来存储可阅读的文本信息
下面看一段代码:
#include <stdio.h>
int main()
{
char a[] = { 97, 98, 99 };
char b[] = { 'D', '.', 'T', '.' };
int i = 0;
for(i=0; i<sizeof(a); i++)
printf("%c", a[i]);
printf("\n");
for(i=0; i<sizeof(b); i++)
printf("%c", b[i]);
printf("\n");
return 0;
}
下面为输出结果:
- 注意事项
- 没有专用的字符串类型(字符串:双引号括起来的有序字符集 [这里可以看成字符串常量])
- 只能通过字符数组“模拟”字符串变量
- 存在字符串字面量,但仅能作为常量使用
- 示例:
#define DT "D.T." printf("%s\n", DT); printf("%s\n", "Software");
- 字符串中的 0 元素
- 整数 0 即字符串中的 0 元素(char 是最小的整型)
- 0 元素对应的字符为 '\0’ (转义字符)
- '0' 与 '\0' 不同,表示一个非0值,对应的整数为48
下面编写程序看一下吧:
#include<stdio.h>
int main()
{
printf("%d\n",'\0');
printf("%d\n", '0');
return 0;
}
下面为输出结果:
- “字符串变量” - 字符数组
- C语言中通过字符数组定义字符串
- 当字符数组中存在 0 元素时,可当作字符串使用
- 字符数组中的 0 元素表示了一个字符串的结束
- 字符数组中的元素,不一定是字符串中的元素
- 可以使用字符串常量进行初始化
- 字符串长度 小于 字符数组大小
- 一个字符数组不一定是一个字符串
- 而一个字符串一定是一个字符数组
字符数组中的元素,不一定是字符串中的元素,这句话该怎么理解呢?下面看一段代码:
#include<stdio.h> int main() { char s[] = {'D','.','T','.','\0'}; char ss[] = {'D','.','\0','T','.'}; printf("%s\n",s); printf("%s\n",ss); return 0; }
下面为输出结果:
可以看到,'\0'出现的位置不同,字符串打印出来的结果也会不一样,字符数组中的元素不一定全部打印出来。
下面看一段代码,加深对字符数组的理解:
#include <stdio.h>
int main()
{
char dt[] = "abcd";
char name[] = { "efg" };
int ds = sizeof(dt);
int ns = sizeof(name);
int i = 0;
printf("dt = %s\n", dt);
printf("name = %s\n", name);
printf("dt size = %d\n", ds);
printf("name size = %d\n", ns);
printf("dt array value:\n");
for(i=0; i<ds; i++)
printf("%d ", dt[i]);
printf("\n");
i = 0;
while( dt[i] != 0 )
i++;
printf("dt length = %d\n", i);
name[0] = 'x';
name[1] = 'y';
name[2] = 'z';
printf("name = %s\n", name);
return 0;
}
下面为输出结果:
由上面可以看出,字符串长度 小于 字符数组大小,而且 name 的内容可以改变,这就对应了“字符串变量”。
- 小结
- C语言中的字符数组可以存储文本信息
- C语言中没有专用字符串类型
- C语言中的 “字符串变量” 只能使用字符数组模拟
- 当字符数组中存在0元素时,即可当做字符串使用
五、字符数组与字符串(下)
- 注意事项
- C语言中没有专用的字符串类型,也没有针对字符串的专用运算操作!
- 字符串工具包
- C语言中提供了字符串相关的工具包,即: string.h
- string.h中定义了字符串相关的实用工具,如:字符串连接
- 字符串工具的本质就是与字符串相关的运算操作
- strlen(s)→获取字符串的长度
- strcpy(s1, s2)→将s2中的字符复制到s1, s1 <-- s2
- strcat(s1,s2)→将s2追加到s1后面, s1 <-- s1 + s2
- strcmp(s1, s2)→比较s1和s2是否相等,相等时为0
下面看一段代码:
#include <stdio.h>
#include <string.h>
int main()
{
char s[10] = "abcd";
int size = sizeof(s);
int len = strlen(s);
printf("size = %d\n", size);
printf("len = %d\n", len);
return 0;
}
下面为输出结果:
再来看一组关于字符串工具使用的代码:
#include <stdio.h>
#include <string.h>
int main()
{
char s[10] = "abcd";
char d[] = "efg";
int len = strlen(s);
char in[16] = {0};
printf("s = %s\n", s);
printf("len = %d\n", len);
strcpy(s, d);
strcat(s, d);
len = strlen(s);
printf("s = %s\n", s);
printf("len = %d\n", len);
printf("Input: ");
scanf("%s", in);
if( strcmp(s, in) == 0 )
{
printf("equal\n");
}
else
{
printf("non-equal\n");
}
return 0;
}
下面为输出结果:
这里注意一点就是字符串的输入,不需要使用 &
- 字符串小知识
- 字符串常量的本质是字符数组,大小为字符串长度+1
- 使用字符串工具进行字符串赋值时:
- 必须保证存储赋值结果的字符数组足够大(防止越界)
- 必须保证参与赋值的字符串必须合法(字符数组存在 0 元素)
- 小结
- C语言中的没有直接的字符串运算操作
- string.h工具包间接提供了对字符串的操作
- 通过 #include <string.h> 声明使用字符串工具包
- 借助工具包可进行字符串赋值,连接,比较等操作
六、数组专题练习
1.对一个数组进行倒序输出:
#include<stdio.h>
int main()
{
int a[] = {2, 3, 4, 5, 6, 7, 8, 9};
int len = sizeof(a) / (sizeof(*a));
int i = 0;
for(i = 0; i < len/2; i++)
{
int t = a[i];
a[i] = a[len - i -1];
a[len - i -1] = t;
}
for(i = 0; i < len; i++)
{
printf("%d ",a[i]);
}
return 0;
}
下面为输出结果:
2.房间里有50盏灯(编号为1-50),初始状态为打开。现在有10个学生(编号为2,4,6,...),每个学生进入房间后拨动灯的开关,规则为:当灯编号能够整除自己编号时才能拨动,完成后退出房间。问:当最后一个学生退出房间时,哪些灯依旧是打开状态?
#include <stdio.h>
#define LN 50
#define SN 10
int main()
{
int light[LN] = {0};
int student[SN] = {0};
int i = 0;
int j = 0;
for(i=0; i<LN; i++) light[i] = 1; // 1 表示灯的打开状态, 0 表示灯的关闭状态
for(i=0; i<SN; i++) student[i] = (i + 1) * 2; // 2, 4, 6, 8, ...
for(i=0; i<SN; i++)
for(j=0; j<LN; j++)
if( (j + 1) % student[i] == 0 )
{
light[j] = !light[j];
}
for(i=0; i<LN; i++)
if( light[i] )
printf("%d ", i + 1);
return 0;
}
下面为输出结果:
这里一个小技巧就是把灯开的状态设置为1,灯灭的状态设置成0,并存到数组中
3.字符数组边界问题,主要就是注意赋值后不能越界
#include <stdio.h>
#include <string.h>
int main()
{
int i = 0;
int e = 0;
char r[10] = ""; // char r[10] = {0};
for(i=0; i<strlen("abcd")+1; i++)
{
e = "abcd"[i];
printf("%d ", e);
}
printf("\n");
strcat(r, "abcd");
strcat(r, "efghi");
printf("r = %s\n", r);
return 0;
}
下面为输出结果:
4.字符串长度问题:设 char s[10] = "\n\\\r";则:s的长度为:
#include <stdio.h>
#include <string.h>
int main()
{
char s[10] = "\n\\\r";
printf("the length is %d\n",strlen(s));
return 0;
}
下面为输出结果:
\表示转义,\n,\\,\r均为转义字符
5.字符串倒序输出问题:
#include <stdio.h>
#include <string.h>
int main()
{
char s[] = "abcdefg";
int len = strlen(s);
int i = 0;
int j = 0;
for(i = 0, j = len-1; i < j; i++, j--)
{
char t = s[i];
s[i] = s[j];
s[j] = t;
}
printf("s = %s\n",s);
return 0;
}
下面为输出结果:
6.加深对字节大小,字符串长度的理解
#include <stdio.h>
#include <string.h>
int main()
{
char s[] = "abc\0de\0fg";
printf("%d %d\n", sizeof(s), strlen(s));
printf("%s\n",s);
return 0;
}
下面为输出结果:
字符数组的大小为10,而字符串的大小为3,因为字符串结束的字符就是第一次出现 0 元素。
7.将字符数组中的中间多余0元素删除,合并所有其它字符,进而构成一个字符串。
#include <stdio.h>
#include <string.h>
int main()
{
char s[] = "abc\0de\0fg";
int size = sizeof(s);
int i = 0;
int j = 0;
while( i < size )
{
if( s[i] == 0 )
{
for(j=i+1; j<size; j++)
{
s[j-1] = s[j];
}
size--;
}
else
{
i++;
}
}
printf("s = %s\n", s);
return 0;
}
下面为输出结果:
主要思想:当字符数组里面出现 0 元素,就把后面的元素向前移动一位。