C/C++指针本质论

C/C++指针本质论

6.1.1 指针与内存
本章内容是重点
1.地址值、地址类型、地址变量、地址常量的引入
(1)地址值和内存单元。
 计算机中的内存,一般都是以字节为单位进行划分的。也就是说,计算机内存的最小单位是1个字节(8位)。
 内存单元:一个内存单元占据1个字节。
 地址值:每个内存单元使用一个值来标识,这个值就是地址值。地址值使用整数来表示,但它的类型不是整型。可见,一个地址值对应一个内存单元,可以使用一个地址值来指示一个内存单元。
 地址值的大小:在32位机器上,要表示所有的内存地址,需要使用4个字节(32位)的二进制整数才能标识出所有的内存单元,因此标识一个内存单元的地址值应是4个字节的二进制整数(一般使用十六进制形式书写)。
(2)地址类型:地址值应该拥有一种数据类型,就像浮点数拥有浮点型一样,本文把地址值拥有的类型称为“地址类型”。因此,当内存中存储的数据为地址值时,它是地址类型,就像内存中存储的数据是浮点数,为浮点型一样。在32位机器上地址类型占据的内存大小一般为4个字节。
(3)读者可能认为地址类型的值是一个整数不好理解,其实这就好比一个整数值既可以是浮点型也可以是整型,程序能区分出一个整数值按什么类型进行存储。比如将一个整数值赋给浮点型变量,则这个整数值会被按照浮点型的格式存储在内存中。
(4)地址类型变量(地址变量):拥有地址类型之后,就可以创建地址类型的变量,就好比整型是一种类型,整型变量是表示整型的变量,整型变量存储的值是整数值。
(5)地址常量:十进制的整数常量就是地址常量,就好比2.3是浮点数常量一样。因此,一个整数值既是整数常量又是地址常量,但在C++中对地址常量的使用是有限制的。
2.指针类型、指针变量、指针值、指针常量的引入
在C++中没有地址类型变量、地址类型、地址值、地址常量的说法,取而代之的是指针类型变量(指针变量)、指针类型、指针值、指针常量。也就是说,在C++中指针类型就是指地址类型,指针变量就是指地址变量,指针值就是指地址值,指针常量就是指地址常量。
3.指针变量如何存储地址值
(1)指针类型的大小:在32位机器上指针类型的大小一般是4个字节(当然也可以更大,依系统而不同)。
(2)指针变量用于存储变量的地址值,而变量一般不止占据一个内存单元的大小,但指针变量只能保存一个内存单元的地址值,因此指针变量只能选择存储变量的首地址值或尾地址值(依机器而定),一般机器存储的都是变量的首地址值,然后根据指针指向的变量类型,就能确定该变量应占据多少个内存单元。指针变量相当于一个指向某个对象的箭头。
(3)地址值是一个整数值,这就意味着可以直接将一个整数值赋给指针变量,但C++禁止直接将整数值赋给指针变量(0值除外),因此在C++中这种操作不会成功。虽然现在禁止直接将整数值赋给指针变量,但是可以通过将整数值强制转换为地址类型(指针类型)后再使用。比如(int *)100=1;,表示把值1存储在内存中地址值为100的位置;int *p=(int *)100;,表示把内存中地址值为100的位置赋给指针p。
4.图解对象、变量、类型、左值、内存、内存的划分、指针的引入
图解对象、变量、类型、左值、内存、内存的划分、指针的引入如图6.1所示。
在这里插入图片描述
说明:
(1)一个内存单元占据1个字节(8位),其中每一位都可以存储一个二进制数值,每个内存单元都使用一个地址值进行标识,在32位机器上,一般使用4个字节的地址值来标识一个内存单元。比如图6.1中的内存单元1使用地址值0x0012FF60进行标识,这样更方便我们访问内存。0x0012FF60是以十六进制形式表示的整数值,是地址(或指针)类型。
(2)数据类型决定了分配多少个连续的内存单元,可以存储什么样的数据,以及进行什么样的运算。说简单一点,就是数据类型决定了怎样解释内存单元中的数据。比如int类型一般占据4个字节,则int就决定了应为数据分配4个字节的内存单元,即要分配4个内存单元。
(3)C++使用“对象”这一概念来表示多个连续的内存单元(注意,并不仅仅指一个内存单元)。在图6.1中,假设某数据为int类型,且为该数据分配的内存单元分别是内存单元1至4,则对象指的就是内存单元1至4总共4个内存单元。
(4)变量是命名的对象,左值则为指示一个对象的表达式。假设图中的内存单元1至4为一个对象,并为该对象取一个名字为a,则a就是变量,表示的就是内存单元1到4这4个字节的内存单元。左值是一个表达式,这个表达式应能表明指示的是一个对象,比如a=1;表达式,a指示了对象表示的内存单元1至4,因此a是左值。因为单独的变量就是一个表达式,所以a就是一个左值。因此,变量名既可以代表该对象本身,同时也是左值。
(5)内存中存储的数据依声明时的类型而被解释,若图6.1中的内存单元1至4的数据为整型,则该数据被解释为整数1094713344;若为浮点型,则该数据是浮点数12(按IEEE 754标准进行转换),若此数据被解释为一个内存地址值,则该数据为地址值0x41400000(十六进制)。
(6)指针变量存储的值应是地址类型的值(地址值)。假设有一个指针变量p,则存储的值应是内存单元的地址值,比如图6.1中的0x0012FF60、0x0012FF61等。假设int类型被分配到内存单元1至4,并将这个对象命名为a(即变量),且把变量a的地址赋给指针p,则指针p存储的值就是变量a所代表的内存单元的首地址(依机器而定),即内存单元1的地址值0x0012FF60。
5.易混概念
以下概念虽然被混合使用,但从上下文中应能很容易区分开来。
(1)地址、地址值、地址类型:单独说“地址”一词时既可以表示地址值,也可以表示地址类型,这能从上下文区分开来,这就好比我们说3是一个整数,这个整数既可以代表整数值,也可以代表整数类型。
(2)由于习惯的原因,指针类型、指针变量值、指针值常被简单地称为指针,比如int *p;,把p说成是指向int的指针,这里既可以强调p是一个指针类型的变量,也可以强调p的类型是指针;还可以说p是一个int指针,同样既可以强调p是指针类型,也可以强调p是指针类型的变量。
(3)“地址”和“指针”两个概念也常被混合使用,比如&a;既可以说成是返回a的地址,也可以说成是返回a的指针。
(4)指针的类型与指针指向的类型:经常把指针指向的类型说成是指针的类型,比如int *p;,表示指针p的类型为“指向int的指针”。本书会经常使用“指针类型”这个概念。

