一、复合类型
  复合类型(compound type)是指基于其他类型定义的类型。本次主要介绍引用和指针两种。

1.1 引用
  引用(reference)为对象起了另外一个名字,引用类型引用(refers to)另外一种类型。通过将声明写成&d的形式定义引用类型,其中d是声明的变量名。
  引用不是对象,只是已存在对象的别名,所以不能定义引用的引用。为引用赋值,实际是把值赋给了与引用绑定的对象,获取应用的值,实际是获取与引用绑定的对象的值。需要注意的是引用必须初始化,而且一旦初始化就不能重新绑定到另外一个对象,以及引用只能绑定在对象上,不能与字面值或某个表达式的计算结果绑定在一起。

int ival = 1024;
int &refVal = ival;      //refVal指向ival,是ival的另外一个名字)
refVal = 2;              //把2赋给refVal指向的对象,即赋给ival
int &refVal2 = refVal;   //将refVal2绑定到了ival上
int &refVal3;            //报错,引用必须被初始化
int &refVal4 = 10;       //引用类型的初始值必须是一个对象

1.2 指针
  指针(pointer)是指向(point to)另外一种类型的复合类型,与引用类似,指针也实现了对其它对象的间接访问。但与引用不同的是,第一,指针本身就是一个对象,允许对指针赋值和拷贝;第二,指针无需在定义时赋初值。和其他内置类型一样,在块作用域内定义的指针如果没有初始化也将拥有一个不确定的值。
  定义指针类型的方法是将生命写成*d的形式,其中d是变量名,指针存在某个对象的地址,想要获取该地址,需要使用取地址符(操作符&):

int *ip1;             //ip1是指向int型对象的指针
int ival = 42;
int *p = &ival;       //p存放变量ival的地址,或者说p事指向变量ival的指针

  因为在声明语句中指针的类型实际上被用于指定它所指对象的类型,所以如果指针指向一个其他类型的对象,对该对象的操作将发生错误。指针的值(即地址)有下列四种状态:
  ①指向一个对象;
  ②指向紧邻对象所占空间的下一个位置;
  ③空指针,即指针没有指向任何对象;
  ④无效指针,即上述情况之外的值。

1.2.1 利用指针访问对象
  如果指针指向了一个对象,那么可以使用解引用操作符(操作符*)来访问该对象:

int ival = 42;
int *p = &ival;
cout << *p;         //由符号*得到指针p所指的对象,输出42

  对指针解引用会得出所指的对象,同样如果对给解引用的结果赋值,实际上就是给指针所指的对象赋值:

*p = 0;            //由符号*得到指针p所指对象,即经由p为变量ival赋值
cout << *p;        //输出p所指对象ival的值,即为0

符号含义总结:

int i = 42;
int &r = i;      //&紧随类型名,是声明一部分,所以r是一个引用int *p          //*紧随类型名,是声明一部分,所以p是一个指针
p = &i;          //&出现在表达式中,所以&是一个取地址符
*p = i;          //*出现在表达式中,所以是一个解引用符

1.2.2 空指针和void* 指针
  空指针(null pointer)不指向任何对象,得到空指针最直接的方法是使用字面值nullptr来初始化指针,它可以被转换成其它任意的指针类型;还可以通过将指针初始化为字面值0或者名为NULL的预处理变量来生成空指针,但应尽量避免使用NULL。

int *p1 = nullptr;
int *p2 = 0;
int *p2 =NULL;

  使用未经初始化的指针是引发运行时错误的一大原因,所以应初始化所有指针。如果实在不清楚指针应该指向何处,就把它初始化为nullptr或者0,这样程序就能检测它有没有指向任何具体的对象了。
  void*是一种特殊的指针类型,可以存在任意对象的地址。

double obj = 3.14,*pd = &obj;
void *pv = &obj;
pv = pd;            //pv可以存放任意类型的指针

1.2.3 指针的赋值和其他操作
  引用本身不是一个对象,所以一旦定义引用,就无法再绑定到另外的对象上,但是指针和其存放的地址没有这种限制,给指针赋值就是令它存放一个新的地址,即指向一个新的对象。

