3.1 数组
本章主要是介绍数组和字符串,两个都可以用来保存大量的数据。其中字符串可以视作一种特殊的字符数组,并且由于其特殊性,会介绍一些比较实用的方法和工具
数组的定义
“数组是指有序的元素序列。如果将有限个类型相同的变量的集合命名,那么这个名称就是数组名,而组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量。”
上面这段话引自 数组是什么意思 php中文网
(标记出来的是我觉得比较重要的要点)
通常,我们在c++中用这样的方式定义一个一维数组(通常hhhh)
#inlcude<stdio.h>
int main(){
数组里面的变量类型名+空格+[最多能够存储多少个该类型]
java中[]内可以不加数字表示没有固定数组的长度
但gcc的编译器这么做会报错
但可以像下面这样定义:
char a[]="dhiuaslhf 32`14324";
int b[]={1,2,3,4,5,6,7}
}
这里我举一个例子:
#include<stdio.h>
int main(){
int a[10];
}
这里我定义了一个可以放10个int型变量的a数组。
我们可以通过以下的方式给我们的数组赋值:
#include<stdio.h>
int main(){
int a[10]={1,2,3,4,5,6,7,8,9,10};
}
这个数组最多只能放10个int型的整数,如果放多了呢?

就会报出这样的错误
如果说我们只给数组的部分元素进行了赋值,那其他的元素的值会是多少呢?

