分类
Level3

数组

程序 = 逻辑 + 数据,数组是存储数据的强而有力的手段。

矩阵(Matrix)是一个按照长方阵列排列的复数或实数几何,是高等代数学中的常见工具,也常见于统计分析等应用数学学科中。矩阵的研究历史悠久,拉丁方阵幻方在史前年代已有人研究。

输入两个学生的成绩, 我们可以定义两个变量来存储, 可是如果输入全班的学生的成绩, 怎么存放呢? 全校学生的成绩呢? 这时, 我们需要用到数组.
数组: 是相同数据类型组成的集合int, double, long long, 结构体 都可以作为组成元素。

一维数组定义
只声明,不赋值: 数据类型 数组名[大小]
声明,并赋初始值:数据类型 数组名[大小] = {数组元素}。
int b[10]; //定义数组b,没有赋初始值
int a[6] = {10, 20, 30, 40,}; //定义数组a,前四个元素初始为指定值数组定义长度为6,位置是从0号下标开始,到5号下标。如果访问到6号下标,则数组下标越界。下标为负数或者超过数组长度时,编程的编译不会报错,但是运行时可能发生意想不到的运行错误
数组所占内存等于所有元素所占的内存之和。最重要特性: 数组占用的内存是连续的一块内存

#include <iostream>
using namespace std;
int main(){
    int a[6] = {10, 20, 30, 40};
    for(int i = 0; i <= 10; i++) {  //观察这里会输出什么?
	cout << a[i] << " ";
    }
    return 0;
}

下面这样对吗?

int n;
cin >> n;
int a[n]; //数组的大小必须是整数常量,不能是变量,错误的用法
const int N = 10000;  //常量,使用中不可修改它的值
int a[N],b[N],c[N*3]; //可以这么用

选择题: c++中数组a[10], 下面表述正确的是()

A: a代表了是a[1]的地址
B: a代表了整个数组的元素
C: a代表了数组的首地址  //正确
D: a的值可以改变

数组的初始化
依次赋值 int a[3] = {1,2,3};
部分赋值 int a[3] = {1,};
单个赋值 int a[3]; a[0] = 1; a[1] = 2;

int main(){
int a[5] = {10, 1, };//等价于int a[5]={10,1,0,0,0}
int b[10];
b=a;//错,a、b是数组的首地址,只能通过元素赋值
int c[]={10, 1, 3};//数组长度是3的数组
char d[3]= {'a', 'b'};//等价于char d[]="ab";
return 0;
}

如果局部变量的数组一开始没有赋初值,数组中的每个元素都可能是一个随机数,并不一定是0,因此,如果想要给整个数组赋初值0,只需要把第一个元素赋值为0,或只用一个大括号来表示。当然,更推荐使用memset()函数。

int a[10]={0};   //全部元素会赋值为0
int a[10]={};  //全部元素会赋值为0

数组大小 sizeof(a) // 返回a占用内存的字节数. 不是数组元素的个数.

访问数组元素 只能访问下标为0 ~ size-1的元素。

#include<iostream>
using namespace std; 
int main(){
    int a[5]={1,2,3};
    cout<<a[0]<<a[1]<<a[2]<<endl; //输出下标0,1,2的值
    a[0]=5;  //修改下标0的值
    for(int i=0;i<5;i++){  //遍历数组a,数组所有下标的值
	cout<<a[i]<<' ';
    }
    return 0;
}

数组下标越界 数组下标不能是负数;下标应该在数组长度范围以内;每次写完数组的循环, 都要代入数组下标的边界值,确认是否越界

int a[N]; // 数组元素的下标值为 0 - (N-1)
for(int i = 0; i <= n; i++) {
    if(a[i] > a[i-1]) { //下标n不在长度范围内,不小心就越界
    }
}

内存操作函数
头文件:#include<cstring> //头文件

int a[10] = {10, 1, };
int b[10];
memcpy(b, a, sizeof(b));  //memcpy复制a中的10个元素到b

memset 使用是按字节赋值,即对每个字节赋同样的值,这样组成int型的4个字节就会被赋成相同的值。由于0的二进制补码是全0,-1的二进制补码为全1,不会出错。如果要对数组赋值其他数字,那么建议用循环赋值。初学者牢记memset不可将数组初始化成任意值,记住下面的常用赋初值方法:

int a[100];
memset(a, 0, sizeof(a));  //元素全部清0
memset(a, 0x7f, sizeof(a));  //元素全部初始化为极大值
memset(a, -1, sizeof(a));  //元素全部初始化为-1

数组类型

int a[100];    //定义一个整数数组,长度是100
char s[100];   //定义一个字符数组,长度是100
long long lla[100];   //定义一个long long数组,长度是100      
double d[100];   //定义一个double数组,长度是100  
... ...   

一维数组的应用模板

int  a[100000];  //定义一个整数数组,长度是100000
int n;
scanf("%d", &n);
for(int i = 0; i < n; i++) {
    scanf("%d", &a[i]);    //输入依次读入每个元素,
    // cin>>a[i]
}
for(int i=0;i<n;i++){ //循环遍历数组的每个元素
    printf("a[%d]=%d ", i, a[i]); //输出: 打印输出数组
    // cout<<a[i]<<' ';
}

数组的元素删除 删除第i个, 后面的元素要前移,需要遍历操作,挺麻烦的。
数组插入元素 i下标插入 x, 原来i和i之后的元素要后移动,需要遍历操作,挺麻烦的。
注意 如果数组大小较大(大概106级别),则需要将其定义在主函数外面(全局变量),否则程序异常退出。原因是函数内部申请的局部变量存储在系统栈,允许申请的空间较小;而函数外部申请的全局变量来自于静态存储区,允许申请的空间较大。在算法竞赛中使用全局静态数组又简单又方便

#include<cstdio>
int a[1000010];//很大的全局数组,自动初始化为0
int main(){
    memset(a, -1, sizeof(a));  //元素全部初始化为-1
    return 0;
}

二维数组 二维数组的元素在内存中是连续存放的,其本质也是按照一维数组的方式存储。二维数组也可以在定义时,对数组初始化。
int a[2][4]={{0,1,2,3},{7,2,9,5}};
int a[2][4]={0,1,2,3,7,2,9,5};
int a[2][4]={{0,1,2},{0}};
二维数组做参数时,第一维可以省略,后面定义第一维长度变量,第二维必须指定。
int sum(int a[][5],int n);
二维数组其本质也是按照一维数组的方式访问元素,需要根据所访问的元素行列下标计算出对应的下标。二维数组所占内存大小,是全部元素所占内存之和。

多维数组 当一维数组作为数组元素的时候, 就组成了多维数组,如二维、三维、四维、… 。以上课的教室为例,一般有多行和多列, 就组成了二维数组 学生[行][列]. 一个城市有多个学校, 一个学校有多个年级, 每个年级有多个班级, 每个班级又有多个行和列。所以可以学生[学校][年级][班级][行][列]。按照一维数组的方式访问元素,需要根据所访问的元素各维度下标计算出元素的位置。

计算机中的图像保存多用数组,比如:
2020 年初赛真题: 现有一张分辨率为2048*1024像素的32位真彩色图像, 请问要存储这张图像, 需要多大的存储空间? ( )
A 16MB
B 4MB
C 8MB
D 32MB
解析:32位是说这个图像的一个像素点需要32bit的数来表示. 所以有 2048 * 1024 * 32 / 8 = 8MB