IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C++】二维数组的行指针和列指针 -> 正文阅读

[C++知识库]【C++】二维数组的行指针和列指针

在C++中,数组与指针有密切的关系。任何通过数组和下标实现的表达式可等价地通过指针和偏移量实现。下面首先介绍一维数组与指针的关系,之后介绍二维数组的行指针和列指针。

1.一维数组和指针

定义一个长度为3的一维数组a

int a[3] = {1, 2, 3};

假设其首元素地址为0x9F36FAE0,则数组元素在内存中的示意图如下图所示:
一维数组内存示意图

1.1 指向数组元素的指针

指向数组元素的指针声明和初始化方式如下:

int* p = a;  // 等价于p = &a[0]

这里声明了一个指向数组a的首元素的指针p

由于在C++中数组名就是数组首元素的地址,因此可以直接使用数组名来初始化指针,也可以显式地取数组元素的地址(如&a[0])。

对于数组a和指向其首元素的指针p,有以下等价形式:

含义等价形式类型
首元素的值a[0]*ap[0]*pint1
首元素的地址&a[0]a&p[0]pint*0x9F36FAE0
第i个元素的值a[i]*(a+i)p[i]*(p+i)int3 (i = 2)
第i个元素的地址&a[i]a+i&p[i]p+iint*0x9F36FAE8 (i = 2)

★根据C++地址算术运算的定义,无论a是数组名还是指针,都有

  • a[i]等价于*(a+i)
  • &a[i]等价于a+i

1.2 指向数组的指针

指向数组的指针声明和初始化方式如下:

int (*q)[3] = &a;

这里声明了一个指向数组a 本身的指针q,因此*q等价于a

注意:数组a的地址&a在数值上等于其首元素的地址a,但二者的类型不同。

对于数组a和指向其本身的指针q,有以下等价形式:

含义等价形式类型
首元素的值a[0](*q)[0]**qint1
首元素的地址&a[0]&(*q)[0]*qint*0x9F36FAE0
第i个元素的值a[i](*q)[i]*(*q+i)int3 (i = 2)
第i个元素的地址&a[i]&(*q)[i]*q+iint*0x9F36FAE8 (i = 2)
数组的地址&aqint(*)[3]0x9F36FAE8

1.3 验证代码

#include <cstdio>

using namespace std;

int main() {
    int a[3] = {1, 2, 3};
    int* p = a;
    int (*q)[3] = &a;

    printf("首元素的值\n");
    printf("a[0]\t*a\tp[0]\t*p\t(*q)[0]\t**q\n");
    printf("%d\t%d\t%d\t%d\t%d\t%d\n", a[0], *a, p[0], *p, (*q)[0], **q);
    putchar('\n');

    printf("首元素的地址\n");
    printf("&a[0]\t\ta\t\t&p[0]\t\tp\t\t&(*q)[0]\t\t*q\n");
    printf("%p\t%p\t%p\t%p\t%p\t%p\n", &a[0], a, &p[0], p, &(*q)[0], *q);
    putchar('\n');

    printf("第i个元素的值\n");
    printf("i\ta[i]\t*(a+i)\tp[i]\t*(p+i)\t(*q)[i]\t*(*q+i)\n");
    for (int i = 0; i < 3; ++i)
        printf("%d\t%d\t%d\t%d\t%d\t%d\t%d\n", i, a[i], *(a + i), p[i], *(p + i), (*q)[i], *(*q + i));
    putchar('\n');

    printf("第i个元素的地址\n");
    printf("i\t&a[i]\t\ta+i\t\t&p[i]\t\tp+i\t\t&(*q)[i]\t\t*q+i\n");
    for (int i = 0; i < 3; ++i)
        printf("%d\t%p\t%p\t%p\t%p\t%p\t%p\n", i, &a[i], a + i, &p[i], p + i, &(*q)[i], *q + i);
    putchar('\n');

    printf("数组的地址\n");
    printf("&a\t\tq\n");
    printf("%p\t%p\n", &a, q);
    return 0;
}

2.二维数组和指针

定义一个大小为2×3的二维数组a

int a[2][3] = {{1, 2, 3}, {4, 5, 6}};

