文章目录
有符号和无符号类型注意事项
- 当我们赋给一个无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的值。例如8bit大小的unsigned char可以表示0到255区间内的值,如果我们赋了一个区间外的值,则实际的结果是该值对256取模后的余数。因此,把-1赋给8bit的unsigned char所得的结果为255。
- 当我们赋给带符号类型一个超出它表示范围的值时,结果是未定义的。此时,程序可能继续工作,可以崩溃,也可以产生垃圾数据。
- 切勿混淆有符号类型和无符号类型。如果表达式中既有有符号又有无符号类型,当有符号类型取值为负时会出现异常结果。这是因为带符号数会自动转换为无符号数。
cpp
int a = -1;
unsigned b = -1;
std::cout << a * b << std::endl; // b->2**32 - 1
- 字符型划分为三种:char、signed char、unsigned char。值得注意的是,前两种类型并不一样。char类型会根据编译器的表现为signed活unsigned。
变量
变量提供一个具名的、可供程序操作的存储空间。对于C++程序员来说,变量和对象一般可以互换使用。
初始化
cpp
int units_sold = 0;
int units_sold = {0};
int units_sold{0};
作为C++11中的新标准,用花括号初始化得到了全面的应用。这种初始化形式被称为列表初始化。现在不论是初始化对象还是某些时候为对象赋值,都可以使用这样一组由花括号括起来的初始值了。
默认初始化
如果定义变量时没有指定初始值,那么会被默认初始化。此时初始值由变量类型和定义变量的位置决定。
- 内置类型:定义在全局区的内置类型被初始化为0;定义在函数内部的内置类型不被初始化。一个未被初始化的内置类型变量的值是未定义的,如果尝试拷贝或其他形式访问该值将引发错误。
- 每个类各自决定其初始化对象的方式。是否允许不经初始化就定义该对象也有类自己决定。
复合类型
复合类型是指基于其他类型定义的类型,例如引用和指针。
引用
引用并非对象,它只是一个已经存在的对象的别名。引用必须使用已存在的对象进行初始化。
cpp
// 定义多个引用
int a = 0, b = 1;
int &aa = a, bb = b; // aa为a的引用,bb只是初始化值和b一样的普通变量
// const 引用
const int &cc = a;
// cc无法修改,修改a之后,cc也会随着变化
// a = 3;
// cc也会变成3
指针
指针无需在定义时初始化,和内置类型初始化一样,若在块作用域内(局部)定义,则也将拥有一个不确定的值。
指向指针的引用
引用本身不是一个对象,因此不能定义指向引用的指针。但是指针是一个对象,因此可以定义指向指针的引用:
cpp
int i = 42;
int *p;
int *&r = p;
r = &i; // r可以影响p指向的地址
*r = 0; // r可以影响*p指向的内容
const 限定符
cpp
// 定义在全局区的变量,默认为extern,访问范围为整个程序
int everywhere = 0;
// 定义在全局区的const变量,访问区域为当前文件
const int localFile1 = 1;
const int localFile2 = everywhere;// 可直接使用右值初始化也可使用已初始化的变量初始化。
// 定义在全局区域的const变量也显示通过extern将其作用区域拓展到整个程序
extern const int everywhereConst = 2;
顶层const
指针本身是一个对象,它又可以指向另外一个对象。因此,指针本身是不是常量和指针所指向的内容是不是常量是两个相互独立的问题。顶层const指的是指针本身是一个常量,底层const则是指针指向的内容是一个常量。
顶层const的拷贝没什么值得注意的地方。
底层const在执行拷贝时有一定的限制。拷入和拷出的必须具有相同的底层const资格,或者两个对象的数据类型能够相互转换。一般来说非常量可以转换为常量,反之则不行。
cpp
int i = 0;
int *const p1 = &i; // 顶层
const int ci = 42; // 顶层
const int *p2 = &ci; // 底层
const int *const p3 = p2; // 底层和顶层
const int &r = ci; // 用于声明引用的const都是底层const
i = ci; // ok,顶层无需注意
p2 = p3; // ok,具有相同的底层const
int *p = p3; // error,p无底层
p2 = &i; // ok,int* 可以转换为const int*
int &r = ci; // error,普通引用不能绑定常量
const int &r2 = i; // ok,const int&可绑定普通int
常量表达式
常量表达式是指值不会改变并且在编译时就能得到计算结果的表达式。一个对象或表达式是不是常量表达式由它的数据类型和初始值决定:
cpp
const int max_files = 20; // 1
const int limit = max_files + 1; // 1
int staff_size = 27; // 0
const int sz = get_size(); // 0
sz的本身虽然是一个常量,但是其值却需要在运行时才能获取,因此不是常量表达式。
constexpr
C++11中,允许将变量声明为constexpr类型以便编译器来验证该变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须使用常量表达式初始化。
cpp
constexpr int mf = 20;
constexpr int limit = mf + 1;
constexpr int sz = size(); // size是一个constexpr时才是正确的
只有内置类型、引用、指针可以用constexpr修饰。constexpr指针的初始值必须是0或者nullptr,或者存储在固定地址的某个对象(全局对象)。还有一点值得注意,cosntexpr仅对指针有效,与指针指向的内容无关:
cpp
const int *p = nullptr; // 指向内容不变
constexpr int *p = nullptr; // 指向地址不变
typedef
定义类型的同义词,通常被用以以下目的
- 隐藏特定类型的实现,强调使用的目的
- 简化复杂的类型定义,使其更易理解
- 允许同一类型用以多个目的,使得每次使用的目的更加明确等等
处理类型
auto
auto一般会忽略顶层const,保留底层const:
cpp
int i = 0;
const int ci = i, &cr = ci;
auto b = ci; // int,忽略顶层
auto b = cr; // int,引用以引用对象的类型作为推导
auto b = &cr; // const int*,常量对象取地址是底层const
// 如果希望推断出顶层const,需要明确指出
const auto b = ci;
decltype
decltype可以返回表达式的类型,编译器分析表达式并得到他的类型,却不实际计算表达式的值。
cpp
decltype(f()) sum = x;
decltype处理顶层const和引用的方式和auto不同,其可完整范围操作数的类型,包括顶层const:
cpp
const int ci = 0, &cj = ci;
decltype(ci) x = 0; // const int
decltype(cj) y = x; // const int&
decltype(cj) z; // err,z是一个引用,必须初始化
值得注意的是,如果表达式的内容是解引用,则decltype将得到引用类型:
cpp
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // int,r是一个引用,r + 0的值是int
decltype(*p) c; // int&
如果给变量在加一个括号,得到也是引用
cpp
int i;
decltype(i) a; // int
decltype((i)) b; // int&
枚举
cpp
// 枚举成员值可以是不唯一的,point2和point3的值都是2
// 枚举类型名可以省略
enum Points{point1 = 1, point2, point3 = 2, point4};
设计自己的头文件
cpp
// 头文件用于声明而不是定义
// 因为头文件包含在多个源文件中,所以不应该含有变量或者函数的定义
extern int ival = 10; // 虽声明为extern,但是有初始化式,因此是定义
double fica_rate; // 虽然没有初始化式,但是没有extern,因此也是定义
// 头文件中可以包含三种定义,这些实体可在多个源文件中定义,只要每个源文件中的定义是相同的
// 1、定义类;
// 2、在编译就知道的const对象;
// 3、inline函数
![](https://i-blog.csdnimg.cn/direct/0b94dd95835a41d59b7f35bd7c981bc4.png)