《C++Primer》第二章-变量和基本类型-学习笔记(2)

《C++Primer》第二章-变量和基本类型-学习笔记(2)

日志:
1,2020-02-26 笔者提交文章的初版V1.0

作者按:
最近在学习C++ primer,初步打算把所学的记录下来。

传送门/推广
上一篇
《C++Primer》第二章-变量和基本类型-学习笔记(1)

字面值常量

字面值常量(literal constant):像 42 这样的值(就像《银河系漫游指南》里关于宇宙终极问题的答案"42"),在C++程序当作字面值常量。称之为字面值是因为只能用它的值称呼它》而称之为常量是因为它的值不能修改。每个字面值都有相应的类型,例如:42 是 int 型,3.14159 是 double 型。只有内置类型存在字面值,而类类型是没有字面值的。因此,也没有任何标准库类型的字面值。
常量可以是任何的基本数据类型,可分为整型数字、浮点数字、字符、字符串和布尔值。

整型字面值规则

定义字面值整数常量可以使用以下三种进制中的任一种:十进制、八进制和十六进制
字面值整数常量的前缀指定了基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。

//下面是值20的三种进制的表示
20    // 十进制(decimal)
024   // 八进制(octal)
0x14  // 十六进制(hexadecimal)

字面值整数常量的后缀能够强制将字面值整数常量转换为 long、unsigned 或 unsigned long类型。后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)。后缀可以是大写,也可以是小写,U 和 L 的顺序任意。(注意!定义长整型时,应该使用大写字母 L。小写字母 l 很容易和数值 1 混淆。)

//整型常量20带着不同的后缀
20u   // 无符号整数 
20l   // 长整数 
20ul  // 无符号长整数
20lu  // 无符号长整数

浮点字面值规则

通常可以用十进制或者科学计数法来表示浮点字面值常量。使用科学计数法时,指数用 E 或者 e 表示。默认的浮点字面值常量为 double 类型。在数值的后面加上 F 或 f 表示单精度。同样加上 L 或者 l 表示扩展精度(再次提醒,不提倡使用小写字母l)。

//下面每一组字面值表示相同的值: 
3.14159F    3.14159E0f
.001f       1E-3F
12.345L     1.2345E1L 
0e0         0.

布尔字面值规则

布尔常量只有两个,而且都是标准的 C++ 关键字。

true 值代表真。
false 值代表假。

需要说明的是 true 和 false 的值不要当成1和 0,二者是有区别的。

字符字面值规则

字符常量可以是一个普通的字符(例如 ‘x’)、一个转义序列(例如 ‘\n’),或者一个通用的字符(例如 '\u5427 ')
C11标准中引入了通用字符名(Universal Character Names)概念。通用字符名也是Unicode。C11中采用两种方法来表示通用字符名:

  • \u 后面加正好4位十六进制数(只能是4位,不够补0)。
  • \U 后面加正好8位十六进制数(只能是8位,不够补0)。

这两者也称为“短标识符”。\u****或 \U********中的 **** 或 ******** 部分所表示的值是Unicode码点值(Unicode code point)

可打印的字符型字面值通常用一对单引号来定义:

'a'          'b'

这些字面值都是 char 类型的。在字符字面值前加 L 就能够得到 wchar_t类型的宽字符字面值。例如:

L'a'          L'b'

非打印字符的转义序列

有些字符是不可打印的。不可打印字符实际上是不可显示的字符,比如退格或者控制符。还有一些在语言中有特殊意义的字符,例如单引号、双引号和反斜线符号。不可打印字符和特殊字符都用转义字符书写。转义字符都以反斜线符号开始.
C++ 语言中定义了如下转义字符:

\\	\ 字符
\'	' 字符
\"	" 字符
\?	? 字符
\a	警报铃声
\b	退格键
\f	换页符
\n	换行符
\r	回车
\t	水平制表符
\v	垂直制表符