其他几个元素的值是随机的(大概?反正不全是0就对了)
这里要注意的是对于a数组而言,第一个元素的下标是从0开始的,也就是说int a[10] 里面,包含的元素是a[0 - 9](懒得打字了,懂我意思就好2333
并且数组定义的大小是有范围的,因为调用的空间有限,有兴趣的可以尝试一下最大能定义到多少 (电脑:不,你没有兴趣)
如果你是大学科班出身的话,很可能还会学习到非常多其他的赋值方法,由于那里面很多的赋值方法不具有普遍性甚至我个人更倾向于认定为在卖弄,所以在这里并不多做赘述
书上提到了一段代码
a[n++]=x;
事实上就是这两个代码合并起来
a[n]=x;
n++;
事实上对于n++而言,优先对n进行a[n]=x的赋值运算,再进行n++的运算,如果是++n的话,就会优先进行n++的运算,再进行a[n]=x的赋值运算。
memcpy 函数
使用这个函数首先是要包含头文件<string.h>的
格式是这样的:memcpy(存储目标内容的目标数组名(存储目标内容的地址名指针),要复制的数据源(存储被复制的内容的地址名指针),需要被复制的变量个数n(理论上说应该是需要赋值的字节长度))
注意到我这里没有加sizeof,事实上如果不加sizeof,这里默认的是两个字符串,如果需要进行更改,第三个参数位置这么写即可:sizeof(变量类型)*n (元素个数)。全部复制的话直接:sizeof(复制的数据源的数组名即可)。
(这里我回头整理的时候想了一下可能是sizeof的一些用法,不过很可惜的是我并不擅长使用这个哎)
那如果要选择从哪里开始复制呢(不更改的话默认从头开始复制),需要更改第二个参数:要复制的数据源+开始复制的数组下标即可
选择从哪里存储被复制的元素的操作类似,更改第一个参数:存储目标内容的目标数组名+开始的下标
字符串(字符数组)的例子参考:想什么呢,当然是菜鸟教程啦
例子:
事实上如果你是虔诚的C语言使用者,那你用的更多的应该是strcpy函数,事实上这个函数的用法和规律和memcpy函数的用法和规律是非常相似的。
数组运用
还记得2.5排列的那道题目么,我们当时没有用数组的方法做,代码看起来非常的复杂,这里我们用数组重新再做一遍。
解决方案如下: (似乎也没有简单多少)
#include<stdio.h>
#include<string.h>//用于调用memset
int main(){
int a[10];//这个数组分别用来存储1-9中每个数字出现的次数,例如a[3]里面放的就是3出现了几次
for (int i=100;i<=333;i++){
memset(a,0,sizeof(a));//将数组a中所有元素全部赋值为0
//每次都要重新赋值一遍,防止对后面的循环造成影响
a[i%10]++;//i的个位十位百位
a[i%100/10]++;
a[i/100]++;
a[(2*i)%10]++;//2i的个位十位百位
a[(2*i)%100/10]++;
a[(2*i)/100]++;
a[(3*i)%10]++;//3i的个位十位百位
a[(3*i)%100/10]++;
a[(3*i)/100]++;
//现在a[1-9]分别放了其下标在这三个三位数中出现的次数
bool flag=true;//用于判断这个三位数是否符合条件
for (int j=1;j<=9;j++){
if (a[j]!=1)//如果1-9中的某个数不是出现一起且仅出现一次
{
flag=false;//直接跳出循环
break;
}
}
if (flag)//如果经历上面的循环,flag还没有变为false
printf("%d %d %d\n",i,i*2,i*3);
}
return 0;
}
这里运用了计数排序的思想
memset(a,0,sizeof(a))的作用是将a数组清零,它在string.h中定义。比起用for循环更加的快捷和方便
memset赋值赋值0就好了,不要想着把0的参数改了,去赋值其他的数字(-1是可以的),事实上memset本身也是运用于字符串的工具,里面很多的用法和我们想的不是一样的,有兴趣的可以参考: 某教程 和 memset用法详解
开灯问题
有 n 盏灯,编号为 1~n,第 1 个人把所有灯打开,第 2 个人按下所有编号为 2 的倍数的开关(这些灯将被关掉),第 3 个人按下所有编号为 3 的倍数的开关(其中关掉的灯将被打开,开着的灯将被关闭),依此类推。一共有 k 个人,问最后有哪些灯开着?输入:n 和 k,输出开着的灯编号。k≤n≤1000
样例输入: 7 3
样例输出: 1 5 6 7
分析:我们可以将这n盏灯看作数组里的n个元素,开始的时候都是关着的,我们可以将关着的这个状态定义为0 (bool 里面的 false),那么开灯就变为1。在模拟完整个过程后,遍历一遍下标为1-n的所有元素,元素值为1即为开着的灯,进行输出。
代码如下:
#include<stdio.h>
#include<string.h>//用于调用memset
int main(){
int a[1011],n,k;
memset(a,0,sizeof(a));
scanf("%d%d",&n,&k);
for (int i=1;i<=k;i++){//一共k个人,进行k轮
for (int j=i;j<=n;j+=i) //这样做的原因可以减少循环和判断的次数,直接找到1-n中被i整除的数
a[j]=1-a[j];//0-->1,1-->0;
}
for (int i=1;i<=n;i++)
if (a[i]) printf("%d ",i);//输出下标,即为处于开灯状态(1)的灯
return 0;
}
书本上first的定义是用于规范格式的,因为有的oj对输出格式要求非常高,由于我没交oj,所以没有做这方面的处理
蛇形填数
给定一个 n , 在 n * n 的方阵中填入 1 ,2, 3,……,n * n, 要求填成蛇形。
例如在 n = 5 时 , 如下所示:
13 14 15 16 1
12 23 24 17 2
11 22 25 18 3
10 21 20 19 4
9 8 7 6 5
)
分析:我拿到这道题的时候乍一看想用dfs做,分析了一下,发现它填数的顺序还是比较有规律的。我们这里考虑使用二维数组,首先定义左上角为1,然后先往下,再往左,再往上,再往右进行填数。那么怎么判断什么时候开始转弯呢,在这个方阵的周围围上一圈障碍物即可。
首先什么叫做二维数组呢?
二维数组本质上是以数组作为数组元素的数组,即“数组的数组”。
这样子是不是感觉非常复杂?说的更加通俗一点。就是本来的一维数组里面的每一个元素,我们定义为一个数组。二维数组中就含有多个的一维数组。
事实上,二维数组通常以矩阵和方阵的形式表现出来,a[m][n],其中m即为行,n为列。(关系型数据库)
代码如下:
#include<stdio.h>
#include<string.h>
int a[30][30];//定义在main函数外的我们称为全局变量,全局变量是可以被本程序所有对象或函数引用
int main(){
int n,x,y,tot=0;
scanf("%d",&n);
//x,y 为进行填数的格子的坐标
memset(a,0,sizeof(a));//将数组所有的元素赋值为0,为0的元素即为可以进行赋值(填数)的元素
for (int i=0;i<=n+1;i++){//设置障碍
a[i][0]=1;
a[i][n+1]=1;
a[0][i]=1;
a[n+1][i+1]=1;
}
}
代码写到这里,我们先输出来看一下:
n* n的方阵中,0的位置都是可以进行填数,那为什么我输出了n+1* n+1呢?事实上周围外面的一圈即做“障碍物”的作用,保证在填数的过程中不会,不会填到n*n的范围之外。
(我写完代码之后在这里想了一下,感觉似乎这圈“障碍物”不是完全必要的,但是为了保险起见,我还是预定义了一下)。
代码如下:
#include<stdio.h>
#include<string.h>
int a[30][30];//定义在main函数外的我们称为全局变量,全局变量是可以被本程序所有对象或函数引用
int main(){
int n,x,y,tot=0;
scanf("%d",&n);
//x,y 为进行填数的格子的坐标
memset(a,0,sizeof(a));//将数组所有的元素赋值为0,为0的元素即为可以进行赋值(填数)的元素
for (int i=0;i<=n+1;i++){//设置障碍
a[i][0]=1;
a[i][n+1]=1;
a[0][i]=1;
a[n+1][i+1]=1;
}
x=1;
y=n;//预定左上角的位置为开始填数的位置
a[x][y]=1;
tot=1;
while (tot<n*n){//填n*n个数
while (a[x+1][y]==0){//向下为可填的数则向下走
tot++;
a[x+1][y]=tot;
x++;
}
while (a[x][y-1]==0){//向左为可填的数向左走
tot++;
a[x][y-1]=tot;
y--;
}
while (a[x-1][y]==0){//向上为可填的数向上走
tot++;
a[x-1][y]=tot;
x--;
}
while (a[x][y+1]==0){//向右为可填的数向右走
tot++;
a[x][y+1]=tot;
y++;
}
//一圈做完进行下一轮,直到数被全部填完
}
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++)
printf("%6d",a[i][j]);
printf("\n");
}
}
书上的代码非常的简洁,因为他将很多++还有赋值的代码放到了一起,还使用了! 的定义。但是正如书上所说,利用C语言简洁的语法的前提是,代码的准确性,和可读性。所以还是建议先按照自己的习惯来保证正确性比较好一点。