复合类型

  复合类型(compound type)是指基于其他类型定义的类型,C++语言有几种复合类型,这里只介绍两种:引用和指针

一:引用

  引用(reference)为对象起了另外一个名字,引用类型引用另外一种类型,通过将声明符写成&d的形式来定义引用类型,其中d是声明的变量名。

  int ival = 1024

  int &refval = ival;//refval指向ival(是ival的另外一个名字)

  int &refval2;  //报错,引用必须初始化

一般在初始化变量的时候,初始值会被拷贝到新建的对象中,然而定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用,一旦初始化完成,引用将和它的初始值对象一直绑在一起,因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。

  

引用即别名

  引用并非对象,相反的,它只是为一个已经存在的对象起的另外的名字。

  定义了一个引用之后,对其进行的所有操作都是在与之绑定的对象上进行的;

  refval = 2;  //把2赋值给refval指向的对象,此处即是赋值给了ival;

  int ii = refval; //与ii = ival执行的结果是一样的。

为引用赋值,实际上是把值赋给了与引用绑定的对象,获取引用的值,实际上是获取了与引用绑定的对象的值,同理,以引用作为初始值,实际上是以与引用绑定的对象作为初始值;

  //正确:refval3绑定到了那个与refval绑定的对象上,这里就是绑定到ival上

  int &refval3 = refval;

  //利用与refval绑定的对象的值初始化变量

  int i = refval;//正确,i 被初始化为ival的值

  因为引用本身不是一个对象,所以不能定义引用的引用;

引用的定义

  允许在一条语句中定义多个引用,其中每个引用标识符都必须以符号&开头。

  int i = 1024,i2 = 2048; //i和i2都是int;

  int &r = i,r2 = i2; //r是一个引用,与i绑定在一起,r2是int;

  int i3 = 1024,&ri = i3;//i3 是int,ri是一个引用,与i3绑定在一起。

  int &r3 = i3,&r4 = i2; //r3和r4都是引用。

引用类型都要和与之绑定的对象严格匹配,而且,引用只能绑定到对象上,而不能与字面值或某个表达式的计算结果绑定在一起。

  int &refval4 = 10;   //错误,引用类型的初始值必须是一个对象。

  double dval = 3.14;

  int &refval5 = dval; //错误,此处引用类型的初始值必须是int型对象。

二:指针

  指针(pointer)是指向(point to)另外一种类型的复合类型,与引用类似,指针也实现了对其它对象的间接访问,然而指针与引用相比又有很多不同点,其一,指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。其二,指针无须在定义时赋值,和其它内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。

  WARNING:指针通常难以理解,即使是有经验的程序员也常常因为调试指针引发的错误而备受折磨。

  定义指针类型的方法将声明符写成*d的形式,其中d是变量名,如果在一条语句中定义了几个指针变量,每个变量前面都必须有符号*;

  int *ip1, *ip2; //ip1,ip2都是指向int型对象的指针。

  double dp,*dp2; //dp2是指向double型对象的指针,dp是double型对象。

获取对象的地址

  指针存放某个对象的地址,要想获取该地址,需要使用取地址符(操作符&);

  int ival = 42;

  int *p = &ival;  //p存放了ival的地址,或者说p是指向了变量ival的指针。

第二条语句把p定义了一个指向int的指针,随后初始化p另其指向名为ival的int对象,因为引用不是对象,没有实际地址,所以不能定义指向引用的指针。

指针的类型都要和它所指向的对象严格匹配。

  double dval;

  double *pd = &dval; //正确,初始值是double型对象的地址

  double *pd2 = pd; //初始值是指向double对象的指针

  

  int *pi = pd;  //错误,指针pi的类型和pd的类型不匹配。

  pi = &dval; //错误,试图把double型对象的地址赋给int型指针。

因为在声明语句中指针的类型实际上被用于指定它所指向对象的类型,所以两者必须匹配,如果指针指向了一个其他类型的对象,对该对象的操作将发生错误。

指针值

  指针的值(即地址)应属于下列4种状态之一:

  1:指向一个对象;

  2:指向紧邻对象所占空间的下一个位置;

  3:空指针,意味着指针没有指向任何对象;

  4:无效指针,也就是上述情况之外的位置;

试图拷贝或者以其他形式访问无效指针的值都将引发错误,编译器并不负责检查此类错误,这一点和试图使用未初始化的变量一样,访问无效指针的后果无法预计,因此程序员必须清楚任意给定的指针是否有效。

尽管第2种和第三种形式的指针是有效的,但其使用同样受到限制,显然这些指针没有指向任何具体的对象,所以试图访问此类指针对象的行为不被允许,如果这样做了,后果也是无法预计的。

