指针pointer
数据类型决定了变量可以表示的数值范围。除此之外,表示变量在内存中生命期范围存储期以及变量名也都是变量的重要属性。
取址运算符-address operator
单目运算符`&` `&a` 取得`a`的地址(生成指向a的指针)
取址运算符&
的功能是取得对象的地址。
对于使用
register
关键字声明的寄存器对象,不能加上取址运算符&
.
指针
int foo = 178;
int *pf = &foo;
printf("pointer value == %d\n", *pf);
printf("foo value == %d\n", foo);
//pointer value == 178
//foo value == 178
上例中,变量名前加*
,通过该声明定义了指向int
型变量的指针变量,他们指向的是int
型变量。
以上的两种形式可以理解为:
int
型变量: 保存整数的盒子指向
int
型变量的指针变量:保存存放整数对象的地址的盒子。
指针运算符
单目运算符 * *p p指向的对象
根据上例,可以得出:
*pf
就是foo
,*pf
是foo
的别名(alias)
解引用
通过在指针前写上指针运算符*
来访问该指针指向的对象,称为解引用(dereference)
例: *pf
指针的类型
指向Type
型对象的指针,即Type*
型指针。
并不是表示指向OO号,更确切地说是指向以OO号为首地址的
Type
型对象
空指针 - null pointer
什么也不指向的特殊指针是空指针(null pointer),表示空指针的对象式宏NULL
是空指针的常量。
指针和数组
数组名原则上会被解释为指向该数组起始元素的指针。
指针
p
指向数组中的元素e
时,
p + i
为指向元素e
后第i
个元素的指针
p - i
为指向元素e
前第i
个元素的指针
void array_pointer(void)
{
int a[5] = {1, 2, 3, 4, 5};
int *p = a;
for (int i = 0; i < 5; i++) {
printf("&a[%d] = %p p+%d = %p\n", i, &a[i], i, p+i);
}
}
/*
&a[0] = 0x7ffeefbff4f0 p+0 = 0x7ffeefbff4f0
&a[1] = 0x7ffeefbff4f4 p+1 = 0x7ffeefbff4f4
&a[2] = 0x7ffeefbff4f8 p+2 = 0x7ffeefbff4f8
&a[3] = 0x7ffeefbff4fc p+3 = 0x7ffeefbff4fc
&a[4] = 0x7ffeefbff500 p+4 = 0x7ffeefbff500
*/
数组名在什么情况下不被视为指向起始元素的指针
- 作为
sizeof
运算符的操作数出现时
sizeof(array)
不会生成指向起始元素的指针的长度,而是生成数组整体的长度
- 作为取址运算符
&
的操作数出现时
&
数组名不是指向起始元素的指针的指针,而是指向数组整体的指针。
指针运算符和下标运算符
当指针
p
指向数组中的元素e
时,
指向e
后第i
个元素的*(p + i)
,可以写为p[i]
;
指向e
前第i
个元素的*(p - i)
,可以写为p[-i]
Type*
型指针p
指向Type
型数组a
的起始元素a[0]
时,指针p
的行为就和数组a
本身一样。
数组的下标表示位于起始元素后的第几个元素的位置,因此必须从0开始。
虽然可以为指针加上整数,但是指针之间相加是不可以的
下标运算符的操作数
* (p + i)
括号内的p + i
, 是p
和i
的加法运算。和算术类型的数值间的加法运算a + b
等同于b + a
一样, p + i
也等同于i + p
。
也就是说, * (p + i)
和 * (i + p)
是等价的。
这样一来,是不是访问数组元素的表达式p[i]
也可以写成i[p]
呢。实际上确实是可以的。
下标运算符[]
,是具有两个操作数的双目运算符。
其中一个操作数的类型是:
- 指向
Type
型对象的指针
另一个操作数的类型是:
- 整数类数据类型
下标运算符[]
的操作数的顺序是随意的。就像a + b
等同于b + a
一样, a[3]
和3[a]
也是一样的。
下标运算符[]
所生成的值的类型是
Type
型
指针p
指向数组a
的起始元素a[0]
时,
a[i] *(a + i) p[i] *(p + i)
这4个表达式表示的都是相同的元素。实际上
a[i] i[a] *(a + i) *(i + a) p[i] i[p] *(p + i) *(i + p)
这8个表达式表示的都是相同的元素。
数组和指针的不同点
int *p;
int y[5];
p = y; //OK
int a[5];
int b[5];
a = b; //错误
赋值表达式的左操作数不可以是数组名。
数组的传递
在函数间传递数组时,可以灵活应用指针和数组的相似性。
void arr_set(int v[], ...)
{
/*----do sth------*/
}
void arr_set(int v[5], ...)
{
/*-----do sth-----*/
}
void arr_set(int *v, ...)
{
/*-----do sth-----*/
}
以上三个函数, 第一个和第二个都可以解释为第三个。 形参V
的类型不是数组, 而是指针。即使像第二个函数那样指定元素个数,该值也会被无视。
调用上述arr_set
函数时,int*
型的形参V
将使用实参a
,即&a[0]
进行初始化。
函数间数组的传递,是以指向第一个元素的指针的形式进行的。在被调用的函数中作为指针接收的数组,实际上就是调用方传递的数组。