8.1 指针变量

8.1.1 内存的概述

在32位平台,每一个进程拥有4G的空间。系统为内存的每一个字节分配一个32位的地址编号(虚拟地址)。这个编号称之为地址.

 无论什么类型的地址,都是存储单元的编号,在32位平台下都是4个字节,即任何类型的指针变量都是4个字节大小。

一个变量有4个字节,四个地址,那么指针变量保存的是哪一个地址呢?规定保存的是变量的首地址,如上图所示,num的大小为四个字节,&num是第一个字节的地址。 

 8.1.2 地址和指针变量的关系

地址就是内存的地址编号。指针变量的本质是变量,只是该变量保存的是内存的地址编号,而不是普通的数值。如下图所示:

8.1.3 指针变量的定义

1、定义步骤

  1. *修饰指针变量p
  2. 保存谁的地址,就先定义谁
  3. 从上往下整体替换

案例1

定义一个指针变量p 保存int num的地址:int *p
定义一个指针变量p 保存数组int arr[5]首地址: int(*p)[5]
定义一个指针变量p 保存函数入口地址: int fun(int,int);int(*p)(int,int)
定义一个指针变量p 保存结构体变量的地址: struct stu lucy;struct stu *p
定义一个指针变量p 保存指针变量int *q的地址: int **p

2、在32位平台任何类型的指针变量都是4字节

3、指针变量和普通变量建立关系

int num=10;
int *p;
p=#//普通变量和指针变量建立关系
cout<<*p//10

在定义的时候,*仅仅作为修饰p为指针变量,而在使用时,*p表示取所保存的地址编号所对应的空间的内容。

8.1.4 指针变量的初始化

指针变量在操作之前,必须指向合法的地址空间

1、指针变量如果不初始化立即操作,会出现段错误

int *p;
cout<<*p<<endl;

 2、指针变量如果没有指向合法的空间,建议初始化为NULL

int *p=NULL;//NULL是赋值给p int *p;p=NULL;

但是不要操作指向NULL的指针变量,因为NULL实质是地址为0的地址空间,而地址为0的内存是受严格保护的。

3、将指针变量初始化为合法的地址(变量的地址、动态申请的地址、函数入口的地址等等)

int num=10;
int *p=&num;//int *p;p=&num;
int data=10,*p=&data;//此时data为int类型,而p为int*类型
int* p,data;//p为int*类型,data为int类型
定义变量时是由基本类型修饰的,所以不要误以为p和data都是int*类型

8.1.5 指针变量的类型

1、指针变量自身的类型:将指针变量拖黑,剩下的类型就是指针变量自身的类型

int *P;p自身的类型为int *

指针变量自身的类型主要用于赋值语句的判断,以免出现类型不匹配的情况

int num=10;
int *p=&num;
//在使用中,num为int &num为int*  即对变量名取地址,整体类型加一个*
//p为int* *p为int   即对指针变量取*,整体类型减一个*
//在使用中&和*相遇,从左往右依次抵消

例如:*&p==p;

案例:int num=10,*p=&num,**q=&p;以下结果正确的是ABC

A:*p=10

B:*q=&num

C:p=&num

D:q=&num

2、指针变量指向的类型

将变量名和离它最近的一个*一起拖黑,剩下的类型就是指针变量指向的类型

int *p;p指向的类型为int

3、指针变量指向的类型决定了取值宽度

int num=0x01020304;
int *p=&num;
为啥*p==num==0x01020304?

若num为int,取0x01020304(int为4B)

若num为char,取0x04(char为1B)

4、指针变量的指向类型决定了+1的跨度

8.1.6 综合案例分析

int num=0x01020304;

案例1:取出0x0102的地址

short *p=(short *)&num;
*(p+1);

案例2:取出0x02的值

char *p=(char *num);
*(p+2);

案例3:取出0x0203的值

char *p=(char *)&num;
*(short *)(p+1);

8.1.7 *p等价于num

int num=10;
int *p=&num;
//p==num
//*p==*&num==num

8.1.8 指针变量的注意事项

1、void不能定义普通变量,因为系统无法为类型为void的变量开辟空间,不知道类型也就无法知道开辟多大的空间,如void num;是错误的

2、void *可以定义指针变量

void *p;//p自身的类型为void *,在32位平台任意类型的指针为4B
那么系统知道为p开辟4B空间,所以定义成功

此时p就是万能的一级指针变量,能保存任意一级指针的地址编号

int num=10;
void *p=&num;
short data=20;
p=&data;

万能指针一般用于函数的形参达到算法操作多种数据类型的目的

注意:不要对void *p的指针变量取*

int num=10;
void *p=&num;
*p;//error p指向的类型为void 无法确定p的取值宽度 所以不能取*p

对p取*之前先进性指针类型强转

int num=10;
void *p=&num;
cout<<*(int *)p<<endl;

3、指针变量未初始化不要取*

int *p;
*p;//error段错误

