基础回顾-4 指针

指针也是一个变量,只不过它存储的值跟别的类型不同,它存放的是另外一个变量的内存直接地址。

变量的地址

访问变量的地址是通过 & 符和 %p来实现的。

int a = 5;
printf("%p \n",&a);         //%p 内存地址的输出符, & 访问地址运算符
复制代码
指针声明

指针声明一般形式是

类型 * 指针名;
复制代码

指针存放的是一个变量的地址,所以指针的类型跟它存放的变量类型相同。即

int *p1;            //一个整型指针
char *p2;           //一个字符型指针
float *p3;          //一个浮点型指针
复制代码
赋值

对指针赋值,与之前提到的取变量地址类似,在指针初始化和初始化之后赋值都是可以的

int a = 5;          //  &a = 0x7ffeefbff5ac

int *p;
p = &a;             //  p = 0x7ffeefbff5ac ,   

int *r = &a;        //  r = 0x7ffeefbff5ac
复制代码

上面的代码有写到,0x7ffeefbff5ac ,这是一个代表内存地址的十六进制数,所有的指针内容都是如此的格式,与指针类型无关。

空指针

初始化指针的时候,如果没有确切的地址可以赋值,我们可以使用 NULL 值对指针初始化,产生了一个 空指针

int *p = NULL;      //  此时 p = 0x0
复制代码
使用

使用指针的时候我们一般用 * 运算符,来返回指针指向的内存地址存放的变量的值,可能有点绕口,直接看代码

int a = 5;
int *p = &a;
printf("%p \n",p);          // 结果是: 0x7ffeefbff5ac
printf("%d \n",*p);         // 结果是:5
复制代码

这个地方要小心注意的是,不能看到 a*p 的值一样,就直接使用 *p,这个是 严重错误 的。

int a = 5;
int *p;
*p = 5;         // 不能直接用 *p 赋值
复制代码

因为 p 也是一个变量,这种使用方式就是在 p 都未初始化时,直接用 * 操作符,对 p 进行了访问,不能访问未初始化的指针(未初始化 不等于 空指针), 这是指针的特殊性(别的类型如intfloat等,不初始化时系统会默认给初始化,指针不会)。

指针与数组

将数组对指针赋值时,也是 类型 * 指针名 的形式。

int a[10];
int *p = a;
复制代码

如上图的将一个 int 数组赋值给 int *p。使用数组时,单独的数组名相当于数组第一个元素的地址,即&a[0]

指针的偏移

指针的偏移指的是指针存储的地址的偏移,单位是指针类型的字节长度。
int *p ,指针 p 存储的地址,偏移单位为一个int长度的字节。
char *p ,指针 p 存储的地址,偏移单位为一个char长度的字节。
...

偏移和数组
int a[5] = {1,2,3,4,5};
int *p = a;
printf("%p ---  %d \n",p,*p);      // 0x7ffeefbff5a0 ---  1 
p++;
printf("%p ---  %d \n",p,*p);      // 0x7ffeefbff5a4 ---  2
//一个 int 占4个字节

char b[3] = {'A','c','n'};
char *q = b;
printf("%p ---  %c \n",q,*q);      // 0x7ffeefbff5ad ---  A 
q++;
printf("%p ---  %c \n",q,*q);      // 0x7ffeefbff5ae ---  c 
//一个 char 占1个字节
复制代码

地址偏移取值,常常用的是 *(p + n) ,也可以用 p[n] ,用数组来类比

int a[5] = {1,2,3,4,5};
int *p = a;
//取第2个元素,就是首元素地址偏移一个单位之后的地址所指向的值
// a[1]  ->  *(&a[0] + 1)  ->  *(a + 1) ->  *(p + 1)  ->  p[1] 
a[1] == p[1]    //两者值相等
复制代码

通过上述演变,发现数组跟指针很类似,但是它们不是同一东西,因为 数组是地址常量,指针是地址变量 。 如上面的 int a[5]a 就是一个地址常量,它就是数组的首地址,它不可变。 而 int *p ,p 是一个地址变量,它的值可以随意修改。

常量指针 和 指针常量

常量指针,是指针指向的内容是常量,指针指向的内容不可通过指针修改(* p = 4,错误的),指针的指向可以修改。形式一般为

const int *p;
int const *q;
复制代码

使用方面

int a = 3;
const int *p = &a;
*p = 4 ;            //通过指针修改值,错误的
a = 4;              //变量自行修改,合法的

