再上一篇,咱们介绍了基本调试。以前也说了,之因此把调试放在前面讲是由于后面的文章基本都会用到调试。观察咱们的程序到底发生了什么。让咱们可以直接明了的看清楚问题的本质。本篇将深刻一点介绍指针这个让无数初学者畏惧的东西。但愿你们再看完本篇以后能对指针有新的认识,以后再也不害怕它。以为它就那么回事。那下面咱们就努力攻克这个令咱们“害怕”的东西。程序员
咱们可能进入大学读计算机相关专业,基本第一门编程语言就是C语言。可能老师们也喜欢跟学生总结整本书难点在什么地方。那么指针必然是老师提到的难点之一。我我的以为这样的总结还不如不总结,缘由很简单,由于这样会给学生心理负担,学到指针的时候那根弦都崩的很紧。从骨子里就认定了它有难度,初学者脆弱的心灵所以而感到害怕。换个角度,为何咱们不能以为指针也就那么回事?没有什么特别的嘛,哪里难了嘛!这样不是既有信心又有兴趣去搞定它?说了这么多,只想强调一点,什么东西都报怀疑态度未必是件坏事。指针不是老师说的那么恐怖。好了,下面咱们就系统的从几个角度去理解指针。编程
概念上理解 所谓指针,没学过编程语言的可能会以为是指南针或者鼠标的指针。呵呵,这种说法虽然差之千里,可是也不是毫无道理。为何呢?好比指南针,以C语言指针的角度去思考,那么指南针之因此叫指南针由于它始终是指向南方的。对!南方,顿时恍然大悟。联系起来能够想象成:指南针就是指针变量,它指向南方。南方便是指南针这个变量的值。那么 指南针(指针) == 南方(这里的==能够理解成if( a == 100 )里面的比较运算,下文同理)。此时咱们又发现南方有座大山,大山在南方。哇,又恍然大悟。那这么说来大山就生在南方,假如咱们想象南方就是内存的某个地址单元。大山就是这个地址单元的值。所以又有等式:*指南针 == 大山。数组
问题一:这里多了个星号是为何?(看完后面我但愿你能答出这个问题)编程语言
再来,咱们就傻瓜的认为指针就是咱们经常使用的鼠标在桌面熟悉的那个箭头。咱们的箭头在咱们的控制下,咱们想点哪儿就点哪儿。哈哈,如此神奇。例如咱们想点桌面的“记事本”图标。因而咱们将箭头指向那个图标,而后双击。便打开了咱们之前留下的一些记事。咱们就能看到了。从这个简单的操做又可让咱们产生联想了。箭头就比如咱们程序里面的指针,咱们在想要打开记事本的时候,就箭头指向它。在这个时候,箭头指向了记事本。箭头(指针)== 记事本。在双击打开记事本以后,里面有内容,好比是:“我爱你!”。内容在记事本里面。那么这些内容就能够理解成是记事本里面存放的值,只不过这些值是以字符串的形式存储在里面的。所以有表达式:*箭头 == “我爱你”。ide
上面举了两个比较形象一点的例子,相信你们脑子里有一个基本的关系链了吧。就是 指针----->地方----->东西。也就是某个地方有个东西。这个地方被一张藏宝图记录下来放到一个隐秘的地方了。这张藏宝图就是所谓的指针,指明咱们想要找的宝贝的那个地方。清楚了形象的意思,再来句专业的形象一点的概念:指针就是指向某个内存地址的一个变量,这个变量的值就是存放的这个内存地址。这个内存地址里面又存放了咱们的数据。这样就构成了一个关系。咱们能够方便的经过指针找到该地址而且向这个地址写入数据或者读取该地址的值。比较直接的读写方式就是赋值。函数
用法上理解 在前面了解了指针的概念,相信你们已经火烧眉毛想看到直接的代码了。很正常,程序员就喜欢最直接了当贴出代码。可是假如咱们没有理解到他的意义,贴出的代码可能你也只是粗略的一看。认为本身已经清楚不已,这样每每会漏掉很多细节。也会少不少精彩。google
指针也是变量(你们不可能不知道什么是变量吧,若是不知道本身拍本身砖头,而后去google)。不要由于它多了个星号就以为它很特别,它就是个变量而已。由于是变量,那么指针也就有类型了,这个类型能够理解成就是指针的类型。它是某个类型就只能在语法上赋值为这种类型的值。也能够指针的类型理解成是他指向内存的地址里面存放的数据的类型,好比:spa
int* p; // 它就表示指向的内存地址里面存放的是int类型的值。指针
int a = 100;调试
p = &a; // 知道了吧,简单的赋值操做就让p指向了a所在的内存地址,在CC++语言里用&去某个变量的内存地址。简单的语句,我们干了很多事,咱们将变量a的内存地址取了出来,而后赋值给了指针p。是否是咱们的对应关系:
指南针 南方 大山
鼠标箭头 记事本 存放的记事
藏宝图 大山 宝贝
指针 内存地址 数据值
p &a a(100金币)
根据上面的对应关系你们更清楚了吧,既然指针有类型,那么咱们就多看一些类型:
char* pName = "masefee"; // 这一句,是一个字符类型的指针。既然咱们说的指针是存放的内存数据的地址,那么这里的就是后面"masefee"字符串的首地址,何为首地址?首先咱们知道这个字符串确定是存放在内存里面的,而后咱们又知道内存的最小存储单元是字节,既然是字节,那么这个字符串总共占用的字节数就是8字节。7个字母加一个结束符'/0'一共8个。你们又会问了,为何须要结束符?缘由很简单,咱们这个pName指针只指向了这个字符串的起始地址(首地址),它并不知道这个串有多长。假如没有结束符,咱们取这个字符串的时候怎么知道取到哪里为止呢?所以'/0'就专门用来结束,这个也是个字符,在内存里面存放的值就是数字0.意思就是说'/0'字符的ASCII码就是0。扯远了,前面说了一共占8字节,内存中不可能在同一地址下存放8个字节哟。一个内存地址只能存放一个字节。因此这个"masefee"就占用了8个内存地址,首地址就是m字符的地址。你们能够将pName选中拖放到内存窗口的地址栏观察。形如:
0x0012fed4 m 首地址,pName的值就等于0x0012fed4这个地址值。
0x0012fed5 a
0x0012fed6 s
0x0012fed7 e
0x0012fed8 f
0x0012fed9 e
0x0012feda e
0x0012fedb 0
上面就是数据在内存里面存放的位置关系,逐字节存储。这里注意,在内存里面一般咱们看到的是16进制。这里只是为了更形象直接写成字符了。
short level = 2500;
short* pLevel = &level;
这两句相信你们也不难理解了,pLevel指向的就是level所在的内存地址。先看看在内存中的存放关系:
0x0012fed4 0xC4
0x0012fed5 0x09
这里只有2行是由于short只占2字节(16位)。那为何奇怪的变成0xc4 0x09呢? 再观察0x0012fed4比0x0012fed5小,咱们知道2500的十六进制数十0x09C4。这里为何倒过来存放的呢? 高位存放在了后面,低位存放在了前面?缘由是由于CPU,有的CPU是顺着存放有的是倒过来存放的。这里咱们不追究,记住就能够了。这样一来,pLevel的值究竟是哪一个地址呢?答案很简单,小的那个。也能够理解成首地址。从小的地址往大的地址读取是人之常情撒。那为何没有向字符串那样有结束符呢?缘由也很简单,short咱们是知道长度的,不须要结束符。这里记住一点,咱们将一个大于1字节的基本数据类型变量(int short等)的地址赋值给指针后,该指针指向的地址咱们能够将该变量理解成只在一个字节上(地址上)。该指针指的内存地址里面的值就是该变量值。固然你也能够就根据他的存储占用字节,理解该指针就是指向的这几个字节的首字节。另外,若是level的值不够占用2字节,另一个字节就会被自动填充0。这里咱们要取pLevel指向的地址里面的值能够用语句:
short lv = *pLevel; // *就表示间接访问该指针所指向的内存里面的值,这是CC++语法,就是这么规定的,后台处理就别管了。知道取指针所指向的内存地址里面的值的方式就是在前面搞一个星号,称之为间接访问。这样取出来后,lv的值就等于level的值:2500.
char className[ 5 ] = { 'M', 'a', 's', 'e', '/0' };
char* pClassName = className;
问题二: pClassName所指向的地址是数组里面哪个字符的地址?这里为何直接赋值没有加&符号?
int array[ 3 ] = { 1, 2, 3 };
int* pArray = array;
这两句在问题二解决以后天然就会了,这里须要注意的是,int是4字节,占用的内存地址将有4个,假如该int变量值很小占用不到4个字节,剩余字节将填充为0.
本质上理解 谈到本质,指针能够说就是地址。为何?由于他的值就是某个变量的所在内存地址。由于咱们一般使用的电脑是32位机,那么咱们每一个字节的地址就占用4个字节,地址是16进制数,是整数。因此任何指针存放的值都是一个整数。因此没有特殊的状况(这种状况咱们基本碰不到,这里就不说了)咱们任何一种类型的指针都占用4字节,由于它存放的是32位整数。所以前面咱们定义声明指针如:
int* p; char* p; 这里的int,char并非表明指针自己的类型,而是指向的地址里面的值的类型。这里的p存放的就是一个32位的整数,你能够大胆的认为它就是一无符号整数。只不过这个无符号的整数是具备地址信息。
那么有的同窗就会疑问了,既然是存放的一个整数,那么这个指针又是被存放在哪儿的呢?前面不是说了吗?咱们指针也是变量,那么指针也就有它本身的内存地址,也就是用来存放这个指针的。好比:
int* p = &a;
int** pp = &p;
这里就不得不说说二级指针了,其实也没有什么特别的,二级指针名义上就是指针的指针。二级指针存放的是存放一级指针的那个内存地址。就比如:个人抽屉里---->藏宝图--->大山---->宝贝。这里的抽屉就是二级指针。藏宝图也仍是要放到一个地方藏起来撒。否则都发财了。对吧!
谈到本质,指针既然是存放的整数,那么咱们能够大胆的强制类型转换:
int a =100;
int* p = &a;
unsigned int addr = ( unsigned int )p; // 将变量p强制类型转换成无符号整数,此时它就是一个实实在在的整数了,这个整数就是变量a的内存地址了。是否是更加以为指针也就那么回事了?呵呵,咱们继续。
说到这里,咱们不得不说说咱们前面提到的void类型了,以前咱们将void一般用来表示函数没有返回值。在这里,咱们将了解到它的另外一面。那就是:
void* p; // 无明确指向类型的指针,意思就是p并不知道他所指向的内存地址里面的数据是什么类型。看具体用法:
int* pInt;
p = ( void* )pInt;
这里的int型指针被强制转换成无类型的,既然是无类型,那么就不可能使用:
int var = *p;
这样加上星号,咱们前面已经说了是间接访问p所指向的内存地址下面的值,这里p是存放了内存地址没错。可是这里是无类型的,咱们的p就不知道到底该读取多少个字节到var变量里。可能有的朋友又会说那假若有结束符呢? 呵呵,也是不行的,既然是无类型,怎么能乱下结论必定是有结束符的数据类型呢?所以在C++语法上面是不容许间接访问void*指针的。 若是想读取这个地址里面的值,那么此时就至关灵活了。你能够把这个void型指针强制类型转换成任何类型的指针。而后赋值过去。这样作是危险的,可是有时也是必须的。你在这样作的时候得保证这个void指针强制赋值过去后,你的数据是没有问题的。
假如:
int a = 0x7fffffff;
int* pA = &a;
void* p = ( void* )pA;
short b = *( short* )p; // 这里是先强制类型转换,而后再间接访问值,因此是*( short* ).
这时b的值将会把a的值进行截断,由于short的范围比int的范围小。这样数据就会出问题。这里清楚了吧。
说到这里又不得不说说空指针和野指针了,空指针既然叫空指针,他的意思就是这个指针所存放的内存地址为0,不如:
int* a = 0; 指针a所存的地址就是0x00000000,这个地址专门用于指针初始化或者清零,内存的这个地址是被保护起来的,既不能往里面写数据也不能读里面的数据。若是试图如:
int* pNULL = 0;
int var = *pNULL;
这样将出错,出错信息是:
什么什么.exe的什么什么地方 未处理的异常: 0xC0000005: 读取位置 0x00000000 时发生访问冲突 。
若是试着写:
*pNULL = 100;
将出错:
什么什么.exe的什么什么地方 未处理的异常: 0xC0000005: 写入位置 0x00000000 时发生访问冲突 。
因此,咱们不少时候不能保证某个指针是否正常不为空指针时就得加以判断:
if ( pNULL )
*pNULL = 100;
这样程序就不会给空指针赋值了,读取一个道理。之后讲函数的时候会进一步讲解空指针的防范。
另外就是野指针,所谓野指针就跟野猪一个道理,处处乱跑。野指针就是指向了不应指向的内存地址,假如这个内存地址不可写或者不可读,咱们的程序将会崩溃。假如这个内存没有被保护,读写均可以的话,这样的错误将很难找到。数据将会出错。会出现不少莫名其妙的现象;不少时候会由于越界恰好修改了某个指针的值,这样指向的内存地址就有可能不是咱们想要的地址就形成了野指针。函数那一篇咱们也将详细讲解野指针的避免。由于在函数里面讲野指针将更具体直观。仍是举个野指针的例子吧:
int* p = &a; // 正常
p = ( int* )0x12345678; // 这句不要奇怪,既然指针可以转换为整数,整数一样能够转换为指针,这里转换事后,p的值就等于0x12345678;这个内存地址并非咱们想要的,也极可能是非法的。这里的p就野了。也就是野指针。
这里再简单说说数据拼接,假如我有一个short类型的数组:
short a[ 2 ] = { 0xffff, 0xeeee };
short* pA = a;
这样一来,pA[ 0 ] 就是0xffff,pA[ 1 ]就是0xeeee。再有一个:
int* pInt = ( int* )pA; 将pA转换为int*(指针之间能够随便转换,只要确保不出错,前面已经说过),short*变成了int*。咱们知道2个short恰好等于一个int所占的内存。而后咱们操做这个pInt:
int var = *pInt; 这里也能够直接使用:int var = *( int* )pA; 那么此时var取出来的就是4个字节的内存数据,咱们知道short数组a有2个元素恰好占用4个字节,并且数组是连续存放的。那么var将是这两个元素的组合值。
问题三:大家的电脑里组合出来的var的值是什么?
问题四:假如a数组有4个元素,而后转化成int*的pInt,那么var有几个?该怎么获取?
好了,不知不觉写了整整接近4小时了,这篇终于差很少了。累!这篇较之之前的文章要更深刻一点,因此篇幅比较大一些。但愿不懂的朋友多读几遍,理解其中的意思。相信大家的悟性!呵呵。打算睡觉了。Good morning!
四个问题必定得好好思考哟!
【C++语言入门篇】系列: