指针知识点

  1. 概念

内存中每个字节都有一个编号,这个编号就叫做指针,也叫做地址。

专门用于存储这个变量叫做指针变量。

指针是专门用来存放地址的。

指针的大小取决于地址存储需要多大的空间,一般32位的平台是4个字节,64位的平台是8个字节。

因此指针大小一般是相同的。

地址是由低到高排列的。

指针:指针变量

地址:地址编号

  1. 指针相关的操作

& :取地址符,获取变量的地址

对于多字节的变量,取地址取到的是编号最小的那个,叫做首地址

* :在定义指针变量的时候,只起到一个标识作用,标识定义的是一个指针变量

在其他场景下,对指针取*操作,都表示操作指针保存的地址里面的内容

  1. 指针和变量的关系

从分配的地址中的几个字节中的第一个字节顺序往下存。

  1. 指针的基本使用

  1. & 可以获取变量的地址; 使用 %p 输出;

例:

printf("&a = %p\n", &a);

  1. 使用指针可以保存变量的地址;

定义指针的格式 数据类型 *指针变量名:

int *p;

p = &a;

(指针p保存了变量a的地址 我们称之为 指针p指向变量a。)

也可以用初始化的写法 int *p = &a;

  1. 当指针保存了变量的地址后 就可以通过指针操作变量对应的内存空间了

*p = 520;

printf("*p = %d a = %d\n", *p, a); // 520 520

注:不能使用普通变量来保存地址。

例:

long long value = &a;

printf("value = %#llx\n", value);//只保存可以

*value = 1314;//但是普通变量不允许取 * 操作

  1. 指针只能保存已经分配了的地址,对没有分配的地址进行取 * 操作错误是不可预知的。

  1. 指针类型的作用,决定了从他保存的地址开始一共能操作多少个字节。

例:

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访问就属于非法访问。

预防野指针方法:

  1. 初始化;

  1. 小心指针越界;

  1. 指针指向空间释放NULL(例:int *p=NULL;),指向 NULL 的指针叫做 空指针, NULL 本质 (void *)0

  1. 指针使用前检查有效性。

  1. 指针的运算

指针的运算本质就是指针保存的地址量作为运算量来参与运算。

既然是地址的运算,能进行的运算就是有限的了。

相同类型的指针变量之间做运算才有意义。

指针类型决定了:指针解引用的权限有多大;指针走一步能走多远。

指针能作用的运算:

算数运算: + - ++ --

关系运算:> < >= <= == !=

赋值运算: =

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 指针-指针

指针-指针=两个指针之间的数据个数。

因此指针-指针的前提是两个指针指向的是同一个空间。

  1. 大小端存储的问题

不同类型的CPU对多字节数据的存储方式也是不同的,分为小端存储和大端存储。

用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;

}

  1. 指针和一维数组

  1. 数组名就是数组的首地址,数组名的操作空间 和 数组的类型是一致的:

int s[5] = {10, 20, 30, 40, 50};

printf("s = %p\n", s);

printf("s+1 = %p\n", s+1);//相差1个int,

  1. 数组名[下标] 访问元素的本质就是对指针取*操作,即:

s[i] <==> *(s+i)

  1. 定义一个指针来保存数组的首地址的不同写法:

下述写法虽然不同,但本质都是将数组s的首地址存入p之中,因此,除了下述写法,还有其他不同写法;基于上述,不难推断出,数组的操作与指针的*操作高度一致。

(1)int *p = &s[0];

(2)int *p = s; //常用的写法

(3)不要使用int *p = &s;// &s 这种写法相当于指针的升维操作 改变了指针的操作空间

注:不要对数组名进行 取地址 & 操作 !!!

  1. 当指针保存了数组的首地址之后 就可以操作数组元素了,有如下的等价关系

s[i] <==> *(s+i) <==> *(p+i) <==> p[i]

  1. p 和 s 的区别:

(1)p 是指针是变量,可以被赋值,也可以执行++操作;

(2)s 是数组名是常量,不可以被赋值,也不可以执行++操作。

注:要与后文指针直接指向字符串做对比。

  1. 指针和二维数组

  1. 二维数组数组名也是首地址。

  1. 二维数组数组名的操作空间:

printf("s+1 = %p\n", s+1);//相差16 --> 4*sizeof(int)

也就是说二维数组的数组名操作空间是一整行元素——我们称之为行指针

  1. 对二维数组的数组名取 一次*操作相当于给行指针进行降维操作,将操作空间是一行元素的指针降维成操作空间,是一个元素的指针的列指针,对列指针取二次*操作,才是操作内容,也就是说 有如下的等价关系:

s[i][j] <==> *(s[i]+j) <==> *(*(s+i)+j)

注:二维数组的数组名 操作空间是一行元素,已经超过了基本类型的操作空间了,所以不能使用普通的指针来指向二维数组,因为普通的指针没法按行操作

int *p = s;//一般不这样使用

  1. 保存二维数组的首地址需要用数组指针。

  1. 二维数组的遍历

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");

}

  1. 数组指针

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 就已经越界了

  1. 指针数组

本质是一个数组,数组中每个元素都是一个指针。

格式:

数据类型 *指针数组名[下标];

定义了一个指针数组 数组名叫 name2 数组中共有4个元素,每个元素都是一个 char * 类型的指针,如下:

char * name2[4] = {NULL};

name2[0] = "zhangsan";(将字符串常量区地址赋给name2[0])

name2[1] = "lisi";

name2[2] = "fulajimier.fulajimiluoweiqi.pujing";

name2[3] = "zhaoliu";

可以进行*操作(读取内容),不能修改。

  1. 指针函数

例: int *my_func3(int x, int y)

本质是一个函数,返回值是一个指针类型。

注意:不能返回局部变量的地址,因为局部变量在函数调用结束之后,就被操作系统回收了。

可以返回的数据:

1.全局变量的地址

2.static修饰的局部变量的地址

3.参数传递过来的地址(如 strcpy)

  1. 函数指针

例: 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;

  1. 指针和字符串

虚拟内存的划分:

  1. 可以将字符串保存在字符数组中,s1是数组在栈区 "hello world" ,字符串常量在字符串常量区,这个操作相当于用字符串常量区的 "hello world",给栈区的数组初始化。

char s1[32] = "hello world";//后面对s1的操作操作的都是栈区的数组

  1. 栈区的内容是允许修改的,如下:

*s1 = 'H';

printf("s1 = [%s]\n", s1);//Hello world

char s2[32] = "hello world";

  1. 栈区定义多个数组,即使保存一样的数据,数组的首地址也不一样

  1. 也可以使用指针直接指向字符串常量,这种写法指针变量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);//一样的

  1. 二级指针

二级指针是用来保存一级指针的地址的,多用于将一级指针的地址作为函数的参数传递时。

int a = 10; //变量

int *p = &a; //一级指针

int **q = &p; //二级指针

变量、一级指针、二级指针 关系图。

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;//错误的

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值