C语言指针详解2 -- 数组指针

  本篇文章主要讲解C语言中关于数组指针的相关内容,共有数组名的特殊作用,使用指针访问数组,数组指针,数组传参的本质,二级指针,指针数组以及用指针数组模拟二维数组七个板块。

目录

一  数组名的特殊作用

1  一维数组

2  二维数组

二  使用指针访问数组 

1  一维数组

2  二维数组

三  数组指针 

四  数组传参的本质 

1  一维数组

2  二维数组

五  二级指针

六  指针数组

七  用指针数组模拟二维数组


一  数组名的特殊作用

1  一维数组

  我们先来看一段代码:

#include<stdio.h>

int main()
{
  int arr[] = {1, 2, 3, 4, 5};
  
  int* p1 = &arr[0];

  printf("p1    = %p\n", p1);
  printf("p1+1  = %p\n", p1 + 1);
  printf("arr   = %p\n", arr);
  printf("arr+1 = %p\n", arr+1);

  return 0;
}

  运行结果是:

通过以上代码我们可以看到,在一维数组中,其实数组名代表的也是一个地址,而且代表的就是首元素的地址。

  但是数组名有两种例外情况是不代表首元素地址的(二维数组相同),第一种情况就是在sizeof操作符中,数组名代表的是整个数组,如:

#include<stdio.h>

int main()
{
  int arr[5] = {1, 2, 3, 4, 5};

  int sz = sizeof(arr);

  printf("%d\n", sz);
  return 0;
}

运行结果是:

 sizeof操作符是求括号里参数在内存中所占的字节数,数组中是5个整型元素,由于此时的数组名代表整个数组,所以才能求出20。

  第二种情况就是数组名放在了取地址操作符的后面(比如&arr),此时的数组名也是代表整个数组,所以&数组名取出的是整个数组的地址,如:

#include<stdio.h>

int main()
{
  int arr[5] = {1, 2, 3, 4, 5};
  
  printf("arr   = %p\n", arr);
  printf("&arr  = %p\n", &arr);
  printf("arr+1 = %p\n", arr + 1);
  printf("&arr+1= %p\n", &arr + 1);
  return 0;
}

运行结果是:

我们可以看到虽然数组名与&数组名为同一个地址,但是数组名+1是跳过了4个字节,因为数组名代表首元素地址,所以其地址是int*类型,所以一次跳过4个字节,而&数组名+1却是跳过了20个字节,因为&数组名代表的是整个数组的地址,所以+1会跳过整个数组,它的指针类型我们到后面再说。

2  二维数组

  在讲解二维数组的数组名之前,我们先来理解一下二维数组。

  我们都知道一个二维数组可以这样创建:

int arr[3][5] = { 0 };

这代表创建了一个3行5列的二维数组,每行的下标是0,1,2,而每一样中又有5个元素,下标分别是0,1,2,3,4,那么我们是不是可以这样理解:二维数组中一共有3个元素,每个元素是一个一维数组,分别是arr[0],arr[1],arr[2],每个数组里面又包含5个元素,例如,第一行数组的第一个元素就是arr[0][0]。

  这样来看,其实二维数组是一个一维数组的数组,在arr这个二维数组中,arr[0],arr[1],arr[2]就是二维数组的3个元素,也是二维数组中每个一维数组的数组名,表示每行数组中的首元素地址,也就是&arr[0][0],&arr[1][0],&arr[2][0],而二维数组的数组名arr其实是表示二维数组的首元素的地址,也就是arr[0]这个一维数组的地址(其类型到数组指针章节讲解),显然arr+1是跳过整个第一行的数组的,如:

#include<stdio.h>

int main()
{
  int arr[3][5] = { 0 };
  
  printf("arr   = %p\n", arr);
  printf("arr+1 = %p\n", arr + 1);
  
  return 0;
}

  运行结果是:

二  使用指针访问数组 

1  一维数组

  在以前,我们都是通过数组下标来访问数组的,如:

#include<stdio.h>

int main()
{
  int arr[] = {1, 2, 3, 4, 5};
  int sz = sizeof(arr)/sizeof(arr[0]);

  int i = 0;
  for (i = 0;i < sz;i++)
  {
    printf("%d ",arr[i]);
  }
  
  return 0;
}

现在学习了指针之后,我们就可以通过指针来访问数组了,如:

#include<stdio.h>

int main()
{
  int arr[] = {1, 2, 3, 4, 5};
  int* p = &arr[0];

  int i = 0;
  for (i = 0;i < 5;i++)
  {
    printf("%d ", *(p + i));
  }

  return 0;
}

 运行结果是:

