​【程序猿必备:指针与数组的高级技能秘籍】(中)

简介: ​【程序猿必备:指针与数组的高级技能秘籍】

【程序猿必备:指针与数组的高级技能秘籍】(上):https://developer.aliyun.com/article/1424712


多维数组和多级指针


二维数组


基本概念


  • 几乎大部分书中所画的二维数组,都是矩阵样子,具体可以参考书中的图。
  • 但是,现在我们要在这里澄清,书中的图,最多只能称之为示意图,并非真的内存布局图。
  • 可以想象一些问题:如果按照书中矩阵样子画二维数组的话,那么三维数组,四维数组又该如何画呢?


二维数组的基本内存布局


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



结论:二维数组在内存地址空间排布上,也是线性连续且递增的。


二维数组如何画图


  1. 只有能够正确画出二维数组的布局图,才算真正能深刻理解二维数组的空间布局
  2. 以它为例:char a[3][4] = { 0 };


  • 数组的定义是:具有相同数据元素类型的集合,特征是,数组中可以保存任意类型。
  • 那么数组中可以保存数组吗?      答案是可以!
  • 在理解上,我们甚至可以理解所有的数组都可以当成"一维数组"!(这样理解的好处,我们后面就说)
  • 就二维数组来说,我们认为二维数组,可以被看做“一维数组”,只不过内部“元素”也是一维数组
  • 那么内部一维数组是在内存中布局是“线性连续且递增”的,多个该一维数组构成另一个“一维数组”,那么整体便也是线性连续且递增的
  • 这也就解释了,上述地址为何是连续的。
  • 在强调一遍,我们认为:二维数组可以被看做内部元素是一维数组的一维数组。


#include <stdio.h>
int main()
{
  char a[3][4] = { 0 };
    //char* p = a;//a首元素(是一个数组,char[3]的地址)
    //a的类型是int(*p)[3]
    /*
        A:&a -> 二维数组的地址
        B:a -> 第一个一维数组的地址
        C:&a[0][0] -> 第一个数组的第一个元素的地址
        地址值都是:005BFAA4
        printf("%p\n", &a);
      printf("%p\n", &a + 1);005BFAB0
      printf("%p\n", a);
      printf("%p\n", a+1);005BFAA8
      printf("%p\n", &a[0][0]);
      printf("%p\n", &a[0][0] + 1);005BFAA5
    */
    //所以这里需要强制类型转换
  char* p = (char*)a;
  for (int i = 0; i < 3 * 4; i++) {
    //printf("%p\n", &p[i]);
    printf("%p\n", p + i);
  }
  //用来对比
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++) {
      printf("a[%d][%d] : %p\n", i, j, &a[i][j]);
    }
  }
  return 0;
}



#include<stdio.h>
int main()
{
    //下面的代码输出结果是什么呢?
  int a[3][4] = { 0 };
  printf("%d\n", sizeof(a)); 
  printf("%d\n", sizeof(a[0][0])); 
  printf("%d\n", sizeof(a[0])); 
  printf("%d\n", sizeof(a[0] + 1)); 
  printf("%d\n", sizeof(*(a[0] + 1))); 
  printf("%d\n", sizeof(a + 1)); 
  printf("%d\n", sizeof(*(a + 1))); 
  printf("%d\n", sizeof(&a[0] + 1)); 
  printf("%d\n", sizeof(*(&a[0] + 1))); 
  printf("%d\n", sizeof(*a)); 
  printf("%d\n", sizeof(a[3])); 
  return 0;
}


提示:数组名补充:两种情况代表整个数组,其他都是首元素地址


  1. sizeof(a):这里a必须单独出现,不能包含操作符的运算。
  2. &a


#include<stdio.h>
int main()
{
  /*
    数组名补充:两种情况代表整个数组,其他都是首元素地址
      sizeof(a) 这里a必须单独出现
      & a
  */
  //所有的数组都可以看做成为"一维数组"
  int a[3][4] = { 0 };
  printf("%d\n", sizeof(a)); 
  //a代表整个数组,二维数组一共有3*4=12个元素,每个元素4个字节 - 48
  printf("%d\n", sizeof(a[0][0])); 
  //a[0][0]代表二维数组的第一个一维数组元素内的第一个整形  - 4
  printf("%d\n", sizeof(a[0])); 
  //a[0]代表二维数组的第一个一维数组,这个一维数组有4个元素 - 16
  printf("%d\n", sizeof(a[0] + 1)); 
  //a[0]是第一个一维数组, 由于只有两种情况代表整个数组,其他都是首元素地址,\
  a[0]+1这里进行了+1的操作,所以这里的a[0]就是一维数组的首元素地址,\
  +1跳过sizeof(int)个字节,获取的是&a[0][1]的地址 - 4
  printf("%d\n", sizeof(*(a[0] + 1))); 
  //由上一个题目可以得出来,这里的解引用获取的是a[0][1] - 4
  printf("%d\n", sizeof(a + 1)); 
  //这里是由于+1的操作,a就是首元素的地址,+1后就是第二个一维数组的地址 - 4
  printf("%d\n", sizeof(*(a + 1)));
  //由上一个题目可以得出来,这里的解引用获取的是a[1]的所有元素 - 16
  printf("%d\n", sizeof(&a[0] + 1)); 
  //&a[0]第一个数组的地址,+1就是第二个数组的地址 - 4
  printf("%d\n", sizeof(*(&a[0] + 1))); 
  //由上一个题目可以得出来,这里的解引用获取的是a[1]的所有元素 - 16
  printf("%d\n", sizeof(*a)); 
  //这里由于*操作,a就是首元素的地址,就是第一个一维数组的地址,*a - 16
  printf("%d\n", sizeof(a[3])); 
  //这里越界,代表二维数组的第四个一维数组 - 16
  return 0;
}



