Skip to content

Latest commit

 

History

History
625 lines (419 loc) · 28.7 KB

Chapter1.md

File metadata and controls

625 lines (419 loc) · 28.7 KB

Chapter1

第一章 变量和基本类型

数据类型

数据类型是程序的基础,它告诉我们数据的意义以及我们能在数据上执行的操作

基本内置类型

C++中定义了一套包含 算术类型(arithmetic type)和空类型(void) 在内的基本数据类型。其中算术类型包含了 字符(char)、整数形(int)、布尔值(bool)和浮点数(double、float)。空类型不对应具体的值,经常用于函数的返回值。

C++算术类型如下表

类型 含义 最小尺寸
bool 布尔类型 未定义
char 字符 8位
wchar_t 宽字符 16位
char16_t Unicode字符 16位
char32_t Unicode字符 32位
short 短整形 16位
int 整形 16位
long 长整形 32位
long long(C++11) 长整形 64位
float 单精度浮点数 6位有效数字
double 双精度浮点数 10位有效数字
long double 扩展精度浮点数 10位有效数字

对于整形来说,即 short、int、long、long long 可以分为带符号(signed)--表示正数、负数、0不带符号(unsigned)--仅仅表示大于0的值两种。整形都是带符号的。在整形前面使用关键字 unsigned 就变成了无符号类型。例如 unsigned int

对于字符型来说,字符型 char 被分为了三种: charsigned charunsigned char。注意:charsigned char 不是同一种类型。虽然字符有三种类型,但是表现形式只有两种:带符号和不带符号, 至于 char 会表现为那种形式,具体有编译器决定。

值在C++中是一个纯粹的数学抽象概念。在程序中作为一种只读数据存在。

字面值常量(literal)

顾名思义,字面值常量就是值一见便知。字面值常量的形式和值决定了它的数据类型。 字面值常量又分为整形和浮点型字面值、字符和字符串字面值、转义序列、布尔字面值和指针字面值。

整形和浮点型字面值

整形字面值的具体数据类型由它的符号决定。默认情况下,十进制字面值是带符号数。尽管整形字面值存储在带符号数据类型中,但严格来说,十进制字面值不会是负数,例如:当我们使用一个形如 -42 的负十进制字面值,那个负号并不在字面值之内,它的作用仅仅是对字面值取负而已。

浮点数字面值表现为一个小数科学计数法表示的指数。例如:3.141594e-10

字符和字符串字面值

单引号括起来的一个字符称为char型字面值;由双引号括起来的零个或多个字符则构成字符串型字面值。例如:

'a' // 字符字面值
"F U C++!"  // 字符串字面值 

字符串字面值的类型其实是由常量字符构成的数组(array),(见TODD)。编译器将会在每个字符串的结尾处添加一个空字符('\0'),因此字符串字面值的实际长度要比内容多1

转义序列

有两类字符程序员不能使用,一类是不可打印(nonprintable),例如退格(删除)和其他控制字符(上下左右)。另一类是在C++中有特殊含义的字符,这个时候就需要转义序列。转义序列均以反斜杠\开始。C++规定的转义序列如下表:

转义序列 含义
\n 换行
\t 横向制表符
\a 宽字符
\v 纵向制表符
\b 退格符
\" 双引号
\' 单引号
\ 反斜线
\r 回车符
\f 进纸符

布尔字面值和指针字面值

布尔字面值为 truefalse。 指针字面值(见TODO)

变量

变量(variable),在C++中指的是一块内存空间,是与计算机内存这个物理设备关联在一起的。(故,需要区分数学上抽象的变量的概念)。在本笔记中,变量(以及后面的对象)均指具有某种数据类型的内存空间

初始化

在定义(创建)一个变量(或对象)的时候,获得了一个特定的值,我们就说这个变量(或对象)被 初始化(initialized) 了。例如:

int x = 10; // 初始化整形变量x的值为10.

在C++中,初始化和赋值是两个完全不同的操作。二者在概念上严格区分初始化:在定义(创建)变量(对象)的时候赋予其一个初值 赋值:把变量(对象)的当前值擦除,用一个新的值来代替 例如:

int x = 10; // 初始化整形变量x的值为10.
x = 20;     // 赋值,将x的当前值10擦除,用一个新值20代替.

以上概念需要严格区分!!

C++中定义初始化的好几种不同形式。

列表初始化

以下4中方式均能初始化整形变量x,而初始化列表是C++11引入的新标准,即用花括号去初始化变量

