7.数组


基本

  • int a[10];里的’[‘和’]'都是运算符。
  • vla,可变长数组,定义时用变量做长度,曾经支持过,但是会引发UB,现在不要用
  • 直接打数组名,出来的是第一个元素的地址。
  • 数组越界,如果越界的不多,可能刚刚好越到代码里定义的其他变量上,这个时候MSVC编译出来的程序甚至会觉得没问题,因为那个越界对应的地址确确实实是程序申请的内存,会认为你确实能改,导致其他的变量意外地就不对了。

一维数组

一维数组初始化

1
2
3
4
5
6
int a[10] = {1,2,3,4,5,6,7,8,9,10};
int a[10] = {1,2,3,4,5,6,7,8,9,10,11};//报错
int b[] = {1,2,3,4,5,6,7,8,9,10};//数组长度缺省?数组长度自动为10,因为后面有10个数字
int c[10] = {1,2,3};//下标3~9的全部自动为0.
int d[1000] = {0};//全部都是0了!但是理解方式应该为:将d[0]初始化为0,其他的元素自动为0
int e[1000] = {1};//将e[0]初始化为1,其他的元素自动为0。不是全1!
  • 再次强调,int e[1000] = {1};e[0]初始化为1,其他的元素自动为0。不是全1!
  • 需要说明的是:只要你有对数组进行初始化的这个动作,那么所有你没初始化到的元素全部都会初始化为0.所以你甚至可以这样,全部初始化为0
1
int a[10] = {};
  • 和之前的变量一样的,如果是静态局部,或者全局,都会自己初始化为0。如果是自动变量的数组,又没有做初始化这个动作,那么数组值都是意外值。

二维数组

  • int a[6][5];行在前,列在后。6行5列。
  • 内存里面存放也是这个顺序,a[0][0]a[0][1]……a[0][4]a[1][0]……
  • 对于二维数组的正确理解方式是:a是一个一维数组,有6个元素:a[0]a[1]……每个元素都是一个一维数组,每个一维数组有5个元素,比如a[0]这个数组有5个元素a[0][0]a[0][1]……
  • 更高维的数组,都遵循一样的逻辑

二维数组初始化

1
2
3
4
5
6
7
8
9
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};//按照先行后列的顺序,一个个排进去
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12,13};//报错
int a[3][4] = {{1,2,3,4},
{5,6,7,8},
{9,10,11,12}};//括号套括号
int a[3][4] = {{1},{2},{3}};//初始化了每行第一个数字,
int a[][4] = {1,2,3,4,5,6,7,8,9,10,11,12};//行缺省自动为3
int a[][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};//行缺省自动为3
int a[][4] = {1,2,3,4,5,6,7,8,9,10};//实际还是3行4列,a[2][2]、a[2][3]都是0
  • 对于二维数组,列标不可以省略!对于更高维数组,只有第一个值可以不省略,其他都要填写。逻辑是这样的,只有最外层的括号包含的二级括号的数量是编译器可以自己确定的,这代表了第一个长度,而再往里面走,每个都不一样,编译器其实不知道你要什么。
1
int b[][2][3] ={{……(不写里面的内容了)}};

数组的使用

  • for循环遍历数组,一般习惯是前闭后开:
1
2
3
for (int i = 0; i < N; i++){
xxx;
}
  • 固定的数组的长度可能要多次用到,最好的方法是去弄一个常变量,或者符号常量。

简单排序算法

  • 冒泡排序
1
2
3
4
5
6
7
8
9
10
11
//沈坚示例
int i, j;
for (i = 0; i < N - 1; i++) {
for (j = 0; j < N - (i - 1); j++) {
if (a[j] > a[j + 1]) {
t = a[j];
a[j] = a[j + 1];
a[j + 1] = t;
}
}
}
  • 选择排序
1
2
3
4
5
6
7
8
9
10
11
12
13
//沈坚示例
int i, j, k,t;
for (i = 0; i < 10; i++) {
k = i;
for (j = i + 1; j < n; j++) {
if (a[j] < a[k]) {
k = j;
}
}
t = a[i];
a[i] = a[k];
a[k] = t;
}

数组传入函数

  • 把数组名字作为实参传入函数,形参是对应类型的数组。因为本质上数组名是首地址,所以传递的时候,直接把函数外的数组给了函数内部使用,函数内部的修改会对外部产生影响。
  • 就是因为用的实际上是实参的那个数组,所以定义的形参数组不占用空间,是虚的。
  • (对于一维数组而言)定义形参数组可以指定数组长度也可以不指定长度。因为这个长度毫无作用,完全由传入的实参的长度决定。所以你乱写形参数组长度都无所谓。建议是不写。
1
2
void func(int a[]){//就这样定义形参的数组
}
  • 形参数组的类型和传入的实参数组类型必须完全一致,哪怕是intunsigned int这种区别都不可以有。
  • 多维数组本质都是一维数组。多维数组传入函数也和上面一样,多维数组形参的元素类型都必须和传入的实参的数组的元素类型一样。因此,多维数组只能让第一个长度缺省,后面所有的长度都必须指定并与实参保持一致。
