C语言:数组与指针
1.前言:
在我们生活中,当我们需要将相同类型的物品放在到一起时,我们可以使用篮子,竹筐之类的作为分类的载体,盛放在一起,便于之后的查找,在编程中为了达到这一目的,便有了数组
众所周知,内存被分为了很多个内存单元,但假如这些内存单元没有了自己的编号时,那编译器就很难寻找其中的元素,就如生活中当我们需要进入一个大楼中访问自己朋友家时,倘若没有门牌号,我们就需要一间一间的寻找,但倘若有了门牌号,我们就只需要到相应的楼层去找相应的房间,同理,内存单元当有了相应的编号,便可以提高效率,所以便有了地址,即指针
2.数组
1.一维数组
1.数组的概念
相同数据类型的集合就是数组
何为一维数组呢?
首先,一维数组就是像下面这个例子一样
int arr[10]={1,2,3,4,5,6,7,8,9,10}
就是只有如一条线一样的排布,就像我们所知的一维,点和线一样
但是,在这里有一点需要我们注意的是:数组的每一个数据都有自己的小标,并且下标是从0开始的
#include <stdio.h>
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
printf("%d %d\n",arr[0],arr[8]);
return 0;
}
由此,我们可以做到将整个数组打印出来
#include <stdio.h>
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int sz=sizeof(arr)/sizeof(arr[0]);
for(int i=0;i<sz;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
return 0;
}
在此需要说明一点数组通常是指该元素的首地址,除了一下两种情况
- 使用了sizeof(数组名)
- &数组名,此时取的是整个数组的地址
2.解释及要求
那么接下来我们将要对上面的数组开始解释
首先,[ x ],这里的x是指这个数组的最大能容纳的元素数量,例如上面的arr[10],10指的就是arr这个数组最多能容纳10个元素
对此,当然 [ ] 中可以不写数值,让编译器自己去完成这项工作
例如:
#include <stdio.h>
int main()
{
int arr[]={1,2,3,4,5};
//这时在将鼠标放在arr上,就会发现[]中,已经由编译器填入应有的数据
return 0;
}
当然,既然有[ ]对数组的容纳量有要求,就不能使后面的元素大于其最大容纳量,否则会报错
初始值设定值太多
就是指我们的放入这个数组的元素已经超过了数组的最大容纳量了,这种也称为访问违规
当我们填入的数据不足填满时,编译器会自动帮我们在该数组剩下的空位置填0
#include <stdio.h>
int main()
{
int arr[10]={1,2,3,4,5};
return 0;
}
通过监视,我们可以知道,剩下空位被填0了
3.数组类型
接下来,就是定义数组前的数据类型是指该数组所存放的元素的类型
int arr1[5]={1,2,3,4,5};
char arr2[5]="abcde";
float arr3[5]={1.0,2.0,3.0,4.0,5.0};
数组后面存放的内容是什么,前面的数据类型就是对应的
假如不这样做,可能会导致发生一些错误,众所周知,int的字节是4,而char的字节是1,其范围是-128~127,若用char定义的数组去存放int类型的数据,会使该int类型的数据从32位直接被截成8位,这时就可能出现错误
例如:
#include <stdio.h>
int main()
{
char arr[]={5000};
return 0;
}
除此之外,前面的数据类型也从一方面写出了该数组的大小
#include <stdio.h>
int main()
{
int arr1[10]={0};//4*10=40
char arr2[10]={0};//2*10=20
double arr3[10]={0};//8*10=80
printf("%zu\n",sizeof(arr1));
printf("%zu\n",sizeof(arr2));
printf("%zu\n",sizeof(arr3));
return 0;
}
还有就是,这些数据类型也从一方面写出了当我们下标+1时,数组中地址增加的量
#include <stdio.h>
int main()
{
int arr1[10]={0};
printf("%p\n",&arr1[0]);
printf("%p\n",&arr1[1]);
char arr2[10]={0};
printf("%p\n",&arr2[0]);
printf("%p\n",&arr2[1]);
return 0;
}
这里,我们可以看见int类型的数组中每个元素的地址差4个字节,char类型的数组中每个元素的地址差1个字节
2.二维数组
二维数组正如我们的二维空间一样,是一个面,所以其相较于一维数组,多了行,列的概念
#include <stdio.h>
int main()
{
int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };
int sz1 = sizeof(arr) / sizeof(arr[0]);//行
int sz2 = sizeof(arr[0]) / sizeof(arr[0][0]);//列
for (int i = 0; i < sz1; i++)
{
for (int j = 0; j < sz2; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
该二维数组打印出来的结果是
二维数组与一维数组几乎无差别,像二维数组的功能,也能通过一维数组达成
例如:
#include <stdio.h>
int main()
{
int arr1[4]={1,2,3,4};
int arr2[4]={2,3,4,5};
int arr3[4]={3,4,5,6};
int sz=sizeof(arr)/sizeof(arr[0]);
for(int i=0;i<sz;i++)
{
printf("%d ",arr1[i]);
}
printf("\n");
for(int i=0;i<sz;i++)
{
printf("%d ",arr2[i]);
}
printf("\n");
for(int i=0;i<sz;i++)
{
printf("%d ",arr3[i]);
}
printf("\n");
return 0;
}
这就是用一维数组来达成二维数组的方法,但是二维数组对于这种行列更适合,效率更高
但是这两种方法也是有所区别的:二维数组的元素在内存存储中是连续的,而一维数组不是连续的(大概率)
下标跟一维数组一样,都是从0开始的,但是前面[ ]表示行,后面的[ ]表示列
1.二维数组的用法
1.可以通过{ }来分组
#include <stdio.h>
int main()
{
int arr[3][4]={{1,2},{3,4},{5,6}};
for(int i=0;i<3;i++)
{
for(int j=0;j<4;j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
return 0;
}
从这里,我们也可以知道,当该数组的一行的元素没满的时候,编译器会自动补0
2.{ }中的元素不能超过数组表示列的[ ]中的最大容量
#include <stdio.h>
int main()
{
int arr[2][2] = { 1,{2,3,4} };
return 0;
}
此时编译器会报错:
初始值设定项值太多
3.定义一个二维数组的时候,其行可以不定义,但列要求一定要定义
# include <stdio.h>
int main()
{
int arr[][]={1,2,3,4,5,6,7,8};
return 0;
}
此时编译器会报错:
数组不能包含此类型的元素
因为,当我们不对二维数组的列进行定义的话,编译器无法知道我们的想法,最终就会导致编译器凌乱,也就是报错
这也变相地告诉我们,arr[ ][ ]相当于将前面的arr[ ]当作了数组名,而后面[ ]就是该数组的最大容纳量,只不过该数组的全部元素在内存中是连续的
当然有一维数组,二维数组,那当然存在三维数组,甚至更高维的数组,这就不进行讲述了
3.指针
我们从前言中知道了指针就是存放一个元素的地址的,而用来保存该指针的变量就将其称为指针变量,但在我们通常的对话中,会将指针变量念作指针,但本质上其实还是指针变量
#include <stdio.h>
int main()
{
int a=0;
int* pa=&a;//这里pa就是指针变量,*在这里的作用是说明pa是一个指针,int用于说明pa指向的对象a的数据类型是int
return 0;
}
1.指针的大小
在不同操作系统下的编译器中,指针的大小其实也不一样
在32位操作系统下,指针的大小是4个字节,而在64位的操作系统下,指针的大小是8个字节
这里我们可以验证一下:
#include <stdio.h>
int main()
{
printf("%zu\n",sizeof(int*));
printf("%zu\n",sizeof(char*));
printf("%zu\n",sizeof(double*));
printf("%zu\n",sizeof(float*));
return 0;
}
32位操作系统:
64位操作系统:
但有人就会发现,这些指针前面的数据类型不管是什么,似乎大小都是一样的,这是事实,因为这里的数据类型只能说明其指向的对象的数据类型是什么,但除了这之外就没有别的区别了吗?当然是有的,但把这留到一会讲
2.指针的使用
我们既然知道指针能存放地址,那么只要对指针变量使用*(解引用),然后对该整体进行操作,这时就能对指针指向的对象造成影响
#include <stdio.h>
int main()
{
int a=0;
int* pa=&a;
*pa=10;
printf("%d\n",a);
return 0;
}
这就是指针的作用,其可以用在一些函数中,因为我们知道函数中的形参只是实参的一份临时拷贝,当函数结束时,形参就会销毁,故无法对实参造成影响,所以这时候就能使用指针,从而对实参造成影响
用函数将两个数字进行交换
#include <stdio.h>
void Switch(int* pa,int* pb)
{
int tmp=*pa;
*pa=*pb;
*pb=tmp;
}
int main()
{
int a=1,b=2;
Switch(&a,&b);
printf("%d %d\n",a,b);
return 0;
}
注意:使用指针的时候记得初始化,否则会形成野指针,即指针里存放的是随机值
#include <stdio.h>
int main()
{
int a;
int* pa=&a;//不能这样写,这里就是形成的是野指针,此时pa里存放的是随机值
return 0;
}
避免野指针的方法:
1.指针初始化
2.小心指针越界(在像数组和指针使用时,指针不要指向数组外的空间)
3.指针指向空间放置即使是NULL
4.避免返回局部变量的地址(像函数中变量)
5.指针使用之前检查其有效性
3.指针间的运算
指针之间能够进行减法运算,这样便可以得到两个指针之间的元素总数,多用于数组中求其数组中的两元素间有多少的元素
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3,4 };
int* parr = arr;
for (int i = 0;; i++)
{
if (arr[i] == 4)
{
printf("%d", (parr + i) - (parr));
break;
}
}
return 0;
}
#include <stdio.h>
int main()
{
char arr[]="abcdef";
int* parr=arr;
for(int i=0;;i++)
{
if(arr[i]=='\0')
{
printf("%d",(parr+i)-(parr));
break;
}
}
return 0;
}
4.指针的类型
指针的类型有像:int*,short*,long*,long long*,char*,double*,float*
而指针的类型主要取决于指针指向的对象的数据类型
当我们的数据类型用错时,可能会发生错误
还有指针中对常量的加减法与数组中相似
#include <stdio.h>
int main()
{
int arr1[10]={0};
int* parr1=arr1;
printf("%p\n",parr1);
printf("%p\n",parr1+1);
char arr2[10]={0};
char* parr2=arr2;
printf("%p\n",parr2);
printf("%p\n",parr2+1);
return 0;
}
因此,我们拥有了灵活使用的方法,可以根据我们对于地址是1个1个字节的移动,还是4个4个字节的移动
4.指针和数组
数组和指针通常可以混合使用
因为我们知道数组名通常指的是该数组的首元素地址,有地址通常就会与指针有交集
例如:
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7 };
int* parr = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0;i<sz; i++)
{
printf("%d ", *parr + i);//也可以使用arr[i]
//*(parr+i)实际上是=arr[i]
}
printf("\n");
return 0;
}
这也能够很好地访问数组中的内容
5.二级指针
二级指针顾名思义,就i是用于存放指针的指针变量
#include <stdio.h>
int main()
{
int a=1;
int* pa=&a;
int** ppa=&pa;//这里ppa就是一个二级指针
**ppa=2;
printf("%d\n",a);
return 0;
}
二级指针跟普通指针的要点几乎差不多,就是得解释一下,第二个是指ppa是一个指针变量,而int指的是ppa指向的对象是一个指针,而这个指针指向的对象的数据类型是int
其与一级指针一样都能对真正的对象造成影响
6.总结
数组和指针在编程中十分常见,应用范围也很广泛,这两者也通常会一起搭配着使用