2.1 基本内置类型
- wchar_t, char16_t, char32_t 属于算术类型,最小尺寸见30页
- long long 在C++ 11中新定义
- 大多数机器的字节(byte)由8比特(bit)构成,字(word)则由32或64比特(bit)构成,也就是4或者8字节,31页
- 类型选择几个建议:1明确数值不为负时选无符号类型(unsigned ___);
- 2 整数运算一般不用short而是int, 当数值范围超过int, 用long long;
- 3 算术表达式中尽量不用 char 或者bool , 因为char类型在某些机器上是有符号的,另一些机器是无符号的,容易出现差错;
- 4 浮点运算用double 少用float ,两者计算代价相差无几甚至有些机器double比float还快, long double一般不用因为精度需求没那么高
- unsigned char c = -1, 假设char 占 8 比特, c的值为255, (-1的补码表示为11111111,如果是无符号表示就是255)
- signed char c2 = 256, 假设char 占8比特,c2的值是未定义的 (256无法用8位表示) 33页
-
unsigned u =10; int i = -42; std::cout<<u+i<<std::endl;
- 上面代码输出的值,如果int占32位,结果为2^32-32;
- 如果两个字符串字面值位置紧邻且仅由空格、缩进和换行符分隔,则它们实际上是一个整体,因此可以分多行书写字符串字面值 36页
- 泛化的转义序列形式是\x后跟1个或多个十六进制数字,\后紧跟1个或2个或3个八进制数字(只会判断最多3位);
- 练习2.6 int month=09, 此处为无效的八进制数字
2.2 变量
- C++语言中用等号=来初始化和复制是两个完全不同的操作,尽管在很多编程语言里几乎可以忽略二者的区别;
- 列表初始化且初始值存在丢失信息的风险时,编译器将报错 40页:
int main() { long double ld = 3.141592654; int a{ ld }, //报错C2397 从“long double”转换到“int”需要收缩转换 b = { ld }; int c(ld), d = ld; cout << a << endl << b << endl << c << endl << d << endl; return 0; }
-
变量能且只能被定义一次,但是可以被多次声明,使用extern关键字声明一个变量而不显式去定义,在函数体内部,如果视图初始化一个有extern关键字标记的变量,将引发错误;
extern int global_int; int main() { global_int = 2; //LNK2001 无法解析的外部符号 "int global_int" std::cout << global_int << std::endl; return 0; }
如果要在多个文件中使用同一个变量,就必须将声明和定义分离,此时变量的定义必须出现在且只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义。 41页
-
C++用户自定义标识符中不能连续出现两个下划线、也不能以下划线紧连大写字母开头(这里我在VS2019试了一下貌似是可以的,不过还是尽量不要这么操作把,数字开头是会报错的) 47页
显式访问全局变量可以在前面加::作用域操作符,因为全局作用域本身没有名字,所以当作用域操作符的左侧为空时,向全局作用域发出请求获取作用域操作符右侧名字对应的变量;
2.3 复合类型
- 一条声明语句有一个基本数据类型(int, double, char等)和紧随其后的一个声明符列表(包括*,& 后跟变量名)组成,因此当声明引用或者声明指针的时候如 int *a 应该看做 int (*a) 而不是 (int*) a,这也能解释一些用逗号(,)连接的连续声明的语句,*和&可以看作声明符,a可以看作变量标识符,声明符+变量标识符构成了声明符列表,如下
int i = 1024 , i2=2048; //i 和 i2都是int int &r = i, r2=i2 // r是一个引用,与i绑定在一起, r2是int int i3 = 1024, &ri = i3 // i3是int, ri是一个引用,与i3绑定在一起
-
像* 和 &这样的符号,既能用作表达式里的运算符,也能作为声明的一部分出现,意义由符号的上下文决定;
-
nullptr 是C++11新标准引入的方法以前的空指针声明有两种方式,现在加入nullptr之后有三种:
int *p1 = nullptr; int *p2 = 0; //此处的0是字面常量,不能用值为0的int类型代替 int *p3 =NULL;
在可能的情况下,尽量初始化所有的指针,如果不清楚指向何处,就初始化为nullptr或者0, 49页
-
任何非零指针对应的bool条件值都是true;
-
引用本身不是一个对象,因此不能定义指向引用的指针,但指针是对象,所以存在对指针的引用
-
面对一条比较复杂的指针或者引用的声明语句时,从右向左阅读有助于弄清楚它的真是含义,如下代码
int i = 42; int *p; //p是int型指针 int *&r = p; //r是引用,引用的是int型的指针 r = &i; //把i的地址赋给p *r =0 ; //p指向的值改为0
从右向左看,离r最近的是&,说明r是一个引用,其余部分用于确定引用的类型是什么
2.4 const限定符
- const声明的对象与非const限定符声明的对象的限制主要在于不能执行改变其内容的操作, const int 和普通的int一样都能参与算术运算、也都能转换成一个bool值;还有一种是初始化,可以用const int 初始化 int 也可以用int 初始化 const int;
- 引用的类型必须与其所引用对象的类型一致,有两个例外,第一个是初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可
int i= 42; const int &r1 = i; //允许将const int&绑定在一个普通int对象上 const int &r2 = 42; //正确,r1是一个常量引用 const int &r3 = r1 * 2 //正确, r3是一个常量引用 int &r4 = r1 * 2 //错误,r4是一个普通的非常量引用,只能引用 int型变量 double dval = 3.14 const int &ri = dval; //正确, double型的dval 变量可以转换成int型 //实际上 上面两行的过程是这样的 const int temp = dval; const int &ri = temp;
虽然const int &可以引用double型变量,但是还是会得到一个警告,
int main() { int i = 3; double dval = 3.14; const int& ri = i; int& ri2 = i; //ri = 4; std::cout << ri << std::endl; //输出4 return 0; }
此处可以用ri2来改变i的值,不能通过改ri来改i的值,因此const int&声明的变量不能作为=赋值运算符的左值;
-
指向常量的指针不能用于改变所指对象的值,要想存放常量对象的地址,只能使用指向常量的指针(即指针不能作为左值来更改其所指对象的值) 详见56页;
-
和常量引用一样,指向常数的指针也没有规定其所指的对象必须是一个常量,所谓指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变
double pi = 3.14159; double dval = 3.14; const double *cptr = &dval ; *cptr = 42; //错误,不能通过*cptr改变dval的值 cptr = pi //正确, cptr可以指向别处,但是不能通过*cptr改变别处的值
这里的理解是一个难点,详见56页
-
顶层const与底层const的区别在于,顶层const表示该const修饰的对象是一个常量,不可更改,底层const表示指针所指的对象为常量,因此底层const只在指针*和引用&中存在,
int i =0; int *const p1= &i; //不能改变p1的值,顶层const const int ci = 42; //不能改变ci的值,顶层const const int *p2 = &ci; //可以改变p2的值,不能改变*p2的值,底层const const int *const p3 = p2; //靠右的是顶层const, 靠左的是底层const const int &r = ci; //所有声明引用的const都是底层const,因为引用不是对象,不存在指针的多种情况
-
如果认定变量是一个常量表达式,就把它生命诚constexpr类型(C++11新特性)
-
尽管指针和引用都能定义成constexpr,但他们的初始值受到严格限制。一个constexpr指针的初始值必须是nullptr或者0,或者是储存于某个固定地址中的对象;
-
constexpr把它所定义的对象置为了顶层const,如下代码可以用来区分
const int *p = nullptr; //p是一个指向整型常量的指针 constexpr int *q = nullptr; //q是一个指向整数的常量指针
2.5 处理类型
-
类型别名两种定义方法,一种是使用关键字typedef,
typedef double wages; //wages是double的同义词 typedef wages base, *p //base是double的同义词, p是double*的同义词 //typedef语句和前面的声明语句一样,后面跟的声明符可以包含类型修饰(如*,&)
另一种C++11新标准是使用using
using SI = Sales_item; // SI是Sales_item的同义词,等价于 typedef Sales_item SI;
-
当用别名指代某个复合类型或常量时,容易产生意想不到的结果,理解上应该弄清楚,下例:
typedef char *pstring; const pstring cstr = 0 ; // cstr是指向char类型的常量指针,不能改cstr值,即cstr不能为左值,此处的const为顶层const const char *cstr = 0; // cstr是指向const char类型的指针, 不能通过*cstr改值, 即*cstr不能为左值,此处的const为底层const
详见61页
-
auto 类型说明符(C++11新特性)让编译器通过初始值来推算变量的类型,因此auto定义的变量必须有初始值;
-
auto可以在一条语句中声明多个变量,但是所有变量的初始数据类型必须一样,尽量不要在一条语句中声明;
-
decltype 和auto的区别,详见63页,有一种情况需要注意就是decltype((variable))双层括号的结果永远是引用,而decltype(variable)的结果只有当variable本身是引用时才是引用;因为(variable)可以看作一个赋值表达式,而赋值表达式的类型就是引用;
-
decltype()表达式内容是解引用操作,也将得到引用类型,如decltype(*p)得到的是p指针所指对象的引用;
-
练习2.38举例表示一种decltype和auto指定类型不一样
int c = 1; const int ci = 0, & cj = ci; auto ck1 = cj; // ck1为 int, 可以成为左值赋值 decltype(cj) ck2 = c; // ck2为 const int& ,不可以成为左值赋值 ck1 = 2; // 正确 ck2 = 3; // 错误,ck2无法赋值
2.6 自定义数据结构
-
类体右侧表示结束的花括号后必须写一个分号,这是因为类体后面可以紧跟变量名以示对该类型对象的定义,所以分号必不可少;分号表示声明符的结束。
-
预处理器(preprocessor)是在编译之前执行的一段程序,可以部分地改变我们写的程序,#include就是意向预处理功能;
-
写头文件时要设置保护符,#ifdef, #define, #ifndef, #endif