数组a的定义为: int a[3][4];下面哪个不能表示a[1][1] ?


  • (A) 、*(&a[0][0]+5)
  • (B) 、*(*(a+1)+1)
  • (C)、*(&a[1]+1)
  • (D)、*(a[1]+1)


提示:


       A:&a -> 二维数组的地址
       B:a -> 第一个一维数组的地址
       C:&a[0][0] -> 第一个数组的第一个元素的地址



what is the value of & p[4][2] - &a[4][2]?


#include<stdio.h>
int main()
{
  int a[5][5];//二维数组
  int(*p)[4];//数组指针
  p = (int(*)[4])a;//a是首元素地址
  printf("a_ptr = %p,p_ptr = %p\n", &a[4][2], &p[4][2]);
  printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
  return 0;
}


二级指针


  • 指针变量是变量吗?是的
  • 变量有地址吗?有的
  • 地址是数据吗?是滴
  • 数据可以被其他变量保存吗? 当然可以

结论:指针变量也有地址。

#include <stdio.h>
int main()
{
  int a = 10;
  int* p = &a;
  int** pp = &p;
  p = 100; //什么意思
  *p = 100; //什么意思
  pp = 100; //什么意思
  *pp = 100; //什么意思
  **pp = 100; //什么意思
  return 0;
}


  1. 将指针p的值赋为100,这意味着指针p现在指向内存地址100处的值。
  2. 将指针p指向的内存地址的值赋为100,即将a的值赋为100。
  3. 将指针pp的值赋为100,这意味着指针pp现在指向内存地址100处的值。
  4. 将指针pp指向的指针的值赋为100,这意味着指针p的值被修改为内存地址100。
  5. 将指针pp指向的指针所指向的内存地址的值赋为100,即将a的值赋为100。


【程序猿必备:指针与数组的高级技能秘籍】(下):https://developer.aliyun.com/article/1424717

相关文章
|
4天前
|
算法 索引
单链表题+数组题(快慢指针和左右指针)
单链表题+数组题(快慢指针和左右指针)
9 1
|
13天前
|
存储
如何使用指针数组来实现动态二维数组
指针数组可以用来实现动态二维数组。首先,定义一个指向指针的指针变量,并使用 `malloc` 为它分配内存,然后为每个子数组分配内存。通过这种方式,可以灵活地创建和管理不同大小的二维数组。
|
13天前
|
存储
如何通过指针数组来实现二维数组?
介绍了二维数组和指针数组的概念及其区别,详细讲解了如何使用指针数组模拟二维数组,包括定义与分配内存、访问和赋值元素、以及正确释放内存的步骤,适用于需要动态处理二维数据的场景。
|
13天前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
19天前
魔法指针 之 二级指针 指针数组
魔法指针 之 二级指针 指针数组
15 1
|
21天前
|
存储
一篇文章了解区分指针数组,数组指针,函数指针,链表。
一篇文章了解区分指针数组,数组指针,函数指针,链表。
16 0
|
28天前
|
编译器 C语言
【C语言】指针篇-深入探索数组名和指针数组- 必读指南(2/5)
【C语言】指针篇-深入探索数组名和指针数组- 必读指南(2/5)
|
3月前
|
搜索推荐 C语言
指针与数组
指针与数组
56 9
|
3月前
|
算法 Java
双指针在数组遍历中的应用
文章深入探讨了双指针技术在数组遍历中的应用,通过实战例子详细解释了快慢指针和首尾指针的不同用法,并提供了解决LeetCode相关问题的Java代码实现。
|
3月前
|
存储 程序员 C语言
指针的高级应用:指针数组、数组指针、函数指针等。
指针的高级应用:指针数组、数组指针、函数指针等。
97 0