基本内置类型
- 赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数
举例:
8bit大小的unsigned char可以表示0 ~ 255一共256个数
把-1赋给unsigned char会得到255
运算方式为:
(-1) % 256
= (-1 + 256) % 256
= 255 % 256
= 255
- 给带符号类型超出它表示范围的值时,结果是未定义的
- 当一个算术表达式中既有无符号数又有int时,那个int会转换为无符号数
unsigned u = 10;
int i = -42;
cout << u + i << endl;
//将-42转换为无符号数,结果等于负数加上无符号数的模
//无符号数的模即无符号数的计量范围,如果机器int占32位,则是2的32次方
//2的32次方+(-42) = 4294967264
- 注意不要有这样的想法:反正不打算输出负数,就用无符号数来写循环
for (unsigned u = 10; u >= 0; --u)
cout << u << endl;
//当u变成0时,继续执行--u变为-1
//不满足无符号数要求,如果int占32位,将会转换为2的32次方+(-1)
//即死循环
- 形如42的值被称作字面值常量。严格来说十进制字面值不会是负数,-42这样的字面值,负号并不在字面值内,它的作用仅仅是对字面值取负值
变量
- 初始化和赋值是两个概念。初始化是创建变量时赋予其一个初始值,赋值是把对象当前的值擦除再用一个新的值替代
- 声明使得名字为程序所知,而定义负责创建与名字关联的实体。区别就是声明仅用来告诉编译器变量的名称和类型,而不分配内存。
- 如果想声明一个变量并且不定义它,就在变量名前加上extern,而且不能显式地初始化变量。声明extern表示i在别的文件中已经定义,提示编译器遇到此变量时在其它模块中寻找其定义。
extern int i; //声明i且没有定义
int j; //声明且定义
//extern语句包含初始值就不再是声明,而是定义了,如extern int i = 10;
- 变量只能定义一次,但能声明多次
- 新建局部变量能覆盖同名全局变量
复合类型
- 引用一旦初始化完成就和初始值对象一直绑定在一起。不能令引用重新绑定到另外一个对象,也因此引用必须被初始化
- 引用本身不是对象,所以不能定义引用的引用
- 引用只能绑定在对象上,不能与字面值或表达式的计算结果绑定
int &ref = 10; //出错
- 引用本身不是对象,所以不能定义指向引用的指针
- 两个指针存放的地址值相同,则它们相等
- void*是一种特殊的指针类型,可用于存放任意对象的地址。但是不能直接操作void/*指针所指对象
double obj = 3.14, *pd = &obj;
void *pv = &obj;
pv = pd;
- 指针是对象,所以存在对指针的引用
int *p;
int *&r = p; //r是对指针p的引用
//从右往左读,r首先是引用,*表示引用的类型是int *
const限定符
- const对象必须初始化
- const对象被设定为仅在文件内有效。当多个文件出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量
- 如果想在一个文件中定义const,而在其他多个文件中声明并使用它,可以对const变量(不管声明还是定义)都加上extern。
//文件1
extern const int bufsize = fcn();
//文件2
extern const int bufsize; //和文件1是同一个
- 引用类型必须与其所引用对象的类型一致,但是有两个例外。一是可以在初始化常量引用时允许用任意表达式作为初始值
int i = 42;
const int &r1 = i; //√ 可以将const int绑定到普通int对象上
const int &r2 = 42; //√
const int &r3 = r1 * 2; //√
int &r4 = r1 * 2 //× r4是普通的int,不能引用const
- 二是允许令一个指向常量的指针指向一个非常量对象
const double pi = 3.14;
double *ptr = π //错,ptr是普通指针
const double *cptr = π
double dval = 3.14;
cptr = &dval; //√
- 顶层const:本身是const;底层const:指向的是const
- int *const ptr 从右向左看,ptr先是const,说明不能修改自己,即不能修改指向的地址,但是可以修改地址内的值。因为自己是常量,所以是顶层const
- const int *ptr 从右向左看,ptr是int指针,指向的是const int,说明不能修改指向的值,但是可以修改地址。因为指向的是常量,所以是底层const
- 执行拷贝操作时,拷入拷出对象必须有相同的底层const资格,或者两个对象的数据类型必须能够转换
int i = 0;
int *const p1 = &i; //顶层
const int ci = 42; //顶层
const int *p2 = &ci; //底层
const int *const p3 = p2; //左边底层const,右边顶层const
const int &r = ci; //声明引用的const都是底层const
int *p = p3; //错误,p3包含底层const定义,p没有
p2 = p3; //p2 p3都是底层const
p2 = &i; //允许一个指向常量的指针指向非常量对象
int &r = ci //错误,普通int&不能绑定到int常量上
const int &r2 = i; //const int&可以绑定到普通int上
constexpr和常量表达式
- 常量表达式:1.值不会改变 2.编译过程就能得到计算结果的表达式
const int max_files = 20; //常量表达式
const int limit = max_files + 1; //常量表达式
int staff_size = 27; //非常量表达式,因为是普通int
const int sz = get_size(); //get_size的值要运行时才知道,违反第二条,所以不是常量表达式
- 声明constexpr的变量一定是常量,而且必须用常量表达式初始化
constexpr int mf = 20;
constexpr int limit = mf + 1;
constexpr int sz = size(); //size()是constexpr函数时才是正确的语句
- constexpr指针的初始值必须是nullptr或0,或者是存储于某个固定地址中的对象
- 函数体内定义的变量一般来说不存放在固定地址中,所以constexpr指针不能指向这样的变量。而定义于函数体外的对象地址固定不变,能用来初始化constexpr指针
- 在constexpr声明中如果定义一个指针,限定符constexpr仅对指针有效,和指针所指对象无关。换句话说constexpr把它定义的对象置为顶层const
const int *p = nullptr; //指向常量的指针
constexpr int *q = nullptr; //指向整数的常量指针
- 因为常量指针,所以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 \ using定义类型的别名
using SI = Sales_item;
typedef double wages;
typedef wages base, *p; //base是double同义词,p是double*同义词
//上句可以分开成两句
//typedef wages base;
//typedef wages *p;
//理解技巧:将typedef遮起来,可以看出就像在定义变量
//double wages; double base; double *p
//然后变量名就可以当作自己的类型的别名了
- 用auto能让编译器来分析表达式所属类型。auto必须有初始值\
- 因为一条语句只能由一个基本数据类型,所以语句中所有变量的初始基本数据类型必须一致
auto sz = 0, pi = 3.14;
//错误!sz和pi的类型不一致
- auto一般忽略顶层const而保留底层const
int i = 0;
const int ci = i, &cr = ci;
auto b = ci; //b是int,忽略了ci的顶层const
auto c = cr; //cr是int
auto d = &i; //d是int*
auto e = &ci; //e是const int*,对常量对象取地址是一种底层const
const auto f = ci; //明确指明f要是const int
- 设置类型为auto的引用时,初始值中的顶层const属性会保留
auto &g = ci; //g是const int&
auto &h = 42; //错误,不能为非常量引用绑定字面值
const auto &j = 42; //正确
- 切记符号&和*属于声明符,而不是基本数据类型的一部分
auto &m = ci, *p = &ci;
auto &n = i, *p2 = &ci; //错误
//n是int,p2是const int,矛盾了
- decltype返回操作数的数据类型
decltype(f()) sum = x; //sum类型就是f()返回类型
- 如果decltype使用的表达式是变量,则返回该变量的类型(包括顶层const和引用)
const int ci = 0, &cj = ci;
decltype(ci) x = 0; //x是const int
decltype(cj) y = x; //y是const int&
decltype(cj) z; //出错,z是const int&但是没有初始化
- 如果使用的表达式不是变量,则返回表达式结果对应的类型
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; //正确,加法的结果是int
decltype(*p) c; //错误,c是int&,必须初始化
- 如果给变量加上一层或多层括号,编译器会把它当作表达式。变量是可以作为赋值语句左值的特殊表达式,所以decltype会得到引用类型
decltype((i)) d; //错误,d是int&,要初始化
//decltype((variable))的结果永远是引用