C++-027-指针

C+±027-指针-2020-3-9

一、指针的概念

程序运行时是将数据存储在内存中
所谓地址,就是系统为了方便管理,将内存划分为一块一块,每一块都编上"门牌号",这个"门牌号"就是内存的地址。所有的数据都要按照系统给定的"门牌号"存入相应的位置,只要知道了数据的"门牌号",系统就可以由此找到该数据。例如,我们将字符串"All right”存入内存,

如图

地址 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011
内容 A l l r i g h t

可以看到,A字符存在地址2003的位置上;t字符存在地址2011的位置上…(一个半角西文字符占一个字节)。
所以只要找到地址为2003的位置,就可以找到字符"A"。
地址是永远固定不变的,就好像我们家门口的门牌号一样。
指针即告诉你地址的值,你可以根据这个值找到内存的相应位置。所以,指针的值为地址值,如2002等,指针的值均为整数,因为地址的值为整数。
如图

地址 2002 2004 2008 2008 2012 2016 2020 2024 2028 2032
内容

专门存放指针的变量叫指针变量,为了与其他普通变量区分开来,我们在变量名前加"*"号。

int *_point;//注意下划线"_"不属于规范写法,只是为了便于识别指针而已。

则系统开辟一个4字节的空间存放变量*_point,例如存放在2020,其中,*_point称为指针变量,它是一个指向int型的变量。_point是指针,它的值是地址,如2004。号表示取_point指向的地址上的具体内容。假设从地址2004起存放了整数100,则_point=100;
如图

地址 2000 2004 2008 2012 2016 2020 2024 2028 2032
内容 100 *_point

地址2004处存放了整形数据100
地址2020处存放了指针变量*_point
指针变量*_point是一个int型变量
指针_point的值2004
指针变量*_point的值为100
指针变量*_point的地址为2020

我们可以看出,指针变量*_point也是变量的一种,但它是一个特殊的变量,是存放另一个变量地址的变量,它的值即是指针_point指向地址上的存储值。
之所以要在指针变量前加一个数据类型,如int*_point;是要告诉系统,该指针变量指向的位置应该是一个int型,系统就会根据该数据类型所占的字节数返回相应的数据。
因为指针变量也是变量的一种,所以我们也可以将它当作普通变量来使用,

int *_point=3

该语句的作用与int i=3的作用是相同的。

二、指针的声明

声明指针的一般形式如下:

数据类型标识符 * 指针变量名;

例如

int *p_iPoint;//声明一个整型指针
float *a,*b;//声明两个浮点指针

三、指针的赋值

指针可以在声明的时候赋值,也可以后期赋值。
(1)在初始化时赋值

int i=100;
int *p_iPoint=&i;

(2)在后期赋值

int i=100;
p_iPoint=&i;

四、指针的使用

(1)指针变量名是p,而不是*p
p=&i的意思是取变量i的地址赋给指针变量p
例如

//输出变量的地址值
#Include<iostream>
using namespace std;
void main()
{
int a=100;//定义一个变量a
int * p=&a;//定义一个指针变量p并初始化
printf("%d\n",p)//按十进制输出a的地址
}

(2)指针变量不可以直接赋值,例如

int a=100;
int *p;
p=100;

编译不能通过,有errorC2440:’=’:cannot convert from ‘const int’ to’int *'错误提示。

int a=100;
int *p;
p=(int *)100;//通过强制转换将100赋值给指针变量
printf("%d",p);//输出地址,能够输出地址
printf("%d",p);//输出指针指向的值,出错语句

(3)不能将*p当变量使用,例如

int a=100;
int *p;
*p=100;//指针没有获得地址
printf("%d",p);//输出地址,能够输出地址
printf("%d",p);//输出指针

上面代码可以编译通过,但运行时会弹出错误对话框,如图


未经处理的处理
————————————————————————
0x00401052处有未经处理的异常(在HelloWorld.exe中);
0xC0000005:写入位置0xCCCCCCCC时发生访问冲突。
复制详细信息
异常设置


下面的实例通过指针来实现数据大小比较的功能。

#include<iostream>
using namespace std;
void main()
{
int *p1,*p2;
int *p;//临时指针
int a,b;
cout<<"input a:"<<endl;
cin>>a;//给a赋值
cout<<"input b:"<<endl;
cin>>b;//给b赋值
p1=&a;p2=&b;//p1指向a地址,p2指向b地址
if(a<b)//如果a<b,交换p1和p2所指向的地址
{
p=p1;
p1=p2;
p2=p;
}
cout<<"a="<<a;
cout<<" ";
cout<<"b="<<b;
cout<<endl;
cout<<"较大数:"<<*p1<<"较小的数:"<<*p2<<endl;
}

五、指针运算符和取地址运算符

1.指针运算符和取地址运算符简介
*和&是两个运算符,*是指针运算符,&是取地址运算符。
取地址运算符,变量i的值为100,存储在内存地址为4009的地方,取地址运算符&使指针变量p得到地址4009.
指针运算符,指针变量存储的是地址编号4009,指针通过指针运算符可以得到地址。
输出指针对应的值。