4、指针变量初始化为NULL不要取*

int *p=NULL;
*p;//error段错误

5、指针变量不要越界访问

char ch='a';
int *p=&ch;
*p;//error越界访问非法内存

p指向的类型应为char型,而此时定义为int型,故会发生越界

8.2 数组元素的指针

8.2.1 数组元素的指针概述

数组元素的指针变量,是用来保存数组元素的地址

int arr[5]={10,20,30,40,50}
int *p//定义一个指针变量,保存数组元素的地址
p=&arr[0];
p=arr;//arr作为地址第0个元素的地址arr=&arr[0]
p=&arr[3];

8.2.2 数组元素的指针变量和数组名(作为地址)等价

int arr[5]={10,20,30,40,50};
int n=sizeof(arr)/sizeof(arr[0]);
int *p=arr;//p=arr
//遍历整个数组
int i;
for(int i=0;i<n;i++)
{
//cout<<arr[i]<<" ";
//cout<<*(arr+i)<<" ";
cout<<*(p+i)<<" ";
}
cout<<endl;

8.2.3 在使用中[ ]就是*()的缩写

int arr[5]={10,20,30,40,50};
int n=sizeof(arr)/sizeof(arr[0]);
cout<<"arr[1]= "<<arr[1]<<endl;//20
cout<<"*(arr+1)= "<<*(arr+1)<<endl;//20
cout<<"*(arr+1)= "<<*(1+arr)<<endl;//20
cout<<"1[arr]= "<<1[arr]<<endl;//20
//[]是*()的缩写,[]左边的值放在+的左边 []右边的值放在+的右边 整体取*

为啥arr==&arr[0]?

&arr[0]==&*(arr+0)==arr+0==arr

案例1:p[-1]的值

int arr[5]={10,20,30,40,50};
int *p=arr+3;
//30

案例2:p[1]的值

int arr[5]={10,20,30,40,50};
int *p=arr+3;
//50

8.2.4 指向同一数组的元素的两个指针变量间的关系

8.3 字符串与指针

8.3.1 字符数组

char str1[128]="hello world";

str1是数组,开辟128字节存放字符串"hello world"

sizeof(str1)==128字节

8.3.2 字符串指针变量

char *str2="hello world";

sizeof(str2)==4字节或8字节

str2是指指针变量在栈区/全局区保存的是字符串常量"hello world"的首元素地址,而"hello world"在文字常量区

8.4 指针数组

指针数组:本质是数组,只是数组的每个元素为指针

8.4.1 数值的指针数组


 

int num1=10;
int num2=20;
int num3=30;
int num4=40;
int *arr[4]={&num1,&num2,&num3,&num4};
int n=sizeof(arr)/sizeod(arr[0]);
for(int i=0;i<n;i++)
{
cout<<*arr[i]<<" ";//10 20 30 40 
}

8.4.2 字符指针数组

char *arr[4]={"hehehehe","xixixixi","lalalala","hahahaha"};

8.4.3 二维字符数组

char *arr[1]={"hehehehe", "xixixixixi", "lalalalala", "hahahahaha"};
char arr[1][128]={"hehehehe", "xixixixixi", "lalalalala", "hahahahaha"};

arr1是指针数组,存放的是每个字符串的首元素的地址

arr2是二维字符数组,存放的是每个字符串

8.5 指针的指针

int num=10;
int *p=num;
int **q=&p;

n级指针可以保存n-1级指针变量的地址

8.6 数组指针

8.6.1 数组首元素地址和数组首地址

数组首元素地址:&arr[0]=arr  arr+1跳过一个元素

数组的首地址:&arr; &arr+1跳过整个数组

8.6.2 数组指针的本质

本质是指针变量,保存的是数组的首地址

int arr[5]={10,20,30,40,50};
int (*p)[5]=NULL;//数组指针
int (*p)[5]=&arr;//数组指针

8.6.3 数组指针的案例

int arr[5]={10,20,30,40,50};
int (*p)[5]=&arr;//数组指针
cout<<*((int *)(p+1)-2)<<endl;//40

p保存的是数组首地址,p+1跳一个数组,再将p+1强转为int *类型,(int *)(p+1)-2)后退两个元素,所以是倒数第二个元素即40

总结:

int *arr[5];//指针数组,本质是数组,每个元素为int *
int (*arr)[5];//数组指针,本质是指针变量,保存的是数组的首地址(数组的每个元素必须为int)

8.6.4 二维数组和数组指针的关系

1、

int arr[n];  int *p;
int arr[n][m];  int (*p)[m];
int arr[n][m][k];  int (*p)[m][k]
n维数组和n-1维的数组指针等价

8.7 多维数组的物理存储

不管几维数组再物理上都是一维存储,在逻辑上是多维的

8.8 指针与函数

8.8.1 指针变量作为函数的参数

如果想在函数内部修改外部变量的值,需要将外部变量的地址传递给函数。

案例:传地址

