当前位置:首页 > 技术学院 > 技术前线
[导读]指针与数组是C语言中很重要的两个概念,它们之间有着密切的关系,利用这种关系,可以增强处理数组的灵活性,加快运行速度,本文着重讨论指针与数组之间的联系及在编程中的应用。

最早在大学里学c语言的时候对指针和数组这两个概念混淆不清,经常混为一谈,相信很多学友和我的情况类似。在这里,我会将自身对c语言的了解以及对这本书中相关内容的理解,把指针和数组的联系与区别总结一下。

一.指针与数组的联系:

指针与数组是C语言中很重要的两个概念,它们之间有着密切的关系,利用这种关系,可以增强处理数组的灵活性,加快运行速度,本文着重讨论指针与数组之间的联系及在编程中的应用。

1.指针与数组的关系

当一个指针变量被初始化成数组名时,就说该指针变量指向了数组。如:

char str[20], *ptr;

ptr=str;

ptr被置为数组str的第一个元素的地址,因为数组名就是该数组的首地址,也是数组第一个元素的地址。此时可以认为指针ptr就是数组str(反之不成立),这样原来对数组的处理都可以用指针来实现。如对数组元素的访问,既可以用下标变量访问,也可以用指针访问。

2.指向数组元素的指针

若有如下定义:

int a[10], *pa;

pa=a;

则p=&a[0]是将数组第1个元素的地址赋给了指针变量p。

实际上,C语言中数组名就是数组的首地址,所以第一个元素的地址可以用两种方法获得:p=&a[0]或p=a。

这两种方法在形式上相像,其区别在于:pa是指针变量,a是数组名。值得注意的是:pa是一个可以变化的指针变量,而a是一个常数。因为数组一经被说明,数组的地址也就是固定的,因此a是不能变化的,不允许使用a++、++a或语句a+=10,而pa++、++pa、pa+=10则是正确的。由此可见,此时指针与数组融为一体。

3.指针与一维数组

理解指针与一维数组的关系,首先要了解在编译系统中,一维数组的存储组织形式和对数组元素的访问方法。

一维数组是一个线形表,它被存放在一片连续的内存单元中。C语言对数组的访问是通过数组名(数组的起始地址)加上相对于起始地址的相对量(由下标变量给出),得到要访问的数组元素的单元地址,然后再对计算出的单元地址的内容进行访问。通常把数据类型所占单元的字节个数称为扩大因子。

实际上编译系统将数组元素的形式a[i]转换成*(a+i),然后才进行运算。对于一般数组元素的形式:<数组名>[<下标表达式>],编译程序将其转换成:*(<数组名>+<下标表达式>),其中下标表达式为:下标表达式*扩大因子。整个式子计算结果是一个内存地址,最后的结果为:*<地址>=<地址所对应单元的地址的内容>。由此可见,C语言对数组的处理,实际上是转换成指针地址的运算。

数组与指针暗中结合在一起。因此,任何能由下标完成的操作,都可以用指针来实现,一个不带下标的数组名就是一个指向该数组的指针。

4.指针与多维数组

用指针变量可以指向一维数组,也可以指向多维数组。但在概念上和使用上,多维数组的指针比一维数组的指针要复杂一些。

例如,在一个三维数组中,引用元素c[i][j][k]的地址计算最终将换成:*(*(*(c+i)+j)+k)。了解了多维数组的存储形式和访问多维数组元素的内部转换公式后,再看当一个指针变量指向多维数组及其元素的情况。

1)指向数组元素的指针变量

若有如下说明:

int a[3][4];

int *p;

p=a;

p是指向整型变量的指针;p=a使p指向整型二维数组a的首地址。

*(*(p+1)+2)表示取a[1][2]的内容;*p表示取a[0][1]的内容,因为p是指向整型变量的指针;p++表示p的内容加1,即p中存放的地址增加一个整型量的字节数2,从而使p指向下一个整型量a[0][1]。

2)指向由j个整数组成的一维数组的指针变量

当指针变量p不是指向整型变量,而是指向一个包含j个元素的一维数组。如果p=a[0],则p++不是指向a[0][1],而是指向a[1]。这时p的增值以一维数组的长度为单位。

5.指针与字符数组

C语言中许多字符串操作都是由指向字符数组的指针及指针的运算来实现的。因为对于字符串来说,一般都是严格的顺序存取方式,使用指针可以打破这种存取方式,更为灵活地处理字符串。

另外由于字符串以′\0′作为结束符,而′\0′的ASCII码是0,它正好是C语言的逻辑假值,所以可以直接用它作为判断字符串结束的条件,而不需要用字符串的长度来判断。C语言中类似的字符串处理函数都是用指针来完成,使程序运行速度更快、效率更高,而且更易于理解。

二.指针与数组的区别:

1.把数组作为参数传递的时候,会退化为指针

数组名作为函数形参时,在函数体内,其失去了本身的内涵,仅仅只是一个指针;很遗憾,在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。

所以,数组名作为函数形参时,其沦落为一个普通指针!它的贵族身份被剥夺,成了一个地地道道的只拥有4个字节的平民。

