5. 指针与数组
C语言的灵魂
基础
指针是能够存放一个地址的一组存储单元,通常是2个字节或者4个字节取地址,一元运算符&,只能作用于内存中的对象(变量,数组元素),不能作用于表达式,常量,register类型的变量间接寻址/引用,一元运算符,*指针变量也是变量,不是只能通过间接引用来使用,例如 ip = iq
int x =
1, y =
2, z[
10];
int *ip;
ip = &x;
y = *ip;
*ip =
0;
ip =&z[
0];
指针的运算
相同类型之间指针的赋值指针同整数之间的加减法指向相同数组的两个指针的减法或者比较指针赋值为0,或者与0的比较,0通常定义为NULL其他指针运算全部都是非法的
指针和数组
指针变量可以指向数组元素指针的增减反映到指向数组的不同位置的元素间接引用可以使用数组元素数组名等于数组第一个元素的地址,数组名上可以进行间接寻址指针和数组名的区别在于数组名不是变量,不能像变量一样操作它字符指针
字符串常量 “I am a string”
是一个数组可以作为函数参数,printf可以作为右值
char amessage[] =
"now is the time";
char *pmessage =
"now is the time";
这个区别就大了,amessage是可以修改内容的,pmessage指向的常量,是不能改变的。
字符串数组和字符指针的区别
数组和指针的区别,数组名不能变,指针可以变数组通过下标的变化遍历,指针自增自减来移动
void strcpy(
char s[],
char t[])
{
int i =
0;
while(s[i] = t[i] !=
'\0')
i++;
}
void strcpy(
char *s,
char *t)
{
while((*s++ = *t++) !=
'\0')
;
}
多维数组
由低一维的元素作为成员组成的数组作为形参的时候,最高维数量可以留空
f(int daytab[
2][
13]) {
...} /* 正确 */
f(int daytab[][
13]) {
...} /* 正确 */
f(int (*daytab)[
13]) {
...} /* 正确, 解读方式:daytab is a pointer to array
13 of int, 本篇最后会介绍复杂声明的解读 */
指针数组
char *month_name(
int n)
{
static char *name[] =
{
"illegal month",
"January",
"Februrary",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
};
return (n <
1 || n >
12)? name[
0] : name[n];
}
name是一个数组,元素是指向char的指针name数组大小是12, 成员分别指向12个月名字的字符串常量
int a[10][20];
int *
b[10];
上面这个例子,a是一个10 * 20 的二维数组b是一个10个成员的指针数组,每个成员都是一个指向int类型的指针b中的指针,可以指向一个int型变量b中的指针,可以不指向任何变量b中的指针,可以指向一个int数组
/* 支持C语言的环境中,一般main带有
2个参数
*/
/* 这是echo命令的一个实现
*/
int main(
int argc, char
*argv[])
{
while(--argc >
0)
printf(
"%s%s",
*++argv, (argc >
1)?
" " :
"");
printf(
"\n");
}
argc 用于参数计数argv 用于参数向量,是一个char指针数组
argv[0] 是启动改程序的程序名,所以argc至少是1,如果argc是1,说明没有参数例如 echo hello, world
argv[0] = “echo”argv[1] = “hello,”argv[2] = “world”argv[3] = 0argv为什么能够进行自增计算,参数中argv不是一个指针数组么,虽然成员是指针,但是argv是数组啊,以下是对这个问题的解答
数组作为参数与指针的等价的
以下几对形参的写法,是等价的
f(char s[])
f(char *s)
f(&a[2])
f(a + 2)
f(char *argv[])
f(char **argv)
也就是说,即使形参给出的是一个数组,在函数运行的时候,还是会把它先转成一个对应类型的指针这样做的原因基于以下几个方面
参数传递是值传递,其实也就是这个值的一个副本传递进入函数,如果参数是数组,是不是要为数组建立一个副本呢?数组很大呢?所以实际传递给函数的是一个指针的副本,根据数组的类型,指向其中的第一个元素如上面给出的第二对例子,如果实参不是完整的数组,而是从某个成员开始,之后的数组,也是可以的,这无法通过数组名来实现,这是一个没有声明过的子数组数组名和指针在很多情况下可以混用(关于他们的区别用法,需要单独开一篇来介绍)因此编译器会把参数中的数组,改换成指针,在函数内部以指针变量的类型来处理,自增自减都是可以的
函数指针
函数本身不是变量,函数名不能当变量名可以定义指向函数的指针,这个指针可以
被赋值存放在数组中传递给函数作为函数的返回值
int (*comp)(void *, void *);
...
if ((*comp)(v[i], v[left]) <
0)
函数指针必须用括号包起来,使用的时候用间接寻址操作符*声明的时候不带括号,那么就不是函数指针,而是函数名了
int (*comp)(
void *,
void *);
int *comp(
void *,
void *);
复杂声明
复杂声明解释的一个方法是将代码按照一定的规则翻译成文字,这个规则应该掌握,通过一系列例子说明基本规则是从待分析的名称开始向外侧翻译
遇到*翻译成pointer遇到基本类型,翻译成to/of xxx右侧遇到[] 翻译成 array of 难以言表,自己体会领悟吧
char **argv
argv: pointer
to pointer
to char
int (*daytab)[
13]
daytab: pointer
to array
13 of int
int *daytab[
13]
daytab: array
13 of pointer
to int
void *comp()
comp:
function returning pointer
to void
void (*comp)()
comp: pointer
to function returning pointer
to void
char (*(*x())[])()
x:
function returning pointer
to array
of pointer
to function returning
char
char (*(*x[
3])())[
5]
x: array
3 of pointer
to function returning pointer
to array
5 of char