今天呢 , 我们就来"研究"2个知识点 , 如果你能露骨的认知 ,那么数组的本质和数组指针这个地方对大家也就没问题了
C语言是一门面向过程的语言 , 相比现在比较流行的面向对象 , 有助于提高大家的思维逻辑 , 利于理解语言内在的好处 , 学习C语言应该多理解 , 多思考 。
从一维数组入手 :
C语言的数组名到底是什么 ? 我相信肯定有同学说 : 数组首地址
那么我要准确的回答 , 数组名代表的是数组的地址呢 ? 还是数组第一个元素的地址呢?
那么我们来验证一下 :
一.打印地址
- int main(int argc, const char * argv[]) {
-
- int arr[5] = {1,3,5,7,9};
-
- printf("arr = %p\n",arr);
-
- printf("&arr = %p\n",&arr);
-
- printf("&arr[0]=%p\n",&arr[0]);
-
- /*
- arr = 0x7fff5fbff770
- &arr = 0x7fff5fbff770
- &arr[0]=0x7fff5fbff770
- */
-
- return 0;
- }
复制代码 你会发现 , arr == &arr == &arr[0] , 这个也就是我们常说的数组首地址
但是我们并不能验证它到底是怎么呢 ? 接着看 :
二.首先我们先遍历这个数组的元素的地址 :
- printf("------------遍历数组元素地址 :\n");
-
- for (int i = 0 ; i < 5; i++) {
-
- printf("&arr[%d]=%p\n",i,&arr[i]);
-
- }
复制代码------------遍历数组元素地址 : &arr[0]=0x7fff5fbff770 &arr[1]=0x7fff5fbff774 &arr[2]=0x7fff5fbff778 &arr[3]=0x7fff5fbff77c &arr[4]=0x7fff5fbff780 三 , 我们让arr , &arr , &arr[0]都+1- printf("arr+1 = %p\n",arr+1);
- printf("&arr+1 = %p\n",&arr+1);
- printf("&arr[0]+1=%p\n",&arr[0]+1);
复制代码arr = 0x7fff5fbff770 &arr = 0x7fff5fbff770 &arr[0]=0x7fff5fbff770 ------------遍历数组元素地址 : &arr[0]=0x7fff5fbff770 &arr[1]=0x7fff5fbff774 &arr[2]=0x7fff5fbff778 &arr[3]=0x7fff5fbff77c &arr[4]=0x7fff5fbff780 ------------ +1 : arr+1 = 0x7fff5fbff774 &arr+1 = 0x7fff5fbff784 &arr[0]+1=0x7fff5fbff774
这个时候就有意思了 , 你会发现 arr+1 == &arr[0]+1 , 但是&arr + 1却得到这个很奇怪的值 , 这又是什么呢 ?
我们来画个图 :
对着这个图 , 你看懂了什么 , 数一下 :
&arr+1 = 0x7fff5fbff784 是不是正好就是越界的地方啊 , 而arr + 1 , &arr[0]+1都为0x7fff5fbff774 , 所以我们就可以得到一个结论
arr为数组首元素的地址 , 也就是&arr[0] , arr+1 , 就相当于 &arr[0]+1 , 也就是以第一个元素地址为长度+1 , 跳到第二个元素的地址
&arr为整个数组的地址 , %arr+1 , 相当于以整个数组为长度+1 , 所以就跳到刚好越界的地方了, 同样我们上图理解 :
二维数组深入 :
我们先看图 , 然后思考 :
arr , &arr , arr[0] , &arr[0] , &arr[0][0] , 他们之间的关系- //定义一个二维数组
- int arr[2][3] = {
- 1,2,3, // arr[0]
- 4,5,6 // arr[1]
- };
-
- printf("------------逐个分析-----------\n");
-
- printf("arr = %p\n",arr);
-
- printf("arr[0] = %p\n",arr[0]);
-
- printf("&arr[0] = %p\n",&arr[0]);
-
- printf("arr[0][0] = %p\n",&arr[0][0]);
-
- printf("&arr = %p\n",&arr);
复制代码------------逐个分析----------- arr = 0x7fff5fbff780 arr[0] = 0x7fff5fbff780 &arr[0] = 0x7fff5fbff780 arr[0][0] = 0x7fff5fbff780 &arr = 0x7fff5fbff780
你会发现 : &arr == arr == arr[0] == &arr[0] == &arr[0][0] , 也就是我们俗称的数组首地址 , 那么他们本质真的是同一个东西吗 ? 接下来我们该怎么做 , 没错就是让他们都+1
- printf("------------让他们都+1-----------\n");
-
- printf("arr+1 = %p\n",arr+1);
-
- printf("arr[0]+1 = %p\n",arr[0]+1);
-
- printf("&arr[0]+1 = %p\n",&arr[0]+1);
-
- printf("arr[0][0]+1 = %p\n",&arr[0][0]+1);
-
- printf("&arr+1 = %p\n",&arr+1);
复制代码------------逐个分析----------- arr = 0x7fff5fbff780 arr[0] = 0x7fff5fbff780 &arr[0] = 0x7fff5fbff780 arr[0][0] = 0x7fff5fbff780 &arr = 0x7fff5fbff780 ------------让他们都+1----------- arr+1 = 0x7fff5fbff78c arr[0]+1 = 0x7fff5fbff784 &arr[0]+1 = 0x7fff5fbff78c arr[0][0]+1 = 0x7fff5fbff784 &arr+1 = 0x7fff5fbff798 这个时候你会发现 arr+1 == &arr[0]+1 都是0x7fff5fbff78c , 解释: 二维数组第一个元素的地址 , 那么二维数组的第一个元素是什么呢 ? 我们可以理解为二维数组的元素是由2个一维数组组成的 , 看图:
arr[0]又是什么 ? arr[0]+1呢
2种理解方式 :
1. arr[0]是这个二维数组第一行第一列的元素地址 , arr[0]+1 , 则以第一行第一列的元素地址为长度+1 , 跳到第一行第二列的地址上
2. arr[0]是这个二维数组的第一个一维数组里面第一个元素的地址 , arr[0] +1 则跳到这个一维数组的第二个元素的地址上.
arr[0] == &arr[0]
&arr 则是整个二维数组的地址 , &arr+1 , 以整个二维数组为长度+1 , 跳转到越界的地方
总结 :
&arr : 整个数组的地址
arr : 第一行第一列的元素地址
&arr[0] : 第一行元素的地址 arr == &arr[0] , arr+1 = &arr[1] (第二行) , arr + 2 = &arr[2] (第三行) .... 以行为长度跳转
arr[0] : 第一行第一个元素的地址
&arr[0][0] : 第一行第一个元素的地址 arr[0] = &arr[0][0] , arr[1] = &arr[0][1] .....
那么接下来 , 你是否能分别通过下标法和数组名发遍历数组 , 如果能 , 那么此处你将没问题 :
- //遍历二维数组的元素____下标法
- for (int i = 0; i < 2; i++) {
-
- for (int j = 0; j < 3; j++) {
-
- // 下标法 :
- printf("arr[%d][%d]=%d\t",i,j,arr[i][j]);
-
- }
-
- printf("\n");
- }
-
- printf("--------------------------------\n");
- //遍历二维数组的元素的值____数组名法
- for (int i = 0; i < 2; i++) {
-
- for (int j = 0; j < 3; j++) {
-
- // 数组名法 :
- printf("arr[%d][%d]=%d\t",i,j,*(*(arr+i)+j));
-
- }
-
- printf("\n");
- }
复制代码
那么这个数组名法怎么写呢 : 我们来分步骤写 :
- arr == &arr[0]
- arr+i == &arr[i]
- *(arr+i) == arr[i]
- *(arr+i)+j == arr[i]+j == *arr[i][j]
- *(*(arr+i)+j) == *(arr[i]+j) == arr[i][j]
复制代码 所以 用数组名就是这么遍历数组的
引出指针 , 以及二维数组指针
那么接下来可以引出指针了 , 首先这个是个什么数组 , 二维数组 , 那么我们就会会定义一个二维数组指针
什么是二维数组指针 ? 方便记忆方法 : xxx指针 , 就是指向xxx的指针 , 比如说 函数指针 : 指向函数的指针 , 字符串指针 , 指向字符串的指针 , 数组指针指向数组的指针 , 当然这个只是一个简单暴力的记忆方法 , 要理解
那么怎么定义一个二维数组指针呢 ?
- 数据类型 (*指针变量名)[二维数组列数];
- 1.先定义后初始化
- int (*pa)[3];
- pa = arr;
- 2. 定义的同时初始化
- int (*p)[3] = arr;
复制代码 那么怎么用二维数组指针遍历数组呢 ?
- //定义二维数组指针
- int (*p)[3] = arr;
-
- //遍历二维数组的元素的值____指针法
- for (int i = 0; i < 2; i++) {
-
- for (int j = 0; j < 3; j++) {
-
- // 数组名法 :
- printf("arr[%d][%d]=%d\t",i,j,*(*(p+i)+j));
-
- }
-
- printf("\n");
- }
复制代码
那么到这里 , 3中方法遍历数组都说完了, 那么问题来了, 为什么我们要用指针遍历呢 ? 我们不用指针遍历不可以吗?
答案是肯定的 , 我们不用指针 , 用下标法 , 数组名法 , 就是直接获取数组元素的值 , 而用指针法 , 就是间接的获取数组元素的值 ,
打个比方 :
- int num = 10;
- num = 20 ;
- 请问num的值被修改了吗 ?
- -----------------------------
- int num = 10;
- int *p = #
- *p = 20;
- 请问 , num的值被修改了吗?
复制代码 前者就是直接修改 , 后者, 也就是使用指针是间接修改 , 所以指针的基本用法 , 其实就是间接的获取或者修改变量的值
什么时候用指针而不用数组名呢 ? 再把刚刚的一维数组用3钟方式遍历 : 指针变量 *(p++) == *(p+i) ; 而 *(arr++) 是错误的 , 因为p是变量 , arr是数组名 , 我们可以认为是常量
好啦 , 差不多到尾声了 , 这个帖子带大家用面向过程的思想 , 理解数组名的意义 , 并引出指针的用法 , 在学习C语言的过程中 , 就应该多思考 , 多理解, 因为C是一门面向过程的语言, 要抓住细节, 以后才更好的理解面向对象的底层 .
|