&与运算符
注:读取内存地址时,本章假设读取的是首地址,而不是尾地址。
1.理解&(取址)与
(解引用)运算符
注意:一个内存单元占据1个字节的大小。
(1)对&(取址)运算符的简单理解:就是读取对象所示连续内存单元的地址(值),但只能读取首地址,即第一个内存单元的地址值。比如&a,表示读取操作数a的地址值。当然,&该运算符并不会把操作数所表示对象的所有内存单元地址值都读取出来,一般只读取第一个内存单元的地址(即首地址),然后根据操作数的类型就能知道共占据多少个内存单元。若a是int类型,占4个字节内存单元,地址分别是0x0000 0002~0x0000 0005,则&a将只会读取操作数a所在的连续内存单元的首地址0x000 0002,而不会全部读出。
(2)&运算符的操作数应是表示一个对象的左值,该对象不能是位段,且声明时无register(寄存器)存储类区分符(寄存器是没有地址可取的)。因为左值是指示一个对象的表达式,也就是说,左值本身就是表示的对象,所以说&运算符的操作数应是一个左值。比如&3是错误的,因为整数3是右值;再比如register int a=1;,则&a;是错误的,因为操作数是register变量。
(3)注意:register关键字只是C++标准对编译器的一个建议,编译器不一定会执行该操作,因此在某些编译器上对register变量使用&运算符不一定会出错。为了使兼容性最好,不要对register变量使用&运算符。
(4)ANSI C中对&运算符描述的原话:“&(地址)运算符的结果是指向由操作数所指示的对象或函数的指针,其结果类型是指向type的指针”。理解标准:虽然&运算符的结果是指针,但并没有为该指针分配内存地址,因此&运算符的结果不能作为左值,只能作为右值。因为指针指向的是操作数所指示的对象或函数的指针,所以这个右值就是操作数所指示对象或函数的地址,在这里也可将&运算符的结果作为指针常量(即地址值)来理解。比如int a=1;,则&a的结果是指向操作数a所指示对象的指针,这个指针并没有分配内存地址,只能作为右值使用,这时该指针的值就是操作数a所代表的一片连续的内存单元(4个字节)的首地址或尾地址,该指针的类型是“指向int的指针”。此处混用了指针和地址的概念。
(5)(解引用)运算符,其操作数应是指针类型,若操作数指向一个对象(即操作数是指针),则结果是一个指示该对象的左值。若操作数的类型是指向某类型的指针,则结果类型就是该类型。运算符的操作数应是右值,但其类型必须是指针类型。
 理解1:运算符的结果是操作数指向的对象的左值或指针所指对象的左值。意思是说使用运算符之后得到的是一个左值,这个左值表示的是操作数所指向的对象,在左值不是很重要的情况下可以忽略左值,因此