#include<iostream>
using namespace std;
void main()
{
int a=100;
int *p=&a;
cout<<"a=:"<<a<<endl;
cout<<"p=:"<<p<<endl;
}

2.指针运算符和取地址运算符的说明
声明并初始化指针变量时同时用到了*和&这两个运算符,例如:

int *p=&a;

该语句等同于如下

int *(p=&a);

如果写成*p=&a;程序会报错。
&*p中的p只能是指针变量,如果将*放在变量名前,编译的时候会有编译错误,例如

#include<iostream>
using namespace std;
void main()
{
int a=100;
int *p;
printf("%d",&a);
}

编译程序会出现errorC2100:illega indirection的错误提示。
3.&*p和*&a的区别
&和*的运算符优先级别相同,按自右而左的方向结合,因此&*p是先进行*运算,p相当于变量a;再进行&运算,&*p就相当于取变量a的地址。&a是先计算&运算符,&a就是取变量a的地址,然后计算*运算,*&a就相当于取变量a所在地址的值,实际就是变量a。

六、指针运算

指针变量存储的地址值,对指针做运算就等于对地址做运算。
输出指针运算后地址值

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int a=100;
int *p=&a;
printf("address:%d\n",p);
p++;
printf("address:%d\n",p);
p--;
printf("address:%d\n",p);
p--;
printf("address:%d\n",p);
}
address:7012088
address:7012092
address:7012088
address:7012084

--------------------------------
Process exited with return value 0
Press any key to continue . . .

程序首先输出的是指向变量a的指针地址值7012088,然后对指针分别进行自加运算、自减运算、自减运算,输出的结果分别是7012092、7012088、7012084.
指针进行一次加1运算,其地址值并没有加1,而是增加了4,这和声明指针的类型有关。
p++是对指针做自加运算,相当于语句p=p+1,地址是按字节存放数据,但指针加1并不代表地址值加1各字节,而是加上指针数据类型所占的字节宽度,要获取字节宽度需要使用sizeof关键字,例如,整数的字节宽度是sizeof(int),sizeof(int)的值为4.双精度整型的字节宽度是sizeof(double),其值为8,将实例中的int指针改为double,如下

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
double a=100;
double *p=&a;
printf("address:%d\n",p);
p++;
printf("address:%d\n",p);
p--;
printf("address:%d\n",p);
p--;
printf("address:%d\n",p);
}
address:7012080
address:7012088
address:7012080
address:7012072

--------------------------------
Process exited with return value 0
Press any key to continue . . .

七、指向空的指针与空类型指针

指针可以指向任何数据类型的数据,包括空类型(void)

void *p;//定义一个指向空类型的指针变量

空类型指针可以接受任何类型的数据,当使用它时,可以将其强制转化为所对应数据类型。
空类型指针的使用。

#include<iostream>
using namespace std;
int main()
{
int *pl=NULL;
int i=4;
pl=&i;
float f=3.333f;
bool b=true;
void *pV=NULL;
cout<<"依次赋值给空指针"<<endl;
pV=pl;
cout<<"pV=pl--------"<<*(int*)pV<<endl;
cout<<"pV=pl--------转为float类型指针"<<*(float*)pV<<endl;
pV=&f;
cout<<"pV=&f--------"<<*(float*)pV<<endl;
cout<<"pV=&f--------转为int类型指针"<<*(int *)pV<<endl;
return 0; 
}
依次赋值给空指针
pV=pl--------4
pV=pl--------转为float类型指针5.60519e-045
pV=&f--------3.333
pV=&f--------转为int类型指针1079332831

--------------------------------
Process exited with return value 0
Press any key to continue . . .

可以看到空指针赋值后,转化为对应得指针才能得到我们所期望得结果。若将它转换为其他类型指针,得到得结果将不可预知,非空类指针同样具有这样得特性。在本实例中,出现了一个符号NULL,它表示空指。空值无法用输出语句表示,而且赋空得指针无法被使用,直到它被赋予其他的值。

八、指向常量的指针与指针常量

同其他数据类型一样,指针也有常量,使用const关键字形式如下:

int i=9;
int *const p=&i;
*p=3;

将关键字const放在标识符前,表示这个数据本身是常量,而数据类型是int*,即整型指针。与其他常量一样,指针常量必须初始化。我们无法改变它的内存指向,但是可以改变它指向内存的内容。
若将关键字const放到指针类型的前方,形式如下:

int i=9;
int const * p=&i;

这是指向常量的指针,虽然它所指向的数据可以通过赋值语句进行修改,但是通过该指针修改内存内容的操作是不被允许的。
当const以如下形式使用时:

int i=9int const * const p=&i;

该指针是一个指向常量的指针常量。即不可以改变它的内存指向,也不可以通过它修改指向内存的内容。
实例

