第三章 处理数据

第三章 处理数据

3.1 简单变量

  • 程序通常都需要存储信息。为把信息存储在计算机中,程序必须记录3个基本属性:①信息将存储在哪里②要存储什么值③存储何种类型的信息。

3.1.1 变量名

  • C++提倡使用有一定含义的变量名,且必须遵循几种简单的C++命名规则:
    • 在名称中智能使用字母字符、数字和下划线(_)。
    • 名称的第一个字符不能是数字。
    • 区分大写字符与小写字符。
    • 不能将C++关键字用作名称。
    • 以两个下划线或下划线和大写字母打头的名称将保留给实现(编译器及其使用的资源)使用。以一个下划线开头的名称被保留给实现,用作全局标识符。
    • C++对于名称的长度没有限制,名称中所有的字符都有意义,但有些平台有长度限制。
  • 如果想用两个或更多的单词组成一个名称,通常的做法是用下划线字符将单词分开,如my_onions;或者从第二个单词开始将每个单词的第一个字母大写,如myEyeTooth。这两种形式都很容易将单词区分开。

3.1.2 整型

  • 整数就是没有小数部分的数字。C++提供好几种整型,这样便能够根据程序的具体要求选择最合适的整型。
  • 不同C++整型使用不同的内存量来存储整数。使用的内存量越大,可以表示的整数值范围也越大。另外,有的类型(符号类型)可表示正值和负值,而有的类型(无符号类型)不能表示负值。术语宽度用于描述存储整数时使用的内存量。使用的内存越多,则越宽。
  • C++的基本整型(按宽度递增的顺序排列)分别是char、short、int、long和C++11新增的long long,其中每种类型都有符号版本和无符号版本,因此总共有10种类型可供选择。

3.1.3 整型short、int、long和long long

  • C++的short、int、long和long long类型通过使用不同数目的位来存储值,最多能够表示4种不同的整数宽度。
  • C++提供了一种灵活的标准,它确保了最小长度,如下所示:
    • short至少16位;
    • int至少与short一样长;
    • long至少32位,且至少与int一样长;
    • long long至少64位,且至少与long一样长。
  • 类型的宽度随实现而异,这可能在将C++程序从一种环境移到另一种环境(包括在同一个系统中使用不同编译器)时引发问题。
  • 这4种类型(int、short、long和long long)都是符号类型,这意味着每种类型的取值范围中,负值和正值几乎相同。例如,16位的int的取值范围为-32768到+32767。
  • sizeof运算符返回类型或变量的长度,单位为字节。
  • 头文件climits(在老式实现中为limits.h)中包含了关于整型限制的信息。具体地说,它定义了表示各种限制的符号名称。例如,INT_MAX为int的最大取值,CHAR_BIT为字节的位数。
#include<iostream>
#include<climits>
int main(){
    using namespace std;
    int n_int = INT_MAX;
    short n_short = SHRT_MAX;
    long n_long = LONG_MAX;
    long long n_llong = LLONG_MAX;

    //获得类型的字节数
    cout<<" int is "<<sizeof(int)<<" bytes."<<endl;
    cout<<" short is "<<sizeof(short)<<" bytes."<<endl;
    cout<<" long is "<<sizeof(long)<<" bytes."<<endl;
    cout<<" long long is "<<sizeof(long long)<<" bytes."<<endl;
    cout<<endl;

    //最大值
    cout<<"Maximum values:"<<endl;
    cout<<"int:"<<n_int<<endl;
    cout<<"short:"<<n_short<<endl;
    cout<<"long:"<<n_long<<endl;
    cout<<"long long:"<<n_llong<<endl<<endl;

    cout<<"Minimum int value:"<<INT_MIN<<endl;
    cout<<"Bits per byte:"<<CHAR_BIT<<endl;
    return 0;
}
3.1.3.1 运算符sizeof和头文件limits
  • 可对类型名或变量名使用sizeof运算符。对类型名(如int)使用sizeof运算符时,应将名称放在括号中;但对变量名(如n_short)使用该运算符,括号是可选的:
cout<<" int is "<<sizeof(int)<<" bytes."<<endl;
cout<<" short is "<<sizeof n_short<<" bytes."<<endl;
  • 头文件climits定义了符号常量来表示类型的限制。如前所示,INT_MAX表示类型int能够存储的最大值。
  • 在C++编译过程中,首先将源代码传递给预处理器。#define和#include一样,也是一个预处理器编译指令。该编译指令(#define INT_MAX 32767)告诉预处理器:在程序中查找INT_MAX,并将所有的INT_MAX都替换为32767。修改后的程序将在完成这些替换后被编译。
  • 在那些被设计成可用于C和C++中的头文件中,必须使用#define。