利用指针访问对象

  如果指针指向了一个对象,则允许使用解引用符(操作符*)来访问对象;

  int ival = 42;

  int *p = &ival; //p存放着变量ival的地址,或者说p是指向变量ival的指针;

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

对指针解引用会得到所指的对象,因此如果给解引用的结果赋值,实际上也就是给指针所指对象赋值;

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

  cout << *p; // 输出0

如上述程序所示,为*p赋值实际上是为p所指的对象赋值

空指针

  空指针(null pointer)不指向任何对象,在试图使用一个指针之前代码可以首先检查它是否为空,一下列出几个生成空指针的方法。

  int *p = nullptr; //等价于int *p = 0;

  int *p2 = 0; //直接将p2初始化为字面常量0;

  //需要首先#include<cstdlib>

  int *p3 = NULL; // 等价于int *p3 = 0;

得到空指针最直接的办法就是用字面值nullptr来初始化指针,这也是C++11新标准刚刚引入的一种方法,nullptr是一种特殊类型的字面值,它可以被转换成任意其他指针类型,另一种方法就是如对p2的定义一样,也可以通过将指针初始化为字面值0来生成空指针。

过去的程序还会用到一个名为NULL的预处理变量来给指针赋值,这个变量在头文件cstdlib中定义,它的值就是0

当用到一个预处理变量时,预处理器就会自动将它替换为实际值,因此用NULL来初始化指针和用0来初始化指针是一样的,在新标准下,现在的C++最好使用nullptr,同是应尽量避免使用NULL。

  把int变量直接赋值给指针是错误的操作,即使int变量的值恰好是0也不行

  int zero = 0;

  int *pi;

  pi = zero; //错误,不能把int变量直接赋值给指针

建议

  使用未经初始化的指针是引发运行时错误的一大原因。

  和其它变量一样,访问未经初始化的指针所引发的后果是无法预计的,通常这一行为将造成程序崩溃,而且一旦崩溃,要想定位到出错的位置将是特别棘手的问题。

  在大多数编译器环境下,如果使用了未经初始化的指针,则该指针多占内存空间的当前内容将被看做一个地址值。访问该指针,相当于访问一个本不存在的位置上的本不存在的对象,槽糕的是,如果指针所占内存空间中恰好有内容,而这些内容又被当作了某个地址,我们就很难分清它到底是合法还是非法的了。

  因此我们建议初始化所有的指针,并且在可能的情况下,尽量定义了对象之后再定义指向它的指针。如果实在不清楚应该指向何处,就把它初始化为nullptr或者0,这样程序就能检测并知道它没有指向任何具体的对象了。

赋值和指针

  指针和引用都能提供其他对象的间接访问,然而在具体实现细节上二者有很大不同,其中最重要的一点就是引用本身并非是一个对象。一旦定义了引用,就无法令其再绑定到另外的对象,之后每次使用这个引用都是访问它最初绑定的那个对象。

  指针和它存放的地址就没有这种限制了,和其他任何变量(只要不是引用)一样,给指针赋值就是令它存放一个新地址,从而指向一个新的对象。

  int i = 42;

  int *pi = 0; //pi被初始化,但没有指向任何对象;

  int *pi2 = &i; //pi2被初始化,存有i的地址

  int *pi3;  //如果pi3定义于块内,则pi3的值是无法确定的。

  pi3 = pi2; //pi3和pi2指向了同一个对象i;

  pi2 = 0; //现在pi2不指向任何对象了;

  有时候想搞清楚一条赋值语句到底是改变了指针的值还是改变了指针所指对象的值不太容易,最好的办法就是记住永远改变的是等号左侧的对象。

  pi = &ival; //pi的值被改变了,现在pi指向了ival;

  意思就是为pi赋了一个新的值,也就是改变了那个存放在pi内的地址值,相反,如果写出以下语句:

  *pi = 0; //ival的值被改变,指针pi并没有改变;

  则*pi(也就是指针pi指向的那个对象)发生改变

void*指针

   void*是一种特殊的指针类型,可用于存放任意对象的地址,一个void*指针存放着一个地址,这一点和其他指针类似,不同的是,我们对该地址中到底是个什么类型的对象并不了解。

  double obj = 3.14, *pd = &obj;

  void *pv = &obj; //正确,void*能存放任意类型对象的地址   obj可以是任意类型的对象

  pv = pd; //pv可以存放任意类型的指针。

  利用void*指针能做的事情比较有限,拿它和别的指针比较,作为函数的输入和输出,或者赋值给另外一个void*指针。不能直接操作void*指针所指的对象,因为我们不知道这个对象到底是什么类型,也就无法确定能在这个对象上做哪些操作。

  概括来说,以void*的视角来看内存空间也就仅仅是内存空间,没办法访问内存空间中所存的对象。

