函数,数组和指针
接着上一次的总结,现在写一个函数,该函数的功能是返回数组中所有元素的和。调用函数应该是什么样子?
可能是这样的:
total=sum(marbles);
那么该函数的原型是什么?数组名是该数组首元素的地址,所以实际参数marbles是一个存储int类型的地址,应该把它赋予一个指针形式的参数。该形参是一个指向int类型的指针。如下:
int sum(int *ar);//对应的函数原型
sum()获得了该数组首元素的地址。但是,该参数并未包含数组元素个数的信息,我们有两种方式让函数获得这一信息。
第一,在函数代码中直接写上固定数组的大小。
int sum(int *ar)
{
int i;
int total=0;
for(i=0;i<10;i++) //假设数组有10个元素
total+=ar[i]; //ar[i]与*(ar+i)
return total;
}
该函数上面的定义,限制了智能计算10个int类型的元素。
第二种方法:把数组大小作为第二个参数。这是非常常用的方法,也非常常见。
int sum(int *ar,int n) //更常用的方法
{
int i;
int total;
for (i=0;i<n;i++)
total+=ar[i];
return total;
}
上述写法,第一个参数告诉函数该数组的地址和数据类型,第二个形参告诉函数该数组中元素的个数
关于函数的形参,还有一点要关注。只有在函数原型或函数定义开头中,才可以用int ar[ ]替代int *ar;这两种形式都表示ar是一个指向int类型的指针但是,int ar[ ]只能用于声明形式参数。第二种形式(int ar[ ])表示了指针ar指向的不仅仅是一个int类型的值,还是一个int类型数组的元素。
关于声明数组形参:
因为数组名是该元素的地址,作为实际参数的数组名要求形式参数是一个与之匹配的指针。只有在这种情况下,int ar[ ]和inr *ar才能解释成一样的。
所以以下4种原型是等价的:
int sum(int *ar,int n);
int sum(int *,int );
int sum(int ar[ ],int n);
int sum(int [ ],int );
指针形参
函数要处理数组必须要知道开始和结尾。sum()函数使用一个指针形参标记数组的开始。用一个整数形参表明待处理数组的元素个数。除此之外,还有一种方法:传递两个指针。
一个指针指明开始。一个指明结束处。例如以下实现:
#include<stdio.h>
#define SIZE 10
int sump(int *start,int *end);
int main(void)
{
int marbles[SIZE]={10,20,5,39,4,16,11,19,26,30,15};
long answer;
answer=sump(marbles,marbles+SIZE);
printf("%ld\n",answer);
return 0;
}
int sump(int *start,int *end)
{
int total=0;
while(start < end)
{
total+=*start;
start++;
}
return total;
}
赋值表达式: total+=*start 把首元素10加给total。然后表达式start++递增指针变量start,使其指向数组的下一元素。start位指向int的指针,递增1相当于其值递增int类型的大小。
另外,循环体可以进一步简写为:
total+=*start++
这里就又涉及到指针运算中的优先级
一元运算符星号和++优先级相同,但结合律是从右至左,所以start++先求值。然后才是星号start。即为:指针先递增后指向。也就是先把指针指向的值加到total上,再递增指针。若反过来,就为先递增指针,再使用指针指向的值。
如果是(*start)++则是先使用start指向的值,在递增该值,而不是递增指针。指针将一直指向同一个位置,但是该位置上的值发生了变化。
再附上一个栗子,帮助更好的理解运算优先级。
#include<stdio.h>
int data[2]={100,200};
int moredata[2]={300,400};
int main(void)
{
int *p1,*p2,*p3;
p1=p1=data;
p3=moredata;
printf("*p1=%d,*p2=%d,*p3=%d\n",*p1,*p2,*p3);
printf("*p1++=%d,*++p2=%d,(*p3)++=%d\n",*p1++,*++p2,(*p3)++);
printf("*p1=%d,*p2=%d,*p3=%d\n",*p1,*p2,*p3);
}
运行结果:
*p1=100, *p2=100 ,*p3=300
*p1++=100, *++p2=100, (*p3)++=300
*p1=200, *p2=200, *p3=301
以上除了*p3改变了数组元素的值,再首地址上加1,其余均为指针移动。
指针的基本操作
以下为整理的指针变量的8个基本操作:
- 赋值
可以把地址赋给指针,例如,用数组名,带地址运算符,(&)的变量名,另一个指针赋值。但地址类型也要和指针兼容,至少避免不明智的类型转换。 - 解引用
*运算符给出指针指向地址上存储的值。 - 取地址
指针变量也有自己的地址和值,对指针而言,&运算符给出指针本身的地址。 - 指针与整数相加
可以使用+运算符把指针与整数相加/整数与指针相加(先后顺序不同),无论是前者还是后者,整数都会和指针所指向类型的大小(以字节为单位)相乘,然后把结果与初地址相加。若相加结果超出了初始指针指向的数组范围,计算结果未定义。 - 递增/递减指针
递增指向数组元素的指针可以让该指针移动至数组的下一个元素。递减同理。 - 指针减去一个整数
可以用-运算符,从一个指针中减去一个整数。指针必须是第一个运算对象,证书是第二个运算对象。该整数将乘以指针指向类型的大小,然后用初始地址减去乘积。 - 指针求差
计算两个指针的差值,通常求差的两个指针分别指向同意数组的不同元素。通过计算求出两元素之间的距离。差值单位与数组类型的单位相同。 - 比较两个指针
使用关系运算符比较两个指针的值,前提是两个指针指向相同类型的对象。
最后在强调一点,不要使用未初始化的指针!!
错误示例:
int *pt;//未初始化
*pt=5; //严重错误
上面的是很严重的错误。由于创建一个指针时,系统之分配了储存指针本身的内存,并未分配储存数据的内存,所以在使用前,必须用已分配的地址初始化它。
这两种方法可解决未初始化的问题:
1.可用一个现有变量的地址初始化指针。
2.或者使用malloc()函数分配内存。
题外话:本想着一次性把和指针有关的东西一次写完,又觉得写在一个博客里把有的重点堆在一起,日后复习看起来感觉很乱。就单独把指针变量的基本操作与指针形参单独罗列出来记录。重拾C并且进一步学习C是为了给接下来自学数据结构与算法打一个厚实的基础,不得不说C primer plus真的是很好的一本书,知识真的很详细,不过有的还是没有讲到,不过都等着我发现了,进一步挖掘最底层的东西。