1
2
3
4
void func(int a[][2][3][4][5]){//正确
}
void func(int a[][][][][]){//错误
}
  • C/C++的函数返回类型不能是数组。可以返回数组的指针来达到目的。
  • 数组定义以后就不能再用a={1,2,3,4,5};这种方法去整体赋值了,只能逐个修改。

字符数组

  • 字符数组专指类型为charunsigned char的数组。
  • 字符数组其余所有的特性都继承前面说的所有关于数组的特性。但是增加了部分特殊的特性。
  • C/C++原生只有字符串变量,没有字符串常量。原生的实现方法就是用带有‘\0’的字符数组实现。注意:只有带有’\0’才能表示字符串变量。
  • 字符串的长度:'\0’前的所有字符数量;字符数组的长度:定义时确定的长度
  • 除和普通数组一样初始化,字符数组还有一些特殊初始化方法:
1
2
3
4
5
6
char a[5]={"abcd"};
char a[4]={"abcd"};//错误。必须要考虑尾0
char a[5]="abcd";//大括号可省
char a[]="abcd";//长度缺省为5
char a[10]="abcd";//a[4]是因为字符串所以是\0,a[5]-a[9]默认初始化为\0,也就是a[4]-a[9]都是\0
char a[][10]={"abc","defg","hijklm"};
  • 道理和上面是一样的,字符数组定义以后不能再整体赋值,比如a = "qwert";这是不行的,很容易忽略这个问题。字符数组有专门的函数strcpy()可以做到这个。
  • 输出了非图形字符,那么就要认为这以后所有的输出都不可信,即使看上去好像对。但是会存在不对的情况。
  • 普通的数组直接拿数组名输出会输出一个地址,但是字符数组做了特殊处理,会按照字符串的形式从起始地址开始输出。(也就是说,如果你的字符数组里面没有\0(即不可以按照字符串理解),然后又直接输出,会发生越界)
  • #include<string>(C++)和#include<cstring>(C++)/#include<string.h>( C)是两个东西。前面的是字符串类,是比较符合直觉的一系列完整的字符串的体系;后面的是C风格字符串,引进的是一些处理字符数组的工具。所以说,C语言里面是用不到字符串变量的,实际上都还是字符数组。
  • MSVC会觉得c风格字符串这个库里部分函数不安全,不让用,加上#define _CRT_SECURE_NO_WARNINGS

C风格字符串的常用字符串处理函数

strlen(const char s[])

  • 求字符串长度,一直找到第一个’\0’,为止,在此之前所有字符的长度。
  • 返回值是长度

strcat(char dst[], const char src[])

  • 把src拼接到dst的后面,这个操作会导致dst被改动,src不会变化。返回值是拼接完成的dst
  • 为了能够成功拼接,必须保证dst的长度是足够把src拼进来的。否则这个函数不管的,直接给你越界。
  • 同样的,字符串到第一个\0就认为结束,剩下的他根本不管。这是这里所有函数的原则。
  • 拼接过去只会影响拼接的那一部分,如果dst里面后面有多的东西,而拼过去的时候又没有到,那些东西都是会保留下来的。

strncat(char dst[], const char src[], const unsigned int n)

只拼src的前n个字符。如果n大于了字符串src的长度,那就等于strcat();尾0还是会主动添加的。其他和上面一样。

strcpy(char dst[], const char src[])

  • 把src的内容(含尾零)复制到dst上面并覆盖原有内容。这个操作会导致dst被改动,src不会变化。返回值是拼接完成的dst
  • 为了能够成功拼接,必须保证dst的长度是足够容纳src里字符串的长度。否则这个函数不管的,直接给你越界。
  • 复制过去只会影响src长度的那一部分,如果dst里面后面有多的东西,而复制过去的时候又没有到,那些东西都是会保留下来的。

strncpy(char dst[], const char src[], const unsigned int len)

只精准复制src的前n个字符(包括‘\0’,也算作一个字符,这里没有当作字符串来讲,不会自动加尾0)到dst。如果len大于了字符串src的长度,之后的内容会被覆盖为0,不会停!n以后的还是保留原样

strcmp(const char s1[], const char s2[])

  • 比较s1和s2两个字符串。一位一位比下来,如果全部完全相等输出0,如果出现不一样,只看第一次出现不一样那里,s1的那一位大于s2,那就输出正数,s1小于s2,就输出负数。一般是输出+1和-1,有的编译器会输出差值。这里很不统一,但正负和0的结果肯定是统一的,所以一般只去和0比较。
  • 字符数组直接去用比较运算符那是在比较地址,做的不是这种比较。
  • 无需加#define _CRT_SECURE_NO_WARNINGS

strncmp(const char s1[], const char s2[], const unsigned int len)

  • 只比较前n个字符。n大于短串长度的话,比较到短串后的尾0就结束。
  • 无需加#define _CRT_SECURE_NO_WARNINGS
  • 其他规则一样。

  • c方式的字符处理,有一些很简单的函数,#include <cctype>(C++)/#include <ctype.h>( C)
  • sprintf(字符数组, "格式串", 输出表列),C方式输出到字符数组里。要加#define _CRT_SECURE_NO_WARNINGS。字符数组长度要足够,否则越界。
  • sscanf(字符数组, "格式串", 输入地址表列),C方式从字符数组输入。要加#define _CRT_SECURE_NO_WARNINGS