#include<iostream>
using std::cout;
using std::endl;
int main()
{
	int i=5;
	const int c=99;
	const int*pR=&i;//这个指针只能用来”读“内存数据,但可以改变自己的地址
    int*const pC=&i;//这个指针本身是常量,不能改变指向,但它能够改变内存的内容
	const int *const pCR=&i;//这个指针只能用来"读"内存数据,并且不能改变指向
	cout<<"三个指针都指向了同一个变量i,同一块内存"<<endl;
	cout<<"指向常量的指针pR操作:"<<endl;
	//*pR=6  // 去掉语句前方注释报错
	cout<<"通过赋值语句修改i:"<<endl;
	i=100;
	cout<<"i:"<<i<<endl;
	cout<<"将pR的地址变成常量c的地址:"<<endl;
	pR=&c;
	cout<<"*pR:"<<*pR<<endl;
	cout<<"指向常量的指针pC操作:"<<endl;
	//pC=&c ;            //去掉语句前方注释报错
	cout<<"通过pC改变i值:"<<endl;
	*pC=6;
	cout<<"i:"<<i<<endl;
	cout<<"指向常量的指针常量pCR操作:"<<endl;
	//pCR=&c;//报错
	//*pCR=100;//报错
	cout<<"通过pCR无法改变任何东西,真正做到了只读"<<endl;
	return 0; 
} 
三个指针都指向了同一个变量i,同一块内存
指向常量的指针pR操作:
通过赋值语句修改i:
i:100
将pR的地址变成常量c的地址:
*pR:99
指向常量的指针pC操作:
通过pC改变i值:
i:6
指向常量的指针常量pCR操作:
通过pCR无法改变任何东西,真正做到了只读

--------------------------------
Process exited with return value 0
Press any key to continue . . .

九 、指针与数组

1.指针与一维数组

系统需要提供一定量连续的内存来存储数组中的各元素,内存都有地址,指针变量就是存放地址的变量,如果把数组的地址赋给指针变量,就可以通过指针变量来引用数组。引用数组元素有两种方法:下标法和指针法。

通过指针引用数组,就要先声明一个数组,再声明一个指针。

int a[10];
int *p;

然后通过&运算符获取数组中元素的地址,再将地址值给指针变量。

p=&a[0];

把a[0]元素的地址赋给指针变量p,即p指向a数组的第0好元素
下面通过实例使读者了解指针和数组间的操作

#include<iostream>
using namespace std;
int main()
{
	int i,a[10];
	int *p;
	//利用循环,分别为10个元素赋值
	for(i=0;i<10;i++)
	{
		a[i]=i;
	} 
	//将数组中的10个元素输出到显示设备
	p=&a[0];
	for(i=0;i<10;i++,p++)
	cout<<*p<<endl; 
	return 0; 
} 
0
1
2
3
4
5
6
7
8
9

--------------------------------
Process exited with return value 0
Press any key to continue . . .

如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素。
p+i和a+1是a[i]的地址。a代表首元素的地址,a+i也是地址,对应数组元素a[i]。
(p+i)或*(a+1)是p+i或a+i所指向的数组元素,即a[i]。
程序中使用指针获取数组首元素的地址,也可以将数组名赋值给指针,然后通过指针访问数组。
如下:

#include<iostream>
using namespace std;
int main()
{
	int i,a[10];
	int *p;
	//利用循环,分别为10个元素赋值
	for(i=0;i<10;i++)
	{
		a[i]=i;
	} 

	p=a;//让p指向数组a的首地址 
	for(i=0;i<10;i++,p++)	//将数组中的10个元素输出到显示设备
	cout<<*p<<endl; 
	return 0; 
} 
0
1
2
3
4
5
6
7
8
9

--------------------------------
Process exited with return value 0
Press any key to continue . . .

程序中使用数组地址来进行计算,a+1表示数组a中的第i个元素,然后通过指针运算符就可以获得数组元素的值。

#include<iostream>
using namespace std;
int main()
{
	int i,a[10];
	int *p;
	//利用循环,分别为10个元素赋值
	for(i=0;i<10;i++)
	{
		a[i]=i;
	} 

	p=a;//让p指向数组a的首地址 
	for(i=0;i<10;i++,p++)	//将数组中的10个元素输出到显示设备
	cout<<*(a+i)<<endl; 
	return 0; 
} 
0
1
2
3
4
5
6
7
8
9

--------------------------------
Process exited with return value 0
Press any key to continue . . .

指针操作数组的一些说明如下:
(1)*(p–)相当于a[i–],先对p进行*运算,再使p自减
(2)*(++p)相当于a[++i],先使p自加,再进行*运算。
(3)*(–p)相当于a[–i],先使p自减,再进行*运算。

2.指针与二维数组

可以将一维数组的地址赋给指针变量,同样也可以将二维数组的地址赋给指针变量,因为一位数组的内存地址是连续的,二维数组的内存地址也是连续的,可以将二维数组看成是一维数组。
因为多维数组可以看成是一维数组,本实例实现将多维数组转换成一维数组的功能。

