首页 | 社区 | 博客 | 招聘 | 文章 | 新闻 | 下载 | 读书 | 代码
亲,您未登录哦! 登录 | 注册

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。

  如果上面看得不是很明白,不要紧,在以后举出的例子中将会逐渐了解字符串的使用的。

  

   静态和动态

  上面依然没有解决根本问题——C++依旧只能通过变量这个映射元素来访问内存,在访问某块内存前,一定要先建立相应的映射,即定义变量。有什么坏处?让我们先来了解静态和动态是什么意思。

  收银员开发票,手动,则每次开发票时,都用已经印好的发票联给客人开发票,发票联上只印了4个格子用以记录商品的名称,当客人一次买的商品超过4种以上时,就必须开两张或多张发票。这里发票联上的格子的数量就被称作静态的,即无论任何时候任何客人买东西,开发票时发票联上都印着4个记录商品名称用的格子。

  超市的收银员开发票,将商品名称及数量等输入电脑,然后即时打印出一张发票给客人,则不同的客人,打印出的发票的长度可能不同(有的客人买得多而有的少),此时发票的长度就称为动态的,即不同时间不同客人买东西,开出的发票长度可能不同。

  程序无论执行多少遍,在申请内存时总是申请固定大小的内存,则称此内存是静态分配的。前面提出的定义变量时,编译器帮我们从栈上分配的内存就属于静态分配。每次执行程序,根据用户输入的不同而可能申请不同大小的内存时,则称此内存是动态分配的,后面说的从堆上分配就属于动态分配。

  很明显,动态比静态的效率高(发票长度的利用率高),但要求更高——需要电脑和打印机,且需要收银员的素质较高(能操作电脑),而静态的要求就较低,只需要已经印好的发票联,且也只需收银员会写字即可。

  同样,静态分配的内存利用率不高或运用不够灵活,但代码容易编写且运行速度较快;动态分配的内存利用率高,不过编写代码时要复杂些,需自己处理内存的管理(分配和释放)且由于这种管理的介入而运行速度较慢并代码长度增加。

  静态和动态的意义不仅仅如此,其有很多的深化,如硬编码和软编码、紧耦合和松耦合,都是静态和动态的深化。

  

   地址

  前面说过“地址就是一个数字,用以唯一标识某一特定内存单元”,而后又说“而地址就和长整型、单精度浮点数这类一样,是数字的一种类型”,那地址既是数字又是数字的类型?不是有点矛盾吗?如下:

  浮点数是一种数——小数——又是一种数字类型。即前面的前者是地址实际中的运用,而后者是由于电脑只认识状态,但是给出的状态要如何处理就必须通过类型来说明,所以地址这种类型就是用来告诉编译器以内存单元的标识来处理对应的状态。  

本栏文章均来自于互联网,版权归原作者和各发布网站所有,本站收集这些文章仅供学习参考之用。任何人都不能将这些文章用于商业或者其他目的。( Pfan.cn )

编程爱好者论坛

本栏最新文章