int i = 42;
int *pi = 0;      //pi被初始化,但没有指向任何对象
int *pi2 = &i;    //pi2被初始化,存有I的地址
int *pi3;         //如果pi3定义在块内,则pi3的值是无法确定的
pi3 = pi2;        //pi3和pi2指向同一对象I
pi2 = 0;          //pi2不指向任何对象了

  只要指针拥有一个合法值,就能将它用在条件表达式中,如果指针值为0,条件去false:

int ival= 1024;
int *p = 0;
int *pi2 = &ival;
if(pi)          //pi的值为0,所以其条件值是false
if (pi2)        //pi2指向ival,它的值不是0,所以条件值是true 

1.3 理解复合类型的声明
  变量的定义包括一个基本数据类型和一组声明符,在统一条定义语句中,基本数据类型只有一个,但是声明符的形式却可以不同,即一条定义语句可能定义出不同类型的变量:

int i =1024,*p = &i,&r = i;

  通过*的个数可以区分指针的级别,即**表示指向指针的指针,以此类推:

int ival = 2014;
int *pi = &ival;
int **ppi = &pi;

  引用本身不是对象,所以不能定义指向引用的指针,但是指针是对象,所以存在对指针的引用:

int j =42;
int *p;         //p是一个int型的指针
int *&r = p;    //r是一个对指针p的引用
r = &j;         //r引用了一个指针,因此给r赋值&j就是令p指向j
*r = 0;         //解引用r得到j,也就是p指向的对象,将j的值改为0

  注意:面对复杂的指针或引用的声明语句时,从右向左阅读有助于弄清楚它的真是含义。

二、const限定符
  如果我们希望定义一种不能改变其值的变量,可以用关键字const对变量的类型加以限定,因为const对象一旦创建之后其值就不能在改变,所以const对象必须初始化:

const int bufsize = 512;
const int k;                 //错误,k是一个未经初始化的常量

  在默认情况下,const对象被设定为仅在当前文件中有效,当多个文件中出现同名的const变量时,等同于在不同文件中分别定义了独立的变量。如果想在多个文件之间共享const对象,那么也必须在变量的定义之前添加extern关键字。

2.1 const的引用
  如果把引用绑定在const对象上,我们称之为对常量的引用(reference to const),与普通引用不同的是,对常量的引用不能用作修改它所绑定的对象:

const int ci = 1024;
const int &r1 = ci;           //正确,引用及其对应的对象都是常量
r1 = 42;                      //错误,r1是对常量的引用
int &r2 = ci;                 //错误,试图让一个非常量引用指向一个常量对象

  在学习引用时提到引用的类型必须与其所引用对象的类型一致,但是在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可,对const的引用可能引用一个非const的对象,但是不能通过引用改变绑定对象的值。

int j = 42;
const int &r1 = j;            //允许将const int&绑定到普通int对象上
r1 = 40;                      //r1是常量引用,不能通过其改变绑定的对象值
const int &r2 = 42;           //正确,初始化常量引用r2
int &r4 = r1*2;               //错误,r4是一个普通的非常量引用

2.2 指针和const
  与引用一样,可以令指针指向常量或者非常量,类似于常量引用,指向常量的指针(pointer to const)不能用于改变其所指对象的值,如果要存储常量对象的地址,只能使用指向常量的指针;在学习指针时提到指针的类型必须与其所指对象的类型一致,但是允许令一个指向常量的指针指向一个非常量对象。

const double pi = 3.14;        //pi是个常量,它的值不能改变
double *ptr = &pi;             //错误,ptr是一个普通指针
const double *cptr = &pi;      //正确,cptr可以指向一个双精度常量
*cptr = 42;                    //错误,不能给*cptr赋值
double val = 3.14;
cptr = &val;                   //正确,可以改变其指向

  指针是对象而引用不是,因此就像其它对象类型一样,允许把指针本身定义为常量,即常量指针(const pointer),其必须初始化,而且一旦初始化,则它的值(存放在指针中的那个地址)就不能改变,把*放在const关键字之前用以说明指针是一个常量,即表示不变的是指针本身的值而不是指向那个对象的值:

int errNumb = 0;
int *const curErr = &errNumb;   //curErr将一直指向errNumb
*curErr = 10;                   //正确,不能改变指向,但是可以改变指向的值
const double pi = 3.1415;
const double *const pip = &pi;  //pip是一个指向常量对象的常量指针