#include<iostream>
using namespace std;
int main()
{
int array1[3][4]={{1,2,3,4},
{5,6,7,8},
{9,10,11,12}
};
int array2[12]={0};
int row ,col,i;
cout<<"array old"<<endl;
for(row=0;row<3;row++)
{
	for(col=0;col<4;col++)
	{
		cout<<array1[row][col];
	}
	cout<<endl; 
} 
cout<<"array new"<<endl;//将三行合并成一行
for(row=0;row<3;row++)
{
	for(col=0;col<4;col++)
	{
		i=col+row*4;
		array2[i]=array1[row][col];
	}
} 
for(i=0;i<12;i++)
cout<<array2[i]<<endl;

	return 0; 
} 
array old
1234
5678
9101112
array new
1
2
3
4
5
6
7
8
9
10
11
12

--------------------------------
Process exited with return value 0
Press any key to continue . . .

使用指针变量遍历二维数组。

#include<iostream>
#include<iomanip>
using namespace std;
int main()
{
	int a[4][3]={1,2,3,4,5,6,7,8,9,10,11,12};
	int *p;
	p=a[0];
	for(int i=0;i<sizeof(a)/sizeof(int);i++)
	{
		cout<<"address:";
		cout<<a[i];
		cout<<"is";
		cout<<*p++<<endl;
	}
} 
address:0x6afea8is1
address:0x6afeb4is2
address:0x6afec0is3
address:0x6afeccis4
address:0x6afed8is5
address:0x6afee4is6
address:0x6afef0is7
address:0x6afefcis8
address:0x6aff08is9
address:0x6aff14is10
address:0x6aff20is11
address:0x6aff2cis12

--------------------------------
Process exited with return value 0
Press any key to continue . . .

在这里插入图片描述
如图
a代表二维数组的地址,通过指针运算符可以获得数组中的元素。
(1)a+n表示n行的首地址。
(2)&a[0][0]即可以看作数组0行0列的首地址,同样还可以看作是二维数组的首地址。
&a[m][n]就是第m行n列元素的地址。
(3)&a[0]是第0行的首地址,当然&a[n]就是第n行的首地址。
(4)a[0]+n表示第0行第n个元素地址。
(5)*(*(a+n)+m)表示第n行第m列元素。
(6)*(a[n]+m)表示n行第m列元素。
使用数组地址将二维数组输出。

#include<iostream>
using namespace std;
int main()
{
	int i,j;
	int a[4][3]={{1,2,3},{4,5,6},{7,8,9},{10,11,12}};
	cout<<"the array is:"<<endl;
	for(i=0;i<4;i++)//四行
	{
		for(j=0;j<3;j++)//三列
		cout<<*(*(a+i)+j)<<endl;//输出第i行的第j个元素 
	} 
	return 0;
} 
the array is:
1
2
3
4
5
6
7
8
9
10
11
12

--------------------------------
Process exited with return value 0
Press any key to continue . . .

函数名a是一个指向数组的指针。直接依照a偏移a+n所得到的是行数组a[n]的地址&a[n]的地址&a[n],也就是行地址。a[n]是一个指针,它也是第n行的一维数组的名字。获得具体元素加上偏移求值,得到*(a[n]+m),也就是*(*(a+n)+m)。

#include<iostream>
using namespace std;
int main()
{
int i;
int j;
int a[3][4];
int (*b)[4];//定义一个数组指针,可以指向一个含有4个整型变量的数组
int *c[4];//定义一个指针数组,储存指针的数组,最多只能储存4个指针
int *p;
p=a[0];//让p指向数组a的第0行的行地址
b=a;//让b指向数组a
cout<<"利用连续内存的特点,使用int指针将二维int数组初始化"<<endl;
for(i=0;i<12;i++)//初始化二维数组 
{
	*(p+i)=i+1;//给第i行首元素赋值
	cout<<a[i/4][i%4]<<",";
	if((i+1)%4==0)//每4列换行
	{
		cout<<endl;
	} 
} 
cout<<"使用指针数组的指针,二维数组的值改变"<<endl;
for(int i=0;i<3;i++)
{
	for(j=0;j<4;j++)
	{
		*(*(b+i)+j)+=10;//通过数组指针修改二维数组内容 
	}
} 
cout<<"请使用指针数组,再次输出二维数组"<<endl;
for(i=0;i<3;i++)
{
	for(j=0;j<4;j++)
	{
		c[j]=&a[i][j];//用指针数组里的指针指向a[i][j]
		cout<<*(c[j])<<",";
		if((j+1)%4==0)//每四列换行
		{
			cout<<endl;
		} 
	}
} 
	return 0;
} 
利用连续内存的特点,使用int指针将二维int数组初始化
1,2,3,4,
5,6,7,8,
9,10,11,12,
使用指针数组的指针,二维数组的值改变
请使用指针数组,再次输出二维数组
11,12,13,14,
15,16,17,18,
19,20,21,22,