\ooo与\xddd表示法

我们可以将任何字符表示为以下形式的通用转义字符:\ooo。这里 ooo 表示三个八进制数字,这三个数字表示字符的数字值。
下面的例子是用 ASCII 码字符集表示字面值常量:

\7 (bell)  \12 (newline)  \40 (blank)  \0 (null)  \062 ('2')  \115 ('M')

字符’\0’通常表示“空字符(null character)”,它有着非常特殊的意义。
同样也可以用十六进制转义字符来定义字符:它由一个反斜线符、一个 x 和一个或者多个十六进制数字组成。

"Hello\tWorld!\n" // string literal using newlines and tabs

字符串字面值规则

前面介绍的所有字面值都有基本内置类型,但是字符串字面值很复杂,并没有基本内置类型。字符串字面值是一串常量字符。
字符串字面值常量用双引号括起来的零个或者多个字符表示。不可打印字符表示成相应的转义字符。

'h' // single quote: character literal  表示单个字符 h
"h" // double quote: character string literal 表示包含字母 h 和空字符两个字符的字符串。

正如存在宽字符字面值,如

L'h'

也存在宽字符串字面值,一样在前面加“L”,如

L"hello world" 

宽字符串字面值是一串常量宽字符,同样以一个宽空字符结束。

字符串字面值的连接

两个相邻的仅由空格、制表符或换行符分开的字符串字面值(或宽字符串字面值),可连接成一个新字符串字面值。这使得多行书写长字符串字面值变得简单:

// concatenated long string literal  连接长字符串常量
std::cout << "a multi-line "
"string literal "
"using concatenation"
65
<< std::endl;

//上述输出的结果是
a multi-line string literal using concatenation

如果连接字符串字面值和宽字符串字面值,将会出现什么结果呢?例如:

// Concatenating plain and wide character strings is undefined
std::cout << "multi-line " L"literal " << std::endl;

//其结果是未定义的,也就是说,连接不同类型的行为标准没有定义。

这个程序可能会执行,也可能会崩溃或者产生没有用的值,而且在不同的编译器下程序的动作可能不同。

多行字面值

处理长字符串有一个更基本的(但不常使用)方法,这个方法依赖于很少使用的程序格式化特性:在一行的末尾加一反斜线符号可将此行和下一行当作同一行处理。
可以使用这个特性来编写长字符串字面值:

// multiline string literal
std::cout << "a multi-line \
string literal \
using a backslash"
<< std::endl;
return 0;
}
//注意反斜线符号必须是该行的尾字符,并且不允许有注释或空格符。

上面这种用法在一行输入不下的情况下经常使用,在Linux命令行下输入命令也是这种用法。

变量

变量的基本概念

变量(variable)提供了程序可以操作的有名字的存储区。C++ 中的每一个变量都有特定的类型,该类型决定了变量的内存大小和布局、能够存储于该内存中的值的取值范围以及可应用在该变量上的操作集。C++ 程序员常常把变量称为“变量(variable)”或“对象(object)”。
变量是左值,因此可以出现在赋值语句的左边。数字字面值是右值,因此不能被赋值。

变量名

变量名,即变量的标识符,可以由字母、数字和下划线组成。变量名必须以字母或下划线开头,并且区分大小写字母:C++ 中的标识符都是大小写敏感的。变量的命名经验参照《匈牙利命名法》。文件的命名经验参照阮一峰的开发者手册《为什么文件名要小写?》

需要说明的是,C++ 保留了一组词用作该语言的关键字。关键字不能用作程序的标识符。

asm do if return try auto double inline short typedef bool dynamic_cast int 
signed typeid break else long sizeof typename case enum mutable static union
catch explicit namespace static_cast unsigned char export new struct using
class extern operator switch virtual const false private template void
const_cast float protected this volatile continue for public throw wchar_t
default friend register true while delete goto reinterpret_cast
//上表中为C++98/03中的63个关键字