常用类型最大值的符号常量表示
CHAR_MAXchar 的最大值
SHRT_MAXshort 的最大值
INT_MAXint 的最大值
LONG_MAXlong 的最大值
LLONG_MAXlong long 的最大 值
常用类型最小值的符号常量表示
CHAR_MINchar 的最小值
SHRT_MINshort 的最小值
INT_MINint 的最小值
LONG_MINlong 的最小值
LLONG_MINlong long 的最小值
3.1.3.2 初始化
  • 初始化将赋值与声明合并在一起。例如,下面的语句声明了变量n_int,并将int的最大取值赋给它:
int n_int = INT_MAX;
  • 上述初始化语法来自C语言,C++还有另一种C语言没有的初始化语法:

    int wrens(432);
    
  • 如果不对函数内部定义的变量进行初始化,该变量的值将是不确定的。这意味着该变量的值将是它被创建之前,相应内存单元保存的值。

  • 如果知道变量的初始值应该是什么,则应对它进行初始化,将变量声明和赋值分开,可能会带来不确定的问题。

3.1.4 无符号类型

  • 前面介绍的4种整型都有一种不能存储负数值的无符号变体,其优点是可以增大变量能够存储的最大值。例如,如果short表示的范围为-32768到+32767,则无符号版本的表示范围为0~65535。
  • 仅当数值不会为负时才应使用无符号类型,如人口、粒数等。
  • 要创建无符号版本的基本整型,只需使用关键字unsigned来修改声明即可:
unsigned short change;
unsigned int rovert;
unsigned quarterback;
unsigned long gone;
unsigned long long lang_lang;
//注意,unsigned本身是unsigned int的缩写
#include<iostream>
#define ZERO 0
#include<climits>

int main()
{
    using namespace std;
    short sam = SHRT_MAX;
    unsigned short sue = sam;
    cout<<"sam is "<<sam<<endl;
    cout<<"sue is "<<sue<<endl<<endl;

    sam = sam+1;
    sue = sue+1;
    cout<<"sam is "<<sam<<endl;
    cout<<"sue is "<<sue<<endl<<endl;
    
    sam = ZERO;
    sue = ZERO;
    cout<<"sam is "<<sam<<endl;
    cout<<"sue is "<<sue<<endl<<endl;

    sam = sam-1;
    sue = sue-1;
    cout<<"sam is "<<sam<<endl;
    cout<<"sue is "<<sue<<endl<<endl;

    return 0;
}
  • 整型变量的行为就像里程表。如果超越了限制,其值将为范围另一端的取值。C++确保了无符号类型的这种行为;但C++并不保证符号整型超越限制(上溢和下溢)时不出错。

3.1.5 选择整型类型

  • int被设置为对目标计算机而言最为“自然”的长度。自然长度指的是计算机处理起来效率最高的长度。如果没有非常有说服力的理由来选择其他类型,则应使用int。
  • 如果变量表示的值不可能为负,如文档中的字数,则可以使用无符号类型,这样变量可以表示更大的值。
  • 如果知道变量可能表示的整数值大于16位整数的最大可能值,则使用long。即使系统上int为32位,也应这样做。这样,当程序移植到16位系统时,就不会突然无法正常工作。
  • 如果要存储的值超过20亿,可使用long long。
  • 如果short比int小,则使用short可以节省内存。通常,仅当有大型整型数组时,才有必要使用short。如果节省内存很重要,则应使用short而不是使用int,即使它们的长度是一样的。
  • 如果只需要一个字节,可使用char。

3.1.6 整型字面值

  • 整型字面值(常量)是显式地书写的常量,如212或1776。与C相同,C++能够以三种不同的计数方式来书写整数:基数为10、基数为8(老式UNIX版本)和基数为16(硬件黑客的最爱)。
  • C++使用前一(两)位来标识数字常量的基数。如果第一位为19,则基数为10(十进制);因此93是以10为基数的。如果第一位是0,第二位为17,则基数为8(八进制);如果前两位为0x或0X,则基数为16(十六进制)。
#include<iostream>

int main()
{
    using namespace std;
    int chest = 42;
    int waist = 0x42;
    int inseam = 042;

    cout<<"chest="<<chest<<"(十进制中的42)"<<endl;
    cout<<"waist="<<waist<<"(十六进制中的0x42)"<<endl;
    cout<<"inseam="<<inseam<<"(八进制中的042)"<<endl;

    return 0;
}
  • 不管把值书写为十进制、十六进制还是八进制,都将以相同的方式存储在计算机中——被存储为二进制数。

  • 如果要以十六进制或八进制方式显示值,则可以使用cout的一些特殊特性。前面指出过,头文件iostream提供了控制符endl,用于指示cout重起一行。同样,它还提供了控制符dec、hex和oct,分别用于指示cout以十进制、十六进制和八进制格式显示整数。

  • 默认格式为十进制,在修改格式之前,原来的格式将一直有效。

