第3章 数据类型(Data Types) 65                                            什么是数据类型?数据类型是指:   (1)一定的数据在计算机内部的表示方式;   (2)该数据所表示的值的集合;   (3)在该数据上的一系列操作。   在数学上,专家们典型地用代数群论对数据类型进行研究(?参考文献[6]CH5.4)。在计算机语言中,将数据用一定的数据类型来描述是为了将一系列相同性质的数据归类,统一值域和规范操作,以便这些数据在描述问题、数据抽象(?CH11.1.2)中得到更好的运用,从而通过数学和计算机的手段解决问题。   对于要解决的具体问题,一般的做法是将问题数量化,描述成一定数据类型下的实体和相关的操作,通过语言的编译器对其进行识别,最后让计算机执行操作,运行获得求解结果。   以数据类型规定数据的描述和行为的编程手段,有利于数据的逻辑描述和正确性检查,有利于数据操作的高质和高效。   在C++中,数据类型不仅规定了某一数据是整数、浮点数或者自定义的类型名称,而且规定了数据的组织形式以及操作方法。数据类型是程序设计中描述数据的工具,对数据类型的选取或规定形式,直接决定了编程中解决问题的具体方法。   C++中的数据类型,有语言既定的内部数据类型(inner types),也有程序员自定义的外部数据类型。其中,内部数据类型有:        ● 整数类型(int);     ● 字符类型(char);     ● 布尔类型(bool);     ● 单精度浮点(float);     ● 双精度浮点(double)。      还可以通过数组、指针、引用等定义基于上面这些数据类型以及其他外部数据类型的变异类型。例如:        ● 整型数组(int[]);     ● 浮点引用(double&);     ● 字符指针(char*)。      内部数据类型及其变异构成了C++的基本数据类型。注意,有些书对于内部数据类型(inner types)和基本数据类型(base types)是不加区分的。内部数据类型是指语言本身具有的数据类型,主要指整数类型和其相关的衍生类型,以及浮点类型和其相关的衍生类型。基本数据类型主要是指编程中可自由运用的,对内部数据类型做适当“变形”所构成的数据类型。   程序员自定义的数据类型主要是指用class关键字(?CH8)构造的数据类型。除此之外,用enum、union、struct关键字也能定义单纯空间意义上的数据类型。   要解决具体问题,必须首先学会用数据类型描述问题中的具体事物。世界上的问题形形色色,仅用语言内部的数据类型描述事物是远远不够的,还必须借助于语言所提供的数据类型描述机制自定义数据类型。要自定义数据类型,就必须先了解语言的内部和基本的数据类型。因为自定义数据类型是以内部数据类型为基础的。 3.1 整型(int Types)   使用整数是人们描述自然现象的最基本的数学方式,因此,以解决人类计算问题为目标的计算机语言提供了整数数据类型,即整型。它规定了整数的表示形式、整数的运算(操作),以及整数在计算机中的表示范围。 ? 3.1.1 二进制补码(Binary Complement)   通常的计算机语言在计算机内部都是以二进制补码形式表示整数的。因为二进制补码用来表示整数具有高度的一致性,并且统一了加减法的意义,简化了乘除法运算,甚至直接简化了计算机的硬件结构。   将十进制正整数转换成二进制补码形式的整数的转换方法是采用“除2取余法”,即对被转换的十进制整数除以2,取其余数,并将商再除以2,再取余数,直到商为0。每次除下来的余数按先后构成了从低位到高位的二进制整数。例如:    35=100011(2)      转换的具体步骤为:    35÷2=17......1 17÷2=8......1 8÷2=4......0 4÷2=2......0 2÷2=1......0 1÷2=0......1      由于计算机内部表示数的字节单位都是定长的,以2的幂次展开,或者8位,或者16位,或者32位……于是,一个二进制数用计算机表示时,位数不足2的幂次时,高位上要补足若干个0。例如,35以8位长和16位长在计算机内部表示时的二进制数分别为:    00100011 0000000000100011      两个十进制整数相加在计算机中是做二进制数加法运算的,例如:    35+12=00100011+00001100 =00101111 =101111(2) =47      一个十进制负整数,表示成二进制补码形式的整数时,该负整数的对应正整数先转换成二进制数,然后“取补”,规则是“取反加一”,例如,用8位长度的二进制形式表示:    -15=-1111(2)     =-00001111     =11110001      用二进制补码表示的数中,以最高位是否为0判断该数是否为正数。例如:    01111110-------正数 10001101-------负数      因此,一定长度的二进制补码中总是有一半是正数,一半是负数。例如,8位二进制补码中有128个数最高位为0,即128个正数(其中含0),另外128个数为负数;所能表示的最大正整数是127,即01111111,最小非负数是0,即00000000;所能表示的最接近于0的负整数是–1,即11111111;绝对值最大的负整数是–128,即10000000。   由于0取补后还是0,其他数取补后从正数转为负数,或从负数转为正数,所以体现了二进制补码表示形式的一致性。它为计算机进行加减运算带来了设计上的方便。   在二进制补码的运算中,减法相当于取补后相加,如果相加后在最高位有进位,则简单地弃之了事。因此,二进制补码运算在计算机中没有减法。例如:    3-5=00000011-00000101 =00000011+11111011 =11111110 =-00000010(2) =-2      在二进制补码中,有一种很有用的移位操作,8位二进制码的左移1位操作就是将最高位挤出,最低位补0。例如,6(00000110)左移1位后得到12(00001100),即相当于6乘2等于12。   由于二进制整数左移1位相当于做乘2运算,所以,二进制补码的乘法在具体的操作中都分解成了一系列的左移和加法操作。例如:    3×5=00000011×00000101 =00000011×00000001+00000011×00000100 =00000011左移0位 + 00000011左移2位 =00000011+00001100 =00001111 =15      同理,二进制整数做除以2运算相当于右移1位。所以,二进制补码的除法运算在计算机中都分解成了一系列的左、右移和加法操作。例如:    13÷3=00001101÷00000011 =(00001100+00000001)÷00000011 =00001100÷00000011+00000001÷00000011 =00000100余00000001 =4      由于整数除法中结果没有小数,所以其除法也就是抛弃余数的整除法。读者必须明白实际实现的乘除法操作设计比这里描述的要复杂(?参考文献[7]CH8)。 ? 3.1.2 整型数表示范围(int Range)   整型的设计有多种形式,按表示的长度分,有8位、16位和32位,以后还有64位,大型机还有128位,随着计算机的发展,整型数的位长也在增加。每一种长度都分为有符号(signed)和无符号(unsigned)两种,并且总是指定一种为默认类型,见表3-1。 表3-1 整型分类表 类 型 有符号形式 无符号形式 默 认 8位 signed char unsigned char signed char 16位 signed short int unsigned short int signed short int 32位 signed int unsigned int signed int 32位 signed long int unsigned long int signed long int      例如:    unsigned int x = 23; int y = -67; //等价于signed int y = -67; unsigned int z = -43; //表示方式有错      值得注意的是,默认类型并不属于C++标准,而是编译器的设定,有些C++编译器的char默认为unsigned char,而且其长度为16位。不过,目前流行的C++编译器都是按表3-1所示默认的。不管怎样,所有的编译器均应满足C++标准所规定的整数长度关系式:    char ≤ short int ≤ int ≤ long int      表3-2是目前流行的32位编译器的各种整数类型表示范围一览表。 表3-2 整型数表示范围 类 型 字节数 位数 表 示 范 围 解 释 下 限 上 限 char 1   8 –128 127 –27~(27–1) signed char 1   8 –128 127 –27~(27–1) unsigned char 1 8 0 255 0~(28–1) short int 2 16 –32768 32767 –215~(215–1) signed short int 2 16 –32768 32767 –215~(215–1) unsigned short int 2 16 0 65535 0~(216–1) int 4 32 –2147483648 2147483647 –231~(231–1) signed int 4 32 –2147483648 2147483647 –231~(231–1) unsigned int 4 32 0 4294967295 0~(232–1) long int 4 32 –2147483648 2147483647 –231~(231–1) signed long int 4 32 –2147483648 2147483647 –231~(231–1) unsigned long int 4 32 0 4294967295 0~(232–1)    ? 3.1.3 编译器与整型长度(Compiler & int Length)   C++编译器在不同的计算机硬件上的表现是不同的。目前计算机主板上的主流CPU是64位的,而目前的C++编译器版本则仍然是32位的,软件相对于硬件总是滞后的。所谓32位编译器,是指它能将程序源代码编译成最高为32位的CPU指令系统代码,或者更加直接地说,默认int类型的长度是32位。C++编译器过去曾是16位的,今天是32位的,那么自然,明天将是64位的。   32位C++编译器并非一定只能编译那些32位CPU指令系统的代码。为了兼容运行环境,32位C++编译器可以将代码编译成较低级别的指令系统。32位C++编译器还可以表示64位整型,C++ 11标准规定64位整型名称为long long。   例如,在32位编译器中,若将代码编译成16位机器指令系统,则:    int a = 327777; //错,16位机器指令表示的有符号整数最大只能为32767    就不正确,而如将代码编译成32位机器指令系统,则上述语句就是合理的。为了使编写的程序具有可移植性,在各种机器指令环境下,或者说在各种操作系统环境下运行,都能得到唯一的结果,必须分辨编译器。上面那个定义语句若在低版本编译器编译,就应该写为:    long int a = 327777;       ? 3.1.4 整数字面值(Integer Literals)   整数用具体的数值表示就是整数字面值。整数字面值遵循文法表示(?附录A.5)。   整数可以用十进制、八进制和十六进制数表示。编程时,用非0数字开头的数字序列表示十进制数,0开头的数字序列表示八进制数,0X或0x开头的数字和ABCDEFabcdef序列表示十六进制数。例如: int a = 23; long int b = 02345; unsigned int c = 0x79fa;      整数字面值可以区分类型(长度),如果像上面这样朴素的整数字面值,则默认为int型整数,即signed int型;如果要表示unsigned int或者long int,则可以在整数字面值后面加U或L,大小写都可以。例如:    b = 02345L; //long c = 235u + 123u; //unsigned      文法就是语法,C++语言都是由语法规定的。可以参考附录A,以了解怎样学习文法。   语言的描述要受到实现的限制,即受到计算机发展技术的限制,受到编译器的限制。例如,文法中规定的整数字面值是非0数字开头的数字序列。但对序列的长度没有具体说明,其数字序列是递归定义的形式。   事实上,下面超过整数范围描述的字面值在各个计算机中有不同的解释:    int a = 1234567890123456789001234567890;      存储在a空间的值究竟是多少呢?C++标准告诉我们,当整数的表示在整型表示范围内时,任何编译器的理解是一致的,但当其超过了所表示的范围时,不同的编译器有不同的处理方式,因而,上述a的值是不可预料的。   例如,在VC编译器中,该语句报错,而在BCB编译器中,编译能通过,但输出的a是一个莫名其妙的数。而对20位长度的整数字面值,在VC中居然也通过了编译,但与BCB一样,其值是荒谬的。那是因为各个编译器对整数字面值(更确切地说,是C++语言的词法单位)长度限制不同,超过一定的长度,就是错误;没有超过规定的长度,但超过了表示范围,虽合法但不合理。 ? 3.1.5 整数算术运算(Integer Arithmetic Operations)   整数可以进行+、–、*、/、%、<<、>>、<<=、>>=、!、^、<、<=、>、>=、==、^=、&、|、&=、|=、&&、||、&&=、||=、!=、=、+=、–=、*=、/=、%=、++、– –、,、?:等操作。其中有些是在整数之间做比较的,有些是在两个整数上面做算术运算的,有些是做位操作(?CH4.5)的,有些是做赋值操作的。   +、–、*、/、%这五种操作是整数的算术运算。其中,“/”是整除运算,“%”是取余运算。例如,11%5=1。规定除数不能为0,否则将导致运行错误。值得一提的是,余数的正负性决定于被除数的正负性,这与整除“/”操作所得结果的“负负得正”不同。例如:    11/(-5) = -2 -11/(-5) = 2 //结果符号遵循负负得正原理 11%(-5) = 1 -11%(-5) = -1 3.2 整数子类(int Subtypes) ? 3.2.1 字符型(char Type)   ASCII码有128个字符,其中,ASCII值(即字符的整数值)0~31和127为不可见字符。不可见字符也称控制字符。直接表示可见的ASCII字符(即字符的字面值)是用单引号括起来的单个字符。例如,'a','x','?','$'等。除了这种形式的字面值外,C++还允许使用一种特殊形式的字面值,即以“\”打头的格式字符,称为转义字符(escape character)。   经常用的不可见字符就是用一个转义符后跟一个专门的字符表示。例如,换行符用 '\n' 表示。有些符号虽可见,但表示上有时与语法发生冲突,也用转义符委婉表示。例如,单引号字符表示为'\",不能表示为'";字串"I say "OK""应写为"I say \"OK\""。可见字符在知道其ASCII值的前提下也可以用转义字符的形式表示,例如,'A'的ASCII码为65,也可用转义字符 '\101' 或 '\x41' 表示,即表示为转义符后跟去掉前导0的八进制或十六进制数。表3-3列出了一些转义字符。 表3-3 C++转义字符 字符形式 整数值 代表符号 字符形式 整数值 代表符号 \a 0x07  响铃bell \" 0x22 双引号 \b 0x08  退格backspace \' 0x27 单引号 \t 0x09  水平制表符HT \? 0x3F 问号 \n 0x0A  换行return \\ 0x5C 反斜杠字符\ \v 0x0B  垂直制表符VT \ddd 0ddd 1~3位八进制数 \r 0x0D  回车 \xhh 0xhh 1~2位十六进制数      字符型是针对处理ASCII字符而设的。字符型在表示方式和操作上与整数吻合,在表示范围上是整数的子集。它由一字节(8bit)组成,所以只能表示256个状态值。由于ASCII码有128个字符,所以可以用signed char(即char)中的所有正数表示所有ASCII码值,而负数表示非正常状态,以示区别。由于它可以看作为整数的子集,所以其运算可以参与到整型数中去,只要不超过其范围。例如:    char a = 31; int b = a + '\a'; //31+65=96      然而它与整数毕竟还是有区别的,最大的区别是在输出方式上,字符型的输出不是整数,而是该整数所代表的ASCII码字符。例如:    int a = 65; char b = 65; cout< using namespace std; //------------------------------------- int main(){ for(int i=1; i<=10; ++i){ cout< using namespace std; int main(){ float f=19.2F; int* pa = (int*)&f; for(int i=31; i>=0; i--) cout<<(*pa>>i & 1)<<(i==31||i==23 ? "-":""); cout<<"\n"; }//====================================      窥探浮点数的计算机内部表示,没有专门的数据类型表示其操作,这里采用对存储空间逐位判断的方法查看位码,用到了两种超前技术:一种是指针技术(?CH3.7);另一种是位操作(?CH4.5)。 ? 3.3.2 浮点型表示范围 (Floating-PointType Ranges )   对于单精度浮点数来说,由于阶码有8位,可以表示正负指数。当尾数取到全1再加上小数点前面的1,阶码取到最大为127时,浮点数取到正负数的最大值:    ±1.11111111111111111111111×2127 =±(2 - 2-23)×2127 ≈±2128 ≈±3.4×1038    那么,单精度浮点数表示的最接近于0的实数是什么呢?其精度又是多少呢?   对于规格化数,当尾数全为0,阶码为–126时,最接近于0的数,其值为2–126≈±1.2×10–38。   其有效位数由尾数表示,有24位,即精度有二进制的24位,又因为224≈1.7×107,所以精度相当于十进制的7位。也就是说,十进制数,阶码在±38之内,有效位数在7之内,其单精度浮点数都能精确表示。   对于非规格化数,即阶码为–127,表示2–126,当尾数为0.00000000000000000000001时,最接近于0,其值为2–23–126=2–149≈1.4×10–45,但是该数的精度只有1位!因此,单精度浮点数最接近于0的数由非规格化的特殊浮点数所表示,约为±1.4×10–45。非规格化数只能象征性地表示最接近于0的数,因为其精度表示差。   到了long double,浮点标准不再为那1位精度优化,不分规格化与否,所以表示范围直接由0.11111…1×216383≈5.9×104931得到,最近0数直接由0.0000…1×2–16383≈9.1×10–4952得到。表3-4是C++中浮点类型的一些说明。 表3-4 浮点类型说明 类型 类别 float double long double 说明   单精度   双精度 长双精度 位数   32位   64位 80位 长度   4字节   8字节 10字节 表示范围   ±3.4×1038   ±1.8×10308 ±5.9×104931 规格化近0数(保证精度)   ±1.2×10–38   ±2.2×10–308 ±9.1×10–4952 非规格化近0数   ±1.4×10–45   ±4.9×10–324 阶码   8位   11位 15位 尾数   23位   52位 64位 二进制有效位数   24位   53位 64位 十进制有效位数   7位   15位 19位 规格化数阶值范围   –126~127   –1022~1023 –16383~16383 非规格化数阶值   –127   –1023 NaN阶值   –128   –1024 –16384 3.4 C-串与string(C-strings & string) ? 3.4.1 C-串(C-strings)   在C++中,有两种字符串,一种是从C沿袭过来的,称为C-字符串,简称C-串。C-串是以一个全0位(整数0)字节作为结束符的字符序列。该全0字节既是8位的整数0,也是ASCII码的0。C-串还称为ASCIIZ串(即ASCII字符序列加上尾巴Zero)。   C-串也是字符串字面值,其格式为双引号括起来的字符序列。例如,我们前面用到的“Hello!”。它在空间中的存储形式为图3-2所示。   很显然,C-串的空间长度为字符串长度加1。如果要将C-串放入字符数组,则元素个数非比字符数多1不可。例如:    char buffer[7]="Hello!"; //若为char buffer[6]="Hello!";则为错误!      我们知道,字符字面值的类型为char,那么C-串又是什么类型呢?   C-串的类型为char*,说得更精确一点,是const char*。事实上,所有的字面值类型都是const的。char*称为字符指针,它与字符数组虽然类型不同,但操作上是一样的,都表示C-串的起始地址。 ? 3.4.2 字符指针与字符数组(char Pointers & char Arrays)   指针是表示内存空间位置的存储实体(?CH3.7)。字符指针就是所指向的空间位置上的值,当作字符操作的类型。例如:    char* str="Hello"; cout<<*str< using namespace std; //------------------------------------- int main(){ cout<<("join"=="join" ? "" : "not ")<<"equal\n"; char* str1="good"; char* str2="good"; cout<<(str1==str2 ? "" : "not ")<<"equal\n"; char buffer1[6]="Hello"; char buffer2[6]="Hello"; cout<<(buffer1==buffer2 ? "" : "not ")<<"equal\n"; }//====================================         还有C-串的复制问题:    char* str1="Hello"; char* str2=str1; //意味着str1与str2共享"Hello"空间      数组复制干脆告禁:    char a1[6]="Hello"; char a2[6]=a1; //错:数组是不能复制的      为了比较C-串的字典序大小,在C库函数(C++头文件cstring或C头文件string.h)中,专门设计了C-串的比较函数strcmp。因而C库函数为其设计了strcpy函数。总之,C库函数设计了一系列的C-串库函数,解决C-串的赋值、复制、修改、比较、连接等问题。例如:    //===================================== //f0304.cpp //C-串操作 //===================================== #include #include using namespace std; //------------------------------------- int main(){ char* s1 = "Hello "; char* s2 = "123"; char a[20]; strcpy(a, s1); //复制 cout<<(strcmp(a,s1)==0 ? "" : "not ")<<"equal\n"; //比较 cout< #include #include using namespace std; //------------------------------------- int main(){ string a, s1 = "Hello "; string s2 = "123"; a = s1; //复制 cout<<(a==s1 ? "" : "not ")<<"equal\n"; //比较 cout<>s;) //用string串 cout<>a;) //用C-串 cout<>的读入方式总是将前导的空格(所谓空格,即包括空格、回车、水平或垂直制表符等)滤掉,将单词读入,当遇到空格时结束本次输入。   也可以通过getline将其一次性地输入:    string s; getline(cin, s); //string串的读入一行 cout<>不能辨别空格与回车的差异,因此只能用getline的方式逐行读入数据到string实体中,但在string实体中分离若干个整数还是显得有点吃力。一个好的方法是用string流:    //===================================== //f0306.cpp //整行读入再分解读入 //===================================== #include #include #include using namespace std; //------------------------------------- int main(){ ifstream in("aaa.txt"); for(string s; getline(in, s);){ int a, sum=0; for(istringstream sin(s); sin>>a; sum += a); //用string流分解s串的整数 cout<>a即是从string流中输入整数到a中,输啊输,一直输到string中的最后一个整数!   string流很有用,有时候要将内容先逐个输出到string中,最后才根据计算结果来编排输出格式。这时候,用string流就很管用。   由于string可以很方便地修改、复制、插入、删除、拼接、比较等,所以在输入/输出流中,还能够进一步编辑流的格式,对于程序处理的统一性、方便性带来了莫大的好处。   getline函数的返回是流状态,通过其可以判断文件是否还有数据行可读。   在C++的早些时候,用C-串流比较多,定义方式与string流不同,它在头文件strstream中说明,因为使用C-串流的历史也不长,况且如今C++标准已经走得很远了,所以不应再回头去用那个迂腐的东西。 3.5 数组(Arrays) ? 3.5.1 元素个数(Number of Elements)   数组定义的格式为:    类型名 数组名[常量表达式];      常量表达式表示数组的元素个数,并不表示最大下标值。例如:    int a[5];    则表示可以访问的元素为a[0]~a[4]。但不能访问a[5],见图3-4。   常量表达式的值只要是整数或整数子集就行。例如:    int a['a']; //表示int a[97];      数组定义是具有编译确定意义的操作,它分配固定大小的空间,就像变量定义一样的明确。因此元素个数必须是由编译时就能够定夺的常量表达式。下面这样的数组定义有问题:    int n=100; int a[n]; //错:数组元素个数必须是常量      虽然,根据上下文,编译似乎已经知道n的值,但编译动作因变量性质而完全不同。变量性质就是具有空间占用的可访问实体,编译每次碰到一个变量名称就对应一个访问空间的操作。因此,int a[n]实际上要在运行时,才能读取变量n的值,才能确定其空间大小。这与数组定义的编译时确定意义的要求相违背,因而编译时报错。   而对于下面的定义,却是允许的。因为编译中,常量虽也占空间,甚至也涉及内存访问,但因数据确定,而可以被编译用常数替代。事实上,常量在编译时经常是优化的目标,能省略内存空间访问就应该省略,讲求效率的C++编译器会乐此不疲:    const int n=100; int a[n]; //ok ? 3.5.2 初始化(Initialization)   数据的读入一般涉及从其他外部设备中输入的过程,但元素不多而又确定数据值的小数组可以直接在程序中初始化。例如:    int iArray[10] = {1,1,2,3,5,8,13,21,34,55};      注意上述形式中,大括号中的初始值个数不能多于数组定义的元素个数。初始值不能通过逗号的方式省略,初始值也不能为空。但在总体上,初始值可以少于数组定义的元素个数。例如:    int array1[5] = {1,2,3,4,5,6}; //错:初始值个数太多 int array2[5] = {1,,2,3,4}; //错:不能以逗号方式省略 int array3[5] = {1,2,3, }; //错:同上 int array4[5] = {}; //错:初始值不能为空 int array5[5] = {1,2,3}; //ok int array6[5] = {0}; //ok      只要动用了大括号,就是实施了初始化。对于实施初始化的数组,如果初始值个数不足方括号中规定的元素个数,则后面的元素值全补为0。因此,array5[3]、array5[4]为0,且array6的全部元素都为0。   具有初始化的数组定义,其元素个数可以省略,即方括号中的表达式可以省略。这时候,最后确定的元素个数取决于初始化值的个数。例如:    //===================================== //f0307.cpp //省略数组定义中方括号内的表达式 //===================================== #include //------------------------------------- int main(){ int a[]={1,2,3,4,5}; //数组a有5个元素 //... for(int i=0; i using namespace std; //------------------------------------- int array1[5]={1,2,3}; //有初始化 int array2[5]; //无初始化 //------------------------------------- int main(){ int array3[5]={2}; //有初始化 int array4[5]; //无初始化 cout<<"array1: "; for(int i=0; i<5; ++i) cout< using namespace std; //------------------------------------- int main(){ int array1[2][3]={1,2,3,4,5}; //依次初始化全部6个元素,最后一个默认为0 int array2[2][3]={{1,2},{4}}; //以维为单元,3个元素为1个单元,共2个单元 cout<<"array1: "; for(int i=0; i<2; ++i) for(int j=0; j<3; ++j) cout< a(10); (2) vector b(10, 1); (3) vector c(b); (4) vector d(b.begin(), b.begin()+3);      vector是带类型参数的模板类型,尖括号中为元素类型名,它可以是任何合法的数据类型。   第1种形式定义了10个整数元素的向量,但并没有给出初值,因而,其值是不确定的。   第2种形式定义了10个整数元素的向量,且给出其每个元素的初值为1。这种形式是数组望尘莫及的。数组只能通过循环来成批地赋值。   第3种形式用另一个现成的b向量创建一个c向量。   第4种形式定义了其值依次为b向量中第0到第2个(共3个)元素的向量。   因此,创建向量时,不但可以整体向量复制性赋值,还可以选择其他容器的部分元素来复制定义。特别地,向量还可以从数组获得初值。例如:    int a[7]={1,2,5,3,7,9,8}; vector va(a, a+7);      上面的第4种形式的b.begin()、b.end()是表示向量的起始元素位置和最后一个元素之外的元素位置。向量元素位置也属于一种类型,称为遍历器。遍历器不单表示元素位置,还可以在容器中前后挪动。每种容器都有对应的遍历器。向量中的遍历器类型为:vector::iterator。因此,如果要输出向量中所有元素,可以有两种循环控制方式:    for(int i=0; i::iterator it=a.begin(); it!=a.end(); ++it) //第2种 cout<<*it<<" ";      第1种形式是下标方式,a[i]是向量元素操作,这种形式与数组一样;第2种形式是遍历器方式,*it是指针间接访问形式,它的意义是it所指向的元素值。   a.size()是向量中元素的个数,a.begin()表示向量的第一个元素,这种操作方式是一个对象捆绑一个函数调用,表示对该对象进行某个操作。类似这样的使用方式称为调用对象a的成员函数,这在对象化程序设计中很普遍(?CH8.2)。向量中的操作都是通过使用成员函数完成的。它的常用操作有:    a.assign(b.begin(), b.begin()+3); //b向量的0~2元素赋给a a.assign(4,2); //使a向量只含0~3元素,且赋为值2 int x = a.back(); //将a的最后一个元素值赋给整数变量x a.clear(); //a向量中元素清空(不再有元素) if(a.empty()) cout<<"empty"; //a.empty()经常作为条件,它判断向量空否 int y = a.front(); //将a的第一个元素值赋给整数变量y a.pop_back(); //删除a向量的最后一个元素 a.push_back(5); //在a向量最后插入一个元素,其值为5 a.resize(10); //将向量元素个数调至10个。多则删,少则增补,其值随机 a.resize(10,2); //将向量元素个数调至10个。多则删,少则增补,其值为2 if(a==b) cout<<"equal"; //向量的比较操作还有!=,<,<=,>,>=      除此之外,还有元素的插入与删除、保留元素个数和容量观察等操作(?参考文献[10] CH6.2)。   向量是编程中使用频率最高的数据类型。这不仅是因为数据的顺序排列性在生活中最常见,还因为向量有一些整体赋值、判空和元素添加、搜索等最简单的常规操作。当数据并不复杂时,可以代替其他数据类型而很好地工作。特别是向量可以自动伸展,容量可以自动增大,使得对一些不确定数量的顺序性操作数据在工作上带来了极大的方便。 ? 3.6.2 添加元素(Adding Elements)   常规数组必须在定义时确定元素个数,之后的使用中不得更改。向量较之数组的优越之处是可以改变元素数量的多少。添加元素是其一种操作。例如,读入一个文件aaa.txt的数据到向量中,文件中为一些整数(不知个数)。要判断向量中的元素有多少个两两相等的数对。程序代码如下:    //===================================== //f0310.cpp //向量操作 //===================================== #include #include #include using namespace std; //------------------------------------- int main(){ ifstream in("aaa.txt"); vector s; //无元素的空向量 for(int a; in>>a;) s.push_back(a); int pair=0; for(int i=0; i& a, vector& b){ vector temp = a; a = b; b = temp; }    它要涉及向量的创建、赋值和再赋值,最后还要销毁临时向量。但若用vector的swap操作,这些工作都可以省掉。只要做微不足道的地址交换工作,岂不美哉?!   例如,文件aaa.txt中含有一些行,每行中有一些整数,可以构成一个向量。整个文件可以看成是一组向量,其中每个元素又都是向量,只不过作为元素的向量其长度参差不齐。设计一个排序程序,使得按从短到长的顺序输出每个向量。这时候,程序代码如下:    //===================================== //f0311.cpp //若干个向量按长短排序 //===================================== #include #include #include #include using namespace std; //------------------------------------- typedef vector> Mat; Mat input(); void mySort(Mat& a); void print(const Mat& a); //------------------------------------- int main(){ Mat a = input(); mySort(a); print(a); }//------------------------------------ Mat input(){ ifstream in("aaa.txt"); Mat a; for(string s; getline(in, s);){ //循环读入行 vector b; istringstream sin(s); for(int ia; sin>>ia;) b.push_back(ia); //分解行中整数存入b向量 a.push_back(b); //将b向量添加入a矩阵 } return a; }//------------------------------------ void mySort(Mat& a){ for(int pass=1; pass using namespace std; //------------------------------------- int main(){ float f = 34.5; int* ip = reinterpret_cast(&f); cout<<"float address: "<<&f<<"=>"<"<<*ip<(表达式)是最不讲道理的,见程序f0312.cpp。除此之外,还有静态转换static_cast(表达式)(?CH4.3.3)、动态转换dynamic_cast(表达式)(?CH12.7.1)和常量转型const_cast(表达式)(?CH12.7.3)。   程序f0312.cpp中,两种类型的地址转换形式为:    int* ip = reinterpret_cast(&f);      C++好难哦,reinterpret_cast这么难的保留字,亏他设计得出:)!然而这是一种逆向目标的设计,通过不让程序员产生使用的快感达到少用慎用的目的。C++语言所蕴含的哲理,会令无法深入编程技术的程序员获得最大的利益。   反过来,如果该地址指向的空间以整数指针间接访问的形式赋予100,则以浮点的眼光去看的时候,发现一切都变了!请读者亲自分析一下运行结果的最后一行。 ? 3.7.3 指针运算(Pointer Operations)   指针值表示一个内存地址,因此它内部表示为整数,这在显示的时候可以看到。指针所占的空间大小总是等同于整型变量的大小,但它不是整型数,我们重温数据类型的三个要素:数据表示、范围表示、操作集合。指针与整型虽有相同的数据表示,相同的范围表示,但它们具有不同的操作。例如,整型变量不能进行间接访问操作,所以指针与整型不是相同的数据类型。   指针不服从整型数操作规则。例如,两个指针值不能相加。两个指针值相减得到一个整型数,指针值加上或减去一个整数得到另一个指针值等。   指针不能赋予一个整型数。要想获得整型数表示的绝对地址,应将整型数重解释转换为对应指针的类型。例如:    int* ip = 1234567; //错:不能进行int到int*的直接转换 int* sp = reinterpret_cast(1234567); //ok      指针的加减整型数的操作大多数用在数组这种连续的又是同类型元素的序列空间中。可以把数组起始地址赋给一指针,通过移动指针(加减整数)对数组元素进行操作。数组名本身就是表示元素类型的地址,所以可以直接将数组名赋给指针。例如:    //===================================== //f0313.cpp //===================================== #include using namespace std; //------------------------------------- int main(){ int iArray[6]; for(int i=0; i<6; ++i) //数组元素赋值 iArray[i] = i*2; //用ip指针访问数组元素 for(int* iP=iArray; iP using namespace std; //------------------------------------- int main(){ int a = 78, c = 18; const int* ip = &a; const int* const icp = &c; a = 60; c = 28; cout<<"ip =>"<"< using namespace std; //------------------------------------- int main(){ int int1 = 5; int& rInt = int1; cout<<"&int1: "<<&int1<<" int1: "< using namespace std; //------------------------------------- int main(){ int sum[5]={0}; //存放每种方法的结果 int iArray[]={1,4,2,7,13,32,21,48,16,30}; int size = sizeof(iArray)/sizeof(*iArray); int* iPtr=iArray; for(int n=0; n using namespace std; //------------------------------------- void mySwap(int* a, int* b); //------------------------------------- int main(){ int a = 16, b = 48; cout<<"a = "<