最近在学习C语言。发现源代码里有如下几种定义:
(char *) p[N];
char *p[N];
char (*p)[N];
一开始觉得应该都一样吧,仔细研究了一下,竟然大不相同,而且还是C的难点之一。
下面附上解释:
(char *) p[N]; 把p[N]强制转化成指向char型的指针;
char *p[N]; 一个char指针数组,包含N个指针;
char (*p)[N]; 一个指向char[N]的指针。
char *p和char p[]赋值时的区别
char *s="abc";
char str[]="abccd";
经过反汇编,得到:
char *str="abc";
mov
char str[ ]="abccd"; 的汇编代码
mov
mov
mov
mov
1、char *s="abc";
2、char str[]="abccd";
关于数组形参的真相
(2011-07-21 17:00:43)分类: C语言基础 |
写了几个函数,想通过数组的方式传参,搞了半天终于发现,所谓C/C++中的"数组形参"是根本不存在的,在数组传入函数时,实际上仅仅传入了指向实参数组的首地址的指针。这也难怪我企图使用sizeof求形参所占内存个数的时候,却得到了一个指针的长度。
void average(int arr[12]);// 实际形参arr是一个int*
int anArray[]={1,2,3};//声明了一个有三个元素的数组
const int anArraySize = sizeof(anArray)/sizeof(anArray[0]); // =3
average(anArray);
好吧,业内专业人士对这种数组到指针的自动转换的行为叫做“退化”。即数组退化成指向其首元素地址的指针。同样,当函数作为形参被进行传递时,也会自动退化。不过,和数组退化时丢失边界不同,一个退化的函数同样具有良好的感知能力,可以保持其参数类型和返回类型。
由于数组形参中数组边界被忽略了,因此通常在声明时最好将其省略。
void average(int arr[]); //忽略边界,形参仅仅是一个int*
当然,如果数组边界的精确数值非常重要,并且希望函数只接受含有特定数量元素的数组,可以考虑使用一个引用形参:
void average(int (&arr)[12]);//引用调用,区别于传值调用
这时,函数就只能接受大小为12的整形数组;
average(anArray);//错误!anArray是一个int[3];
在C++中可以使用模板来帮助代码的泛化:
template<int n>
void average(int (&arr)[n]);//让编译器来替我们推导出n的值
但是,更为传统的做法还是将数组的大小作为形参明确的传入函数:
vodi average(int arr[],int size);
当然,我们也可以使用模板:
template<int n>
inline void average(int (&arr)[n])
{
}
从以上讨论中我们可以得知,使用数组作为函数形参的最大问题在于:数组的大小必须以形参的方式显式的编码,并以单独的实参传入,或者在数组内部以一个结束符值作为指示。另一个困难在于,不管数组是如何声明的,一个数组通常是通过指向其首元素地址的指针进行操纵。如果那个指针作为实参传递给函数,我们前面声明引用形参的技巧将无济于事。
int *anArray2 = new int [anArraySize];
average(anArray2);//错误,不能将int *初始化为int (&)[n]
average_n(anArray, anArraySize);//没问题;
出于这些原因,常采用某种标准“容器”(通常是vector或者string)来代替对数组的大多数传统用法,并且经常应该优先考虑使用标准容器。
从本质上来说,对维数组形参并不比一维数组来的困难,但他们看上去更有挑战性。
void process(int arr[10][20]);
和一维数组一样,形参并不是一个数组,而是一个指向数组首元素地址的指针。不过,多维数组是数组的数组,因此形参是一个指向数组的指针。
注意,第二个边界没有退化,否则将无法对形参执行指针算数。但如前所述,让代码的读者清楚的知道你期望的实参是一个数组,这通常是一个好主意:
void process(int arr[][20]); //仍然是一个指针,但更清晰。
void process_2d(int *a, int n, int m)
{
}
template<int n, int m>
inline void process(int (&arr)[n][m])
{
}