数组表示法与指针表示法C

时间:2017-01-28 23:14:27

标签: c arrays pointers

在数组表示法上使用指针表示法有什么好处吗?我意识到可能有一些特殊情况,指针表示法更好,但在我看来,数组表示法更清晰。我的教授告诉我们,他更喜欢指针符号"因为它是C"但它并不是他要标记的东西。而且我知道将字符串声明为字符数组与将指针声明为字符串存在差异 - 我只是在谈论通常循环数组。

2 个答案:

答案 0 :(得分:4)

如果编写一个简单的循环,则数组和指针形式通常都会编译为相同的机器代码。

特别是非常量循环退出条件存在差异,但只有在尝试优化特定编译器和体系结构的循环时才有意义。

那么,我们如何考虑一个依赖两者的现实世界的例子?

这些类型实现了动态确定大小的双精度浮点矩阵,并带有单独的引用计数数据存储:

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降至零时,将释放所有者结构。这意味着你只需要记住" drop"你使用的每个矩阵,所引用的数据将被尽快正确管理和释放(不需要),但永远不会太早。

这都假设是一个单线程的过程;多线程处理相当复杂。

要访问矩阵struct matrix m中的元素,行r,列c,假设0 <= r < m.rows0 <= c < m.cols,您可以使用m.origin[r*m.rowstep + c*m.colstep]。< / p>

如果您想转置矩阵,只需交换m.rowsm.cols,以及m.rowstepm.colstep。所有这些变化都是读取数据(存储在所有者结构中)的顺序。

(注意origin指向矩阵中第0行第0列出现的双精度; rowstepcolstep可以是负数。这允许所有类型的奇怪的&#34;观察&#34;对于其他沉闷的常规数据,如镜子和对角线等。)

如果我们没有C99灵活数组成员 - 比方说,我们只有指针,根本没有数组符号 - ,所有者结构data成员必须是指针。这意味着在硬件级别进行额外的重定向(减慢数据访问速度)。我们要么需要分别分配data指向的内存,要么使用技巧指向所有者结构本身后面的地址,但要适当地对齐一个双。

多维数组确实有它们的用途 - 基本上,当所有维度的大小(或除了一个维度之外的所有维度)都是已知的 - 并且编译器处理索引时很好,但它并不意味着它们总是比使用指针的方法更容易。例如,在上面的矩阵结构的情况下,我们总是可以定义一些辅助预处理器宏,比如

#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);

&#34;额外&#34;此类宏中的括号确保如果您使用计算(例如i + 4*j作为行),则索引计算是正确的((i + 4*j)*m.rowstep而不是i + 4*j*m.rowstep)。在预处理器宏中,这些括号并不是真正的&#34;额外的&#34;一点都不除了确保正确计算外,还有&#34;额外&#34;括号也告诉其他程序员宏编写器一直小心避免这些与算术相关的错误。 (我一个人考虑它&#34;良好的形式&#34;把括号放在那里,即使在语法不公平不需要它们的情况下,如果它传达了&#34;保证&#34;给其他开发者阅读代码。)

在所有这些文本之后,这导致了我最重要的一点:我们的人类程序员使用数组符号而非指针符号更容易表达和理解,反之亦然。 "Foo"[1]显然等于'o',而*("Foo"+1)则不那么明显。 (然后,1["foo"]也不是,但你可以责怪C标准化人员。)

基于上面的例子,我认为这两个符号是互补的;它们确实有很大的重叠,特别是在简单的循环中 - 在这种情况下可以选择一个 - 但是能够利用这两个符号并选择一个不是基于一个人的熟练程度而是基于对于什么的意见wrt最有意义。可读性和可维护性,对于任何C程序员来说都是一项重要的技能,我不是很谦虚。

答案 1 :(得分:2)

实际上,如果您将数组参数传递给C中的函数,实际上是将指针传递给它的开头。这并不是真正传递一个常识的数组,首先,因为传递一个数组会包括传递它的实际长度,第二,因为传递一个数组(作为一个值)意味着它的复制。 换句话说,你真的传递一个指向数组开头的迭代器(比如C ++中的std::vector::begin()),但你假装你自己传递了数组。事实上这非常令人困惑。因此,使用指针代表那些真正以更清晰的方式发生的事情,它绝对应该是首选。

阵列符号可能也有一些优点,但我不认为它们超重了所提到的缺点。首先,使用数组表示法强调指向单个值的指针与指向连续块的指针之间的区别。然后,您可以为自己的引用指定传递数组的预期大小。但是这个大小实际上并没有传递给表达式或函数,或者以某种方式检查了哪个事实非常令人困惑。