同时下面两篇文章也做了很好的总结

C++ 还保留了一些词用作各种操作符的替代名。这些替代名用于支持某些不支持标准C++操作符号集的字符集。它们也不能用作标识符。表 2列出了这些替代名。

and bitand compl not_eq or_eq xor_eq
and_eq bitor not or xor
表2. C++ 还保留了一些词用作各种操作符的替代名(11个)

变量的定义

每个定义都是以类型说明符开始,后面紧跟着以逗号分开的含有一个或多个
说明符的列表。分号结束定义。例如:

int A;
double B, C;
std::string D;
int E,F,
G;           //多个变量可以定义在同一条语句中:比如这个int E,F,G;还换行了

类型说明符指定与对象相关联的类型:int 、double、std::string 都是类型名。其中 int 和 double 是内置类型,std::string 是标准库定义的类型。

变量的初始化

变量定义指定了变量的类型和标识符,也可以为对象提供初始值。定义时指定了初始值的对象被称为是已初始化的。
C++ 支持两种初始化变量的形式:

  • 复制初始化(copy-initialization)。复制初始化语法用等号(=)
  • 直接初始化(direct-initialization)。直接初始化则是把初始化式放在括号中

例如:

int A(1024); //  直接初始化
int A = 1024; // 复制初始化,这里是初始化,而不是赋值

C++ 中理解“初始化不是赋值”是必要的。初始化创建变量并给它赋初始值,而赋值则是擦除对象的当前值并用新值代替。使用=来初始化变量使得许多 C++ 编程新手感到迷惑,他们很容易把初始化当成是赋值的一种形式。但是在 C++ 中初始化和赋值是两种不同的操作。这个概念特别容易误导人,因为在许多其他的语言中这两者的差别不过是枝节问题因而可以被忽略。即使在 C++ 中也只有在编写非常复杂的类时才会凸显这两者之间的区别。但是这是一个很关键的概念.

初始化的形式

初始化“内置类型的对象”只有一种方法:提供一个值,并且把这个值复制到新定义的对象中。对内置类型来说,复制初始化和直接初始化几乎没有差别。
对类类型的对象来说,有些初始化仅能用直接初始化完成。这是由其构造函数决定的。

初始化多个变量

当一个定义中定义了两个以上变量的时候,每个变量都可能有自己的初始化式。 对象的名字立即变成可见,所以可以用同一个定义中前面已定义变量的值初始化后面的变量。已初始化变量和未初始化变量可以在同一个定义中定义。前面说的两种形式的初始化文法可以相互混合。

#include <string>
// ok: salary defined and initialized before it is used to initialize wage
double salary = 9999.99,
wage(salary + 0.01);
// ok: mix of initialized and uninitialized
int interval,
month = 8, day = 7, year = 1955;
// ok: both forms of initialization syntax used
std::string title("C++ Primer, 4th Ed."),
publisher = "A-W";

对象可以用任意复杂的表达式(包括函数的返回值)来初始化:

double price = 109.99, discount = 0.16;
double sale_price = apply_discount(price, discount);

变量初始化规则

当定义没有初始化式的变量时,系统有时候会帮我们初始化变量。这时,系统提供什么样的值取决于变量的类型,也取决于变量定义的位置

内置类型变量的初始化
内置类型变量是否自动初始化取决于变量定义的位置。在函数体外定义的变量都初始化成 0,在函数体里定义的内置类型变量不进行自动初始化。除了用作赋值操作符的左操作数,未初始化变量用作任何其他用途都是没有定义的。未初始化变量引起的错误难于发现。

