《C++ Primer》笔记 第2章 变量和基本类型
如果你的数值超过了int表示范围,选用long long
如果你需要使用一个不大的整数,那么明确指定它的类型是signed char或者unsigned char
执行浮点数运算选用double
当一个算术表达式中既有无符号数又有int值时,那个int值就会转换成无符号数。
当从无符号数中减去一个值时,不管这个值是不是无符号数,我们都必须确保结果不能是一个负值。
分行书写的字符串字面值
std::cout << "a really, really long string literal " "that spans two lines" << std::endl;
如果反斜线\后面跟着的八进制数字超过3个,只有前3个数字与\构成转义序列。
字符和字符串字面值
前缀 含义 类型 u Unicode 16字符 char16_t U Unicode 32字符 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 我们在使用对象这个词时,并不严格区分是类还是内置类型,也不区分是否命名或是否只读。
初始化和赋值是两个完全不同的操作。
初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而已一个新值来替代。
列表初始化:
int unit = 0; int unit = {0}; int unit{0}; int unit(0); long double ld = 3.1415926536; int a{ld}, b = {ld}; // 错误:转换未执行,因为存在丢失信息的危险 int c(ld), d = ld; // 正确:转换执行,且确实丢失了部分值
在函数体内部的内置类型变量将不被初始化。
变量声明
extern int i; // 声明i而非定义i int j; // 声明并定义j
任何包含了显示初始化的声明即成为定义。
变量能且只能被定义一次,但是可以被多次声明。
用户自定义的标识符中不能连续出现两个下划线,也不能以下划线紧连大写字母开头。此外,定义在函数体外的标识符不能以下划线开头。
因为全局作用域本身并没有名字,所以当作用域操作符的左侧为空时,向全局作用域发出请求获取作用域操作符右侧名字对应的变量。
#include<iostream> using namespace std; int reused = 1; int main() { int reused = 2; // 局部变量 ::reused = 3; reused = 4; cout << "reused: " << reused << endl; cout << "::reused: " << ::reused << endl; // 显示地访问全局变量 return 0; }
引用必须被初始化
int iVal = 1024; int &refVal = iVal; // refVal是iVal的另一个名字
无法令引用重新绑定到另外一个对象,因此引用必须初始化。
引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字。
因为引用本身不是一个对象,所以不能定义引用的引用。
除两种例外情况外,引用类型都要和与之绑定的对象严格匹配。而且,引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。
因为引用不是对象,没有实际地址,所以不能定义指向引用的指针。
现在的C++程序最好使用
nullptr
,同时尽量避免使用NULL
。把int变量直接赋给指针是错误的操作。
int zero = 0; int *pi = zero;
赋值永远改变的是等号左侧的对象。
对于两个类型相同的合法指针,可以用相等操作符(==)或不相等操作符(!=)来比较它们,比较的结果是布尔类型。如果两个指针存放的地址值相同,则它们相等;反之它们不相等。
void*指针:
- 拿它和别的指针比较、作为函数的输入或输出
- 赋给另外一个void*指针
- 不能直接操作void*指针所指的对象
定义多个变量:
int *p1, *p2;
int* p1; int* p2;
指针是对象,所以存在对指针的引用。
默认情况下,const对象被设定为仅在文件内有效。
如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字。
常量引用是对const的引用
const int ci = 1024; const int &r1 = ci; // 正确:引用及其对应的对象都是变量 r1 = 42; // 错误:r1是对常量的引用 int &r2 = ci; // 错误:试图让一个非常量引用指向一个常量对象
初始化和对const的引用
double dval = 3.14; const int &ri = dval; // 注意,如果ri非const就非法 /* * 编译器把上述代码变成了如下形式: * const int tmp = dval; // 由双精度浮点数生成一个临时的整型常量 * const int &ri = tmp; // 让ri绑定这个临时量 */
当执行对象的拷贝操作时,顶层const不受影响,底层const的限制不能忽视。拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之则不行。
常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定。
声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。
一般来说,如果你认定变量是一个常量表达式,那就把它声明成constexpr类型。
声明constexpr时使用字面值类型。算术类型、引用和指针都属于字面值类型。自定义类、IO库、string类则不属于字面值类型,也就不能被定义成constexpr。
一个constexpr指针的初始值必须是nullptr或者0,或者是存储与某个固定地址中的对象。函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量。相反的,定义于所有函数体之外的对象其地址固定不变,能用来初始化constexpr指针。
限定符constexpr仅对指针有效,与指针所指的对象无关。
const int *p = nullptr; // p是一个指向整型常量的指针 constexpr int *q = nullptr; // q是一个指向整数的常量指针
constexpr把它所定义的对象置为了顶层const。
与其他常量指针类似,constexpr指针既可以指向常量也可以指向一个非常量。
constexpr int *np = nullptr; // np是一个指向整数的常量指针,其值为空 int j = 0; constexpr int i = 42; // i的类型是整型常量 // i和j都必须定义在函数体之外 constexpr const int *p = &i; // p是常量指针,指向整型常量i constexpr int *p1 = &j; // p1是常量指针,指向整数j
类型别名:
typedef double wages; using wages = double; typedef char *pstring; using pstring = char*; const pstring cstr = 0; // cstr是指向char的常量指针(注意,不能理解为const char *cstr = 0;) const pstring *ps; // ps是一个指针,它的对象是指向char的常量指针
auto让编译器通过初始值来推算变量的类型。显然,auto定义的变量必须有初始值。
因为一条声明语句只能有一个基本数据类型,所以该语句中所有变量的初始基本数据类型都必须一样。
auto i = 0, *p = &i; // 正确 auto sz = 0, pi = 3.14; // 错误
使用引用其实是使用引用的对象。特别是当引用被用作初始值时,真正参与初始化的其实是引用对象的值。此时编译器以引用对象的类型作为auto的类型。
auto一般会忽略掉顶层const,同时底层const则会保留下来。
int i = 0; const int ci = i, &cr = ci; auto b = ci; // b是一个整数 auto c = cr; // c是一个整数 auto d = &i; // d是一个整型指针 auto e = &ci; // e是一个指向整数常量的指针(对常量对象取地址是一种底层const)
如果希望推断出的auto类型是一个顶层const,需要明确指出:
const auto f = ci;
还可以将引用的类型设为auto,设置一个类型为auto的引用时,初始值中的顶层常量属性仍然保留
auto &g = ci; // g是一个整型常量的引用,绑定到ci auto &h = 42; // 错误:不能为非常量引用绑定字面值 const auto &j = 42; // 正确:可以为常量引用绑定字面值
要在一条语句中定义多个变量,切记,符号&和*只从属于某个声明符,而非基本数据类型的一部分,因此初始值必须是同一种类型:
auto &m = ci, *p = &ci;
decltype的作用是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。(如:
decltype(f()) sum = x;
中sum的类型就是函数f的返回类型,但不实际调用函数f)如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内),需要指出的是,引用从来都作为其所指对象的同义词出现,只有用在decltype处是一个例外。
如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型。
- 如果该表达式的结果对象能作为一条赋值语句的左值,decltype返回一个引用类型。
- 如果表达式的内容是解引用操作,则decltype将得到引用类型。
- 如果给变量加上了一层或多层括号,编译器就会把它当成是一个表达式。变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型。(切记:
decltype((variable))
(注意是双层括号)的结果永远是引用,而decltype(variable)
结果只有当variable本身就是一个引用时才是引用。)
赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型,如果i是int,则表达式i=x的类型是int&。
类体右侧的表示结束的花括号后必须写一个分号,这是因为类体后面可以紧跟变量名以示对该类型对象的定义,所以分号必不可少(很多新手程序员经常忘了在类定义的最后加上分号)。分号表示声明符(通常为空)的结束。一般来说,最好不要把对象的定义和类的定义放在一起。
类的数据成员定义了类的对象的具体内容,每个对象有自己的一份数据成员拷贝。
C++11规定,可以为数据成员提供一个类内初始值。对类内初始值的限制与之前介绍的类似:或者放在花括号里,或者放在等号右边,记住不能使用圆括号(因为会和函数声明混淆)。
预处理变量无视C++语言中关于作用域的规则。