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的元素的指针。
- 指针操作可能比下标操作效率高,但可维护性却不一定有下标操作好。
- 数组和指针不相等。
C语言入坑指南-数组之谜的更多相关文章
- C语言入坑指南-被遗忘的初始化
前言 什么是初始化?为什么要初始化?静态变量和局部变量的初始化又有什么区别?实际应用中应该怎么做?本文将一一回答这些问题. 什么是初始化 初始化指的是对数据对象或者变量赋予初始值.例如: int va ...
- C语言入坑指南-缓冲区溢出
前言 缓冲区溢出通常指的是向缓冲区写入了超过缓冲区所能保存的最大数据量的数据.如果说之前所提到的一些问题可能只是影响部分功能的实现,那么缓冲区溢出将可能会造成程序运行终止,被不安全代码攻击等严重问题, ...
- Rust入坑指南:核心概念
如果说前面的坑我们一直在用小铲子挖的话,那么今天的坑就是用挖掘机挖的. 今天要介绍的是Rust的一个核心概念:Ownership.全文将分为什么是Ownership以及Ownership的传递类型两部 ...
- ElasticSearch入坑指南之概述及安装
---恢复内容开始--- ElasticSearch入坑指南之概述及安装 了解ElasticSearch ElasticSearch(简称ES)基于Lucene的分布式全文检索引擎.使用ES可以实现近 ...
- Rust入坑指南:鳞次栉比
很久没有挖Rust的坑啦,今天来挖一些排列整齐的坑.没错,就是要介绍一些集合类型的数据类型."鳞次栉比"这个标题是不是显得很有文化? 在Rust入坑指南:常规套路一文中我们已经介绍 ...
- electron入坑指南
electron入坑指南 简介 electron 实际集成chrome浏览器和node环境, 运行你写的网页 app 基本目录结构 index.html 名称可以不是index, 这个文件与普通网页的 ...
- Elasticsearch入坑指南之RESTful API
Elasticsearch入坑指南之RESTful API Tags:Elasticsearch ES为开发者提供了非常丰富的基于Http协议的Rest API,通过简单的Rest请求,就可以实现非常 ...
- eclipse中导入外部包却无法查看对应源码或Javadoc的入坑指南
eclipse中导入外部包却无法查看对应源码或Javadoc的 入坑指南 出现这个错误的原因是,你虽然导入了.jar包,但没有配置对应的Javadoc或源码路径,所以在编辑器中无法查看源 码和对应AP ...
- Rust入坑指南:亡羊补牢
如果你已经开始学习Rust,相信你已经体会过Rust编译器的强大.它可以帮助你避免程序中的大部分错误,但是编译器也不是万能的,如果程序写的不恰当,还是会发生错误,让程序崩溃.所以今天我们就来聊一聊Ru ...
随机推荐
- Noip模拟76 2021.10.14
T1 洛希极限 上来一道大数据结构或者单调队列优化$dp$ 真就没分析出来正解复杂度 正解复杂度$O(q+nm)$,但是据说我的复杂度是假的 考虑一个点转移最优情况是从它上面的一个反$L$形转移过来 ...
- STM32 学习笔记之中断应用概览--以f103为例
异常类型 F103 在内核水平上搭载了一个异常响应系统, 支持为数众多的系统异常和外部中断.其中系统异常有8 个(如果把Reset 和HardFault 也算上的话就是10 个),外部中断有60个.除 ...
- python文件读写及修改
转载:https://www.cnblogs.com/zhxwind/p/8761618.html 文件的读写有三种形式:读.写和追加. 一.读模式 r 和读写模式 r+ 1.读模式 r 读模式r特点 ...
- 用python写一个自动化盲注脚本
前言 当我们进行SQL注入攻击时,当发现无法进行union注入或者报错等注入,那么,就需要考虑盲注了,当我们进行盲注时,需要通过页面的反馈(布尔盲注)或者相应时间(时间盲注),来一个字符一个字符的进行 ...
- redis 的单机安装
redis 单机安装 参考文档地址:https://www.cnblogs.com/withfeel/p/10655994.html 1,下载redis,下载地址http://download.red ...
- Vue 基础自查——watch、computed和methods的区别
1 前言 创建一个Vue实例时,可以传入一个选项对象 const vm = new Vue({ data: { msg: 'hello' }, computed: {}, methods: {}, w ...
- ELK集群之kibana(4)
kibane安装及基础使用 Kibana的安装 Kibana包含前端展示.es操作简化 yum localinstall kibana-7.6.2-x86_64.rpm -y Kibana配置修改ki ...
- idea查看方法在哪里被调用
方法一 选中方法名,右键选择Find Usages 方法二 选中方法快捷键ctrl + alt + h查看Hierarchy Callers
- PTA 7-1 邻接矩阵表示法创建无向图 (20分)
PTA 7-1 邻接矩阵表示法创建无向图 (20分) 采用邻接矩阵表示法创建无向图G ,依次输出各顶点的度. 输入格式: 输入第一行中给出2个整数i(0<i≤10),j(j≥0),分别为图G的顶 ...
- Java使用iText7生成PDF
前言 我们之前使用js库html2canvas + jspdf实现html转PDF.图片,并下载(详情请戳:html页面转PDF.图片操作记录),大致原理是将页面塞到画布里,以图片的方式放到PDF中, ...