数组名可以当做常量指针使用,那么指针是否也可以当做数组名来使用呢?
数组的访问方式:
以下标的形式访问数组中的元素;
以指针的形式访问数组中的元素;
指针以固定增量在数组中移动时,效率高于下标形式,指针增量为1且硬件具有增量模型时,效率更高。
下标形式与指针形式的转换:a[n] <---> *(a+n) <---> *(n+a) <---> n[a]
#include <stdio.h>
int main()
{
int a[5] = {0};
int* p = a; //指针指向了一个合法的数组
int i = 0;
for(i=0; i<5; i++)
{
p[i] = i + 1;
}
for(i=0; i<5; i++)
{
printf("a[%d] = %d\n", i, *(a + i));
}
printf("\n");
for(i=0; i<5; i++)
{
i[a] = i + 10;
}
for(i=0; i<5; i++)
{
printf("p[%d] = %d\n", i, p[i]);
}
return 0;
}
注意:
现代编译期的生成代码优化率已经大大提高,在固定增量时,下标形式的效率已经和指针形式的效率相当,但从可读性和代码维护的角度来看,下标的形式更优。
数组和指针的不同之处:
在ext.c中:
int a[] = {1, 2, 3, 4, 5}; //定义数组
在test.c中:
#include <stdio.h>
int main()
{
extern int* a; //声明指针
printf("&a = %p\n", &a); //a定义时的地址值
printf("a = %p\n", a); //在这个地址值上取第一个数作为地址
printf("*a = %d\n", *a); //ox1,这个内存地址是预留给操作系统的,所以无法访问
return 0;
}
编译为报错,运行:
./a.out
&a = 0x804a014
a = 0x1
段错误
但如果这样声明的时候:
#include <stdio.h>
int main()
{
extern int a[];
printf("&a = %p\n", &a);
printf("a = %p\n", a);
printf("*a = %d\n", *a);
return 0;
}
即外部声明和定义是一致的。编译运行的结果:
&a = 0x804a014
a = 0x804a014
*a = 1
为什么会出现这种情况呢?数组名是不是指针呢?
首先必须明确,数组名并不是指针。
在外部.c文件中,定义了一个包含五个int类型的数组,所以编译器会在内存中分配20个字节的空间,其地址如上所示。换句话说,标识符a在编译后得到一个新的意义,代表了一个地址:0x804a014,接下来,当编译text.c时,发现声明了的extern int* a,编译器在此时将默认为a为一个外部定义的指针,代表一个地址。
所以第一行打印就打印了a的地址值,第二行打印呢?a的值是什么呢?a是一个指针变量,变量有四个字节,所以编译器会在a的地址处读取四个字节的地址,因为此时a被认为是一个指针,所以此时读取的是在这个地址(0x804a014)上的0001这个数,即0x1。但是0x1这个地址是预留给操作系统的,但凡用户态的程序要访问这个地址(第三行打印),就会出现段错误。
a与&a的区别:
a为数组首元素的地址
&a为整个数组的地址
a和&a的区别在于指针运算:
#include <stdio.h>
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int* p1 = (int*)(&a + 1);
int* p2 = (int*)((int)a + 1);
int* p3 = (int*)(a + 1);
printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]);
return 0;
}
上述代码编译运行输出什么呢?
// A. 数组下标不能是负数,程序无法运行
// B. p1[-1]将输出随机数,p2[0]输出2, p3[1]输出3
// C. p1[-1]将输出乱码, p2[0]和p3[1]输出2
值得注意的是p2,在对a进行运算时进行了强制类型转换,这样进行运算时,就不是地址值了。
将代码修改如下,更方便理解:
#include <stdio.h>
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int* p1 = (int*)(&a + 1);
int* p2 = (int*)((int)a + 1);
printf("a = %p\n", a);
printf("*(int*)((int)a) = %d\n", *(int*)((int)a));
printf("*(int*)((int)a + 1) = %d\n", *(int*)((int)a + 1));
int* p3 = (int*)(a + 1);
printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]);
return 0;
}
编译运行:
~/will$ ./a.out
a = 0xbffe3780
*(int*)((int)a) = 1
*(int*)((int)a + 1) = 33554432
5, 33554432, 3
p2 = 0xbffe3780 + 1 = 0xbffe3781; p2已经不是指针运算了,即在原地址处加1,
p2[0] = *p2 ; 即在0xbffe3781处的数据:
0002
1000 2000 3000 4000 5000//小端系统
*p2 ---> 0x0200 0000
0x02000000十六进制转化为十进制为33554432
数组参数:
void f(int a[]); <---> void f(int* a);
void f(int a[5]); <---> void f(int* a);
一般情况下,当定义的函数中有数组参数时,需要定义另一个参数来标识数组的大小。
数组参数会退化为指针
#include <stdio.h>
void func1(char a[5])
{
printf("In func1: sizeof(a) = %d\n", sizeof(a));
*a = 'a';
a = NULL; //此处的a是指针,不表示数组
}
void func2(char b[])
{
printf("In func2: sizeof(b) = %d\n", sizeof(b));
*b = 'b';
b = NULL;
}
int main()
{
char array[10] = {0};
func1(array);
printf("array[0] = %c\n", array[0]);
func2(array);
printf("array[0] = %c\n", array[0]);
return 0;
}
编译运行:
~/will$ gcc test.c
~/will$ ./a.out
In func1: sizeof(a) = 4
array[0] = a
In func2: sizeof(b) = 4
array[0] = b
数组参数并不是数组了,而是一个指针。
小结:
数组名和指针仅使用方式相同
数组名的本质不是指针
指针的本质不是数组
数组名并不是数组的地址,而是数组元素的地址(体现在指针运算)
函数的数组参数退化为指针。