在C语言中我们经常用到数组,这其中有很多我们需要了解并且注意的地方。以下是我学习数组之后的一个简单总结。
这里我们详细说明一维数组和二维数组的相关知识。
一维数组
一维数组的创建
数组是一组相同类型元素的集合。数组的创建方式:
type_t arr_name [const_n];
例如:int arr[100];或者 char arr[100]等,但像int count = 10;int arr[count];这样的数组是无法正常创建的,所以在这里需要注意的一点是数组创建, []中要给一个常量才可以,不能使用变量。
一维数组初始化
数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。
例如:int arr1[10] = {1,2,3};(数组中有10个元素,前三个元素初始化为1,2,3 后面剩下的7个元素全部默认为0)
//数组中有多少个元素由初始化的内容决定,数组中有4个元素
int arr2[] = {1,2,3,4};
int arr3[3] = {1,2,3};
char arr4[3] = {'a',98, 'c'};
char arr5[] = {'a','b','c'};
char arr6[] = "abcdef";
//数组在创建的时候如果想不指定数组的确定的大小就得初始化。
//数组的元素个数根据初始化的内容来确定。
//对于下面的代码要进行区分
char arr1[] = "abcd";//数组中存放的是'a' 'b' 'c' 'd' '\0'
char arr2[4] = {'a','b','c','d'};//数组中存放的是'a' 'b' 'c' 'd'
char*p = "abcdef";//p中存放的是字符'a'的地址//
一维数组的使用
对于数组的使用我们之前学到过一个操作符: [],下标引用操作符。
它其实就是数组访问的操作符。[]需要接收两个操作符就是数组名和下标。
在数组的使用当中需要注意的两点是数组是使用下标来访问的,下标是从0开始。
数组的大小可以通过计算得到。例如 :
int arr[5];
int sz = sizeof(arr)/sizeof(arr[0]);
//其中sizeof的作用就是返回一个对象或者类型所占的内存字节数
一维数组在内存中的存储
看一段代码
int main()
{
int arr[5] = {0};
int i = 0;
for(i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
{
printf("&arr[%d] = %p\n", i, &arr[i]);
}
return 0;
}
运行结果如图所示:
由此可以看出,随着数组下标的增长,元素的地址也呈现一定规律的递增。由此得出结论:数组在内存中是连续存放的。
指针的初步介绍
为了很好的管理内存,我们首先对内存进行了一个详细的编址。所以,内存中的一个内存单元(字节)对应一个地址。在32位的平台上指针的大小是4个字节。64位平台是8个字节。所以我们在C语言中取出某个变量的地址。int n = 5; &n;(取出n的地址)。指针可以理解为是一个专门用来存放地址的一个变量。通常我们讲的指针其实是一个指针变量。当我们拿到指针的时候也要能找到指针所指向的变量,这里我们学过的*(解引用操作符)就可以派上用场了。int n = 1; int *p = &n;(将n的地址存放在p指针变量中) *p = 2;(改变的其实是n这个变量的内容)
一维数组的指针访问
看一段代码
#include <stdio.h>
int main()
{
int arr[] = {1,2};
printf("%p\n", arr);(已知这里会输出一个地址)
printf("%d\n", *arr);(对这个地址解引用)
return 0;
}
运行结果如图所示
由此可见,数组的数组名其实是数组首元素的地址。
下面一段代码:
#include <stdio.h>
int main()
{
int arr[3] = {0};
int i = 0;
for(i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
{
printf("&arr[%d] = %p\n", i, &arr[i]);
printf("%p\n", arr+i);
}
return 0;
}
运行结果如下:
从结果我们不难发现,通过对数组名+整数的运算,其实可以获取到数组每个元素的地址。这样我们就可以使用指针来访问我们的数组了。
二维数组
二维数组的创建和初始化
二维数组的创建和初始化与一维数组相差无几。这里举例予以说明:
二维数组的创建
int arr[1][2];
char arr[2][5];
double arr[3][3];
二维数组的初始化
int arr[3][4] = {1,2,3,4};
int arr[3][4] = {{1,2},{4,5}};
int arr[][4] = {{2,3},{4,5}};
二维数组的使用:二维数组的使用同样是通过下标的方式。
二维数组在内存中的存储:实际上,二维数组也和一维数组一样在内存中是连续存储的。
可以通过代码,观察结果:
#include <stdio.h>
int main()
{
int arr[2][2];
int i = 0;
for(i = 0; i < 2; i++)
{
int j = 0;
for(j = 0; j < 2; j++)
{
printf("&arr[%d][%d] = %p\n", i, j,&arr[i][j]);
}
}
return 0;
}
运行结果如下:
通过对结果的观察我们也不难发现二维数组在内存中是连续存储的这一特点。
二维数组的指针访问
现在我们知道二维数组在内存中其实是和一维数组一样连续存储的。同样,那么我们也可以通过指针来访问我们的二维数组。如下代码运行结果为1 2 3 4:
#include <stdio.h>
int main()
{
int arr[2][2] = {0};
int *p = &arr[0][0];
int i = 0;
for(i=0; i<2*2; i++)
{
*(p+i)= i;
}
for(i=0; i<2; i++)
{
int j = 0;
for(j=0; j<2; j++)
{
printf("%d ", arr[i][j]);
}
}
return 0;
}
有关数组的运算
学习数组,我们对于其必要的相关运算也必须要熟练掌握。在这里事先说明两点:数组名代表整个数组的时候只有两种情况:1.sizeof(数组名),这里的数组名表示整个数组。2.&数组名,这里的数组名表示整个数组。除1 2以外所有的数组名均表示首元素地址。
接下来用代码实例说明:
int a[] = {1,2,3,4};
//输出结果为16。a代表整个数组,求的是整个数组的大小
printf("%d\n",sizeof(a));
//输出结果为4。a表示首元素地址,加0还是首元素地址,地址大小均为4个字节
printf("%d\n",sizeof(a+0));
//输出结果为4。a表示首元素地址,对其解引用,求得是首元素的大小,int型为4个字节
printf("%d\n",sizeof(*a));
//输出结果为4。首元素地址加1,表示的是数组第2个元素的地址
printf("%d\n",sizeof(a+1));
//输出结果为4。数组第2个元素的大小
printf("%d\n",sizeof(a[1]));
//输出结果为4。取得是首元素的地址
printf("%d\n",sizeof(&a));
//输出结果为4。取出首元素的地址后加1表示求第二个元素的地址大小
printf("%d\n",sizeof(&a+1));
//输出结果为4。取首元素的地址求大小
printf("%d\n",sizeof(&a[0]));
//输出结果为4。取首元素的地址再加1,求数组第二个元素的地址大小
printf("%d\n",sizeof(&a[0]+1));
char arr[] = {'a','b','c','d','e','f'};
//输出结果是一个随机值。arr代表整个数组,计算时遇到‘\0’结束
//而在该数组中没有‘\0’,因此是一个随机值
printf("%d\n", strlen(arr));
//输出结果是一个随机值。arr表示首元素地址,+0依旧是首元素地址
//计算同上一条语句
printf("%d\n", strlen(arr+0));
//arr表示首元素地址,解引用表示首元素,而strlen无法求一个确定的内容的元素个数
//因此该条语句是错误的
printf("%d\n", strlen(*arr));
//情况同上一条语句
printf("%d\n", strlen(arr[1]));
//取得是首元素的地址,计算同该段代码第一条语句
printf("%d\n", strlen(&arr));
//输出结果是随机值,取首元素的地址,+1后是第二个元素的地址
//从第二个元素开始计数遇到‘\0’结束
printf("%d\n", strlen(&arr+1));
//输出结果是随机值,计算同上一条语句
printf("%d\n", strlen(&arr[0]+1));
char *p = "abcdef";
//输出结果是4,p代表的是一个地址
printf("%d\n", sizeof(p));
//输出结果是4,p表示首字符的地址+1就是第二个字符的地址
printf("%d\n", sizeof(p+1));
//输出结果是1,p是该字符串第一个字符的地址,解引用后就是第一个字符
printf("%d\n", sizeof(*p));
//输出结果是1,p[0]是该字符串第一个字符的地址
printf("%d\n", sizeof(p[0]));
//输出结果是4,取得是首字符的地址
printf("%d\n", sizeof(&p));
//输出结果是4,取首字符的地址后+1就是第二个字符的地址
printf("%d\n", sizeof(&p+1));
//输出结果是4,取首字符的地址后+1就是第二个字符的地址
printf("%d\n", sizeof(&p[0]+1));
//输出结果是6,p代表的是字符串的地址,从字符a开始往后直到遇到‘\0’结束
printf("%d\n", strlen(p));
//输出结果是5,p表示首字符的地址+1就是第二个字符的地址
//从字符b开始往后直到遇到‘\0’结束
printf("%d\n", strlen(p+1));
//解引用表示首元素,而strlen无法求一个确定的内容的元素个数
//因此该条语句是错误的
//printf("%d\n", strlen(*p));
//情况同上一条语句
//printf("%d\n", strlen(p[0]));
//输出结果是随机值
printf("%d\n", strlen(&p));
//输出结果是随机值,此时+1跳过了整个字符串,不知道什么时候会遇到‘\0’
printf("%d\n", strlen(&p+1));
//输出结果是5,p+1是p的下一个地址
printf("%d\n", strlen(&p[0]+1));
对于二维数组a[3][4],我们可以将其看作是一维数组,该数组中有3个一维数组a[0],a[1],a[2],每个一维数组中有4个元素。这种思想在我们以后的数组学习和使用中至关重要。
int a[3][4] = {0};
//输出结果为48,a表示整个数组
printf("%d\n",sizeof(a));
//输出结果为4,a[0][0]表示数组中第一个元素
printf("%d\n",sizeof(a[0][0]));
//输出结果为16,a[0]表示该二维数组的首元素的地址,需要注意的是
//此首元素是该二维数组中的第一行元素的集合
printf("%d\n",sizeof(a[0]));
//输出结果为4,a[0]表示该二维数组的首元素的地址,需要注意的是
//此首元素是该二维数组中的第一行元素的集合,+1就是第二个元素
//同样的,此第二个元素是该二维数组中第二行元素的集合
printf("%d\n",sizeof(a[0]+1));
//输出结果为4,首元素的地址+1就是第二个元素的地址
printf("%d\n",sizeof(a+1));
//输出结果为4,取得是该二维数组的首元素的地址,
//此首元素是该二维数组中的第一行元素的集合,+1就是第二个元素
//同样的,此第二个元素是该二维数组中第二行元素的集合
printf("%d\n",sizeof(&a[0]+1));
输出结果为16,对该二维数组的首元素地址解引用后就是首元素
printf("%d\n",sizeof(*a));
数组作为函数参数
有时候我们在写代码时,会将数组作为参数传个函数。这里需要注意的是,数组作为函数参数的时候,不会把整个数组的传递过去。实际上只是把数组的首元素的地址传递过去了。所以即使在函数参数部分写成数组的形式:int arr[ ]表示的依然是一个指针: int *arr。