C语言程序设计
chapter 4 数组
1. 数组的概念
数组:同类数据的集合
定义方式:类型 数组名[数组大小];
数组中的变量称为为数组元素,由于数组中每个元素都有下标,因此数组元素也称为下标变量。
数组下标取值从0开始,使用数组时下标不能越界,同一数组的所有数组元素在内存中占用一片连续的存储单元。
例如:int num[10];
//表示定义一个数组num,最多能存放10个int类型的元素。
解析:定义了一个名字为 num 的数组,它有10个元素,每个元素都是一个int型变量,下标变量为num[0]~num[9]
,num数组占用了一片连续的、大小为10*sizeof(int)字节的空间。
例如:int num[10][10];
//表示定义一个数组num,最多能存放10*10个int类型的元素。
像这样的用两个 [] 的定义的数组,我们称之为二维数组,同样还有三维数组,多维数组,但是需要注意的时,电脑内存分配是有限的,数组所占空间大小也是有限的。
2. 数组元素的引用
每个数组元素都是一个变量,数组元素可以表示为:数组名[下标]
其中,下标可以是任何值为整型的表达式,该表达式里可以包含变量和函数调用。引用时,下标值应在数组定义的下标值范围内。
例如:若i、j都是int型变量,则 num[5]、num[i+j]、num[i++]
都是合法的元素。
在定义一个一维数组的同时,可以给数组中的元素赋初值。
格式:类型名 数组名[元素个数]={值1,值2,……};
例如:int a[10]={0,1,2,3,4,5,6,7,8,9};
相当于:a[0]=0 a[1]=1 a[2]=2 a[3]=3 a[4]=4 …… a[8]=8 a[9]=9
使用数组时,要注意:
(1)数组下标为正整数
(2)在定义元素个数的下标范围内使用
当在程序中把下标写成负数或者大于元素个数时,程序编译不会出错,当然结果也是错的。
(3)定义数组时使用的容量大小必须使用常数,且需要注意:
在 C语言中常量的定义为:#define N 10000
在C++中还支持:const int N=1000;
但是这个方式在C语言中表示只读变量,不能等同于为常量。
3. 作用域
根据数组定义的位置,我们将数组分为全局变量和局部变量
全局变量:是指不被{}包裹起来的变量;
局部变量:是指被{}包裹起来的变量。
并且编译器会给这些数组变量初始化不同的值,具体如下:
全局变量,默认初始化为 0.
局部变量,随机初始化一个值.
局部变量,部分赋值,则未赋值部分初始化为 0.
#include<stdio.h>
#define N 100
int a[N]; //全局变量,默认初始化为 0
int b[N]={1,2,3,4,5}; //全局变量,部分赋值,其余默认初始化为 0
int main() {
int c[N];//局部变量,随机初始化一个值
int d[N]= {1,2,3,4,5};//局部变量,部分赋值,则未赋值部分初始化为 0
int n=10;
printf("a[i]:"); for(int i=0; i<=n; i++) printf("%d ", a[i]); printf("\n");
printf("b[i]:"); for(int i=0; i<=n; i++) printf("%d ", b[i]); printf("\n");
printf("c[i]:"); for(int i=0; i<=n; i++) printf("%d ", c[i]); printf("\n");
printf("d[i]:"); for(int i=0; i<=n; i++) printf("%d ", d[i]); printf("\n");
return 0;
}
输出结果:
a[i]:0 0 0 0 0 0 0 0 0 0 0
b[i]:1 2 3 4 5 0 0 0 0 0 0
c[i]:7603456 0 75 0 0 3 12260288 0 0 0 1
d[i]:1 2 3 4 5 0 0 0 0 0 0
4. 数组案例学习
【题目描述】现有 10 个数据 {0,1,2,3,4,5,6,7,8,9},将他们存储到内存中,并逆序输出。
#include<stdio.h>
int main(){
int n=10;
int a[10]={0,1,2,3,4,5,6,7,8,9};
for(int i=n-1; i>=0; i--){
printf("%d ", a[i]);
}
return 0;
}
【题目描述】现有 n 个整数数据,将他们存储到内存中,并逆序输出(n<=10000)。
输入样例:
5
45 12 34 89 21
输出样例:
21 89 34 12 45
#include<stdio.h>
#define N 10000
int a[N];
int main(){
int n; scanf("%d", &n);
for(int i=0; i<n; i++){ // [0, n-1]
scanf("%d", &a[i]);
}
for(int i=n-1; i>=0; i--){// [n-1, 0]
printf("%d ", a[i]);
}
return 0;
}
5. 最大值所在位置
【题目描述】输入 n 个正整数,存放在 a[1]~a[n] 中,输出最大值所在位置(n<=10000)。
输入样例:
5
1 2 6 4 5
输出样例:
3
#include<stdio.h>
#define N 10010
int a[N];
int main(){
int n, max_a=-1, max_i=-1; scanf("%d", &n);
for(int i=1; i<=n; i++){
scanf("%d", &a[i]);
if(max_a<a[i]){
max_a=a[i];
max_i=i;
}
}
printf("%d", max_i);
return 0;
}
6. 斐波那契数列
【题目描述】斐波那契数列指的是这样一个数列:0,1,1,2,3,5,8,13,21...
求数列的前 50 项,并按照从大到小的顺序输出。
无输入,输出样例部分如下:
50 : 7778742049
49 : 4807526976
48 : 2971215073
47 : 1836311903
46 : 1134903170
45 : 701408733
...
5 : 3
4 : 2
3 : 1
2 : 1
1 : 0
#include<stdio.h>
#define N 100
long long a[N]={0,0,1};
int main(){
int n=50;
for(int i=3; i<=n; i++){
a[i] = a[i-1]+a[i-2];
}
for(int i=n; i>=1; i--){
printf("%d : %lld\n", i, a[i]);
}
return 0;
}
7. 第几天
【题目描述】输入年、月、日,输出该天是这一年的第几天。
输入样例:2021 8 24
输出样例:236
#include<stdio.h>
int main(){
int a[12]={31,28,31,30,31,30,31,31,30,31,30,31};
int year,month,day; scanf("%d%d%d", &year, &month, &day);
int ans=day;
if(month>2){
if(year%400==0 || year%4==0&&year%100!=0) ans++;
}
for(int i=0; i<month-1; i++){
ans += a[i];
}
printf("%d", ans);
return 0;
}
8. 起立与坐下
【题目描述】有n个人,编号为 1~n。
开始时,所有人都站着,接着第2个人和2的倍数的人坐下,
然后,第3个人及3的倍数的人按相反的操作(坐的人站起来,站着的人坐下),
依次类推,一共操作到第k人及k的倍数。
问最后有哪些人站着。输入n和k,输出站着的人的编号。
输入样例:7 3
输出样例:1 5 6 7
#include<stdio.h>
#define N 10010
int a[N];//0表示站着,1表示坐着
int main(){
int n,k; scanf("%d%d", &n, &k);
for(int i=2; i<=k; i++){
for(int j=1; j<=n; j++){
if(j%i==0) a[j]=!a[j];
}
}
for(int i=1; i<=n; i++){
if(a[i]==0) printf("%d ", i);
}
return 0;
}
9. 排序
【题目描述】输入n个数,存在数组a中,每个数都是介于0~k之间的整数,
此处k为某个整数(n<=100000,k<=1000),按从小到大的顺序,输出a数组中的数据。
输入样例:
10
2 3 1 2 4 55 3 55 3 2
输出样例:
1 2 2 2 3 3 3 4 55 55
#include<stdio.h>
#define N 1010
int a[N];
int main(){
int n, k=1000; scanf("%d", &n);
for(int i=1; i<=n; i++){
int x; scanf("%d", &x);
a[x]++; //计数思想
}
for(int i=0; i<=k; i++){
for(int j=1; j<=a[i]; j++){
printf("%d ", i);
}
}
return 0;
}
10 . 二维数组
二维数组的定义格式:类型说明符 数组名[常量表达式1][常量表达式2]
例如:int a[3][4], b[5][6];
解释:定义 a为一个 3*4(3行4列)的数组,b为一个 5*6 (5行6列)的数组,且均为int类型。
用矩阵形式是在逻辑上的概念,可以形象的表示出行列关系。
在内存中,各元素是连续存放的,并不是二维的,而是线性的。
a[0][0] a[0][1] a[0][2] a[0][3]
a[1][0] a[1][1] a[1][2] a[1][3]
a[2][0] a[2][1] a[2][2] a[2][3]
b[0][0] b[0][1] b[0][2] b[0][3] b[0][4] b[0][5]
b[1][0] b[1][1] b[1][2] b[1][3] b[1][4] b[1][5]
b[2][0] b[2][1] b[2][2] b[2][3] b[2][4] b[2][5]
b[3][0] b[3][1] b[3][2] b[3][3] b[3][4] b[3][5]
b[4][0] b[4][1] b[4][2] b[4][3] b[4][4] b[4][5]
二维数组的初始化
1.分行赋值:int a[3][4]={{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
2.同行赋值:int b[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
上述两种赋值方式均是可行的,且结果是相同的。
a,b均为:
1 2 3 4
5 6 7 8
9 10 11 12
但是,如果按照如下方式赋值会出现不同的结果:
1.分行赋值:int a[3][4]={{1,2,3,4}, {5}, {9,10,11,12}};
2.同行赋值:int b[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
a:
1 2 3 4
5 0 0 0
9 10 11 12
b:
1 2 3 4
5 6 7 8
9 10 11 12
二维数组的赋值特性与一维数组相同,全局默认为0;局部部分赋值,未赋值部分为0;局部未赋值,则随机。
11. 二维数组案例学习
【题目描述】给定正整数 n, m,以及一个 n*m 的矩阵,请按原矩阵保存并输出,
同时输出该矩阵顺时针旋转 90度后的新矩阵,
输出的原矩阵以及新矩阵之间换行,且每个数据占 5个宽度。
输入样例:
2 4
1 2 3 4
5 6 7 8
输出样例:
1 2 3 4
5 6 7 8
5 1
6 2
7 3
8 4
#include<stdio.h>
#define N 100
int a[N][N];
int main(){
int n, m; scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++){
for(int j=1; j<=m; j++){
scanf("%d", &a[i][j]);
}
}
for(int i=1; i<=n; i++){
for(int j=1; j<=m; j++){
printf("%5d", a[i][j]);
}printf("\n");
}
printf("\n");
for(int i=1; i<=m; i++){
for(int j=n; j>=1; j--){
printf("%5d", a[j][i]);
}printf("\n");
}
return 0;
}
12. 杨辉三角
【题目描述】给出 n(n≤20),输出杨辉三角的前 n行。
如果你不知道什么是杨辉三角,可以观察样例找找规律
输入样例:6
输出样例:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
#include<stdio.h>
#define N 110
int a[N][N];
int main(){
int n=10; scanf("%d", &n);
a[1][1]=1;
for(int i=2; i<=n; i++){
for(int j=1; j<=i; j++){
a[i][j]= a[i-1][j-1]+a[i-1][j];
}
}
for(int i=1; i<=n; i++){
for(int j=1; j<=i; j++){
printf("%d ", a[i][j]);
}printf("\n");
}
return 0;
}
13. 蛇形方阵
【题目描述】给出一个不大于 9 的正整数 n,输出 n×n 的蛇形方阵。
从左上角填上 1 开始,顺时针方向依次填入数字,如同样例所示。
注意每个数字有都会占用 3 个字符,前面使用空格补齐。
输入样例:4
输出样例:
1 2 3 4
12 13 14 5
11 16 15 6
10 9 8 7
#include<stdio.h>
int a[10][10];
int main(){
int n; scanf("%d", &n);
a[1][1]=1;
int i=1, j=1, cnt=1;
while(cnt<n*n){
while(j+1<=n && a[i][j+1]==0){//右
a[i][++j] = ++cnt;
}
while(i+1<=n && a[i+1][j]==0){//下
a[++i][j] = ++cnt;
}
while(j-1>0 && a[i][j-1]==0){//左
a[i][--j] = ++cnt;
}
while(i-1>0 && a[i-1][j]==0){//上
a[--i][j] = ++cnt;
}
}
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
printf("%3d",a[i][j]);
}printf("\n");
}
return 0;
}
14. 字符数组
我们已经知道字符型数据是以字符的ASCII代码存储在存储单元的,一般占一个字节,由于ASCII代码也属于整数形式,因此在 C99标准中,把字符类型归纳为整型类型中的一种。
C语言中没有字符串类型,也没有字符串变量,字符串是存放在字符数组中的。
char c[11];// 定义一个大小为 11的字符数组 c
c[0]='H'; c[1]='e'; c[2]='l'; c[3]='l'; c[4]='o'; c[5]=' ';
c[6]='W'; c[7]='o'; c[8]='r'; c[9]='l'; c[10]='d';
int c[11]; //char类型本身就是以ASCII代码存储的,所以用 int也可以,但是浪费空间
c[0]='H'; c[1]='e'; c[2]='l'; c[3]='l'; c[4]='o'; c[5]=' ';
c[6]='W'; c[7]='o'; c[8]='r'; c[9]='l'; c[10]='d';
字符数组的初始化
列表初始化
char c[11]={'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};
把11个字符依次赋值给 c[0]~c[10]这 11个元素。
char c[]={'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};
如果没有给出数组长度,系统会自动根据初值个数确定数组长度,如上数组 c的长度会自动定位 11.
字符串常量初始化
char c[]={"c program"};
char c[]="c program"; //可以省略 {}
字符串结束标志 : '\0'
C系统在用字符数组存储字符串常量时会自动加一个 '\0' 作为结束符。
例如:"c program" 共 9个字符,字符串时存储在一维数组中的,在数组中它占 10字节,最后一个字节 '\0'是由系统自动加上去的。
字符数组并不要求它的最后一个字符为 '\0',甚至可以不包含 '\0',比如像如下写法也是合法的:
char c[9]={"c program"};
字符数组的输入输出
1.逐个输入输出:用格式符 "%c"
2.将整个字符串一次输入输出:用格式符 "%s"
char str[100];
scanf("%s", str); //不加 &, 在 C语言中 数组名代表该数组的第一个元素的地址,也就是该数组的起始地址。
printf("%s", str);
printf("%o", str); //以8进制形式输出数组的起始地址。
当然,字符数组也可以构建二维数组,甚至三维,多维,不过这一点就不多赘述了,有兴趣可以自己测试一下。
15. 字符串处理函数
注意:字符串处理函数的头文件:#include<string.h>
char str[10]="Hello World!";
puts(str); //输出字符串,puts()输出时会将 '\0'转换为 '\n'即输出完字符数组后换行
gets(str); //输入字符串,gets()返回字符数组的首地址,但是由于溢出问题在 C11已被移除
char str1[100]="Hello ", str2[]="World!";
strcat(str1, str2);//连接字符串,将 str2连接到 str1之后, 并返回 str1的首地址。(str1必须足够大)
注意:strcat连接前两个字符串都有 '\0', 连接时将 字符串 str1后的 '\0'取消,只在新串最后保留 '\0'.
strcpy(str1, str2);//复制字符串,将 str2复制到 str1中
注意:不能直接: str1="Hello "; str1=str2; 这两条语句都是不合法的。
strncpy(str1, str2, n);//复制前n个字符,将 str2的前 n个字符复制到 str1中
strcmp(str1, str2);//字符串比较,从左至右,按 单个字符的ASCII代码比较。
如果 str1 与 str2相等, return 0;
如果 str1 大于 str2, return 正整数;
如果 str1 小于 str2, return 负整数;
strlen(str);//返回字符串的实际长度
strlwr(str);//字符串大写转小写
strupr(str);//字符串小写转大写
char ch = tolower(ch);//单个字符转小写,头文件 <ctype.h>
char ch = toupper(ch);//单个字符转大写,头文件 <ctype.h>
16. 字符计数
【题目描述】给定一个字符串(长度不超过100)以及单个字符,
求该字符在字符串中出现的次数,不区分大小写。
输入格式:两行,第一行一个字符串,第二行单个字符。
输出格式:一个整数,表示该字符在字符串中出现的次数。
输入样例:
ABCabcA
a
输出样例:
3
题解:
#include<stdio.h>
#include<string.h>
#include<ctype.h>
char a[101], b;
int main(){
scanf("%s\n%c", a, &b);
int len=strlen(a), ans=0;
b=tolower(b);
for(int i=0; i<len; i++){
a[i] = tolower(a[i]);
if(a[i]==b) ans++;
}
printf("%d\n", ans);
return 0;
}
17. 寻找字符串
【题目描述】给定两个字符串 a, b,寻找字符串 b 在字符串 a 中出现的次数,字符可重叠。
如:a="abababa", b="aba",则 b 在 a中出现的次数为 3 次。
输入格式:两行,第一行为字符串 a,第二行为字符串 b,字符串长度不大于 1000。
输出格式:一个整数,表示字符串 b 在 a 中出现的次数。
题解:
#include<stdio.h>
#include<string.h>
#define N 1010
char a[N], b[N];
int main(){
fgets(a, 1001, stdin);//会读入\n, 也可以使用scanf("%s", a);
fgets(b, 1001, stdin);
int len1=strlen(a)-1, len2=strlen(b)-1;
int ans=0;
for(int i=0; i<len1; i++){
bool flag=1;
for(int j=0; j<=len2/2; j++){
if(a[i+j]!=b[j]){
flag=false; break;
}
}
if(flag) ans++;
}
printf("%d\n", ans);
return 0;
}
18. 对称字符串
题目描述:对于给定的正整数 N(N<=20), 有如下对应的对称字符串。
A[1]:A
A[2]:ABA
A[3]:ABACABA
A[4]:ABACABADABACABA
...
A[N]:...
输入格式:一个正整数 N。
输出格式:一行字符串A[N]。
题解:
#include<stdio.h>
#include<string.h>
#define N 1000000
char a[N];
int main(){
int n, len=0; scanf("%d",&n);
for(int i=1; i<=n; i++){
strcat(a+len+1, a);
a[len]='A'+i-1;
len = strlen(a);
}
printf("%s\n", a);
return 0;
}