1. 数组大小
我相信,在C#/Java中,更多的人愿意用List<T>来取代数组,一方面是List提供了较多的方法,另一方面也无需我们去指定数组的大小。
那么在C语言中,我们既然需要必须指定数组的大小,而一般来讲,很多数组大小事我们无法确定并且经常会发生变化的,那么我们最好的方式就是用宏定义来限定数组的大小。
- #define SIZE 10
- int main (void)
- {
- int a[SIZE];
- }
如果包含多个数组的话,用宏就很难记忆,那么我们就可以利用sizeof运算符。
- int main (void)
- {
- int a[]={1,3,4,55,6,7,89,9,0};
- int i ;
- printf("%d",(int)sizeof(a)/(int)sizeof(a[0]));
- for(i=0;i<(int)sizeof(a)/(int)sizeof(a[0]);i++)
- {
- a[i]=0;
- }
- for(i=0;i<(int)sizeof(a)/(int)sizeof(a[0]);i++)
- {
- printf("%d\n",a[i]);
- }
- }
注意,我们之前说过,sizeof返回的值是size_t,因此,我们在计算时,最好将其先强制类型转换为我们可以控制的类型。
2. 数组初始化
一般情况下,我们初始化数组都是把整数数组初始化为0,那么我们一般会怎么做呢?
- #define SIZE 5
- int main (void)
- {
- int a[SIZE]={0,0,0,0,0};
- }
那么如过SIZE=100怎么办,那么很多人都会这样去做。
- #define SIZE 100
- int main (void)
- {
- int a[SIZE];
- int i ;
- for(i=0;i<SIZE;i++)
- {
- a[i]=0;
- }
- }
其实我们完全不用麻烦,这么一句代码就可以搞定了。
- #define SIZE 100
- int main (void)
- {
- int a[SIZE]={0};
- }
在C99中,提供了一种初始化式,使得我们可以这样来写。
- #define SIZE 100
- int main (void)
- {
- int a[SIZE]={[5]=100,[50]=49};
- }
而其他的数字就都默认为0。那么我们来考虑这样一段代码:
- #define SIZE 10
- int main (void)
- {
- int a[SIZE]={1,2,3,4,5,[0]=6,7,8};
- }
那么在C99中,这段代码的结果究竟是什么呢?这个就需要我们来了解一下数组初始化式的原理。
其实,编译器在初始化式数组列表时,都会记录下一个待初始化的元素的位置,比如说在初始化index=0的元素时,会记录下1,这样以此类推,但是当初始化index=5的时候,首先根据他的初始化式记录下一个待初始化的元素时index=1,然后初始化index=0的元素为6。那么也就是说:最后的结果应该是{6,7,8,4,5,0,0,0,0,0}。
3. 常量数组
当数组加上const就变成了常量数组,常量数组主要有两个好处。
1. 告诉使用者,这个数组是不应该被改变的。
2. 有助于编译器发现错误。
4. C99的变长数组
这是个很爽的东西,我们再也不必担心为数组指定大小而发愁了,指定大了会造成空间的浪费,指定小了又不够用。
在C99中,他的长度会由程序执行时进行计算。
方式如下:
- int main (void)
- {
- int size;
- int a[size];
- scanf("%d",&size);
- }
5. 数组的复制
很多时候,我们需要把一个数组的元素复制到另一个数组上,我们大多数人第一个想到的就是循环复制。
- #define SIZE 10
- int main (void)
- {
- int a[SIZE];
- int b[SIZE];
- int i ;
- for(i=0;i<SIZE;i++)
- {
- a[i]=i;
- }
- for(i=0;i<SIZE;i++)
- {
- b[i]=a[i];
- }
- for(i=0;i<SIZE;i++)
- {
- printf("%d",b[i]);
- }
- }
其实还有一种更好的方法是使用memcpy方法,这是一个底层函数,它把内存的字节从一个地方复制到另一个地方,效率更高。
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #define SIZE 10
- int main (void)
- {
- int a[SIZE];
- int b[SIZE];
- int i ;
- for(i=0;i<SIZE;i++)
- {
- a[i]=i;
- }
- memcpy(b,a,sizeof(a));
- for(i=0;i<SIZE;i++)
- {
- printf("%d",b[i]);
- }
- }
5. 数组作为函数参数
函数是我们学习程序设计语言最基本的东西了,我在此不再赘述。只讨论一种特殊情况,就是数组作为函数的参数传递。
我们都知道,其实在传递数组的时候,实际上是传递了数组首元素的指针。明确了这一点之后,我们就可以思考下面的问题。
既然他只是传递了数组首元素的指针,那么他必然无法知道整个数组的大小,因此,我们如果希望在函数中用到数组的长度,必须要进行显式传递。
- int Sum(int a[],int size)
- {
- int i ,sum=0;
- for(i=0;i<size;i++)
- {
- sum+=a[i];
- }
- return sum;
- }
那么既然,函数无法检测传入数组的长度,我们也可以利用这一个特性来计算数组前N个数的和,或者是利用这一特性来告诉函数,实际上,数组的有效长度要小于数组的真实长度。
6. C99中变长数组作为函数参数
首先在数组一节中,我们谈到了C99中的变长数组是个很好的东西。那么我们来看看变长数组作为函数参数的情况。
我们看之前的代码,size和a[]并没有直接的联系,那么当变长数组作为参数就会解决这样的情况。
- int Sum(int size,int a[size])
- {
- int i ,sum=0;
- for(i=0;i<size;i++)
- {
- sum+=a[i];
- }
- return sum;
- }
这个代码,则明确地表示了数组a的长度是size,也就是说在size和a[]之间建立起了直接的联系。
但是在这里我们需要注意一点,就是参数的顺序,长度一定要写在数组之前,否则会出现a[size]找不到size的错误。
在进行函数声明时,我们可以有以下几种方式:
- int Sum(int ,int a[*]);
- int Sum(int n ,int a[n]);
- int Sum(int n, int a[*]);
- int Sum(int ,int a[]);
- int Sum(int n ,int a[]);
个人比较推荐第一种,因为我觉得第一种最为简便,而且可以表明a是一个变长数组。像第四种和第五种,我个人认为是两种很不好的方式。
7. C99中数组参数声明使用static
C99中允许在数组参数声明中使用关键字static。例如:
- int Sum(int a[static 10],int n)
- {
- }
从函数本身来讲,static并没有对函数的本身实现造成任何影响。static 10的含义是数组的长度至少是10。那么当函数调用时,编译器会事先从内存中取出10个数,而不是在函数调用的时候才一次次的去取,这样就可以使函数的效率更高。
8. main函数的返回值
在初学C语言的时候,谭老的书上大部分都是这样的代码:
- void main ()
- {
- printf("Hello world");
- }
但是实际上,这段函数有两个缺陷:
A. 从编程风格上来看,最好显式地声明main函数没有参数
B. main函数应该返回状态码,在某些操作系统中,程序终止时可以检测到状态码,来监视程序是否正常结束。即使你不需要这个状态码,其他人也可能需要。
因此,这个函数最好这样来实现:
- int main (void)
- {
- printf("Hello world");
- return 0;
- }
还记得我们之前说过exit(0)么,我们之前说,在main函数中写return 0和exit(0)是没有区别的。那么我们就来看看return 和 exit的区别。
exit属于<stdlib.h>头文件,我们之前说过,0是状态码中成功的意思,那么为了更直观,C标准库为我们提供了这样的两个宏定义。
- int main (void)
- {
- printf("Hello world");
- exit(EXIT_SUCCESS); //成é功|
- exit(EXIT_FAILURE); //失§败ü
- }
让我们转向定义可以发现:
- /* Definition of the argument values for the exit() function */
- #define EXIT_SUCCESS 0
- #define EXIT_FAILURE 1
在<stdlib.h>中的这两个宏定义。但是这两个值并不是固定的,而是由实现定义的。
另外,return 和 exit的一个最典型差异就是,在其他函数中调用return 不会引起程序的终止,但是无论在哪里调用exit都会引起程序终止,我们看一个程序。
- int main (void)
- {
- printf("Begin\n");
- BreakTest();
- printf("End\n");
- }
- int BreakTest()
- {
- return 0;
- }
这段代码不应该产生任何疑问:
接下来看下这段代码:
- int main (void)
- {
- printf("Begin\n");
- BreakTest();
- printf("End\n");
- }
- int BreakTest()
- {
- exit(EXIT_SUCCESS);
- }
由此可知,exit使整个的程序都被终止了。