第4章 指针和引用
指针和引用是C++中两个重要的复合数据类型,使用范围十分广泛,若使用得当,它们就是程序员手中的神兵利器,但如果程序员对其理解肤浅,胡乱应用,只会让事情一团糟,本章将带领你一步步学会使用指针和引用。
本章主要涉及以下知识点。
❑指针的定义与使用:介绍关于指针变量的概念及其使用。
❑指针的运算:介绍对于指针的一些常用操作方法。
❑动态内存分配:介绍C++中的内存管理与分配方法。
❑常量指针:介绍关于C++中的常量指针的概念及特点。
❑指针与数组:讲解指针数组的声明与使用,同时对指针数组的概念进行了介绍。
❑特殊的引用:介绍C++中引用的概念、特点及其使用方法。
4.1 指针的定义与使用
通过前面的学习,我们了解到内存是按字节排列的存储空间,每个字节都有一个编号,被称为“地址”,程序中用到的数据和声明的变量就存放在这一个个的字节中,不同类型的数据和变量占用的字节数不同,如short型变量占用两个内存字节,习惯上将某个变量占用的字节数称为内存单元,内存单元占用的字节数随其存储变量的数量而有所不同。
通过变量名可以访问该变量对应的内存单元,实际上,我们还可以直接通过地址来访问某个内存单元,为形象地说明这个问题,请看下面这个比喻。
假设我们要去A公司给B经理送信,有两种方法。一是告诉传达室人员,这封信要送给B经理,由传达室人员转送,二是知道B经理在C楼D层E房间,通过这个地址直接将信送给B经理。此例将内存比做A公司,B经理就是某个变量,C楼D层E房间是变量所占用的内存空间的地址,传达室人员可以理解为编译器。在程序中,我们可以直接通过名字来访问某个变量,也是借助了编译器的帮助,编译器维护了一个变量名和地址的映射表,同时,如果知道了某个变量内存单元的地址,我们就可以直接对这块内存进行访问,为了存储地址信息,C++提供了指针这个复合数据类型。
与使用变量名相比,使用地址(指针)访问内存单元有更大的灵活性。通过传达室人员只能转发消息,转送东西,而直接到办公室则可以和B经理直接进行沟通,但这种灵活性也可能会带来一定的危险,有人可能会直接到办公室影响B经理的正常工作。因此,指针是把“双刃剑”。
4.1.1 声明一个指针变量
指针是一种数据类型,基于该类型声明的变量称为指针变量,该变量存放在内存中的某个地址,和普通的变量一样,在使用指针变量之前应先对指针变量进行声明。
类型指针变量名;//如intpNum;
“*”表示语句声明的是一个指针变量,类型指定了指针所指的内存单元的数据类型。
注意
可以将int*理解成一种复合类型,是指向int型数据的指针。
应当注意下面的语句。
int*pNum1,pNum2;
声明了一个指针(pNum1)和一个int型变量(pNum2),在一次性声明多个指针时,每个指针变量名前都要加,“intpNum1,*pNum2;”便能一次性声明了两个指针变量。
注意
有的程序员提倡传统的用法,将星号与指针变量名靠近,这样,“int*pNum1,pNum2;”的意义似乎更好理解,使用哪种写法取决于你的习惯。
指针变量存储的内容是内存中某个字节的地址,指针变量占用的内存字节数随系统的不同有所不同,在普通的windows 32平台上,指针变量占4个字节,但在其他一些内存模块中,指针变量可能只占用两个字节。简单地说,指针变量占用的字节多少完全取决于程序的内存空间的大小。
代码4.1 指针变量的值及其占用的字节数PointerSample
<————————————————-文件名:example401.cpp——————————-> 01 #include<iostream> 02 int main() 03 { 04 using namespace std; 05 double num=3;//声明一个double型变量num 06 double*pNum;//声明一个double型指针变量pNum 07 pNum=#//用num的地址为pNum赋值 08 cout<<"pNum在内存中的位址是:"<<&pNum<<endl; 09 cout<<"pNum的值为:"<<pNum<<endl; 10 cout<<"num在内存中的地址为:"<<&num<<endl; 11 cout<<"可以使用指针访问num:"<<*pNum<<endl;//输出指针pNum占据的内存大小 12 13 cout<<"pNum(指针类型)在内存中的字节数:"<<sizeof(pNum)<<endl; 14 //输出double变量num占据的内存大小 15 cout<<"num(double型)在内存中的字节数:"<<sizeof(num)<<endl; 16 return 0; 17 }
输出结果如下所示。
pNum在内存中的位址是:0012FF74 pNum的值为:0012FF78 num在内存中的地址为:0012FF78 可以使用指针访问num:3 pNum(指针类型)在内存中的字节数:4 num(double型)在内存中的字节数:8
【代码解析】代码第5行,代码“double num=3;”声明了一个double型变量num,并对其赋初值为3,编译器将在内存中开辟一块8个字节的区域,将num和这8个字节关联起来,并将这8个字节初始化为3,代码第6行double*pNum声明了一个指向double类型的指针,但并没有对其进行初始化,编译器将在内存中开辟一块4个字节的内存区域,并将其和pNum关联起来,同时,代码第7行,语句“pNum=#”是对指针变量赋值,“&”称为取地址符,含义是将num在内存中的地址“0012FF78”赋值给pNum,代码4.1的内存模型示意图如图4.1所示,每个字节有8个二进制位,用内存地址来标识,对指针变量而言,其中存储的内容恰恰是某个数据在内存中的地址。
可以通过“指针变量名”来对指针所指向的内存单元进行访问,代码4.1将pNum声明为一个指向double类型的指针。因此,编译器知道pNum是一个以double类型存储的值。这种访问方式称为间接访问,以区别于通过变量名“num”访问该内存单元的字节访问方式,从本质上说,两者是等价的。对本例而言,都是对0012FF78~0012FF7F这8个字节(double型)进行访问。
图 4.1 指针和变量内存模型示意图
注意
声明语句“doublepNum;”和间接访问“pNum”中的“*”意义不同,前者称指针运算符,与前面的double结合构成“指向double型变量的指针”类型,后者是间接引用符,不要将两者混淆。
注意
内存单元的分配是由编译器根据程序中的声明语句自动完成的,程序员关心的是借助某个变量的地址来对其进行访问和控制,往往不在意该地址的具体数值。
对同一个系统,指向不同类型的指针变量占用的内存字节数一般是相同的,但也有例外,见代码4.2。编译器通过指针变量的声明来确定其所指数据的类型和长度,从深层次讲,指针指向的是内存中的1个字节,编译器会根据该字节和声明语句中指针变量的类型来决定对那几个字节进行操作,这对所有类型的指针变量是等价的。
代码4.2 指向不同类型的指针变量占用相同的内存字节数SizeofPointer
<—————————————文件名:example402.cpp——————————————> 01 #include<iostream> 02 int main() 03 { 04 using namespace std; 05 double*pNum,num=0;//double型指针pNum、double型变量num 06 pNum=# 07 short*pSnum,sNum=0;//short型指针pSnum、short型变量sNum 08 pSnum=&sNum; 09 char*pChar,chr='A';//char指针pChar、char型变量chr 10 pChar=&chr; 11 cout<<"指向double型的指针变量pNum占用的内存字节数:"<<sizeof(pNum)<<endl; 12 cout<<"指向short型的指针变量pSnum占用的内存字节数:"<<sizeof(pSnum)<<endl; 13 cout<<"指向char型的指针变量pChar占用的内存字节数:"<<sizeof(PChar)<<endl; 14 return 0; 15 }
【代码解析】代码第11~13行是调用sizeof()函数,来取得当前变量所占用的内存字节数,该函数的应用将在后面进行讲解。
输出结果如下所示。
指向double型的指针变量pNum占用的内存字节数:4 指向short型的指针变量pSnum占用的内存字节数:4 指向char型的指针变量pChar占用的内存字节数:4对前面提到的概念进行辨析和总结,如下列代码所示。 double num=3; double*pNum; pNum=#
❑num:double类型的变量。
❑pNum:指向double类型的指针变量,其值是num的地址。
❑&num:返回变量num的地址,与pNum等价。
❑*pNum:pNum所指的变量,间接访问方式,与num等价。
❑&(*pNum):与&num(即pNum)等价,num的地址。
❑(&num):与pNum(即num)等价,变量num。
注意
指针变量常常以字母p(代表pointer)开头,以增强程序的可读性。