注意,上述代码中是通过指针加上一个偏移量来找到数组中的每个元素,所以代码中的p+i就相当于&arr[i],而且这里是把arr首元素的地址赋给了p,所以这里*(p + i) 是等价于 *(arr + i)的。 

  既然数组名也代表首元素地址,我们也可以通过数组名来进行数组元素的访问,如:

#include<stdio.h>

int main()
{
  int arr[] = {1, 2, 3, 4, 5};

  int i = 0;
  for (i = 0;i < 5;i++)
  {
    printf("%d ", *(arr + i));
  }
  
  return 0;
}

运行结果是一样的。

  到这我们就有一个问题了,既然arr[i]可以访问数组中的每个元素,*(arr+i)也可以访问每个元素,那么这两个是不是等价的呢?其实arr[i]这种方式,在内存中也是通过指针加减偏移量来找到每个元素的,所以i[arr]也是可以访问每个元素的,如:

#include<stdio.h>

int main()
{
  int arr[] = {1, 2, 3, 4, 5};

  int i = 0;
  for (i = 0;i < 5;i++)
  {
    printf("%d ", i[arr]);
  }
  
  return 0;
}

运行结果是:

   所以总结一下,如果把arr数组首元素的地址赋给指针变量p,这里会有几个等价关系:arr[i] =

i[arr] = *(arr + i) = *(p + i) = p[i]

2  二维数组

  通过指针访问二维数组元素代码如下:

#include<stdio.h>

int main()
{
  int arr[2][5] = {1,2,3,4,5,6,7,8,9,10};

  for (int i = 0;i < 2;i++)
  {
    for (int j = 0;j < 5;j++)
    {
      printf("%d ", *(*(arr + i) + j));//arr[i][j] == *(arr[i] + j)
    }
    printf("\n");
  }
 
  return 0;
}

 运行结果:

三  数组指针 

  在二维数组数组名那一章节中,我们提到二维数组数组名其实是第一行数组的地址,是个数组指针,所以如果要把二维数组首元素的地址赋给一个指针变量,应该这样写代码:

#include<stdio.h>

int main()
{
  int arr[2][5] = { 0 };
  
  int(*p)[5] = arr;

  return 0;
}

所以p数组指针的类型就是:

int(*)[5]

p先与*结合代表p是一个指针变量,左边的int和右边的[5]代表其是一个数组元素类型为int,数组元素个数为5的数组指针。

四  数组传参的本质 

1  一维数组

  以前我们对于数组传参是这样传参的:

#include<stdio.h>

void test(int arr[], int sz)
{
  int i = 0;
  for (i = 0;i < sz;i++)
  {
    arr[i] = i;
  }
}

int main()
{
  int arr[10] = { 0 };
  int sz = sizeof(arr)/sizeof(arr[0]);
  test(arr, sz);

  int i = 0;
  for (i = 0;i < sz;i++)
  {
    printf("%d ", arr[i]);
  }
 
  return 0;
}

而且我们知道数组传参是不会创建新的数组的,而且数组中的数组元素个数也不需要写,那么数组传参传递的真的是整个数组吗?我们再来看这么一段代码:

#include<stdio.h>

void test(int arr[])
{
  printf("%d ",sizeof(arr)/sizeof(arr[0]));
}

int main()
{
  int arr[10] = { 0 };
  test(arr);

  return 0;
}

运行结果是:

我们可以看到在x86环境下,运行结果是1,如果传递的arr表示整个数组的话,这里的运行结果应该是10,那么为什么是1呢?其实这里传递的arr是首元素的地址,在x86环境下是4个字节,而一个int类型的元素也是4个字节,所有运行结果就是1,所以形参中的数字是可以不写数组元素个数的。 

  既然传递的是数组首元素的地址的话,那么我们就可以这样写参数了:

#include<stdio.h>

void test(int* arr, int sz)
{
  int i = 0;
  for (i = 0;i < sz;i++)
  {
    arr[i] = i;
  }
}

int main()
{
  int arr[10] = { 0 };
  int sz = sizeof(arr)/sizeof(arr[0]);
  test(arr, sz);

  int i = 0;
  for (i = 0;i < sz;i++)
  {
    printf("%d ", arr[i]);
  }
 
  return 0;
}

 运行结果是:

  总结一下,一维数组传参函数的形参部分是既可以写成数组形式和指针形式的,写成数组形式会更好理解一点。 

2  二维数组

  类比一维数组,二维数组传参也是传递的首元素地址,但是二维数组首元素地址代表的是第一行数组的地址,所以二维数组形参部分也是可以写成指针形式的,只不过需要写成数组指针,如:

#include<stdio.h>

void test(int(*arr)[5], int sz)
{
  int i = 0;
  for (i = 0;i < sz;i++)
  {
    int j = 0;
    for (j = 0;j < 5;j++)
    {
      arr[i][j] = j;
    }
  }
}

int main()
{
  int arr[2][5] = { 0 };
  test(arr, 2);
  
  int i = 0;
  for (i = 0;i < 2;i++)
  {
    int j = 0;
    for (j = 0;j < 5;j++)
    {
      printf("%d ", arr[i][j]);
    }
    printf("\n");
  }
 
  return 0;
}

运行结果是:

五  二级指针

  在前面的讲解中,我们知道指针变量是用来存储其他变量的地址的,但是指针变量也是变量,其也是有地址呢,那么指针变量的地址该存放在什么地方呢?答案也是指针变量中,只不过是二级指针变量中,如:

#include<stdio.h>

int main()
{
  int a = 10;
  int* p = &a;
  int** pp = &p;

  printf("%d\n",**pp);

  return 0;
}

运行结果是:

可以看到,图中的pp就是就是个二级指针,其类型是int**;类型,按照之前的理解,与pp变量名挨的近的那颗星代表pp是个指针变量,int*代表其所指向的变量类型为int*类型

  以此类推,三级指针变量就是用来存储二级指针变量的地址。所以,总结一下,就是二级指针是用来存储一级指针变量的地址,三级指针用来存储二级指针变量的地址。 

六  指针数组

  我们知道数组中的元素如果是整型的话,那么就叫做整型数组,如果是字符的话,那么就叫做字符数组,那么既然指针变量是个变量的话,那是不是也可以构成数组呢?答案是肯定的,一个数组中如果包含的变量是指针类型,那么就叫做指针数组,如:

#include<stdio.h>

int main()
{
  int a = 10;
  int b = 20;
  int c = 30;
  int* p[3] = {&a, &b, &c};
  
  printf("%d\n", *p[0]);

  return 0;
}

运行结果是:

 当然这里也可以写成指针访问数组的形式:

#include<stdio.h>

int main()
{
  int a = 10;
  int b = 20;
  int c = 30;
  int* p[3] = {&a, &b, &c};
  
  printf("%d\n", *(*(p + 0)));

  return 0;
}

  这里要注意指针数组与数组指针的区别,数组指针是一个数组类型的指针,比如一个5个元素的整形数组指针类型为int(*)[5],指针数组是一个指针变量的数组,比如上面那个代码,每个数组元素的类型为int*类型。

七  用指针数组模拟二维数组

  我们可以发现一个指针数组中存放的是多个地址,所以是可以存放多个数组首元素地址的,当一个指针数组中包含多个一维数组首元素地址的时候就可以用指针数组模拟出二维数组了,如:

#include<stdio.h>

int main()
{
  int arr1[] = {1, 2, 3, 4, 5};
  int arr2[] = {2, 3, 4, 5, 6};
  int arr3[] = {3, 4, 5, 6, 7};

  int* prr[] = {arr1, arr2, arr3};

  int i = 0;
  for (i = 0;i < 3;i++)
  {
    int j = 0;
    for (j = 0;j < 5;j++)
    {
      printf("%d ", prr[i][j]);//*(*(prr + i) + j)
    }
    printf("\n");
  }
  
  return 0;
}

运行结果是:

  值得注意的是,这里只是用指针数组模拟出二维数组的效果,并不是真正的二维数组,因为真正的二维数组的内存空间是连续的,显然这里的三个一维数组之间的内存空间并不是连续的

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园建设方案旨在通过融合先进技术,如物联网、大数据、人工智能等,实现校园的智能化管理与服务。政策的推动和技术的成熟为智慧校园的发展提供了基础。该方案强调了数据的重要性,提出通过数据的整合、开放和共享,构建产学研资用联动的服务体系,以促进校园的精细化治理。 智慧校园的核心建设任务包括数据标准体系和应用标准体系的建设,以及信息化安全与等级保护的实施。方案提出了一站式服务大厅和移动校园的概念,通过整合校内外资源,实现资源共享平台和产教融合就业平台的建设。此外,校园大脑的构建是实现智慧校园的关键,它涉及到数据中心化、数据资产化和数据业务化,以数据驱动业务自动化和智能化。 技术应用方面,方案提出了物联网平台、5G网络、人工智能平台等新技术的融合应用,以打造多场景融合的智慧校园大脑。这包括智慧教室、智慧实验室、智慧图书馆、智慧党建等多领域的智能化应用,旨在提升教学、科研、管理和服务的效率和质量。 在实施层面,智慧校园建设需要统筹规划和分步实施,确保项目的可行性和有效性。方案提出了主题梳理、场景梳理和数据梳理的方法,以及现有技术支持和项目分级的考虑,以指导智慧校园的建设。 最后,智慧校园建设的成功依赖于开放、协同和融合的组织建设。通过战略咨询、分步实施、生态建设和短板补充,可以构建符合学校特色的生态链,实现智慧校园的长远发展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值