假设其首元素地址为0x19AFFC40。

二维数组在概念上可理解为矩阵:
二维数组矩阵示意图

但二维数组在内存中的实际存储形式是按行的顺序依次存储,如下图所示:
二维数组内存示意图

可以看出,二维数组在内存中是以类似于一维数组的形式存储的,元素a[i][j]的偏移量可使用公式i*列数+j计算。

虽然二维数组并不是以矩阵的形式存储的,但二维数组的每一行就是一个一维数组,在这一点上与矩阵的概念是一致的。

2.1 地址运算

由于二维数组就是一维数组的数组,元素类型是一维数组,数组名仍然是首元素的地址。在这里“首元素”就是二维数组的首行,因此a是首行的地址。使用数组名a可进行如下的地址运算。

2.1.1 行

含义等价形式类型
首行a[0]*aint[3]{1, 2, 3}
首行的地址&a[0]aint(*)[3]0x19AFFC40
第i行a[i]*(a+i)int[3]{4, 5, 6} (i = 1)
第i行的地址&a[i]a+iint(*)[3]0x19AFFC4C (i = 1)

由此可以看出,即使数组元素的类型变成了一维数组,1.1节最后的结论仍然成立。

2.1.2 数组元素

含义等价形式类型
首行首列元素的值a[0][0]*a[0](*a)[0]**aint1
首行首列元素的地址&a[0][0]a[0]*aint*0x19AFFC40
第i行首列元素的值a[i][0]*a[i](*(a+i))[0]**(a+i)int4 (i = 1)
第i行首列元素的地址&a[i][0]a[i]*(a+i)int*0x19AFFC4C (i = 1)
第i行第j列元素的值a[i][j]*(a[i]+j)(*(a+i))[j]*(*(a+i)+j)int6 (i = 1, j = 2)
第i行第j列元素的地址&a[i][j]a[i]+j*(a+i)+jint*0x19AFFC54 (i = 1, j = 2)

2.2 行指针

二维数组行指针的声明和初始化方式如下:

int (*q)[3] = a;  // 等价于q = &a[0]

这里声明了一个指向二维数组a首行的指针q,因此*q等价于a[0]

从语法上可以看出,行指针本质上就是指向数组的指针。二维数组a的首行a[0]就是一个长度为3的一维数组,因此可以使用其地址&a[0]来初始化行指针q。另一方面,因为数组名就是首元素的地址,因此a等价于&a[0]

对于二维数组a和指向其首行的指针q,有以下等价形式:

含义等价形式类型
首行a[0]q[0]*qint[3]{1, 2, 3}
首行的地址&a[0]a&q[0]qint(*)[3]0x19AFFC40
第i行a[i]q[i]*(q+i)int[3]{4, 5, 6} (i = 1)
第i行的地址&a[i]&q[i]q+iint(*)[3]0x19AFFC4C (i = 1)

与数组元素相关的表达形式这里不再列举,直接将a替换为q即可。

2.3 列指针

二维数组列指针的声明和初始化方式如下:

int *p = &a[0][0];

这里声明了一个指向二维数组a首行首列元素的指针p

可以看出,列指针就是指向数组元素的普通指针。通过列指针可以将二维数组当作一维数组、利用偏移量计算公式来访问数组元素。

对于二维数组a和指向其首行首列元素的指针p,有以下等价形式:

含义等价形式类型
首行首列元素的值a[0][0]p[0]*pint1
首行首列元素的地址&a[0][0]&p[0]pint*0x19AFFC40
第i行首列元素的值a[i][0]p[i*3]*(p+i*3)int4 (i = 1)
第i行首列元素的地址&a[i][0]&p[i*3]p+i*3int*0x19AFFC4C (i = 1)
第i行第j列元素的值a[i][j]p[i*3+j]*(p+i*3+j)int6 (i = 1, j = 2)
第i行第j列元素的地址&a[i][j]&p[i*3+j]*p+i*3+jint*0x19AFFC54 (i = 1, j = 2)

注:通过列指针访问元素时用到了列数3,而通过行指针或者数组名+下标访问元素时不需要,能够“自动”找到对应的地址,这就是在声明中必须指定列数的原因。

2.4 验证代码

#include <cstdio>

