本篇会对C/C++中【常见指针相关知识】一直进行总结迭代,记得收藏吃灰不迷路,一起学习分享喔
请大家批评指正,一起学习呀~
- 一、指针基本知识
- 1.1 指针的定义
- 1.2 (*) 和( &) 运算符
- 1.3 如何声明指针变量
- 1.5 指针解决函数间通信问题
- 二、指针与数组的关系
- 2.1 指针与整数、指针与指针的加减运算
- 2.2 数组形参的写法
- 2.3 指针递增练习
- 2.4 指针数组和数组指针
- 2.4.1 指针数组定义
- 2.4.2 指针数组示例
- 2.4.3 指针数组小tips:一个for循环便利二维数组
- 2.4.4 数组指针定义
- 2.4.5 数组指针示例
- 2.4.6 数组指针小tips:利用数组指针去打印二维数组
- 2.4.7 数组的传参使用
- 2.5 结构指针
- 2.6 指针的地址、指针的值、指针所指向内容的值
- 2.7 void类型的指针
- 2.8 函数指针
- 三、二级指针(较难喔,坚持住咯)
- 3.1 二级指针的概念
- 3.2 二级指针的示例
- 3.3 二级指针的用途
- 四、多级指针(万变不离其宗)
- 五、指针和多维数组
- 六、指针和字符串的关系
一、指针基本知识
1.1 指针的定义
指针是一个值为内存地址的变量(或数据对象)。首先指针是一个变量,它的值就是某个变量的内存地址,就好比 int 类型的变量它的值是整数也是一样的。
从一个例子来着手叭:
#include <stdio.h>
#include <stdlib.h>
int main(void){
int room = 2;
//定义两个指针变量指向room
int *p1 = &room;
int *p2 = &room;
printf("room 地址:0x%p\n", &room);
printf("p1 地址:0x%p\n", &p1);
printf("p2 地址:0x%p\n", &p2);
printf("room 所占字节:%d\n", izeof(room));
printf("p1 所占字节:%d\n", sizeof(p1));
printf("p2 所占字节:%d\n", sizeof(p2));
system("pause");
return 0;
示例中定义了一个int类型的变量room,定义了两个指向int类型的指针p1和p2指向room。此时room的地址(&
是取地址运算符)printf("room 地址:0x%p\n", &room);
就是指针p1和p2的值,但p1和p2的地址又是两个不同的值,跟room的地址完全是两回事儿。
1.2 (*) 和( &) 运算符
假设有个指向 int 类型的指针ptr:int *ptr;
有个 int 类型的变量:int bat;
假设 ptr = & bat;
即 ptr 指向 bat。
此时使用间接运算符或者解引用运算符*
后面跟上指针ptr(*ptr
)就可以拿到指针指向内存地址上存储的值。上式组合可得:
ptr = & bat;
ptr指向bat,即bat的地址赋给了ptr
val = *ptr;
val指向ptr的值,即ptr指向地址上的值赋给了val。
(* ptr是指针ptr指向地址上的值,即p的值是一个内存地址是一个变量,它指向的值是* p,是内存地址上存储的值)
===> val = bat;
——————————————————————————————————————
示例:
nurse = 2;
ptr = &nurse;
val = *ptr;
最终得到:
val = 2
;
——————————————————————————————————————
总结:
1、&
取地址运算符:后面跟一个变量名时,就可以拿到该变量的地址。&nurse
就可以拿到nurse的地址。
2、*
解引用运算符:后面跟一个指针或者地址的时候,可以拿到该地址上存储的值。
3、&
取地址运算符与*
解引用运算符从一定层面上看作是可以相互抵消的
1.3 如何声明指针变量
指针变量的声明必须指定指针指向变量的类型,因为不同类型的变量所占用的内存大小不同。
指针变量声明如下:
int *pi
;//pi是指向int类型变量的指针
char *pc
;//pc是指向char类型变量的指针
需要注意:
32 位系统中,int 整数占4 个字节,指针同样占4 个字节
64 位系统中,int 整数占4 个字节,指针同样占8 个字节
但这不是个例啦,
在 32 位平台下,无论指针的类型是什么,sizeof(指针名)都是 4.
在 64 位平台下,无论指针的类型是什么,sizeof(指针名)都是 8.
#include <stdio.h>
#include <stdlib.h>
int main(void){
int room = 2 ;
int room1 = 3 ;//3p
int *p1 = &room;
int *p2 = p1;//int *p2 = &room;
//1.访问(读、写)指针变量本身的值,和其它普通变量的访问方式相同
int *p3 = p1;
printf("room 的地址: %d\n", &room);//6222525
printf("p1 的值: %d p2 的值: %d\n", p1, p2);//p1 = 6222525,p2 = 6222525
printf("p3 的值: %d\n", p3);//p3 = 6222525
p3 = &room1;
printf("p3 的值: %d, room1 的地址:%d\n", p3, &room1);//不建议用这
种方式
//使用16 进制打印,把地址值当成一个无符号数来处理
printf("p1=0x%p\n", p1);
printf("p1=0x%x\n", p1);
printf("p1=0x%X\n", p1);
system("pause");
return 0;
}
示例中我们在对指针的值打印的时候一般采用十六进制的%p
(ANSIC专门为指针提供的转换说明%p),或者直接采用十六进制0x%x、0x%X
打印
1.5 指针解决函数间通信问题
这也是使用指针的主要原因之一,因为被调函数没有办法修改函数的实参,所以只能采用指针的形式将参数的地址传过去,即被调函数中改变主调函数的变量(被调函数中一班不会改变主调函数的值,如果需要的话就要使用指针作为参数)。
首先看一个程序示例:需要一个函数交换x和y的值,就需要将x和y的地址作为参数传递给被调函数。
#include <stdio.h>
#include <stdlib.h>
void interchange(int *u,int *v);
int main(void){
int x = 5,y = 10;
printf("Originally x = %d and y = %d\n",x,y);
interchange(&x,&y);
printf("Now x = %d and y = %d\n",x,y);
system("pause");
return 0;
}
void interchange(int *u,int *v){
int temp;
temp = *u;
*u = *v;
*v = temp;
}
//output
Originally x = 5 and y = 10
Originally x = 10 and y = 5
如此,便能理解为什么输入函数scanf("%d",&num)
了,因为它是将读取的值存储到指定的地址上。
二、指针与数组的关系
使用指针的程序会更有效率,尤其在处理数组的时候,而且数组跟指针的关系十分密切,数组名就是数组首元素的地址,假如arr是一个数组,则下式成立:
arr = &arr[0]
arr
和 &arr[0]
都是数组首元素的地址(首元素内存地址)
arr
是数组首元素地址,&arr
整个数组的首地址,地址是一样的,只是意义不同。
dates
是数组首元素的地址,dates + index
是数组第index
个元素dates[index]
的地址,*(dates + index)
是该元素的值。
数组的地址就是数组首元素的地址
2.1 指针与整数、指针与指针的加减运算
C中指针加1指的是增加一个存储单元,对于数组来讲就是意味着加1后的地址是下一个元素的地址,而不是下一个字节的地址。因为在数组中每个元素所占用的字节大小是固定的,所以指针加1,指针的值递增它所指向类型的大小,比如指向的是int类型的数组,那么指针加1,指针的值直接递增4个字节,也就是指向下一个元素了。以下面short类型数组举例。
pti是short类型的指针,所以每次指针加1,pti的值就会递增2个字节,2个字节刚好是一个数组元素的大小,所以指针加1,指针的指向就往下走一步。
总结:
1、 指针加一个数之后与数组之间的关系
dates = &dates[0];
dates + 2 = &dates[2];
//相同的地址
*(dates + 2) = dates[2];
//相同的值
模板:
dates + n = &dates[n];
//相同的地址
*(dates + n) = dates[n];
//相同的值
*(dates + n)
与 dates + n
的区别:
*(dates + n)
:表示dates的第n个元素的值
dates + n
:表示dates的第1个元素的值+n
2、指针与整数的运算,指针加减数字表示的意义是指针在数组中位置的移动;对于整数部分而言,它代表的是一个元素,对于不同的数据类型,其数组的元素占用的字节是不一样的,
比如指针+ 1,并不是在指针地址的基础之上加1 个地址,而是在这个指针地址的基础上加1 个元素占用的字节数:
- 如果指针的类型是char*,那么这个时候1 代表1 个字节地址
- 如果指针的类型是int*,那么这个时候1 代表4 个字节地址
- 如果指针的类型是float*,那么这个时候1 代表4 个字节地址
- 如果指针的类型是double*,那么这个时候1 代表8 个字节地址
3、通用公式:
数据类型 *p
;
p + n 实际指向的地址:p 基地址+ n * sizeof(数据类型)
p - n 实际指向的地址:p 基地址- n * sizeof(数据类型)
比如:
1)对于int
类型,比如p 指向0x0061FF14,则:
p+1 实际指向的是0x0061FF18,与p 指向的内存地址相差4 个字节;
p+2 实际指向的是0x0061FF1C,与p 指向的内存地址相差8 个字节
2)对于char
类型,比如p 指向0x0061FF28,则:
p+1 实际指向的是0x0061FF29,与p 指向的内存地址相差1 个字节;
p+1 实际指向的是0x0061FF2A,与p 指向的内存地址相差2 个字节;
4、指针与指针之间的加减运算
我们先总结再看示例,知识点:
(1)指针和指针可以做减法操作,但不适合做加法运算;
(2)指针和指针做减法适用的场合:两个指针都指向同一个数组,相减结果为两个指针之
间的元素数目,而不是两个指针之间相差的字节数。
比如:
int int_array[4] = {12, 34, 56, 78}
;
int *p_int1 = &int_array[0]
;
int *p_int2 = &int_array[3]
;
p_int2 - p_int1
的结果为3,即是两个指针之间的元素数目为3 个。
如果两个指针不是指向同一个数组,它们相减就没有意义。
3)不同类型的指针不允许相减
比如:
char *p1
;
int *p2
;
p2-p1
是没有意义的。
2.2 数组形参的写法
在函数原型和函数定义中,可以使用 int arr[] 代替 int * arr
int arr[] 和 int * arr 两种形式都表示arr是一个指向 int 类型的指针。如下图所示中列举了很多诸多相似的写法,一起学习一下。
接下来分别演示用数组表示法和指针法进行数组求和运算。数组表示法就是将整个数组作为参数传递,指针表示法就是将数组名也就是首元素地址或者说是指针作为参数传递。
1、首先用数组表示法进行求和:
int main(void){
int marble[10] = {……};//省略不写啦
int answer = sum(marble,10);
printf("%ld",answer);
}
int sum(int arr[],int N){
int total = 0;
for(int i = 0;i < 10;i++){
total += arr[i];
}
return total;
}
2、首先用指针表示法进行求和:
int main(void){
int marble[10] = {……};//省略不写啦
int answer = sum(marble,marble + 10);
printf("%ld",answer);
}
int sum(int *start,int *end){
int total = 0;
while(start < end){
total += *start;
start++;
}
return total;
}
start++
递增指针变量start
是指向数组的下一个元素,因为start
是指向int
的指针,所以start+1
相当于其值递增int
类型的大小。
2.3 指针递增练习
1、total = *start ++
;total = *++start
;这两个语句是非常常见且非常重要的。(++
和 *
的优先级相同)
2、total = *start ++
;表示:先将指针指向位置上的值赋给total,然后在递增指针
3、total = *++start
;表示:先递增指针,再将指针指向的值赋给total
经典练习:
#include <stdio.h>
int data[2] = {100,200};
int moredata[2] = {300,400};
int main(void){
int * p1,*p2,*p3;
p1 = p2 = data;
p3 = moredata;
printf("*p1 = %d,*p2 = %d, *p3 = %d\n",*p1,*p2,*p3);
printf("*p1++ = %d,*++p2 = %d, (*p3)++ = %d\n",*p1++,*++p2,(*p3)++);
printf("*p1 = %d,*p2 = %d, *p3 = %d\n",*p1,*p2,*p3);
}
输出:
第一行输出应该是没有问题的,因为:
p1 = p2 = data;
p3 = moredata;
所以:
p1 = p2 = data = &data[0]
;->*p1 = *p2 = *data = *(&data[0]) = &data[0] = 100
;
p3 = moredata;
->*p3 = *moredata = *(&moredata[0]) = moredata[0] = 300
;
第二行输出:
*p1++
:是先打印p1所指地址上的值,在进行递增。所以结果为:p1所指地址上的值100,但展示结果之后p1指针便已自增,指向data[1]上
*++p2
:先进行指针递增,p2指向data[1]上,在输出指针指向位置上的值200
(*p3)++
:先将p3指向位置的值输出300,在进行值的递增
因为来到第三行:
*p1 = 200,*p2 = 200,*p3 = 301
注意:
1、*p1++
和(*p1)++
是不同滴,虽然++
和 *
的优先级相同,但是结合律有顺序,是从右向左的,所以*p1++
是先执行p1++
再执行*p1
的。total = *p1++
先将p1指向的值赋给total,但还是先进行p1++
,因为根据自增运算规律可知,p1++之后并不会立刻改变值,而是先使用再递增嘛,所以它跟(*p1)++
不同,前者++
的时候是指针递增,而后者递增的时候是值的递增。
2、P++
(P是指针)的概念是在P当前地址的基础上,自增P对应类型的大小,即P = P+1*(sizeof(类型))
2.4 指针数组和数组指针
想要理解指针数组和数组指针的区别,很简单。来看两个小练习就明白辽。
根据题目要求,正确的声明变量:
2.4.1 指针数组定义
A:psa是一个内含20个元素的数组,每个元素都是指向int的指针。
这种我们将其称为指针数组,即装着指针的数组:int *(p1[20])
;
定义:类型 * (指针数组名[元素个数])
其中p1是数组名称
如下图所示:int * (p1[5])
,数组p1内含5个int类型的指针
2.4.2 指针数组示例
指针数组的示例:指针数组可以被用来记录最高点和次高点的地址,最后通过解应用可以拿到最高与次高点的值
#include <stdio.h>
#include <stdlib.h>
int main(void){
//二维矩阵中找到身高最高和次高的值
int girls[4][3] = {
{173, 158, 166},
{168, 155, 171},
{163, 164, 165},
{163, 164, 172} };
//定义一个有两个元素的指针数组,每个元素都是一个指针变量
//定义两个指针放进指针数组中,让他们分别指向最高和次高的地址
//qishou[0]中最高的地址,qishou[1]中次高的地址
//同理如果要找出前十个身高最高的,直接将2换成10即可
int* (qishou[2]);
//base case 先通过前两个身高分别定出最高和次高放入指针数组中
if (girls[0][0] > girls[0][1]) {
qishou[0] = &girls[0][0];//最高
qishou[1] = &girls[0][1]; //次高
}
else {
qishou[0] = &girls[0][1]; //最高
qishou[1] = &girls[0][0]; //次高
}
//让其他元素与最高和次高进行比较,总共12个元素
for (int i = 2; i < 12; i++) {
//girls[i/3][i%3] 非常巧妙的一个循环就可以遍历二维数组的方法
//如果新身高小于等于此身高就直接略过
if(*qishou[1] >= girls[i/3][i%3]){
continue;
}
//候选者高于次高
//1.候选者比"冠军"矮
if(girls[i/3][i%3] <= *qishou[0]){
qishou[1] = &girls[i/3][i%3];
}else {
//2.候选者比"冠军"高
qishou[1] = qishou[0];
qishou[0] = &girls[i/3][i%3];
}
}
printf("最高的身高: %d , 次高的身高: %d\n", *qishou[0], *qishou[1]);
system("pause");
return 0;
}
2.4.3 指针数组小tips:一个for循环便利二维数组
tips:插播一个小知识点:一个for循环便利二维数组
2.4.4 数组指针定义
B:p1是一个指向数组的指针,数组内含有20个char类型的值。
这种我们将其称为数组指针,即指向数组的指针:int (*p1)[20]
;
定义:类型 (*数组指针名)[元素个数]
其中p1是指针名称
如下图所示:int (*p2)[5]
,数组指针p2指向内含5个int类型数据的数组
2.4.5 数组指针示例
数组指针的示例:
使用数组指针访问数组的两种方式:
数组法: (*p)[j]
指针法: *((*p)+j)
假设有题目:据同学们报告,A 栋学生楼有学生用高倍望眼镜偷看别人洗澡,宿管办领导决定逐个宿舍排查,得到的线报是A0 到A3 宿舍的某个子最矮的男生:以二维数组表示宿舍,某宿舍个子最小也就是说二位数组中的最小值
下面分别使用数组下标法和指针法来解决
#include <stdio.h>
#include <stdlib.h>
int main()
{
//四个宿舍分别是A0~A3
int A[4][3]={{173, 158, 166},
{168, 155, 171},
{163, 164, 165},
{163, 164, 172}};
//定义一个指向三个成员的数组的指针
//用来指向二维数组中的某一行
int (*p)[3];
//定义一个空指针,最后指向最爱身高的地址
//没空指针没有指向任何地址
int * boy = NULL;
//p指向第一个数组,A[0]恰好有三个int类型的数据
p = &A[0];
//第一种数组下标法
//四个宿舍
/*for(int i=0; i<4; i++){
//每个宿舍三个成员
for(int j=0; j<3; j++){
//(*p) 等同于A[0],A[0][0]等同于(*p)[0]
printf(" %d", (*p)[j]);
}
printf("\n");
p++; //p++ = &A[1]
}*/
//将新定义的指针首先指向第一个元素的位置,假定它是最小的身高
boy = &(*p)[0];//(*p)[0] = A[0][0]
//boy = (*p);
//第二种指针访问法
//数组成员: *p *(p+1) *(p+2)
//p = a,*p = a[0]![请添加图片描述](https://img-blog.csdnimg.cn/2dab615237474fa9a873eda2a0ca5a39.png)
for(int i=0; i<4; i++){
for(int j=0; j<3; j++){
//打印二位数组的方法
printf(" %d", *((*p)+j));
//找出最矮个字的数字
//后面的数字如果大于首元素
if( *boy > *((*p)+j)){
//将后面元素的地赋值给最小身高的地址
boy = (*p)+j;
}
}
printf("\n");
p++;
}
printf("偷窥的学生是: %d\n", *boy);
system("pause");
return 0;
}
2.4.6 数组指针小tips:利用数组指针去打印二维数组
tips:插播一个小知识点:利用数组指针去打印二维数组
int (*p)[3]
;
p = &A[0]
;
*p = A[0]
;
要是:
p++
;
则:
*p = A[1]
;
(*p)[0] = A[1][0]
;
(*p)[1] = A[1][1]
;
第二种方法:
数组指针的定义:int(*p)[3]
;
p = &A[0]
->*p = A[0]
->(*p)+j = A[0][j] 的地址
->*((*p)+j ) = A[0][j]的值
2.4.7 数组的传参使用
数组传参时,会退化为指针!,所以被调函数中可以用指针来接收
(1)退化的意义:C 语言只会以值拷贝的方式传递参数,参数传递时,如果只拷贝整个数
组,效率会大大降低,并且在参数位于栈上,太大的数组拷贝将会导致栈溢出。
(2)因此,C 语言将数组的传参进行了退化。将整个数组拷贝一份传入函数时,将数组名看做常量指针,传数组首元素的地址。
一维数组传参退化为一级指针:
被调函数中形参为:int * arr
;
二维数组传参退化为二级指针:
被调函数中形参为:int ** arr
;
栗子就不举啦!!!
2.5 结构指针
结构指针请参考这边喔~C语言复合类型之结构(struct)篇(结构指针)
2.6 指针的地址、指针的值、指针所指向内容的值
图中可以总结出:
1、p的值就是i的地址&i
2、*p就是指针所指向内存单元的数据,即指针所指向内存地址上存储的值
3、&p指指针本身的地址
4、&*p的值跟p是一样的,都是i的地址&i
2.7 void类型的指针
void是一个空类型,作为函数返回值值类型(即不要求返回值)。
void*
=> 空类型指针,只存储地址的值,丢失类型,无法访问,要访问其值,我们必须对这个指
针做出正确的类型转换,然后再间接引用指针。
1、所有其它类型的指针都可以隐式自动转换成void 类型指针,反之需要强制转换
2、而且void类型指针不允许++这种算数运算
3、void类型指针是很常见滴嗯哼
2.8 函数指针
首先让我们思考一下,函数是否有地址?写个小代码试试看叭~
由上图可知函数确实存在地址,那什么是函数指针呢?
指向数组的指针称为:数组指针
指向函数的指针称为:函数指针
指向函数的指针中存储着函数代码的起始处的地址
函数指针的定义:函数返回值类型 (*指针名)(函数参数列表类型)
例如:
原本函数:int compare_char(const void *a, const void *b)
;
int (*fp)(const void *, const void *)
;
因此: (*fp)
是一个参数列表为(const void *, const void *)
、返回值类型为int
的函数
其实很简单,就是将函数名 compare_char
替换成 (*fp)
即可
需要注意:int (*fp)(const void *, const void *)
中的()不能少,因为运算优先级的问题。
小总结一下:如何声明函数指针:
将函数原型中函数名直接换成(*p)即可,p是自定义的指针名
三、二级指针(较难喔,坚持住咯)
只要理解了上面指针的一些性质,相信我们一定可以顺水推舟推敲出二级指针是个什么玩意儿哈哈哈。
3.1 二级指针的概念
二级指针也是一个普通的指针,只是二级指针的值是一级指针的地址罢了。
一级指针:指向一个普通变量,并保存该普通变量的地址;
二级指针:指向一个一级指针,并保存该一级指针的地址;
int guizi1 = 888
;
int *guizi2 = &guizi1
; //1 级指针,保存guizi1 的地址
int **liujian = &guizi2
; //2 级指针,保存guizi2 的地址,guizi2 本身是一个一级指针变量
3.2 二级指针的示例
刘建手里持有第一个柜子的地址
第一个柜子里面是第二个柜子的地址
第二个柜子里面是第三个柜子的地址
第三个柜子里面放着888
#include <stdio.h>
#include <stdlib.h>
int main(void){
int guizi2 = 888; //存枪的第3个柜子
int *guizi1 = &guizi2; //存第3 个柜子地址的第2个柜子
int **liujian = &guizi1; //手握第一个柜子地址的刘建
printf("刘建打开第一个柜子,获得第二个柜子的地址:0x%p\n", *liujian);
printf("guizi2 的地址:0x%p\n", &guizi2);
int *tmp;
tmp = *liujian;
printf("访问第二个柜子的地址,拿到枪:%d\n", *tmp);
printf("刘建一步到位拿到枪:%d\n", **liujian); //缩写成**liujian
system("pause");
return 0;
}
结果分析:
int guizi2 = 888
; //存枪的第3 个柜子
int *guizi1 = &guizi2
; //存第3 个柜子地址的第2个柜子
int **liujian = &guizi1
; //手握第一个柜子地址的刘建
二级指针的解引用【重点知识】:
1、二级指针liujian
的一次解引用 **liujian
就可以得到被指向指针的地址;
2、二级指针进行两次解引用就可以得到所指的一级指针所指地址的值;
第一次解应用:*liujian = guizi1 = &guizi2
第二次解引用:![请添加图片描述](https://img-blog.csdnimg.cn/b067977535c74fe7894305bfe0d43440.png)
则:
**liujian
一步到位的取法可以直接得到二级指针所指以及指针地址上的值。
二级指针的解引用非常重要,需要牢记!!
3.3 二级指针的用途
- ==一级指针可以将主调函数中的变量通过参数“带入”被调函数内部进行修改,但没办法将被调函数中的内部变量“带出”到主调函数中
我们知道使用指针可以在被调函数中修改主调函数中变量的值,如下:
但是想要把被调函数中的内部变量“带出”到主调函数中需要用到双指针,如下所示,被调函数 boy_home中的内部变量boy拿到主调函数中进行打印
其中*meipo = &boy;
表示将二级指针的值,即一级指针的来存储boy的地址
最后通过printf("boy: %d\n", *meipo);
中的*meipo
拿到一级指针地址上的值23
2. 二级指针可以不但可以将变量通过参数”带入“函数内部,也可以将函数内部变量“带出”到函数外部。
四、多级指针(万变不离其宗)
工作中二级指针时有见到,多级指针其实已经很少啦
声明办法如下:
int guizi1 = 888
;
int *guizi2 = &guizi1
; //普通指针
int **guizi3 = &guizi2
; //二级指向一级
int ***guizi4 = &guizi3
; //三级指向二级
int ****guizi5 = &guizi4
; //四级指向三级
……
五、指针和多维数组
前面我们在打印二维数组的时候,已经体现出了指针和二维数组的关系啦,在这儿我们重新总结记录一下
p302
六、指针和字符串的关系
指针在字符串定义时的作用
指针的地址和指针的值,两者的区别,用p328页处的的例题来进行说明