指针
目录
一、指针特点
○ 让代码更加简洁高效
○ 提供直接访问内存操作
○ 利用指针可以直接操作硬件
二、指针概念
1. 地址
● 为了区分内存中不同字节的编号(寻址范围:)
2. 指针
● 指针就是地址,地址就是指针,指针比地址多了指向的概念
3. 指针变量
● 存放指针(地址)的变量称为指针变量(64位操作系统中所有指针变量均为8个字 节)
三、 指针相关运算符
1. &:
● 取地址运算符,获得变量在内存空间中的首地址
■ 只有变量(左值)才能&,常量和表达式不能进行&操作
■ &构成的表达式值为:变量在内存空间中的首地址
int Num = 0;
&Num:得到Num在内存空间中4字节空间的首地址
■ &构成的表达式类型位:变量的类型升级后的类型
int Num;
&Num: int -> int *
2. *:
● 获得指针指向的空间,或者对应空间中的值
■ * 运算符连接的内容必须为指针类型,不能是普通变量类型
■ 如果直接使用 * 对应的表达式,表达式值为:该指针指向空间中的值
*&Num; //获得&Num指向的空间中int类型的值
■ 如果使用 * 对应的表达式其类型为:指针类型降级后的类型
//int * -> int
int *p;
*p;
■ * 对应的表达式作为左值表示,将等号右边的值放入指针指向的空间
四、指针变量的定义
1. 定义方法
存储类型 数据类型 *变量名;
int *p;
char *p;
float *p;
double *p;
int *p, *q;
注意:不能操作未经初始化的指针(野指针)
2. 野指针
● 未经初始化的指针或者指向已经被释放空间的指针,称为野指针
3. 空指针
● 指向内存地址为0x0的指针称为空指针,用NULL来表示
● 内存地址0x0空间为只读空间,不能赋值,NULL指针不能执行*p = value操作
int *p = NULL;
int *q = NULL;
int *p, *q;
4. 指针特性(变量访问的两种形式)
1)直接访问
■ 通过变量的变量名访问变量空间
2)间接访问
■ 通过变量在内存中的地址访问变量空间
int num = 0;
int *p = NULL;
p = #
*p; //间接访问
num; //直接访问
五、指针的算术运算
1. 指针运算符
● 指针偏移的大小由指针指向的数据类型大小决定
● 两个指针相减得到的结果为,两个地址间相差的数据类型元素个数
● +、-、++、--
2. 不同指针之间的区别
● * 操作时处理方式不同
char * //取0x2000开始的1个字节空间数据
int * //取0x2000开始的4个字节空间数据
float * //取0x2000开始的4个字节空间数据
double * //取0x2000开始的8个字节空间数据
● 算数运算
char * //偏移量为1个字节
int * //偏移量为4个字节
float * //偏移量为4个字节
double * //偏移量为8个字节
六、指针常见操作
○ 指针有两个需要注意的概念:指针变量本身,指针指向的空间
○ 对指针变量:p 或者 q 修改,改变的是指针的指向
○ 对 *p 或者 *q 操作,改变的是指针指向的空间,指针还是指向原有的空间
int a = 100;
int b = 200;
int *p = NULL;
int *q = NULL;
p = &a;
q = &b;
q = &a; //让指针q指向a
*q = a; //将a的值赋值给q指向的空间,q本身没有改变
*p = *q; //将q指针指向的空间中的值,赋值给p指针指向的空间
p = q; //将指针变量q中的值赋值给指针变量p
七、指针作为函数参数
1. 值传递
● 实参将值传递给形参,形参的值改变,实参不会改变
● 函数体内部想使用函数体外部变量值的时候,使用值传递
2. 地址传递
● 实参将地址传递给形参,形参是指向实参的指针,可以利用形参修改实参的值
● 函数体内部想修改函数体外部变量值的时候,使用地址传递
八、指针和数组的关系
1. 数组和指针的关系
● 数组的数组名是指向数组第一个元素的指针常量
● 数组的数组名 a 可以理解为 int * 型
注意:两种特殊情况 a 不能理解为 int *
■ sizeof:sizeof(a) == 20 sizeof(int *) == 8
■ &: &a == int (*)[5] &int * == int **
int a[5] = {1, 2, 3, 4, 5};
int *p = NULL;
p = a;
p = &a[0];
//a == &a[0]
//访问下标为n的元素
a[n] == *(a+n) == *(p+n) == p[n]
2. 数组的本质
● 如:int a[5];
■ 开辟20个字节空间,常量 a 表示空间首地址
■ 当访问 a[i] 元素时,等价于 *(a+i),能操作对应的空间
3. 数组作为函数参数
● 三种传递形式:
int fun(int a[5]);
int fun(int a[], int len);
int fun(int *parray, int len);
4. 字符型数组机字符串的传递
1)字符串传参
int fun(char *pstr);
2)字符串的遍历
while (*pstr != '\0')
{
pstr++;
}
九、const 指针
1. const 指针形式
const int *p;
int const *p;
int *const p;
const int *const p;
int const *const p;
● 第一种和第二种形式等价(const修饰 *p)
■ 指针变量 p 的值可以改变,但是不能利用p修改指向空间中的值
● 第三种(const修饰 p)
■ 指针变量 p 的值不能变,但可以利用指针 p 修改指向空间中的值
■ 注意指针变量 p 必须初始化,否则后续无法赋值
● 第四种和第五种等价(const修饰 *p,const修饰 p)
■ 指针变量 p 的值不能变,也不能通过 p 修改指向空间中的值
■ 注意指针变量 p 必须初始化,否则后续无法赋值
十、指针函数和函数指针
1. 指针函数
● 是函数,函数的返回值是指针
■ 不能返回局部变量的地址
■ 指针函数返回的地址可以作为下一个函数调用的参数
2. 函数指针
● 是指针,指针指向一个函数
十一、二级指针
1. 定义
● 二级指针是指向一级指针变量的指针
/*
定义一个指针变量q,占8个字节空间,指向一个指针变量空间,即指向一级指针变量的指针,也就是
二级指针
*/
int **q;
2. 使用场景
● 函数体内部想修改函数体外部指针变量值的时候,需要传指针变量的地址即二级指针
● 指针数组传参,数组的数组名是指向数组第一个元素的指针,第一个元素是指针,所 以数组名为指向指针的指针即二级指针
十二、指针数组和数组指针
1. 定义
● 指针数组:是数组,数组的每个元素是指针
● 数组指针:数指针,指针指向整个数组
int *a[5];
//定义一个数组,占40个字节,数组名为a,数组中每个元素都是int *型的指针
int (*a)[5];
//定义一个指针,占8个字节,指针变量名为a,是指向数组20个字节空间的指针
2. 指针数组
char *pstr[5];
● 存放字符串使用字符型数组,操作字符串使用指针
● 存放字符串数组使用字符型二维数组,操作字符串数组使用指针数组
3. 利用指针数组操作二维数组中的字符串实现排序
● 二维数组存放字符串数组
● 指针数组元素变化代替二维数组字符串变化,提高程序效率
4. 数组指针
● 对一维数组数组名&:值不变,类型升级为指向整个数组的指针
● 对指针数组 *:值不变,类型降级为指向数组第一个元素的指针
int a[5] = {1, 2, 3, 4, 5};
a == &a[0]
a == int *
注意:两种特殊情况 a 不能理解为 int *
● sizeof:获得数据类型、变量所占字节
● &: & int * -> int ** &a -> int (*p)[5]
5. 数组指针的使用场景
1)一维数组和指针的关系:
■ 数组的数组名是指向数组第一个元素的指针常量
int a[5] = {1, 2, 3, 4, 5};
int *p = NULL;
p = &a[0];
p = a;
//a == &a[0]
a[n] == *(a+n) == *(p+n) == p[n]
2)二维数组和指针的关系:
■ 数组的数组名是指向数组第一行元素的数组指针
int a[2][3] = {1, 2, 3, 4, 5, 6};
int *p = NULL;
int (*q)[3] = NULL;
p = &a[0][0];
p = a[0];
p = *a;
q = a;
a:指向数组第一行元素的数组指针 int (*)[3]
a[0]:指向a[0][0]的指针 int *
a[1]:指向a[1][0]的指针 int *
访问数组第m行第n列元素的方式:
a[m][n]
*(a[m]+n)
*(*(a+m)+n)
*(p+m*N+n)
*(*(q+m)+n)
*(q[m]+n)
q[m][n]
6. 二维数组的传参
int a[2][3] = {1, 2, 3, 4, 5};
int fun(int (*p)[3], int len);
7. 指针数组的传参
char *pstr[5] = {NULL};
int fun(char **ppstr, int len);
十三、void *指针
○ void * 指针主要用来保存内存地址
○ void* 转换为 char *、int *、double * 类型时,不需要类型转换,直接赋值就行
○ char *、int *、double * 转换为 void * 类型时,需要强制类型转换
○ void *多用于函数参数和返回值,统一参数和返回值类型
void *p;
char *a;
int *b;
double *c;
p = (void *)a;