第一种指针:堆栈有栈顶指针,队列有头指针和尾指针,这些概念中的“指针”本质是一个整数,是数组的索引,通过指针访问数组中的某个元素。
第二种指针:把一个变量所在的内存单元的地址保存在另外一个内存单元中,保存地址的这个内存单元称为指针,访问变量要通过指针间接寻址,这种指针在C语言中可以用一个指针类型的变量表示。
int i;
int *pi = &i;
char c;
char *pc =&c;
(1)&是取地址运算符,&i表示取变量i的地址;int *pi = &i;表示定义一个指向int型的指针变量pi,并用i的地址来初始化pi。
(2)定义字符型变量c和一个指向c的字符型指针pc。
如果要让pi指向另一个整型变量j,可以重新读pi赋值:
pi =&j;
现在要通过指针pi间接寻址到变量j,把变量j的值增加10,可以写成:
*pi = *pi +10;
[ ]在声明和表达式中有不同的含义,[ ]用在声明中表示一个数组;用在表达式中提取下标运算符。同理,*用在声明中表示一个指针类型,用在表达式中是间接寻址运算符。
*和&互为逆运算。如果表达式E可以做左值,则*&E和E等价;如果表达式E是指针类型,则&*E和E等价。
指针之间相互赋值,用一个指针初始化另一个指针:
int *ptri = pi;
或者
int *ptri;
prti = pi;
上述代码表示pi指向哪就让prti也指向哪,本质上就是把变量pi所保存的地址赋值给变量prti。
强制类型转换。 两个指针必须同一类型,才能用一个指针给另一个指针赋值。不同类型,先强制转换再赋值,如pi是int *型,pc是char *型,不可以pi = pc,应该如下操作
pi = (int *)pc;
**定义一个指针类型的局部变量需要初始化。**错误情况如下:
int main(void)
{
int *p;
...
*p =0;
...
}
解析:栈上分配的变量初始值是不确定的,也就是说指针p所指向的内存地址是不确定的,后面用*p访问不确定的地址会导致不确定的后果,可能引发段错误,也可能以外改写了数据而导致程序在随后的运行中出错。像这种指向不确定地址的指针称为野指针,为避免野指针,在定义指针变量时就应该明确地给它赋值,或把它初始化为NULL:
int main(void)
{
int *p = NULL;
...
*p =0;
...
}
NULL在C标准库的头文件stddef.h中定义:
#define NULL ((void *) 0)
指针也是一种标量类型,可以用()运算符做强制类型转换,其他标量类型也可以转换成指针类型,指针类型也可以转换成其他标量类型,比如在上面的定义中把整形的0强制转成void *指针,这个指针指向0地址,称为空指针。
空指针的特殊之处在于,操作系统不会把任何数据保存在地址0及其附近,也不会把地址0~0xfff的页面映射到物理内存,所以任何对地址0的访问都会立刻导致段错误。*p = 0;会导致段错误,就像放在眼前的炸弹一样很容易找到,相比之下,野指针的错误就像埋下地雷一样,更难发现和排除,这次走过去没事,下次走过去就有事。
**void *类型。**在编程时经常需要一种通用指针,可以转换为任意其它类型的指针,任意其它类型的指针也可以转换为通用指针。ANSI在将C语言标准化时引入了void *类型,void *指针与其它类型的指针之间可以隐式转换,而不必用类型转换运算符。
void func(void *pv)
{
/* *pv = 'A' is illegal */
char *pchar = pv;
*pchar = 'A';
}
int main(void)
{
char c;
func(&c);
printf("%c\n", c);
...
}