- 基本概念
先看以下例子:
int a[10];
int *pa = &a[10];
pa ;
首先指针pa指向a[0]的地址,注意后缀运算符的优先级高于单目运算符,所以是取a[0]的地址,而不是取a的地址。然后pa 让pa指向下一个元素(也就是a[1]), 由于pa 是int* 指针,一个int 型元素占4个字节,所以pa 使pa 所指向的地址加4,注意不是加1。
用方框表示存储空间,用箭头表示指针和变量之间的关系:
既然指针可以用 运算符,当然也可以用 , - 运算符,pa指向a[1],那么pa 2指向a[3]。
*(pa 2)也可以写成pa[2],pa就像数组名一样,a[2]之所以能取数组的第二个元素,是因为它等于*(a 2),当数组名做右值时自动转换成指向首元素的指针,所以a[2]和pa[2]本质上是一样的,都是通过指针间接寻址访问元素。
由于a做右值使用时和&a[0] 是一个意思,所以 int *pa = &a[0]; 通常不这么写,
而是写成更简洁的形式: int *pa = a;
在函数原型中,如果参数是数组,则等价于参数是指针的形式,例如:
void func(int a[10])
{
...
}
第一种形式的方括号中的数字可以不写,仍然是等价的:
void func(int a[])
{
...
}
参数写成指针形式还是数组形式对编译器来说没有区别,都表示这个参数是指针,之所以规定两种形式是为了给代码的人提供有用的信息,如果这个参数指向一个元素,通常写成指针的形式,如果这个参数指向一串元素中的首元素,则通常写成数组的形式。
- 指针与自增运算符之间的关系
(*p) , 先传值,后值自增1,类比a
*p == *(p ), 先传值,后地址自增1
*p == (*p), 值先自增1,后传值,类比a
* p == *( p), 地址先自增1, 后传值
#include <stdio.h>
int main()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *pa = &a[0];
printf("*pa = %d\n", *pa);
pa ;
printf("*pa = %d\n", *pa);
printf("*pa = %d\n", *( pa));
return 0;
}
- 指针与const限定符
const限定符和指针结合起来常见的情况有以下几种:
const int *a;
int const *a;
这两种写法是一样的,a是一个指向const int 型的指针,a 所指向的内存单元不可改写,所以(*a) 是不允许的,但 a 可以改写,所以a 是允许的。
int * const a;
a 是一个指向int 型const 指针,*a 是可以改写的,但a不允许改写。
int const * const a;
a 是一个指向const int 型的const 指针,因此*a 和 a 都不允许改写。
指向非const 变量的指针或者非const变量的地址可以传给指向const变量的指针,编译器可以做隐式类型转换,例如:
char c = 'a';
const char *pc = &c;
但是,指向const变量的指针或者const变量的地址不可以传给指向非const变量的指针,以免透过后者意外改写了前者所指向的内存单元,下面的代码编译器在编译时会报警告:
const char c = 'a';
char *pc = &c;
良好的编程习惯应该尽可能多地使用const, 有以下几个优点:
1. const 给代码的人传达非常有用的信息。比如一个函数的参数是 const char *, 或者是const int *,
在调用这个函数时就可以放心地传给它char * 或 const char * 指针,
而不必担心指针所指的内存单元被改写。
2. 尽可能多地使用const 限定符,把不该变的都声明为只读,这样可以依靠编译器检查程序中的Bug,
防止意外改写数据。
3. const 对编译器优化是一个有用的提示,编译器也许会把const 变量优化成常量。
字符串字面值通常分配在.rodata段,字符串字面值类似于数组名,当字面值作为右值使用时自动转换成指向首元素的指针,这种指针应该是co nst char * 型。printf 函数原型的第一个参数是const char *型,可以把char *或const char *指针传给它,参考下面的调用:
const char *p = "abcd";
const char str1[5] = "abcd";
char str2[5] = "abcd";
printf(p);
printf(str1);
printf(str2);
printf("abcd");
如果要定义一个指针指向字符串字面值,这个指针应该是const char * 型,如果写成char *p = "abcd"; 会有被串改的隐患:
int main(void)
{
char *p = "abcd";
...
*p = 'A';
...
}
p 指向 .rodata 段,不允许改写,但编译器不会保存,在运行时出现段错误。