Bootstrap

C++/C基础知识-数组(详解)

目录

数组简介

一、一维数组的创建和初始化

1.数组的创建

2.数组的初始化

3.一维数组的使用

二、二维数组的创建和初始化

1.二维数组的创建

2.二维数组的创建

3.二维数组的创建

4.二维数组在内存中的存储

三、数组越界

数组名是什么?

一位数组

一维数组的创建和初始化

数组的创建

数组的初始化

一维数组的使用

一维数组在内存中的存储

二维数组 

变长数组

变长数组仍然是静态数组

一种“自欺欺人”的写法


数组简介

一、一维数组的创建和初始化

1.数组的创建

数组是一组相同类型元素的集合。
数组的创建方式:

type_t   arr_name[const_n];
//type_t 是指数组的元素类型
//const_n 是一个常量表达式,用来指定数组的大小

数组创建的实例:

//代码1
int arr1[10];
//代码2
int count = 10;
int arr2[count];
//数组时候可以正常创建?
//代码3
char arr3[10];
float arr4[1];
double arr5[20];

:数组创建,[]中要给一个常量才可以,不能使用变量。

2.数组的初始化

数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。
看代码:

int arr1[10] = { 1,2,3 };
int arr2[] = { 1,2,3,4 }; 
int arr3[5] = { 1,2,3,4,5 };
char arr4[3] = { 'a',98, 'c' }; 
char arr5[] = { 'a','b','c' }; 
char arr6[] = "abcdef";

数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。
但是对于下面的代码要区分,内存中如何分配。

char arr1[] = "abc";
char arr2[3] = {'a','b','c'};

3.一维数组的使用

对于数组的使用我们之前介绍了一个操作符:[],下标引用操作符。它其实就数组访问的操作符。我们来看代码:

#include <stdio.h>
int main(){    
	int arr[10] = {0};
	//数组的不完全初始化   
	//计算数组的元素个数    
	int sz = sizeof(arr)/sizeof(arr[0]);  
	//对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以:    
	int i = 0;
	for(i=0; i<10; ++i)    {
		printf("%d ", arr[i]);   
	}    return 0;
}

总结:

        1.数组是使用下标来访问的,下标是从0开始。
        2.数组的大小可以通过计算得到。

4.一维数组在内存中的存储
接下来我们探讨数组在内存中的存储。
看代码

#include <stdio.h>
int main() {
    int arr[10] = { 0 };
    int i = 0;
    for (i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i) {
        printf("&arr[%d] = %p\n", i, &arr[i]);
    }
    return 0;
}

我们知道,随着数组下标的增长,元素的地址,也在有规律的递增。
由此可以得出结论:数组在内存中是连续存放的。

二、二维数组的创建和初始化

1.二维数组的创建

//数组创建
int arr[3][4];
char arr[3][5];
double arr[2][4];

2.二维数组的创建

//数组初始化
int arr[3][4] = {1,2,3,4};
int arr[3][4] = {{1,2},{4,5}};
int arr[][4] = {{2,3},{4,5}};

3.二维数组的创建

二维数组的使用也是通过下标的方式。
看代码:

#include <stdio.h>
int main(){   
    int arr[3][4] = {0};   
    int i = 0;   
    for(i=0; i<3; i++)    {
        int j = 0;
        for(j=0; j<4; j++)        {
            arr[i][j] = i*4+j;      
        }  
          }    
    for(i=0; i<3; i++)    {
        int j = 0;
        for(j=0; j<4; j++)        {
            printf("%d ", arr[i][j]);     
        }   
         }    
    return 0;
}

4.二维数组在内存中的存储

像一维数组一样,这里我们尝试打印二维数组的每个元素。

#include <stdio.h>
int main(){   
    int arr[3][4];  
    int i = 0;    
    for(i=0; i<3; i++)    {
        int j = 0; 
        for (j = 0; j < 4; j++) { 
            printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);
        }
    }   
    return 0;
}

