C++每日一课(十四)

指针


计算机程序在存储数据时必须要知道的3个属性
1.存在何处
2.存的值是多少
3.存的信息是什么类型


为了达到上面的目的,可以定义一个简单的变量,指出符号名、值、类型
另一种可以达到目的方式是使用C++中的指针
指针是一个变量,它存的是值的地址,而并非值本身。


常规变量的地址可以使用&这个取地址符来获取


/*
作者:xiesheng
时间:2017-07-06
版本:v1.0
说明:指针
*/
#include <iostream>


int main() {


	using namespace std;
	int i = 1;
	double d = 1.0;


	cout << "i的值是:" << i << " ;它的内存地址是:" << &i << endl;
	cout << "d的值是:" << d << " ;它的内存地址是:" << &d << endl;


	system("pause");
	return 0;
}





i的值是:1 ;它的内存地址是:00AFF29C
d的值是:1 ;它的内存地址是:00AFF2A0
请按任意键继续. . .




在显示地址的时候cout使用十六进制表示,它也是常用的内存地址描述法。
在显示的时候这两个地址相差4,这是因为i的类型是int型,这种类型使用4个字节,当然的些系统中也会先存d再存i这个时候的差值就会是8,还有些系统这两个变量不在相邻近的内存地址单元。


对于指针来说它存储的就是值的地址,指针名表示的是地址
*运算符称为间接值或解除引用运算符,把这个运算符应用于指针可以得到该地址处存储的值


/*
作者:xiesheng
时间:2017-07-06
版本:v1.0
说明:指针
*/
#include <iostream>


int main() {


	using namespace std;
	int i = 5;	//定义一个整型变量
	int * p_i;	//定义一个可以指向整型变量地址的指针


	p_i = &i;	//指针变量的赋值
	cout << "i的值:i=" << i << " ;*p_i=" << *p_i << endl;
	cout << "i的地址:&i=" << &i << " ;p_i=" << p_i << endl;


	//使用指针进行间接运算
	*p_i = *p_i + 1;
	cout << "当前i的值:" << i << endl;


	system("pause");
	return 0;
}




i的值:i=5 ;*p_i=5
i的地址:&i=0115EE64 ;p_i=0115EE64
当前i的值:6
请按任意键继续. . .


从上面可以看到i与p_i的关系
i存的是值,p_i存的是地址
&i的结果是地址,*p_i的结果是值
由于p_i指向i,因而*p_i和i是完全等价的,在程序中可以像使用i一样使用*p_i,而且可以把值直接赋给*p_i这个就相当于修改i存储的值


指针的声明与初始化


1.必须要指定它指向值的类型,因为对于char和double来说它们使用的字节数是不相同的。它们存储值时使用的内部格式也是不相同的。
比如声明如下:
int * p_i;
这里声明*p_i,它的类型是int
*运算符是使用于指针,我们说p_i,指向int类型,还可以说p_i的类型是指向int的指针或int*
p_i是指针(地址),而*p_i是int,不是指针


C风格中,声明格式一般是:int *ptr;它强调的是*ptr是一个int类型的值
C++风格一般声明是:int* ptr;它强调的是int*是一种类型,指向int的指针。
注意:int* p1,p2;
这个声明是创建一个指针p1和一个int变量p2.
对于指针变量存储的值的类型是有区别的,但是对于它本身就是一个地址,不管是什么类型,其本身的长度是一样的,一般来说地址需要2个或者4个字节,具体使用多长取决于计算机系统。


可以在声明语句中初始化指针,在这种情况下,被初始化的是指针,而不是它指向的值
int i =5;
int * p_i = &i;


使用指针的风险


在C++创建指针时,计算机会分配用于来存储地址的内存,但是是不会分配用来存储所指向的数据的内存。一定要记得为数据分配一个空间。
一定要在使用*运算符前把指会初始化为一个确定的地址。


指针与数字的区别
指针不是整型,虽然计算机是把它当整数来处理的,但是对于整数来说可以进行加、减、乘、除,那对两个地址做乘法则是没有任何意义的。
注意:C++中不允许简单地把整数赋值给指针。如果这样赋值编译器会通知类型不匹配的错误。但是可以通过强制类型的转把整数转成适当的地址类型。


new分配内存
指针是如何实现程序运行时分配内存的呢?
C语言中可以使用库函数malloc()来分配内存,C++中除了可以使用这个库函数外还可以使用new运算符。


int * pn = new int;
这里 new int则是告诉程序要适合存储int的内存,new运算符会根据类型来确定需要多少字节的内存,然后找到这样的内存,并返回它的地址并把地址赋给pn
pn此时就被声明为指向int的指针,pn就是地址,*pn则是在这个地址上存储的值


/*
作者:xiesheng
时间:2017-07-07
版本:v1.0
说明:指针
*/
#include <iostream>


