指针是 C 语言的一个重要特色。它们提供一种统一方式,能够远程访问数据结构。
指针基本的概念其实非常简单,下面的代码说明了许多这样的概念:
struct str { /* Example Structure */
int t;
char v;
};
union uni { /* Example Union */
int t;
char v;
} u;
int g = 15;
void fun(int *xp)
{
void (*f)(int*) = fun; /* f is a function pointer */
/* Allocate structure on stack */
struct str s= {1, 'a'}; /* Initialize structure */
/* Allocate union from heap */
union uni *up = (union uni *) malloc(sizeof(union uni));
/* Locally declared array */
int *ip[2] = {xp, &g};
up->v = s.v + 1;
printf("ip = %p, *ip = %p, **ip = %d\n",
ip, *ip, **ip);
printf("ip+1 = %p, ip[1] = %p, *ip[1] = %d\n",
ip+1, ip[1], *ip[1]);
printf("&s.v = %p, s.v = '%c'\n", &s.v, s.v);
prinf("&up->v = %p, up->v = '%c'\n", &up->v, up->v);
printf("f = %p\n", f);
if (--(*xp) > 0)
f(xp); /* Recursive call of fun */
}
int test()
{
int x = 2;
fun(&x);
return x;
}
-
每个指针都有一个类型。这个类型表明指针指向的对象是哪一类的。示例代码中,看到如下的一些指针类型:

注意,该表中,既指明了指针本身的类型,也指出了它所指向的对象的类型。通常,如果对象类型为 T T T,则指针的类型为 T ∗ T* T∗。特殊的void *类型代表通用指针。例如,malloc函数返回一个通用指针,然后它再被强制类型转换成一个有类型的指针(第21行)。 -
每个指针都有一个值。这个值是某个指定类型的对象的地址。特殊的 NULL(0) 值表示该指针没有指向任何地方。
-
指针是用
&运算符创建的。这个运算符可应用到任何lvalue类的 C 表达式上,即可以出现在赋值语句左边的表达式,这样的例子包括变量以及结构、联合和数组的元素。示例代码中,看到这个操作符应用到全局变量g上(第24行),应用到结构元素s.v上(第32行),应用到联合元素up->v上(第33行),以及应用到局部变量x上(第42行)。 -
*操作符用于指针的间接引用。其结果是一个值,它的类型与该指针的类型相关。看到间接引用应用到ip和*ip上(第 29 行),应用到ip[1]上(第31行),以及应用到xp上(第 35 行)。此外,表达式up->v(第 33 行)既间接引用了指针up,同时还选取了域v。 -
数组与指针是紧密联系的。可以引用一个数组的名字(但是不能修改),就好像它是一个指针变量一样。数组引用(如
a[3])与指针运算和间接引用(如*(a+3)) 有一样的效果。可以在 29 行看到这一点,打印出数组ip的指针值,并用*ip引用它的第一项(元素0)。 -
指针也可以指向函数。这提供了一个很强大的存储(storing)和传递代码引用的功能,这些代码可以被程序的某个其他部分调用。看看变量
f(第 15 行),它被声明为一个指向函数的变量,该函数以一个int *作为参数,并返回void。赋值语句使f指向fun。在后面使用f(第 36 行)时,是在进行递归调用。
函数指针
函数指针声明的语法对于程序员新手是难以理解的。对于这样一个声明:void (*f)(int *);要从里(从 “
f” 开始)往外读。因此,看到像 “(*f)” 表明的那样,f是一个指针。像 “(*f)(int *)” 表明的那样,它是一个指针,指向一个以一个int *作为参数的函数。最后,它是一个指向一个以int *作为参数并返回void的函数的指针。
*f两边的括号是必须的,因为否则声明void *f(int *);就要读成
(void *) f(int *);也就是,它会被解释成一个函数原型,声明了一个函数
f,它以一个int *作为参数并返回一个void *。
代码中包含很多对 prinf 的调用,打印出一些指针(用指令 %p)和值。在执行时,产生下面这样的输出:

可以看到,这个函数执行了两次——第一次是从 test 中直接调用(第 42 行),而第二次是间接的递归调用(第 36 行)。可以看出,打印出来的指针值都对应于地址。那些从 0xbfffef 开始的指针指向栈中的位置,而其他的是全局存储的一部分(0x804965c),或是可执行代码的一部分(0x8048414) ,或是堆中的位置(0x8049760 和 0x8049770)。
数组ip 被初始化了两次——每次调用 fun 都初始化一次。第二次的值(0xbfffef68)小于第一次的值(0xbfffefa8),这是因为栈是向下增长的。不过,数组的内容两次都是一样的。数组元素 0(*ip) 是一个指向 test 栈帧中变量
x
x
x 的指针,元素 1 是一个指向全局变量
g
g
g 的指针。
可以看到,结构
s
s
s 也被初始化了两次,两次都是在栈中,而变量 up 指向的联合是在堆中分配的。
最后,变量
f
f
f 是一个指向函数 fun 的指针。在反汇编代码中,看到如下 fun 的初始化代码:

打印出来的指针
f
f
f 的值 0x8048414 就是 fun 的代码中第一条指令的地址。
向函数传递参数
其他语言(如Pascal)提供两种方式来向过程传递参数——传值(by value)和引用(by reference)。
传值是指调用者提供实际的参数值,而引用是指调用者提供一个指向该值的指针。
在 C 中,所有的参数都是传值的,但是可以通过显式地产生一个指向一个值的指针,并把该指针传递给过程,从而实现了引用参数的效果。函数fun(&x)中的参数xp就是这样的。第一次调用fuc(&x)时(第 42 行),给了一个函数对test中局部变量 x x x 的引用。每次调用fun时,这个变量都会减小,从而在两次调用之后,递归会停止。








![[MySQL]一文带你学明白数据库控制语言——DCL](https://img-blog.csdnimg.cn/a1651f2a1e5b4f35a9e56de9aecdb402.png)










