数组并非指针
一、什么是声明,什么是定义
只能出现在一个地方。定义它确定对象的类型并分配内存,用于创建新的对象。例如:
int a[100];
可以出现多次。声明它描述了对象的类型,用于指代其他地方定义的对象(例如在其它文件里)。例如:
extern int a[];
记住C语言中的对象(比如函数和变量)必须有且只有一个定义,但它可以有多个 extern 声明。
extern对象的声明告诉编译器对象的类型和名字,对象的内存分配则在别处进行。由于并未在声明中为数组分配内存,所以并不需要提供关于数组长度的信息。对于多维数组,需要提供除左边一维之外其它维的长度。
int a[100];
#include <stdio.h>
#include "a.c"
int main(int argc, char **argv)
extern int *a;
printf("%d\n", a[0]);
return 0;
}
[dendi875@localhost Temp]$ gcc -g b.c -o b
b.c: 在函数‘main’中:
b.c:6: 错误:与‘a’类型冲突
a.c:1: 附注:‘a’的上一个定义在此
#include <stdio.h>
#include "a.c"
int main(int argc, char **argv)
extern int a[];
printf("%d\n", a[0]);
return 0;
}
二、数组并非指针
#include <stdio.h>
int main(int argc, char **argv)
char p[] = "abcd";
printf("%c\n", p[0]);
return 0;
}
[dendi875@localhost Temp]$ gcc -g a.c -o a
[dendi875@localhost Temp]$ ./a
a
#include <stdio.h>
int main(int argc, char **argv)
char *p = "abcd";
printf("%c\n", p[0]);
return 0;
}
[dendi875@localhost Temp]$ gcc -g p.c -o p
[dendi875@localhost Temp]$ ./p
a
上面两个例子分别对数组和指针使用了下标的访问,它们都能正常运行并结果一致。但就是这种情况让我们错误的认为数组和指针完全相同。
对数组下标的引用
char p[] = "abcd"; p[i];
编译器编译时为 p 变量分配了一个地址假设为 7321,上面的代码运行时分为两个步骤:
对指针进行下标的引用
char *p = "abcd"; p[i];
编译器编译时为p变量分配了一个地址假设为 1237,上面的代码运行时分为三个步骤:
三、数组和指针的其它区别
指针 | 数组 |
---|---|
保存数据的地址 | 保存数据 |
间接访问数据,首先取得指针的内容,把它作为地址,然后从这个地址提取数据。
如果指针有一个下标 p[i] ,就把指针的内容加上 i 作为地址,从中提取数据 |
直接访问数据,a[i] 只是简单地以 a + i 为地址取得数据 |
通常用于动态数据结构 | 通常用于存储固定数目且数据类型相同的元素 |
相关的函数 malloc,free等 | 隐式分配和删除 |
通常指向匿名数据 | 自身即为数据名 |
数组和指针都可以在它们的定义中用字符串常量进行初始化。尽管看上去一样,底层机制却不相同。
char *p;
char buff[4096];
定义指针时,编译器并不为指针所指向的对象分配空间,它只是分配指针本身的空间,除非在定义的同时赋给指针一个字符串常量进行初始化。例如,下面的定义创建了一个字符串常量(为其分配了内存)
char *p = "abcd";
float *pi = 3.14; /* 错误,无法通过编译 */
int main(int argc, char **argv)
char *p = "abcd";
p[0] = 'x'; // 运行时会导致段错误 (core dumped)
*p = 'y'; // 同样的运行时会导致段错误 (core dumped)
return 0;
#include <stdio.h>
int main(int argc, char **argv)
char s[] = "abcd";
s[0] = 'x';
printf("s = %s\n", s);