void setnum(int *p)//int *p=&num;
{
//*p==num
*p=100;
}
void test01()
{
int num=0;
setnum02(&num);//单向传递之传地址
cout<<"num="<<num<<endl;//修改成功
}

8.2.2 一维数组作为函数的参数

函数内部想操作(读或写)外部数组元素,将数组名传递给函数。

一维数组作为函数的形参会被优化成指针变量

//void outputIntarray(int arr[5],int n)
//优化成指针变量
void outputIntarray(int *arr,int n)
{
cout<<"内部sizeof(arr)="<<sizeof(arr)<<endl;//4B
for(int i=0;i<n;i++)
{
//cout<<*(arr+i)<<" ";
cout<<arr[i]<<" ";//推荐
}
void testo2()
{
int arr[5]={10,20,30,40,50};
int n=sizeof(arr)/sizeof(arr[0]);
cout<<"外部sizeof(arr)="<<sizeof(arr)<<endl;//20B
outputIntarray(arr,n);//10 20 30 40 50
}

8.3.3 二维数组作为函数的参数

函数内部想操作函数外部的二维数组,需要将二维数组名传递给函数

二维数组名作为函数的形参,会被优化为一维的数组指针

//void outputIntDoubleArray(int arr[3][4], int row, int col)
//二维数组 作为函数的形参 会被优化成 一维的数组指针
void outputIntDoubleArray(int (*arr)[4], int row, int col)
{
cout<<"内部sizeof(arr) = "<<sizeof(arr)<<endl;//4B
int i=0,j=0;
for(i=0;i<row;i++)
{
for(j=0;j<col;j++)
{
cout<<arr[i][j]<<" ";
}
cout<<endl;
}
}
void test03()
{
int arr[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
int row = sizeof(arr)/sizeof(arr[0]);
int col = sizeof(arr[0])/sizeof(arr[0][0]);
cout<<"外部sizeof(arr) = "<<sizeof(arr)<<endl;//48B
outputIntDoubleArray(arr, row, col);
}

8.8.4 函数的返回值为指针类型

将函数内部的合法地址通过返回值返回给函数外部使用。

注意:函数不要返回普通局部变量的地址,因为局部变量在被使用后就会被立即释放,结束后地址所指向的内存可能是空的或者已经被其它进程所使用

int *getAddr(void)
{
//int data=100;//不要返回局部变量的值
static int data=100;//静态全局变量
return &data;
}
void test()
{
int *p=NULL;
p=getAddr();
cout<<"*p="<<*p<<endl;//100
}

8.9 函数指针

8.9.1 函数指针的定义

函数名代表函数的入口地址;

函数指针:本质是一个指针变量,只是该变量保存的是函数的入口地址

//函数指针p只能保存有两int形参以及int返回值的函数入口地址
int (*p)(int,int)=NULL;
int myAdd(int x,int y)
{
return x+y;
}
void test05()
{
int (*p)(int x,int y) = NULL;
cout<<"sizeof(p) = "<<sizeof(p)<<endl;//4B
//函数指针 和 函数入口地址建立关系
p = myAdd;
//通过p调用myAdd函数(函数+(实参))
cout<<p(10,20)<<endl;//30
}

8.9.2 函数指针变量注意事项

函数指针变量不要+1 无意义

不要对函数指针变量取* 无意义

函数指针变量判断大小><无意义

函数指针变量可以赋值 p2=p1;让p2指向p1所指向的函数

函数指针变量可以判断相等p2==p1

8.9.3 函数指针变量使用typedef定义

int myAdd(int x,int y)
{
return x+y;
}
void test05()
{
//给函数指针类型取别名
typedef int (*FUN_TYPE)(int x,int y);
FUN_TYPE p=NULL;
cout<<"sizeof(p) = "<<sizeof(p)<<endl;//4B
//函数指针 和 函数入口地址建立关系
p = myAdd;
//通过p调用myAdd函数(函数+(实参))
cout<<p(10,20)<<endl;//30
}

8.9.4 函数指针作为函数的参数

目的:让算法功能多样化

案例:设计一个算法完成加减乘除

int myAdd(int x,int y)
{
return x+y;
}
int mySub(int x,int y)
{
return x-y;
}
int myMul(int x,int y)

{
return x*y;
}
int myDiv(int x,int y)
{
return x/y;
}
//设计算法 操作上面的函数
int myCalc(int x,int y, int (*func)(int,int) )
{
return func(x,y);
}
void test06()
{
cout<<myCalc(10,20, myAdd)<<endl;//30
cout<<myCalc(10,20, mySub)<<endl;//-10
cout<<myCalc(10,20, myMul)<<endl;//200
cout<<myCalc(10,20, myDiv)<<endl;//0
}

Logo

中德AI开发者社区由X.Lab发起,旨在促进中德AI技术交流与合作,汇聚开发者及学者,共同探索前沿AI应用与创新。加入我们,共享资源,共创未来!🚀

更多推荐