总结:
本章我们首先介绍了C++中各种基本内置类型的长度、精度以及范围等区别,接着引入了变量的概念,讲解了变量的意义及其与字面值常量的比较,又引申到了const修饰符所定义的常量。我们还介绍了连接数据以完成各种计算的操作符的用法和它们的优先级,并且讲解了操作数在不同类型之下产生的类型转换。最后我们介绍了一些C++中常用的基本语法,比如注释和基本的预处理器命令。
除了下文要介绍的这些基本内置类型,C++还定义了void类型。这并不是一个具有具体数值的类型,它一般只用在函数定义中,表示函数没有返回值,或者表示通用的指针类型。
short、 int 和 long int
有符号数可以表示正数和负数,数字的第一位表示符号,1为负,0为正。无符号数只能表示非负数,由于不需要第一位来区分正负,无符号数可以表示的正数范围会比有符号数多一倍,16位的有符号短整型short可以表示的最大正整数为32767,而16位的无符号短整型unsigned short可以表示的最大正整数为65535(二进制多一位可以表示的数就多一倍)。
其中代码还遇到了一些问题
#include
using namespace std;
// 整型的存储空间
int main() {
cout << "short的存储空间为" << sizeof(short) << "字节。" << endl;
cout << "unsigned short的存储空间为" << sizeof(unsigned short) << "字节。" << endl;
cout << "int的存储空间为" << sizeof(int) << "字节。" << endl;
cout << "unsigned int的存储空间为" << sizeof(unsigned int) << "字节。" << endl;
cout << "long int的存储空间为" << sizeof(long int) << "字节。" << endl;
cout << "unsigned long int的存储空间为" << sizeof(unsigned long int) << "字节。" << endl;
return 0;
#include
using namespace std;
// 整型的范围
int main() {
short shortMin = -32768;
short shortMax = 32767;
unsigned short ushortMin = 0;
unsigned short ushortMax = 65535;
int intMin = -2147483647 - 1; // 由于编译器的问题,不能直接用-2147483648
int intMax = 2147483647;
unsigned int uintMin = 0;
unsigned int uintMax = 4294967295;
long int longMin = -2147483647 - 1;
long int longMax = 2147483647;
unsigned long int ulongMin = 0;
unsigned long int ulongMax = 4294967295;
cout << "short的范围为" << shortMin << "到" << shortMax << endl;
cout << "unsigned short的范围为" << ushortMin << "到" << ushortMax << endl;
cout << "int的范围为" << intMin << "到" << intMax << endl;
cout << "unsigned int的范围为" << uintMin << "到" << uintMax << endl;
cout << "long int的范围为" << longMin << "到" << longMax << endl;
cout << "unsigned long int的范围为" << ulongMin << "到" << ulongMax << endl;
return 0;
}
#include
using namespace std;
// 整型溢出
int main() {
short shortMin = -32769;
short shortMax = 32768;
unsigned short ushortMin = -1;
unsigned short ushortMax = 65536;
int intMin = -2147483647 - 2;
int intMax = 2147483648;
unsigned int uintMin = -1;
unsigned int uintMax = 4294967296;
long int longMin = -2147483647 - 2;
long int longMax = 2147483648;
unsigned long int ulongMin = -1;
unsigned long int ulongMax = 4294967296;
cout << "short下溢值:" << shortMin << ",上溢值:" << shortMax << endl;
cout << "unsigned short下溢值:" << ushortMin << ",上溢值:" << ushortMax << endl;
cout << "int下溢值:" << intMin << ",上溢值:" << intMax << endl;
cout << "unsigned int下溢值:" << uintMin << ",上溢值:" << uintMax << endl;
cout << "long int下溢值:" << longMin << ",上溢值:" << longMax << endl;
cout << "unsigned long int下溢值:" << ulongMin << ",上溢值:" << ulongMax << endl;
return 0;
}
其中低于最小值的叫作下溢(Underflow ),高于最大值的叫作上(Overflow )。向下溢出的值会回到最大值,而向上溢出的值会回到最小值。
字符型有char和wchar_t两种类型。char的大小至少是1个字节,范围一般是-128 ~ 127,足够用来表示键盘上可以看到的字符。wchar_t是宽字符类型,至少是2个字节,由于中文等其他语言有许多字符,因此需要一个比char范围更大的类型来表示。
#include
using namespace std;
// 字符型的存储空间和范围
int main() {
cout << "char的存储空间为" << sizeof(char) << "字节。" << endl;
cout << "wchar_t的存储空间为" << sizeof(wchar_t) << "字节。" << endl;
char charMin = -128;
char charMax = 127;
wchar_t wcharMin = 0;
wchar_t wcharMax = 65535;
// 由于打印字符会直接打印其所代表的字母或符号,为了观察数值范围需要类型转换为int
cout << "char的范围为" << (int)charMin << "到" << (int)charMax << endl;
cout << "wchar_t的范围为" << wcharMin << "到" << wcharMax << endl;
return 0;
}
#include
using namespace std;
// 打印所有ASCII字符
int main() {
// 循环打印所有128个ASCII字符
for (int i = 0; i < 128; i++) {
char ch = i;
cout << i << " :" << ch << " ";
// 每打印5个字符换一行
if (i % 5 == 0) cout << endl;
}
char ch = 12;
cout << "12:" << ch;
return 0;
}
从运行结果中我们可以看到,14以前的字符有些无法显示,而有些则打乱了格式,甚至把11-13所在的那行覆盖了。这是因为0-31和127都是控制字符,可以对页面中的字符起到控制作用,其中,有一些字符也能以笑脸或者星形的形式显示。
浮点型是用来表示小数的数据类型,共有3种: float、double和long double
其中float需要保证有6位有效数字,double和long double都需要保证有10位有效数字。
布尔型bool的值只有true(真)和false(假)两种。我们也可以将整数赋值给bool,编译器会自动进行类型转换,0会转换成false,而其他数字都会转换成true。
#include
using namespace std;
// 算术操作符
int main() {
int a = 5;
int b = 2;
cout << "+ a: " << (+a) << endl;
cout << "- a: " << (-a) << endl;
cout << "a + b: " << (a + b) << endl;
cout << "a - b: " << (a - b) << endl;
cout << "a * b: " << (a * b) << endl;
cout << "a / b: " << (a / b) << endl;
cout << "a % b: " << (a % b) << endl;
return 0;
}
“+”作为取正符号时没有任何作用,“-”则会返回数字的相反数。
#include
using namespace std;
// 关系操作符
int main() {
int a = 5;
int b = 2;
int one = 1;
bool t = true;
cout << "a > b: " << (a > b) << endl;
cout << "a < b: " << (a < b) << endl;
cout << "a >= b: " << (a >= b) << endl;
cout << "a <= b: " << (a <= b) << endl;
cout << "t == one: " << (t == one) << endl;
cout << "t != one: " << (t != one) << endl;
return 0;
}
#include
using namespace std;
// 逻辑操作符
int main() {
cout << "true && true: " << (true && true) << endl;
cout << "true && false: " << (true && false) << endl;
cout << "false && false: " << (false && false) << endl;
cout << "true || true: " << (true || true) << endl;
cout << "true || false: " << (true || false) << endl;
cout << "false || false: " << (false || false) << endl;
cout << "!true: " << !true << endl;
cout << "!false: " << !false << endl;
return 0;
}
#include
using namespace std;
// 位操作符
int main() {
int a = 1; // 0b01
int b = 3; // 0b11
cout << "a & b: " << (a & b) << endl;
cout << "a | b: " << (a | b) << endl;
cout << "a ^ b: " << (a ^ b) << endl;
cout << "~a: " << ~a << endl;
return 0;
}
#include
using namespace std;
// 自增自减操作符
int main() {
int a = 2;
cout << "a = " << a << endl;
cout << "++a: " << ++a << endl;
cout << "a++: " << a++ << endl;
cout << "--a: " << --a << endl;
cout << "a--: " << a-- << endl;
return 0;
}
为什么a递增了两次,又递减了两次,结果却都是3呢?
这是因为在a++之后先返回了原来a的值3,再加1成为4;
之后自减由于是前缀的–a,4减1变成3后返回给了cout;
最后的a–与a++类似,返回3之后a实际的最终值是2。
我们需要区分以下几种概念:“int a=b; ”是一个带初始化的变量定义语句;
“a=b;”是一个赋值语句;
“c=a=b”中的“a=b”是一个赋值表达式,
而“c=a=b”也是一个赋值表达式,其中“a=b”的返回值将会继续赋值给c。
#include
using namespace std;
// 赋值操作符
int main() {
int a = 3;
int b = 2;
cout << "a += b: " << (a += b) << endl;
cout << "a -= b: " << (a -= b) << endl;
cout << "a *= b: " << (a *= b) << endl;
cout << "a /= b: " << (a /= b) << endl;
cout << "a %= b: " << (a %= b) << endl;
cout << "a <<= b: " << (a <<= b) << endl;
cout << "a >>= b: " << (a >>= b) << endl;
cout << "a &= b: " << (a &= b) << endl;
cout << "a ^= b: " << (a ^= b) << endl;
cout << "a |= b: " << (a |= b) << endl;
return 0;
}
#include
using namespace std;
// 条件操作符
int main() {
int a = 3;
int b = 2;
int c = 5;
int max = a > b ? a : b;
int min = a < c ? a : c;
cout << "max: " << max << endl;
cout << "min: " << min << endl;
return 0;
}
我们可以看到,条件操作符由“?”和“:”两部分组成。“?”前面是一个结果为布尔值的条件表达式,而“:”的前后则是这个条件为true和false时条件操作符会返回的表达式。
复合嵌套的条件表达式:
#include
using namespace std;
// 嵌套条件操作符
int main() {
int a = 3;
int b = 2;
int c = 5;
int max = a > b ? (a > c ? a : c ) : (b > c ? b : c);
int min = a < b ? (a < c ? a : c ) : (b < c ? b : c);
cout << "max: " << max << endl;
cout << "min: " << min << endl;
return 0;
}
实现三个操作数最大值和最小值
#include
using namespace std;
// 逗号操作符
int main() {
int a = 3;
int b = 2;
int c = 5;
int result = (c = b, b--, a++);
cout << "c: " << c << endl;
cout << "b: " << b << endl;
cout << "a: " << a << endl;
cout << "result: " << result << endl;
return 0;
}
我们可以看到,逗号操作符的操作数不一定得是变量或者赋值表达式,也可以是其他表达式。而逗号表达式的结果是逗号右边的表达式a++自增前的值(后缀自增在表达式结果返回后计算),也就是3(嵌套的就是最右边的表达式结果)。
#include
using namespace std;
// 操作符优先级
int main() {
int a = 3;
int b = 2;
// ((a + b) < (a - (b * 0))) || (a > b)
if (a + b < a - b * 0 || a > b) {
cout << "条件成立!" << endl;
}
return 0;
}
这一套规则的原则是尽量保持精度,因此精度低的类型( float)会往精度高的类型( double)转换,而存储空间小的类型( char )也会往空间大的类型( int)转换。
#include
using namespace std;
// 基本隐式转换
int main() {
cout << "浮点数1.2与整数2相加:" << 1.2 + 2 << endl;
cout << "字符a与整数1000相加:" << 'a' + 1000 << endl;
return 0;
}
#include
using namespace std;
// 赋值语句中的隐式转换
int main() {
double dblNum = 2.9;
int num = dblNum + 3;
cout << "num的最终结果是:" << num << endl;
return 0;
}
由于赋值语句的左值有着确定的类型,右边的表达式不管在计算中发生了什么样的类型转换,最后都要转换成左值的类型。动手写3.4.2中,尽管加法中int转成了double,但是最后计算好的double还是要转回int。
条件操作符中的隐式转换
#include
using namespace std;
// 条件操作符中的隐式转换
int main() {
int num = 4 ? 3 : 2;
cout << "num的最终结果是:" << num << endl;
return 0;
}
展示了条件操作符或条件语句中的任何表达式都要转换为布尔型,这是因为在这两个地方我们需要布尔值来做一个“两个分支”的选择。在这里只有0会转换为false,其他值将一律转换成true,所以这里的4转换成了true,而条件操作符将3赋值给了num。
总结C++中基本内置类型的转换规则:
bool→char→unsigned char→short(wchar_t)→unsigned short(wchar_t)→int→unsigned int→longint >unsigned long int —>float→ >double→>long double
强制转换,转换风格是C语言的方式
#include
using namespace std;
// C风格显式转换
int main() {
cout << "浮点数1.2与整数2相加:" << (int)1.2 + 2 << endl;
cout << "字符a与整数1000相加:" << 'a' + (char)1000 << endl;
return 0;
}
在示例中,2本来应该隐式转换成2.0与1.2相加的,但现在由于显式转换的出现,1.2被强制转换成了1,而后面的1000被强制转换为char之后,由于溢出而变成了一个负数的char( 1000的二进制数是1111101000,由于char只有8位,会被截取成11101000,转换成数值就是-24了),反而将代表a的97减小成了一个更小的数。此处涉及补码的相关内容。
C++风格:
#include
using namespace std;
// C++类型转换操作符
int main() {
float floatNum = 1.2;
int intNum = static_cast<int>(floatNum);
cout << "浮点数1.2转换为整数的结果为" << intNum << endl;
return 0;
}
除了static_cast这个与C风格类型转换基本类似的操作符以外,C++还有const_cast 、dynamic_cast和reinterpret_cast这3种特殊的类型转换操作符。由于它们都涉及指针,因此需要在后面的章节中才能讲解。
相比较于C风格的类型转换,使用C++类型转换操作符有以下好处:
1.容易辨识。尖括号(<>)使得C++类型转换操作符非常容易辨识,而在编程中我们也可以搜索“_cast”来寻找类型转换。C风格的类型转换语法显然就更容易被忽略,从而导致错误。
2.语义明确。C++的几种类型转换操作符语义都是互不相同的,而C风格的类型转换都是用同一语法代替,并且一般类型reinterpret_cast并不能用C风格的类型转换实现(只有涉及指针的时候才可以)。
单行注释://
成对注释:/* */
注 成对注释不能嵌套
除了#include之外,C++中还有许多带有井号(#)的命令,它们统称为预处理器命令( Preprocessor Directives )。这些命令将在编译的预处理阶段执行,也就是在正式编译之前,编译器就已经将这些命令过了一遍,并生成了新的代码。预处理器命令一般都会起到编译时代码替换的作用,换种说法就是可以让程序员偷懒。就拿#include来说,如果不用这个指令,我们就要把头文件中的各种定义在每个用到它们的文件中再写一遍,这样程序员是不是就不能偷懒了?
因为#include也是一种预处理器命令,所以我们就把头文件和链接的知识放到一起讲解,不过C++的链接是一个很大的话题,在这里只做简单的介绍。本节的内容是掌握多文件编程开发的必备知识,因此相较于本章其他小节可能显得有些难懂,读者也可以等到需要编写多个文件的程序时再来参考本节内容。
在前面的示例中我们编译的都是单文件的程序,而对于大型程序来说,我们必须有效地将代码分别放在不同的文件中。编译器分别编译每个单独的文件,生成相应的.obj目标文件;链接器再将这些.obj目标文件和必要的.lib库文件链接,从而生成最后的可执行代码。
变量和函数都可以将声明( Declaration)与定义(Definition)分离。定义是一定要初始化的,而声明却只是让编译器知道现在有这样一个变量,接下来可以用了。一个文件A中定义的变量a也可能被另一个文件B使用,而这个文件B也需要在单独编译的时候知道这个变量a的信息,所以也需要声明。因此我们知道声明是可以存在多个的,但是带有初始值或函数体的定义只能有一个,不然编译器无法确定要使用的初始值或函数体到底是哪一个。接下来让我们看两个相关的示例:
// 声明和定义
//int num; // 变量不能在同一文件中重复声明
int num = 2; // 唯一的定义
#include
using namespace std;
// 声明和定义
// 不同文件中的声明, 不加extern相当于是没有初始化的定义
extern int num;
int main() {
cout << "num的值为:" << num << endl;
return 0;
}
动手写3.6.1展示了不同文件中多次声明的情况。其中,我们可以看到num在3.6.1_1.cpp中被定义,而在3.6.1_2.cpp中被使用。在使用前我们先用声明语句让编译器知道要去别的文件中寻找定义,因此我们需要使用exterm关键字来表明变量定义在别的文件中,或者也可以使用#include包含的方式。
需要注意的是,变量不能在同一文件中声明两次,因为没有初始化的变量声明会被解读为默认初值的定义,而函数的定义和声明在形态上就有明显的区别,在编译时不会产生歧义。
// 重定义
int num = 2;
#include
using namespace std;
// 重定义
int num = 2;
int main() {
cout << "num的值为:" << num << endl;
return 0;
}
上述的示例只适用于使用个别在其他文件中定义的变量的情况,在实际的大型程序中,我们可能有许多这样的变量,并且同一批变量和函数会被许多文件使用。对于这种情况,显然一个一个地声明就效率太低了,而且容易出错。这个时候我们就可以把变量和函数的声明放到头文件中,并在需要的文件中使用#include包含头文件。
不同于一般源代码文件的后缀“.cpp”,头文件(Header File)一般用“.h”作为后缀。头文件主要存放extern变量声明和函数声明两种声明,也可以放类定义、const常量定义和inline函数定义,这是因为这些定义重复出现也是没问题的,只要保证定义都相同就可以。按照惯例,我们一般都会把变量声明和函数声明对应的定义,以及类定义中成员函数的定义放在与头文件同名的cpp文件中,并包含头文件,这样一来,使用这些定义的用户文件只要包含头文件就不会重复地包含cpp,从而避免重定义错误。图3.6.2清晰地表现了这一思想:
我们可以看到,“1.cpp”和“2.cpp”想使用一些和数学相关的变量和函数,就只需要包含“math.h”,而如果函数的实现和变量的值有修改,在这两个文件中也不需要修改。接下来我们来看一个实际编写并使用头文件的示例:
头文件
#include
// 非系统库包含不用尖括号,而用双引号,也要加.h后缀
#include "363.h"
using namespace std;
// 头文件的使用
// Author: 零壹快学
int main() {
int area = PI * r * r;
cout << "area的值为:" << area << endl;
return 0;
}
之前的章节中讲过预处理器命令大多用编译时的代码替换,而应用得最多的情况就是当我们需要重复多次写同一段代码的时候可以用一个简短的表达来代替,让编译器帮助我们写较长的代码,这也就是C++中的宏(Macro )。宏的一个常见的例子就是常量字面量的声明。
#include
using namespace std;
// 宏的使用
#define PI 3.14
int main() {
float r = 2.0f;
float area = PI * r * r;
cout << "area的值为:" << area << endl;
return 0;
}
#include
using namespace std;
// 带参数的宏
#define max(a, b) ( a > b ? a : b )
#define min(a, b) ( a < b ? a : b )
int main() {
int result = min(max(1, 3), 2);
cout << "result的值为:" << result << endl;
return 0;
}
用宏实现了取最大值和最小值的操作并可以反复使用
当宏比较复杂时,可以使用分行符来解决
#include
using namespace std;
// 分行符
#define max(a, b, c) ( (a > b) ? \
((a > c) ? a : c ) : \
((b > c) ? b : c ) )
int main() {
cout << "最大值为:" << max(2, 3, 1) << endl;
return 0;
}
条件编译在跨平台程序中的大致用法
#include
#include
using namespace std;
// 条件编译在跨平台代码中的应用
// 这些参数一般会在配置文件中定义
#define WINDOWS
#define VS2019
#if defined (WINDOWS) && defined(VS2015)
string output = "平台:Windows,编译环境:Visual Studio 2015";
#elif defined (WINDOWS) && defined(VS2019)
string output = "平台:Windows,编译环境:Visual Studio 2019";
#else // 在visual studio中不被执行的代码的字体颜色会变浅
string output = "平台:其他平台,编译环境:未知";
#endif
int main() {
cout << output << endl;
return 0;
}