C语言数组的一些常见问题

1.实参与形参的区别

函数的参数分为形参和实参两种。形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用。实参出现在主调函数中,进入被调函数后,实参变量也不能使用。形参和实参的功能是作数据传送。发生函数调用时,主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。 
函数的形参和实参具有以下特点: 
a. 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。 
b. 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使实参获得确定值。 
c. 实参和形参在数量上,类型上,顺序上应严格一致,否则会发生类型不匹配”的错误。 
d. 函数调用中发生的数据传送是单向的。即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。

2.什么是数组

简单说,数组就是许多相同类型的变量的集合。类型是什么,类型是某一类的值的集合。数组是一种特殊的类型,数组的"地址"是首地址,他的值也是首地址。虽然值相同但是类型是不同的。比如 int a[3] = {1,2,3} ,sizeof(a)是12,因为a是一个数组。sizeof(&a)是4因为&a是一个数组指针也是指针变量所以是4。

3.什么是指针

指针其实就是存有地址的变量,和其他变量一样,也有着自己的类型,唯一的区别就是,指针在定义时需要加一个*号,比如,int *p, 在32位的编译器下,指针都是4个字节。指针身为变量,当然也可以进行正常的加减,但是它与普通变量的运算不同的是,指针的加减 所加所减的是指针所指向的类型。对于指向数组的指针,我们称之为数组指针,需要注意的是,数组的数组名代表数组首个元素的地址。因此,在某些场合我们可以将数组名当作一个指针看待,但是在使用数组名作为一个指针的时候,一定不能疏忽它与真正的指针之间的不同之处。

4.数组作为参数传递给函数时,可以通过sizeof得到数组的大小吗?

当把数组作为函数的参数时,你无法在程序运行时通过数组参数本身告诉函数该数组的大小,因为函数的数组参数相当于指向该数组第一个元素的指针。这意味着把数组传递给函数的效率非常高,也意味着程序员必须通过某种机制告诉函数数组参数的大小。为了告诉函数数组参数的大小,人们通常采用以下两种方法:
第一种方法是将数组和表示数组大小的值一起传递给函数,例如memcpy()函数就是这样做的:
    memcpy( dest,source,length );
第二种方法是引入某种规则来结束一个数组,例如在C语言中字符串总是以ASCII字符NUL('/0')结束,而一个指针数组总是以空指针结束。

5.为什么要小心对待位于数组后面的那些元素的地址呢?
     假如你的程序是在理想的计算机上运行,即它的取址范围是从00000000到FFFFFFFF,那么你大可以放心,但是,实际情况往往不会这么简单。
    在有些计算机上,地址是由两部分组成的,第一部分是一个指向某一块内存的起始点的指,针(即基地址),第二部分是相对于这块内存的起始点的地 址偏移量。这种地址结构被称为段地址结构,子程序调用通常就是通过在栈指针上加上一个地址偏移量来实现的。采用段地址结构的最典型的例子是基于Intel 8086的计算机,所有的MS-DOS程序都在这种计算机上运行(在基于Pentium芯片的计算机上,大多数MS-DOS程序也在与8086兼容的模式下运行)。即使是性能优越的具有线性地址空间的RISC芯片,也提供了寄存器变址寻址方式,即用一个寄存器保存指向某一块内存的起始点的指针,用另一个寄存器保存地址偏移量。
    假如你的程序使用段地址结构,而在基地址处刚好存放着数组a0(即基地址指针和&a0[0]相同),这会引出什么问题呢?既然基地址无法(有效地)改变,而偏移量也不可能是负值,因此“位于a0[0]前面的元素”这种说法就没有意义了,ANSIC标准明确规定引用这个元素的行为是没有定义的。
    同样,假如数组a(其元素个数为MAX)刚好存放在某段内存的尾部,那么地址&a[MAX]就是没有意义的,假如你的程序中使用了&a[MAX],而编译程序又要检查&a[MAX]是否有效,那么编译程序必然就会报告没有足够的内存来存放数组a。
    尽管在编写基于Windows,UNIX或Macintosh的程序时不会碰到上述问题,但是C语言不仅仅是为这几种情况设计的,C语言必须适应各种各样的环境,例如用微处理器控制的烤面包炉,防抱死刹车系统,MS-DOS,等等。严格按C语言标准编写的程序能被顺利地编译并能服务于任何目的,但是,有时程序员也可以适度地背离C语言的标准,这要视程序员、编译程序和程序用户三者的具体要求而定。

