一、简单变量
1、变量名
为了把信息存储在计算机,我们需要知道3个基本属性:信息将要存储在哪里、存储什么值、存储什么类型的信息。C++可以采用声明一个变量的方式来实现,声明中的类型描述了数据的类型,变量名可以用来找到变量的数据和位置。
可以使用变量名来访问该内存单元,虽然这些语句并没有说明这个值存储在内存的什么位置,但程序确实存储了这些信息,我们可以使用&运算符来检索该变量的内存地址。
变量的命名规则:
- 在名称中只能使用字母字符、数字和下划线;
- 名称的第一个字符不能是数字;
- 区分大小写;
- 不能将C++关键字用作名称;
- 以两个下划线或下划线和大写字母打头的名称被保留给实现(编译器及其使用的资源)使用。以一个下划线开头的名称被保留给实现,用作全局标识符。
- C++对于名称的长度没有限制,名称中所有的字符都有意义,但有些平台有长度限制。
2、整型
整数就是没有小数部分的数字,不可能用有限的计算机内存表示所有整数。不同的C++整型使用不同的内存量来存储整数,使用的内存量越大,表示的整数范围也越大。另外,有的整数类型可以表示正值和负值,而有的类型不能表示负值。术语宽度用于描述存储整数时使用的内存量,使用的内存越多,则越宽。
3、多种整型
计算机内存由一些叫做位(bit)的单元组成,C++的short、int、long和long long类型通过使用不同数目的位来存储值,最多能表示4种不同的整数宽度。short类型是short int的缩写,long类型是long int的缩写。
C++确保了每种类型的最小长度:
- short最少16位;
- int至少与short一样长;
- long至少32位,且至少与int一样长;
- long long至少64位,且至少与long一样长。
计算机内存的基本单位是位(bit),字节(byte)通常指8位内存单元。从这个意义上讲,字节指的就是描述计算机内存量的度量单位,1KB等于1024字节,1MB等于1024KB。然而C++字节由至少能够容纳实现的基本字符集的相邻位组成,也就是说可能取值的数目必须等于或超过字符数目。美国通用的字符集ASCII和EBCDIC字符集,都可以用8位来存储;而国际编码可能需要更大的字符集如Unicode,因此有些实现使用16位或32位的字节。有些人使用八位组表示8位字节。
这四种类型(short、int、long、long long)都是符号类型,这意味着每种类型的取值范围中,正值和负值的绝对值几乎相等,但一般最小负值的绝对值比最大正值的绝对值大1。
sizeof运算符返回类型或变量的长度,单位为字节。运算符是内置的语言元素,对一个或多个数据进行运算,并生成一个值。
#include <iostream>
#include <climits>
using namespace std;
int main()
{
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 n_short<<" bytes. "<<endl;
cout<<"long is "<<sizeof n_long<<" bytes. "<<endl;
cout<<"long long is "<<sizeof n_llong<<" bytes. "<<endl;
cout<<"最大值:"<<endl;
cout<<"int: "<<n_int<<endl;
cout<<"short: "<<n_short<<endl;
cout<<"long: "<<n_long<<endl;
cout<<"long long: "<<n_llong<<endl;
cout<<"int最小值:"<<INT_MIN<<endl;
cout<<"每个字节几位:"<<CHAR_BIT<<endl;
return 0;
}
(1)运算符sizeof和头文件climits
sizeof运算符指出,在使用8位字节的系统中,int的长度是4个字节。可以对类型名或者变量名使用sizeof运算符,当对类型名使用时,需要将类型名放在括号中;当对变量名使用时,括号是可选的。头文件climits定义了符号常量来表示类型的限制,编译器厂商提供了climits文件,该文件指出了其编译器中的值。
climits文件中包含与下面类似的语句:
#define INT_MAX 32767
在C++编译过程中,首先将源代码传递给预处理器,该预处理器编译指令告诉预处理器,让它在程序中查找INT_MAX并将其替换为32767。因此#define编译指令的工作方式与文本编译器或字处理器中的全局搜索并替换命令相似。修改后的程序在经过这些替换后被编译。预处理器查找独立的标记,跳过嵌入的单词,同样,我们也可以使用#define来定义我们自己的符号常量。然而,#define是C语言遗留下来的,C++提供的一种更好的创建符号常量的方法就是使用关键词const,所以不会经常使用#define。而那些被设计成可以用于C和C++的头文件,必须使用#define。
(2)初始化
初始化将赋值和声明合并在一起了。可以使用字面值常量来初始化,也可以将变量初始化为另一个变量,条件是后者已经定义过,甚至可以使用表达式来初始化变量,条件是当程序执行到该声明时,表达式中所有的值都是已知的。C++还有一种C没有的初始化语法:
int a=1;//C
int b(2);//C++
如果知道变量的初始值是什么,就应该进行初始化。
(3)C++11初始化方法
还有另外一种初始化方法,这种方法用于数组和结构。但在C++98中,也可用于单值变量。
int a={2};
将大括号初始化器用于单值变量的情形还不多,但C++11标准使得这种情形更多了。这种初始化方法的等号是可选的。其次,大括号内可以不包含任何东西。在这种情况下,变量将被初始化成零。以前,C++使用不同的方式来初始化不同的类型,C++11使得可将大括号初始化器用于任何类型,这是一种通用的初始化语法。
4、无符号类型
前面的四种整型都有一种不能存储负值的无符号变体,可以用来增大变量能够存储的最大值。仅当数值不为负时才使用无符号类型,使用时,只需要用unsigned来修饰就好。unsigned的本身是unsigned int的缩写。
#include <iostream>
#include <climits>
#define zero 0
using namespace std;
int main()
{
short sam=SHRT_MAX;
unsigned short sue=sam;
cout<<"Sam has "<<sam<<" dollars and Sue has "<<sue;
cout<<" dollars deposited. "<<endl
<<"Add $1 to each account. "<<endl<<"Now ";
sam=sam+1;
sue=sue+1;
cout<<"Sam has "<<sam<<" dollars and Sue has "<<sue;
cout<<" dollars deposited. \nPoor Sam!"<<endl;
sam=zero;
sue=zero;
cout<<"Sam has "<<sam<<" dollars and Sue has "<<sue;
cout<<" dollars deposited. "<<endl;
cout<<"Take $1 from each account. "<<endl<<"Now ";
sam=sam-1;
sue=sue-1;
cout<<"Sam has "<<sam<<" dollars and Sue has "<<sue;
cout<<" dollars deposited. "<<endl<<"Lucky Sue!"<<endl;
return 0;
}
可以看出,这些整型变量的行为就像里程表,当超越了限制,其值将为范围另一端的取值。C++确保了无符号类型的这种行为,但C++并不保证符号整型超越限制时(上溢和下溢)不出错。
5、选择整型类型
通常,int被设计为对目标计算机而言最为“自然”的长度,自然长度指计算机处理起来效率最高的长度,所以通常使用int。
如果变量的值不可能为负数,那么可以使用无符号类型,可以用来表示更大的值。
如果知道变量可能表示的整数值大于16位整数的最大可能值,则使用long,防止从int32位系统移植到int16位系统时发生错误。
如果short比int小,则使用short可以节省内存。通常,仅当有大型数组时,才有必要用short。数组是一种数据结构,在内存中连续存储同类型的多个值。如果节省内存很重要,那么就应该使用short,即使它们长度一样,防止从int16位移植到int32位时内存所需翻倍。
如果只需要一个字节,则使用char。
6、整型字面值
整型字面值(常量)是显式地书写的常量。C++可以使用三种不同的方式来书写整数:基数为10,基数为8和基数为16。C++使用前一位或两位来标识数字常量的基数,如果第一位为1~9,则基数为10;如果第一位为0,第二位为1~7,则基数为9;如果前两位为0x或0X,则基数为16。而不管写法如何,数据在计算机中都是以二进制存储的。
#include <iostream>
using namespace std;
int main()
{
int a=42;
int b=0x42;
int c=042;
cout<<"a = "<<a<<"(42 in decimal)"<<endl;
cout<<"b = "<<b<<"(0x42 in hex)"<<endl;
cout<<"c = "<<c<<"(042 in octal)"<<endl;
return 0;
}
如果要以十六进制或八进制方式显示值,则可以使用cout的一些特性。头文件iostream提供了控制符endl,同样它还提供了控制符dec、hex和oct,分别用于指示cout以十进制、十六进制和八进制显示整数。默认格式为十进制,在修改格式之前,原来的格式将一直有效。
#include <iostream>
using namespace std;
int main()
{
int a=42;
cout<<"a = "<<a<<" (42 in decimal)"<<endl;
cout<<oct;
cout<<"a = "<<a<<" (42 in oct)"<<endl;
cout<<hex;
cout<<"a = "<<a<<" (42 in hex)"<<endl;
return 0;
}
诸如cout<<hex;等代码不会在屏幕上显示任何内容,只是修改cout显示整数的方式。因此,控制符hex实际上是一条消息,告诉cout采取何种行为,且控制符hex在名称空间std中,所以不可以当作变量名;而如果没有using编译指令,则可以当作变量名。
7、C++如何确定常量类型
程序的声明将特定整型变量的类型告诉了编译器,那么对于整型常量来说,除非有理由存储为其它类型(如使用了特殊后缀或者值太大,不能存储为int),否则C++将整型常量存储为int类型。
首先考察后缀:后缀是放在数字常量后面的字母,用于表示类型。
整数后面的l或L后缀表示该整型为long常量,u或U后缀表示unsigned int常量,ul(可以采用任何一种顺序,大写和小写均可)表示unsigned long常量,C++11提供了ll或LL来表示long long和ull、Ull、uLL、ULL表示的unsigned long long。
其次考察长度:在C++中,对于不带后缀的十进制数,将使用int、long、long long中能够存储该数的最小类型来表示;对于不带后缀的十六进制或八进制整数,将使用下面几种类型中能够存储该数的最小类型表示:int、unsigned int、long、unsigned long、long long或unsigned long long。在将40000表示为long的计算机系统中,十六进制数0x9C40(40000)将被表示为unsigned int。这是因为十六进制通常被用来表示内存地址,而内存地址是没有符号的,因此unsigned long比long更适合存储十六位地址。
8、char类型:字符和小整数
char类型是专门为了存储字符而设计的,存储字母和存储数字不同,编程语言通过使用字母的数值编码实现。因此,char类型是另一种整型,它足够长,能够表示目标计算机系统中的所有基本符号(所有的字符、数字、标点符号等)。实际上,很多系统的基本符号都不超过128个,用一个字节即可表示所有符号。因此,虽然char常被用来处理字符,但也可以用作比short更小的整型。
字符集中的字符用数值编码表示,C++使用的是其主机系统的编码,如ASCII和EBCDIC;C++支持的宽字符可以存储更多的值,如Unicode。
输入时,cin将键盘输入的字符转换为字符编码;输出时,cout将字符编码转换为字符。cin和cout的行为都是由变量类型引导的。如果将77存储在int中,cout将输出77。C++对字符用单引号,对字符串使用双引号。
cout的一项特性是cout.put()函数,该函数显示一个字符。
#include <iostream>
using namespace std;
int main()
{
char ch='M';
int i=ch;
cout<<"The ASCII code for "<<ch<<" is "<<i<<endl;
cout<<"Add one to the character code: "<<endl;
ch=ch+1;
i=ch;
cout<<"The ASCII code for "<<ch<<" is "<<i<<endl;
cout<<"Displaying char ch using cout.put(): ";
cout.put(ch);
cout.put('i');
return 0;
}
(1)程序说明
C++将字符表示为整数提供了方便,使得操纵字符值很容易。不必使用笨重的转换函数在字符和ASCII码之间来回转换。即使通过键盘输入的数字也被视为字符。
char ch;
cin >> ch;
如果输入5并按回车键,上述代码将读取字符“5”,并将其对应的字符编码存储到变量ch中。
int n;
cin >> n;
如果输入5并按回车键,上述代码将读取字符“5”,并将其转换为数字5存储到变量ch中。
(2)成员函数cout.put()
类定义了如何表示和控制数据。成员函数归类所有,描述了操纵类数据的方法。类ostream中有一个put()成员函数,用来输出字符。只能通过类的特定对象(如cout对象)来使用成员函数。要通过对象使用成员函数就需要使用句点将对象名和函数名称连接起来。句点被称为成员运算符。
cout.put()成员函数提供了另一种显示字符的方法,可以替代插入运算符。原因是,在C++的Release2.0之前,cout将字符变量显示为字符,而字符常量显示为数字,C++早期版本和C一样,也把字符常量存储为int类型。即'M'的编码77被存储在一个16位或32位的单元中,而char一般占8位。下面的语句从常量'M'中复制8位到变量char中:
char ch = 'M';
但对cout来说,'M'和ch完全不同,虽然存储的值是一样的。所以,下面的语句将打印$符号的ASCII码值:
cout << '$';
但下面的语句将打印字符$:
cout.put('$');
在Release2.0之后,C++将字符常量存储为char类型,而不是int类型,那么cout可以正确处理字符常量了。
cin对象同样也有几种不同的方式可以读取输入的字符。
(3)char字面值
在C++中,书写字符常量的方式有多种。
对于常规字符(字母、数字和标点符号),最简单的方法是将字符用单引号括起。这种表示法代表的是字符的数值编码,但它优于数值编码,它更加清晰,且不需要知道编码方式。
有些字符不能通过键盘直接输入到程序中,有一些字符被C++赋予了特殊的含义,也无法直接从键盘输入到程序中。对于这些字符,C++提供了一种特殊的表示方法——转义序列。可以在字符串或字符常量中使用转义序列。
应该像处理常规字符那样处理转义序列,也就是说,将它作为字符常量时,应该用单引号括起;将它们放入字符串时,不要使用单引号。
换行符可以替代endl,用于在输出中重起一行。可以使用字符常量表示法('\n')或者字符串方式("\n")来表示。
cout << endl; //使用endl控制符
cout << '\n'; //使用字符常量
cout << "\n"; //使用字符串
在较长的字符串中,换行符更方便一点;在显示数字时,endl更方便一点。
可以基于字符的八进制和十六进制编码来使用转义序列。如Substitute (替补)的ASCII码为26,对应的八进制编码为032,十六进制编码为0x1a。可以用下面的转义序列表示该字符:\032或\x1a。将这些编码用单引号括起,可以得到相应的字符常量,也可以把它们放到字符串里。
在可以使用数字转义序列或符号转义序列时,应使用符号序列。数字表示与特定的编码方式有关,而符号表示适用于各种编码方式,其可读性也更强。
#include <iostream>
using namespace std;
int main(){
cout << "\aOperation \"Hyperhype\" is now activated!\n";
cout << "Enter your agent code:_____\b\b\b\b\b";
long code;
cin>>code;
cout << "\aYou entered "<<code<<"...\n";
cout <<"\aCode verified! Proceed with Plan Z3!\n";
return 0;
}
(4)通用字符名
C++实现支持一个基本的源字符集,即可用来编写源代码的字符集。它由标准美国键盘上的字符(大写和小写)和数字、C语言中使用的符号以及其他一些字符(如换行符和空格)组成。还有一个基本的执行字符集,它包括在程序执行期间可处理的字符,它增加了一些字符,如退格和振铃。
C++还允许实现提供扩展源字符集和扩展执行字符集,另外那些被作为字母的额外字符也可用于标识符名称中。C++有一种表示这种特殊字符的机制,它独立于任何特定的键盘,使用的是通用字符名。
通用字符名的用法类似于转义序列,通用字符名可以以\u或\U打头,\u后面是4个十六进制位,\U后面是8个十六进制位,这些位表示的是字符的ISO 10646码点,ISO 10646是这一种正在制定的国际标准,为大量的字符提供了数值编码。
如果所用的实现支持扩展字符,则可以在标识符和字符串中使用通用字符名。实际上,从易读性的角度来看,在变量名中使用\u00F6没有多大意义,但如果实现的扩展源字符集包括所对应的字符,它可能允许从键盘上输入该字符。
注意,C++使用“通用编码名”,而不是“通用编码”,这是因为应将\u00F6解释为“Unicode码点为U-00F6的字符”。支持Unicode的编译器知道这是对应的哪个字符,但无须使用内部编码00F6,无论计算机使用的是ASCII还是其他编码系统,都可在内部表示字符T;同样,在不同的系统中,将使用不同的来表示字符。在源代码中,可使用适用于所有系统的通用编码名,而编译器将根据当前系统使用合适的内部编码来表示。
Unicode提供了一种表示各种字符集的解决方案——为大量字符和符号提供标准数值编码,并根据类型将它们分组。Unicode给每个字符一个编号——码点,类似于U-222B,其中U表示这是一个Unicode字符,而222B是该字符的十六进制编号。国际标准化组织(ISO)建立了一个工作组,专门开发ISO 10646——这也是一个对多种语言文本进行编码的标准。ISO 10646小组和Unicode小组从1991年开始合作,确保他们的标准同步。
(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存储ASCII字符,则char有没有符号都没有关系,在这种情况下,可以使用char。
(6)wchar_t
程序需要处理的字符集可能无法用一个8位的字节来表示。对于这种情况,C++的处理方式有两种:首先,如果大型字符集是实现的基本字符集,则编译器厂商可以将char类型定义为一个16位的字节或者更长的字节;其次,一种实现可以同时支持一个小型的基本字符集和一个较大的扩展字符集。8位的char可以用来表示基本字符集,另一种类型wchar_t(宽字符类型)可以表示扩展字符集。
wchar_t是一个整数类型,它有足够的空间,可以表示系统使用的最大扩展字符集。这种类型与另一种整型(底层(underlying)类型)的长度和符号属性相同,对底层类型的选择取决于实现,因此在一个系统中,它可能是unsigned short,而在另一个系统中,则可能是int。
cin和cout将输入和输出看作是char流,因此不适于处理wchar_t类型。iostream头文件的最新版本提供了作用相似的工具wcin和wcout,可用于处理wchar_t流。另外,可以通过加上前缀L来指示宽字符常量和宽字符串。例如:
wchar_t bob=L'P';
wcout<<L"tall"<<endl;
(7)C++11新增类型char16_t和char32_t
在计算机系统上进行字符和字符串编码时,仅使用Unicode码点并不够。具体地说,进行字符串编码时,如果有特定长度和符号特征的类型,将很有帮助,而类型wchar_t的长度和符号特征随实现的不同而不同。因此,C++新增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。
与wchar_t一样,char16_t和char32_t也都有底层类型——一种内置整型,但底层类型可能会随着系统的改变而改变。
9、bool类型
在计算中,布尔变量的值可以是true或false。C++将非零值解释为true,将零值解释为false。现在可以使用bool类型来表示真或假,它们分别使用预定义的字面值true和flase来表示。
字面值true和false都可以类型转换为为int类型,true转换为1,而false转换为0。
另外任何数字值或指针值都可以被隐式转换(即不用显式强制转换)为bool类型。任何非零值被转换为true,而零值被转换为false。
二、const限定符
符号名称指出了常量表示的内容。另外,如果程序在多个地方使用同一个常量,则需要修改该常量时,只需要修改一个符号定义就好。有一种处理符号常量的方法就是使用const关键词来修改变量声明和初始化。
常量被初始化后,其值就被固定了,编译器将不允许再修改该常量的值。如果这样做,C++将指出程序试图给一个只读变量赋值,关键词const叫做限定符,因为它限定了声明的含义。
一种常见的做法是将名称的首字母大写,以提醒这是个常量。这不是一种通用约定,但有助于区分常量和变量。另一种约定是将整个名称大写,使用#define时,通常采用这种约定。还有一种约定是以字母k打头。其他组织还有其他特殊的编码约定,要求其程序员遵守。
创建常量的通用格式为:
const type name=value;
应该在声明中对const进行初始化。
如果在声明常量时没有提供值,则该常量的值是不确定的,且无法修改。
const相较于#define的优点:
- 它能够明确指定类型;
- 可以使用C++作用域规则将定义限制在特定的函数或文件中;
- 可以将const用于更复杂的类型。
三、浮点数
浮点数能够表示带小数的部分,提供的范围也比整型更大,如果数字很大,无法使用long存储,那么可以使用浮点数类型。
浮点数将一个带小数的值分为两部分存储,一部分表示值,称为基准值,另一部分用于对值进行放大和缩小,称为缩放因子。浮点数可以表示小数值、非常大和非常小的数值。
1.书写浮点数
C++用两种书写浮点数的方式,一种是常用的标准小数点表示法,即使小数部分为0,小数点也将确保数字以浮点形式存储。C++标准允许实现表示不同的区域,例如,提供了使用欧洲方法的机制,即将逗号而不是句点作为小数点。然而这些选项控制的是数字在输入和输出中的外观,而不是数字在代码中的外观。
第二种表示浮点值的方法是E表示法。如3.45E6,E6指的10的6次方,6被称为指数,3.45被称为尾数。E表示法最适合非常大和非常小的数。E表示法确保数字以浮点格式存储,即使没有小数点。E和e都可以,指数可正可负,但数字中不能有空格。
d.dddE+n意味着将小数点右移n位,d.dddE-n意味着将小数点左移n位,因为小数点可移动,所以称之为“浮点数”。
2.浮点类型
C++有三种浮点类型float、double和long double。这些类型是按它们可以表示的有效位数和允许的指数最小范围来描述的。有效位是数字中有意义的位。有效位不依赖小数点的位置。
C和C++对有效位数的要求是,float至少32位;double至少48位,且不少于float;long double至少和double一样多。这三种类型的有效位数可以一样多。然而,通常float为32位,double为64位,long double为80、96或128位。另外,这三种类型的指数范围至少是-37到37。可以从头文件cfloat或float.h中找到系统的限制。
#include <iostream>
using namespace std;
int main(){
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;
}
(1)程序说明
程序演示了float和double类型及它们表示数字时在精度方面的差异,该程序使用了ostream方法setf()。这种调用迫使输出使用定点表示法,以便更好地了解精度,它防止程序把较大的值切换为E表示法,并使程序显示到小数点后6位。参数iso_base::fixed和iso_base::floatfield是通过包含iostream来提供的常量。
通常cout会删除结尾的零,调用cout.setf()将在新的实现中覆盖这种行为。float的精度比double低,由于cout打印6位小数,因此mint和tub都是精确的。但当程序将每个数乘以一百万时,tub在第7个3之后就与正确值有了差异。tub在7位有效位上还是精确的,该系统确保float至少有六个有效位。然而,double类型的变量显示了13个3,因此它至少13位是精确的,系统确保double至少15个有效位。
cout所属的ostream类有一个类成员函数,能够精准地控制输出的格式——字段宽度、小数位数、采用小数格式还是E格式等。
3.浮点常量
在程序中书写浮点常量时,程序默认将它们存储为double类型。如果想要存储为float类型,需要使用f或F后缀。对于long double类型,可以使用l或L后缀。
4.浮点数的优缺点
与整数相比,浮点数有两大优点,一是它们可以表示整数之间的值,二是它们有缩放因子,它们可以表示的范围比整数大的多。但浮点运算的速度通常比整数运算慢,且精度降低。
#include <iostream>
using namespace std;
int main(){
float a=2.34e22;
float b=a+1;
cout<<"a = "<<a<<endl;
cout<<"b-a= "<<b-a<<endl;
return 0;
}
2.34e+22是一个小数点左边有23位数字,加上1,就是在第23位加1。但float类型只能表示数字的前6位或前7位,因此修改第23位对这个值不会有任何影响。
类型分类:signed char、int、short、long、long long统称为符号整型,它们的无符号版本称为无符号整型,bool、char、wchar_t、char16_t、char32_t、符号整数和无符号整数统称为整型,float、double和long double统称为浮点型,整型和浮点型统称为算术类型。
四、C++算术运算符
5种基本C++算术运算符:
- +运算符对操作数执行加法运算;
- -运算符从第一个数中减去第二个数;
- *运算符将操作数相乘;
- /运算符用第一个数除以第二个数;
- %运算符求模,它生成第一个数除以第二个数后的余数。两个操作数都必须是整型,用于浮点数会编译错误。
变量和常量都可以作为操作数。
#include <iostream>
using namespace std;
int main(){
float hats,heads;
cout.setf(ios_base::fixed,ios_base::floatfield);
cout<<"Enter a number:";
cin>>hats;
cout<<"Enter another number:";
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;
}
1.运算符优先级和结合性
当多个运算符可用于同一个操作数时,C++使用优先级规则来决定首先使用哪个运算符。
算术运算符遵循通常的代数优先级,先乘除,后加减,同时也可以利用括号来执行自己定义的优先级。其中*、/和%优先级相同,加减的优先级在它们之后。当两个运算符的优先级相同时,C++将看操作数的结合性是从左到右,还是从右到左。从左到右的结合性意味着如果两个优先级相同的运算符被作用于同一个操作数,则首先应用左边的运算符;从右到左的结合性则首先应用右边的运算符。
注意,仅当两个运算符被用于同一个操作数时,优先级和结合性规则才有效。
2.除法分支
除法运算符的行为取决于操作数的类型。如果两个操作数都是整数,则执行整数除法,结果的小数部分被抛弃,使得最后的结果是一个整数。如果其中一个(或两个)操作数是浮点值,则小数部分将保留,结果为浮点数。
#include <iostream>
using namespace std;
int main(){
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;
}
第一行输出整数部分,小数部分被丢弃;第二行和第三行表示只要有一个是浮点数,结果就是浮点数。实际上,对不同类型进行运算时,C++将它们全部转为同一类型。最后两行的相对精度表示,如果两个操作数都是double类型的,则结果就是double类型的;如果两个操作数都是float类型的,则结果就是float类型的。浮点数在默认情况下是double类型的。
在上述程序中,除法运算符表示了三种不同的运算:int除法、float除法、double除法,C++根据上下文来确定运算符的含义。使用相同的符号进行多种操作叫做运算符重载。C++有一些内置的重载示例。C++还允许扩展运算符重载,以便弄够用于用户定义的类。
3.求模运算符
求模运算符返回整数除法的余数。它与整数除法相结合,尤其适用于解决要求将一个量分成不同的整数单元的问题。
#include <iostream>
using namespace std;
int main(){
const int trans=14;
int lbs;
cout<<"Enter your weight in pounds : ";
cin>>lbs;
int stone=lbs/trans;
int pounds=lbs%trans;
cout<<lbs<<"pounds are "<<stone<<" stone, "<<pounds<<" pound(s)."<<endl;
return 0;
}
4.类型转换
C++丰富的类型允许根据需求选择不同的类型,这也使得计算机的操作更复杂。由于有11种整型和3种浮点型,因此计算机需要处理大量不同的情况,尤其是对不同的情况进行运算时。为处理这种混乱,C++自动执行很多类型转换:
- 将一种算数类型的值赋给另一种算数类型的变量时,C++将对值进行转换
- 表达式包含不同的类型时,C++将对值进行转换
- 将参数传递给函数时,C++将对值进行转换
(1)初始化和赋值进行转换
C++允许将一种类型的值赋给另一种类型的变量,这样做时,值将被转换为接受变量的类型。将一个值赋给值取值范围更大的类型通常不会导致什么问题,然而,将一个数值范围大的值赋给范围小的变量可能会需要降低精度。
将0赋给bool变量时,将被转换为false,非零值将被转换为true。
将浮点值赋给整型将导致两个问题,首先,浮点值转换为整型会将数字截短(截去小数部分);其次,float类型对于int变量来说可能太大了。在这种情况下,C++并没有定义结果是什么;这意味着不同的实现的反应可能不同。当将整数变量初始化为浮点值时,有些编译器将提出警告,表示这将丢失数据。
#include <iostream>
using namespace std;
int main(){
cout.setf(ios_base::fixed,ios_base::floatfield);
float tree=3;
int guess(3.9832);
int debt=7.2e12;
cout<<"tree = "<<tree<<endl;
cout<<"guess = "<<guess<<endl;
cout<<"debt = "<<debt<<endl;
return 0;
}
(2)以{}方式初始化时进行的转换
C++11将使用大括号地初始化称为列表初始化,因为这种初始化常用于给复杂的数据类型提供值列表。与之前的初始化方式相比,它对类型转换的要求更严格。具体地说,列表初始化不允许缩窄,即变量的类型可能无法表示赋给它的值。例如,不允许将浮点型转换为整型。
在不同的整型之间转换或将整型转换为浮点型可能会被允许,条件是编译器知道目标变量可以正确地存储赋给它的值。例如,可以将long变量初始化为int值,因为long总是至少和int一样长;相反方向的转换也可能被允许,只要int变量可以存储赋给它的long常量。且用列表初始化时,括号中需要是常量。
(3)表达式中的转换
当同一个表达式中包含两种不同的算术类型时,C++会执行两种自动转换。首先,一些类型在出现时便会自动转换;其次,有些类型在与其他类型一起出现在表达式中时,将被转换。
首先,对于自动转换。在计算表达式时,C++将bool、char、unsigned char、signed char和short转换为int。这些转换被称为整型提升。
还有其他的整型提升,如果short比int短,则unsigned short类型将转换为int;如果两者长度相同,则unsigned short类型将转换为unsigned int。这种规则确保了在对unsigned short进行提升时不会损失数据。
同样,wchar_t被提升为下列类型中第一个宽度足够存储wchar_t取值范围的类型:int、unsigned int、long或unsigned long。
将不同类型进行算术运算时,也会进行一些转换,例如将int和float相加。当运算涉及两种类型时,较小的类型将被转换为较大的类型。总之,编译器通过校验表来确定在算术表达式中执行的转换,编译器将依次查阅校验表。
校验表:
- 如果有一个操作数的类型是long double,则将另一个操作数转换为long double;
- 否则,如果有一个操作数的类型是double,则将另一个操作数转换为double;
- 否则,如果有一个操作数是float,则将另一个操作数转换为float;
- 否则,说明操作数都是整型,则执行整型提升;
- 在这种情况下,如果两个操作数都是有符号或无符号的,且其中一个操作数的级别比另一个操作数的级别低,则转换为级别高的类型;
- 如果一个操作数是有符号的,另一个是无符号的,且无符号操作数的级别比有符号操作数的级别高,则将有符号操作数转换为无符号操作数的类型;
- 否则,如果有符号类型可表示无符号类型的所有可能取值,则将无符号操作数转换为有符号操作数所属的类型;
- 否则,将两个操作数都转换为有符号类型的无符号版本。
整型级别:有符号整型按级别从高到低依次为long long、long、int、short和signed char。无符号整型的排列顺序与有符号整型相同。类型char、signed char和unsigned char的级别相同。bool类型级别最低。wchar_t、char16_t、char32_t的级别与其底层类型相同。
(4)传递参数时的转换
传递参数时的类型转换通常由C++函数原型控制。然而,也可以取消原型对参数传递的控制,但尽量不要。在这种情况下,C++将对char和short类型应用整型提升。为了保留与C的兼容性,在将参数传递给取消原型对参数传递控制的函数时,C++将float提升为double。
(5)强制类型转换
C++允许强制类型转换机制进行显式地类型转换。强制类型转换的格式有两种,如将int类型的a转换为long类型
(long) a;
long (a);
强制类型转换并不会修改变量a自身,而是创建一个新的、指定类型的值。可以在表达式中使用这个值。
强制转换的通用格式为:
(typename) value;
typename (value);
第一种格式为C语言,第二种格式为C++,新格式的想法是,要让强制类型转换就像是函数调用。这样对内置类型的强制转换就像是为用户定义的类设计的类型转换。
C++还引入了4个强制类型转换运算符,对它们的使用要求更为严格。在这四种运算符中,static_cast<>可用于将值从一种数值类型转换为另一种数值类型。
static_cast<typeName> (value);
#include <iostream>
using namespace std;
int main(){
int auks,bats,coots;
auks=19.99+11.99;
bats=(int)19.99+(int)11.99;
coots=int (19.99)+int (11.99);
cout<<"auks = "<<auks<<", bats = "<<bats<<", coots = "<<coots<<endl;
char ch='Z';
cout<<"The code for "<<ch<<" is "<<int(ch)<<endl;
cout<<"Yes, the code is "<<static_cast<int> (ch)<<endl;
return 0;
}
该程序指出了使用强制类型转换的两个原因,首先,可能有一些值被存储为double类型,但要使用它们来计算得到一个int类型的值;其次,是使一种格式的数据能够满足不同的期望。
5.C++11中的auto声明
C++11新增了一个工具,让编译器能够根据初始值的类型推断变量的类型。为此它重新定义了auto的含义,在初始化声明中,如果使用关键字auto而不指定变量的类型,编译器将把变量的类型设置成与初始值相同。
auto n=100; //int
auto x=1.5; //double
auto y=1.3e12L //long double
自动推断类型并非为这种简单情况而设计的。处理复杂类型,如标准模块库(STL)中的类型时,自动类型的优势体现出来了。
std::vector<double> scores;
std::vector<double>::iterator pv=scores.begin();
可以重写为
std::vector<double> scores;
auto pv=scores.begin();