专栏首页编程珠玑C语言入坑指南-数组之谜

C语言入坑指南-数组之谜

前言

在C语言中,数组和指针似乎总是“暧昧不清”,有时候很容易把它们混淆。本文就来理一理数组和指针之间到底有哪些异同。

数组回顾

在分析之前,我们不妨回顾一下数组的知识。数组是可以存储一个固定大小的相同类型元素的顺序集合。为了便于我们说明,假设有以下数组声明:

int a[5];
char b[] = "hello";
  • 数组大小必须在编译期就作为一个常数确定下来。
  • 但C99中引入了变长数组,允许数组的维度是表达式 ,但在数组分配内存时,其表达式的值可以被求出。
  • 数组下标运算实际上都是通过指针进行的,也就是说a[4]与*(a+4)是等价的,甚至你会发现和4[a]也是一样的。
  • 数组名一般代表了指向该数组下标为0的元素的指针,并且printf("%s\n",hello)与printf("%s\n",&hello[0])等效。

数组和指针不相等

考虑下面的声明:

int c[4];//假设int占4字节
int *d;

对于上面的声明,编译器会给c预留内存空间4*4字节,并且数组名代表着指向数组第一个元素的指针。但对于d,却只为指针本身保留了内存空间。 所以此时有下面的操作:

c[3];        //合法
*(c+3);      //合法
*d;          //不合法,d指向了内存中不确定位置
c++;        //不合法,一维数组名是指针常量,常量不能被修改掉
d++;        //可通过编译

另外,下面的两种情况也是不一样的:

char c[] = "hello";
char *d = "hello";

前者对字符数组a进行了初始化,后者将d指向了字符串常量。字符串常量存储在只读区,因此有下面的操作:

 c[0] = 'H';  //合法,可修改数组内容
 *d = 'H';    //不合法,字符串常量内容不可更改
 d[0] = 'H'   //不合法

数组名的含义

绝大多数情况,数组名都代表着指向该数组中下标为0的元素的指针,但是有例外:

int e[4];//假设int为4字节
sizeof(e);

上面的sizeof(e)的值并非4或8(指针占用空间),而是4*4 = 16。也就是说,当数组名被用作运算符sizeof的参数时,它的计算结果是整个数组的大小,而非第一个元素的指针大小。 再来看下面这种情况:

int temp[5];
char *p = &temp;
char *q = temp;

在这里,p和q的值是一样的,含义却不一样,前者是指向数组的指针,而后者是指向该数组中下标为0的元素的指针。因此p+1指向了temp的末尾,而q+1指向了temp的第2个元素。

数组长度计算

如何计算数组长度?考虑下面的代码:

int f[] = {1,2,3,4,5,6};
int *g = f;
size_t len_f = sizeof(f)/sizeof(int)//正确计算方法
size_t len_g = sizeof(g)/sizeof(int)

上面的len_f和len_g的值相等吗?显然并不相等。事实上,只有len_f得到了数组f的长度,而len_g的值并没有任何实际意义。

不能作为参数的数组

所谓的数组不能作为参数,并不是指声明的数组不能作为参数传递,而是指当数组名作为参数时,数组名会被转换为指向该数组下标为0的元素的指针。 而下面的两种声明,其实也是等效的:

size_t arrayLen(const int *arr);
size_t arrayLen(const int arr[]);

我们来看一个例子,说明数组作为参数的情况:

#include <stdio.h>
int arraySum(const int arr[])
{
    unsigned int loop = 0;
    /*循环前计算好长度,提高性能*/
    unsigned int len = sizeof(arr)/sizeof(int);
    int sum = 0;
    if(NULL == arr)
    {
        return 0;
    }
    for(loop = 0; loop < len; loop++)
    {
        sum+=arr[loop];
    }
    return sum;   
}
int main(void)
{
    int a[] = {1,2,3,4,5,6};
    int sum = arraySum(a);
    printf("arr sum is %d",sum);
    return 0;
}

我们运行上面的程序,发现最终结果并不是我们预期的21,而是3。问题在于,a作为参数传入到arraySum中时,它是作为指针的,那么在函数内部计算sizeof(arr)自然只是得到了指针占用的内存大小。对于64位程序,这个大小是8,那么len的值为2,最终只计算了两个元素的和。

思考:该如何修改上面的程序才能得到正确的结果?

总结

我们来总结一下前面的核心内容:

  • 数组下标运算实际上都是通过指针进行的。
  • 数组名代表着指向该数组中下标为0的元素的指针,但有例外:sizeof(数组名)返回整个数组的大小,而非指针大小;&数组名返回一个指向数组的指针,而不是指向该数组中下标为0的元素的指针的指针。
  • 数组名作为参数时,数组名会被转换成指向该数组下标为0的元素的指针。
  • 指针操作可能比下标操作效率高,但可维护性却不一定有下标操作好。
  • 数组和指针不相等。

思考

下面的代码输出结果是什么?

#include<stdio.h>
int main(void)
{

    int a[5] = {1,2,3,4,5};
    int *p = (int*)(&a+1);
    printf("%d,%d",*(a+1),*(p-1));
    return 0;
}

本文分享自微信公众号 - 编程珠玑(shouwangxiansheng),作者:守望先生

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-10-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 再说数组

    这里就必须说到前面提的例外,&作用于数组名时,它是当做数组的,因此取地址后,得到的是一个指向具有5个元素的数组的指针,即int (*)[5]。而指针运算又是什么...

    编程珠玑
  • 为什么建议你用nullptr而不是NULL?

    在C语言中,我们常常用NULL作为指针变量的初始值,而在C++中,却不建议你这么做。

    编程珠玑
  • GDB调试入门,看这篇就够了

    GDB(GNU Debugger)是UNIX及UNIX-like下的强大调试工具,可以调试ada, c, c++, asm, minimal, d, fortr...

    编程珠玑
  • ERP 的关键评估因素

    编译丽丽 对企业资源规划 (ERP) 系统来说,哪些特性是最重要的, 特别是 ERP 的选型这项目艰巨的任务, 确定哪些特性重要、哪些特性不重要这并非易事。 以...

    人称T客
  • Flask前后端分离实践:Todo App(3)

    如果你们是看了Miguel的狗书,或是李辉大大的狼书,一定知道我们在提交表单时,常常会附带上一个隐藏的csrf值,用来防止CSRF攻击。关于CSRF是什么这里就...

    岂不美哉Frost
  • 一周极客热文:微软将推新编程语言M#:系统编程级别的C#

    据国外媒体VB报道,微软正在开发一款新的编程语言:M# 。它在C#的基础上添加了系统编程特性,可用来构建各种类型的应用,尤其是云计算应用。 微软员工Joe Du...

    钱曙光
  • 工具 | 15个排名最佳的数据科学Python包

    排名 Python 和 R 语言是数据科学中最常见、最受欢迎的工具之一。而且因为 Python 的简单易用,相对其他语言,我们可以使用更少的代码就能表达大多数概...

    小莹莹
  • 资源 | 15个排名最佳的数据科学Python包

    选自The data Incubator 机器之心编译 参与:蒋思源、黄小天 Python 语言是数据科学中最常见、最受欢迎的工具之一。近日,Data Incu...

    机器之心
  • Python教程之HelloWorld

    孙亖
  • Canvas 动画引擎解析与微信小程序中的应用

    抗击疫情,腾讯云在行动。在开发微信小程序的过程中,我们经常需要展现一些图形和图表。目前市面上有好几款常用的图形库,在这些图形库的底层都有渲染引擎在支撑。 ZRe...

    腾讯云大学

扫码关注云+社区

领取腾讯云代金券