小议C语言中的数组和指针

2024-08-31 19:32
文章标签 语言 数组 指针 小议

本文主要是介绍小议C语言中的数组和指针,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.引言
  在C语言中,数组和指针是其中非常重要又联系紧密的两种数据类型,同时也是重点难点集中的地方。在学习这些内容时,经常会碰到这样一些问题,例如:数组名是什么,就是首地址吗?数组如何访问元素?数组为什么不能进行越界检查?数组表示法和指针表示法有何关系,谁更好?正确理解这些问题,对指针和数组的使用是非常有帮助的。
2.数组名的含义
  在大多数的教材中都对数组名作出这样的解释:数组是一组数据的集合,它们在内存中占据一片连续的存储空间,数组名并不代表整个数组,而是数组占据的连续空间的起始地址[1]。例如:
  int a[10], *p;
  p=a;
  在上面的语句直接将数组名赋值给指针变量p,指针变量p就获取了数组a的首地址。在这里a表示数组的首地址,但并不是所有场合数组名都是首地址。例如:
  int a[10];
  printf("%dn",sizeof(a));
  printf("%dn",a);
  printf("%dn",&a);
  将程序补充完整运行后得到如下结果:
  小议C语言中的数组和指针
  如果将数组名理解为指针常量,sizeof(a)的值应该是4,因为所有类型的指针都只占4个字节。而实际运行的结果是40,sizeof(a)返回的是整个数组所占的字节数。同样,如果数组名是指针常量的话,&a的值应该是指针的地址, &a和a应该是两个不同的结果。而结果是数组的首地址,&a和a结果相同。可以看出,在sizeof(a)和&a这两种用法中,a都被理解为数组本身而不是数组的首地址。
  所以,数组名和指针常量并不等同,数组具有确定数量的元素,数组名除了是首地址外还包含了数组长度、数组类型等的属性信息。而指针是一个标量,只有一个表示地址的值。当数组名出现在表达式(sizeof和&除外)中,编译器会其生成一个指针常量。因此,应该说数组名的值是指针常量,而不能说数组名就是指针常量[2]。
3.字符串常量和指针
  字符串常量在内存中占据一块连续的存储空间,不能当作一个简单的标量,这点和数组是非常相似的。如果表达式中出现字符串常量该如何理解呢?既然数组名的值是数组的首地址,那么字符串常量是否也有类似的用法呢?我们知道字符串常量可以赋值给字符指针变量,例如:
  char *ptr="Hello";
  "Hello"是个字符串常量,通过上面的操作指针ptr获取了该字符串在内存中的首地址
这里字符串常量理解为其首地址。如果字符串"Hello"出现在其他地方是否也可以同样理解呢?请看下面的程序。
  printf("%sn","abc"+1);
  printf("%cn",*"abc");
  printf("%cn","abc"[2]);
  printf("%cn",*("abc"+2));
  将程序补充完整运行后结果如下:
  小议C语言中的数组和指针
  结果跟预料的一样,在上面的表达式中,字符串常量的值也是该字符串的首地址。"abc"+1的结果是一个指向字符b的指针;*"abc"表示首地址指向的一个字符;"abc"[2]和*("abc"+2)等价,表示将首地址加2,然后取出新地址指向的一个字符。这和我们对数组名的使用方式类似,字符串常量的值是该字符串的首地址。字符串常量的这种用法可读性较差,但在某些场合下使用会有很好的效果。例如下面的函数实现将10进制数转换为16进制数。
  Btoascii(unsigned int num)
  {   unsigned int quo,rem;   //quo为除16的商,rem为除16的余数
      quo=num/16;
      if(quo!=0)  Btoascii(quo);
      rem = num;
      if(rem<10)  putchar(rem+'0');
      else        putchar(rem-10+'A');}
  由于字符0到9和字符A到F的ascII码并不连续,所以上面的程序中使用了if分支来对取余后的结果分别处理转换成相应的16进制字母。如果使用字符串常量则比较简单。改写后的函数如下:
  Btoascii(unsigned int num)
  {   unsigned int quo,rem;   //quo为除16的商,rem为除16的余数
      quo=num/16;
      if(quo!=0)  Btoascii(quo);
      rem = num;
      putchar("0123456789ABCDE"[rem]);}
