本篇说明C++中的重中又重的关键——指针类型,并说明两个很有意义的概念——静态和动态。
数组
前面说了在C++中是通过变量来对内存进行访问的,但根据前面的说明,C++中只能通过变量来操作内存,也就是说要操作某块内存,就必须先将这块内存的首地址和一个变量名绑定起来,这是很糟糕的。比如有100块内存用以记录100个工人的工资,现在要将每个工人的工资增加5%,为了知道各个工人增加了后的工资为多少,就定义一个变量float a1;,用其记录第1个工人的工资,然后执行语句a1 += a1 * 0.05f;,则a1里就是增加后的工资。由于是100个工人,所以就必须有100个变量,分别记录100个工资。因此上面的赋值语句就需要有100条,每条仅仅变量名不一样。
上面需要手工重复书写变量定义语句float a1;100遍(每次变一个变量名),无谓的工作。因此想到一次向操作系统申请100*4=400个字节的连续内存,那么要给第i个工人修改工资,只需从首地址开始加上4*i个字节就行了(因为float占用4个字节)。
为了提供这个功能,C++提出了一种类型——数组。数组即一组数字,其中的各个数字称作相应数组的元素,各元素的大小一定相等(因为数组中的元素是靠固定的偏移来标识的),即数组表示一组相同类型的数字,其在内存中一定是连续存放的。在定义变量时,要表示某个变量是数组类型时,在变量名的后面加上方括号,在方括号中指明欲申请的数组元素个数,以分号结束。因此上面的记录100个工资的变量,即可如下定义成数组类型的变量:
float a[100];
上面定义了一个变量a,分配了100*4=400个字节的连续内存(因为一个float元素占用4个字节),然后将其首地址和变量名a相绑定。而变量a的类型就被称作具有100个float类型元素的数组。即将如下解释变量a所对应内存中的内容(类型就是如何解释内存的内容):a所对应的地址标识的内存是一块连续内存的首地址,这块连续内存的大小刚好能容纳下100个float类型的数字。
因此可以将前面的float b;这种定义看成是定义了一个元素的float数组变量b.而为了能够访问数组中的某个元素,在变量名后接方括号,方括号中放一数字,数字必须是非浮点数,即使用二进制原码或补码进行表示的数字。如a[ 5 + 3 ] += 32;就是数组变量a的第5 + 3个元素的值增加32.又:
long c = 23; float b = a[ ( c – 3 ) / 5 ] + 10, d = a[ c – 23 ];
上面的b的值就为数组变量a的第4个元素的值加10,而d的值就为数组变量a的第0个元素的值。即C++的数组中的元素是以0为基本序号来记数的,即a[0]实际代表的是数组变量a中的第一个元素的值,而之所以是0,表示a所对应的地址加上0*4后得到的地址就为第一个元素的地址。
应该注意不能这样写:long a[0];,定义0个元素的数组是无意义的,编译器将报错,不过在结构或类或联合中符合某些规则后可以这样写,那是C语言时代提出的一种实现结构类型的长度可变的技术,在《C++从零开始(九)》中将说明。
还应注意上面在定义数组时不能在方括号内写变量,即long b = 10; float a[ b ];是错误的,因为编译此代码时,无法知道变量b的值为多少,进而无法分配内存。可是前面明明已经写了b = 10;,为什么还说不知道b的值?那是因为无法知道b所对应的地址是多少。因为编译器编译时只是将b和一个偏移进行了绑定,并不是真正的地址,即b所对应的可能是Base - 54,而其中的Base就是在程序一开始执行时动态向操作系统申请的大块内存的尾地址,因为其可能变化,故无法得知b实际对应的地址(实际在Windows平台下,由于虚拟地址空间的运用,是可以得到实际对应的虚拟地址,但依旧不是实际地址,故无法编译时期知道某变量的值)。
但是编译器仍然可以根据前面的long b = 10;而推出Base - 54的值为10啊?重点就是编译器看到long b = 10;时,只是知道要生成一条指令,此指令将10放入Base - 54的内存中,其它将不再过问(也没必要过问),故即使才写了long b = 10;编译器也无法得知b的值。
上面说数组是一种类型,其实并不准确,实际应为——数组是一种类型修饰符,其定义了一种类型修饰规则。关于类型修饰符,后面将详述。
字符串
在《C++从零开始(二)》中已经说过,要查某个字符对应的ASCII码,通过在这个字符的两侧加上单引号,如'A'就等同于65.而要表示多个字符时,就使用双引号括起来,如:"ABC".而为了记录字符,就需要记录下其对应的ASCII码,而ASCII码的数值在-128到127以内,因此使用一个char变量就可以记录一个ASCII码,而为了记录"ABC",就很正常地使用一个char的数组来记录。如下:
char a = 'A'; char b[10]; b[0] = 'A'; b[1] = 'B'; b[2] = 'C';
上面a的值为65,b[0]的值为65,b[1]为66,b[2]为67.因为b为一个10元素的数组,在这其记录了一个3个字符长度的字符串,但是当得到b的地址时,如何知道其第几个元素才是有效的字符?如上面的b[4]就没有赋值,那如何知道b[4]不应该被解释为字符?可以如下,从第0个元素开始依次检查每个char元素的值,直到遇到某个char元素的值为0(因为ASCII码表中0没有对应的字符),则其前面的所有的元素都认为是应该用ASCII码表来解释的字符。故还应b[3] = 0;以表示字符串的结束。
上面的规则被广泛运用,C运行时期库中提供的所有有关字符串的操作都是基于上面的规则来解释字符串的(关于C运行时期库,可参考《C++从零开始(十九)》)。但上面为了记录一个字符串,显得烦琐了点,字符串有多长就需要写几个赋值语句,而且还需要将末尾的元素赋值为0,如果搞忘则问题严重。对于此,C++强制提供了一种简写方式,如下:
char b[10] = "ABC";
上面就等效于前面所做的所有工作,其中的"ABC"是一个地址类型的数字(准确的说是一初始化表达式,在《C++从零开始(九)》中说明),其类型为char[4],即一个4个元素的char数组,多了一个末尾元素用于放0来标识字符串的结束。应当注意,由于b为char[10],而"ABC"返回的是char[4],类型并不匹配,需要隐式类型转换,但实际没有进行转换,而是做了一系列的赋值操作(就如前面所做的工作),这是C++硬性规定的,称为初始化,且仅仅对于数组定义时进行初始化有效,即如下是错误的:
char b[10]; b = "ABC";
而即使是char b[4]; b = "ABC";也依旧错误,因为b的类型是数组,表示的是多个元素,而对多个元素赋值是未定义的,即:float d[4]; float dd[4] = d;也是错误的,因为没定义d中的元素是依次顺序放到dd中的相应各元素,还是倒序放到,所以是不能对一个数组类型的变量进行赋值的。
由于现在字符的增多(原来只用英文字母,现在需要能表示中文、日文等多种字符),原来使用char类型来表示字符,最多也只能表示255种字符(0用来表示字符串结束),所以出现了所谓的多字节字符串(MultiByte),用这种表示方式记录的文本文件称为是MBCS格式的,而原来使用char类型进行表示的字符串称为单字节字符串(SingleByte),用这种表示方式记录的文本文件称为是ANSI格式的。
由于char类型可以表示负数,则当从字符串中提取字符时,如果所得元素的数值是负的,则将此元素和下一个char元素联合起来形成一short类型的数字,再按照Unicode编码规则(一种编码规则,等同于前面提过的ASCII码表)来解释这个short类型的数字以得到相应的字符。
而上面的"ABC"返回的就是以多字节格式表示的字符串,因为没有汉字或特殊符号,故好象是用单字节格式表示的,但如果:char b[10] = "AB汉C";,则b[2]为-70,b[5]为0,而不是想象的由于4个字符故b[4]为0,因为“汉”这个字符占用了两个字节。
上面的多字节格式的坏处是每个字符的长度不固定,如果想取字符串中的第3个字符的值,则必须从头开始依次检查每个元素的值而不能是3乘上某个固定长度,降低了字符串的处理速度,且在显示字符串时由于需要比较检查当前字符的值是否小于零而降低效率,故又推出了第三种字符表示格式:宽字节字符串(WideChar),用这种表示方式记录的文本文件称为是Unicode格式的。其与多字节的区别就是不管这个字符是否能够用ASCII表示出来,都用一个short类型的数字来表示,即每个字符的长度固定为2字节,C++对此提供了支持。
short b[10] = L"AB汉C";
在双引号的前面加上“L”(必须是大写的,不能小写)即告诉编译器此双引号内的字符要使用Unicode格式来编码,故上面的b数组就是使用Unicode来记录字符串的。同样,也有:short c = L'A';,其中的c为65.
如果上面看得不是很明白,不要紧,在以后举出的例子中将会逐渐了解字符串的使用的。