2.3 顶层const和底层const
  由于指针本身是不是常量以及指针所致的是不是一个常量是两个独立的问题,所以我们使用名称顶层const(top-level const)来表示指针本身是个常量,用名词底层(low-level const)来表示指针所致对象是一个常量;更一般的,顶层const可以表示任意的对象是常量,如果算术类型、类、指针等任何数据类型,底层const则与指针和引用等符合类型的基本类型部分有关。

int j = 0;
int *const p1 = &j;             //不能改变p1的值,是一个顶层const
const int ci = 42;              //不能改变ci的值,是一个顶层const
const int *p2 = &ci;            //允许改变p2的值,是一个底层const
const int *const p3 = p2;       //靠右的const是顶层const,靠左是底层const
j = ci;                         //正确,ci是顶层const,对此操作无影响
p2 = p3;                        //正确,p2和p3指向对象类型相同
int *p = p3;                    //错误,p3包含底层const定义
const int &r2 = j;              //正确,const int&可以绑定到普通int上

2.4 constexpr和常量表达式
  常量表达式(const expression)是指不会改变并且在编译过程中就能得到计算结果的表达式,显然字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。

const int max_files = 20;      //max_files是常量表达式
const int limit = max_files+1; //limit是常量表达式
int staff_size = 27;           //staff_size不是常量表达式
const int sz = get_size();     //sz不是常量表达式

  尽管staff_size的初始值是个字面值,但是它的数据类型知识普通的int而不是const int,所以它不属于常量表达式;sz本身是一个常量,但是它的具体值需要等到运行时才能获取到,所以也不是常量表达式。
  在一个复杂系统中,很难分辨一个初始值到底是不是常量表达式,所以C++11新标准允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式,声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化,一般来说,如果你认定变量是一个常量表达式,那么久把它声明为constexpr类型。

constexpr int mf = 20;         //20是常量表达式
constexpr int limit = mf+1;    //mf+1是常量表达式
constexpr int sz = size();     //当size时一个constexpr函数时才正确

  常量表达式的值需要在编译时就得到计算,因此对声明conste时用到的类型必须有所限制,因为这些类型比较简单,值也显而易见,就把他们称之为“字面值类型(literal type)”。算术类型、引用和指针都属于字面值类型,自定义的类、IO库、String类型则不属于字面值类型,也就不能定义成conste。一个constexpr指针的初始值必须是nullptr或0,或者是存储于某个固定地址中的对象,由于函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量,而函数体外的对象其地址固定不变,因而可以用来初始化constexpr指针。
  在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指对象无关,constexpr把它所定义的对象置为顶层const,与其他常量指针类似,constexpr指针既可以指向常量也可以指向非常量。

const int *p = nullptr;          //p是一个指向整型常量的指针
constexpr int *q = nullptr;      //q是一个指向整数的常量指针
int j = 0;
constexpr int k = 42;            //k的类型是整型常量
//如果j、k均定义在函数体之外
constexpr const int *p1 = &k;    //p1是常量指针,指向整型常量k
constexpr int *p2 = &j;          //p2是常量指针,指向整数j

参考文献:
①C++ Primer 第五版。