using namespace std;

int main() {
    int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int (*q)[3] = a;    // 行指针
    int *p = &a[0][0];  // 列指针

    printf("首行的地址\n");
    printf("&a[0]\t\ta\t\t&q[0]\t\tq\n");
    printf("%p\t%p\t%p\t%p\n", &a[0], a, &q[0], q);
    putchar('\n');

    printf("第i行的地址\n");
    printf("i\t&a[i]\t\ta+i\t\t&q[i]\t\tq+i\n");
    for (int i = 0; i < 2; ++i)
        printf("%d\t%p\t%p\t%p\t%p\n", i, &a[i], a + i, &q[i], q + i);
    putchar('\n');

    printf("首行首列元素的值\n");
    printf("a[0][0]\t**a\tp[0]\t*p\n");
    printf("%d\t%d\t%d\t%d\n", a[0][0], **a, p[0], *p);
    putchar('\n');

    printf("首行首列元素的地址\n");
    printf("&a[0][0]\t*a\t\t&p[0]\t\tp\n");
    printf("%p\t%p\t%p\t%p\n", &a[0][0], *a, &p[0], p);
    putchar('\n');

    printf("第i行首列元素的值\n");
    printf("i\ta[i][0]\t**(a+i)\tp[i*3]\t*(p+i*3)\n");
    for (int i = 0; i < 2; ++i)
        printf("%d\t%d\t%d\t%d\t%d\n", i, a[i][0], **(a + i), p[i * 3], *(p + i * 3));
    putchar('\n');

    printf("第i行首列元素的地址\n");
    printf("i\t&a[i][0]\t\t*(a+i)\t\t&p[i*3]\t\tp+i*3\n");
    for (int i = 0; i < 2; ++i)
        printf("%d\t%p\t%p\t%p\t%p\n", i, &a[i][0], *(a + i), &p[i * 3], p + i * 3);
    putchar('\n');

    printf("第i行第j列元素的值\n");
    printf("i\tj\ta[i][j]\t*(*(a+i)+j)\tp[i*3+j]\t*(p+i*3+j)\n");
    for (int i = 0; i < 2; ++i)
        for (int j = 0; j < 3; ++j)
            printf("%d\t%d\t%d\t%d\t\t%d\t\t%d\n", i, j, a[i][j], *(*(a + i) + j), p[i * 3 + j], *(p + i * 3 + j));
    putchar('\n');

    printf("第i行第j列元素的地址\n");
    printf("i\tj\t&a[i][j]\t*(a+i)+j\t&p[i*3+j]\tp+i*3+j\n");
    for (int i = 0; i < 2; ++i)
        for (int j = 0; j < 3; ++j)
            printf("%d\t%d\t%p\t%p\t%p\t%p\n", i, j, &a[i][j], *(a + i) + j, &p[i * 3 + j], p + i * 3 + j);
    putchar('\n');
    return 0;
}

3.应用

下面的程序分别使用行指针和列指针遍历二维数组,并展示了如何将行指针和列指针传递给函数:

#include <iostream>

using namespace std;

void print_2d_array(int a[][3], int m, int n);
void print_2d_array_row_pointer(int (*q)[3], int m, int n);
void print_2d_array_col_pointer(int* p, int m, int n);

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

    print_2d_array(a, 2, 3);
    print_2d_array_row_pointer(q, 2, 3);
    print_2d_array_col_pointer(p, 2, 3);
    return 0;
}

void print_2d_array(int a[][3], int m, int n) {
    cout << "直接打印二维数组:" << endl;
    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j)
            cout << a[i][j] << ' ';
        cout << endl;
    }
}

void print_2d_array_row_pointer(int (*q)[3], int m, int n) {
    cout << "使用行指针打印二维数组:" << endl;
    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j)
            cout << q[i][j] << ' ';
        cout << endl;
    }
}

void print_2d_array_col_pointer(int* p, int m, int n) {
    cout << "使用列指针打印二维数组:" << endl;
    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j)
            cout << p[i * n + j] << ' ';
        cout << endl;
    }
}

4.参考

二维数组与指针(详解)

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-14 09:47:23  更:2022-05-14 09:47:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 18:57:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码