--------------------------------
Process exited with return value 0
Press any key to continue . . .

3.指针与字符数组

字符数组是一个一维数组,使用指针同样也可以引用字符数组。引用字符数组的指针维字符指针,字符指针就是指向字符型内存空间的指针变量,其一般的定义语句如下

char *p;

字符数组就是一个字符串,通过字符指针可以指向一个字符串:
语句:

char *string ="www.mingri.book";

等价于下面两个语句:

char *string;
string ="www.mingri.book";

下面通过两个实例来实现连接两个字符数组的功能。
通过指针偏移连接两个字符串。

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
    char str1[50],str2[30],*p1,*p2; 
    p1=str1;//让两个指针分别指向两个数组
	p2=str2;
	cout<<"please input string1:"<<endl;
	gets(str1);//给str1赋值
	cout<<"please input string2:"<<endl;
	gets(str2);//给str2赋值
	while(*p1!='\0')
	p1++;//把p1移动到str1的末尾
	while(*p2!='\0')
	*p1++=*p2++;//取p2指向的值赋到p1指向的地址(str1的末尾),即连接str1和str2
	*p1='\0';
	cout<<"the new string is:"<<endl;
	puts(str1);//输出新的str1 
	return 0;
} 
please input string1:
hello
please input string2:
world
the new string is:
helloworld

--------------------------------
Process exited with return value 0
Press any key to continue . . .

程序需要用户输入两个字符数组,然后通过字符指针将两个字符串连接起来。
同样地,还可以使用处理字符串函数strcat来实现连接两个字符串。
通过字符串函数strcat连接两个字符串。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
    char str1[50],str2[30],*p1,*p2; 
    p1=str1;//让两个指针分别指向两个数组
	p2=str2;
	cout<<"please input string1:"<<endl;
	gets(str1);//给str1赋值
	cout<<"please input string2:"<<endl;
	gets(str2);//给str2赋值
	strcat(str1,str2);
	cout<<"the new string is:"<<endl;
	puts(str1);//输出新的str1 
	return 0;
} 
please input string1:
hello
please input string2:
world
the new string is:
helloworld

--------------------------------
Process exited with return value 0
Press any key to continue . . .

十、指针在函数中的应用

1.传递地址

以前所接触到的函数都是按值传递参数,也就是说实参传递进函数体内后,生成的是实参的副本。当在函数内改变副本的值并不影响到实参。而指针传递参数时,指针变量产生了副本,但副本与原变量所指向的内存区域是同一个。对指针副本指向的变量进行改变,就是改变原指针变量所指向的变量。
调用自定义函数交换两变量值

#include<iostream>
using namespace std;
void swap(int *a,int *b)
{
	int tmp;
	tmp=*a;
	*a=*b;
	*b=tmp;
}
void swap(int a,int b)
{
	int tmp;
	tmp=a;
	a=b;
	b=tmp;
}
int main()
{
    int x,y;
    int *p_x,*p_y;//定义两个整数指针
	cout<<"input two number"<<endl;
	cin>>x;
	cin>>y;
	p_x=&x;
	p_y=&y;
	cout<<"按指针传递参数交换"<<endl;
	swap(p_x,p_y);
	cout<<"x="<<x<<endl;
	cout<<"y="<<y<<endl;
	cout<<"按值传递参数交换"<<endl;
	swap(x,y);
	cout<<"x="<<x<<endl;
	cout<<"y="<<y<<endl; 
	return 0;
} 
input two number
1
2
按指针传递参数交换
x=2
y=1
按值传递参数交换
x=2
y=1

--------------------------------
Process exited with return value 0
Press any key to continue . . .

使用指针传递参数的函数真正实现了x与y的交换,而按值传递函数只是交换了x与y的副本。
swap函数是用户自定义的重载函数,在main函数中调用该函数交换变量a和b的值。按指针传参的swap函数的两个形参被传入了两个地址值,也就是传入了两个指针变量,在swap函数的函数体内使用整型变量tmp作为中转变量,将两个指针变量所指向的数值进行交换。在main函数内首先获取输入两个数值,分别传递给变量x和y,将x和y的地址传递给swap函数。在按指针传递的swap函数内,两个指针变量的副本a和b所指向的变量正是x与y,而按值传递的swap函数并没有实现交换x与y的功能

2.指向函数的指针

指针变量也可以指向一个函数。一个函数在编译时被分配给一个入口地址,这个函数入口地址就称为函数的指针。可以用一个指针变量指向函数,然后通过该指针变量调用此函数。
一个函数可以带回一个整型值、字符值、实型值等,也可以带回指针型的数据,即地址。其概念与以前类似,只是带回的值类型是指针类型而已。返回指针值的函数简称为指针函数。
定义指针函数的一般形式如下:

类型名 *函数名(参数表列);

例如,定义一个具有两个参数和一个返回值的函数的指针

int sum(int x, int y)//定义一个函数
int *a(int ,int)//定义一个函数指针
a=sum;//让函数指针a指向函数sum