在这里插入图片描述

通过结果我们可以分析到,其实二维数组在内存中也是连续存储的。

三、数组越界

数组的下标是有范围限制的。
数组的下规定是从0开始的,如果输入有n个元素,最后一个元素的下标就是n-1。
所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。
C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,所以程序员写代码时,最好自己做越界的检查。

#include <stdio.h>
int main(){    
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};  
    int i = 0;    
    for(i=0; i<=10; i++)    {
        printf("%d\n", arr[i]);//当i等于10的时候,越界访问了   
    }    
    return 0;
}

二维数组的行和列也可能存在越界。

数组名是什么?

#include <stdio.h>
int main(){    
    int arr[10] = {1,2,3,4,5};    
    printf("%p\n", arr);   
    printf("%p\n", &arr[0]);   
    printf("%d\n", *arr);    
    //输出结果    
    return 0;
}

结论:
数组名是数组首元素的地址。(有两个例外)
如果数组名是首元素地址,那么:

int arr[10] = {0};
printf("%d\n", sizeof(arr));

为什么输出的结果是:40?

补充:

sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
&数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
除此1,2两种情况之外,所有的数组名都表示数组首元素的地址。

一位数组

一维数组的创建和初始化

数组的创建

数组是一组相同类型元素的集合。

数组的创建方式:

type_t arr_name [const_];
//type_t 是指数组的元素类型
//const_n 是一个常量表达式,用来指定数组的大小

数组创建的实例

//代码1
int arr1[10];
//代码2
int count = 10;
int arr2[count];//数组不可以正常创建
//代码3
char arr3[10];
float arr4[1];
double arr5[20];

注:数组创建,【】中必须是一个常量,使用变量则会报错。

数组的初始化

int arr1[10] = {1,2,3};
int arr2[] = {1,2,3,4};
int arr3[5] = {1,2,3,4,5};//完全初始化
char arr4[] = {'a',98,'c'};
char arr5[] = {'a','b','c'};
char arr6[] = "abcdef";

数组在创建得时候如果想不指定数组得确定的大小就得初始化。数组得元素个数根据初始化得内容来确定。
但是对于下面得代码要区分,内存中如何分配?

char arr1[] = "abc";
char arr2[3] = {'a','b','c'};
int main()
{
    char arr1[] = "abc";
    char arr2[3] = {'a','b','c'};
    printf("%d\n", sizeof(arr1));//输出4
    printf("%d\n", sizeof(arr2));//输出3
    printf("%d\n", strlen(arr1));//输出3
    printf(""%d\n, strlen(arr2));//输出一个随机值
    return 0;
}

解释如下:
arr1 ----> a b c \0 ----> 占4个字节(sizeof)
arr2 ----> a b c ----> 占3个字节(sizeof)
arr1 ----> a b c \0 ----> 占3个字节(strlen)
arr2 ----> a b c ----> 随机值(strlen)因为后面遇到\0是随机的,不确定的

#include <stdio.h>
#include <string.h>
 
int main()
{
    //创建一个数组 - 存放整形 - 10个
    //int arr[10] = {1,2,3};//不完全初始化,剩下的元素默认初始化为0
    //char arr2[5] = {'a','b'};//将b改为98也是可行的,98是b的
    //char arr3[5] = "ab";//ok
    char arr4[] = "abcdef";
    printf("%d\n", sizeof(arr4));//输出结果是7,
    //sizeof 计算 arr4所占空间的大小
    printf("%d\n", strlen(arr4));//输出结果是6,strlen - 遇到\0停止,不记录
    //strlen 求字符串的长度
    //[a b c d e f \0]
    //int n = 5;
    //char ch[n];//err
    return 0;
}

1、strlen 和 sizeof 没有什么关联
2、strlen 是求字符串长度的 - 只能针对字符串求长度 - 库函数 - 使用得引用头文件
3、sizeof 计算变量、数组、类型得大小 - 单位是字节 -操作符

