c - 数组符号与指针符号C

标签 c arrays pointers

使用指针表示法比数组表示法有什么好处吗?我意识到,在某些特殊情况下,指针表示法更好,但在我看来,数组表示法更清晰。我的教授告诉我们,他更喜欢指针符号“因为它是C”,但这不是他将要标记的东西。我知道,将字符串声明为字符数组和将指针声明为字符串是有区别的——我只是在一般情况下在数组中循环。

最佳答案

如果您编写一个简单的循环,那么数组和指针窗体通常都编译成相同的机器代码。
特别是非常量循环退出条件存在差异,但只有当您试图优化特定编译器和体系结构的循环时才有意义。
那么,我们考虑一个依赖于两者的现实世界的例子怎么样?
这些类型实现动态确定大小的双精度浮点矩阵,并具有单独的引用计数数据存储:

struct owner {
    long          refcount;
    size_t        size;
    double        data[];    /* C99 flexible array member */
};

struct matrix {
    long          rows;
    long          cols;
    long          rowstep;
    long          colstep;
    double       *origin;
    struct owner *owner;
};

其思想是,当需要矩阵时,可以使用struct matrix类型的局部变量来描述它。在C99灵活数组成员中,所有引用的数据都存储在动态分配的struct owner结构中。在您不再需要矩阵之后,必须显式地“删除”它。这允许多个矩阵引用同一数据:您甚至可以有单独的行、列或对角线向量,对其中一个的任何更改都会立即反映在所有其他向量中(因为它们引用相同的数据值)。
当一个矩阵与数据相关时,通过创建一个空矩阵,或者通过引用另一个矩阵所引用的现有数据,就可以增加所有者结构。每当删除矩阵时,引用的所有者结构refcount都会减少。当refcount降至零时,所有者结构将被释放。这意味着您只需要记住“删除”您使用的每个矩阵,并且所引用的数据将被正确地管理并尽快发布(不需要),但决不能太早。
这一切都假设一个单线程进程;多线程处理相当复杂。
要访问矩阵struct matrix m、行r、列c中的元素,假设0 <= r < m.rows0 <= c < m.cols,可以使用m.origin[r*m.rowstep + c*m.colstep]
如果您想转置矩阵,只需交换m.rowsm.cols,以及m.rowstepm.colstep。所有这些变化,就是数据(存储在所有者结构中)的读取顺序。
(注意origin指向矩阵中第0行第0列处出现的双精度值,rowstepcolstep可以为负。这允许对原本单调的常规数据(如镜子和对角线等)进行各种奇怪的“查看”。)
如果我们没有C99灵活数组成员——比如,我们只有指针,而根本没有数组符号——所有者结构data成员必须是指针。这将意味着在硬件级别上有一个额外的重定向(将数据访问速度减慢一点)。我们要么需要分别分配data所指向的内存,要么使用技巧指向所有者结构本身后面的地址,但要适当地对齐成double。
多维数组确实有它们的用途——基本上,当所有维度的尺寸(或者所有维度都是一维的)已知时,编译器可以很好地处理索引,但并不意味着它们总是比使用指针的方法更容易。例如,在上述矩阵结构的情况下,我们总是可以定义一些辅助预处理器宏,比如
#define MATRIXELEM(m, r, c)  ((m).origin[(r)*(m).rowstep + (c)*(m).colstep])

不可否认,它的缺点是对第一个参数m求值三次。(这意味着MATRIXELEM(m++,0,0)实际上会尝试增加m三次。)在这种特殊情况下,m通常是struct matrix类型的局部变量,这会尽量减少意外。一个人可以有。
struct matrix m1, m2;

/* Stuff that initializes m1 and m2, and makes sure they point
   to valid matrix data */

MATRIXELEM(m1, 0, 0) = MATRIXELEM(m2, 0, 0);

这些宏中的“extra”括号确保如果使用计算(例如i + 4*j作为行),索引计算是正确的((i + 4*j)*m.rowstep而不是i + 4*j*m.rowstep)。在预处理器宏中,这些括号根本不是“额外的”。除了确保正确的计算,使用“额外”括号还可以告诉其他程序员,宏编写器在避免此类与算术相关的错误时非常小心。(我认为把括号放在那里是“好形式”,即使在语法不明确不需要括号的情况下,如果它向阅读代码的其他开发人员传达了这种“保证”。)
在所有这些文字之后,这引出了我最重要的一点:有些东西比指针符号更容易被我们人类程序员表达和理解,反之亦然。"Foo"[1]相当明显等于'o',而*("Foo"+1)则不那么明显。(同样,两者都不是1["foo"],但您可以将此归咎于c标准化人员。)
根据上面的例子,我认为这两个符号是互补的;它们确实有很大的重叠,特别是在简单的循环中——在这种情况下,只选择一个是可以的——但是能够利用这两个符号并选择一个并不是基于一个人的熟练程度,而是基于一个人对什么是最有意义的WRT的看法。可读性和可维护性,在我看来,对任何C程序员来说都是一项重要的技能。

关于c - 数组符号与指针符号C,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41915930/

相关文章:

c - C 中包含三个案例的 Switch 语句。第三个案例运行不正常

c - 使用 C 将 CSV 文件解析为结构

mysql - 将 SQL 结果格式化为数组;按键访问?

javascript - 使用javascript的reduce方法将对象数组转换为每年和每月

c - 为什么变量在递归调用中获得相同的地址?

c - 指向结构的指针,试图引用其数据成员 - C

c++ - C++ 中指针的自动返回类型

c - 将 memmove 与指针一起使用

c - list.h 中对 INIT_LIST_HEAD 的 undefined reference

c++ - 为什么迭代 2D 数组行专业比列专业更快?