4.数组如何访问元素
  在C语言中,数组访问元素的[ ]符号被解释为下标运算符。在大多数的教材中是这样解释的:在计算整型a[i]时,先将整型的字节数4×i,再加到首地址a上得到元素地址,然后通过地址取出该元素的值,即a[i]和*(a+i)等价。确实,从两种写法编译后的汇编语句来看是完全相同,但这只能证明两者是等价的,但并不能说明a[i]就是按*(a+i)编译处理的。那么有没有什么方法可以验证a[i]就是按*(a+i)编译处理的呢?
  如果下标表示法是按指针表示法编译执行的,那么可以设想2[a]的写法也应该是对的,因为它可以解释为*(2+a),同样可以访问数组的第3个元素。编程如下:
  int a[10]={0,1,2,3,4,5,6,7,8,9};
  printf("%dn",2[a]);
  将程序补充完整后运行结果为:
  小议C语言中的数组和指针
  可以看出2[a]也是可以编译通过的写法,并且就等于a[2],这只有按指针法来理解才说的通,否则它是不符合C语言里关于标识符的命名规则的。理解了下标法的编译处理方式,也就不难理解为什么C语言中不对数组进行越界检查了。因为数组访问元素的下标运算实际是变址运算,与指针表示法是等同的,所以即使下标值超过了数组的长度,也是可以计算地址并访问数据的,只不过访问到的数据是不确定的。
5.下标法和指针法的效率比较
  既然下标法和指针法是等价的,那么哪种表示法的效率更高呢?在很多教材里都提到指针的代码效率高,但它究竟是如何做到的呢,我们试着来分析一下。例如:将数组元素依次赋值为0,下标法代码如下:
  int a[10],i;
  for(i=0;i<10;i++)
  a[i]=0;
  在VC6.0中编写这段代码并查看对应的汇编语言结果如下:
  00401028 mov  dword ptr [ebp-2Ch],0
  0040102F jmp  main+2Ah (0040103a)
  00401031 mov  eax,dword ptr [ebp-2Ch] 
  00401034 add  eax,1
  00401037 mov  dword ptr [ebp-2Ch],eax 
  0040103A cmp  dword ptr [ebp-2Ch],0Ah
  0040103E jge  main+3Dh (0040104d)
  00401040 mov  ecx,dword ptr [ebp-2Ch] 
  00401043 mov  dword ptr [ebp+ecx*4-28h],0 
  0040104B jmp  main+21h (00401031)
  将源代码改写成指针的形式:
  int a[10],*p;
  f

or(p=a;p<a+10;p++)
  *p=0;
  在VC6.0中编写这段代码并查看对应的汇编语言结果如下:
  00401028 lea  eax,[ebp-28h]
  0040102B mov  dword ptr [ebp-2Ch],eax
  0040102E jmp  main+29h (00401039)
  00401030 mov  ecx,dword ptr [ebp-2Ch]
  00401033 add  ecx,4
  00401036 mov  dword ptr [ebp-2Ch],ecx
  00401039 lea  edx,[ebp]
  0040103C cmp  dword ptr [ebp-2Ch],edx
  0040103F jae  main+3Ch (0040104c)
  00401041 mov  eax,dword ptr [ebp-2Ch]
  00401044 mov  dword ptr [eax],0
  0040104A jmp  main+20h (00401030)
  比较这两段汇编代码,可以看到对a[i]的求值需要先取得i的值,并将i与4(整型的长度)相乘然后从地址中取值,而乘法是要花费一定的时间和空间的。指针表示法中,p++的实现是直接将p的值加4,没有用到乘法。显然这两段代码,第2种使用了指针表示的代码执行效率较高。
  从上面的举例可以看出,当要让数组1次1步的移动时,指针比下标要更有效率。但要注意仅仅将下标法改写成指针法是没有任何区别的。例如:
  int a[10],i;     int a[10],i;
  for(i=0;i<10;i++)    for(i=0;i<10;i++)
  a[i]=0;      *(a+i)=0;
  这两段代码产生的编译代码完全一样。指针的高效率体现在将下标变址语言中的乘法转换成了指针变量的加法,实现这种转换的代码才会获得效率的提高。对程序而言,可读性对代码的维护也是非常重要的,这点下标法要比指针法好。因此,在对可读性影响不大的前提下,可选择用指针法代替下标法。