一维数组的使用

对于数组的使用我们之前介绍了一个操作符:【】,下标引用操作符。它其实就是数组访问的操作符。代码如下:

#include <stdio.h>
#include <string.h>
 
int main()
{
    char arr[] = "abcdef";//[a][b][c][d][e][f][\0]
    printf("%c\n", arr[3]);
    int i = 0;
    //int len = strlen(arr);
    //for (i = 0; i < len; i++)
    for (i=0; i<(int)strlen(arr);i++)
    {
        printf("%c ", arr[i]);
    }
    return 0;
}


#include <stdio.h>
#include <string.h>
int main()
{
    int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    //对数组内容赋值,数组是使用下标来访问的,下标从0开始,所以:
    printf("%d\n", sz);
    int i = 0;//做下标
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    
    return 0;
}

总结:
1、数组是使用下标来访问的,下标是从0开始的;
2、数组的大小可以通过计算来得到;

一维数组在内存中的存储

代码如下:

#include <stdio.h>
 
int main()
{
    int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("&arr[%d] = %p\n",i, &arr[i]);
    }
    return 0;
}

在这里插入图片描述

 随着数组下标的增长,元素的地址,也在有规律的递增。由此可以得出结论:数组在内存中是连续存放的。

二维数组 

前面介绍了一维数组,接下来介绍如何定义和使用二维数组。

二维数组定义的一般形式如下:

类型说明符 数组名[常量表达式1][常量表达式2]

与一维数组的定义唯一的不同是多了一个常量表达式2,其中,常量表达式1为第一维的长度,常量表达式2为第二维的长度。通常在处理二维数组的时候,为了便于理解,都将数组视为一个矩阵,常量表达式1表示矩阵的行数,而常量表达式2表示矩阵的列数。与一维数组一样,在定义二维数组时,常量表达式同样不能为变量。下面先通过一段代码来看二维数组的定义。

#include<stdio.h>
 
#define M 4
#define N 3
 
 
int main() {
  int arr[M][N];
  for (int i = 0; i < M; i++)
  {
    for (int j = 0; j < N; j++)
    {
      printf("&arr[%d][%d]=%d\t", i, j, &arr[i][j]);
    }
    printf("\n");
  }
  return 0;
}

运行结果:在这里插入图片描述

 将二维数组arr视为一个矩阵,下图显示了数组中每个元素在矩阵中的存放位置。image.png

 数组中各个元素在矩阵中对应的位置由二维数组的两个下标决定。我们可以将定义的二维数组int arr[4][3]视为由arr[4]和int [3] 两部分构成,将arr[4]视为一个整型一维数组,其中含有4个元素arr[0]、arr[1]、arr[2]、arr[3],每个元素都是int[3]类型的,也就是说,每个元素又是一个一维数组,每个一维数组含有3个元素,如arr[0]含有arr[0][1]、arr[0][1]、arr[0][2]三个元素。

知道了二维数组的这种特殊结构之后,接下来通过下图来了解二维数组在内存中的存储结构。image.png

 通过上述二维数组在内存中的存储结构图可以发现,二维数组中的所有元素都存储在一片连续的内存单元中,所占用的内存大小为元素类型所占用的内存大小乘以第一维及第二维的长度。如果以矩阵的方式来分析二维数组的存储方式,那么先从矩阵第一行从左往右依次存储完所有元素,然后按照同样的方法存储第二行的所有元素,直到存储完所有数组元素为止。

接下来再看一个二维数组的示例:

任意输入一个3行3列的二维数组,求对角元素之和

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
 
int main() {
  int arr[3][3];
  int i, j, sum = 0;
  printf("please input:\n");
  for (i = 0; i < 3; i++)
  {
    for (j = 0; j < 3; j++)
    {
      scanf("%d", &arr[i][j]);
    }
  }
  for (i = 0; i < 3; i++)
  {
    for ( j = 0; j < 3; j++)
    {
      if (i==j)
      {
        sum += arr[i][j];
      }
    }
  }
  printf("the result is: %d\n", sum);
  return 0;
}

