C语言指针

都说C语言中最重要的概念就是指针,最难学的也是指针。网上各种讲解指针的文章五花八门,一开始看多了都有点糊涂,其实花点时间自己写一些测试代码,打印出来,思考一下跟自己一开始猜想的答案有何不同,还是好理解得多。

本质

指针本质上是地址,记录某个内存单元的位置; 不过它不仅仅是地址,它除了指向一个地址之外,还隐含了一个信息:一个长度,这样结合起来看,实际上它是代表了一个区块。

这个区块长度就是指针的类型。比如一个int类型的指针,它的长度将是4个字节,那么一旦指针加1:

int *pi;
pi++;

它会向高地址方向移动4个字节。以此类推,char类型的就是1个字节了。

声明

声明指针变量就用“*”符号:

int a = 123;
int *p1;
int *p2 = &a;
int *p3 = p2;

初始化的指针指向的地址是随机的,也就是野指针。

这里的*号是指针间接寻址运算符,*pi表示取指针pi所指向的变量的值,也称为间接引用。

数组指针个指针数组

  • 这是指针数组:
    int *p1[10];
    
  • 这是数组指针:
    int (*p1)[10];
    

原因是因为“[]”的优先级比“*”高。 实际上定义成这样更容易看点:

int (*)[10] p2; //实际上不能这么写

引用网上的一张图片查看区别:

结构体指针

一个结构体的大小,等于里面最大成员的成员变量的倍数;例如:

Struct S {
	int a;
	char c;
};


其长度是int的倍数,也就是8,而不是4+1=5; 如果是嵌套的:

Struct S {
	int a;
	char c;
	struct S child;
};

则长度是16;

创建一个指针的方式同样也差不多,但访问成员变量则是使用“->”符号:

// 声明一个结构体
struct S s;
s.a = 1;
s.c = 'y';

// 创建指针
struct S *ps = &s;

// 访问成员:
ps->a = 2;
ps->c = 'n';

函数指针

// 定义一个函数
void foo(int a)
{
	//...
}

// 定义一个函数指针func1:
void (*func1)(int) = foo;

// 用指针执行函数:
func1(2);

不能写成

void *func1(int);

因为这样“*”跟“void”先结合了,变成了返回一个void*指针类型的函数体;

不同环境的区别

同一环境中指针的大小都一样,只是带要区分32位和64位的机器。如果是32位的话则是4个字节,64位是8个字节。 (据说数据指针跟函数指针在某些特殊情况下会有不同,但还没遇见过。) 另外,不论32位还是64位的机器,int类型都是相同的,4个字节,而long类型则会有所不同。

试题

  • 关于指针类型的题目
    问打印出来的结果是什么?
    #include <stdio.h>
    
    int main(int argc, const char *argv[])
    {
        int a[] = {1, 2, 3, 4, 5}; 
        int *p = (int*)(&a + 1); 
        printf("%d, %d\n", *(a+1), *(p-1));
        return 0;
    }
    

我第一次接触到时候回答:

2, 1

实际上答案应该是:

2, 5

(不幸的是这还是一次面试中的题目。所以给面试官逮住不停追问,结果当然不怎么好了。)

但这次为什么呢?第一个数好理解,a是数组名,是指向数组第一个元素的指针,第一个元素指针当然是int类型了,所以*(a+1)就是移动了sizeof(int)的长度,也就是4个字节。

而第二个为什么是5呢?因为&a的意思是对数组a取地址,也就是数组a类型的指针,这个类型的长度是数组的长度。那么(&a + 1)就是往后移动了数组a的长度,也就是5个int类型的长度,sizeof(int) * 5。只是后面(int*)(&a + 1)则是强制转换成了int类型指针罢了。

所以这样子后面到了*(p-1),就是往前移动了int类型的长度,即是4个字节。就变成指向最后一个数字“5”了。

这里有一篇讲得更清晰,而且还讲到了二维数组的情况: 关于C语言指针的问题