02 变量和基本类型

数据的类型定义了数据的两个方面

  1. 定义了数据元素的内容。数据类型决定了数据在内存中占的比特数,以及如何解释这些比特位的含义。

  2. 定义了这类数据上能进行的操作

对象类型决定了改对象支持的操作。一条表达式是否合法取决于其中参与运算的对象的类型。在python这类动态语言中,类型在运行时进行检查。而c++这样的静态类型的语言,类型的检查在编译时进行。

2.1 基本内置类型

分类

  • 算数类型 arithmetic type

    • 整型,包括整数、字符和布尔类型在内

    • 浮点型,float和double分别由7和16个有效位

  • 空类型 void

字符类型中的char16_t, char32_t为Unicode字符集服务。

不要把无符号类型和带符号类型的变量一起运算。带符号数会自动转换成无符号数,带来不可知的结果。

字面值常量 literal

字面值常量的形式决定了其数据类型。

整型和浮点型字面值

整数0开头为八进制,0x或0X开头为16进制。short没有对应的字面值。浮点数的字面值默认是double类型。

字符和字符串字面值

字符串字面值是常量字符构成的数组。编译器会在每个字符串结尾节加空字符\0

如果两个字符串字面值相邻或仅由空格缩进换行符分割,则它们实际上是一个整体。

转义序列

\后加数字的话,可以跟1-3个八进制数,或者\x后跟1到多个16进制数。

指定字面值的类型

字面值可以通过加前缀或后缀来指定类型

字符和字符串字面值的前缀

  • u char16_t

  • U char32_t

  • L wchar_t

  • u8 char

整型字面值的后缀

  • u or U, unsigned

  • l or L, long

  • ll or LL, long long

浮点型字面值

  • f or F, float

  • l or L, long double

2.2 变量

变量提供了一个具名的、可供程序操作的存储空间。变量的数据类型决定了

  • 变量所占内存空间的大小和布局方式

  • 该空间能存储的值的范围

  • 变量能参与的运算

c++中变量和对象可以互换使用。

变量定义

初始化和赋值是完全两个不同的操作

  • 初始化:创建变量时赋予其一个初始值

  • 赋值:把对象的当前值擦除,写入一个新的值。

c++11中列表初始化得到了全面的应用。使用初始化列表进行初始化的值如果存在信息丢失的风险,则编译器会报错。

int main()
{
long double ld = 3.1415926;
int a{ld}; // a和b是列表初始化的两种写法,这里都会报错
int b = {ld};
int c(ld);
int d = ld;
return 0;
}

如果不指定值,则会进行默认初始化。在函数外面,默认的初始值是0。在函数内,则不定。

变量声明和定义的关系

c++支持分离式编译。cout, cin就是在标准库中定义的变量,我们的程序能够使用。为支持分离式编译,c++将声明和定义分开。声明值规定了变量名和类型,而定义还要分配存储空间。

如果想声明一个变量,用extern关键字。

extern int j; // 声明j
int i; // 声明并定义i
extern int k = 3; // 声明并定义k,赋值会低消extern

如果要在多个文件中使用同一个变量,就必须将声明和定义分离。变量的定义只能出现在一个文件中。在其他用到这个变量的地方必须进行声明,但是不能重复定义。

作用域

全局作用域没有名字,如果作用域操作符左边空着,表示使用全局作用域。

int reused = 42;
int main()
{
int unique = 0;
std::cout << reused << " " << unique << std::endl; // 42 0
int reused = 0;
std::cout << reused << " " << unique << std::endl; // 0 0
std::cout << ::reused << " " << unique << std::endl; // 42 0
return 0;
}

2.3 复合类型

这里介绍两种:引用、指针

引用

这里讲的引用其实是左值引用(lvalue reference)

  • 引用初始化后和对象绑定在一起,不能被修改。因此引用必须被初始化。

  • 引用本身不是对象,不能定义引用的引用。

  • 引用只能绑定在对象上,不能和字面值或表达式的计算结果绑定在一起

int &a = 1; // a和b这两种写法都是错的
int x = 1, y = 1;
int &b = x + y;

指针

  • 指针本身是对象,有地址,可以定义指针的指针

  • 引用不是对象,没有地址,因此也不能定义指向引用的指针

注意在声明中,&*用于组成引用和指针复合类型。而在表达式中,&是取地址运算符,*是解引用运算符。

三种生成空指针的方法:

int *p1 = nullptr; // 推荐使用这种方法
int *p2 = 0;
int *p3 = NULL; // NULL是预处理变量,不属于命名空间std。

非0指针bool值为true。否则为false。在比较两个指针时,存放的地址相同则相等,反之不相等。

void*指针能存放任意类型的对象的地址。但不能通过void*指针去访问对象。因为不能确定对象的类型,也就不能确定对象能参与那些操作。

int x = 1;
void *p1 = &x;
std::cout << *p1 << std::endl;

理解复合类型的声明

如前所述,不存在引用和引用指向引用的指针。但是我们可以声明指针的引用

int i = 42;
int *p; // p是一个int型指针
int *&r = p; // r是对指针p的引用。从右向左读,r首先是个引用,然后是个指针类型的引用
r = &i; // 让r指向i,实际上p也指向了i
*r = 0; // 这时i的值是0

2.4 const限定符

const对象一旦创建后就不能修改,所以必须初始化。

指针和const

  • 顶层const - 指针本身是常量int *const p

  • 底层const - 指针指向的对象是常量const int *p

对常量的引用

引用只有底层引用,即对常量的引用。

不管是指针还是引用,如果对象本身是常量,则必须用底层指针底层引用。反过来,底层指针底层引用可以绑定到非常量对象上。

constexpr和常量表达式

  • 常量表达式(const expression):1.类型是const。2.在编译中计算到结果。

有些时候我们不确定一个表达式是否能在编译中就计算出结果。c++11通过声明为constexpr来强制编译器检查。

constexpr int a = get_size(); // 如果get_size()不能再编译期间就得到结果的话,则会报错

2.5 处理类型

类题别名

传统方法是用typedef

typedef double wages, *p; // wages是double的同义词, p是double*的同义词

新标准中可以用using

using SI = Sales_item;

decltype类型指示符

编译器分析表达式的类型,但并不实际计算表达式

decltype(f()) sum = x; // sum的类型就是函数f返回值的类型。函数f并不会并真的调用