概念
内存中每个字节都有一个编号,这个编号就叫做指针,也叫做地址。
专门用于存储这个变量叫做指针变量。
指针是专门用来存放地址的。
指针的大小取决于地址存储需要多大的空间,一般32位的平台是4个字节,64位的平台是8个字节。
因此指针大小一般是相同的。
地址是由低到高排列的。
指针:指针变量
地址:地址编号
指针相关的操作
& :取地址符,获取变量的地址
对于多字节的变量,取地址取到的是编号最小的那个,叫做首地址
* :在定义指针变量的时候,只起到一个标识作用,标识定义的是一个指针变量
在其他场景下,对指针取*操作,都表示操作指针保存的地址里面的内容
指针和变量的关系
![](https://i-blog.csdnimg.cn/blog_migrate/4d47fe2cf99a6583a0307a164f85ab3e.png)
从分配的地址中的几个字节中的第一个字节顺序往下存。
指针的基本使用
& 可以获取变量的地址; 使用 %p 输出;
例:
printf("&a = %p\n", &a);
使用指针可以保存变量的地址;
定义指针的格式 数据类型 *指针变量名:
int *p;
p = &a;
(指针p保存了变量a的地址 我们称之为 指针p指向变量a。)
也可以用初始化的写法 int *p = &a;
当指针保存了变量的地址后 就可以通过指针操作变量对应的内存空间了
*p = 520;
printf("*p = %d a = %d\n", *p, a); // 520 520
注:不能使用普通变量来保存地址。
例:
long long value = &a;
printf("value = %#llx\n", value);//只保存可以
*value = 1314;//但是普通变量不允许取 * 操作
指针只能保存已经分配了的地址,对没有分配的地址进行取 * 操作错误是不可预知的。
指针类型的作用,决定了从他保存的地址开始一共能操作多少个字节。
例:
int *p 能操作4个字节,char *p 只能操作一个字节。
所以一般情况下我们都让指针的类型 和指向的变量的类型保持一致,目的是为了让操做空间的大小一致。
注:常量是没有地址可言的,以及多个指针定义标准:
int *p3, p4;//这种写法 p3 是指针 p4就是一个int类型的变量。
int *p3, *p4; //这样写 p3 和 p4 才都是指针。
4.1指针基操
使用指针可以保存变量的地址。
int *p; //定义指针的格式 数据类型 *指针变量名。
p = &a; //指针p保存了变量a的地址 我们称之为 指针p指向变量a
也可以用初始化的写法 int *p = &a;
这两个操作其实都是将地址存入p之中。
指针只能保存已经分配了的地址.
定义多个指针时,要注意:
int *p3, *p4; //这样写 p3 和 p4 都是指针。
int *p3, p4;//这种写法 p3 是指针 ,p4就是一个int类型的变量。
4.2 野指针
定义指针如果不初始化,里面都是随机值,也就是指针指向随机地址,这种指针叫做野指针 , 野指针对程序是有害的,错误不可预知。
注:在归还地址后(return(&a)),此时p还存有a的地址,但是已经属于野指针,再次通过p访问就属于非法访问。
预防野指针方法:
初始化;
小心指针越界;
指针指向空间释放NULL(例:int *p=NULL;),指向 NULL 的指针叫做 空指针, NULL 本质 (void *)0 ;
指针使用前检查有效性。
指针的运算
指针的运算本质就是指针保存的地址量作为运算量来参与运算。
既然是地址的运算,能进行的运算就是有限的了。
相同类型的指针变量之间做运算才有意义。
指针类型决定了:指针解引用的权限有多大;指针走一步能走多远。
指针能作用的运算:
算数运算: + - ++ --
关系运算:> < >= <= == !=
赋值运算: =
0 | 1 | 2 | 3 | 4 |
5.1指针算数运算
指针在进行加减运算时一般都是以一个指针类型的大小进行计算。
例:
int s[5] = {10, 20, 30, 40, 50};
int *p1 = &s[0];
int *p2 = p1+4; //p2 = p1+4*sizeof(int),即一个指针加上一个整数n 表示加上n个指针的数据类型的大小
printf("*p1 = %d\n", *p1);//10
printf("*p2 = %d\n", *p2);//50
5.2 指针-指针
指针-指针=两个指针之间的数据个数。
因此指针-指针的前提是两个指针指向的是同一个空间。
大小端存储的问题
不同类型的CPU对多字节数据的存储方式也是不同的,分为小端存储和大端存储。
![](https://i-blog.csdnimg.cn/blog_migrate/84d47d59828fc42d85dfabf9b48aff8d.png)
用C语言写一个简单的程序,判断你使用的主机是大端存储还是小端存储。
#include <stdio.h>
int main(){
int a = 0x12345678;
char *p = (char *)&a;
if(0x78 == *p){
printf("小端\n");
}else if(0x12 == *p){
printf("大端\n");
}
return 0;
}
指针和一维数组
数组名就是数组的首地址,数组名的操作空间 和 数组的类型是一致的:
int s[5] = {10, 20, 30, 40, 50};
printf("s = %p\n", s);
printf("s+1 = %p\n", s+1);//相差1个int,
数组名[下标] 访问元素的本质就是对指针取*操作,即:
s[i] <==> *(s+i)
定义一个指针来保存数组的首地址的不同写法:
下述写法虽然不同,但本质都是将数组s的首地址存入p之中,因此,除了下述写法,还有其他不同写法;基于上述,不难推断出,数组的操作与指针的*操作高度一致。
(1)int *p = &s[0];
(2)int *p = s; //常用的写法
(3)不要使用int *p = &s;// &s 这种写法相当于指针的升维操作 改变了指针的操作空间
注:不要对数组名进行 取地址 & 操作 !!!
当指针保存了数组的首地址之后 就可以操作数组元素了,有如下的等价关系
s[i] <==> *(s+i) <==> *(p+i) <==> p[i]
p 和 s 的区别:
(1)p 是指针是变量,可以被赋值,也可以执行++操作;
(2)s 是数组名是常量,不可以被赋值,也不可以执行++操作。
注:要与后文指针直接指向字符串做对比。
指针和二维数组
二维数组数组名也是首地址。
二维数组数组名的操作空间:
printf("s+1 = %p\n", s+1);//相差16 --> 4*sizeof(int)
也就是说二维数组的数组名操作空间是一整行元素——我们称之为行指针
对二维数组的数组名取 一次*操作相当于给行指针进行降维操作,将操作空间是一行元素的指针降维成操作空间,是一个元素的指针的列指针,对列指针取二次*操作,才是操作内容,也就是说 有如下的等价关系:
s[i][j] <==> *(s[i]+j) <==> *(*(s+i)+j)
注:二维数组的数组名 操作空间是一行元素,已经超过了基本类型的操作空间了,所以不能使用普通的指针来指向二维数组,因为普通的指针没法按行操作
int *p = s;//一般不这样使用
保存二维数组的首地址需要用数组指针。
二维数组的遍历
int i = 0;
int j = 0;
for(i = 0; i < 3; i++){
for(j = 0; j < 4; j++){
//printf("%d ", s[i][j]);
//printf("%d ", *(s[i]+j));//这种写法不常用
printf("%d ", *(*(s+i)+j));
}
printf("\n");
}
数组指针
int (*p)[n]
本质是一个指针,指向一个二维数组,也叫行指针,数组指针多用于将二维数组作为函数的参数传递,数组指针的操作空间是一行元素:p++; //向后偏移12字节 == 3*sizeof(int)
格式:
数据类型 (*指针变量名)[列宽];
定义了一个数组指针p指向二维数组s:
int (*p)[4] = s;//初始化的写法
或
int (*p)[4] = NULL;
p = s;
数组指针指向二维数组后,操作就和二维数组数组名的操作是一样的了,也就是说有如下的等价关系:
s[i][j] <==> *(s[i]+j) <==> *(*(s+i)+j) <==> p[i][j] <==> *(p[i]+j) <==> *(*(p+i)+j)
二维数组的遍历:
int i = 0;
int j = 0;
for(i = 0; i < 3; i++){
for(j = 0; j < 4; j++){
//printf("%d ", s[i][j]);
//printf("%d ", *(s[i]+j));//这种写法不常用
//printf("%d ", *(*(s+i)+j));
//printf("%d ", p[i][j]);
//printf("%d ", *(p[i]+j));//这种写法不常用
printf("%d ", *(*(p+i)+j));
}
printf("\n");
}
p和s的区别:p是变量,s是常量。
注:不能对一维数组名取地址 :
对一维数组的数组名取 & 操作 相当于指针的升维操作,把本来操作空间是一个元素的指针 升维 成操作空间是一行元素的指针,而我们的p操作空间只有一个元素,所以类型不匹配 会报警告,虽然可以使用数组指针来消除这个警告,但是此时的p 基本上就没有意义了因为此时的 p+1 就加了一整行,而我们的数组就只有一行,也就是说 p+1 就已经越界了
指针数组
本质是一个数组,数组中每个元素都是一个指针。
格式:
数据类型 *指针数组名[下标];
定义了一个指针数组 数组名叫 name2 数组中共有4个元素,每个元素都是一个 char * 类型的指针,如下:
char * name2[4] = {NULL};
name2[0] = "zhangsan";(将字符串常量区地址赋给name2[0])
name2[1] = "lisi";
name2[2] = "fulajimier.fulajimiluoweiqi.pujing";
name2[3] = "zhaoliu";
可以进行*操作(读取内容),不能修改。
指针函数
例: int *my_func3(int x, int y)
本质是一个函数,返回值是一个指针类型。
注意:不能返回局部变量的地址,因为局部变量在函数调用结束之后,就被操作系统回收了。
可以返回的数据:
1.全局变量的地址
2.static修饰的局部变量的地址
3.参数传递过来的地址(如 strcpy)
函数指针
例: void (*p)(int, int)
本质是一个指针,指向一个函数。
格式:
返回值类型 (*函数指针名)(函数的形参表);
函数指针的典型使用场景----用作回调函数
int my_add(int x, int y){
int temp = x+y;
return temp;
}
int my_sub(int x, int y){
int temp = x-y;
return temp;
}
//在jisuan函数内部通过 函数指针p调用函数时,
//具体调用的是哪一个函数 取决于 用户调用 jisuan函数时,传递的第三个参数
//第三个参数是哪个函数 通过p调用的就是哪个函数
//相当于通过p去调用用户指定的函数 称之为 回调函数
int jisuan(int x, int y, int (*p)(int, int)){
int temp = p(x, y);
return temp;
}
int main(int argc, const char *argv[])
{
int a = 10;
int b = 20;
printf("%d\n", jisuan(a, b, my_add));//30
printf("%d\n", jisuan(a, b, my_sub));//-10
return 0;
指针和字符串
虚拟内存的划分:
![](https://i-blog.csdnimg.cn/blog_migrate/d56f928fb897fa4abd5532e48a51a6d4.png)
可以将字符串保存在字符数组中,s1是数组在栈区 "hello world" ,字符串常量在字符串常量区,这个操作相当于用字符串常量区的 "hello world",给栈区的数组初始化。
char s1[32] = "hello world";//后面对s1的操作操作的都是栈区的数组
栈区的内容是允许修改的,如下:
*s1 = 'H';
printf("s1 = [%s]\n", s1);//Hello world
char s2[32] = "hello world";
栈区定义多个数组,即使保存一样的数据,数组的首地址也不一样
也可以使用指针直接指向字符串常量,这种写法指针变量p在栈区保存的地址是字符串常量区 "hello world"的地址,字符串常量区的内容是不允许修改的,因此该法与前文指针不同,不可对内容进行修改:
char *p1 = "hello world";
普通指针:char *p=&a;
printf("p1 = %s\n", p1);//读操作允许
注:不管定义多少个指针,只要指向同一个字符串常量 那么保存的地址就是一样的
char *p2 = "hello world";
printf("p1 = %p, p2 = %p\n", p1, p2);//一样的
二级指针
二级指针是用来保存一级指针的地址的,多用于将一级指针的地址作为函数的参数传递时。
int a = 10; //变量
int *p = &a; //一级指针
int **q = &p; //二级指针
变量、一级指针、二级指针 关系图。
![](https://i-blog.csdnimg.cn/blog_migrate/bf99994604c26fdd1e20ffd6c2b81731.png)
int a = 10;
int *p = &a;
int **q = &p;
有如下等价关系:
a <==> *p <==> **q
a <==> p <==> *q
p <==> q
通过二级指针也可以操作变量 但是需要取 ** 操作
**q = 1314;
printf("a = %d\n", a);//1314
//注意:用一级指针保存一级指针的地址
//int *q2 = &p; //可以保存
//但是一级指针不能取 ** 操作 所以保存了也没有意义
//**q2 = 1314;//错误的