指针作为程序设计语言中的一个重要概念,在教学和实践项目中都显得非常重要。在此作一个有关C++指针的专题介绍,旨在总结指针的概念、特点以及应用情况,以帮助广大C++初学者更好地使用C++语言解决他们在学习以及工作中遇到的困难。
1、程序、进程与内存
当我们说编写一个具有什么功能的程序,或者运行某一个程序时,这里面所设计的到的程序是一个静态的概念,并不会产生任何结果;而当我们说一个正在运行的程序,或者某个程序占用了多少内存的时候,这样的程序则是一个动态的概念,即进程。进程将会占用系统资源以完成其预定义的功能并产生相应的结果。不过人们在实际工作中有时候并不是太过于区分程序和进程的概念,而往往以程序论之。
当一个程序要运行的时候,操作系统首先需要把该程序调入内存,这样就创建了一个进程,这个过程称为加载。加载所要做的工作很多,但是最基本的任务是把程序代码以机器可以理解的方式调入内存,并且程序运行所需要的数据也会调入进程的地址空间。为了提高对内存中数据的访问速度,操作系统都是对内存地址进行编号了的,就好比如果你去小区找一个人,如果你知道这个人住小区的确切地址,比如4栋3单元210,你就可以根据这个地址直接找到这个人。相应的,如果你知道了存储所需要数据的内存地址编号,就可以直接根据该编号访问内存中的数据了。这里所提的数据既可以是代表数学意义上的数值,也可以是程序设计意义上的程序代码(二进制代码),因为程序在运行时,其代码也是被加载进了内存的。
2、内存模型
计算机系统中,内存的基本单位是字节。因此,内存编号也是以一个字节为一个基本单位的。内存编号的起止范围决定于操作系统的可寻址范围。例如,如果操作系统是16位的,即是说该操作系统支持16位的寻址空间,因此其地址范围从20~216-1。这就决定了对应于该16位操作系统的内存地址编号从0x0000-0xffff,见图1。
同样,如果操作系统是32位的,即该操作系统支持32位的寻址空间,其地址范围从20~232-1,决定了对应于该32位操作系统的内存地址编号从0x00000000-0xffffffff。
2.1、内存数据格式
当得到某一个有效内存地址编号的时候,我们就可以操作以该编号为开始的内存数据了。为什么这里面要强调“有效”二字呢?操作系统为了保证自身的安全和稳定,以及一些其它因素,由用户所创建进程的运行地址空间范围是有限制的,访问内存数据所依赖的地址编号也是严格限制的,这样可以避免用户有意或无意地访问了操作系统核心数据,保证了操作系统的安全和稳定。
这样,当得到一个有效内存地址编号时,我们怎么确定内存中是何种格式的数据,以及如何读取或更改所需要格式的数据呢?一般来说,内存中的数据主要分为两类:数值型和字符串。
数值型的数据就是具有数学意义上的量,如1,2,1.234567,-2,-3e-4,等等;而字符串则表达了具有一定文法意义上的文本,如“你好!”,“hello, world!”等等。因此,在访问内存数据之前,首先需要确定访问的数据类型是什么。除此之外,还要确定要访问内存的大小,即从某一内存地址编号开始要访问多少个字节的内存。对于数值型的数据来讲,内存的大小决定了量的范围,而对于字符串型的数据来讲,内存的大小则决定了所表达的文本信息量。
例如,假设编号为0x80000000的内存地址是一个有效的可访问地址,从该地址开始的连续4个字节内存的二进制内容依次为01000001,01000010,01000011,00000000,如图2。
可以用下表概括从地址编号为0x80000000开始,访问的数据类型与内存大小所决定的访问结果。
表1. 数据类型与内存大小决定的访问结果
大小 类型 |
数值型 |
字符串型 |
1字节 |
65 |
“A” |
2字节 |
16706 |
“AB” |
3字节 |
4276803 |
“ABC” |
4字节 |
1094861636 |
“ABCD” |
由此可以看到,即使获得了一个有效的内存地址编号,我们也需要用数据类型和内存大小来约束通过该编号进行的内存数据访问,以得到确切的结果。
3、C++语言中的指针
在C语言中,指针的概念就已经存在,并且凭借指针的强大,赋予了C语言编程灵活、可直接访问内存、执行效率高等一系列的优点,可以毫不夸张地说,指针就是C语言的灵魂。作为与C语言完全兼容的C++语言则完美继承了指针的概念,并依托面向对象程序设计语言的特点,扩展了原有C语言指针中的概念,使得指针的使用达到了一个新的高度。
但是,C++语言的初学者往往被指针弄得莫名其妙,即使当时能够明白指针的含义,然后看到某些源代码中的有关指针用法后,原本以为已正确理解指针的感觉荡然无存。指针这种被C++初学者认为神秘的东西是学习C++语言的一个障碍,而这个障碍可能使诸多C++学习者转向其它面向对象编程的语言了。
本节将通过三个小节的内容来阐述指针,希望对初学者有所帮助。
3.1、C++语言中指针的本质
C++语言中的指针本质是什么?恐怕这个问题是理解和使用指针的一个基本前提。其实,指针并不神秘,因为指针实际上就是一个变量,即指针的本质是变量。
为什么这么说呢?我们在使用C++语言编程的时候,经常所说的指针实际上是通过变量来表达的,即指针变量。由于程序员口头之间在交流时,为了方便或者某些原因,常常把“变量”两个字省略,但是这并不阻碍交流的结果,因为程序员们对此心领神会。在变量前面加上“指针”二字构成“指针变量”,又在一定程度上表明了指针变量和其它类型的普通变量有一定区别,即指针变量具有自身的特殊性。
与普通变量相比较,指针变量具有以下几点特殊性,这也是C++初学者理解指针的难点所在,特别是后两条:
Ø 定义方法与普通变量不同
Ø 指针变量的值与普通变量所表达的含义不同
Ø 指针变量的运算方式与普通变量不同
3.1.1、指针变量的定义方法与普通变量不同
int i; // 定义一个int类型的普通变量,变量名为i
int *i; // 定义一个int类型的指针变量,变量名为i
可见,一个星号(*)决定了一个变量在定义时是普通变量,还是指针变量。我们应该养成良好的代码编写风格,在定义指针变量时,为了强调该变量与普通变量之间的区别,经常在命名该指针变量时,将第一字符定为p,因此更好的定义指针变量的方式应该为:
int *pi; // 养成良好的定义指针变量的习惯