【C语言_17】深入学习指针!详解篇!

时间:2022-10-09 16:02:51

初步了解指针:​​【C语言_16】初步了解指针!​

一.指针运算

1.指针类型与整型加减

//错误示范
#include<stdio.h>
int main()
{
char* pc=0;
short* ps=0;
int* pi=0;
long* pl=0;
long long* pll=0;
float* pf=0;
double* pd=0;

pc=100;
ps=100;
pi=100;
pl=100;
pll=100;
pf=100;
pd=100;

pc=pc+1;
ps=ps+1;
pi=pi+1;
pl=pl+1;
pll=pll+1;
pf=pf+1;
pd=pd+1;

printf("pc=%u\n",pc);
printf("ps=%u\n",ps);
printf("pi=%u\n",pi);
printf("pl=%u\n",pl);
printf("pll=%u\n",pll);
printf("pf=%u\n",pf);
printf("pd=%u\n",pd);
return 0;
}

【C语言_17】深入学习指针!详解篇!

如何修改呢?

这时候就需要用强制类型转换:

//修改后
#include<stdio.h>
int main()
{
char* pc=0;
short* ps=0;
int* pi=0;
long* pl=0;
long long* pll=0;
float* pf=0;
double* pd=0;

pc=(char*)100;
ps=(short*)100;
pi=(int*)100;
pl=(long*)100;
pll=(long long*)100;
pf=(float*)100;
pd=(double*)100;

pc=pc+1;
ps=ps+1;
pi=pi+1;
pl=pl+1;
pll=pll+1;
pf=pf+1;
pd=pd+1;

printf("pc=%u\n",pc);
printf("ps=%u\n",ps);
printf("pi=%u\n",pi);
printf("pl=%u\n",pl);
printf("pll=%u\n",pll);
printf("pf=%u\n",pf);
printf("pd=%u\n",pd);
return 0;
}

运行结果:

【C语言_17】深入学习指针!详解篇!

可以看出:指针类型+1后,将首地址向后移动了sizeof(目标数据对象)个字节


  1. sizeof(目标数据对象)称作步长
  2. 指针类型+n后,其首地址向后移动n*步长个字节。
  3. 指针类型-n后,其首地址向前移动n*步长个字节。

2.同类型的指针进行减法运算

#include<stdio.h>
int main()
{
int arr[10];
printf("&arr[0]=%u\n",&arr[0]);
printf("&arr[5]=%u\n",&arr[5]);
printf("&arr[5]-&arr[0]=%u",&arr[5]-&arr[0]);
return 0;
}

运行结果:

【C语言_17】深入学习指针!详解篇!

&arr[5]-&arr[0]=5240956-5240936=20
(运行结果受到了步长sizeof(int)的影响)
20/sizeof(int)=5
指针类型相减后,结果为两个首地址差值除以步长。

除了这两种指针运算的结果在内存中有着实际意义,其他指针运算没有实际意义,在这不做讨论。


二.指针与数组

1.怎样使用指针访问数组?

  • 数组元素在内存中是连续

#include<stdio.h>
int main()
{
int arr[5]={1,2,3,4,5};
int* p=&arr[0];//指针获取数组的首地址
printf("%d ",*p);
printf("%d ",*(p+1));
printf("%d ",*(p+2));
printf("%d ",*(p+3));
printf("%d ",*(p+4));
return 0;
}

运行结果:

【C语言_17】深入学习指针!详解篇!

2.用数组名获取数组首地址

#include<stdio.h>
int main()
{
int arr[5]={1,2,3,4,5};
printf("%u\n",arr);//arr的值是首地址
printf("%u\n",&arr[0]);
return 0;
}

运行结果:

【C语言_17】深入学习指针!详解篇!

为什么数组名的值是数组的首地址呢?数组名是int类型的指针吗?

3.数组名是int类型的指针吗?

我们通过下面这个代码来检验我们的猜想:

#include<stdio.h>
int main()
{
int arr[5]={1,2,3,4,5};
int* p=arr;//指针获取数组的首地址
printf("%d ",*p);
printf("%d ",*(p+1));
printf("%d ",*(p+2));
printf("%d ",*(p+3));
printf("%d ",*(p+4));
return 0;
}

运行结果:

【C语言_17】深入学习指针!详解篇!

看到这大家可能会觉得数组名是一个int类型的指针,让我们继续往下看:

#include<stdio.h>
int main()
{
int arr[5]={1,2,3,4,5};
int* p=arr;
printf("sizeof arr:%d\n",sizeof(arr));
printf("sizeof p:",sizeof(p));
printf("sizeof arr+1:",sizeof(arr+1));
return 0;
}