函数指针能指向返回值与参数列表的函数,当使用函数指针时形式如下:
int c,d;//定义两个整型变量
(*a)(c,d);//调用指针a指向的函数,并传参
使用指针函数进行平均值计算
在未使用指针函数计算平均值时,可以通过自定义函数avg来进行平均值进行。计算哪两个整型数的平均值,直接将两个整型数传递给avg函数,avg函数完成计算后将计算结果传出。

#include<iostream>
#include<iomanip>
using namespace std;
int avg(int a,int b);
int main()
{
	int iWidth,iLength,iResult;
	int i=10;
	int j=30;
	iResult=avg(i,j);
	cout<<iResult<<endl;
}
int avg(int a,int b)
{
	return (a+b)/2;
}

20

--------------------------------
Process exited with return value 0
Press any key to continue . . .

avg函数是一个具有两个参数和一个返回值的函数,可以定义一个指针函数指向该函数,指针函数必须是具有两个整型参数和一个整型返回值的形式。使用指针函数进行平均值计算的程序如下

#include<iostream>
#include<iomanip>
using namespace std;
int avg(int a,int b);
int main()
{
	int iWidth,iLength,iResult;
	int i=10;
	int j=30;
	int(*pFun)(int,int);//定义函数指针 
	pFun=avg;
	iResult=(*pFun)(iWidth,iLength);
	cout<<iResult<<endl;
}
int avg(int a,int b)
{
	return (a+b)/2;
}

988460906

--------------------------------
Process exited with return value 0
Press any key to continue . . .

指针pFun是指向avg函数的函数指针,调用pFun函数指针,就和调用函数avg一样。

3.空指针调用函数

空类型指针指向任意类型函数或者将任意类型的函数指针赋值给空类型指针都是合法的。使用空指针调用自身所指向的函数,仍然按照强制转换的形式使用。

4.从函数中返回指针

定义一个返回指针类型的函数,形式如下:

int *function(参数列表)
{
...;//执行过程
return p;
}

p是一个指针变量,也可以是形式如&value的地址值。当函数返回一个指针变量,我们得到的是地址值。值得注意的是,返回指针的内存内容并不随返回的地址一样经过复制成为临时变量。如果操作不当,后果将难以预料。
指针做返回值

#include<iostream>
using std::cout;
using std::endl;
int *pointerGet(int *p)
{
	int i=9;
	cout<<"函数体中i的地址"<<&i<<endl;
	cout<<"函数体中i的地址"<<i<<endl;
	p=&i;
	return p; 
}
int main()
{
	int *k=NULL;
	cout<<"k的地址:"<<k<<endl;//输出k的初始地址
	cout<<"执行函数,将k赋予函数返回值"<<endl;
	k=pointerGet(k);//调用函数获得一个指向变量i的地址的指针
	cout<<"k的地址:"<<k<<endl;//输出k的新地址(i的地址) 
	cout<<"k所指向内存的内容:"<<k<<endl;//输出一个随机数 
}
k的地址:0
执行函数,将k赋予函数返回值
函数体中i的地址0x6afeb8
函数体中i的地址9
k的地址:0x6afeb8
k所指向内存的内容:0x6afeb8

--------------------------------
Process exited with return value 0
Press any key to continue . . .

可以看到,函数返回的是函数中定义的i地址。函数执行后,i的内存被销毁,值变成了一个不可预知的数。
值为NULL的指针地址为0,但并不意味者着者块内存可以使用,将指针赋值为NULL也是基于安全而考虑的。

十一、指针数组

数组中的元素均为指针变量的数组称为指针数组,一维指针数组的定义形式如下:

类型名*数组名[数组长度];

例如

int *p[4];

指针数组中的数组名也是一个指针变量,该指针变量为指向指针的指针。
例如

int *p[4];
int a=1;
*p[0]=&a;

p是一个指针数组,它的每一个元素是一个指针型数据(其值为地址),指针数组p的第一个值是变量a的地址。指针数组中的元素可以使用指向指针的指针来引用,例如

int *(*p);

*运算符表示p是一个指针变量,*(*p)表示指向指针的指针,*运算符的结合性是从右向左,因此int*(*p);可写成int**p;
指向指针的指针获得指针数组中的元素和利用指针获取一维数组中的元素方法相同,如图
在这里插入图片描述
第一次*运算获取到的是一个地址值,再进行一次*运算,就可以获取到具体值。
用指针数组中各个元素分别指向若干个字符串。

#include<iostream>
#include<cstring> 
using namespace std;
void sort(char *name[],int n)//对字符串进行排序
{
	char *temp;
	int i,j,k;
	for(i=0;i<n-1;i++)
	{
		k=i;
		for(j=i+1;j<n;j++)
		if(strcmp(name[k],name[j])>0)k=j;
		if(k!=i)
		{
			temp=name[i];
			name[i]=name[k];
			name[k]=temp;
		}
	}
} 
void print(char *name[],int n)//输出字符串数组中的元素
{
	int i=0;
	char *p;
	p=name[0];
	while(i<n)
	{
		p=*(name+i++);
		cout<<p<<endl;
	}
} 
int main()
{
	char *name[]={"mingri","soft","C++","mr"};//定义指针数组
	int n=4;
	sort(name,n);
	print(name,n);
	return 0; 
}
C++
mingri
mr
soft

