C++ Primer 学习笔记——第二章
Posted 亚伦的博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ Primer 学习笔记——第二章相关的知识,希望对你有一定的参考价值。
第二章 变量和基本类型
前言
数据类型是程序的基础:它告诉我们数据的意义以及我们能在数据上执行的操作。
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位有效数字 |
基本数据类型char,其空间应确保可以存放机器基本字符集中任意字符对应的数字值。即一个char的大小和一个机器字节一样。
除去布尔型和扩展的字符型之外,其他整型可以划分为带符号的(signed)和无符号的(unsigned)两种。
其中char类型是个特殊存在,其存在三种类型:char、signed char和unsigned char。但是字符的表现形式仅有两种,
根据编译器决定类型char实际上应该表现为那种形式(无符号/有符号)。同时由于其表现形式由编译器决定,存在唯二性,
所以在算术表达式中不能使用char类型。(实在要使用,必须规定其具体类型)
2.1.2 类型转换
对象的类型定义了对象能包含的数据和能参与的运算。类型所能表示的值的范围决定了转换的过程。
当赋给无符号类型一个超出它表示范围的值时,结果时初始值对无符号类型表示数值总数取模后的余数。例如:unsigned char c = -1 //假设char占8比特,c的值为255
,由于8比特大小的无符号char可以表示0~255内的值,其实际结果便是该值对256取模后得到的余数255。
当赋给带符号类型一个超出它范围的值时,结果时未定义的(undefined),存在一下可能:程序可能正常工作、可能程序崩溃
可能产生未知的数据(即垃圾数据)
PS:
程序应当尽量避免依赖于实现环境的行为、避免无法预知的行为
2.1.3 字面值常量
形如 12 的值被称为字面值常量(literal)。每个字面值常量都对应一种数据类型,其形式和值决定了它的数据类型。
整型和浮点型字面值
整型字面值具体的数据类型由其符号和值决定。
20 /* 十进制 */
024 /* 八进制 */
0x24 /* 十六进制 */
默认,十进制为带符号数,剩下两种都有可能。
字符和字符串字面值
字符串字面值的类型实际是由常量字符构成的数组。编译器在每个字符串的结尾处添加一个空字符“\\0”,因此,实际长度比内容多1。
转义字符
C++语言规定的转义序列包括:
换行符 \\n 横向制表符 \\t 报警(响铃)符 \\a
纵向制表符 \\v 退格符 \\b 双引号 \\"
反斜杠 \\\\ 问号 \\? 单引号 \\\'
回车符 \\r 进纸符 \\f
通过为字面值添加前缀和后缀,可以改变整型、浮点型和字符型字面值的默认类型。
布尔字面值和指针字面值
true和false是布尔类型的字面值,nullptr是指针字面值。
2.2 变量
变量提供一个具名的、可供程序操作的存储空间
。C++中每个变量都有数据类型,数据类型决定着变量所占内存空间的大小和布局方式、该空间能存储的值的范围,以及变量能参与的运算。对于C++程序员来说,“变量(variable)”和“对象(object)”一般可以互换使用。
2.2.1 变量定义
变量定义的基本格式:类型说明符(type specifier),一个或多个变量名组成的列表。
PS:
对象(object),指一块能存储数据并具有某种类型的内存空间。
一部分人认为:仅在与类有关的场景下才能使用“对象”这个词;一部分人认为应该将把命名的对象和未命名的对象区分开将命名了的对象叫做变量;还有一部分人仍未应该将能够被程序修改的数据称为对象,而只读程序称为值(value)
初始化
当对象在创建时获得一个特定的值,称为这个对象被初始化(initialized)了。
初始化并不是赋值,虽然都是通过=方式,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来代替。
列表初始化
int a=0;
int a=0;
int a0;
int a(0);
上述四种均可为a赋值。
默认初始化
定义于函数体内的内置类型的对象如果没有初始化,则其值未定义。类的对象如果没有显式地初始化,则其值由类确定。
未初始化的变量含有一个不确定的值,使用未初始化的变量的值绝对是一种错误且严重的编程行为(此行为很难调试),
究其原因:①、编译器虽然会对部分未初始化的变量提出警告,但其本身并未要求检查此类错误;②、使用未初始化的变量会带来无法预测的结果,形成黑箱!强烈建议:对每一个内置类型的变量都应该初始化,虽无强制要求,但是为了程序安全很有必要。
2.2.2 变量声明和定义的关系
为了允许把程序拆分成多个逻辑部分,C++支持分离式编译(separate compilation)机制,在此机制下程序可以分割为若干个文件,每个文件皆可独立编译。
为了实现该机制,那么必须能够存在能够在文件共享代码的方法。例如:std::cout O(∩_∩)O哈哈~
在此之下,C++语言将声明和定义区分开来,声明(declaration)使得名字为程序所知,一个文件若想在别处使用该名字则必须包含对此名字的声明。而
定义(definition)则负责创建与名字关联的实体。
变量声明规定了变量的类型和名字。(与定义区别在于:定义还多一项为变量申请存储空间,也可能赋予一个初始值的功能)
如果想要声明一个变量而非定义该变量,就会在变量前添加关键字extern,同时不要显式的初始化变量。
extern int i; //声明i而非定义i
int j; //声明且定义j
任何包含了显式初始化的声明即称为定义。同时,在函数体内部,如果试图初始化一个已经被关键词extern标记的变量,将引发错误。同时在函数体外这样做,将会出现警告。
Note:
变量能且只能被定义一次,但是可以被多次声明
2.2.3 标识符
标识符(identifier)由字母、数字和下划线组成。其必须是字母或下划线开头。
其变量的命名规范
变量命名有许多约定俗成的规范,下面这些规范能够有效的提高提高程序的可读性:
- 标识符要能体现实际含义
- 变量名一般用小写字母,比如index,不要使用Index或者INDEX
- 用户自定义的类名一般以大写字母开头,如Search()
- 如果标识符由多个单词组成,则单词间应该有明显区分,如Student_name或者StudentName,不可Studentname
2.2.4 名字的作用域
不论在程序的那个位置,使用到的每一个名字都会指向其一个实体。然而,同一名字出现在不同的位置,也有可能指向不同实体。
名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束。
- 全局作用域(global scope)定义在函数体之外,声明后在整个程序的范围内都可以使用。
- 块作用域(block scope)定义于函数体之内,在其函数体内可以使用。
Suggestion
当第一次使用变量时再去定义它。原因①:有助于更容易找到变量的定义;原因②:当定义位置与其第一次被使用位置很近时,会赋予一个更加合理的初始值。
嵌套作用域
- 内层作用域(inner scope)
- 外层作用域(outer scope)
其被嵌套的作用域是相对的,在作用域中声明了某个名字,其所嵌套的所有作用域都可以使用该名字,同时,也允许在其嵌套作用域中重新定义外层作用域的名字。
Warning
如果函数使用到全局变量,不宜再次定义与其名字相同的局部变量。
2.3 复合类型
复合类型(compound type)是指基于其他类型定义的类型。C++语言中有几种复合类型,这里介绍:引用和指针。
2.3.1 引用
Note
C++ 11中新增了一种引用:所谓的“右值引用(rvalue reference)”,这种引用主要用在内置类中。严格来说,我们平常使用的术语“引用(reference)”其实是指“左值引用(lvalue
reference)”。
引用(reference)作用就是为对象起别名。
PS
引用其本质上应该是指针常量(即该形式:数据类型* const 变量名),其限制为:能够更改指针对应的值,但是不能修改对应的地址值。所以:引用
必须初始化。同样的道理,对引用进行的所有操作都是在为与之绑定的对象进行操作,其引用并不是一个对象
,无法定义引用的引用。也就不能使用这样的操作:与字面值绑定或者与某个表达式绑定。
2.3.2 指针
指针(pointer)是“指向(point to)”另一种类型的复合类型。
其与引用类似,指针也实现对其他对象的间接访问。但是指针与引用又有很多地方不相同:
①、指针本身就是一个对象,允许对指针的赋值和拷贝,并且在指针的生命周期中可以先后指向多个不同的对象。
②、指针无须定义时赋初值,但同时和其他内置类型一样,在块作用域内定义指针如果没有初始化,也将拥有一个不确定值。
获取对象的地址
指针存放某个对象的地址,想要获取地址,需要使用取地址符(操纵符&)
Warning
①、因为引用不是对象,所以不能定义指向引用的指针
②、所有指针的类型都要和它所指向的对象严格匹配,因为在声明语句中指针的类型实际上被用来指定它所指向对象的类型
指针值
指针值(即地址值)应该属于下列4种情况之一:
- 指向一个对象
- 指向紧邻的对象所占空间的下一个位置
- 空指针
- 无效指针,即上述情况之外的其他值
任何试图访问无效指针的值都会引发错误,且编译器并不会检查此类错误,程序员应该清楚任意给定的指针的有效性
;虽然2、3两种情况是有效的,但是试图访问其指针指向的对象同样不被允许。
利用指针访问对象
如果指针指向一个对象,允许使用解引用符(操纵符*)来访问该对象。
Warning
解引用操作仅适用于那些明确指向对象的有效指针。
空指针
空指针(null pointer)不指向任何对象。
Suggestion
使用未经初始化的指针是引发运行时错误的一大原因。
建议初始化所有指针,并且在可能的情况下,尽量等到定义了对象后再定义指针。实在不行,便将指针定义为nullptr,使得编译器明白该指针并未指向任何对象。
赋值和指针
指针和引用都能提供对其他对象的间接访问,然而在具体实现细节上二者有很大不同,其中最重要的一点就是引用本身并非一个对象。一旦定义了引用,就无法令其再绑定到另外的对象,之后每次使用这个引用都是访问它最初绑定的那个对象。
但是对于指针,则没有这种限制,和其他任何变量(只要不是引用)一样,给指针赋值就是令它存放一个新地址,从而指向一个新对象。
其他指针操作
- 若指针值为0,则条件取false,其他情况都返回true。
- 对于两个类型相同的合法指针,可以进行比较,比较结果为布尔类型。其比较对象是两个指针的存放的地址值。
void* 指针
void*是一种特殊的指针类型,可用于存放任意对象的地址,但是对存放何种对象类型并不了解。
由于无法知道指针指向的对象是什么类型,所以就不能直接操作void*指针所指向的对象。
概括来讲:void*就仅仅只是内存空间,无法读取内存空间所存的对象。
2.3.3 理解复合类型的声明
前面说过:变量的定义包括一个基本数据类型(base type)和一组声明符。在同一条定义语句中,虽然基本数据类型只有一个,但是声明符的形式却可以不同。例如:
int i = 1024, *p = &i, &r = i; /* i是int类型的数,p是int类型指针,r是int类型引用 */
可能觉得基本数据类型和类型修饰符有什么关系,其实后者不过是声明符的一部分。
Warning
有一种错误的观点认为:在定义语句总,类型修饰符作用于本次定义的全部变量。其造成错误看法的原因很多,其中一种是:
int* p1,p2;
在此观点下,会认为p1和p2是两个都是int类型的指针变量。其实只有p1是,p2是int类型变量。其*仅仅修饰了p1。
所以,为了强调标量具有复合类型,建议将修饰符和变量标识符写在一起。
指向指针的引用
虽然,引用本身不是对象,无法使用指针指向引用,但是指针本身是对象,可以使用引用指向指针。
int i = 1024;
int *p;
int *&r = p; /* r是对指针p的引用,建议从右向左看*&r */
r = &i; /* r本身是引用指针p,所以这里将i的地址赋值给指针p */
*r = 0; /* 指针p指向的i值赋予为0*/
Suggestion
面对一条较为复杂的指针或者引用的声明语句时,从右向左阅读有助于弄清楚它的真实含义。
2.4 const限定符
有时候我们并不希望定义一个变量,它的值无法改变。这个时候,我们就可以通过const对此类变量加以限定。
因为const对象一旦完成创建就无法修改其值,所以const对象必须初始化。
初始化和const
const类型的变量与普通变量并没多大区别,其主要限制在于只能在const类型的对象上执行不改变内容的操作。在此限制下还有一种是初始化,如果利用一个对象取初始化另一个对象,则它们是不是const都无关紧要。
int i =1024;
const int ci = i; /* ci的常量特征仅仅在执行改变ci的操作时才发挥作用,这里仅仅对ci进行初始化操作 */
int j = ci;
默认情况下,const对象仅在文件内有效
默认情况下,const对象被设定为仅在文件内有效,当多个文件中出现了同名的const变量时,其实等同于不同文件中分别定义了独立的变量。
如果希望在多个文件中都可以使用该const类型对象,而无需编译器为每个文件分别生成独立变量。解决方法便是:对于const变量不管是声明还是定义都添加extern关键字。例如:
/* file_1.cpp */
extern const int BUFFERSIZE=1024; /* 初始化常量 */
/* file_1.hpp */
extern const int BUFFERSIZE; /* 在头文件中也使用extern关键字,作用是指明该常量并非本文件独有,它的定义在别处出现 */
Note
如果想要在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字
2.4.1 const的引用
可以将引用绑定到const对象上,称为对常量的引用(reference to const)。。
const int i=1024;
cosnt &r=i; /* 正确,引用的对象和其引用本身都为常量 */
r=42; /* 错误,r是对常量i的引用,常量无法被修改 */
int r2=i; /* 错误,普通引用(对非常量的引用)无法指向常量对象 */
通俗上讲:我们一般将“对常量(const)的引用”说成“常量引用”。但是严格上讲,并不存放常量引用,因为引用本身就不是一个对象,无法保证引用的不变。
初始化和对const的引用
初始化常量引用时运行用任意表达式作为初始值,只要其表达式能够转换为引用类型。运行常量引用绑定非常量的对象、字面值或一般表达式。(这也是“引用的类型必须与所引用的类型一致”的两个例外之一)
int i =23;
const int &v1=i; /* 允许将const int&绑定到一个普通int对象上 */
const int &v2=23; /* 正确,允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式 */
const int &v3 = v1*2; /* 正确 */
int &v4=v1*2; /* 错误,无法将常量赋值给普通变量 */
常量引用仅对引用可参与的操作做出限定,对于引用的对象本身是不是一个常量未作限定。
int i = 23;
int &v1 =i;
const int &v2=i; /* 绑定常量 其实际步骤为:const int temporary = i; const int &v2 = temporary; */
v1 = 0; /* 正确*/
v2=0; /* 错误,v2作为一个常量引用 */
2.4.2 指针和const
与引用类似,存在指向常量的指针(pointer to const),其不能改变所指对象的值。
const double pi = 3.14;
double &ptr= π /* 错误,无法将指向常量的指针指向非常量 */
const double *cptr=π /* 正确 */
*cptr=3.15; /* 错误,无法修改常量的值 */
与对常量的引用一样并未规定所指的对象必须是一个常量。仅作出不能通过该指针修改对象的值。
double dval = 3.14;
cptr = &dval; /* 合法 */
C++ primer plus 第二章学习笔记
1、#include <iostream>的作用是什么?
该编译指令导致预处理器将iostream文件的内容添加到程序中,这是一种典型的预处理操作,再源代码被编译之前,替换或添加文本。
iostream中的io指的是输入和输出,C++中的输入/输出方案涉及iostream文件中的多个定义,为了使用cout来显示消息,第一个程序需要这些定义,#include 编译指令导致iostream文件的内容随源代码文件的内容一起被发送给编译器。
以上是关于C++ Primer 学习笔记——第二章的主要内容,如果未能解决你的问题,请参考以下文章