int main() {


	using namespace std;
	int id = 1001;
	int * p_i = new int;	//为存储一个int类型的值来分配一个内存
	*p_i = 1001;	//把动态分配的内存地址中存储一个值


	cout << "id = " << id << " ;id的地址是:" << &id << endl;
	cout << "指针p_i对应的的值:" << *p_i << " ;指针p_i的地址:" << p_i << endl;


	double * p_d = new double;	//为存储一个double类型的值来分配一个内存
	*p_d = 1001.0;


	cout << "指针p_d对应的值:" << *p_d << " ;指针p_d的地址:" << p_d << endl;
	cout << "int指针占用的存储空间:" << sizeof(p_i) << endl;
	cout << "指针p_i对应的存储空间占用的空间:" << sizeof(*p_i) << endl;
	cout << "double指针占用的存储空间:" << sizeof(p_d) << endl;
	cout << "指针p_d对应的存储空间占用的空间:" << sizeof(*p_d) << endl;

	system("pause");
	return 0;
}




id = 1001 ;id的地址是:003BEF6C
指针p_i对应的的值:1001 ;指针p_i的地址:0090ED98
指针p_d对应的值:1001 ;指针p_d的地址:009C3898
int指针占用的存储空间:4
指针p_i对应的存储空间占用的空间:4
double指针占用的存储空间:4
指针p_d对应的存储空间占用的空间:8
请按任意键继续. . .


上面new分为为int类型和double类型的数据对象分配内存,这个过程是在程序运行过程中进行的,指会p_i,p_d分别指向这两个数据对象。
如果没有指针那么动态创建的数据对象内存单元就无法访问到了。
地址本身只指出了对象存储地址的开始,而没有指出类型(也就是说要用多少个字节),从而可以知道地址只是一个数字没有提供长度等信息,不管是什么指针它们的长度都是相同的。
但是由于有声明指针的类型则程序可以知道使用*运算符后分别是几个字节的值


注意:new分配的内存块与常规变量分配的内存块存储的位置不同,常规变量存储在栈stack内存区域中,new运算则会从堆heap或自由区free store的内存区中分配内存


在使用new请求内存时有可能存在内存不足而无法分配的情况,在这种情况下一般会产生异常,老系统中会返回0,在C++中值为0的指针就是一个空指针,C++会确保空指针不会指向有效的数据,因此它通常被表示运算符或者函数失败。


delete释放内存
当需要内存时可以使用new进行分配,在使用完之后需要使用delete运算符把它归还到内存池当中。当归还之后程序的其它部分就可以使用这些内存了。
delete的用法是 delete 指针名;
int * p_i = new int;
……
delete p_i;


注意这里归还p_i指向的内存并不会删除指针p_i本身,p_i可以重新指向另一个新分配的内存块。
要使用上一定要注意new与delete成对的使用,否则会产生内存泄露


对于已经释放的内存块不要再做释放,这样做会导致不确定的情况发生。


使用delete是把new分配的内存释放,这并不是说只能用于new指针,对而是用于new地址
int * ps = new int;
int * pq = ps;
delete pq; //这个是可以的,因为pq中存的就是用nwe 分配的地址
在程序中要注意,一般来说不要创建两个指向同一内存块的指针,这样做可能会导致重复delete。


使用new创建动态数组
在程序中如果只需要一个值,则只需要声明一个简单变量
对于大型点的程序来说一般是需要用到数组、字符串、结构
如果要编写一个程序,它是否需要数组从运行时用户提供的信息来决定,这个时候如果使用声明来创建数组,则在程序编译的时候就分配了内存,这种叫做静态联编,说明数组是在编译的时候加入到程序当中的
使用new时,如果在运行阶段需要数组则创建它,如果不需要则不创建。还可以在程序运行时选择数组的长度,这种是动态联编,说明数组是在程序运行时创建的,这种数组叫作动态数组。
静态联编时,必须在编写程序时指定数组的长度
运态联编时,程序在运行时确定数组的长度


动态数组的两个问题
1.如何使用C++的new运算符来创建
2.如何使用指针来访问数组的元素


使用new创建动态数组
把数组的元素类型和元素的数目告诉new即可。
int * pi = new int [10]; //[]中的数值就是包含元数数目
new运算符返回的是第一个元素的地址。上面的例子会把地址赋值给指针pi
当程序使用完new分配的内存块后,要使用delete来释放。对于new创建的数组需要使用delete的另一种格式进行释放
delete [] pi; //这个表示释放动态数组使用的内存块


使用new和delete时需要遵守的规则:
1.不要使用delete来释放不是new分配的内存
2.不要使用delete释放同一个内存块两次
3.如果使用new []为数组分配内存,则应该使用delete []来释放
4.如果使用new []为一个实体分配内存,则应使用delete来释放不要[]
5.对于空指针使用delete是安全的


为数组分配内存的通用格式如下:
type_name * pointer_name = new type_name[num_elements];
使用new运算符可以确保内存块足以存储num_elements个类型为type_name的元素,pointer_name则指向第一个元素




