文档版本 更新时间 更新内容
v1.0 2020-09-13 初稿完成

一、指针的重要性
  • 表示一些复杂的数据结构
  • 快速的传递数据,减少内存消耗(直接发送地址)
  • 使函数返回一个以上的值
  • 能直接访问硬件
  • 能够方便的处理字符串
二、指针的定义

1. 什么是地址

地址就是内存单元的编号,从零开始的非负整数,CPU的地址总线宽度决定了寻址范围。

2. 什么是指针

指针就是地址,地址就是指针。

3. 什么指针变量

存放地址/指针的变量。

通常在使用时,会将指针变量简称为指针,但是指针变量和指针并不是一个东西。

4. 一个指针变量占多少个字节

一个指针变量,无论指向的变量占多少字节,该指针变量本身占用的字节数,就是CPU地址总线的宽度

  • 32位CPU中:一个指针变量占用4个字节;
  • 64位CPU中:一个指针变量占用8个字节;
/**
 * CPU:64bit
 * OS:Windows10
 * IDE:Clion
 * Compiler:MinGW-64
*/

#include <stdio.h>

int main() {

    printf("sizeof(void *):%d\r\n", sizeof(void *));

    printf("sizeof(char *):%d\r\n", sizeof(char *));

    printf("sizeof(int *):%d\r\n", sizeof(int *));

    printf("sizeof(long *):%d\r\n", sizeof(long *));

    printf("sizeof(float *):%d\r\n", sizeof(float *));

    printf("sizeof(double *):%d\r\n", sizeof(double *));

    return 0;
}

运行结果为:

sizeof(void *):8
sizeof(char *):8
sizeof(int *):8
sizeof(long *):8
sizeof(float *):8
sizeof(double *):8
三、指针的分类

1. 基本类型指针

① 定义一个指针:

int *p = NULL;

定义了一个变量p,p的类型是int *类型,就是存放int型变量地址的类型

因为指针可以访问硬件,所以指针的默认值通常赋为NULL,防止造成危险。

NULL表示编号为0的地址空间,该地址处既不允许写,也不允许读。

② 指针赋值:

int i = 99;
p = &i;

将int型变量 i 的地址存放到指针变量p中,称为:指针变量 p 指向变量 i

③ 使用指针:

int j;
j = *p;	//执行之后j的值为99

*p表示:以p的内容为地址的变量。

2. 指针和数组

2.1. 指针和一维数组

① 一维数组名:就是一个指针常量,它存放的是一维数组第一个元素的地址

② 下标和指针的关系:p[i] = *(p+i)

③ 一个函数想要对一个数组进行处理,至少需要接收两个参数:数组第一个元素的地址、数组长度。

2.2. 指针变量的运算

① 指针变量不能相加、相乘、相除

② 只有两个指针变量指向的是同一块连续内存(数组)时,才可以相减,表示两个地址之间的差值。

2.3. 指针数组和数组指针

指针数组:是指针变量的数组,其中每个元素都是一个指针变量。

int *a[10];

数组指针:表示一个指向数组的指针变量,就是数组名。

int a[10];
int *p = a;

3. 指针和结构体

4. 指针和函数

4.1. 什么是函数指针

在定义函数的时候,函数名就是一个地址,所以可以先定义一个指针变量,用于保存某个函数的地址,这个指针变量就成为函数指针

4.2. 函数指针的作用

  • 用作回调函数:用户可以自己实现的函数作为回调函数。
  • 抽象分层设计

4.3. 函数指针的用法

① 如何定义:

int (*add)(int a, int b);

② 如何使用:

int my_add(int a, int b)
{
	return a+b;
}
int main()
{
	add = my_add;
	printf("myadd result is:%d\r\n", my_add(1,1));
	printf("add result is:%d\r\n", add(1,1));
	
	return 0;
}

运行结果为:

myadd result is:2
add result is:2

5. 多级指针

int i = 10;

① 一级指针:

int *p = &i;

理解:p是int *类型,指针变量 p 用来存放 int 类型变量的地址。

② 二级指针:

int **q = &p;

理解:q是 int ** 类型,指针变量 q 用来存放 int * 类型变量的地址。

③ 三级指针:

int ***r = &q;

理解:r是 int *** 类型,指针变量 r 用来存放 int ** 类型变量的地址。

④ 使用:

printf("i = %d\r\n", ***r);

示例程序如下:

/**
 * CPU:64bit
 * OS:Windows10
 * IDE:Clion
 * Compiler:MinGW-64
*/

#include <stdio.h>

int main() {

    int i = 10;

    int *p = &i;

    int **q = &p;

    int ***r = &q;

    printf("i = %d, &i = %p\r\n", i, &i);

    printf("p = %p, &p = %p, *p = %d\r\n", p, &p, *p);

    printf("q = %p, &q = %p, *q = %p, **q = %d\r\n", q, &q, *q, **q);

    printf("r = %p, &r = %p, *r = %p, **r = %p, ***r = %d\r\n", r, &r, *r, **r, ***r);
    
    return 0;
}

运行结果为:

i = 10, &i = 000000000061FE1C
p = 000000000061FE1C, &p = 000000000061FE10, *p = 10
q = 000000000061FE10, &q = 000000000061FE08, *q = 000000000061FE1C, **q = 10
r = 000000000061FE08, &r = 000000000061FE00, *r = 000000000061FE10, **r = 000000000061FE1C, ***r = 10
四、void*指针的妙用

① void指针表示无类型的指针,所以void类型的指针,可以转换为任何类型的指针。

比如设计一个通用的API函数,参数可以制定为void*,这样用户调用的时候无论传入int类型的指针,还是结构体类型的指针,都是可以的,但是这也需要用户在调用的时候,指明自己的指针是什么类型。

② 因为void*指针无类型,所以不可以进行运算,不能使用 * 运算符解引用

五、动态内存分配和释放

1. 静态内存与动态内存

  • 静态内存在栈中分配,由系统分配,由系统释放;
  • 动态内存在堆中分配,手动申请,手动释放;

2. 动态内存使用方法

以构造动态数组为例:

#define NUM	10
int *ptr = (int *)malloc(NUM * sizeof(int));

malloc函数用来向系统申请动态内存,参数为申请内存大小(单位:Byte),申请成功则返回内存地址,申请失败则返回NULL。

使用完之后通过函数free来释放:

free(ptr);
ptr = NULL;

3. 静态数组与动态数组的比较

静态数组(传统数组)的缺点有:

  • 大小固定,且只能是常整数,不能是变量;
  • 在程序运行期间,内存一直占用,程序结束后释放;
  • 数组的长度一旦定义,无法再次修改;
  • 函数中定义的数组,在函数返回后被销毁,其它函数无法再次使用;

使用动态内存的动态数组解决了以上四个缺点:

  • 大小任意,程序运行中动态申请,并可以动态改变;
  • 只要申请的内存不释放(free),所有函数都可以使用;