您的位置:首页 > 编程语言 > C语言/C++

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

2017-11-15 00:12 471 查看
一、复合类型

  复合类型(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 = π


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

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 = π             //错误,ptr是一个普通指针
const double *cptr = π      //正确,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 = π  //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 第五版。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  指针 引用 const