由浅到深全面解析指针,这次彻底把指针搞明白

本文深入介绍了C/C++中的指针概念,从变量的地址出发,阐述了指针作为存储地址的特殊变量的性质。通过实例展示了如何声明、初始化和使用指针,强调了指针类型与所指数据类型的一致性。同时,文章讨论了使用new运算符动态分配内存以及delete运算符释放内存的重要性,提醒开发者避免内存管理错误导致的程序崩溃。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

指针在c/c++里属于难理解的知识点,在学习指针之前先来回顾一下变量的知识点,我们知道数据存储在计算机里有3个基本属性(1.数据存储在何处,2.存储的值是多少,3.存储的数据是什么类型)必须要跟踪。下面用一个整形和一个字符串型来做例子,代码如下:

// pointer.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <string>

int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;
	int i=3;
	string str="zdsoft";
	cout<<"i="<<i<<endl;
	cout<<"str="<<str<<endl;

	getchar();
	return 0;
}

代码编译后运行如下图(图1):

 

在使用变量时我们常常不会关心变量的地址是多少 ,但不要忽略它的存在。可以在变量前面使用取地址运算符(&),来获得变量的地址。代码如下:

// pointer.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <string>

int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;
	int i=3;
	string str="zdsoft";
	cout<<"i="<<i<<endl;
	cout<<"str="<<str<<endl;
	cout<<"变量i的地址="<<&i<<endl;
	cout<<"变量str的地址="<<&str<<endl;

	getchar();
	return 0;
}

 代码编译后运行如下图(图1):

 变量的地址是系统给的,不同的系统分配变量的地址的方式可能不一样,这个可以不用关心。

接下来我们正式解析指针,前面说了变量也有地址,在变量前面使用取地址运算符& 就可以得到变量的地址。

那么指针到底是什么呢?它和地址有什么关系。用一句通俗点的话应该怎么概括或定义指针。

初学者难理解指针,我感觉至少有部分原因是因为在文字上对它进行了一次封装。也许这样说比较好理解:指针不是针,它也是变量,只不过是一种特殊的变量,是一种只能存放地址的变量。

因此,指针名表示的是地址。大部分时候我们目的不是想只知道地址,而是要得到地址里放的值或者说数据。在指针前面使用*运算符可以得到该地址处里的数据,感觉上就像是需要中间人介绍一样,因此*运算符称为间接值运算符或解除引用运算符

下面用代码来演示一下这个原理,具体代码如下:

// pointer.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <string>

int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;
	int i=3;
	string str="zdsoft";
	int* pInt; //声明一个指针
	pInt=&i; //把i的地址赋值给pInt指针
	cout<<"i="<<i<<endl;
	cout<<"str="<<str<<endl;
	cout<<"变量i的地址="<<&i<<endl;
	cout<<"变量str的地址="<<&str<<endl;
	cout<<"指针pInt="<<pInt<<endl;
	cout<<"指针pInt指向的值="<<*pInt<<endl;
	getchar();
	return 0;
}

 代码编译后运行如下图(图3):

通过上面的文字解析,再加上代码和代码运行结果图 ,相信大家能够对指针有个基本的认识了,以及指针和变量的不同之处。上面的代码还演示了如何声明一个指针。从上面图3中发现指针pInt里地址指向的值是变量i的值。(这里强调是“指向”,而不是把变量i的值复制过来)。为了证实这一点,只需要对变量的值进行修改后来看看指针里地址的值是否也跟着变了。接下来我们用代码来验证一下:

// pointer.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <string>

int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;
	int i=3;
	string str="zdsoft";
	int* pInt; //定义一个指针
	pInt=&i; //把i的地址赋值给pInt指针
	cout<<"i="<<i<<endl;
	cout<<"str="<<str<<endl;
	cout<<"变量i的地址="<<&i<<endl;
	cout<<"变量str的地址="<<&str<<endl;
	cout<<"指针pInt="<<pInt<<endl;
	cout<<"指针pInt指向的值="<<*pInt<<endl;
	i=4;//变量i被修改了
	cout<<"变量i修改后指针pInt指向的值="<<*pInt<<endl;
	getchar();
	return 0;
}

 代码编译后运行如下图(图4):

反之,修改*pInt,变量i的值也同样被改变。总结:*pInt等于i,*pInt和 i 是同一个东西的两种叫法 。

 从上面指针的声明上可以看到指针也是有类型的。我们知道变量的类型就是变量里值的类型,可是前面说过指针是只能存放地址的变量。既然只能存放地址,而地址又都是整数型(这里一般用16进制表示),难道指针只有int类型?如果你这样想那肯定是理解思路的方向被误导了。在这一点上指针变量和普通变量的区别就出来了。正确的理解方向是这样的:指针的类型和存放在指针里的地址没有关系,而是和该地址里的值有对应的关系。即,int类型指针存放的地址,该地址里必须是int类型的数据。为了证实这一点,我们把上面代码里变量str的地址放到指针pInt里,看看会发生什么。代码如下图(图5):

 

看到没有,直接提示错误:不能将string*类型的值分配到int*类型的实体。

那么再声明个string类型指针,把变量str的地址放到里面看看,代码如下:

#include "stdafx.h"
#include <iostream>
#include <string>