int x = 0;
int x = {0};  // 列表初始化
int x{0};     // 列表初始化
int x(0);

关于列表初始化(list initialization),在类以及后面的vwctor内容均会看到。这里只做一个说明。 需要说明的是,在使用列表初始化的时候,且存在 初始值丢失的风险(类型转换) 的时候,编译器将会报错,例如:

long double pi = 3.1415926536;
int a{pi}, b = {pi}; // 错误,转换未执行,存在丢失信息的危险.
int c(ld), d = ld;   // 正确,转换执行,且确实丢失了部分值.

默认初始化

所谓默认初始化(default initialized),指的是定义(创建)变量(或对象)的时候,没有指定初始值,则变量就会被默认初始化。 对于内置类型的变量没有被显式初始化,它的值由定义(创建)它的位置决定。如果在任何函数之外的变量将会被初始化0。在函数体内部的内置类型将不被初始化。如果试图拷贝或者以其他形式访问,将会报错。 每个类各自决定其初始化对象的方式。而且,是否不经初始化就定义对象也由类自己决定。

总结:

  1. 定义于函数体内部的内置类型的对象(或变量)没有初始化,则其值未定义。
  2. 类的对象如果没有显式初始化,则其值由类确定。

使用未被初始化的变量将会带来无法预计的后果!!!!

变量声明和定义的关系

为了允许把程序拆分成多个逻辑部分来编写,C++支持分离式编译(separate compilation),该机制允许将程序分割为若干个文件,每个文件可以被独立编译。 为了支持这种机制,C++将声明和定义区分开来。声明(declaration)使得名字为程序所知道,如果A文件想要使用B文件中的变量,则必须包含对那个名字的声明。而定义(declaration)则定义(创建)与名字相关联的实体,即内存空间。 定义和初始化的区别在于:初始化会为变量赋予一个初值。 要想声明一个变量而非定义它,在变量名前面添加关键字extern,且不要显示初始化。 例如:

extern int x; // 声明整形变量x而并非定义x.
int y;        // 定义整形变量y. 
int z = 0;    // 初始化整形变量z为0.
z = 10;       // 赋值.
extern double pi = 3.14159; //定义,抵消 extern 的作用

如果在函数体内部试图初始化一个由 extern关键字标记的变量,将会引发错误。

标识符

C++的标识符(identifier),说白了就是变量、函数、类等的名称。这里参考 谷歌开源代码风格之命名规范

标识符或者说命名,一定要规范!!!!

作用域

在C++程序中,无论是在程序的什么位置,使用到的每一个名字(标识符)都会指向一个特定的实体:变量、函数、类型等。然而,同一个名字出现在不同的位置,也可能指向的是不同的实体

名字的作用域

名字(标识符)的作用域(scope)是程序的一部分,在作用域内的名字有其特定的含义,C++中大多数作用域均用花括号分割。

全局作用域

全局作用域(global scope),位于其他所有作用域之外的作用域。全局作用域天然存在于C++程序中,它不需要由程序员人为地定义。

块作用域

块作用域(block scope),又称局部作用域(local scope)。

关于全局变量和局部变量以及变量的生存周期,见(TODO)。

嵌套的作用域

作用域能包含彼此,被包含(被嵌套)的作用域称为内层作用域(inner scope)包含别的作用域的作用域称为外层作用域(outer scope)

在作用域中声明某个名字, 它所嵌套着的所有作用域中都能访问。同时也允许 在内层作用域中重新定义外层作用域已有的名字。例如:code/chapter1/demo1.cpp

/* 该程序仅仅做说明
 * 同时需要注意:函数内部不宜定义与全局变量相同的新变量
 */
#include <iostream>

int reused = 42;  // 全局变量
int main() 
{
  int unique = 0;  // 变量 unique 具有块作用域

  // 第一次输出:全局变量和局部变量
  std::cout << reused << " " << unique << std::endl;

  // 定义并初始化一个新的局部变量,该变量与全局变量同名,将会覆盖(隐藏)全局变量
  int reused = 0;  // 局部变量 reused 具有块作用域

  // 第二次输出:两个局部变量
  std::cout << reused << " " << unique << std::endl;

  // 第三次输出:显示访问全局变量
  std::cout << ::reused << " " << unique << std::endl;

  return 0;
}

上述代码中,第24行的语句 int reused = 0,隐藏了外层作用域中的 reused 变量,因此将会输出 0。此时如果想要访问外层作用域中的 reused 变量,需要加上 域操作符 ::reused