典型的情况是

void func(int A[])

{

//sizeof(A)得到的是4bytes

}

int main()

{

int a[10]; //sizeof(a) 得到的结果是40bytes

funct(a);

}

2、数组名可作为指针常量

根据结论2,数组名可以转换为指向其指代实体的指针,所以程序1中的第5行数组名直接赋值给指针,程序2第7行直接将数组名作为指针形参都可成立。

下面的程序成立吗?

int intArray[10];

intArray++;

读者可以编译之,发现编译出错。原因在于,虽然数组名可以转换为指向其指代实体的指针,但是它只能被看作一个指针常量,不能被修改。

而指针,不管是指向结构体、数组还是基本数据类型的指针,都不包含原始数据结构的内涵,在WIN32平台下,sizeof操作的结果都是4。

顺便纠正一下许多程序员的另一个误解。许多程序员以为sizeof是一个函数,而实际上,它是一个操作符,不过其使用方式看起来的确太像一个函数了。语句sizeof(int)就可以说明sizeof的确不是一个函数,因为函数接纳形参(一个变量),世界上没有一个C/C++函数接纳一个数据类型(如int)为"形参"。

3.对于问题:为什么用strcpy()函数时,

char a[3] = "abc";

strcopy(a,"end");

-------------------没有错。

用-----------------

char *a = "abc";

strcopy(a,"end");

------------------运行时就有错呢?

解释如下:

char *a = "abc"; abc是一个字符串常量,有它自己的存储空间,因为分配在只读数据块,我们无法直接访问。这样赋值后,a只能读,不能写

所以strcpy(a, "end")不行,只有当你为a分配非常量的存储空间后才行

如:

char *a = new char[4];

strcpy(a, "end");

printf("%s", a);

delete []a;

4//main.cpp

int array[3] = {7, 8, 9}; //全局变量

int main()

{

Test1();

Test2();

return 0;

}

//Test1.cpp

extern int array[3];

void Test1()

{

cout << array[1] << endl;

}

//Test2.cpp

extern int *array; //这个地方是不同的

void Test2()

{

cout << array << endl;

cout << array[1] << endl;

}

Test1()和Test2()的输出结果相同吗?

编译一下再看看,就发现执行Test2会有奇怪的结果,第一条语句的输出是7, 第二条语句会死机。而Test1()却一切正常。

这是为什么?

原因在编译器。在Test1.cpp中,由于使用了extern所以编译的时候要先用占位符将array标志一下,在连接的时候用main.cpp中的array进行替换。当编译器给变量赋值的时候,他认为这个值是该变量的地址。就好比:int i = 5;在编译器中编译后会把5的地址0x8291记录

而不是5,在i需要值的时候去0x8291这个地址去取出值给i(这里的i是全局的或者静态变量,这时候才能在编译阶段确定地址)。

所以在Test1.cpp中,把array的地址给了array,假设这个地址是0x34fe,但是由于数组的特性array == &array,所以这里是正常的。而在Test2.cpp中,array是个指针,所以会去0x34fe中取出值给array,所以array = 0x0007(数组的第一个值,这里要做地址,因为是给指针用)

这就是看到的Test2()的输出结果。显然array[1]会死机,因为0x0007地址是没有被分配的,并且是给操作系统用的而不是给用户用的。

5.数组和指针的分配

数组是开辟一块连续的内存空间,数组本身的标示符代表整个数组,可以用sizeof取得真实的大小;指针则是只分配一个指针大小的内存,并可把它的值指向某个有效的内存空间

[全局的和静态的]

char *p= "hello ";

一个指针,指向只读数据块(section)里的 "hello ",可被编译器放入字符串池(也就是说, 你在写一个char *q= "hello ",可能和p共享数据)

char a[]= "hello ";

一个数组,分配在可写数据块(section),不会被放到字符串池中

[局部]

char *p= "hello ";

一个指针,指向只读数据块(section)里的 "hello ",可被编译器放入字符串池(也就是说, 你在写一个char *q= "hello ",可能和p共享数据),另外,在函数中可以返回它的地址,也就是说,指针是局部变量,他指向的数据却是全局的.

char a[]= "hello ";

一个数组,分配在堆栈上,初始化由编译器进行(短的话直接用指令填充,长的就从全局字符串表拷贝),不会被放到字符串池中(但是却可能从字符串池中拷贝过来),也不应该返回

它的地址.

[代码中的字面字符串]

printf( "%s\n ", "hello ");

这两个字面常量( "%s\n "和 "hello "),都在只读数据块里

[用途]

全局指针

用于不需要修改内容,却可能会修改指针的情况(当然,不修改也没人反对)

全局数组,用于不需要修改地址,却需要修改内容的场合

既需要修改指针,有需要修改内容怎么办呢?定义一个数组,在定义一个指针指向它就可以了

函数中如果不需要修改字符串的内容,应该尽量用char*p= "xxx "这种写法.

初始化的局部字符数组影响效率,一般应该尽量避开(应该使用的情况下则不要犹豫)

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