小议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

相关文章

JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法

《JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法》:本文主要介绍JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法,每种方法结合实例代码给大家介绍的非常... 目录引言:为什么"相等"判断如此重要?方法1:使用some()+includes()(适合小数组)方法2

GO语言短变量声明的实现示例

《GO语言短变量声明的实现示例》在Go语言中,短变量声明是一种简洁的变量声明方式,使用:=运算符,可以自动推断变量类型,下面就来具体介绍一下如何使用,感兴趣的可以了解一下... 目录基本语法功能特点与var的区别适用场景注意事项基本语法variableName := value功能特点1、自动类型推

GO语言中函数命名返回值的使用

《GO语言中函数命名返回值的使用》在Go语言中,函数可以为其返回值指定名称,这被称为命名返回值或命名返回参数,这种特性可以使代码更清晰,特别是在返回多个值时,感兴趣的可以了解一下... 目录基本语法函数命名返回特点代码示例命名特点基本语法func functionName(parameters) (nam

Go语言连接MySQL数据库执行基本的增删改查

《Go语言连接MySQL数据库执行基本的增删改查》在后端开发中,MySQL是最常用的关系型数据库之一,本文主要为大家详细介绍了如何使用Go连接MySQL数据库并执行基本的增删改查吧... 目录Go语言连接mysql数据库准备工作安装 MySQL 驱动代码实现运行结果注意事项Go语言执行基本的增删改查准备工作

Go语言使用Gin处理路由参数和查询参数

《Go语言使用Gin处理路由参数和查询参数》在WebAPI开发中,处理路由参数(PathParameter)和查询参数(QueryParameter)是非常常见的需求,下面我们就来看看Go语言... 目录一、路由参数 vs 查询参数二、Gin 获取路由参数和查询参数三、示例代码四、运行与测试1. 测试编程路

Go语言使用net/http构建一个RESTful API的示例代码

《Go语言使用net/http构建一个RESTfulAPI的示例代码》Go的标准库net/http提供了构建Web服务所需的强大功能,虽然众多第三方框架(如Gin、Echo)已经封装了很多功能,但... 目录引言一、什么是 RESTful API?二、实战目标:用户信息管理 API三、代码实现1. 用户数据

Go语言网络故障诊断与调试技巧

《Go语言网络故障诊断与调试技巧》在分布式系统和微服务架构的浪潮中,网络编程成为系统性能和可靠性的核心支柱,从高并发的API服务到实时通信应用,网络的稳定性直接影响用户体验,本文面向熟悉Go基本语法和... 目录1. 引言2. Go 语言网络编程的优势与特色2.1 简洁高效的标准库2.2 强大的并发模型2.

Java中数组与栈和堆之间的关系说明

《Java中数组与栈和堆之间的关系说明》文章讲解了Java数组的初始化方式、内存存储机制、引用传递特性及遍历、排序、拷贝技巧,强调引用数据类型方法调用时形参可能修改实参,但需注意引用指向单一对象的特性... 目录Java中数组与栈和堆的关系遍历数组接下来是一些编程小技巧总结Java中数组与栈和堆的关系关于

Go语言使用sync.Mutex实现资源加锁

《Go语言使用sync.Mutex实现资源加锁》数据共享是一把双刃剑,Go语言为我们提供了sync.Mutex,一种最基础也是最常用的加锁方式,用于保证在任意时刻只有一个goroutine能访问共享... 目录一、什么是 Mutex二、为什么需要加锁三、实战案例:并发安全的计数器1. 未加锁示例(存在竞态)

C语言自定义类型之联合和枚举解读

《C语言自定义类型之联合和枚举解读》联合体共享内存,大小由最大成员决定,遵循对齐规则;枚举类型列举可能值,提升可读性和类型安全性,两者在C语言中用于优化内存和程序效率... 目录一、联合体1.1 联合体类型的声明1.2 联合体的特点1.2.1 特点11.2.2 特点21.2.3 特点31.3 联合体的大小1