定义多个变量

  经常有一种观点会误以为,在定义语句中,类型修饰符(*或&)作用于本次定义的全部变量。造成这种错误看法的原因有很多,其中之一就是我们可以把空格写在类型修饰符和变量名中间。

  int*  p;//合法但是容易产生误导

  我们说这种写法可能产生误导是因为int*放在一起好像是这条语句中所有变量共同的类型一样。其实恰恰相反,基本数据类型是int而非int*,*仅仅是修饰了p而已,对该声明语句中的其他变量,它并不产生任何作用。

  int*  p1, p2; //p1是指向int的指针,p2是int

  涉及指针或引用的声明,一般有两种写法,第一种把把修饰符和变量标识符写在一起。

  int *p1, *p2;//p1,p2都是指向int的指针

  这种形式着重强调变量具有的复合类型, 第二种把修饰符和类型名写在一起,并且每条语句只定义一个变量;

  int*  p1;//p1是指向int的指针

  int*  p2; //p2是指向int的指针

  这种形式着重强调本次声明定义了一种复合类型。

  个人推荐第一种写法。

指向指针的指针

  一般来说,声明符中修饰符的个数没有限制。当有多个修饰符写在一起的时候,按照其逻辑关系详加解释即可,以指针为例,指针是内存中的对象,像其他对象一样也有自己的地址,因此允许把指针的地址再存放到另一个指针当中。

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

  int ival = 1024;

  int *pi = &ival; //pi是指向一个int型的数

  int **ppi = &pi; //ppi是指向一个int型的指针

  此处pi是指向int型的指针,而ppi是指向int型指针的指针,下图描述了他们之间的关系:

  解引用int型指针会得到一个int型的数,同样,解引用指向指针的指针会得到一个指针,此时为了访问最原始的那个对象,需要对指针的指针做两次解引用;

  cout << "The Value of ival\n"

    <<"direct value: " << ival << "\n"

    <<"indirect value: "<< *pi << "\n"

    <<"doubly indirect value: " << **pi <<endl;

  该程序使用三种不同的方式输出了变量ival的值,第一种直接输出,第二种通过int型指针pi输出,第三种两次解引用ppi,取得ival的值;

指向指针的引用

  引用本身不是一个对象,因此不能定义指向引用的指针,但是指针是一个对象,所以存在对指针的引用;

  int i = 42;

  int *p; //p 是一个int型指针

  int *&r = p; //r是一个对指针p的引用

  

  r = &i;//r引用了一个指针,因此给r赋值&i就是另p指向了i;

  *r = 0; //解引用r得到i,也就是p指向的对象,将i的值改为了0;

  要理解r的类型到底是什么,最简单的办法就是从右向左阅读r的定义,离变量名最近的符号(此例中是&r的符号&)对变量的类型有最直接的影响,因此r是一个引用。声明符的其余部分用以确定r引用的类型是什么,此例中的符号*说明r引用的是一个指针,最后

声明的基本数据类型部分指出r引用的是一个int型指针。

原文地址:https://www.cnblogs.com/cxq0017/p/10445277.html

时间: 2024-11-07 17:31:57

复合类型的相关文章

第四章 复合类型

第四章  复合类型 4.1  数组 4.1.1  数组简介 数组(array)是一种数据格式,能够存储多个同类型的值. 声明数组的通用格式如下: typeName arrayName[arraySize]; 表达式arraySize指定数组的元素数目,它只能是以下三种情况之一: 1)        整型常数(如10,枚举值也可以): 2)        const值 3)        常量表达式(如8 * sizeof(int)) 注意:使用数组要注意下标的正确.编译器不会检查使用的下标是否有

C++ 语法--唯一的难点const和复合类型

总结在底下. 1.const和引用 可以把引用绑定在常量上,称之为,对常量的引用.不能对那个引用赋值. 如: const int ci = 1024; const int &ri = ci; 解读:ri是对ci的引用.ri的const意思是视ci为变量. 对于常量,只能使用“对常量的引用”这个引用方式 int &ri = ci;是错误的,因为ci不能赋值,但是可能会对ri赋值从而影响const限定. 所以,他们(制定标准的人)创造了 对常量的引用 的 引用方式. 下面高能!!! 上面说:

C++Primer快速浏览笔记-复合类型

