[Mac-10.7.1 Lion Intel-based, gcc 4.2.1]
Q: 指针到底是什么?
A: 有一天,你初到合肥,要到一个地方,但是路线不熟悉。遂问了一个路人,请问乌邦托在哪里?路人答曰:向南走即可。这个回答就像指针一样,告诉你一个方向。但是,到底向南多少,这就像是指针类型决定的大小。
Q: 看到那个const修饰指针的时候,老是搞不清楚到底是指针是常量还是指针指向的变量是常量?
A: 其实很简单,采用从右向左的读法即可搞定这些。如下例子:
#include <stdio.h>
int main (int argc, const char * argv[])
{
int i = 100;
const int *pi = &i;
*pi = 200;
return 0;
}
保存为const.c, 使用gcc -o const const.c编译:
可以看到编译错误,表明pi是个不可更改其指向数据的指针。按照上面的从右读的原则即为:
const int * pi
常量 整形 指向 pi
读为: pi指向整形常量,即pi指向什么变量可以改变,但是指向的变量的值不可改变。
当然,使用类型方法,const int *pi和 int const *pi的含义是一致的。
如下代码就是ok的:
#include <stdio.h>
int main()
{
int i = 100, j = 200;
const int *pi = &i;
pi = &j;
return 0;
}
编译ok.
另外一种情况:
#include <stdio.h>
int main()
{
int i = 100, j = 200;
int *const pi = &i;
pi = &j;
return 0;
}
编译:
可以看到,编译错误表示pi是只读的,不可以更改pi的值。再使用从右向左的读法:
int * const pi
整形 指向 常 pi
读为: pi常指向整形
这也意味着,pi的值不能更改,但是没有意味着pi指向的数据不可以更改。
#include <stdio.h>
int main()
{
int i = 100, j = 200;
int *const pi = &i;
*pi = j;
return 0;
}
如上代码,编译ok.
Q: 看过很多代码中含有函数指针,它的本质是什么?
A: 它的本质即为一个指针,理论上函数的地址在编译期即可计算得到(当然在链接或者运行时还可能有重新定位)。先看一个简单的例子:
#include <stdio.h>
#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));
int add(int a, int b)
{
return a + b;
}
int main()
{
int (*func)(int, int) = add;
PRINT_D(func(1, 2))
return 0;
}
保存为func_ptr.c, 编译运行:
分析下代码:
int (*func)(int, int)表示声明一个函数指针,它的参数是2个整形,返回值为1个整形;什么地方能体现出是函数指针呢?就在于func前面的*号。
= add; 表示此指针指向add函数。c语言的编译原则是函数编译之后可以确定当前编译状态的地址。为了确定,我们查看下汇编代码:
gcc -S func_ptr.c得到汇编(部分):
_add:
Leh_func_begin1:
pushq %rbp
Ltmp0:
movq %rsp, %rbp
Ltmp1:
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %eax
movl -8(%rbp), %ecx
addl %ecx, %eax
movl %eax, -16(%rbp)
movl -16(%rbp), %eax
movl %eax, -12(%rbp)
movl -12(%rbp), %eax
popq %rbp
ret
Leh_func_end1:
.globl _main
.align 4, 0x90
_main:
Leh_func_begin2:
pushq %rbp
Ltmp2:
movq %rsp, %rbp
Ltmp3:
subq $16, %rsp
Ltmp4:
leaq _add(%rip), %rax
movq %rax, -16(%rbp)
movq -16(%rbp), %rax
movl $1, %ecx
movl $2, %edx
movl %ecx, %edi
movl %edx, %esi
callq *%rax
可以看到_add在汇编代码中是一个标号,标号就意味着是一个地址。callq *%rax可以看到,这是调用add函数。
Q: 用add赋值给func的时候,为什么不用&add, 不是要取函数的地址么?
A: 是的, 这样也可以,不过函数本身就可以看成地址,它们效果一样的。
#include <stdio.h>
#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));
#define PRINT_P(ptr) printf("%10s is %p\n", (#ptr), (ptr));
typedef int (*func)(int, int);
int add(int a, int b)
{
return a + b;
}
int main()
{
PRINT_P(add)
PRINT_P(&add)
return 0;
}
运行:
Q: int (*func)(int, int)这种写法有点复杂吧。
A: 是的,避免写很多这种代码,可以使用typedef来定义一个函数指针。
#include <stdio.h>
#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));
typedef int (*func)(int, int);
int add(int a, int b)
{
return a + b;
}
int main()
{
func add_func = add;
PRINT_D(add_func(1, 2))
return 0;
}
Q: 结构体里面不也是可以含有函数指针的么?这里的函数指针的作用是什么呢?
A: 是的,可以含有。在结构体的函数指针一般为结构体数据服务的,它的实现可以模拟面向对象语言的类的功能。实际上,正因为有了指针,整个编程世界才变得很精彩,很多模拟方式都是通过指针达到的。
#include <stdio.h>
#include <string.h>
#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));
#define PRINT_S(str) printf(#str" is %s\n", (str));
typedef struct Student
{
int age;
char name[32];
int (*get_age)(struct Student *s);
void (*set_age)(struct Student *s, int new_age);
char *(*get_name)(struct Student *s);
void (*set_name)(struct Student *s, char *new_name);
}Student;
int student_get_age(struct Student *s)
{
return s->age;
}
void student_set_age(struct Student *s, int new_age)
{
s->age = new_age;
}
char *student_get_name(struct Student *s)
{
return s->name;
}
void student_set_name(struct Student *s, char *new_name)
{
memset(s->name, 0, sizeof(s->name));
strncpy(s->name, new_name, sizeof(s->name));
}
int main()
{
Student s;
s.get_age = student_get_age;
s.set_age = student_set_age;
s.get_name = student_get_name;
s.set_name = student_set_name;
student_set_age(&s, 25);
student_set_name(&s, "xichen");
PRINT_D(student_get_age(&s))
PRINT_S(student_get_name(&s))
return 0;
编译运行:
Q: 好像这个数组和指针的联系还是挺多的,下面的代码为什么输出的值不对?
#include <stdio.h>
#include <string.h>
#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));
void print_arr_size(int arr[3])
{
PRINT_D(sizeof(arr))
}
int main()
{
int arr[3] = {1, 2, 3};
print_arr_size(arr);
return 0;
}
运行结果:
A: 这是因为数组形式作为参数会被退化成指针导致的,也就是说print_arr_size函数的参数int arr[3]其实等同于int *arr, 所以sizeof(arr)的值是指针的大小(笔者的平台指针大小为8)。
Q: 常常看到函数的原型中有void *, 它到底代表什么?
A: 比如,
void *malloc(size_t);
申请空间,返回对应的指针;但是到底是什么类型的指针,此函数不能确定,是需要外部调用者来确定;所以,void *可以看成通用型指针,其它类型的指针均可以赋值给void *类型指针;而且,void *类型指针都可以通过强制转换变成需要的指针;用面向对象的思想来解释,void *是基类, char *, int *等都是子类。
char *p = (char *)malloc(32);
其它类型指针转换成void *的例子:
#include <stdio.h>
#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));
int main()
{
int i = 1;
void *p = &i;
PRINT_D(*(int *)p)
return 0;
}
当然,void *只是表示一种通用指针,到底此指针是什么类型的不确定,所以不能直接对void *类型变量的数据进行直接操作。