需要说明的是:如果函数有可能用到全局变量,则不应该再定义一个同名的局部变量,因为 会覆盖(隐藏)外层作用域同名变量。因此在定义全局的变量的时候,根据谷歌命名规范,通常在变量名前面添加一个前缀 g ,即 g_reused 表示变量 g_reused 是一个全局变量。

复合类型

所谓复合类型(compound type),指的是 基于其他类型定义 的类型。概念中的其他,可以指基本类型,也可以指自定义类型。(即:套娃)。可以这样去理解(数学角度):如果说基本类型是一个单一函数,那么复合类型就是一个复合函数。(可能不是很恰当)。

C++中有几种复合类型,这里介绍 引用指针

引用

在C++11中引入了 “右值引用”(rvalue reference) 的概念,这个主要用于内置类。
严格来说,当我们说引用(reference)一词的时候,都是 "左值引用"(lvaue reference)

所谓 引用(reference),指的是给对象(变量)起了另外一种名字。(可以理解为引用就是家里人给你取的小名或者外号)。通过符号 & 来声明或者定义一个 引用类型。例如:

int value = 24;
int &refvalue = value;  // 引用。变量 refvalue 指向 value.(换句话说:value的小名叫refvalue)

关于引用,需要注意以下几点:

  1. 引用 必须初始化

    int x = 10; //初始化变量
    int &refx = x; // 引用必须初始化
    int &another_ref_y; // 错误,没有初始化
    

    前面已经强调了:C++中 初始化 的概念。这里再次强调:所谓初始化,指的是在定义(创建)一个变量的时候,赋予一个初始值。

    对于引用来说,在定义的时候会将引用和它的初始值 绑定(bind)在一起。而不是将初始值拷贝给引用。

    一旦引用初始化完成,将和它的 初始值对象一直绑定在一起。所以无法再让引用重新绑定到另一个对象,因此引用必须初始化。(这个就叫专一)

  2. 引用即别名 引用 不是对象(变量),如果不对其初始化,是 没有地址 的。相反的,它只是为一个已经存在的对象(变量)所起的另一个名字。当我们定义了一个引用后,对其进行的所有操作,都是在 与之绑定的对象上 进行的。例如:

    int x = 0;
    int &ref_x = x;
    ref_x = 2; // 把2赋值给ref_x指向的对象,此处是指x. 等价于 x = 2;
    

    为引用赋值,实际上是把值赋给了与 引用绑定的对象;获取引用的值,实际上是获取 与之绑定的对象 的值。

    引用一旦完成初始化,其 地址就和与之绑定的对象地址一致 。例如:

    #include <iostream>
    
    int main()
    {
      int x = 0;
      int &ref_x = x;
      std::cout << &x << " " << &ref_x << std::endl;
      return 0;
    }
    

    输出为:

    0x7ffe9ac7a84c 0x7ffe9ac7a84c
    

    这也就解释了,为什么引用是别名。因为从内存层面上来说,引用和与之绑定的对象都是在同一块内存下。

  3. 因为引用本身不是一个对象,故 不能定义引用的引用。例如:

    int x = 0;
    int& &ref_x = x; //错误,不能定义引用的引用.
    
  4. 引用 只能绑定到对象(变量)上,不能绑定到字面值或者表达式的结果上 !(相关原因见TODO)。

    int &x = 10; // 错误
    int &x = 1 + 1;  // 错误
    
  5. 引用类型必须要与之绑定的对象类型严格匹配。例如:

    int x = 10;
    double &ref_x = x;  // 错误,double类型的引用不能绑定到int类型上
    
  6. 在写代码的过程中,可能你会看到下面两种关于引用(指针)的定义。

    int& x = value;
    int &x = value;
    
    int* ptr;
    int *ptr;
    

    这两种方式表示的都是引用,只是编程习惯不一样罢了。我的理解是(可能不准确):int& x,强调的是本次声明定义了一种符合类型; int &x,强调的是变量具有符合类型,

    我个人更加喜欢后者,不会让人产生误解。(仁者见仁智者见智)例如:

    // 采用第一种方式
    int* p1, p2;
    

    请问上述语句中,p1是什么类型,p2又是什么类型?

    很多人可能会说,p1p2都是int类型的指针。其实不然,因为 * 只修饰了p1,并未修饰p2,两个变量的基本类型都是int,因此上述语句中,p1int类型的指针变量,而p2只是普通的int类型变量,如果此时对p2进行赋值操作,例如:p2 = &x; 就会报错。如果要想p2也变成指针,那么就该做如下定义:

    int* p1;
    int* p2;
    

    将二者分开单独声明。

    如果采用第二种方式,就是这样 int *p1, *p2;, 表明 p1, p2 都是 int 类型的指针。

    上述两种声明方式没有对错,只是侧重不一样, 但是一定要坚持一种方式,不要变来变去!