6.可以把另外一个地址赋给一个数组名吗?
    不可以,尽管在一个很常见的特例中好象可以这样做。
    数组名不能被放在赋值运算符的左边(它不是一个左值,更不是一个可修改的左值)。一个数组是一个对象,而它的数组名就是指向这个对象的第一个元素的指针。    
    假如一个数组是用extern或static说明-的,则它的数组名是在连接时可知的一个常量,你不能修改这样一个数组名的值,就象你不能修改7的值一样。
    给数组名赋值是毫无根据的。一个指针的含义是“这里有一个元素,它的前后可能还有其它元素”,一个数组名的含义是“这里是一个数组中的第一个元素,它的前面没有数组元素,并且只有通过数组下标才能引用它后面的数组元素”。因此,假如需要使用指针,就应该使用指针。

7.字符串和数组的区别
    数组的元素可以是任意一种类型,而字符串是一种非凡的数组,它使用了一种众所周知的确定其长度的规则。
    有两种类型的语言,一种简单地将字符串看作是一个字符数组,另一种将字符串看作是一种非凡的 类型。C属于前一种,但有一点补充,即C字符串是以一个NUL字符结束的。数组的值和数组中第一个元素的地址(或指向该元素的指针)是相同的,因此通常一个C字符串和一个字符指针是等价的。
    一个数组的长度可以是任意的。当数组名用作函数的参数时,函数无法通过数组名本身知道数组的大小,因此必须引入某种规则。对字符串来说,这种规则就是字符串的最后一个字符是ASCII字符“NUL('\0')”。
    在C中,int类型值的字面值可以是42这样的值,字符的字面值可以是“*”这样的值,浮点型值的字面值可以是4.2el这样的单精度值或双精度值。

8.用数组名做函数参数与用数组元素作实参的区别
  a.用数组元素作实参时,只要数组类型和函数的形参变量的类型一致,那么作为下标变量的数组元素的类型也和函数形参变量的类型是一致的。因此,并不要求函数的形参也是下标变量。换句话说,对数组元素的处理是按普通变量对待的。用数组名作函数参数时,则要求形参和相应的实参都必须是类型相同的数组,都必须有明确的数组说明。当形参和实参两者类型不一致时,机会发生错误。
  b.用普通变量或下标变量作函数参数时,形参变量和实参变量都是由编译系统分配的两个不同的内存单元。在函数调用时进行的值传递是把实参变量的值赋予形参变量。在用数组名做函数参数时,不是进行值的传递,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。因为实际上形参数组并不存在,编译系统不为形参数组分配内存。那么,数据的传递是如何实现的?因为数组名就是数组的首地址。因此用数组名做函数参数时所进行的传递实际上是地址的传递,也就是把实参数组的首地址赋予形参数组名。形参数组名取得该首地址后,也就等于有了具体的地址。实际上是形参数组和实参数组为同一数组,共同使用一段内存空间。

9.array_name和&array_name的区别
    前者是指向数组中第一个元素的指针,后者是指向整个数组的指针。
    注意:笔者建议读者读到这里时暂时放下本书,先写一下指向一个含MAX个元素的字符数组的指针变量的说明。提示:使用括号。希望你不要敷衍了事,因为只有这样你才能真正了解C语言表示复杂指针的句法的奥秘。下面将介绍如何获得指向整个数组的指针。
    数组是一种类型,它有三个要素,即基本类型(数组元素的类型),大小(当数组被说明为不完整类型时除外),数组的值(整个数组的值)。你可以用一个指针指向整个数组的值:
    char  a[MAX];    /*arrayOfMAXcharacters*/
    char    *p;      /*pointer to one character*/
    /*pa is declared below*/
    pa=&al
    p=a;             /* =&a[0] */
    在运行了上述这段代码后,你就会发现p和pa的打印结果是一个相同的值,即p和pa指向同一个地址。但是,p和pa指向的对象是不同的。
    以下这种定义并不能获得一个指向整个数组的值的指针。
    char *(ap[MAX]);
    上述定义和以下定义是相同的,它们的含义都是“ap是一个含MAX个字符指针的数组。
    char *ap[MAX];

10.参数的传递

参数传递有传值和传地址两种方式。

传值的过程:
(1)行参与实参各占一个独立的存储空间。
(2)行参的存储空间是函数被调用时才分配的。调用开始,系统为行参开辟一个临时
存储区,然后将各实参之值传递给行参,这时行参就得到了实参的值。
(3)函数返回时,临时存储区也被撤销。
传值的特点:单向传递,即函数中对行参变量的操作不会影响到调用函数中的实参变量。

地址传递方式:参数是地址
void change_by_address(int *x){
    *x=*x+10;
}
实参和行参共享一个存储单元,对行参的操作相应的就改变了实参,此时参数传递是双向的。
此外,参数的存储位置和变量的存储属性对参数传递也有影响,变量的存储属性有动态变量、静态变量
外部变量,动态变量有2种,自动(auto)和寄存器(register)变量。

此外还应注意内存的分配方式,内存分配方式有三种:
(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的
整个运行期间都存在。例如全局变量,static 变量。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函
数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集
中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意
多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存
期由我们决定,使用非常灵活,但问题也最多。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值