类类型变量的初始化
每个类都定义了该类型的对象可以怎样初始化。类通过定义一个或多个构造函数来控制类对象的初始化。如果定义某个类的变量时没有提供初始化式,这个类也可以定义初始化时的操作。它是通过定义一个特殊的构造函数即默认构造函数来实现的。这个构造函数之所以被称作默认构造函数,是因为它是“默认”运行的。如果没有提供初始化式,那么就会使用默认构造函数。不管变量在哪里定义,默认构造函数都会被使用。
有些类类型没有默认构造函数。对于这些类型来说,每个定义都必须提供显式的初始化式。没有初始值是根本不可能定义这种类型的变量的。(意思是说没有默认构造函数的话,必须给它显式初始化,否则出错。

变量声明和定义

变量的定义用于为变量分配存储空间,还可以为变量指定初始值。在一个程序中,变量有且仅有一个定义。
变量的声明用于向程序表明变量的类型和名字。定义也是声明:当定义变量时我们声明了它的类型和名字。
可以通过使用extern 关键字声明变量名而不定义变量
不定义变量的声明包括对象名、对象类型和对象类型前的关键字extern:

extern int i; // declares but does not define i
int i; // declares and defines i

extern 声明不是定义,也不分配存储空间事实上,它只是说明变量定义在程序的其他地方。程序中变量可以声明多次,但只能定义一次。
只有当声明也是定义时,声明才可以有初始化式,因为只有定义才分配存储空间。初始化式必须要有存储空间来进行初始化。

如果声明有初始化式,那么它可被当作是定义,即使声明标记为 extern:

extern double pi = 3.1416; // definition

上述代码虽然使用了 extern ,但是这条语句还是定义了 pi,分配并初始化了存储空间。只有当 extern 声明位于函数外部时,才可以含有初始化式。

声明和定义之间的区别可能看起来微不足道,但事实上却是举足轻重的。

在 C++ 语言中,变量必须且仅能定义一次,而且在使用变量之前必须定义或声明变量。(声明可以多次啦)

任何在多个文件中使用的变量都需要有与定义分离的声明。在这种情况下,一个文件含有变量的定义,使用该变量的其他文件则包含该变量的声明(而不是定义)。//需要使用某个变量的时候,要么声明它,要么定义它。

在变量使用处定义变量

一般来说,变量的定义或声明可以放在程序中能摆放语句的任何位置。变量在使用前必须先声明或定义。

通常把一个对象定义在它首次使用的地方是一个很好 的办法。

在对象第一次被使用的地方定义对象可以提高程序的可读性。读者不需要返回到代码段的开始位置去寻找某一特殊变量的定义,而且,在此处定义变量,更容易给它赋以有意义的初始值。
放置声明的一个约束是,变量只在从其定义处开始到该声明所在的作用域的结束处才可以访问。必须在使用该变量的最外层作用域里面或之前定义变量。

参考资料

【1】C++ Primer 中文版(第四版·特别版)
【2】C++ 的关键字(保留字)完整介绍
【3】C++关键字详解

注解

【4】左值(L-value):左值可以出现在赋值语句的左边或右边。
右值(R-value):右值只能出现在赋值的右边,不能出现在赋值语句的左边。
【5】什么是对象(object)?:C++ 程序员经常随意地使用术语对象。一般而言,对象就是内存中具有类型的区域。
说得更具体一些,计算左值表达式就会产生对象。严格地说,有些人只把术语对象用于描述变量或类类型的值。有些人还区别有名字的对象和没名字的对象,当谈到有名字的对象时一般指变量。还有一些人区分对象和值,用术语对象描述可被程序改变的数据,用术语值描述只读数据。
《C++ primer》遵循更为通用的用法,即对象是内存中具有类型的区域。我们可以自由地使用对象描述程序中可操作的大部分数据,而不管这些数据是内置类型还是类类型,是有名字的还是没名字的,是可读的还是可写的。

本文许可证

本文遵循 CC BY-NC-SA 4.0(署名 - 非商业性使用 - 相同方式共享) 协议,转载请注明出处,不得用于商业目的。
CC BY-NC-SA 4.0

发布了52 篇原创文章 · 获赞 72 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/engineerxin/article/details/104515248