--------------------------------
Process exited with return value 0
Press any key to continue . . .

程序中的print函数中,数组名name代表该指针数组首元素的地址,name+i是name[i]的地址。由于name[i]的值是地址(即指针),因此name+i就是指向指针型数据的指针。还可以设置一个指针变量p,它指向指针数组的元素。p就是指向指针型数据的指针变量,它所指向的字符串如图
在这里插入图片描述
利用指针变量访问另一个变量就是间接访问。如果再一个指针变量中存放一个目标变量的地址,这就是单级间址。指向指针的指针用的是二级间址方法,还有三级间址和四级间址,但二级间址应用最为普遍。

十二、安全使用指针

1.内存分配

1>堆与栈

在程序中定义一个变量,它的值会被放入内存当中。如果没有申请动态分配的方式,它的值将放到栈中。在栈中的变量所属的内存大小是无法被改变的,它们的产生与消亡也与变量定义的位置和储存方式有关。与栈相对应,是一种动态分配方式的内存。当申请使用动态分配方式取储存某个变量,那么这个变量会被放入堆中。根据需要,这个变量的内存大小可以发生改变,内存的申请和销毁的时机则由编程者来操作。

2>关键字new与delete

创建变量之前,编译器没有获取到变量的名称,只具有指向该变量的指针。那么,申请变量的堆内存即是申请自身指向堆。new是C++语言申请动态内存的关键字,形式如下:

p1=new type;

其中,p1表示指针,new是关键字,type是类型名。new返回新分配的内存单元的地址。
这样,pI指针就申请了动态方式,使用它在堆内申请的内存储存int类型的值。
动态分配空间

#include<iostream>
using namespace std;
int main()
{
	int *pl1=NULL;
	pl1=new int;//申请动态分配
	*pl1=111;//动态分配的内存储存的内容变成111的整型变量
	cout<<"pl内存的内容"<<*pl1<<",pl所指向的地址"<<pl1<<endl;
	int *pl2;
	//*pl2=222;//直接赋值会导致错误
	int k;//栈中的变量
	pl2=&k;    //分配栈内存
	*pl2=222;    //分配内存后方可赋值
	cout<<"pl内存的内容"<<*pl2<<",pl所指向的地址"<<pl2<<endl;
	return 0; 
}
pl内存的内容111,pl所指向的地址0x701468
pl内存的内容222,pl所指向的地址0x6afee4

--------------------------------
Process exited with return value 0
Press any key to continue . . .

可以看到指针pl1创建后申请了动态分配,程序自动交给了它一块堆内存。而指针pl2则是获取了栈中的内存地址,属于静态分配。
动态分配方式虽然很灵活,但是随之带来新的问题。申请一块堆内存后,系统不会在程序执行时依据情况自动销毁它。若想释放该内存空间,则需要使用delete关键字。
动态内存的销毁

#include<iostream>
using std::cout;
using std::endl;
int*newPointerGet(int *p1)
{
	int k1=55;
	p1=new int;//变为堆内存
	*p1=k1;//int型变量赋值操作
	return p1; 
}
int*PointerGet(int*p2)
{
	int k2=55;
	p2=&k2;//指向函数中定义变量所在的栈内存
	return p2; 
}
int main()
{
	cout<<"输出函数各自返回指针所指向的内存的值"<<endl;
	int*p=NULL;
	p=newPointerGet(p);//p具有堆内存的地址
	int *i=NULL;
	i=PointerGet(i);//i具有栈内存地址,内存内容被销毁
	cout<<"newGet:"<<*p<<",get"<<*i<<endl;
	cout<<"i所指向的内存没有被立即销毁,执行一个输出语句后:"<<endl;
	//i仍然为55,但不代表程序不会对它进行销毁
	cout<<"newGet:"<<*p<<",get"<<*i<<endl;//执行其他的语句后,程序销毁了栈空间
	delete p;//依照p销毁堆内存
	cout<<"销毁堆内存后:"<<endl;
	cout<<"*p::"<<*p<<endl;
	return 0; 
	 
}
输出函数各自返回指针所指向的内存的值
newGet:55,get55
i所指向的内存没有被立即销毁,执行一个输出语句后:
newGet:55,get55
销毁堆内存后:
*p::7676096

--------------------------------
Process exited with return value 0
Press any key to continue . . .

变量p接受了newGet返回的指针的堆内存地址,所以内存的内容并没被销毁,而栈内存则由系统控制。程序最后使用delete语句释放了堆内存。

2.内存安全

