文档版本 | 更新时间 | 更新内容 |
---|---|---|
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),所有函数都可以使用;