#include<iostream>

int main()
{
    using namespace std;
    int chest = 42;
    int waist = 42;
    int inseam = 42;

    cout<<"chest="<<chest<<"(以十进制表示42)"<<endl;
    cout<<hex;
    cout<<"waist="<<waist<<"(以十六进制表示42)"<<endl;
    cout<<oct;
    cout<<"inseam="<<inseam<<"(以八进制表示42)"<<endl;
    return 0;
}
  • 诸如cout<<hex;等代码不会在屏幕上显示任何内容,而只是修改cout显示整数的方式。因此,控制符hex实际上是一条消息,告诉cout采取何种行为。
  • 由于标识符hex位于名称空间中,而程序使用了该名称空间,因此不能将hex用作变量名。然后,如果省略了编译指令using,而使用std::cout、std::endl、std::hex和std::oct,则可以将hex用作变量名。

3.1.7 C++如何确定常量的类型

  • 除非有理由存储为其他类型(如使用了特殊的后缀来表示特定的类型,或者值太大,不能存储为int),否则C++将整型常量存储为int类型。
  • 后缀是放在数字常量后面的字母,用于表示类型。整数后面的l或L后缀表示该整数为long常量;u或U后缀表示unsigned int常量;ul(可以采用任何一种顺序,大写小写均可)表示unsigned long常量;C++11提供了用于表示类型的long long的后缀ll和LL,还提供了用于表示类型unsigned long long的后缀ull、Ull、uLL和ULL。
  • 在C++中,对十进制整数采用的规则,与十六进制和八进制不同。对于不带后缀的十进制整数,将使用下面几种类型中能够存储该数的最小类型来表示:int、long或long long。对于不带后缀的十六进制或八进制整数,将使用下面几种类型中能够存储该数的最小类型来表示:int、unsigned int、long、unsigned long、long long或unsigned long long。

3.1.8 char类型:字符和小整数

  • char类型是专为存储字符(如字母和数字)而设计的。编程语言通过使用字母的数值编码来存储字母。
  • char类型是另一种整型。它足够长,能够表示目标计算机系统中的所有基本符号——所有的字母、数字、标点符号等。实际上,很多系统支持的字符都不超过128个,因此用一个字节就可以表示所有的符号。因此,虽然char最常被用来处理字符,但也可以将它用做比short更小的整型。
#include<iostream>

int main()
{
    using namespace std;
    char ch;
    cout<<"输入一个字母:";
    cin>>ch;
    cout<<"输入的字母为:"<<ch<<endl;
    return 0;
}
  • 输入时,cin将键盘输入的M转换为77;输出时,cout将值77转换为所显示的字符M;cin和cout的行为都是由变量类型引导的。如果将77存储在int变量中,则cout将把它显示为77.
#include<iostream>

int main()
{
    using namespace std;
    char ch = 'M';
    int i = ch;
    cout<<ch<<"的ASCII码为:"<<i<<endl;

    cout<<"对字符增加一位:"<<endl;
    ch = ch+1;
    i = ch;
    cout<<ch<<"的ASCII码为:"<<i<<endl;

    cout<<"使用cout.put()函数显示一个字符"<<endl;
    cout.put(ch)<<endl;
    cout.put(i)<<endl;
    cout.put('!')<<endl;
    return 0;
}
3.1.8.1 程序说明
  • ‘M’表示字符M的数值编码,因此将char变量ch初始化为’M’,将把ch设置为77。然后,程序将同样的值赋给int变量i,这样ch和i的值都是77。接下来,cout把ch显示为M,而把i显示为77。如前所述,值的类型将引导cout选择如何显示值。
  • 由于ch实际上是一个整数,因此可以对它使用整数操作,如加1,这将把ch的值变为78。
3.1.8.2 成员函数cout.put()
  • cout.put()是一个重要的C++OOP概念——成员函数。
  • 类定义了如何表示和控制数据。成员函数归类所有,描述了操作类数据的方法。
  • 只能通过类的特定对象来使用成员函数。要通过对象使用成员函数,必须用句点将对象名和函数名称连接起来。句点被称为成员运算符。
3.1.8.3 char字面值
  • 对于常规字符(如字母、标点符号和数字),最简单的方法是将字符用单引号括起。这种表示法代表的是字符的数值编码。这种表示法优于数值编码,它更加清晰,且不需要知道编码方式。ASCII系统中的对应情况如下:
    • 'A’为65,即字符A的ASCII码;
    • 'a’为97,即字符a的ASCII码;
  • 有些字符不能直接通过键盘输入到程序中。对于这些字符,C++提供了一种特殊的表示方法——转义序列。可以在字符串或字符常量中使用这些表示法。