指针

指针(poniter),是一种 “指向(point to)” 另外一种类型的复合类型。根据定义:我们可以说,指针是一种复合类型,一种什么样的复合类型呢?一种 指向类型 的复合类型。

定义指针,需要在对象(变量)名之前使用符号 *,例如:

int *ptr;  // 定义一个指针变量 ptr

关于指针,需要说明以下几点:

  1. 指针是一个对象,允许对其赋值和拷贝,而且在指针的生命周期内可以先后指向不同的对象。
  2. 指针无需在定义的时候赋予初值,同其他内置类型一样,在 块作用域 内定义指针没有被初始化,其值不确定。 以上两点是指针与引用不同的地方
  3. 指针的类型要和它所指向的对象(变量)严格匹配

获取对象(变量)的地址

指针 存放的是某个对象(变量)的地址,要想获取该地址,则需要用 取地址符&。例如:

int val = 42;
int *ptr = &val; //指针变量初始化,将val的地址当成初始值给p.故 p是指向变量val的指针.

由于引用不是对象,没有实际地址,故 不能定义指向引用 的指针,即 int& *ptr = x; //错误。但是如果引用被初始化,就可以定义指向引用的指针。例如:

int x = 10;
int &ref_x = x;  //引用初始化
int *ptr = &ref_x;  //引用初始化后,可以定义指向引用的指针
int& *ptr = &x;  // 错误,不能定义指向引用的指针。即:不能定义引用类型的指针。

指针值

在说指针的值之前,首先说,指针是什么,指针是一个存储对象(变量)地址的变量,那么也就是说指针的值,就是地址。总而言之:指针变量就是用来存储地址的, 指针变量的值就只能存储地址

指针的值(即,地址) 有且只有 以下4种状态之一:

  1. 指向一个对象(某一个对象或变量的地址)
  2. 指向紧邻对象所占空间的下一个位置(TODO,尚未理解)
  3. 空指针,意味着该指针没有指向任何对象
  4. 无效指针,也就是上述情况之外的其他值

试图访问或者拷贝无效指针的值,都将引发错误,而编译器将不负责检查此类错误。

当指针没有指向任何对象(变量)的时候,任何对该指针的访问和操作都将引发不可预知的后果。

利用指针访问对象

如果一个指针指向了一个对象,那么可以用 解引用符* 来访问该对象。对指针解引用将会得出所指的对象(变量)。因此,如果给解引用的结果赋值,实际上也就是给指针所指向的对象(变量)赋值。例如:

#include <iostream>

int main() {
  int x = 10;
  int value = 0;
  int *ptr = &x;     // 指针变量ptr的初始化
  value = *ptr;  // 解引用,将指针ptr所指向的对象赋值给value
  std::cout << value << std::endl;  // 输出 10;
}

同时,对 *ptr的操作,都会影响到其指向的对象(变量) x。例如:*ptr = 100; std::cout << x << std::endl; 将会输出100

解引用操作只适用于那些确实指向了某个对象(变量)的有效指针

符号的多义性

从前面的内容来看,在C++语言中,某些符号在不同的语境下,有着不同的含义,编译器会自动根据上下文来确定符号的具体含义。例如:

int i =42;
int & r = i;     // 引用,&跟随类型int出现
if(x&y)         // &出现在语句中,故为“与”运算符
int *ptr = &i;  // *跟着类型int出现,故为指针; &出现在表达式中,故为取地址
*ptr = 996;     // *出现在表达式中,故为解引用
int &f = *ptr   // &为引用,*为解引用

空指针

空指针(null pointer),一个 不指向任何对象的指针。在试图使用一个指针之前,可以用其检查该指针是否为空。

下面的几个方法都可以创建空指针:(重要)

int *ptr1 = nullptr;  // 等价于 int* ptr1 = 0;
int *ptr2 = 0;        // 直接将指针ptr2初始化为字面常量0