int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;
	int i=3;
	string str="zdsoft";

	int* pInt; //声明一个类型为int的指针
	string* pStr; //声明一个类型为string的指针

	pInt=&i; //把变量i的地址赋值给pInt指针
	pStr=&str;//把变量str的地址赋值给pStr指针

	cout<<"变量i的地址="<<&i<<endl;
	cout<<"指针pInt="<<pInt<<endl;
	cout<<"变量str的地址="<<&str<<endl;
	cout<<"指针pStr="<<pStr<<endl;
	getchar();
	return 0;
}

 编译后运行结果如下图(图6):

顺便说一下声明指针的格式,我上面用的格式是C++程序员常用的格式 int* pVar,但是C程序员习惯上喜欢把星号和指针名挨在一起 int *pVar。其实这只是一种书写习惯,你甚至可以星号前后都不要空格 int*pVar。但是如果多个指针名声明时写在一行的话,必须在每个指针名前面都要加上一个*号,像这样: int * pVar1, * pVar2; 而如果写成了这样:int * pVar1, pVar2; 就成声明了一个指针(pVar1)和一个变量(pVar2)。

指针在声明时同样可以初始化,上面声明指针的代码可以写成这样:

int* pInt=&i;
string* pStr=&str;

此时指针pInt是变量i的地址,指针pStr是变量str的地址。 

至此,如果你认真的看完了上面的文章,应该对指针有了初步的认识了。但是,如果你认为自己这就会用指针了,那就大错特错了,这只是初步了解。我们离全面了解还有很大一段距离。

使用指针时容易发生的危险,在C++中创建指针时,计算机只会分配用来存储地址的内存,但不会分配用来存储指针所指向数据的内存。为数据提供空间是一个独立的步骤,忽略这一步无疑是自找麻烦,比如下列代码:

// pointer.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <string>

int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;
	int* pInt;
	*pInt=123;
	cout<<"pInt="<<pInt<<endl;
	cout<<"*pInt="<<*pInt<<endl;
	getchar();
	return 0;
}

编译时没有任何问题,但运行时程序就会崩溃。如下图(图7) 

 从上面代码看,pInt确实是一个指针,但是它指向哪里呢?没有将地址赋值给指针pInt,那么123将被放在哪里呢?由于指针pInt没有被初始化,它可能是任何值。不管值是什么,程序都将它解释为存储123的地址。如果pInt的值恰巧是程序代码的地址,那么程序代码将被改成乱码了,导致程序崩溃。这种错误可能导致最难跟踪的BUG。

总结:一定要在对指针使用解除引用运算符(*)之前,将指针初始化为一个确定的,适当的地址。这就是程序员的清规戒律。

为指针指向的地址数据分配内存空间

前面说了为指针指向的地址数据分配内存空间是一个独立的步骤,这里主要讲使用new来为数据分配内存,这是一个很好的方法,但不是唯一的方法。

new是一个运算符,它可以在程序运行阶段为一个值分配一段儿未命名的内存,并且只能通过指针访问到这个值。这和普通变量有着本质的区别,普通变量是在程序编译阶段就为值分配好了内存,可以用变量名访问,也可以用指针指向它进行访问。使用new分配内存空间的代码如下:

int* pInt=new int;

new int告诉程序,需要适合存储int的内存,new运算符根据后面的数据类型来确定需要多少字节的内存。然后它会找到符合这样条件的内存,并且把地址给pInt,pInt是声明指向int的指针。这里只是拿int类型在做例子,当然,new运算符可以为任何数据类型分配内存空间(除了基本类型,也可以是结构),通用格式如下:

typeName * pointerName = new typeName;

从上面这个通用格式看,需要在前后两个地方指定数据类型,前者是声明指针的类型,后者是new要找到什么样的内存。前后要相对应起来,且不能省略其中一个。下面再用代码来演示一下new用于其它类型,这里还是拿int和string来做例子。代码如下:

// pointer.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <string>

int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;
	int i=100;
	int* pInt=new int;
	*pInt=100;
	string* pStr=new string;
	*pStr="zdsoft";

	cout<<"变量i="<<i<<",变量i的地址="<<&i<<endl;
	cout<<"指针pInt的值="<<*pInt<<",指针pInt的地址="<<pInt<<endl;
	cout<<"指针pStr的值="<<*pStr<<",指针pStr的地址="<<pStr<<endl;
	getchar();
	return 0;
}

代码编译后运行结果如下图(图8):

从上图可以看出虽然变量i的值和指针pInt的值相同(都是100),但它们却是两个不同的内存空间,没有关系,各自独立。还可以看出使用new分配的内存空间可以用*运算符像变量一样访问里面的值。

使用delete运算符释放new分配的内存

前面讲了使用new运算符分配内存,但是计算机可能会由于没有足够的内存而无法满足new的请求,这种情况下会发生异常。所以在使用完new分配的内存空间后必须使用delete运算符释放掉相应的内存空间。new运算符和delete运算符必须是成对的出现,使用delete运算符释放内存空间时,后面要加上指向内存块的指针,且这些内存块最初是由new运算符分配的,代码形式一般如下:

int* pVar=new int;
    ......//code
    ......//code
    ......//code
delete pVar;

不要使用delete运算符释放由声明变量时分配的内存空间。new运算符分配的空间和变量分配的空间所在的区域不同,一个是在堆里,一个是在栈里。这个要讲清楚就有点深了,且要说到汇编里的知识。在这里你只要记住不要这样做就行了。

还有一点,就是不要创建两个指向同一个内存块的指针,因为这样做会增加删除同一个内存块两次的可能性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值