#include<iostream>

int main()
{
    using namespace std;
    char alarm = '\a';
    cout<<alarm<<"Don't do that again!"<<endl;
    cout<<"Ben \"Buggsie\" Hacker";
    return 0;
}

在这里插入图片描述

  • 应该像处理常规字符(如Q)那样处理转义序列(如\n)。也就是说,将它们作为字符常量时,应用单引号括起;将它们放在字符串中时,不要使用单引号。
  • 可以基于字符的八进制和十六进制编码来使用转义序列。例如,Ctr+Z的ASCII码为26,对应的八进制编码为032,十六进制编码为0x1a。可以用下面的转义序列来表示该字符。
  • 在可以使用数字转义序列或符号转义序列(如\0x8和\b)时,应使用符号序列。数字表示与特定的编码方式(如ASCII码)相关,而符号表示适用于任何编码方式,其可读性也更强。
3.1.8.4 通用字符名
  • C++有一种表示国外特殊字符的机制,它独立于任何特定的键盘,使用的是通用字符名(universal character name)。
  • 通用字符名的用法类似于转义序列。通用字符名可以以\u或\U打头。\u后面是8个十六进制位,\U后面则是16个十六进制位。这些位标识的是字符的ISO 10646码点(ISO 10646是一种正在制定的国际标准,为大量的字符提供了数值编码)。
  • 如果所用的实现支持扩展字符,则可以在标识符(如字符常量)和字符串中使用通用字符名。
#include<iostream>

int main()
{
    using namespace std;
    cout<<"Let them eat g\u00E2teau."<<endl;
    return 0;
}
3.1.8.5 signed char和unsigned char
  • 与int不同的是,char在默认情况下既不是没有符号,也不是有符号。是否有符号由C++实现决定,这样编译器开发人员可以最大限度地将这种类型与硬件属性匹配起来。如果char有某种特定的行为非常重要,则可以显式地将类型设置为signed char或unsigned char
  • 如果将char用作数值类型,则unsigned char和signed char之间的差异将非常重要。unsigned char类型的表示范围通常为0~255,而signed char的表示范围为-128~127。
  • 假设要使用一个char变量来存储200这样大的值,则在某些系统上可以,而在另一些系统上可能不可以。但是用unsigned char可以在任何系统上达到这种目的。
  • 如果使用char变量来存储标准ASCII字符,则char有没有符号都没关系,在这种情况下,可以使用char。
3.1.8.6 wchar_t
  • 程序需要处理的字符集可能无法用一个8位的字节表示,如日文汉字系统。对于这种情况,C++的处理方式有两种:
    • 首先,如果大型字符集是实现的基本字符集,则编译器厂商可以将char定义为一个16位的字节或更长的字节。
    • 其次,一种实现可以同时支持一个小型基本字符集和一个较大的扩展字符集。8位char可以表示基本字符集,另一种类型wchar_t(宽字符类型)可以表示扩展字符集。wchar_t类型是一种整数类型,它有足够的空间,可以表示系统使用的最大扩展字符集。这种类型与另一种整型(底层类型)的长度和符号属性相同。对底层类型的选择取决于实现,因此在一个系统中,它可能是unsigned short,而在另一个系统中,则可能是int。
  • cin和cout将输入和输出看作是char流,因此不适用于用来处理wchar_t类型。iostream头文件的最新版本提供了作用相似的工具——wcin和wcout,可用于处理wchar_t流。
  • 可以通过加上前缀L来指示宽字符常量和宽字符串。
#include<iostream>

int main()
{
    using namespace std;
    //将字母p的wchar_t版本存储到变量bob中,并显式单词tall的wchar_t版本
    wchar_t bob = L'p';
    wcout<<L"tall"<<endl;
    return 0;
}
3.1.8.7 C++11新增的类型:char16_t和char32_t
  • C++11新增了类型char16_t和char32_t,其中前者是无符号的,长16位,而后者也是无符号的,但长32位。
  • C++11使用前缀u表示char16_t字符常量和字符串常量,;使用前缀U表示char32_t常量。类型char16_t与/u00F6形式的通用字符名匹配,而类型char32_t与/U0000222B形式的通用字符名匹配。前缀u和U分别指出字符字面值的类型为char16_t和char32_t。
#include<iostream>

int main()
{
    using namespace std;
    char16_t ch1 = u'q';
    char32_t ch2 = U'\U0000222B';
    cout<<ch1<<endl;
    cout<<ch2<<endl;
    return 0;
}