// 需要 #include <cstdlib>
int *ptr3 = NULL;     // 等价于 int* ptr3 = 0;

使用 nullptr来初始化指针变量是C++11引入的新标准,最直接简单。

建议将所有的指针都要初始化。如果不知道指向何处,就令其为 nullptr0

赋值和指针

赋值永远改变的是等号左侧的对象(变量)。例如:

int x = 10;
int y = 100;
int *ptr = &x;  // 初始化,ptr指向x
ptr = &y;       // 赋值,prt现在指向y

指针的其他操作

只要指针是合法的,就可以将其运用在条件表达式中,以及对它进行算术运算。(关于指针的操作,见TODO)

void* 指针

void* 指针是一种特殊的指针,可以 用于存放任意对象的地址。和其他指针类似, void* 中也存储的是一个指针。不同的是:我们对该地址中到底是什么类型的对象并不了解。(该指针就很哲学:来者不拒,什么都能存,但是存的是什么,却不得而知)

不能直接操作该指针所指向的对象(变量),因为并不知道这个对象(变量)是什么类型,也就无法确定能在这个对象(变量)上进行怎样的操作。

double obj = 3.14;
void *pv = &obj; // 正确,void*能存放任何类型对象的地址

类型修饰符和声明语句

声明(或定义)语句的格式是: 基本数据类型紧跟着其后的声明符列表 组成。所谓声明符,指的就是变量。例如: int x, y, z;;,其中, int 就是基本数据类型,而 x,y,z就是声明符列表。

在后面会看到更加复杂的数据类型和声明符,但是他们都是由基本数据类型得到的,因此基础很重要!

在C++中,类型修饰符用于修饰变量的类型, 例如:*&类型修饰符属于声明符(变量)的一部分,例如: int *x; int &y = z;类型修饰符用于修饰变量。且声明符的修饰符的个数没有限制。

指向指针的指针

正如前面所说,类型修饰符没有限制,当有多个类型修饰符写在一起的时候,按照其逻辑关系解释即可。以指针为例,因为指针是对象,因此其本身也有地址,故可以把指针的地址再放到另一个指针中。

通过 * 的个数,可以区分指针的级别。例如: *p 表示 p 是一个指针, **P 是指向指针的指针, ***p 表示的是指向指针的指针的指针(禁止套娃!!!)

指向指针的引用