指针是C++提供给我们强大而灵活的工具,如何安全地使用它们对内存安全地操作是编程者必须要掌握的。在前面的章节中讨论到过指针所指向内存销毁的问题,当一块内存被销毁时,该区域不可复用。若有指针指向该区域,则需要将该指针置空指(NULL)或者指向未被销毁的内存。
内存销毁实质上是系统判定该内存不是编程人员正常使用的空间,系统也会被它分配给别的任务。若擅自使用被销毁内存的指针更改该内存的数据,很可能会造成意想不到的结果。
被销毁的内存

#include<iostream>
using std::cout;
using std::endl;
int *sum(int a,int b)
{
	int*pS=NULL;
	int c=a+b;
	pS=&c;
	return pS;
}
int main()
{
	int*pl=NULL;//将指针初始化为空
	int k1=3;
	int k2=5;
	pl=sum(k1,k2);
	cout<<"*pl的值:"<<*pl<<endl;
	cout<<"也许*pl还保留着i值,但它已经被程序认定为销毁"<<endl;
	cout<<"*pl的值:"<<*pl<<endl;
	cout<<"尝试修改*pl"<<endl;
	*pl=3;
	for(int i=0;i<3;i++)
	{
		cout<<"修改被销毁的内存后*pl的值:"<<pl<<endl; 
	} 
}
*pl的值:8
也许*pl还保留着i值,但它已经被程序认定为销毁
*pl的值:4199040
尝试修改*pl
修改被销毁的内存后*pl的值:0x6afec0
修改被销毁的内存后*pl的值:0x6afec0
修改被销毁的内存后*pl的值:0x6afec0

--------------------------------
Process exited with return value 0
Press any key to continue . . .

指针Pl从从sum函数中得到了一个临时指针,该指针是指针pS的临时复制品,操作完成后消失,它搜保留的地址交给了pl,在函数sum执行完毕后,该域使用的栈内存会被系统销毁甚至挪用。本程序尝试通过pl继续使用、修改它,结果是系统会再次销毁它。在某些场合下,该程序也许会引起内存报错,甚至造成多个程序崩溃。所以对于栈内存的指针一定要明白其何时销毁,不再重复利用它。
与此相对应的另一个安全问题叫内存泄露。如同我们所知道的,再申请动态分配内存后,系统不会主动销毁该堆内存,需要编程者使用delete关键字通知系统销毁。如果不这样做,系统将浪费很多资源,使程序执行时变得臃肿,只需占用数十MB内存的程序可能为此占用上百MB的内存。可见,回收堆内存空间是很重要的。销毁内存时,需要保留指向该堆内存的指针。当没有指针指向一块没被回收的堆内存时,此块内存犹如丢失了一般,称之位内存泄露。
丢失的内存

#include<iostream>
using namespace std;
int main()
{
	float*pF=NULL;
	pF=new float;//动态申请一块内存,用pF去指向
	*pF=4.321f;
	float f2=5.321f;
	cout<<"pF指向的地址:"<<pF<<endl;
	cout<<"pF的值:"<<*pF<<endl;
	pF=&f2;//让pF指向了另一地址,此时上面申请的内存变为不可用
	cout<<"pF指向了f2的地址:"<<pF<<endl;
	if(*pF>5)
	{
		cout<<"*pF的值:"<<*pF<<endl; 
	} 
	return 0;
}
pF指向的地址:0xa41468
pF的值:4.321
pF指向了f2的地址:0x6afee8
*pF的值:5.321

--------------------------------
Process exited with return value 0
Press any key to continue . . .

程序中动态分配的内存开始有pF指向。当pF改变指向后,此块内存再也无法回收了。
一般情况下,我们无法通过调试程序发现内存泄漏,使用,使用动态分配时一定要注意形成良好的习惯。
回收动态内存的一般处理步骤

#include<iostream>
void swap(int*a,int*b)
{
	int temp=*a;
	*a=*b;
	*b=temp;
}
int main()
{
	int*pl=new int;
	*pl=3;
	int k=5;
	swap(pl,&k);
	std::cout<<"*pl:"<<*pl<<std::endl;//使用std名字空间
	std::cout<<"k:"<<k<<std::endl;
	delete pl;//回收动态内存
	pl=NULL;//将pl置空,防止使用已销毁的内存;和上一条语句不可颠倒
	//否则将造成内存泄漏
	return 0; 
}
*pl:5
k:3

--------------------------------
Process exited with return value 0
Press any key to continue . . .

指针是一种灵活高效的内存访问机制,它可以通过变量在内存中的地址来对变量直接操作,但是指针却不能访问寄存器变量,因为寄存器变量并没有保存再内存中,而是保存在寄存器中(从寄存器中读取数据要比从内存中读取数据速度快,使用有些要求频繁使用的数据可以被放在寄存器中)。指针只能访问内存,不能访问寄存器,所以指针访问不到寄存器变量。

发布了91 篇原创文章 · 获赞 101 · 访问量 3292

猜你喜欢

转载自blog.csdn.net/weixin_41096569/article/details/104761993
今日推荐