3.1.9 bool类型

  • 在计算中,布尔变量的值可以是true或false。
  • 字面值true和false都可以通过提升转换为int类型,true被转换为1,而false被转换为0。
  • 任何数字值或指针值都可以被隐式转换(即不同显式强制转换)为bool值。任何非零值都被转换为true,而零被转换为false。

3.2 const限定符

  • 使用const创建常量的通用格式:
//应在声明中对const进行初始化。
//如果在声明常量时没有提供值,则该常量的值将是不确定的,且无法修改。
const type name = value;
  • 常量被初始化后,其值就被固定了,编译器将不允许再修改该常量的值。
  • 关键字const叫做限定符,因为它限定了声明的含义。
  • const相对于#define的优点:
    • const能够明确指定类型。
    • const可以使用C++的作用域规则将定义限制在特定的函数或文件中。
    • 可以将const用于更复杂的类型。

3.3 浮点数

  • 浮点数能够表示带小数部分的数字,它们提供的值范围也更大。如果数字很大,无法表示为long类型,则可以使用浮点类型来表示。浮点数能够表示小数值、非常大和非常小的值。
  • 计算机将带小数部分的数字分成两部分存储。一部分表示值,另一部分用于对值进行放大或缩小。
  • 对于数字34.1245和34124.5,它们除了小数点的位置不同外,其他都是相同的。可以把第一个数表示为0.341245(基准值)和100(缩放因子),而将第二个数表示为0.341245(基准值相同)和10000(缩放因子更大)。缩放因子的作用是移动小数点的位置,术语浮点因此而得名。C++内部表示浮点数的方法与此相同,只不过它基于的是二进制数,因此缩放因子是2的幂,不是10的幂。

3.3.1 书写浮点数

  • C++有两种书写浮点数的方式:
    • 第一种是使用常用的标准小数点表示法。即使小数部分为0(如8.0),小数点也将确保该数字以浮点格式(而不是整数格式)表示。
    • 第二种表示浮点值的方法叫做E表示法,其外观是像这样的:3.45E6,这指的是3.45与1000000相乘的结果;E6指的是10的6次方。因此,3.45E6表示的是3450000,6被称为指数,3.45被称为尾数。
      • E表示法最适合于非常大和非常小的数。
      • E表示法确保数字以浮点格式存储,即使没有小数点。既可以使用E也可以使用e,指数可以是正数也可以是负数。然后,数字中不能有空格。指数为负数意味着除以10的乘方。

3.3.2 浮点类型

  • C++有3种浮点类型:float、double和long double。这些类型是按它们可以表示的有效数位和允许的指数最小范围来描述的。有效位是数字中有意义的位。
  • C和C++对于有效位数的要求是,float至少32位,double至少48位,且不少于float,long double至少和double一样多。这三种类型的有效位数可以一样多。然而,通常float为32位,double为64位,long double为80、96或128位。另外,这3种类型的指数范围至少是-37到37。
#include<iostream>

int main()
{
    using namespace std;
    //调用setf迫使输出使用定点表示法,以便更好地了解精度,它防止程序把较大的值切换为E表示法,并使程序显示到小数点后6位。
    //ios_base::fixed是设置cout为定点输出格式
    //ios_base::floatfield是设置输出时按浮点格式,小数点后有6位数字
    cout.setf(ios_base::fixed,ios_base::floatfield);
    float tub = 10.0/3.0;
    double mint = 10.0/3.0;
    const float million = 1.0e6;

    cout<<"tub="<<tub;
    cout<<", a million tubs="<<million*tub;
    cout<<",\nand ten million tubs=";
    cout<<10*million*tub<<endl;

    cout<<"mint="<<mint<<" and a million mints=";
    cout<<million*mint<<endl;
    return 0;
}

3.3.3 浮点常量

  • 默认情况下,像8.24和2.4E8这样的浮点常量都属于double类型。
  • 如果希望常量为float类型,请使用f或F后缀。
  • 对于long double类型,可使用l或L后缀(由于l看起来像1,因此L是更好的选择)。

3.3.4浮点数的优缺点

  • 与整数相比,浮点数的优点:
    • 浮点数可以表示整数之间的值
    • 由于有缩放因子,它们可以表示的范围大得多
  • 与整数相比,浮点数的缺点:
    • 浮点运算的速度通常比整数运算慢,且精度将降低
#include<iostream>