由于引用不是对象,故不能定义指向引用的指针(int& *p; // 错误),但是指针是对象,所以存在对指针的引用。

int i = 42;
int *p = &i;
int *&r_p = p; // 定义指针的引用. 将变量 r_p 绑定到指针变量 p上

对于一条复杂的包含多个类型修饰符的语句,采用 从右向左 的方式,有助于理解。因为:离变量名最近的符号是对变量有最直接的影响

对于上面的代码来说, int *&r_p = p;,从右向左阅读,最近的是 &,故变量 r_p 是一个引用,然后 * 表明它引用的(要绑定的)的对象是一个指针。最后, int 说明变量 r_p 要绑定的是一个整形的指针。

cosnt修饰符

类型修饰符用于修饰变量的类型,前面讲了两个类型修饰符引用和指针。现在将另一种变量修饰符 const

const 是一种类型修饰符,用于说明 永不改变的对象(变量)。任何对被 const 修饰的对象(变量)进行赋值操作都是错误的。因此: const 的对象(变量) 必须初始化

const int x = 10; // 正确
const int y;      // 错误,k是一个未被初始化的对象.

默认情况下,const仅仅在当前文件中生效。因为编译器会把所有被 const 修饰的变量替换成一个常量。

假设,A文件有一个 const int x = 10;, B文件中有一个 const int x = 10;, 然后将两个文件放在一起编译,是允许的。

const的引用

可以把引用绑定到 const 修饰的对象上,这种被称为 对常量的引用(reference to const)对常量的引用不能用作修改它所绑定的对象

const int x = 10;    //被const修饰的变量必须初始化
const int &ref = x;  //正确
ref = 42;              // 错误,ref是对常量的引用,其值不能变化
int &ref_2 = x;        // 错误:试图让一个非常量引用 ref_2 绑定到一个常量对象 x 上

正如前面所说的, 引用类型的对象必须和引用类型严格匹配。由于 x 是常量,因此对常量 x 的引用必须是常量类型,即 cosnt int &

指针和const

同引用一样, 指向常量的指针(pointer to const)不能用于改变其所指的对象的值。要想存放常量对象的地址,就必须用指向常量的指针(指针类型必须和指向对象的类型严格匹配)。

const double pi = 3.14159;
const double *ptr = &pi;  // 正确
double *ptr_2 = &pi;      // 错误, ptr_2 是普通指针,不能指向常量对象.

非常量指针不能指向常量对象,但是常量指针可以指向非常量对象。例如:

double obj = 3.14;
const int *ptr = &obj;  // 正确.
所谓指向常量的指针或引用,不过是指针“自以为是”罢了,他们自己以为自己指向(绑定)的对象是一个常量,所以会自觉的不去改变所指对象的值。

const指针

指针是对象而引用不是,因此指针同其他指针一样,允许把指针定义为常量,即 常量指针(const poniter)。常量指针必须初始化,而且一旦初始化,它的 值(存放在指针中那个地址)就不能发生改变。在书写的时候,把 * 放在 const 前,说明指针是一个常量,这样书写表明:不变的是指针本身的值而不是指针指向的那个值

int x = 10;
int *const ptr = &x;  // ptr将一直指向x
const double pi = 3.14159;
const double *const ptr_2 = &pi; // ptr_2是一个指向常量对象的常量指针

正如前面所讲的,从右向左阅读。 ptr 右边的第一个是 const,表名这是一个常量变量,声明符中的下一个是 * ,表名变量 ptr 是一个常量指针,最左边的 int 说明是整形。总结起来就是:ptr 一个指向整形的常量指针.

同理可以推断出:ptr_2 是一个指向双精度浮点类型常量的常量指针。

顶层const和底层const(总结内容)

指针,它本身是一个对象,同时它又指向了一个对象。因此: 指针本身是不是常量以及指针所指向的对象是不是常量,是两个相互独立的问题。因此,为了区分。用 顶层const(top-level const) 表示指针本身是一个常量,用 底层const(low-level const) 表示指针所指向的对象是一个常量。

更一般的,顶层const可以表示任意对象都是常量,这一点对任何数据类型都适用;底层const 则与指针和引用等复合类型有关。例如:

int i = 0;
int j = 1;

int *const p1;      // 错误,顶层const修饰的对象,必须初始化
int *const p1 = &i; // 正确,顶层const,必须初始化,表示p1本身是一个常量
p1 = &j;            // 错误,p1是常量,已经指向了i,不能再指向j
*p1 = 100;          // 正确
i = 999;            // 正确

int k = 2;
const int *p2 = &k;      // 底层const,表示p2所指的对象是一个常量
p2 = &i;                 // 正确,指针本身是可以改变的
*p2 = 20;                // 错误,不能通过 *p2 去修改对象的值
k = 10;                  // 正确

根据上面的代码,可以看到:所谓 底层const(指向常量的指针),是自己认为自己指向了常量,“自以为是”的不会通过自己去修改指向对象的值。

constexpr和常量表达式

常量表达式(const expression) 是指值不会变,并且在 编译过程中就能得到计算结果的表达式

一个对象(或者表达式)是不是常量表达式由它的 数据类型初始值 共同决定。例如:

const int max_file = 20;         // 是
const int limit = max_file + 1;  // 是
int size = 27;                   // 不是

constexpr

C++11引入,将变量声明为 constexpr 类型,以便于由编译器来验证变量的值是否是一个常量表达式。 声明为 constexpr 的变量一定是一个常量,且必须用常量表达式初始化。例如:

constexpr int mf = 20;         // 20是常量表达式
constexpr int limit = mf + 1;  // mf + 1 是常量表达式

在后面会讲 constexpr 函数(TODO)

处理类型

类型别名

类型别名(type alias),是一个名字,是 某种类型的同义词。(和引用类似但是又有不同)。

类型别名能让复杂的类型变得简单明了、已于理解和使用,还能帮助程序员清楚知道使用该了类型的目的。

定义别名的方法

  1. 传统方法,关键字 typedef
typedef double wages;  // wages 是 double 的同义词
wages pi = 3.1415;
  1. 别名声明(alias declaration) C++11新标准可以使用关键字 using 来定义类型别名。
using MyInt = int;
MyInt x = 3;

推荐使用 using,因为更加直观,特别是在数组中。例如:

typedef char MyCharArr[4];
using MyCharArr = char[4];

指针、常量和类型别名

如果某个类型别名指代的复合类型或常量(实际工程会经常看到这样),那么把这个类型别名用到声明语句中就会产生意想不到的后果。TODO

auto类型说明符