目录
注:
本笔记参考:《C++ PRIMER PLUS》
面向对象编程(OOP)的本质是设计并扩展自己的数据类型。设计自己的数据类型就是让类型于数据匹配。而在此之前,我们有必要了解C++内置的类型,通过这些“基础组件”来创建自己的类型。
内置的C++类型分为两种:基础类型和复合类型。
简单变量
为了把信息存储在计算机中,程序必须记录3个基本属性:
- 信息存储的位置;
- 要存储的值;
- 存储信息的类型。
声明变量
声明中使用的类型描述了信息的类型和通过符号来表示其值的变量名。例如:
int count = 0;
count = 5;
通过上述语句,程序将找到一块能够存储整数的内存,将该内存单元标记为count,并将5复制到该内存单元内。接下来在程序中就可以使用count来访问该内存单元。
尽管程序并没有说明,这个值将被存储在内存的什么位置,但我们依旧可以通过 &运算符 来检索count的内存地址。
变量名
C++倡导使用一定含义的变量名,这里提供几种简单的C++命名规则:
- 在名称中只能使用字母、数字和下划线(_)。
- 名称的第一个字符不能是数字。
- 区分大写字符和小写字符。
- 不能将C++关键字用作名称。
- 以 两个下划线 / 下划线和大写字母 打头的名称被保留给(编译器及其使用的资源)使用。
- 以 一个下划线 开头的名称被保留给实现,用作全局标识符。
ps:虽然C++对于名称的长度没有限制,名称中所有的字符都有意义,但是有些平台有长度限制。(ANSI C标准只保证名称的前63个字符有意义)
关于上述命名规则的第6点,如果使用类似于 _time_loop 或者 _Donut 这样的名称,并不会导致编译器错误,而是会导致行为的不确定性(即无法确定结果是什么)。这样命名之所以不会出现编译器错误,是因为这样的名称并不非法,但是要留给实现使用。
如果要组合多个单词形成一个名称,可以使用下划线字符将单词分开,如:my_onions;或者从第二个单词开始,每个单词的首字母大学,如:myOnions。
整型
整数是没有小数部分的数字。但整数可以无穷大,计算机的内存却不尽然。因此,语言只能表示所有整数的一个子集。C++提供了多种整型,以便于根据具体需求进行选择。
不同的C++整型使用不同的内存量来存储整数,使用的内存量越大,可以表示的整数值的范围也越大。另外,根据正负值的区分有无,我们可以把类型分为符号类型和无符号类型。术语宽度(width)用于描述存储整数时使用的内存量,使用的内存越多,越宽。
整型short、int、long和long long
计算机内存由一些叫做位(bit)的单位组成。
- 位(bit):计算机的基本单位,有两个值:0 和 1。
- 字节(byte):通常指8位的内存单元。如果从这个意义上说,字节指的就是描述计算机内存量的度量单位。但是也可以说,字节的含义是依赖于实现的。
//创建不同的整型变量
short score; //实际上,short是short int的简称
int tem;
long position; //同样地,long是long int的简称
C++的short、int、long和long long类型通过使用不同数目的位来存储值,最多能够表示4种不同的整数宽度。C++为此通过了一种灵活的标准,它确保了最小长度,如下:
- short至少16位;
- int至少和short一样长;
- long至少32位,且至少和int一样长;
- long long至少64位,且至少与long一样长。
当前的很多系统都使用最小长度,即 short 为16位,long 为32位。而 int 有很多选择,其宽度可以是16位、24位、32位甚至是64位。
上述的4种类型(int、short、long和long long)都是符号类型,这意味着每种类型中,负值和正值的取值范围几乎相同。例如:16位的int的取值范围为-32768到+32767。
上面提到过,“字节”的含义依赖于实现,譬如:在一个系统中,两字节的int可能是16位,但在另一个系统中可能就是32位。为了知道系统中的整数的最大长度,我们需要使用C++内提供的工具:
#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类型的所占空间大小是" << sizeof(int) << "bytes。" << endl;
cout << "short类型的所占空间大小是" << sizeof n_short << "bytes。" << endl;
cout << "long类型的所占空间大小是" << sizeof n_long << "bytes。" << endl;
cout << "long long类型的所占空间大小是" << sizeof n_llong << "bytes。" << endl;
cout << 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 << endl;
cout << "int类型能存储的最小值 = " << INT_MIN << endl;
cout << "每个字节对应位的数量 = " << CHAR_BIT << endl;
return 0;
}
程序执行的结果是:
在上述的程序中:
- 使用了 sizeof运算符,这个运算符可以返回类型或者变量的长度;
- 头文件 climits 定义了表示各种限制的符号名称,包含了关于整型限制的信息。
接下来看看上述程序的主要编程特性。
1. 运算符sizeof 和头文件limits
可对类型名或变量名使用sizeof运算符。对类型名(如int)使用sizeof运算符时,应将名称放在括号中;但对变量名(如n_short)使用该运算符,括号是可选的:
cout << "int is " << sizeof(int) << "bytes.\n";
cout << "short is " << sizeof n_short << "bytes.\n";
头文件climits定义了符号常量来表示类型的限制,下面是一些该文件中定义的符号常量:
符号常量 | 表示 |
CHAR_BIT | char的位数 |
CHAR_MAX | char的最大值 |
CHAR_MIN | char的最小值 |
SCHAR_MAX | signed char的最大值 |
SCHAR_MIN | signed char的最小值 |
UCHAR_MAX | unsigned char的最大值 |
SHRT_MAX | short的最大值 |
SHRT_MIN | short的最小值 |
USHRT_MAX | unsigned short的最大值 |
INT_MAX | int的最大值 |
INT_MIN | int的最小值 |
UNIT_MAX | unsigned int的最大值 |
LONG_MAX | long的最大值 |
LONG_MIN | long的最小值 |
ULONG_MAX | unsigned long的最小值 |
LLONG_MAX | long long的最大值 |
LLONG_MIN | long long的最小值 |
ULLONG_MAX | unsigned long long的最大值 |
预处理器处理符号常量
在climits文件中存在类似于下面的语句行:
#define INT_MAX 32767
在C++的编译过程中,首先会将源代码传递给预处理器。在这里,#define 和 #include 一样,都是一个预处理编译指令。对于#define,预处理器会寻找独立的标记,跳过嵌入的单词,也就是说,预处理器不会将 PINT_MAXTM 替换成 P32767TM。
2. 初始化
一些初始化的方法:
#include<iostream>
using namespace std;
int main()
{
int n_int = INT_MAX; //结合了赋值和声明
int n_a = 5; //使用字面值常量进行初始化
int n_b = n_a; //将变量初始化为另一个变量(n_a已经被定义过)
int n_c = n_a + n_b + 4; //通过表达式初始化变量(表达式内所有值已知)
}
上述的初始化语法均来自C语言,其实,C++也提供了一种C语言没有的初始化语法:
int n_d = 100; //C语言的初始化语法
int n_e(343); //C++的初始化语法
如果不进行初始化,那么在变量被创建时,变量的值就是相应内存单元内原本保存的值。
short n_f;
n_f = 123;
上述这种将变量声明和赋值分开的做法是不推荐的。
3. C++11初始化方式
还有另外一种初始化的方法,这种方式用于数组和结构,但在C++98中,也可用于单值变量:
int a_A = {22};
而在C++11标准中,通过这种方式初始化单值变量的情形变得更多了:
//其中,(=)是可用可不用的
int b_B{12};
int c_C = {2};
//在大括号内不存在任何东西时,变量将被初始化成 零
int d_D = {};
int e_E{};
无符号类型
上述的4种整型都存在一种不存储负数值的无符号变体,其优点是可以增大变量能够存储的最大值。比如:如果short表示的范围为-32768到+32768,则无符号版本的表示范围为0到65535。
如果要创建无符号版本的基本整型,只需要使用关键字unsigned来修改声明即可:
unsigned short a;
unsigned int b;
unsigned c; //这种声明方式也是 unsigned int
usigned long d;
unsigned long long e;
unsigned 本身是 unsigned int 的缩写。
例子:
其中 \t 是制表符,可以忽略。
#include<iostream>
#include<climits>
#define ZERO 0
int main()
{
using namespace std;
short sh = SHRT_MAX;
unsigned short un_sh = sh;
cout << "short_MAX \t= " << sh
<< "\t\tunsigned_short_MAX \t= " << un_sh << endl;
sh = sh + 1;
un_sh = un_sh + 1;
cout << "short_MAX + 1 \t= " << sh
<< "\tunsigned_short_MAX + 1 \t= " << un_sh << endl;
sh = ZERO;
un_sh = ZERO;
cout << "short_ZERO \t= " << sh
<< "\t\tunsigned_short_ZERO \t= " << un_sh << endl;
sh = sh - 1;
un_sh = un_sh - 1;
cout << "short_ZERO - 1 \t= " << sh
<< "\t\tunsigned_short_ZERO - 1\t= " << un_sh << endl;
return 0;
}
程序执行的结果是:
分析:
上述例子说明了程序试图超越整型的限制时将产生的后果。
其中,short_MAX + 1 变为负数是因为:
而 unsigned_short_ZERO - 1 变为一个很大的数字则是因为:
从下图可以看出整型的行为:
选择整型类型
在C++提供的整型中,通常,int类型被设为对目标计算机而言最为“自然”的长度。
自然长度(natural size):指的是计算机处理起来效率最高的长度。
一般的,如果变量表示的整数值大于16位整数的最大可能值,推荐使用 long类型 。这是为了保证程序被移植到16位系统时能够正常运转。同理,如果存储的值超过20亿,推荐使用 long long类型 。
通常,仅当有大型整型数组时,才会考虑使用 short类型 。
如果需要使用一个字节的空间,可使用char。
整型字面值
整型字面值(常量)是显式地书写的常量,如2222或321。
类似于C,C++有三种不同的计数方式来书写整数:基数为10(十进制)、基数为8(十六进制)和 基数为16(十六进制)。
- 十进制:第一位为1~9,如:93;
- 八进制:第一位为0,第二位为1~7,如:042,相当于十进制的34;
- 十六进制:前两位为0x或者0X,如:0x42,也相当于十进制的66;0xF相当于15;0xA5相当于165。
#include<iostream>
int main()
{
using namespace std;
int a = 42;
int b = 0x42;
int c = 042;
cout << "a = " << a << " (十进制的42)\n";
cout << "c = " << c << " (八进制的42)\n";
cout << "b = " << b << " (十六进制的42)\n";
return 0;
}
程序执行结果(在默认情况下,cout将以十进制格式显示整数):
当然,不管把值书写成十进制、八进制或者十六进制的形式,这个值都会以相同的方式被存储在计算机中:被存储为二进制数。
就像头文件iostream提供了endl控制符一样,这个头文件还提供了控制符 dex (十进制)、 oct(八进制) 和 hex(十六进制) 显示整数。而默认的格式为十进制。
#include<iostream>
int main()
{
using namespace std;
int a = 42;
int b = 42;
int c = 42;
cout << "a = " << a << " (将42以十进制形式输出)" << endl;
cout << oct;
cout << "b = " << b << " (将42以八进制形式输出)" << endl;
cout << hex;
cout << "c = " << c << " (将42以十六进制形式输出)" << endl;
return 0;
}
程序执行结果是:
像 cout << hex; 这种代码不会显示在屏幕上,实际上,控制符hex 是一条信息,告诉 cout 采取何种行动。而因为标识符hex位于名称空间std内,现在程序使用了这一名称空间,因此hex不能被用作变量名。
C++如何确定常量的类型
先看代码:
cout << "Year = " << 1492 << "\n";
现在提问:程序会把1492存储为什么类型?是int、long还是其他整型?
答案是,除非有理由存储为其他类型(如:使用了特殊的后缀来表示特定的类型,或者值太大,不能存储为int),否则C++将整型常量存储在int类型。
1. 后缀
后缀就是放在数字常量后面的字母,表示类型。
举个例子(在int为16位、long为32为的系统上):
- 数字 22022 被存储为int,占16位;
- 数字 22022L 被存储为long,占32位;
- 数字 22022LU / 22022UL 被存储为unsigned long。
C++11提供了新的后缀:
后缀名 常量 ll 或者 LL long long常量 ull、Ull、uLL 或者 ULL unsigned long long常量
2. 长度
对于不带后缀的十进制整数,将使用下面几种类型中能够存储该数的最小类型来表示:int 、long 或 long long 。例如,在int为16位、long为32为的系统中:
2 0000被表示为int类型,4 0000被表示为long类型,30 0000 0000 被表示为long long类型。
对于不带后缀的十六进制或八进制整数,将使用下面几种类型中能够存储该数的最小类型来表示:int 、unsigned long 、 long long 或 unsigned long long 。例如,在将40000表示成long的系统中:
十六进制数0x9C40(40000)将被表示成 unsigned int 。(ps:这是因为十六进制经常被用来表示内存地址,而内存地址是没有符号的,因此,unsigned int比long更适合用来表示16为的地址。)
char类型:字符和小整数
char类型是专为存储字符(如字母和数字)而设计的。编程语言使用字符的数值编码来存储字母,因此,char类型是另一种整型。
大多数系统支持的字符不超过128个,因此一个字节足够表示所有符号。
一些现在被使用的字符集:ASCII字符集、EBCDIC字符集和Unicode字符集。
例子:
#include<iostream>
int main()
{
using namespace std;
char ch;
cout << "请输入一个字符: ";
cin >> ch;
cout << "被输入的字符是 " << ch << endl;
return 0;
}
程序执行的结果是:
分析:
这里输入和输出的都是M,而不是其对应的字符编码77。之所以可以这么执行程序,是因为 cin 和 cout 会自动转换输入的字符,而cin和cout的这种行为来自于变量类型的引导。
注:如果使用单引号(' ')括起字符,那么将可以显示字符字面值(即常量):
#include<iostream>
int main()
{
using namespace std;
char ch = 'M'; //char类型的变量被初始化成'M',即77。
int i = ch; //把ch的值赋给i,i的值也是77
cout << "在ASCII中 " << ch << " 的编码是 " << i << endl;
cout << "---将这个字符的编码+1---" << endl;
ch = ch + 1;
i = ch;
cout << "在ASCII中 " << ch << " 的编码是 " << i << endl;
cout << "使用 cout.put() 来展示字符ch: ";
cout.put(ch);
//展示一个字符常量
cout.put('!');
cout << endl;
return 0;
}
程序执行的结果是:
就如之前所说,值的类型将引导cout选择如何显示值(智能对象)。
而由于 ch 实际上是一个整数,所以可以对它进行整数操作,比如上述代码中的 ch + 1 ,可以将 ch 的值变为78。
另一方面,C++为输入字符提供了便捷的方式,使得我们可以直接通过键盘输入字符:
char ch; cin >> ch;
如果输入5,上述代码将会读取字符'5',并将其对应的字符编码(ASCII编码是53)存储在变量ch中。
在程序的最后,该程序使用了函数cout.put()显示变量ch和一个字符常量。
成员函数cout.put()
概念:这是本笔记第一次接触这种函数,它来自于C++ OOP的一个重要概念:成员函数。已知,类定义了如何表示和控制数据,而成员函数归类所有,描述了操纵类数据的方法。
实际上,cout.put()成员函数是提供了另一种显示字符的方法,可以替代 <<运算符(cout << )。
使用:成员函数(如上述的成员函数 put())只能通过类的特定对象(如上述的 cout对象)来使用。而通过 对象 使用 成员函数 ,必须使用 句点(即成员运算符) 将对象名和函数名称连接起来。
现在解释cout.put():通过类对象cout来使用函数put()。
char字面值
在C++中,最简单的书写字符常量的方法就是单引号括起:
- 'A'的ASCII码是65;
- 'a'的ASCII码是97;
- '5'的ASCII码是53;
- ' '的ASCII码是32;
- '!'的ASCII码是33;
如果系统使用EBCDIC,则A的编码将不会是65。
转义字符
有些字符无法直接通过键盘输入到程序中,例如:回车键只会在源代码内开启新的一行。
还有些字符,C++赋予了它们特殊的含义,例如:双引号字符用来分隔字符串字面值,所以双引号无法被放入字符串字面值中。
对于这些不能直接输入程序中的字符,C++提供了一种特殊的表示方法——转义序列。
#include<iostream>
int main()
{
using namespace std;
char alarm = '\a';
cout << alarm << "起床啦!\a\n";
cout << "快点,\"大懒虫\"!再不起床……\n就要迟到了!\n";
}
程序执行结果:
上述代码中,\a 是振铃字符,它可以使终端扬声器振铃。\" 将双引号作为常规字符,而不是字符串分隔符。
字符名称 | ASCII符号 | C++代码 | 十进制ASCII码 | 十六进制ASCII码 |
换行符 | AL (LF) | \n | 10 | 0xA |
水平制表符 | HT | \t | 9 | 0x9 |
垂直制表符 | VT | \v | 11 | 0xB |
退格 | BS | \b | 8 | 0x8 |
回车 | CR | \r | 13 | 0xD |
振铃 | BEL | \a | 7 | 0x7 |
反斜杠 | \ | \ | 92 | 0x5C |
问号 | ? | \? | 63 | 0x3F |
单引号 | ' | \' | 39 | 0x27 |
双引号 | " | \" | 34 | 0x22 |
注意:当转义序列作为字符常量时,应该使用单括号括起来;当转义序列被放在字符串当中时,不要使用单引号。
Ctrl+Z的ASCII码是26,对应八进制编码是032,十六进制编码是0x1a。
注意:在可以使用数字转义序列和符号转义序列(如:\0x8和\b)时,应该使用符号序列。因为数字表示与特定的编码方式(如ASCII码)有关,而符号表示适用于任何编码方式,且可读性更强。
有些基于ANSI C之前的编译器的C++系统不能识别\a。有些系统的行为可能有所不同,例如把\b显示为一个小矩形。
通用字符名
概念:
C++实现支持一个基本的源字符集,即可用来编写源代码的字符集。除此之外,C++标准还允许实现提供扩展源字符集和扩展执行字符集。
而除了上述的字符集,那些被作为字母的额外字符也可用于标识符名称中。C++实现这种特殊字符的机制就是通用字符集。(例如:德国实现可能允许使用日耳曼语的元音变音。)
用法:
通用字符名的用法类似于转义字符,通用字符名可以以 \u 或 \U 打头。\u 后面是8个十六进制位,\U 是16个十六进制位。(这些位的表示是字符的ISO 10646码点)
#include<iostream>
int main()
{
using namespace std;
int n_\u00F6_name = 0;
cout << "g\u00E2teau\n";
return 0;
}
程序执行的结果是:
分析:
ö的ISO 10646码点是00F6,â的码点是00E2。上述的代码将变量名设成了n_ö_name,并且输出gâteau。
如果实现的扩展源字符集包含ö,它可能允许从键盘输入字符。
signed char 和 unsigned char
与int不同,char的符号有无是由C++实现来决定的,这样可以使开发人员将类型与硬件属性最大限度地匹配起来。
当然,也可以显式地设定 signed char 或 unsigned char:
char A_a;
unsigned char B_b;
sign char C_c;
1. 如果将char用作数值类型,就要注意有无符号时char之间的差异了。
- unsigned char 的表示范围通常是0~255;
- signed char 的表示范围是-128到127。
2. 如果使用char变量来存储标准ASCII字符,则char有没有符号都没关系。
wcha_t
程序需要处理的字符集可能无法用8个字节表示,如汉字。为了处理这种情况,除了编译器厂商可以将char定义为更长的字节外,有一种实现可以同时支持一个小型基本字符集和一个较大的扩展字符集。
wchar_t(宽字符类型)可以表示扩展字符集。
wchar_t类型是一种整型类型,它有足够的空间可以表示扩展字符集。它对于底层类型的选择取决于实现,因此,它可能是 unsigned short ,也可能是 int ,这意味着它的大小是不一定的(但是一定比char类型大) 。
cin 和 cout 将输入和输出看作char流,不适合处理 wchar_t类型 。因此,出现了处理 wchar_t类型 的工具—— wcin 和 wcout 。可以通过加上前缀L来指示宽字符常量和宽字符串。
wchar_t a_A = L'P'; //创建wchar_t类型的变量
wcout << L"width" << endl; //输出宽字符类型的字符串
C++新增的类型:char16_t 和 char32_t
char16_t 和 char32_t 更像是一种增补,它们的存在能够更好的满足编译人员的需求。
相比于长度和符号特征都会随着实现的不同而改变的wchar_t而言,char16_t(规定长16位) 和 char32_t(规定长32位)的实现更加固定,都是无符号的。(不过这两种类型的底层类型可能会随系统改变)
前缀 | 对应常量 |
u | char16_t常量 |
U | char32_t常量 |
如:
char16_t ch1 = u'q';
char32_t ch2 = U'\U0000222B'
bool类型
在C++中,非零值被解释为true,零值被解释为flase。
布尔(bool)变量的值可以是 true 和 false 。但此处的true和flase与上述概念不同,是被预设的字面值。例如,可以这样编写语句:
bool a_ready = true;
同时,bool值变量和int变量之间存在着转换关系:
int plus = true; //此处 true 被转换成 1
int delete = false; //此处 false 被转换成 0
另外,任何数字值或指针值都可以被隐式转换成bool值:
bool start = -100; //任何非零值被转换成 true
bool stop = 0; //零被转换成 false