int main()
{
    using namespace std;
    float a = 2.34E+22f;
    float b = a+1.0f;
    cout<<"a="<<a<<endl;
    cout<<"b-a="<<b-a<<endl;
    return 0;
}
  • 该程序将数字加1,然后减去原来的数字。结果应该为1,但是结果为0。
  • 2.34E+22是一个小数点左边有23位的数字。加上1,就是在第23位加1。但float类型只能表示数字中的前6位或前7位,因此修改第23位对这个值不会有任何影响。

3.4 C++算术运算符

  • 五种基本的C++算数运算符:
    • +运算符对操作数执行加法运算。
    • -运算符从第一个数中减去第二个数。
    • *运算符将操作数相乘。
    • /运算符用第一个数除以第二个数。如果两个操作数都是整数,则结果为商的整数部分。
    • %运算符求模。它生成第一个数除以第二个数后的余数。两个操作数必须都是整型,将该运算符用于浮点数将导致编译错误。如果其中一个是负数,则结果的符号满足如下规则:(a/b)*b+a%b=a。
#include<iostream>

int main()
{
    using namespace std;
    //对于float,C++只保证6位有效位。
    float hats,heads;

    //调用setf迫使输出使用定点表示法,以便更好地了解精度,它防止程序把较大的值切换为E表示法,并使程序显示到小数点后6位。
    //ios_base::fixed是设置cout为定点输出格式
    //ios_base::floatfield是设置输出时按浮点格式,小数点后有6位数字
    cout.setf(ios_base::fixed,ios_base::floatfield);
    cout<<"输入一个数字:";
    cin>>hats;
    cout<<"输入另一个数字:";
    cin>>heads;

    cout<<"hats="<<hats<<";heads="<<heads<<endl;
    cout<<"hats+heads="<<hats+heads<<endl;
    cout<<"hats-heads="<<hats-heads<<endl;
    cout<<"hats*heads="<<hats*heads<<endl;
    cout<<"hats/heads="<<hats/heads<<endl;
    return 0;
}

3.4.1 运算符优先级和结合性

  • 当多个运算符可用于同一个操作数时,C++使用优先级规则来决定首先使用哪个运算符。
  • 算术运算符遵循通常的代数优先级,先乘除,后加减。
  • 可以使用括号来执行自己定义的优先级。
  • 当两个运算符的优先级相同时,C++将看操作数的结合性是从左到右,还是从右到左。从左到右的结合性意味着如果两个优先级相同的运算符被同时用于同一个操作数,则首先应用左侧的运算符。从右到左的结合性则首先应用右侧的运算符。
  • 仅当两个运算符被用于同一个操作数时,优先级和结合性规则才有效。

3.4.2 除法分支

  • 除法运算符(/)的行为取决于操作数的类型。如果两个操作数都是整数,则C++将执行整数除法。这意味着结果的小数部分将被丢弃,使得最后的结果是一个整数。
  • 如果其中有一个(或两个)操作数是浮点值,则小数部分将保留,结果为浮点数。
#include<iostream>

int main()
{
    using namespace std;
    cout.setf(ios_base::fixed,ios_base::floatfield);
    cout<<"Integer division:9/5="<<9/5<<endl;
    cout<<"Floating-point division:9.0/5.0="<<9.0/5.0<<endl;
    cout<<"Mixed division:9.0/5="<<9.0/5<<endl;
    cout<<"double constants:1e7/9.0="<<1e7/9.0<<endl;
    cout<<"float constants:1e7f/9.0f="<<1e7f/9.0f<<endl;
    return 0;
}
-------------------------------------------------------------------------------------------------------------------
Integer division:9/5=1
Floating-point division:9.0/5.0=1.800000
Mixed division:9.0/5=1.800000
double constants:1e7/9.0=1111111.111111
float constants:1e7f/9.0f=1111111.125000
  • 对不同类型进行运算时,C++将把它们全部转换为同一类型。
  • 如果两个操作数都是double类型,则结果为double类型。
  • 如果两个操作数都是float类型,则结果为float类型。
  • 浮点常量在默认情况下为double类型。

3.4.3 求模运算符

  • 求模运算符返回整数除法的余数。它与整数除法相结合,尤其适用于解决要求将一个量分成不同的整数单元的问题。

3.4.4 类型转换

  • C++自动执行很多类型转换:
    • 将一种算数类型的值赋给另一种算数类型的变量时,C++将对值进行转换。
    • 表达式中包含不同的类型时,C++将对值进行转换。
    • 将参数传递给函数时,C++将对值进行转换。
3.4.4.1 初始化和赋值进行的转换
  • C++允许将一种类型的值赋给另一种类型的变量。这样做时,值将被转换为接受变量的类型。
  • 将一个值赋给取值范围更大的类型通常不会导致什么问题。例如,将short值赋给long变量并不会改变这个值,只是占用的字节更多而已。
  • 如果将一个很大的long值赋给float变量将降低精度。因为float只有6位有效数字。
  • 有些转换是安全的,有些则会带来麻烦。以下是一些可能出现的转换问题:

在这里插入图片描述

  • 将0赋给bool变量时,将被转换为false;而非零值将被转换为true。
  • 将浮点值赋给整型将导致两个问题:
    • 首先,将浮点值转换为整型会将数字截短(除掉小数部分)。
    • 其次,float值对于int变量来说可能太大了。在这种情况下,C++并没有定义结果应该是什么;这意味着不同的实现的反应可能不同。
#include<iostream>

int main()
{
    using namespace std;
    cout.setf(ios_base::fixed,ios_base::floatfield);
    float tree = 3;
    int guess = 3.9832;
    //int变量无法存储7.2E12,这导致C++没有对结果进行定义的情况发生
    int debt = 7.2E12;
    cout<<"tree="<<tree<<endl;
    cout<<"guess="<<guess<<endl;
    cout<<"debt="<<debt<<endl;
    return 0;
}
-------------------------------------------------------------------------------------------------------------------
tree=3.000000
guess=3
debt=2147483647
3.4.4.2 以{}方式初始化时进行的转换
  • C++将使用大括号的初始化称为列表初始化,因为这种初始化常用于给复杂的数据类型提供值列表。
  • 列表初始化不允许缩窄,即变量的类型滚可能无法表示赋给它的值。例如,不允许将浮点型转换为整型。在不同的整型之间转换或将整型转换为浮点型可能被允许,条件是编译器知道目标变量能够正确地存储赋给它的值。
3.4.4.3 表达式中的转换
  • 当同一个表达式中包含两种不同的算数类型时,C++将执行两种自动转换:首先,一些类型在出现时便会自动转换;其次,有些类型在与其他类型同时出现在表达式中时将被转换。
  • 在计算表达式时,C++将bool、char、unsigned char、signed char和short值转换为int。具体地说,true被转换为1,false被转换为0。这些转换被称为整型提升。
//C++程序取得chickens和ducks的值,并将它们转换为int。在得到结果后,再将结果转换为short类型,因为结果被赋给一个short变量。
short chickens = 20;
short ducks = 35;
short fowl = chickens+ducks;
  • 通常将int类型选择为计算机最自然的类型,这意味着计算机使用这种类型时,运行速度可能最快。

  • 如果short比int短,则unsigned short类型将被转换为int;如果两种类型(short和int)的长度相同,则unsigned short类型将被转换为unsigned int。这种规则确保了在对unsigned short进行提升时不会损失数据。

  • wchar_t被提升成为下列类型中第一个宽度足够存储wchar_t取值范围的数据:int、unsigned int、long或unsigned long。

  • 将不同类型进行算数运算时,也会进行一些转换,例如将int和float相加时。当运算涉及两种类型时,较小的类型将被转换为较大的类型。

  • 编译器通过校验表来确定在算术表达式中执行的转换。C++11对这个校验表稍做了修改,下面是C++11版本的校验表,编译器将依次查阅该列表:

    1. 如果有一个操作数的类型是long double,则将另一个操作数转换为long double。
    2. 否则,如果有一个操作数的类型是double,则将另一个操作数转换为double。
    3. 否则,如果有一个操作数的类型是float,则将另一个操作数转换为float。
    4. 否则,说明操作数都是整型,因此执行整型提升。
    5. 在这种情况下,如果两个操作数都是有符号或无符号的,且其中一个操作数的级别比另一个低,则转换为级别高的类型。
    6. 如果一个操作数为有序号的,另一个操作数为无符号的,且无符号操作数的级别比有符号操作数高,则将有符号操作数转换为无符号操作数所属的类型。
    7. 否则,如果有符号类型可表示无符号类型的所有可能取值,则将无符号操作数转换为有符号操作数所属的类型。
    8. 否则,将两个操作数都转换为有符号类型的无符号版本。
  • 整型级别:

    • 有符号整型按级别从高到低依次为long long、long、int、short和signed char。
    • 无符号整型的排列顺序与有符号整型相同。
    • 类型char、signed char和unsigned char的级别相同。类型bool的级别最低。
    • wchar_t、char16_t和char32_t的级别与其底层类型相同。
3.4.4.4 传递参数时的转换
  • 传递参数时的类型转换通常由C++函数原型控制。
  • 也可以取消原型对参数传递的控制。在这种情况下,C++将对char和short类型(signed和unsigned)应用整型提升。另外,为保持与传统C语言中大量代码的兼容性,在将参数传递给取消原型对参数传递控制的函数时,C++将float参数提升为double。
3.4.4.5 强制类型转换
  • C++允许通过强制类型转换机制显式地进行类型转换。
  • 强制类型转换的格式有两种:
