这里的笔记区别于精简基础,会记录较多C++的细节
文章目录
- 前言
- 一、简单变量
- 1.1 变量名
- 以下是一些有效和无效的C++名称
- 1.2 整型
- 1.3 整型short、int、long和long long
- 数据类型对应的字节和能存储的最大值
- 1.4 无符号类型
- 越界问题
- 1.5 整型字面值
- 进制转换
- 1.6 char类型:字符和小整数
- 输入字符,输出字符对应的ASCII码
- 振铃字符和退格字符
- 1.7 bool类型
- 二、const限定符
- 三、浮点数
- 3.1 书写浮点数
- 3.2 浮点类型
- 定点模式
- 3.3 浮点数的优缺点
- float浮点常量
- 四、C++算术运算符
- 4.1 五种运算符
- 浮点数加减乘除
- 4.2 除法分支
- 双精度和单精度
- 4.3 求模运算符
- 除法和取余结合
- 4.4 类型转换
- 潜在的类型转换带来的风险
- 隐式类型转换和显式类型转换
- 4.5 C++11中的auto声明
- 总结
前言
这一章主要研究数据处理,什么是数据处理呢,就是指一些C++的命名规则,内置的整型,限定符创建符号常量,算术运算符,自动类型转换还有强制类型转换之类的知识。
一、简单变量
1.1 变量名
在讨论简单变量之前我们需要先知道变量名。变量名即我们自己创建的变量名字,这里面有一些规则需要遵循。
- 在名称中只能使用字母字符、数字和下划线(_)。
- 名称的第一个字符不能是数字。
- 区分大写字符与小写字符。
- 不能将C++关键字用作名称。
- 以两个下划线打头或以下划线和大写字母打头的名称被保留给实现(编译器及其使用的资源)使用。以一个下划线开头的名称被保留给实现,用作全局标识符。
- C++对于名称的长度没有限制,名称中所有的字符都有意义,但有些平台有长度限制。
以下是一些有效和无效的C++名称
int Poodle; //valid 有效的
int poodle //valid and distinct from Poodle 有效且与上面的首字母不同
int POODLE; //valid and even more distinct
Int terrier; //invalid -- has to be int,not Int
int my_stars3 //valid
int 4ever; //invalid because starts with a digit
1.2 整型
整型就是没有小数部分的数字,如2、98、-5286和0。在C++中,有的类型(符号类型)可表示正值和负值,而有的类型(无符号类型)不能表示负值。
术语宽度(width)用于描述存储整数时使用的内存量。使用的内存越多,则越宽。C++的基本整型(按宽度递增的顺序排列)分别是char、short、int、long和C++11新增的long long,其中每种都有符号版本和无符号版本,因此总共有10种类型可供选择。
1.3 整型short、int、long和long long
计算机内存由一些叫做位(bit)的单元组成。而C++的short、int、long和long long类型通过不同数目的位来存储值。但可能不同的系统对这些类型的宽度有不同的要求,所以C++提供了一种灵活的标准,它确保了最小长度(从C语言借鉴而来)。
- short至少16位
- int至少与short一样长
- long至少32位,且至少与int一样长
- long long至少64位,且至少与long一样长
也就是说,int型有可能是16位,也有可能是32位(就我们2024年用的最多的win10或win11系统来说一般都是32位),接下来我们就使用头文件climits来看看这些类型所占用的字节数和其能存储的最大值。
数据类型对应的字节和能存储的最大值
#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;//返回这个int数据类型的长度,占了4个字节,一个字节占八位,一共32位cout << "short is " << sizeof n_short << " bytes." << endl;//放的是短整型,short类型,16位,即占了2个字节。//但这个字节并不是每个人都是一样的,根据每个机器和操作系统的不同也会不一样cout << "long is " << sizeof(n_long) << " bytes." << endl;//如果是变量名的话,加不加括号都可以。但是类型就需要加括号,如果记不住就都加括号cout << "long is " << sizeof(n_llong) << " 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;cout << "-----------" << endl;int a{5};//在C++里面,赋值可以不止使用=来赋值,还可以用()或{}来进行赋值。单值变量还是使用常规C的=方法来进行赋值cout << a;return 0;
}
1.4 无符号类型
前面介绍的整型都有一种不能存储符数值的无符号变体,当然,仅当数值不会为负数的时候才应该使用无符号类型,如人口、粒数等。要创建无符号版本的基本整型,只需要使用关键字unsigned来修改声明即可。
接下来的程序清单将演示如何使用无符号整型,并说明程序试图超越整型的限制时产生的后果。
越界问题
#include <iostream>
#include <climits>
#define ZERO 0;//预处理,预编译
int main() {using namespace std;short sam = SHRT_MAX;//有符号短整型的最大值,是在climits头文件里面的一个符号常量SHRT_MAXunsigned short sue = sam;cout << "sam = " << sam << endl;cout << "sue = " << sue << endl;sam = sam + 1;sue = sue + 1;cout << "sam = " << sam << endl;//因为这个是有符号短整型。所以这里会越界,上溢。就会从最大值变成了最小值cout << "sue = " << sue << endl;//这个是无符号短整型,最大值是65535,所以即使+1也并不会越界sam = ZERO;sue = ZERO;sam = sam - 1;sue = sue - 1;cout << "sam = " << sam << endl;//因为这里把ZERO=0赋值给了有符号短整型sam,所以这里会变成-1cout << "sue = " << sue << endl;//而因为这里是无符号短整型sue,会越界,下溢,所以变成了65535return 0;
}
该程序将一个short变量(sam)和一个unsigned short变量(sue)分别设置了最大的short值,在我们的系统上,是32767。然后,将这些变量的值都加了1.这对sue来说没问题,因为新值仍然比无符号整数的最大值小的多;但sam的值从32767变成了-32768!同样,对于sam,将其设置为0减去1也不会有问题;但对于无符号变量sue,将其设置成0并减去1后,它变成了65535。
从这里我们可以看出,C++确保了无符号类型的这种行为;但C++并不保证符号整型超越限制(上溢和下溢)时不出错,而这正是当前实现中最为常见的行为。
1.5 整型字面值
整型字面值(常量)是显式地书写的常量,C++能以三种不同的计数方式来书写整数:分别是十进制,八进制,十六进制。如果第一位数字是1至9,则为十进制。如果第一位数字是0,第二位数字是1至7,则为八进制。如果前两位为0x或者0X,则为十六进制。
进制转换
#include <iostream>
int main() {using namespace std;int chest = 42;//十进制数int waist = 0x42;//十六进制数int inseam = 042;//八进制数cout << "---part 1---\n";cout << "chest = " << chest << endl;cout << "waist = " << waist << endl;cout << "inseam = " << inseam << endl;//不管你赋值的是几进制,这里都会输出为十进制数cout << "---part 2---\n";cout << "chest = " << chest << endl;cout << hex;//使用hex转换成十六进制数来显示cout << "waist = " << waist << endl;cout << oct;//使用oct转换成八进制数来显示cout << "inseam = " << inseam << endl;cout << "---part 3---\n";cout << "chest = " << chest << endl;//这里变成了52十因为受到part2的oct影响,从十进制的42变成了八进制的52.cout << hex;//使用hex转换成十六进制数来显示cout << "waist = " << chest << endl;cout << oct;//使用oct转换成八进制数来显示cout << "inseam = " << chest << endl;return 0;
}
以上代码中,我们使用了头文件iostream提供的控制符endl,用于指示cout重启一行。同样,它还提供了控制符dec、hex和oct,分别用于指示cout以十进制、十六进制和八进制格式显示整数。
1.6 char类型:字符和小整数
下面介绍的是最后一种整型:char类型。顾名思义,char类型是专门为了存储字符(如字母和数字)而设计的。现在存储数字对于计算机来说不算什么,但存储字母则是另外一回事。编程语言通过使用字母的数值编码解决了这个问题。因此,char类型是另一种整型。它足够长,能够表示目标计算机系统中的所有基本符号—所有的字母、数字、标点符号等。实际上,很多系统支持的字符都不超过128个,因此用一个字节就可以表示所有的符号。因此,虽然char最常被用来处理字符,但也可以将它用作比short更小的整型。
而最常用的符号集是ASCII字符集,在ASCII码中,每个字符都对应着不同的数字。
输入字符,输出字符对应的ASCII码
#include <iostream>
int main() {using namespace std;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(ch): ";cout.put(ch);//.为句点,意思是把iostream库里面某个成员调用并使用cout.put('!');cout << endl << "Done" << endl;cout << 'M';return 0;
}
在以上代码中我们可以得知,每个字符对应的ASCII码。
除此之外,我们还有一些字符是不能通过键盘输入到程序中的,例如,按回车换行并不能使字符串包含一个换行符;而是解释为在源代码中开始新的一行。其他一些字符也无法从键盘键入,例如振铃字符和退格字符。
振铃字符和退格字符
#include <iostream>
int main() {using namespace std;//char alarm = '\a';//\a为振铃字符,它可以使得终端扬声器振铃,这里把振铃赋值给alarm字符,然后输出//cout << alarm << "Don't do that again!\a\n";//cout << "Ben \"Buggsie\" Hacker\nwas here!\n";cout << "\aOperation \"Hyperhype\" is now activated:\n";cout << "Enter your ageent code:______\b\b\b\b\b\b";//\b退格字符long code;cin >> code;cout << "\aYou entered " << code << "...\n";cout << "\aCode verified! Proceed with plan Z3 ";return 0;
}
1.7 bool类型
在C++中,非零值可以解释为true,将零解释为false。然而,现在可以使用bool类型来表示真和假了,它们分别用了预定义的字面值true和false表示。也就是说,可以这样编写句子
bool is_ready = true;
字面值true和flase都可以通过提升转换成int类型,true被转换成1,而false被转换成0
int ans = true; //ans assigned 1
int promise = false; // promise assigned 0
另外,任何数字值和指针值都可以被隐式转换(即不用显式强制转换)为bool值。任何非零值都被转换为true,而零被转换为false:
bool start = -100; //start assigned true
bool stop = 0; //stop assigned false
后面的if语句将会经常使用数据类型bool。
二、const限定符
除了#define语句(符号常量,预处理器方法)外,const关键字在C++中是一种更好的处理符号常量的方法,这种方法就是使用const关键字来修改变量声明和初始化。
在C++中,可以使用const来声明数组长度,这在第四章会提到,还有就是作用域规则中与C语言的不同,这在第九章会提到。
三、浮点数
3.1 书写浮点数
C++有两种书写浮点数的方式。
第一种是使用常用的标准小数点表示法:
12.34 //floating-point
939001.31 //floating-point
8.0 //still floating-point
第二种表示浮点值的方法叫作E表示法,其外观是这样的:3.45E6,这指的是3.45与1000000相乘的结果;E6指的是10的6次方,即1后面6个0。因此,3.45E6表示的是3450000,6被称为指数,3.45被称为尾数,下面是一些例子:
2.52e+8 //can use E or e, + is optional
7E5 // same as 7.0E+05
-18.32e13 //can have + or - sign in front
E表示法最适合于非常大和非常小的数,即可以使用E也可以使用e。
3.2 浮点类型
和ANSI C一样,C++也有3种浮点类型:float、double和long double。这些类型是按它们可以表示的有效位数和允许的指数最小范围来描述的。C++对于有效位数的要求是:float至少32位,double至少48位,且不少于float,long double至少和double一样多。这三种类型的有效位数可以一样多。然而,通常flaot为32位,double为64位,long double为80、96或128位。
另外这3种类型的指数范围至少是-37到37。
以下程序将演示float和double类型及它们表示数字时在精度方面的差异(即有效位数)。该程序预览了将在第17章介绍的ostream方法setf()。这种调用迫使输出使用定点表示法,以便更好的了解精度,它防止程序把较大的值切换成E表示法。并使程序显示到小数点后6位。
定点模式
#include <iostream>
int main() {using namespace std;cout.setf(ios_base::fixed, ios_base::floatfield);//让小数的模式以定点模式输出,小数点后显示6位数字。一共三个模式,默认,定点,科学计数模式 float tub = 10.0 / 3.0;const float Million = 1.0e6;//定义常量的时候尽量把变量名首字母变成大写cout << "tub = " << tub;//这里只会打印6位小数,因为上面使用了定点模式cout << ", a million tubs = " << Million * tub << " ";cout << 10 * Million * tub << endl;double mint = 10.0 / 3.0;cout << "mint = " << mint << " ";cout << ", a million mints = " << Million * mint << endl;return 0;
}
这里要注意的是,为何float精度比double低,tub和mint都是精确的,但当程序将每个数乘以一百万后,tub在第7个3之后就与正确的值有了误差。tub在7位有效位上还是精确的(该系统确保float至少有6位有效位,但这是最糟糕的情况)。然而,double类型的变量显示了13个3,因此它有13位是精确的。由于系统确保了15位有效位。
3.3 浮点数的优缺点
与整数相比,浮点数有两大优点。首先,它们可以表示整数之间的值。其次,由于有缩放因子,它们可以表示的范围大得多。最后,浮点运算的速度通常比整数运算慢,且精度降低。
以下程序充分的说明了这点:
float浮点常量
#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;
}
在该程序中,我们给b+1,然后减去原来的数字。结果应该为1才对啊,但系统运行后该程序结果却是0到底是为什么呢。
问题就在于,2.34E+22是一个23位的数字。加上1,就是在末位+1。但float类型只能表示数字中的前6位或前7位,因此修改第23位对这个值不会有任何影响。
四、C++算术运算符
4.1 五种运算符
在C++中,每种运算符都使用两个值(操作数)来计算结果。运算符及其操作数构成了表达式。例如,下面语句中:
int wheels = 4 + 2;
4和2都是操作数,+是加法运算符,4+2则是表达式,其值为6。
下面是5种基本的C++算术运算符。
- +运算符对操作数执行加减运算。例如,4+20等于24。
- -运算符从第一个数中减去第二个数。例如,12-3等于9。
- 运算符将操作数相乘。例如,284等于112。
- /运算符用第一个数除以第二个数。例如,1000/5等于200。如果两个操作数都是整数,则结果为商的整数部分。例如,17/3等于5,小数部分被丢弃。
- %运算符求模。也就是说,它生成第一个数除以第二个数后的余数。例如,19%6为1,因为19是6的3倍余1。两个操作数必须是整型,该运算符用于浮点数将会导致编译错误。如果其中一个是负数,则结果的符号满足如下规则:(a/b)*b+a%b = a。
当然,变量和常量都可以用作操作数,以下的程序说明了这一点:
浮点数加减乘除
#include <iostream>
int main() {using namespace std;float hats, heads;//浮点数是极其不精确的,能不用就不用cout.setf(ios_base::fixed, ios_base::floatfield);//设置使用定点模式cout << "Enter a number: ";cin >> hats;cout << "Enter a 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;}
对于此程序我们如果第一个数字输入50.25,第二个数字输入11.17。正常情况下11.17+加上50.25应该等于61.42,但是输出中却是61.419998。这不是运算问题,而是由于float类型表示有效位数的能力有限。记住,对于float,C++只保证6位有效位。如果需要更高的精度,请使用double或long double。
4.2 除法分支
除法运算符(/)的行为取决于操作数的类型。如果两个操作数都是整数,则C++将执行整数除法。如果其中有一个(或两个)操作数是浮点值,则小数部分将保留,结果为浮点数。以下程序将演示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 << "floading-point division: 9.0/5.0 =" << 9.0 / 5.0 << endl;//默认是一个双精度的浮点常量,double类型cout << "Mixed division: 9.0/5 =" << 9.0 / 5 << endl;//混合,只要里面有一个浮点数,就会变成双精度浮点常量cout << "double constants: 1e7/9.0 =" << 1e7 / 9.0 << endl;//这里还是默认双精度double类型cout << "float constants: 1e7f/9.0f =" << 1e7f / 9.0f << endl;//数字后面加f就是把数字变成float类型return 0;
}
程序备注写的很明白了,这里就懒得解释了。
4.3 求模运算符
比起求模运算符,更多人会熟悉加减乘除。这里会详细点介绍这个运算符。求模运算符返回整数除法的余数。它与整数除法相结合,尤其适用于解决要求将一个量分成不同的整数单元的问题,例如将英寸转换成英尺+英寸的形式。
以下程序将使用整数来计算合多少英石,再用求模运算符来计算余下多少磅。
除法和取余结合
#include <iostream>
int main() {using namespace std;const int Lbs_per_stn = 14;int lbs;cout << "Enter your weight in pounds: ";cin >> lbs;int stone = lbs / Lbs_per_stn;int pounds = lbs % Lbs_per_stn;cout << lbs << " pounds are " << stone << " stone, " << pounds << " pound(s).\n";return 0;
}
4.4 类型转换
C++丰富的类型允许根据需求选择不同的类型,这也使计算机的操作更复杂。为了处理这种潜在的混乱,C++自动执行很多类型转换:
- 将一种算术类型的值赋给另一种算术类型的变量时,C++将对值进行转换;
- 表达式中包含不同的类型时,C++将对值进行转换;
- 将参数传递给函数时,C++将对值进行转换;
如果不知道进行这些自动转换时将发生的情况,将无法理解一些程序的结果。
因此下面详细的介绍这些规则。
1、初始化和赋值进行的转换
C++允许将一种类型的值赋给另一种类型的变量。这样做时,值将被转换为接受变量的类型。
将一个值赋给值取值更大的类型通常不会导致什么问题。例如,将short值赋给long变量并不会改变这个值,只是占用更多的字节而已。然而,将一个很大的long值赋给float变量将减低精度。因为float只有6位有效数字,因此这个值将被四舍五入为2.11122E9。因此有些转换是安全的,有些则会带来麻烦。
下图为潜在数值转换问题:
将0赋给bool变量时,将被转换成false;而非零值将被转换成true。
将浮点型赋给整数将导致两个问题。首先,将浮点值转换为整型将会将数字截短(除掉小数部分)。其次float值对于int变量来说太大了。这种情况下说也不知道会发生什么。
以下程序说明了这点:
潜在的类型转换带来的风险
#include <iostream>
int main() {using namespace std;cout.setf(ios_base::fixed, ios_base::floatfield);float tree = 3;cout << "true = " << tree << endl;int guess(3.9832);//C++的赋值方式,但尽量不要用这种cout << "guess = " << guess << endl;int debt = 7.2E12;//7.2的12次方cout << " debt = " << debt << endl;return 0;
}
在这个程序中,将tree赋给了浮点值3.0。将3.9832赋给了int型导致变量guess这个值被截取为3。将浮点型转换为整型时,C++采取截取(丢弃小数部分)而不是四舍五入。最后,int变量debt无法存储这么大值,导致C++没有对结果进行定义的情况发生。不同的系统可能会有不同的结果。
2、以{}方式初始化时进行的转换
C++11将使用大括号的初始化称为列表初始化,因为这种初始化常用于给复杂的数据类型提供值列表。==列表初始化不允许缩窄,即变量的类型可能无法表示赋给它的值。==例如,不允许将浮点型转换成整型。
3、表达式中的转换
当同一个表达式中有两种不同的算术类型时,会出现什么情况呢?在这种情况下C++将会自动转换:首先,一些类型在出现时便会自动转换;其次,有些类型在与其他类型同时出现在表达式中时将被转换。
4、传递参数时的转换
在第七章的时候会更详细的说明,但这里我们可以先说一个结论,传递参数时的类型转换通常由C++函数原型控制。
5、强制类型转换
C++还允许通过强制类型转换机制显式地进行类型转换。强制类型转换的格式有两种。例如,为将存储在变量thorn中的int值转换成long类型,可以使用下述表达式中的一种:
(long)thorn //returns a type long conversion of thorn
long(thorn)
强制类型转换不会修改thorn变量本身,而是创建一个新的、指定类型的值,可以在表达式中使用这个值。
强制转换的通用格式如下:
(typeName)value
typeName(value)
第一种格式来自C语言,第二种则完全来自C++。
以下程序将演示如何利用强制类型转换来显示char值和ASCII码:
隐式类型转换和显式类型转换
#include <iostream>
int main() {using namespace std;int auks, bats, coots;auks = 19.99 + 11.99;//把浮点型赋给一个整型,这里属于隐式的类型转换cout << "auks = " << auks << endl;//得出31,不存在四舍五入,小数点直接丢弃bats = (int)19.99 + (int)11.99;//这是显式的类型转换,这是C的写法//两个值被截短为19和11,结果为30cout << "bats = " << bats << endl;bats = int(19.99) + int(11.99);//这是显式的类型转换,这是C++的写法cout << "bats = " << bats << endl;char ch = 'E';cout << ch << endl;cout << int(ch) << endl;cout << static_cast<int>(ch) << endl;//强制转换为整型,和上面是一样的//C++中的auto定义变量类型auto n = 100; //n is intauto x = 1.5; //x is doubleauto y = 1.3e12L; //y is long doublereturn 0;
}
4.5 C++11中的auto声明
C++11新增了一个工具,让编译器能够根据初始值的类型推断变量的类型。auto是C语言关键字,但很少使用。在初始化声明中,如果使用了关键字auto,而不指定变量的类型,编译器将把变量的类型设置成与初始值相同。
其实这个关键字一般很少使用,所以后面遇到的时候我们在详细说明。
总结
1、C++的基本类型分为两组:一组由存储为整数的值组成,另一组由存储为浮点格式的值组成。整型之间通过存储值时使用的内存量及有无符号来进行区分。
2、字符通过其数值编码来表示。I/O系统决定了编码是被解释为字符还是数字。
3、通过提供长度不同、有符号和无符号的类型。C++使程序员能根据特定的数据要求选择适合的类型。
4、对于类型转换,需谨慎且小心。