【C++学习笔记】复合类型和const限定符相关推荐

  1. 冰冰学习笔记:类与对象(上)

    欢迎各位大佬光临本文章!!! 还请各位大佬提出宝贵的意见,如发现文章错误请联系冰冰,冰冰一定会虚心接受,及时改正. 本系列文章为冰冰学习编程的学习笔记,如果对您也有帮助,还请各位大佬.帅哥.美女点点支 ...

  2. ASM学习笔记2 - 类的创建和修改 —— ClassWriter的综合应用

    ASM学习笔记2 - 类的创建和修改 -- ClassWriter的综合应用 上回我们说到,通过使用ClassVisitor和ClassReader,我们能够分析已经存在的类.这一节中,我们将使用Cl ...

  3. Python学习笔记 (类与对象)

    Python学习笔记 (类与对象) 1.类与对象 面向对象编程语言类: 一个模板, (人类)-是一个抽象的, 没有实体的对象: (eg: 张三, 李四) 属性: (表示这类东西的特征, 眼睛, 嘴巴, ...

  4. Machine Learning A-Z学习笔记12-分类模型性能评级及选择

    Machine Learning A-Z学习笔记12-分类模型性能评级及选择 1.简单原理 一般认为假阴性比假阳性更严重,如核酸检测 用混淆矩阵表示如下图 准确率驳论(Accuracy Paradox ...

  5. JAVA学习笔记(类的学习)

    JAVA学习笔记(类的学习) 类声明和类体 构造方法和对象创建 对象的引用和实体 成员变量 方法 方法重载 关键字this 包 import语句 访问权限 对象数组 反编译和文档生成器 JAR文件 1 ...

  6. python面向对象编程72讲_2020-07-22 Python学习笔记27类和面向对象编程

    一些关于自己学习Python的经历的内容,遇到的问题和思考等,方便以后查询和复习. 声明:本人学习是在扇贝编程通过网络学习的,相关的知识.案例来源于扇贝编程.如果使用请说明来源. 第27关 类与面向对 ...

  7. 指针、引用以及const限定符、constexpr限定符

    文章目录 复合类型 引用 概念与使用 引用的定义 注意 指针 概念 声明方式 取地址符 指针值 空指针 利用指针访问对象 赋值和指针 void* 指针 指向指针的指针 指向指针的引用 初始化所有指针 ...

  8. C++ 语法 const限定符

    const限定符是常用且容易混淆的概念,很多书中讲解不清晰,甚至<C++ primer>中都有一些错误的论断或者不明确的区分,本文对const限定符做归纳总结. 一.在类型中 顶层cons ...

  9. C语言中的类型限定符.const限定符

    目录 1.1const限定符 1.1.1const限定符修饰普通对象 1.1.2const限定符修饰数组元素 1.1.3const限定符修饰指针类型对象 1.1.4const限定符修饰函数形参类型为数 ...

最新文章

  1. SAP HUM 没有搬到Storage Type 923的HU能用HU02拆包?
  2. 第十六章 贪心算法——0/1背包问题
  3. LabelBinarizer的妙用
  4. 如何将IDEA文件提交至Gitee仓库
  5. 服务启动不了,显示 config 异常的问题排查
  6. m1芯片Mac安装pandas库(Rosetta2转译版)
  7. 解析单句sql_SqlParser 一个利用正则表达式解析单句SQL的类
  8. InteliJ IDEA生成可执行jar运行提示没有主清单属性
  9. ArcGIS地图文档(mxd)过大的问题
  10. VS2010 部署程序在安装完成后自动启动外部程序
  11. google三篇重要论文(英文原文)
  12. 手把手教你入门深度强化学习(附链接代码)
  13. ZYNQ PS部分简介
  14. Windows 下PowerShell 美化之旅(极其简单)
  15. 智慧图书馆中的自助借还系统
  16. 关于使用Cytoscape软件合并多个网络图
  17. 【杂题】cf1041fF. Ray in the tube
  18. 欧氏空间距离和内积_线性空间,度量空间,赋范空间,线性赋范空间,内积空间,巴拿赫空间以及希尔伯特空间、拓扑空间...
  19. 调停者模式(Mediator) Java实现
  20. 推荐一个下载电子书的站点,得益网

热门文章

  1. 开机提示:one of your disks needs to be checked解决方法
  2. 2021最新外卖霸王餐小程序、H5、微信公众号版外系统源码|霸王餐美团/饿了么系统 粉丝裂变玩源码下载
  3. 【青龙面板】快手JS版脚本
  4. python 根据x的值和函数y=20+x2,计算y_new,算出y_new和y的差,记为delta_y。¶绘制x和delt_y的点图,并计算y的方差。有关方差的计算参阅数学资料。
  5. python人工智能工程师要求_想跻身高薪的AI人工智能工程师,你需要符合哪些条件?...
  6. C# 读取西门子S7系列PLC教程及源码
  7. 宏基掠夺者圣盾5000、圣盾3000 评测
  8. java小数是怎么运算的_JAVA中小数的运算
  9. 计算机科学丛书之第9章和第10章代码
  10. 如何将照片从iPhone导入到Mac