//第一种格式来自C语言
(typeName)value;
//第二种格式是纯粹的C++,新格式的想法是,要让强制类型转换就像是函数调用。这样对内置类型的强制类型转换就像是为用户定义的类涉及的类型转换
typeName(value);
//强制类型转换不会修改value变量本身,而是创建一个新的,指定类型的值,可以在表达式中使用这个值。
  • 使用强制类型转换的两个原因:
    • 首先,可能有一些值被存储为double类型,但要使用它们来计算得到一个int类型的值。
    • 最常见的使用强制类型转换的原因——使一种格式的数据能够满足不同的期望。
#include<iostream>

int main()
{
    using namespace std;
    int auks,bats,coots;

    auks = 19.99+11.99;
    bats = (int)19.99+(int)11.99;   //C格式
    coots = int(19.99)+int(11.99);  //C++格式
    cout<<"auks="<<auks<<endl;
    cout<<"bats="<<bats<<endl;
    cout<<"coots="<<coots<<endl;

    char ch = 'Z';
    cout<<"The code for "<<ch<<" is "<<int(ch)<<endl;
    cout<<"The code for "<<ch<<" is "<<static_cast<int>(ch)<<endl;
    return 0;
}

3.4.5 C++11中的auto声明

  • C++11新增了一个工具,让编译器能够根据初始值的类型推断变量的类型。
  • auto是一个C语言关键字。在初始化声明中,如果使用了关键字auto,而不指定变量的类型,编译器将把变量的类型设置成与初始值相同。
  • 自动推断类型并非为简单赋值的情况而设计的。在处理复杂类型,如标准模块库(STL)中的类型时,自动类型推断的优势才能显现出来。

3.5 总结

  • C++的基本类型分为两组:一组由存储为整数的值组成,另一组由存储为浮点格式的值组成。
  • 整型之间通过存储值时使用的内存量及有无符号来区分。整型从最小到最大依次是:bool、char、signed char、unsigned char、short、unsigned short、int、unsigned int、long、unsigned long以及C++11新增的long long和unsigned long long。
  • 还有一种wchar_t类型,它在这个序列中的位置取决于实现。C++11新增了类型char16_t和char32_t,它们的宽度足以分别存储16和32位的字符编码。
  • C++确保了char足够大,能够存储系统基本字符集中的任何成员,而wchar_t则可以存储系统扩展字符集中的任意成员,short至少为16位,而int至少与short一样长,long至少为32位,且至少和int一样长。确切的长度取决于实现。
  • 字符通过其数值编码来表示。I/O系统决定了编码是被解释为字符还是数字。
  • 浮点类型可以表示小数值以及比整型能够表示的值大得多的值。3种浮点类型分别是float、double和long double。C++确保float不比double长,而double不比long double长。通常,float使用32位内存,double使用64位,long double使用80到128位。
  • C++使用运算符来提供对数字类型的算术运算:加减乘除和求模。当两个运算符对同一个操作数进行操作时,C++的优先级和结合性规则可以确定先执行哪种操作。
  • 对变量赋值、在运算中使用不同类型、使用强制类型转换时,C++将把值从一种类型转换为另一种类型。

3.5 总结

  • C++的基本类型分为两组:一组由存储为整数的值组成,另一组由存储为浮点格式的值组成。
  • 整型之间通过存储值时使用的内存量及有无符号来区分。整型从最小到最大依次是:bool、char、signed char、unsigned char、short、unsigned short、int、unsigned int、long、unsigned long以及C++11新增的long long和unsigned long long。
  • 还有一种wchar_t类型,它在这个序列中的位置取决于实现。C++11新增了类型char16_t和char32_t,它们的宽度足以分别存储16和32位的字符编码。
  • C++确保了char足够大,能够存储系统基本字符集中的任何成员,而wchar_t则可以存储系统扩展字符集中的任意成员,short至少为16位,而int至少与short一样长,long至少为32位,且至少和int一样长。确切的长度取决于实现。
  • 字符通过其数值编码来表示。I/O系统决定了编码是被解释为字符还是数字。
  • 浮点类型可以表示小数值以及比整型能够表示的值大得多的值。3种浮点类型分别是float、double和long double。C++确保float不比double长,而double不比long double长。通常,float使用32位内存,double使用64位,long double使用80到128位。
  • C++使用运算符来提供对数字类型的算术运算:加减乘除和求模。当两个运算符对同一个操作数进行操作时,C++的优先级和结合性规则可以确定先执行哪种操作。
  • 对变量赋值、在运算中使用不同类型、使用强制类型转换时,C++将把值从一种类型转换为另一种类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值