运算符的结果就是操作数所指向的对象。这就意味着,使用
运算符之后,可以获取操作数(或指针)所指向的对象。比如int a=1; int p=&a;(注意,此语句只是声明指针p的一种语法形式),若以后在表达式中出现p,则结果是操作数p(p是指针)指向的对象的左值,操作数p指向的是被命名为a的对象, p和a都是表示相同对象的左值,所以对p的改变会影响到变量a的值;对于p=2,则变量a的值也变为2了。注意:变量名a既可以表示被命名为a的对象,也可以表示指向对象a的左值,因为在某些情况下左值就是所表示的对象。
 为什么强调左值?主要是为了说明
运算符的结果不是右值,因此可以使用运算符间接改变指针所指向的对象的值。若运算符的结果是右值,则不能间接改变指针所指向的对象的值。比如int a=1; int p=&a;,则p=2;会间接改变a的值。
 理解2: 运算符能得到操作数所指对象处存储的值。比如int b=1; int p=&b;,则p在需要将p作为右值的地方(会经过从左值到右值的转换),确实得到了p所指对象处存储的值1。但运算符的结果是左值,还可以对p进行赋值,而所指地址处存储的值一般都是右值,不能对右值进行赋值操作。当然,左值可以作为右值使用。
(6)示例:
比如int a=1;,则*&a;的操作数&a是一个指向对象a的指针。也就是说,操作数指向对象a,因此*&a的结果就是操作数&a指向的对象a的左值,即*&a是名称为a的对象的左值。而a本身也是名称为a的对象的左值。也就是说,&a和a都是指名称为a的对象的内存单元,因此&a与a等价,即*&a的值是1,同时*&a是左值,可以对其赋值,比如*&a=3;会间接改变a的值。因为&a是指向int的指针,所以*&a的结果类型是int。
再比如int b=1; int p=&b;,则p的结果是操作数p指向的对象b的左值,即p是名称为b的对象的左值,而b本身就是对象b的左值。也就是说,p与b表示的都是名称为b的对象的内存单元,因此p与b是等价的,即p的值是1,同时p的结果是左值,可以对其赋值,比如p=3;会间接改变b的值。因为p是指向int的指针,所以p的结果类型是int。
(7)注意:
(解引用)运算符与*(乘法)运算符是相同的,&(取址)运算符与&(按位与)运算符是相同的,这些运算符都能根据程序的上下文区分开来。
2.&运算符总结要点
(1)&运算符能读取对象所示连续内存单元的地址(值),一般为首地址或尾地址。
(2)&运算符的操作数是左值。
(3)&运算符的结果是右值,不能作为左值使用。
(4)&运算符的结果类型是“指向某类型的指针”。
3.运算符总结要点
(1)使用
运算符之后,可以获取操作数(或指针)所指向的对象。比如int a=1; int p=&a;,则p就是表示操作数p所指向的对象a。
(2)运算符的操作数应是右值,且是指针类型。
(3)运算符的结果是左值,因此可以对使用运算符之后的结果进行赋值。
(4)运算符的结果类型就是操作数指向的对象的类型。
(5)运算符会间接改变操作数所指向的对象的值。
4.图解指针的概念、地址、&与
运算符
图解指针的概念、地址、&与
运算符如图6.2所示。
在这里插入图片描述
说明:
(1)假设有这样的声明:int a=128; int p=&a;(这是声明指针p的语法形式)。
(2)在声明语句int p=&a;中,&a表示读取变量a所代表的一片连续的内存单元的首地址(即内存单元1的地址)。然后将该首地址用于初始化指针变量p,这时p的值应是变量a所指示的内存单元1的地址值,也就是0x0012FF60。因此,指针p相当于一个箭头,这个箭头指向了变量a所表示的一片内存单元的首个内存单元处(即内存单元1)。
(3)从图6.2中可见,指针变量p的值就是变量a的首地址值, p的值虽然是一个整数,但其类型是地址类型(指针类型),而不是整数类型。
(4)若在以后的表达式或语句中使用形如
p的形式,比如
p=3;,则
是解引用运算符,而不是在声明时表示所声明的对象是一个指针的声明符,这时运算符的结果是指针p所指对象a的左值,因此p和a是等价的,即*p和a都表示名称为a的对象所代表的内存单元1至4。所以,*p=3就相当于对a进行赋值,*p间接改变了变量a存储在内存单元中的值;cout<<*p;相当于输出a的值。
以上内容摘自本人所作《C++语法详解》一书,电子工业出版社出版。

猜你喜欢

转载自blog.csdn.net/hyongilfmmm/article/details/83016300
今日推荐