【啃书C++Primer5】-c++有些理论基础需要了解,墙裂建议看看原书,有太多细节需要注意了

  • 任何常用的编程语言都具备一组公共的语法特征,不同语言仅在特征的细节上有所区别。要想学习并掌握–种编程语言,理解其语法特征的实现细节是第一步。最基本的特征包括:

    • 整型、字符型等内置类型变量,用来为对象命名

    • 表达式和语句,用于操纵上述数据类型的具体值

    • if或while等控制结构,这些结构允许我们有选择地执行-些语句或者重复地执行一些语句

    • 函数,用于定义可供随时调用的计算单元

  • C++提供了一组内置数据类型、相应的运算符以及为数不多的几种程序流控制语句,这些元素共同构成了C++语言的基本形态。以这些元素为基础,我们可以编写出规模庞大、结构复杂、用于解决实际问题的软件系统。仅就C++的基本形态来说,它是一种简单的编程语言,其强大的能力显示于它对程序员自定义数据结构的支持。这种支持作用巨大,显而易见的一个事实是,C++语言的缔造者无须洞悉所有程序员的要求,而程序员恰好可以通过自主定义新的数据结构来使语言满足他们各自的需求。

  • C++中最重要的语法特征应该就是类了,通过它,程序员可以定义自己的数据类型。为了与C++的内置类型区别开来,它们通常被称为“类类型(class type)”。在一些编程语言中,程序员自定义的新类型仅能包含数据成员;另外一些语言,比如C++,则允许新类型中既包含数据成员,也包含函数成员。C++语言主要的一个设计目标就是让程序员自定义的数据类型像内置类型一样好用。基于此,标准C++库实现了丰富的类和函数。

  • 基本内置类型

    • C++定义了一套包括算术类型( arithmetic type)和空类型(void)在内的基本数据类型。其中算术类型包含了字符、整型数、布尔值和浮点数。空类型不对应具体的值,仅用于一些特殊的场合,例如最常见的是,当函数不返回任何值时使用空类型作为返回类型。

    • 算术类型分为两类:整型(integral type,包括字符和布尔类型在内)和浮点型。算术类型的尺寸(也就是该类型数据所占的比特数)在不同机器上有所差别。下表列出了C++标准规定的尺寸的最小值,同时允许编译器赋予这些类型更大的尺寸。某一类型所占的比特数不同,它所能表示的数据范围也不一样。

    • 类型含义最小尺寸
      bool布尔类型未定义
      char字符8位
      wchar_t宽字符16位
      char16_tUnicode字符16位
      char32_tUnicode字符32位
      short短整型16位
      int整型16位
      long长整型32位
      long long长整型64位
      float单精度浮点数6位有效数字
      double双精度浮点数10位有效数字
      long double扩展精度浮点数10位有效数字
    • 布尔类型(bool)的取值是真( true)或者假( false)。C++提供了几种字符类型,其中多数支持国际化。基本的字符类型是char,一个char的空间应确保可以存放机器基本字符集中任意字符对应的数字值。也就是说,一个char的大小和一个机器字节一样。其他字符类型用于扩展字符集,如 wchar_t、char16_t、char32_t。wchar_t类型用于确保可以存放机器最大扩展字符集中的任意一个字符,类型 charl6_t和char32_t则为Unicode字符集服务(Unicode是用于表示所有自然语言中字符的标准)。除字符和布尔类型之外,其他整型用于表示(可能)不同尺寸的整数。C++语言规定一个int至少和一个short一样大,一个long至少和一个int一样大,一个long long至少和一个long一样大。其中,数据类型long long是在C++11中新定义的。

    • 大多数计算机以2的整数次幂个比特作为块来处理内存,可寻址的最小内存块称为“字节(byte)”,存储的基本单元称为“字(word”,它通常由几个字节组成。在C++语言中,一个字节要至少能容纳机器基本字符集中的字符。大多数机器的字节由8比特构成,字则由32或64比特构成,也就是4或8字节。大多数计算机将内存中的每个字节与一个数字(被称为“地址(address)”)关联起来,在一个字节为8比特、字为32比特的机器上,我们可能看到一个字的内存区域如下所示:其中,左侧是字节的地址,右侧是字节中8比特的具体内容。

    • 在这里插入图片描述

    • 我们能够使用某个地址来表示从这个地址开始的大小不同的比特串,例如,我们可能会说地址736424的那个字或者地址736427的那个字节。为了赋予内存中某个地址明确的含义,必须首先知道存储在该地址的数据的类型。类型决定了数据所占的比特数以及该如何解释这些比特的内容。如果位置736424处的对象类型是float,并且该机器中float 以32比特存储,那么我们就能知道这个对象的内容占满了整个字。这个 float 数的实际值依赖于该机器是如何存储浮点数的。或者如果位置736424处的对象类型是unsigned char,并且该机器使用ISO-Latin-1字符集,则该位置处的字节表示一个分号。

    • 浮点型可表示单精度、双精度和扩展精度值。C++标准指定了一个浮点数有效位数的最小值,然而大多数编译器都实现了更高的精度。通常,float 以1个字(32比特)来表示,double 以2个字(64比特)来表示,long double 以3或4个字(96或128比特)来表示。一般来说,类型float和 double分别有7和16个有效位;类型long double则常常被用于有特殊浮点需求的硬件,它的具体实现不同,精度也各不相同

  • 带符号类型和无符号类型

    • 除去布尔型和扩展的字符型之外,其他整型可以划分为带符号的(signed)和无符号的(unsigned)两种。带符号类型可以表示正数、负数或0,无符号类型则仅能表示大于等于0的值。类型int、short、long和long long都是带符号的,通过在这些类型名前添加unsigned就可以得到无符号类型,例如unsigned long。类型unsigned int可以缩写为unsigned。

    • 与其他整型不同,字符型被分为了三种: char、signed char和 unsigned char。特别需要注意的是:类型char和类型signed char并不一样。尽管字符型有三种,但是字符的表现形式却只有两种:带符号的和无符号的。类型char实际上会表现为上述两种形式中的一种,具体是哪种由编译器决定。无符号类型中所有比特都用来存储值,例如,8比特的unsigned char可以表示0至255区间内的值。

  • 和C语言一样,C的设计准则之一也是尽可能地接近硬件。C++的算术类型必须满足各种硬件特质,所以它们常常显得繁杂而令人不知所措。事实上,大多数程序员能够(也应该)对数据类型的使用做出限定从而简化选择的过程。以下是选择类型的一些经验准则:

    • 当明确知晓数值不可能为负时,选用无符号类型。

    • 使用int执行整数运算。在实际应用中,short常常显得太小而 long一般和int有一样的尺寸。如果你的数值超过了int 的表示范围,选用long long.

    • 在算术表达式中不要使用char或 bool,只有在存放字符或布尔值时才使用它们。因为类型 char在一些机器上是有符号的,而在另一些机器上又是无符号的,所以如果使用char进行运算特别容易出问题。如果你需要使用一个不大的整数,那么明确指定它的类型是signed char或者unsigned char.

    • 执行浮点数运算选用 double,这是因为float通常精度不够而且双精度浮点数和单精度浮点数的计算代价相差无几。事实上,对于某些机器来说,双精度运算甚至比单精度还快。long double提供的精度在一般情况下是没有必要的,况且它带来的运行时消耗也不容忽视。

  • 类型转换

    • 对象的类型定义了对象能包含的数据和能参与的运算,其中一种运算被大多数类型支持,就是将对象从一种给定的类型转换(convert)为另一种相关类型。

    • 当在程序的某处我们使用了一种类型而其实对象应该取另一种类型时,程序会自动进行类型转换。此处,有必要说明当给某种类型的对象强行赋了另一种类型的值时,到底会发生什么。

  • 类型所能表示的值的范围决定了转换的过程:

    • 当我们把一个非布尔类型的算术值赋给布尔类型时,初始值为0则结果为false,否则结果为true。

    • 当我们把一个布尔值赋给非布尔类型时,初始值为false则结果为o,初始值为true则结果为1。

    • 当我们把一个浮点数赋给整数类型时,进行了近似处理(取整处理)。结果值将仅保留浮点数中小数点之前的部分

    • 当我们把一个整数值赋给浮点类型时,小数部分记为0。如果该整数所占的空间超过了浮点类型的容量,精度可能有损失

    • 当我们赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。例如,8比特大小的unsigned char可以表示o至255区间内的值,如

      我们赋了一个区间以外的值,则实际的结果是该值对256取模后所得的余数。因此,把-1赋给8比特大小的unsigned char所得的结果是255。

    • 当我们赋给带符号类型一个超出它表示范围的值时,结果是未定义的(undefined)。此时,程序可能继续工作、可能崩溃,也可能生成垃圾数据。

  • 尽管我们不会故意给无符号对象赋一个负值,却可能(特别容易)写出这么做的代码。例如,当一个算术表达式中既有无符号数又有int 值时,那个int值就会转换成无符号数。把int转换成无符号数的过程和把int直接赋给无符号变量一样:

    •     unsigned u = 10;
          int i = -42;
          std::cout << i + i << std::endl;
          std::cout << u + i << std::endl;
          return 0;
      
    • 在这里插入图片描述

    • 在第一个输出表达式里,两个(负)整数相加并得到了期望的结果。在第二个输出表达式里,相加前首先把整数-42转换成无符号数。把负数转换成无符号数类似于直接给无符号数赋一个负值,结果等于这个负数加上无符号数的模。当从无符号数中减去一个值时,不管这个值是不是无符号数,我们都必须确保结果不能是一个负值:

    •     unsigned u1 = 42, u2 = 10;
          std::cout << u1 - u2 << std::endl;
          std::cout << u2 - u1 << std::endl;
          return 0;
      
    • 在这里插入图片描述

  • 字面值常量

    • 我们可以将整型字面值写作十进制数、八进制数或十六进制数的形式。以0开头的整数代表八进制数,以0x或0x开头的代表十六进制数。例如,我们能用下面的任意一种形式来表示数值20: 20 ( 十进制 ) ; 024 ( 八进制 ) ; 0 X 14 ( 十六进制 ) 20(十进制);024(八进制);0X14(十六进制) 20(十进制);024(八进制);0X14(十六进制).

    • 整型字面值具体的数据类型由它的值和符号决定。默认情况下,十进制字面值是带符号数,八进制和十六进制字面值既可能是带符号的也可能是无符号的。十进制字面值的类型是int、long和long long中尺寸最小的那个(例如,三者当中最小是int),当然前提是这种类型要能容纳下当前的值。八进制和十六进制字面值的类型是能容纳其数值的int、unsigned int、long、unsigned long、long long和unsigned long long中的尺寸最小者。如果一个字面值连与之关联的最大的数据类型都放不下,将产生错误。类型short没有对应的字面值。我们将以后缀代表相应的字面值类型。

  • 转义序列

    • 有两类字符程序员不能直接使用:一类是不可打印(nonprintable)的字符,如退格或其他控制字符,因为它们没有可视的图符;另一类是在C++语言中有特殊含义的字符(单引号、双引号、问号、反斜线)。在这些情况下需要用到转义序列(escape sequence),转义序列均以反斜线作为开始,C++语言规定的转义序列包括:

    • 符号备注符号备注符号备注
      \n换行符\t横向制表符\v纵向制表符
      \a报警符\b退格符\\反斜线
      ?问号\r回车符\f进纸符
      单引号"双引号
    • 在这里插入图片描述

    • 对于一个整型字面值来说,我们能分别指定它是否带符号以及占用多少空间。如果后缀中有u,则该字面值属于无符号类型,也就是说,以u为后缀的十进制数、八进制数或十六进制数都将从unsigned int、 unsigned long 和 unsigned long long中选择能匹配的空间最小的一个作为其数据类型。如果后缀中有L,则字面值的类型至少是long;如果后缀中有LL,则字面值的类型将是long long 和unsigned long long 中的一种。显然我们可以将u与工或LL合在一起使用。例如,以UL为后缀的字面值的数据类型将根据具体数值情况或者取unsigned long,或者取unsigned long long。

  • 变量

    • 变量提供一个具名的、可供程序操作的存储空间。C++中的每个变量都有其数据类型,数据类型决定着变量所占内存空间的大小和布局方式、该空间能存储的值的范围,以及变量能参与的运算。对C++程序员来说,“变量(variable)”和“对象(object)”一般可以互换使用。
  • C+程序员们在很多场合都会使用对象(object)这个名词。通常情况下,对象是指一块能存储数据并具有某种类型的内存空间。一些人仅在与类有关的场景下才使用“对象”这个词。另一些人则已把命名的对象和未命名的对象区分开来,他们把命名了的对象叫做变量。还有一些人把对象和值区分开来,其中对象指能被程序修改的数据,而值(value)指只读的数据。
    本书遵循大多数人的习惯用法,即认为对象是具有某种数据类型的内存空间。我们在使用对象这个词时,并不严格区分是类还是内置类型,也不区分是否命名或是否只读

  • C++语言定义了初始化的好几种不同形式,这也是初始化问题复杂性的一个体现。例如,要想定义一个名为units_sold的int变量并初始化为o,以下的4条语句都可以做到这一点:

    • int units_sold = 0;
      int units_sold = {0};
      int units_sold{0};
      int units_sold(0);
      
  • 未初始化的变量含有一个不确定的值,使用未初始化变量的值是一种错误的编程行为并且很难调试。尽管大多数编译器都能对一部分使用未初始化变量的行为提出警告,但严格来说,编译器并未被要求检查此类错误。使用未初始化的变量将带来无法预计的后果。有时我们足够幸运,一访问此类对象程序就崩溃并报错,此时只要找到崩溃的位置就很容易发现变量没被初始化的问题。另外一些时候,程序会一直执行完并产生错误的结果。更糟糕的情况是,程序结果时对时错、无法把握。而且,往无关的位置添加代码还会导致我们误以为程序对了,其实结果仍旧有错。建议初始化每一个内置类型的变量。虽然并非必须这么做,但如果我们不能确保初始化后程序安全,那么这么做不失为一种简单可靠的方法

  • 为了支持分离式编译,C++语言将声明和定义区分开来。声明(declaration)使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义( definition)负责创建与名字关联的实体。变量声明规定了变量的类型和名字,在这一点上定义与之相同。但是除此之外,定义还申请存储空间,也可能会为变量赋一个初始值

  • 任何包含了显式初始化的声明即成为定义。我们能给由extern关键字标记的变量赋一个初始值,但是这么做也就抵消了extern的作用。extern语句如果包含初始值就不再是声明,而变成定义了;在函数体内部,如果试图初始化一个由extern关键字标记的变量,将引发错误。声明和定义的区别看起来也许微不足道,但实际上却非常重要。如果要在多个文件中使用同一个变量,就必须将声明和定义分离。此时,变量的定义必须出现在且只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义

  • C++是一种静态类型(statically typed)语言,其含义是在编译阶段检查类型。其中,检查类型的过程称为类型检查(type checking)。我们已经知道,对象的类型决定了对象所能参与的运算。在C++语言中,编译器负责检查数据类型是否支持要执行的运算,如果试图执行类型不支持的运算,编译器将报错并且不会生成可执行文件。程序越复杂,静态类型检查越有助于发现问题。然而,前提是编译器必须知道每一个实体对象的类型,这就要求我们在使用某个变量之前必须声明其类型

  • C++的标识符(identifier)由字母、数字和下画线组成,其中必须以字母或下画线开头。标识符的长度没有限制,但是对大小写字母敏感。同时,C++也为标准库保留了一些名字。用户自定义的标识符中不能连续出现两个下画线,也不能以下画线紧连大写字母开头。此外,定义在函数体外的标识符不能以下画线开头

  • 变量命名规范

    • 标识符要能体现实际含义。

    • 变量名一般用小写字母,如 index,不要使用Index或INDEX。

    • 用户自定义的类名一般以大写字母开头,如Sales_item。

    • 如果标识符由多个单词组成,则单词间应有明显区分,如 student_loan或studentLoan,不要使用studentloan。

  • C++关键字

    • 在这里插入图片描述
  • C++操作符替代名

    • 在这里插入图片描述
  • 名字的作用域

    • 不论是在程序的什么位置,使用到的每个名字都会指向一个特定的实体:变量、函数、类型等。然而,同一个名字如果出现在程序的不同位置,也可能指向的是不同实体。作用域(scope)是程序的一部分,在其中名字有其特定的含义。C++语言中大多数作用域都以花括号分隔。同一个名字在不同的作用域中可能指向不同的实体。名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束

    • 名字main定义于所有花括号之外,它和其他大多数定义在函数体之外的名字一样拥有全局作用域( global scope)。一旦声明之后,全局作用域内的名字在整个程序的范围内都可使用。名字sum定义于main函数所限定的作用域之内,从声明sum开始直到ma函数结束为止都可以访问它,但是出了main函数所在的块就无法访问了,因此说变量sum拥有块作用域(block scope)。名字val定义于for语句内,在for语句之内可以访问val,但是在main函数的其他部分就不能访问它了。一般来说,在对象第一次被使用的地方附近定义它是一种好的选择,因为这样做有助于更容易地找到变量的定义。更重要的是,当变量的定义与它第一次被使用的地方很近时,我们也会赋给它一个比较合理的初始值

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

    • 在这里插入图片描述

    • 一般在初始化变量时,初始值会被拷贝到新建的对象中。然而定义引用时,程序把引用和它的初始值绑定(bind)在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字。为引用赋值,实际上是把值赋给了与引用绑定的对象。获取引用的值,实际上是获取了与引用绑定的对象的值。同理,以引用作为初始值,实际上是以与引用绑定的对象作为初始值.

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

      • 在这里插入图片描述
    • 引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起:

      • 在这里插入图片描述
  • 指针:指针( pointer)是“指向(point to)”另外一种类型的复合类型。与引用类似,指针也实现了对其他对象的间接访问。然而指针与引用相比又有很多不同点。其一,指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。其二,指针无须在定义时赋初值。和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。定义指针类型的方法将声明符写成*d 的形式,其中d是变量名。如果在一条语句中定义了几个指针变量,每个变量前面都必须有符号*:

    • 在这里插入图片描述
  • 获取对象的地址:指针存放某个对象的地址,要想获取该地址,需要使用取地址符(操作符&):

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k9hzrb3D-1684153356405)(C:\Users\23105\AppData\Roaming\marktext\images\2023-05-15-16-14-57-image.png)]

    • 第二条语句把p定义为一个指向int的指针,随后初始化p令其指向名为ival 的int对象。因为引用不是对象,没有实际地址,所以不能定义指向引用的指针。指针的类型都要和它所指向的对象严格匹配:

    • 在这里插入图片描述

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

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

    • 指向一个对象。

    • 指向紧邻对象所占空间的下一个位置。

    • 空指针,意味着指针没有指向任何对象。

    • 无效指针,也就是上述情况之外的其他值。

  • 利用指针访问对象

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

    •     int ival = 2023;
          int* p = &ival;
          std::cout << *p << std::endl;
          std::cout << p << std::endl;
          return 0;
      
    • 在这里插入图片描述

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

    • 在这里插入图片描述

    • 如上述程序所示,为*p赋值实际上是为p所指的对象赋值。解引用操作仅适用于那些确实指向了某个对象的有效指针。

    • 像&和*这样的符号,既能用作表达式里的运算符,也能作为声明的一部分出现,符号的上下文决定了符号的意义:

    • 在这里插入图片描述

    • 在声明语句中,&和*用于组成复合类型;在表达式中,它们的角色又转变成运算符。在不同场景下出现的虽然是同一个符号,但是由于含义截然不同,所以我们完全可以把它当作不同的符号来看待

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

    • 在这里插入图片描述

    • 当用到一个预处理变量时,预处理器会自动地将它替换为实际值,因此用NULL初始化指针和用0初始化指针是一样的。在新标准下,现在的C++程序最好使用nullpt同时尽量避免使用NULL。把int变量直接赋给指针是错误的操作,即使int变量的值恰好等于0也不行

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

  • 赋值和指针

    • 指针和引用都能提供对其他对象的间接访问,然而在具体实现细节上二者有很大不同,其中最重要的一点就是引用本身并非一个对象。一旦定义了引用,就无法令其再绑定到另外的对象,之后每次使用这个引用都是访问它最初绑定的那个对象。指针和它存放的地址之间就没有这种限制了。和其他任何变量(只要不是引用)一样,给指针赋值就是令它存放一个新的地址,从而指向一个新的对象:

    • 在这里插入图片描述

    • 有时候要想搞清楚一条赋值语句到底是改变了指针的值还是改变了指针所指对象的值不太容易,最好的办法就是记住赋值永远改变的是等号左侧的对象。意思是为pi赋一个新的值,也就是改变了那个存放在pi内的地址值。

  • 对于两个类型相同的合法指针,可以用相等操作符(==)或不相等操作符(!=)来比较它们,比较的结果是布尔类型。如果两个指针存放的地址值相同,则它们相等;反之它们不相等。这里两个指针存放的地址值相同(两个指针相等)有三种可能:它们都为空、都指向同一个对象,或者都指向了同一个对象的下一地址。需要注意的是,一个指针指向某对象,同时另一个指针指向另外对象的下一地址,此时也有可能出现这两个指针值相同的情况,即指针相等。

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

    • 在这里插入图片描述

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

  • 理解复合类型的声明

    • 如前所述,变量的定义包括一个基本数据类型(base type)和一组声明符。在同一条定义语句中,虽然基本数据类型只有一个,但是声明符的形式却可以不同。也就是说,一条定义语句可能定义出不同类型的变量:

    • // i是一个int型的数,p是一个int型指针,r是一个int型引用
      int i = 1024, *p = &i, &r = i;
      
    • 很多程序员容易迷惑于基本数据类型和类型修饰符的关系,其实后者不过是声明符的一部分罢了

  • 定义多个变量

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

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

    • int* p1,p2;//p1是int型指针,p2是int类型
      
    • 涉及指针或引用的声明,一般有两种写法。第一种把修饰符和变量标识符写在一起:

    • int *p1,*p2;//p1和p2都是int类型指针
      
    • 这种形式着重强调变量具有的复合类型。第二种把修饰符和类型名写在一起,并且每条语句只定义一个变量:

    • int *p1;
      int *p2;
      
    • 上述两种定义指针或引用的不同方法没有孰对孰错之分,关键是选择并坚持其中的一种写法,不要总是变来变去。

  • 指向指针的指针

    • 一般来说,声明符中修饰符的个数并没有限制。当有多个修饰符连写在一起时,按照其逻辑关系详加解释即可。以指针为例,指针是内存中的对象,像其他对象一样也有它的地址,因此允许把指针的地址再存放到另一个指针当中。通过*的个数可以区分指针的级别。也就是说,**表示指向指针的指针,***表示指向指针的指针的指针,以此类推:

    • 在这里插入图片描述

    • 此处pi是指向int型数的指针,而ppi是指向int型指针的指针,下图描述了它们之间的关系。

    • 在这里插入图片描述

    •     int ival = 2023;
          int* pi = &ival;
          int** ppi = &pi;
          std::cout << ival << std::endl;
          std::cout << &ival << std::endl;
          std::cout << *pi << std::endl;
          std::cout << pi << std::endl;
          std::cout << &pi << std::endl;
          std::cout << **ppi << std::endl;
          std::cout << *ppi << std::endl;
          std::cout << ppi << std::endl;
          std::cout << &ppi << std::endl;
          return 0;
      
    • 在这里插入图片描述

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

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

    •     int i = 2023;
          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指针。面对一条比较复杂的指针或引用的声明语句时,从右向左阅读有助于弄清楚它的真实含义

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羞儿

写作是兴趣,打赏看心情

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值