【C++ Primer】学习日记(2)

发布于:2022-11-06 ⋅ 阅读:(537) ⋅ 点赞:(0)

2.1 内置类型

C++定义了一套包括算术类型(arithmetic type)和空类型(void)在内的基本数据类型。算术类型包括了字符、整型数、布尔值和浮点数。空类型不对应具体的值,仅用于一些特殊的场合,例如最常见的,当函数不返回任何值时使用空类型作为返回类型。

2.1.1 算术类型

算术类型分为两类:整型(包括字符和布尔类型在内)和浮点型。

类型 含义 最小尺寸
bool 布尔类型 未定义
char 字符 8位
wchar_t 宽字符 16位
char16_t Unicode字符 16位
char32_t Unicode字符 32位
short 短整型 16位
int 整型 16位
long 长整型 32位
long long 长整型 64位
float 单精度浮点数 6位有效数字
double 双精度浮点数 10位有效数字
long double 扩展精度浮点数 10位有效数字

bool类型的取值是真或者假。
基本的字符类型是char,一个char的空间应确保可以存放机器基本字符集中任意字符对应的数字值,也就是一个char的大小和一个机器字节一样。其他字符类型用于扩展字符集,如wchar_t、char16_t、char32_t。
除了字符和布尔值之外,其他的整型用于表示不同尺寸的整数。C++语言规定一个int至少和一个short一样大,一个long至少和一个int一样,一个long long至少和一个long一样大。
浮点型可表示单精度、双精度和扩展精度值。C++标准指定了一个浮点数有效位数的最小值,然而大多数编译器都实现了更高的精度。通常,float以1个字(32比特)来表示,double以2个字(64比特)来表示,long double以3或4个字(96或128比特)来表示。

内置类型的计算机实现

计算机在存储数据时以比特序列存储,每个比特非0即1。
大多数计算机以2的整数次幂个比特作为块来处理内存,可寻址的最小内存块称为"字节(byte)",存储的基本单元称为“字(word)”,它通常由几个字节组成。在C++中,一个字节要至少能容纳机器基本字符集中的字符。大多数机器的字节是由8比特构成,字则由32或64比特构成,也就是4或8字节。
大多数计算机将内存中的每一个字节与一个数字(也被称为地址(address))关联起来,在一个字节为8比特,字为32比特的机器上,我们可以看到一个字的内存区域如下

字节地址 字节中8比特的具体内容
00111011 736424
00011011 736425

带符号类型和无符号类型

除去布尔类型和扩展的字符型之外,其他整型可以划分为带符号的和无符号的两种。带符号类型可以表示正数、负数或0,无符号类型则仅能表示大于等于0的值。
类型int、short、long和long long都是带符号的,通过在这些类型名前添加unsigned就可以得到无符号类型,例如unsigned long。类型unsigned int 也可以缩写为unsigned。
与其他整型不同,字符型被分为了三种:char、signed char和unsigned char。

2.1.2 类型转换

对象的类型定义了对象能包含的数据和能参与的运算,其中一种运算被大多数类型支持,就是将对象从一种给定的类型转换为另一种相关的类型。下面我们把一种算术类型的值赋值给另一种类型:

bool b = 42;
int i = b; //i的值为1
i = 3.14; //i的值为3
double pi = i; //pi的值为3.0
unsigned char c = -1; //假设char占8比特,c的值为255
signed char d = 256; //假设char占8比特,d的值是未定义的

当程序的某处使用了一种算术类型的值而其实所需的值是另一种类型的值时,编译器会自动进行类型转换。例如:

int 1 = 42;
if(i) //if条件的值将为真,如果这块i的值为0则为假,i的所有非0取值都将使跳进为真
	i=0;

含有无符号类型的表达式

无符号版本和有符号版本的区别就是有符号类型需要使用一个bit来表示数字的正负,比如16位系统中一个int能存储的数据的范围为–32768 ~ 32767(16位2进制的最高位作为符号位‘1’为负‘0’为正),而unsigned能存储的数据范围则是0~65535(这个最高位不用做符号位,所以是2的16次方,一共65536)。
在32位的编译器上,unsigned int 的最大值为4294967295,再加上0,一共4294967296个整数,即32位无符号数的模4294967296,这个数加上计算的的负数即为输出结果。
尽管我们不会故意给无符号对象赋值一个负值,但是很容易写出类似的代码。例如:当一个算术表达式中既有int,又有无符号时,int值就会转化为无符号值,把int转化为无符号数的过程和把int直接赋给无符号变量一样:

unsigned u = 10;
int 1 = -20;
cout<<u + 1 <<endl; //如果int是32位,那么输出4294967286

在输出的表达式里,相加前会首先把-20转换成无符号数。把负数转换成无符号数类似于直接给无符号数赋一个负值,结果等于这个负数加上无符号数的模。
一个无符号数在作为条件判断时,例如:

for(unsigned u = 10;u >= 10;--u){
cout<<u;

在这个for循环语句中,每一个执行都会–u,但是u无法小于0,这也就会导致当u=0时,–u的结果是4294967295,就会让这个循环编程一个死循环。
一种解决办法就是用while语句代替for,因为while可以让我们在输出变量之前(而非for那种之后)先减去1:

unsigned u = 11;//需要比要输出的最大值大1
while(u > 0){
--u;//先减1,然后再输出,最后一次输出的就是0
cout<u;
}

这时候我们就可以输出0-10了,当等于0时就可以跳出while。

2.1.3 字面常量

一个形如42的值被称为字面常量,每个字面常量对应一种数据类型。

整型和浮点型字面值

我们可以将整型字面值写作十进制、八进制和十六进制:

数字 进制
20 十进制
024 八进制
0x14 十六进制

浮点型字面值表现为一个小数或者以科学计数法表示的指数,其中指数部分用E或者e标识。

字符和字符串字面值

由一个单括号括起来的一个字符称为char型字面值,双括号括起来的0个或者多个字符则构成字符串字面值。字符串字面值其实是由常量字符构成的数组,编译器会在字符串的结尾加一个空字符(‘\0’)因此字符串面值的实际长度要比它的内容多1,例如:字面值’A’表示就是单独的字符A,而字符串"A"则代表了一个字符的数组,该数组包含了一个字母A和一个空字符。

转义序列

有两类字符程序员不能直接使用:一类是不可打印的字符,如退格或者其他控制字符,因为它们没有可视的图标;另一类是在C++中有特殊含义的字符(单引号,双引号,问号,反斜线)。在这些情况下就需要用到转义序列)转义序列均以反斜线作为开始,C++的转义序列包括。

转义序列 含义
\n 换行
\t 横向制表
\a 报警(响铃)符
\v 纵向制表
\b 退格符
" 双引号
|反斜线
? 问号
单引号
\r 回车符
\f 进纸符

在程序中上述的都可以单独使用。

指定字面值的类型

下面表中是指定字面值的类型,通过添加下表中的前缀和后缀,可以改变整型、浮点型和字符型字面值的默认类型:
字符和字符串字面值

前缀 含义 类型
u Unicode16字符 char16_t
U Unicode32字符 char32_t
L 宽字符 wchar_t
u8 UTF-8(仅用于字符串字面常量) char

整型字面值

后缀 最小匹配类型
u or U unsigned
l or L long
ll or LL long long

浮点型字面值

后缀 类型
f or F float
l or L long double

布尔字面值和指针字面值

布尔类型的字面值:true和false
指针字面值:nullptr

2.2 变量

变量提供一个具名的、可供程序操作的储存空间。C++中每个变量都有其数据类型,数据类型决定着变量所占内存空间大小和布局方式、该空间能存储的值的范围,以及变量能够参与的运算。

2.2.1 变量定义

变量定义的基本形式是:先是类型说明符,随后紧跟着一个或者多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束。

列表初始化

C++定义了初始化的几种不同形式。例如:要定义一个名为i的int变量并初始化为0,以下4条语句都可以实现:

int i = 0;
int i = {0};
int i{0};
int i(0);

用花括号进行初始化的形式叫做列表初始化,无论初始化对象还是为对象赋新值,都可以使用这样一组由花括号括起来的初始值了。

默认初始化

如果定义变量时没有指定初值,则变量被默认初始化,此时变量就被赋予初始值,而初始值是什么是由变量类型决定,同时定义变量的位置也会对此有影响。
如果是内置类型的变量未被显式初始化,他的值由定义的位置决定。定义于任何函数体之外的变量都会初始化为0。