int b = 4;
p = &b;             //指针的指向可以修改,
复制代码

指针常量,是指针本身是一个常量,指向的地址不可修改,但是指针指向内容可通过指针修改。

int * const p;
复制代码
int a = 3;
int b = 4;
int *const p = &a;
p = &b ;            //指针的指向不可修改,错误的
*p = 4 ;            //指针指向的内容可通过 *p 修改
复制代码

还有一种就是指向常量的常量指针,即 const int * const p 。不能修改指针指向,也不能通过指针修改指向的值。

int a = 5;
int b = 4;
const int * const p = &a;
*p = 6;         //不可通过 *p 修改指向内容的值
p = &b;         //不可修改指针指向
复制代码

但是可以通过另外一种方式,指向另外的一个指针

int a = 5;
int *b = &a;
const int * const p = b;
printf("%d \n",*p);     // 5
*b = 2;
printf("%d \n",*p);     // 2
复制代码
复杂类型

有些时候,会将其他类型和指针进行结和,形成新的类型,如

int *p[10];
int (*p)[10];
复制代码

p 离谁最近就是什么类型。按照符号右结合的原则,第一个 int *p[10] 应该是

                    // 用一个A来代替 p[10] ,p离 [10] 最近
int *p[10]  ->  int *(p[10])  ->  int *A
复制代码

这样可以得出,A 的类型(即 p[10] 的类型)是一个整型指针,p[10] 很明显这是一个数组声明的结构。所以 int *p[10] 是一个数组,数组元素是整型指针。

下面看看第二个 int (*p)[10]()优先级比较高,先将其看成一个整体

            //用一个A代替 *p ,p 离 * 最近
int (*p)[10]   ->   int A[10] 

//可以理解为
int A[10];
*p = A;
p = &A;
复制代码

A 的类型(即 *p 的类型)是一个数组, *p 这是一个指针访问方式,指向了 A( 一个整形数组)。则 int (*p)[10] 是一个指针,指向的是一个整型数组。
当然跟 整型数值指针 略有不同

int a[10];
int *p = a;             //p 是指向 数组首元素 a 地址的指针
int (*q)[10] = &a;      //q 是指向数组 a[10] 地址的指针

sizeof(*p);             //这是一个 int 的字节长,因为 *p 是一个 int 变量
sizeof(*q);             //这是一个 int[10] 的字节长,*q 是一个 int 数组
复制代码

可能 int (*p)[10] 这种类型,多用于二维数组传参定义形参的时候把。

还有其他复杂类型,比如

int **p;
复制代码

二级指针,指向另外一个整型指针。可以用来改变它指向的 整形指针的指向,如

int a,b;
int *p = &a;
int **q = &p;       //此时 *q == p
*q = &b;            //此时 *q == p == &b, 即 p 的指向变化成了 int b
复制代码

int *p(float)
复制代码

一个函数,参数为 float 类型,返回值为 指向整型的指针 int *

int (*p)(float)
复制代码

这是一个指针,指向的是一个函数,函数的参数为 float 类型,返回值为 int 类型。这个就是我们常数的 函数指针 。可以直接将方法名赋值给指针,函数名是函数的入口地址,与数组类似。

int func(float) { return 1;}
复制代码
int (*p)(float) ;  //或者  int (*p)(float) = func
p = func;
复制代码

练习一下,

int (*(*p[10])(int))(int);
复制代码

一步步拆解,还是右结合原则得到的 p[10] ,说明这是一个数组。

int (*(*p[10])(int))(int)   //然后将 `p[10]` 用一个符号代替,
        ⬇️⬇️⬇️
int (*(*A)(int))(int)       //结合 *A ,说明数组里面的元素是 某种指针
        ⬇️⬇️⬇️
int (*B(int))(int)          //B 替换 *A,结合 *B(int),说明指针指向的是一个函数,函数参数为 int 类型,返回值为另外一个指针
        ⬇️⬇️⬇️
int C(int)                  //C 替换 (*B(int)) ,得到了一个参数为 int ,返回值为 int 的函数。
复制代码

整理下来:这是一个数组,数组里面的元素是一种指针A,指向了一种参数为一个 int ,返回值为一个指针B的函数,指针B指向了一种参数为一个 int 类型,返回值也是一个 int 类型的函数。
它的大小的是多少?80个字节,因为这是一个元素为指针的长度为10的数组,而指针的大小占8个字节,不论指针指向何种类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值