运行结果如下:

在这里插入图片描述

变长数组

在《C语言的三套标准:C89、C99和C11》一节中我们讲到,目前经常使用的C语言有三个版本,分别是 C89、C99 和 C11。C89(也称 ANSI C)是较早的版本,也是最经典的版本,国内大学几乎都是以该版本为基础进行授课。C99 和 C11 是后来对 C89 的升级,增添了一些新内容(不多),语法更加灵活了,同时兼容 C89。

各种编译器都能很好地支持 C89 标准,但对 C99 的支持却不同:开源组织的 GCC 和 Xcode 使用的 LLVM/Clang 已经支持了大部分(几乎全部)的 C99 标准,而微软的 VC、VS 对 C99 却不感兴趣,直到后来的 VS2013、VS2015、VS2017 才慢慢支持,而且支持得还不好。

为什么要讨论这个问题呢?因为 C89 和 C99 对数组做出了不同的规定:

·在 C89 中,必须使用常量表达式指明数组长度;也就是说,数组长度中不能包含变量,不管该变量有没有初始化。
·而在 C99 中,可以使用变量指明数组长度。

下面的代码使用常量表达式指明数组长度,在任何编译器下都能编译通过:

 下面的代码使用变量指明数组长度,在 GCC 和 Xcode 下能够编译通过,而在 VC 和 VS(包括 VC 6.0、VS2010、VS2013、VS2015、VS2017 等)下都会报错:

 在实际编程中,有时数组的长度不能提前确定,如果这个变化范围小,那么使用常量表达式定义一个足够大的数组就可以,如果这个变化范围很大,就可能会浪费内存,这时就可以使用变长数组。请看下面的代码:

 在 GCC 和 Xcode 下的运行结果:
Input string length: 100↙
Input a string: http://c.biancheng.net/cpp/u/jiaocheng/↙
http://c.biancheng.net/cpp/u/jiaocheng/

变量的值在编译期间并不能确定,只有等到程序运行后,根据计算结果才能知道它的值到底是什么,所以数组长度中一旦包含了变量,那么数组长度在编译期间就不能确定了,也就不能为数组分配内存了,只有等到程序运行后,得到了变量的值,确定了具体的长度,才能给数组分配内存,我们将这样的数组称为变长数组(VLA, Variable Length Array)。

普通数组(固定长度的数组)是在编译期间分配内存的,而变长数组是在运行期间分配内存的。

变长数组仍然是静态数组

注意,变长数组是说数组的长度在定义之前可以改变,一旦定义了,就不能再改变了,所以变长数组的容量也是不能扩大或缩小的,它仍然是静态数组。以上面的代码为例,第 8 行代码是数组定义,此时就确定了数组的长度,在此之前长度可以随意改变,在此之后长度就固定了。

一种“自欺欺人”的写法

有些初学者在使用变长数组时会像下面一样书写代码:

int n;
int arr[n];
scanf(%d",&n);

先定义一个变量 n 和一个数组 arr,然后用 scanf() 读入 n 的值。有些初学者认为,scanf() 输入结束后,n 的值就确认下来了,数组 arr 的长度也随即确定了。这种想法看似合理,其实是蛮不讲理,毫无根据。

从你定义数组的那一刻起(也就是第二行代码),数组的长度就确定下来了,以后再也不会改变了;改变 n 的值并不会影响数组长度,它们之间没有任何“联动”关系。用 scanf() 读入 n 的值,影响的也仅仅是 n 本身,不会影响数组。

那么,上面代码中数组的长度究竟是什么呢?鬼知道!n 是未初始化的局部变量,它的值是未知的。

修改上面的代码,将数组的定义放到最后就没问题了:

int n;
scanf("%d",&n);
int arr[n];

在定义数组之前就输入 n 的值,这样输入的值才有用武之地。

;