2.2.2 声明和定义的关系

为了允许把程序拆分成多个逻辑部分来编写,C++支持分离编译机制该机制允许将程序分割为若干个文件,每个文件都可被独立编译。
如果将程序分为多个文件,则需要有文件间共享代码的方式。例如:一个文件的代码可能需要使用另一个文件中定义的变量,一个实际的例子就是cout和cin,它们定义于标准库,却被我们写的程序来使用。
为了支持分离式编译,C++语言将声明和定义区分开来。声明使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对这个名字的声明。而定义负责创建与名字关联的实体。
变量声明规定了变量的类型和名字,在这一点上定义与之相同。但是除此之外,定义还申请储存空间,也可能会为变量赋一个初始值。
如果要声明一个变量而非定义,那就在变量名前面添加关键字extern,而不要显式的初始化变量:

extern int i;//声明
int j;//定义

任何包含了显式初始化的声明即成为定义,我们能给有extern关键字标识的变量赋一个初始值,但这也就抵消了extern的作用,声明的变量赋初识值就编程了定义,在函数体内部,如果试图给一个extern关键字标记的变量初始化,则会引发错误。
变量只能定义一次,但是可以声明多次。如果要多个文件使用同一变量,就必须将声明和定义分离,此时变量的声明必须出现在且只能出现在一个文件中,而其他使用到该变量的文件必须对其进行声明,且绝对不能重复定义。

2.2.3 标识符

C++的标识符由字母、数字和下划线组成,其中必须以字母或者下划线开头,标识符的长度没有限制,但是对大小写敏感,同时C++语言保留了一些名字供语言本身使用,这些名字不能被用作标识符。
在这里插入图片描述
C++操作符替代名

替代名 对应符号
and &&
and_eq &=
bitand &
bitor |
compl ~
not !
not_eq !=
or ||
or_eq |=
xor ^
xor_eq ^=

2.2.4 名字的作用域

不论在程序的什么位置,使用的名字都会指向一个实体:变量、函数、类型等,如果一个名字出现在程序的不同位置,也可能指向不同的实体。
作用域是一个程序的一部分,在其中名字有其特定的含义。C++中大多数作用域都以花括号分隔。
同一个名字在不同的作用域里面可能指向不同的实体。名字的有效区域始于名字的声明语句,以声明语句所在作用域的末端为结束。一个典型的例子:

#include <iostream>
int main(){
int i = 20;
for(int n = 0;n<2;++n){
std::cout<<n<<std::endl;
}
std::cout<<i <<std::endl; 
return 0;
}

在这段代码里面定义了两个名字main、i和n,同时使用了命名空间名字std,该空间提供了两个名字cout和endl。
名字main定义于所有花括号之外,它就和其他大多数定义在函数体之外的名字一样拥有全局作用域。一旦声明之后,全局作用域内的名字在整个程序范围都可以使用,i定义于main函数所限定的作用域之内。从声明i开始到main函数结束为止都可以访问它,但是出了main函数所在的块就无法访问了,因此说变量i拥有块作用域,名字n定义于for语句内,所以在for语句内可以访问,出了for就不能访问了。一般在定义变量时遵循这样一种原则:当你第一次使用变量时再定义它。

嵌套的作用域

作用域可以彼此包含,被包含(或者是被嵌套)的作用域被称为内层作用域,包含别的作用域的作用域称为外层作用域,作用域中一旦声明了某个名字,它所嵌套的所有作用域中都能访问该名字,同时允许在内层作用域中重新定义外层作用域已有的名字:

#include <iostream>
int num = 666;//定义num并对其初始化为666,这个num拥有全局变量
int main(){
std::cout<<num <<std::endl; //直接使用全局变量num输出,这时候的结果是666
int num =0; //新建局部变量num,这时候会覆盖全局变量num,这时候输出是0
std::cout<<num <<std::endl;
std::cout<<::num <<std::endl; //显式的访问全局变量num,输出666 
return 0;
}

2.3 复合类型

明天再码,手麻了


网站公告

今日签到

点亮在社区的每一天
去签到