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

相关文章

hdu2241(二分+合并数组)

题意:判断是否存在a+b+c = x,a,b,c分别属于集合A,B,C 如果用暴力会超时,所以这里用到了数组合并,将b,c数组合并成d,d数组存的是b,c数组元素的和,然后对d数组进行二分就可以了 代码如下(附注释): #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<que

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

hdu 1166 敌兵布阵(树状数组 or 线段树)

题意是求一个线段的和,在线段上可以进行加减的修改。 树状数组的模板题。 代码: #include <stdio.h>#include <string.h>const int maxn = 50000 + 1;int c[maxn];int n;int lowbit(int x){return x & -x;}void add(int x, int num){while

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目: 题解: static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num & MASK1) == 0) {return

MiniGPT-3D, 首个高效的3D点云大语言模型,仅需一张RTX3090显卡,训练一天时间,已开源

项目主页:https://tangyuan96.github.io/minigpt_3d_project_page/ 代码:https://github.com/TangYuan96/MiniGPT-3D 论文:https://arxiv.org/pdf/2405.01413 MiniGPT-3D在多个任务上取得了SoTA,被ACM MM2024接收,只拥有47.8M的可训练参数,在一张RTX

【C++学习笔记 20】C++中的智能指针

智能指针的功能 在上一篇笔记提到了在栈和堆上创建变量的区别,使用new关键字创建变量时,需要搭配delete关键字销毁变量。而智能指针的作用就是调用new分配内存时,不必自己去调用delete,甚至不用调用new。 智能指针实际上就是对原始指针的包装。 unique_ptr 最简单的智能指针,是一种作用域指针,意思是当指针超出该作用域时,会自动调用delete。它名为unique的原因是这个

如何确定 Go 语言中 HTTP 连接池的最佳参数?

确定 Go 语言中 HTTP 连接池的最佳参数可以通过以下几种方式: 一、分析应用场景和需求 并发请求量: 确定应用程序在特定时间段内可能同时发起的 HTTP 请求数量。如果并发请求量很高,需要设置较大的连接池参数以满足需求。例如,对于一个高并发的 Web 服务,可能同时有数百个请求在处理,此时需要较大的连接池大小。可以通过压力测试工具模拟高并发场景,观察系统在不同并发请求下的性能表现,从而

C语言:柔性数组

数组定义 柔性数组 err int arr[0] = {0}; // ERROR 柔性数组 // 常见struct Test{int len;char arr[1024];} // 柔性数组struct Test{int len;char arr[0];}struct Test *t;t = malloc(sizeof(Test) + 11);strcpy(t->arr,

C语言指针入门 《C语言非常道》

C语言指针入门 《C语言非常道》 作为一个程序员,我接触 C 语言有十年了。有的朋友让我推荐 C 语言的参考书,我不敢乱推荐,尤其是国内作者写的书,往往七拼八凑,漏洞百出。 但是,李忠老师的《C语言非常道》值得一读。对了,李老师有个官网,网址是: 李忠老师官网 最棒的是,有配套的教学视频,可以试看。 试看点这里 接下来言归正传,讲解指针。以下内容很多都参考了李忠老师的《C语言非