C++Primer2.3节介绍了两种复合类型:引用和指针 1.引用 引用并非对象,它只是为一个已经存在的对象所起的别名. 一旦初始化完成,引用将和它的初始值对象一直绑定在一起,不能重新绑定到另一个对象,因此引用必须初始化. 定义引用时,程序把引用和它的初始值绑定到一起,而不是将初始值拷贝给引用. 引用本身不是一个对象,所以不能定义引用的引用 引用只能绑定在对象上,而不能与字面值或者某个表达式的结果绑定在一起. 引用的类型必须和与之绑定的对象的类型严格匹配.但是有两种例外情况: 1.初始化常量引用

hive原生和复合类型的数据加载和使用

原生类型 原生类型包括TINYINT,SMALLINT,INT,BIGINT,BOOLEAN,FLOAT,DOUBLE,STRING,BINARY (Hive 0.8.0以上才可用),TIMESTAMP (Hive 0.8.0以上才可用),这些数据加载很容易,只要设置好列分隔符,按照列分隔符输出到文件就可以了. 假设有这么一张用户登陆表 CREATE TABLE login ( uid BIGINT, ip STRING ) ROW FORMAT DELIMITED FIELDS TERMINA

C++ Primer Plus(四)——复合类型

只能在定义数组时才能初始化,不能将一个数组赋值给另一个数组,但可以使用下标分别赋值给数组元素,但可以将一个string对象赋值给另一个string对象 如果只对数组的一部分初始化,其他元素自动设置为0 C++11可在初始化的大括号里不包含任何东西,这将把所有元素设置为0 C++11在数组列表初始化时,禁止缩窄转换 C-风格字符串以\0结尾,不是\0结尾的字符数组不是字符串 任何两个由空白(空格,制表符,换行符)分隔的字符串常量都将自己拼成一个 sizeof运算符指出整个数组的长度,而strlen

Scala 深入浅出实战经典 第54讲:Scala中复合类型实战详解

王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 腾讯微云:http://url.cn/TnGbdC 360云盘:http://yunpan.cn/cQ4c2UALDjSKy 访问密码 45e2土豆:http://www.tudou.com/programs/view/a6qIB7SqOlc/优酷:http://v.youku.com/v_show/id_

C++ Primer Plus 复合类型

复合类型是指基于基本整型和浮点类类型而创建的数据格式. 1. 数组 1.1 声明数组时,必须指定元素数目,且元素数目必须是编译时已知的,所以变量(运行时设置的)不可以用来指定数目: 1.2 如果只对数组的一部分进行初始化,则编译器将其他元素设置为0; 所以初始化整个数组为0可以--int array[10] = {0}; 注: C++ 11支持去掉代码中的等号以及0,且不支持缩窄转换; 2. 字符串(C-风格字符串) 2.1 C++处理字符串有两种方式--C-风格字符串 & string类库;

《c++primer笔记》复合类型--引用

复合类型:基于其他类型定义的语言 这里介绍两种 引用 指针 声明语句:一个基本数据类型,一个声明符列表组成 引用:为对象起另外一个名字 引用类型:引用另外一种类型 int ival = 1024; int &refVal = ival;        //refVal指向ival(是ival的另一个名字) int &refVal2;              //报错,引用必须被初始化 一般初始化变量:初始值会被拷贝到新建的对象中 定义引用:把引用和他的初始值绑定在一起,而不是拷贝给引用.

Go语言学习笔记(4)复合类型

  Go语言的复合类型,包括数组.切片和映射等. 值.指针和引用类型 通常情况下Go语言中的变量持有相应的值.也就是说,我们可以将一个变量想象成它所持有的值来使用.其中有些例外,通道.函数.方法.映射.切片是 引用变量,它们持有的都是引用,也即保存指针的变量.值在传递给函数或者方法的时候会被复制一次,对于布尔类型和数值类型来说这非常廉价,但是对于大型变 量代价却非常大.而且复制传参的方式,修改值只是修改了副本,这能保证原始变量不被修改,但也一定程度上增加了修改原始值的麻烦.幸好在Go语言中有指

c++primerplus(第六版)编程题——第4章(复合类型)

声明:作者为了调试方便,每一章的程序写在一个工程文件中,每一道编程练习题新建一个独立文件,在主函数中调用,我建议同我一样的初学者可以采用这种方式,调试起来会比较方便. (具体方式参见第3章模板) 1.编写一个程序,如下述输出示例所示的那样请求显示信息:(注意:该程序应该接受的名字包含多个单词,另外,程序将向下调整成绩,即向上调一个字母.假设用户请求A.B 或C,所以不用担心D和F之间的空档.) #include <iostream> #include <string> #inclu