6.小结
  数组和指针是C语言中频率很高的两种数据类型,其中涉及到的语法内容很多。本文主要对教材中较少涉及或未深入分析的一些语法现象(例如,数组名的含义、数组如何访问元素、下标法和指针法的效率比较)进行了分析研究。通过这样的分析,可以帮助理解数组和指针两者之间的一些语法关联,并促使学习者对C语言中更多关联语法现象进行思考,更好地掌握C语言的语法规则。

这篇关于小议C语言中的数组和指针的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1124799

相关文章

Java数组动态扩容的实现示例

《Java数组动态扩容的实现示例》本文主要介绍了Java数组动态扩容的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1 问题2 方法3 结语1 问题实现动态的给数组添加元素效果,实现对数组扩容,原始数组使用静态分配

C语言逗号运算符和逗号表达式的使用小结

《C语言逗号运算符和逗号表达式的使用小结》本文详细介绍了C语言中的逗号运算符和逗号表达式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习... 在C语言中逗号“,”也是一种运算符,称为逗号运算符。 其功能是把两个表达式连接其一般形式为:表达

Go语言实现桥接模式

《Go语言实现桥接模式》桥接模式是一种结构型设计模式,它将抽象部分与实现部分分离,使它们可以独立地变化,本文就来介绍一下了Go语言实现桥接模式,感兴趣的可以了解一下... 目录简介核心概念为什么使用桥接模式?应用场景案例分析步骤一:定义实现接口步骤二:创建具体实现类步骤三:定义抽象类步骤四:创建扩展抽象类步

GO语言实现串口简单通讯

《GO语言实现串口简单通讯》本文分享了使用Go语言进行串口通讯的实践过程,详细介绍了串口配置、数据发送与接收的代码实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 目录背景串口通讯代码代码块分解解析完整代码运行结果背景最近再学习 go 语言,在某宝用5块钱买了个

GO语言zap日志库理解和使用方法示例

《GO语言zap日志库理解和使用方法示例》Zap是一个高性能、结构化日志库,专为Go语言设计,它由Uber开源,并且在Go社区中非常受欢迎,:本文主要介绍GO语言zap日志库理解和使用方法的相关资... 目录1. zap日志库介绍2.安装zap库3.配置日志记录器3.1 Logger3.2 Sugared

Go语言中如何进行数据库查询操作

《Go语言中如何进行数据库查询操作》在Go语言中,与数据库交互通常通过使用数据库驱动来实现,Go语言支持多种数据库,如MySQL、PostgreSQL、SQLite等,每种数据库都有其对应的官方或第三... 查询函数QueryRow和Query详细对比特性QueryRowQuery返回值数量1个:*sql

GO语言中gox交叉编译的实现

《GO语言中gox交叉编译的实现》本文主要介绍了GO语言中gox交叉编译的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录一、安装二、使用三、遇到的问题1、开启CGO2、修改环境变量最近在工作中使用GO语言进行编码开发,因

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础

JavaScript对象转数组的三种方法实现

《JavaScript对象转数组的三种方法实现》本文介绍了在JavaScript中将对象转换为数组的三种实用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友... 目录方法1:使用Object.keys()和Array.map()方法2:使用Object.entr

Go语言中json操作的实现

《Go语言中json操作的实现》本文主要介绍了Go语言中的json操作的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录 一、jsOChina编程N 与 Go 类型对应关系️ 二、基本操作:编码与解码 三、结构体标签(Struc