使用指针来访问动态数组中的元素
int * pi = new int [10];
这样分配了内存后,pi[0]则是第一个元素,pi[1]则是第二个元素,依次类推


/*
作者:xiesheng
时间:2017-07-08
版本:v1.0
说明:指针
*/
#include <iostream>


int main() {


	using namespace std;
	double * p3 = new double[3];	//分配了一个动态数组可以存储3个浮点数double
	p3[0] = 0.1;
	p3[1] = 0.2;
	p3[2] = 0.3;


	cout << "p3[1] = " << p3[1] << endl;
	p3 = p3 + 1;	//把指针向后移动一个位置
	cout << "当把指针p3+1后p3[0] = " << p3[0] << endl;
	cout << "p3[1] = " << p3[1] << endl;


	p3 = p3 - 1;	//把指针移到开始的位置


	delete[] p3;


	system("pause");
	return 0;
}




p3[1] = 0.2
当把指针p3+1后p3[0] = 0.2
p3[1] = 0.3
请按任意键继续. . .


上面的程序要注意,在使用delete[]的时候要把p3这个指针回到原来位置这样才可以给delete[]提供正确的地址。


指针、数组的指针算术
指针与数组基本等价的原因在于指针算术和C++内部处理数组的方式
算术运算中把整数加1后,其值会增加1,但是把指针变量加1,它增加的量是指针指向变量类型的字节数
C++中会把数组名解释为地址,也就是数组的首地址


/*
作者:xiesheng
时间:2017-07-08
版本:v1.0
说明:指针
*/
#include <iostream>


int main() {


	using namespace std;
	double wage[3]{ 10000.0,20000.0,30000.0 };	
	short stacks[]{ 3,2,1 };


	double * pw = wage;
	short * ps = &stacks[0];


	cout << "pw = " << pw << " *pw = " << *pw << endl;
	pw = pw + 1;
	cout << "把pw指针加1后的结果:" << endl;
	cout << "pw = " << pw << " *pw = " << *pw << endl<<endl;


	cout << "ps = " << ps << " *ps = " << *ps << endl;
	ps = ps + 1;
	cout << "把ps指针加1后的结果:" << endl;
	cout << "ps = " << ps << " *ps = " << *ps << endl<<endl;


	cout << "使用元素下标或数组名来获取对应元素的值:" << endl;
	cout << "stacks[0] = " << stacks[0] << endl;
	cout << "stacks[1] = " << stacks[1] << endl<<endl;


	cout << "*stacks = " << *stacks << endl;
	cout << "*(stacks+1) = " << *(stacks + 1) << endl << endl;


	cout << sizeof(wage) << " = 数组array的长度"<<endl;
	cout << sizeof(pw) << " = 指针pw的长度" << endl << endl;


	system("pause");
	return 0;
}



pw = 00FDF098 *pw = 10000
把pw指针加1后的结果:
pw = 00FDF0A0 *pw = 20000


ps = 00FDF0B0 *ps = 3
把ps指针加1后的结果:
ps = 00FDF0B2 *ps = 2


使用元素下标或数组名来获取对应元素的值:
stacks[0] = 3
stacks[1] = 2


*stacks = 3
*(stacks+1) = 2


24 = 数组array的长度
4 = 指针pw的长度


请按任意键继续. . .




指针部分的总结
1.指针的声明
typeName * pointerName;


2.给指针赋值
应该把内存地址赋给指针,可以对变量使用&运算符来获取变量对应的内存地址,new运算符返回的是一个未命名的内存地址。


3.对指针解除引用
对指针解除引用,就是要获得指针指向的值, 需要使用*运算符来操作


4.注意区分指针和指针所指向的值
int * pt = new int;
pt是指向int的指针,*pt不是指向int的指针,而是一个等同于int型的变量,注意pt才是一个指针


5.数组名
在多数情况下,C++的数组名就是指的数组的首元素的地址(数组的首地址)


6.指针算术
C++可以把指针和整数进行加减运算,加1的结果就是等于原来的地址值加上指向类型占用的字节数
两个指针相减得到的是一个整数,得到的是两个指针的差,注意只有当两个指针都同时指向同一数组时这样的相减运算才有意义


7.数组的动态联编、静态联编
静态联编,是使用数组声明来创建数组的时候,这个时候数组的长度在编译的时候就被确定下来了
动态联编,使用new []运算符创建数组时,在程序运行的时候为它分配内存空间,它的长度也是在运行时设置的,使用完了后需要使用delete[]来进行释放内存
int size;
cin>>size;
int * pz = new int[size];
……
delete[] pz;


8.数组表示法与指针表示法
arry[0] 与 *array 是一样的都是第一个元素的值
array[3] 与*(array+3)是一样的都是表示第四个元素的值





猜你喜欢

转载自blog.csdn.net/advent86/article/details/74838579