C++Primer读书笔记:第2章 变量和基本类型
和其他整型不同,char有三种不同的类型:普通char、unsigned char和signed char,虽然char有三种不同的类型,但只有两种表示方式。可以使用unsigned char或signed char表示char类型。使用哪种char表示方式由编译器而定。
C++中,把负值赋给unsigned对象是完全合法的,其结果是该负数对该类型的取值个数求模后的值。所以,如果把-1赋给8位的unsigned char,那么结果是255,因为255是-1对256求模后的值。
对于实际的程序来说,float类型精度通常是不够的——float型只能保证6位有效数字,而double型至少可以保证10位有效数字,能满足大多数计算的需要。
建议:使用内置算数类型
在大多数机器上,使用int类型进行整型计算不易出错。就技术上而言,int类型用16位表示——这对大多数应用来说太小了。实际应用中,大多数通用机器都是使用和long类型一样长的32位来表示int类型。整型运算时,用32位表示int类型和用64位表示long类型的机器会出现应该选择int类型还是long类型的难题。在这些机器上,用long类型进行计算所付出的运行时代价远远高于用int类型进行同样计算的代价,所以选择类型前要先了解程序的细节并且比较long类型与int类型的实际运行时性能代价。
决定使用哪种浮点型就容易多了:使用double类型基本上不会有错。在float类型中隐式的精度损失是不能忽视的,而双精度计算的代价相对于单精度可以忽略。事实上,有些机器上,double类型比float类型的计算要快得多。long double类型提供的精度通常没有必要,而且还需要承担额外的运行代价。
整型字面值规则
20 // decimal
024 // octal
0x14 // hexadecimal
类似地,可以通过在数值后面加U或u定义unsigned类型。同时加L和U就能够得到unsigned long类型的字面值常量。但其后缀不能有空格:
128u /* unsigned */ 1024UL /* unsigned long */
1L /* long */ 8Lu /* unsigned long */
定义长整型时,应该使用大写字母L。小写字母l很容易和数值1相混淆。
浮点字面值规则
3.14159F .001f 12.345L 0
3.14159E0f 1E-3F 1.2345E1L 0e0
在字符字面值前加L就能够得到wchar_t类型的宽字符字面值。
我们可以将任何字符表示为以下形式的通用转义字符:
\ooo
这里ooo表示三个八进制数字,这三个数字表示字符的数字值。
同样也可以用十六进制转义字符来定义字符:
\xddd
它由一个反斜线符、一个x和一个或者多个十六进制数字组成。
正如存在宽字符字面值,也存在宽字符串字面值,一样在前面加“L”,如
L"a wide string literal"
多行字面值
// ok: A \ before a newline ignores the line break
std::cou\
t << "Hi" << st\
d::endl;
等价于
std::cout << "Hi" << std::endl;
注意反斜线符号必须是该行的尾字符——不允许其后面有注释或空格。同样,后继行行首的任何空格和制表符都是字符串字面值的一部分,正因如此,长字符串字面值的后继行才不会有正常的缩进。
建议:不要依赖未定义行为
变量名
语言本身并没有限制变量名的长度,但考虑到将会阅读和/或修改我们的代码的其他人,变量名不应太长。
除了关键字,C++标准还保留了一组标识符用于标准库。标识符不能包含两个连续的下划线,也不能以下划线开头后面紧跟一个大写字母。有些标识符(在函数外定义的标识符)不能以下划线开头。
定义对象
初始化
int ival(1024); // direct-initialization
int ival = 1024; // copy-initialization
初始化不是赋值
在函数体外定义的变量都初始化成0,在函数体里定义的内置类型变量不进行自动初始化。
建议每个内置类型的对象都要初始化。虽然这样做并不总是必需的,但是会更加容易和安全,除非你确定忽略初始化式不会带来风险。
声明和定义
可以通过使用extern关键字声明变量名而不定义它:
extern int i; // declares but does not define i
int i; // declares and defines i
extern声明不是定义,也不分配存储空间。
只有当声明也是定义时,声明才可以有初始化式。如果声明有初始化式,那么它可被当作是定义,即使声明标记为extern:
extern double pi = 3.1416; // definition
在C++语言中,变量必须且仅能定义一次,而且在使用变量之前必须定义或声明变量。
名字的作用域
在函数内定义一个与函数可能会用到的全局变量同名的局部变量总是不好的。局部变量最好使用不同的名字。
在变量使用处定义变量
通常把一个对象定义在它首次使用的地方是一个很好的办法。
定义const对象
因为常量在定义后就不能被修改,所以定义时必须初始化:
const std::string hi = "hello!"; // ok:initialized
const int i, j = 0; // error: i is uninitialized const
const对象默认为文件的局部变量
除非特别说明,在全局作用域声明的const变量是定义该对象的文件的局部变量。此变量只存在于那个文件中,不能被其他文件访问。
通过指定const变量为extern,就可以在整个程序中访问const对象:
// file_1.cpp
// defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();
// file_2.cpp
extern const int bufSize; // uses bufSize from file_1
// uses bufSize defined in file_1
for (int index = 0; index != bufSize; ++index)
// ...
非const变量默认为extern。要使用const变量能够在其他的文件中访问,必须显式地指定它为extern。
引用
当引用初始化后,只要该引用存在,它就保持绑定到初始化时指向的对象。不可能将引用绑定到另一个对象。
要理解的重要概念是引用只是对象的另一个名字。
const引用是指向const对象的引用:
const int ival = 1024;
const int &refVal = ival; // ok: both reference and object are const
int &ref2 = ival; // error: nonconst reference to a const object
可以读取但不能修改refVal,因此,任何对refVal的赋值都是不合法的。这个限制有其意义:不能直接对ival赋值,因此不能通过使用refVal来修改ival。同理,用ival初始化ref2也是不合法的。
术语:const引用是指向const的引用
const引用可以初始化为不同类型的对象或者初始化为右值。同样的初始化对于非const引用却是不合法的,而且会导致编译错误。
非const引用只能绑定到与该引用同类型的对象。
const引用则可以绑定到不同但相关的类型的对象或绑定到右值。
typedef名字
typedef通常被用于以下三种目的:
为了隐藏特定类型的实现,强调使用类型的目的。
简化复杂的类型定义,使其更易理解。
允许一种类型用于多个目的,同时使得每次使用该类型的目的明确。
枚举
定义和初始化枚举
// input is 0, output is 1, and append is 2
enum open_modes {input, output, append};
枚举成员是常量
用来初始化枚举成员的值必须是一个常量表达式。
每个enum都定义一种唯一的类型
枚举类型的对象的初始化或赋值,只能通过其枚举成员或同一枚举类型的其他对象来进行:
enum Forms { shape = 1, sphere, cylinder, polygon };
enum Points { point2d = 2, point2w,
point3d = 3, point3w };
Points pt3d = point3d; // ok: point3d is a Points enumerator
Points pt2w = 3; // error: pt2w initialized with int
pt2w = polygon; // error: polygon is not a Points enumerator
pt2w = pt3d; // ok: both are objects of Points enum type;
类类型
每个类都定义了一个接口和一个实现。接口由使用该类的代码需要执行的操作组成,实现一般包括该类所需要的数据。实现还包括定义该类需要的但又不供一般性使用的函数。
编程新手经常会忘记类定义后面的分号,这是个很普遍的错误!
定义变量和定义数据成员存在非常重要的区别:一般不能把类成员的初始化作为其定义的一部分。
用class和struct关键字定义类的唯一差别在于默认访问级别:默认情况下,struct的成员为public,而class的成员为private。
头文件
头文件为相关声明提供了一个集中存放的位置。头文件一般包含类的定义、extern变量的声明和函数的声明。
头文件的正确使用能够带来两个好处:保证所有文件使用给定实体的同一声明;当声明需要修改时,只有头文件需要更新。
头文件用于声明而不是用于定义
因为头文件包含在多个源文件中,所以不应该含有变量或函数的定义。
对于头文件不应该含有定义这一规则,有三个例外。头文件可以定义类、值在编译时就已知道的const对象和inline函数。
You got a dream
You gotta protect it
People can't do something themselves
They wanna tell you you can do it
If you want something
Go get it