运行结果:

【C语言_17】深入学习指针!详解篇!

我们发现指针p的字节是4,而arr字节大小为20,并不是指针类型。

那为什么arr+1后,输出的字节为4了呢?

因为arr在表达式中,会被转换为指针类型。

当数组名出现在表达式中,数组名将会转换为指向数组第一个元素的指针。

4.指针访问数组

*(数组名+偏移量)
//偏移量:指针指向地址与首地址相差几个元素
*(arr+1)等价于arr[1]
#include<stdio.h>
int main()
{
int arr[5]={1,2,3,4,5};
printf("arr[2]=%d\n",arr[2]);
printf("*(arr+2)=%d\n",*(arr+2));
printf("2[arr]=%d\n",2[arr]);
return 0;
}

运行结果:

【C语言_17】深入学习指针!详解篇!

[]下标运算符:下标运算符最终会展开为指针的形式。

arr[2]展开为*(arr+2)

2[arr]展开为*(arr+2)

三.指针如何作为参数进行传递?

还记得我们在学习函数时讨论过的实参和形参相互独立吗?

#include<stdio.h>
void swap(int x,int y){
int m;
m=x;
x=y;
y=m;
}
int main(){
int a,b,m;
a=1;
b=2;
printf("a=%d b=%d\n",a,b);
swap(a,b);
printf("a=%d b=%d\n",a,b);
return 0;
}

【C语言_17】深入学习指针!详解篇!

swap(a,b)只是将a,b的值赋给了swap函数中的x,y变量。swap函数在内部进行处理,处理完将结果返给主函数,不会影响主函数中变量a,b的值。


如果我们想让主函数中变量a,b的值也发生改变,怎么做呢?

我们可以将变量a,b的首地址赋给swap函数中指针x,y。然后在swap函数中通过指针交换数据对象的值,这样变量a,b的值就会和swap函数中指针x,y的值,一起改变。

#include<stdio.h>
void swap(int* x,int* y){
int m=*x;
*x=*y;
*y=m;
}
int main(){
int a,b,m;
a=1;
b=2;
printf("a=%d b=%d\n",a,b);
swap(&a,&b);
printf("a=%d b=%d\n",a,b);
return 0;
}

运行结果:

【C语言_17】深入学习指针!详解篇!

四.void*类型指针

int n;
void* p=&n;
//丢弃指针类型,只保存首地址
//只有首地址因此无法取值*p(错)
//只有首地址,没有步长,不能进行加减p-1(错)
//任意类型的指针都可以直接赋值给它

五.多级指针与指针数组

1.用指针记录另一个指针的地址

int** p;
int**p;
int **p;
//都是对的
int* 数据对象的指针;//被称作二级指针
#include<stdio.h>
int main()
{
int n=666;
int* pn=&n;
int** ppn=&pn;
printf("&n=%u\n",&n);
printf("pn=%u\n",pn);
printf("*pn=%d\n",*pn);
printf("&pn=%u\n",&pn);
printf("ppn=%u\n",ppn);
printf("**ppn=%d\n",**ppn);
return 0;
}

运行结果:

【C语言_17】深入学习指针!详解篇!

2.声明指针数组

  • 是一个数组
元素类型* 数组名[元素个数];
int* parr[3];

#include<stdio.h>
int main()
{
int arr1[5]={1,2,3,4,5};
int arr2[5]={11,22,33,44,55};
int arr3[5]={111,222,333,444,555};
//定义指针数组parr[3]
int* parr[3];
parr[0]=arr1;
parr[1]=arr2;
parr[2]=arr3;
printf("&arr1=%u\n",arr1);
printf("&arr2=%u\n",arr2);
printf("&arr3=%u\n",arr3);
printf("parr[0]=%u\n",parr[0]);
printf("parr[1]=%u\n",parr[1]);
printf("parr[2]=%u\n",parr[2]);
}

运行结果:

【C语言_17】深入学习指针!详解篇!

3.应用指针数组

#include<stdio.h>
int main()
{
int arr1[5]={1,2,3,4,5};
int arr2[5]={11,22,33,44,55};
int arr3[5]={111,222,333,444,555};
//定义指针数组parr[3]
int* parr[3];
parr[0]=arr1;
parr[1]=arr2;
parr[2]=arr3;
for(int i=0;i<3;i++){
int** p=parr+i;
for(int j=0;j<5;j++){
printf("%d \n",*(*p+j));
}
printf("\n");
}
return 0;
}

运行结果:

【C语言_17】深入学习指针!详解篇!

4.从函数中返回指针

#include<stdio.h>
int* func(){
int n=99;
return &n;
}
int main()
{
int* p=func();
printf("%d",*p);
return 0;
}

运行结果:

【C语言_17】深入学习指针!详解篇!

看似正常,但是存在问题 。变量n在函数结束后,被系统回收,因此失效 ,再次访问可能会得到无意义的值。

如果不想变量n的值被回收可以怎么做?static int n=99;

5.从函数中返回多个变量

#include<stdio.h>
void func(int** a,int** b){
static int x=100;
static int y=200;
*a=&x;
*b=&y;
}
int main()
{
int* a=NULL;
int* b=NULL;
func(&a,&b);
if(a!=0&&b!=0)
printf("a=%d b=%d",*a,*b);
return 0;
}

【C语言_17】深入学习指针!详解篇!

NULL是一个符号常量

#define NULL 0;

六.指针与多维数组

1.声明数组指针

  • 是一个指针
//元素类型 数组名[元素个数];
int arr1[10];//在声明中去掉变量名和分号,就可以得到数据类型int[10]
//目标类型* 变量名;
int* parr1=&arr1;
int[10] arr2[5];//数组名左边的[]都要以都到最右边:int arr2[5][10];
int arr2[5][10];//声明数组arr2,它的元素类型为int[10],元素个数为5
//目标类型 (*变量名)[元素个数];
int (*parr2)[10]=arr2;
//为了和指针数组区分,所以加了括号

2.应用数组指针

#include<stdio.h>
int main()
{
int arr[3][5]={{1,1,1,1,1},
{2,2,2,2,2},
{3,3,3,3,3}};
int (*parr)[5]=arr;
printf("parr=%d\n",parr);
printf("parr+1=%d\n",parr+1);
printf("parr+2=%d\n",parr+2);
printf("sizeof arr=%d\n",sizeof(arr));
printf("sizeof parr=%d\n",sizeof(parr));
printf("sizeof *parr=%d\n",sizeof(*parr));
return 0;
}

运行结果:

【C语言_17】深入学习指针!详解篇!

我们可以看出arr的类型为int[3][5]数组。5*4*3=60。

parr的类型为int(*)[5]指针。

*parr的类型为int[5]数组。5*4=20。

#include<stdio.h>
int main()
{
int arr[3][5] = { {1,1,1,1,1},
{2,2,2,2,2},
{3,3,3,3,3} };
int(*parr)[5] = arr;//声明数组指针
printf("parr=%d\n", parr);
printf("parr+1=%d\n", parr + 1);
printf("parr+2=%d\n", parr + 2);

int* p = *parr;
printf("p=%d\n", p);
printf("p+1=%d\n", p + 1);
printf("p+2=%d\n", p + 2);
printf("p+3=%d\n", p + 3);
printf("p+4=%d\n", p + 4);
printf("p+5=%d\n", p + 5);
printf("p+6=%d\n", p + 6);
return 0;
}

运行结果:

【C语言_17】深入学习指针!详解篇!

可以看出:
parr是指向arr[0][i]首地址的指针。i=(0,1,2,3,4)
parr+1是指向arr[1][i]首地址的指针。i=(0,1,2,3,4)
parr+2是指向arr[2][i]首地址的指针。i=(0,1,2,3,4)
*parr是arr[0][i]数组的值{1,1,1,1,1}。i=(0,1,2,3,4)
*(parr+1)是arr[1][i]数组的值{2,2,2,2,2}。i=(0,1,2,3,4)
*(parr+2)是arr[1][i]数组的值{3,3,3,3,3}。i=(0,1,2,3,4)
p是指向arr[0][0]的指针
p+1是指向arr[0][1]的指针
p+2是指向arr[0][2]的指针
p+3是指向arr[0][3]的指针
p+4是指向arr[0][4]的指针
p+5是指向arr[1][0]的指针
p+6是指向arr[1][1]的指针

3.下标访问与指针访问

  • 数组名[下标]
  • *(数组名+偏移量)
  • 偏移量:指针指向的地址与数组首地址之间相差几个元素

int arr[4]等价于*(arr+4)

七.指针与三维数组示例

1.指针访问三维数组元素

#include<stdio.h>
int main()
{
int arr[2][3][5]={
{{1,1,1,1,1},
{2,2,2,2,2},
{3,3,3,3,3}},

{{11,11,11,11,11},
{22,22,22,22,22},
{33,33,33,33,33}}
};
printf("arr[1][2][3]=%d",*(*(*(arr+1)+2)+3));
return 0;
}

运行结果:

【C语言_17】深入学习指针!详解篇!


持续更新【C语言】系列!有需要的可以移步主页​​秃头程序媛主页​