zoukankan      html  css  js  c++  java
  • 第9讲——内存模型

    首先谈一下这一讲的主要内容。

    • 单独编译
    • 存储连续性、作用域和链接性
    • 定位new运算符

    为何要讲这块知识??C++为在内存中存储数据方面提供了多种选择。通常大型程序都由多个源代码文件组成,这些文件可能共享一些数据。这样的程序涉及到程序文件的单独编译,这些我们都要学习。

    【单独编译】

    我们常常将组件函数放在独立的文件中。

    之前我们学习到,可以单独编译这些文件,然后将它们链接成可执行的程序。(通常,C++编译器既编译程序,也管理链接器。)如果只修改了一个文件,则可以只重新编译该文件,然后将它与其他文件的编译版本链接。这使得管理大程序更便捷。

    此外,大多数C++环境都提供了其他工具来帮助管理。例如,UNIX和Linux系统都具有make程序,可以跟踪程序依赖的文件以及这些文件的最后修改时间。运行make时,如果它检测到上次编译后修改了源文件,make将记住重新构建程序所需的步骤。大多数集成开发环境(包括Microsoft Visual C++、Apple Xcode和Freescale CodeWarrior)都在Project菜单中提供了类似的工具。

    现在我们来看一个简单的示例。我们不是要从中了解编译的细节(这取决于实现),而是要重点介绍更通用的方面,如设计。

     1 // strctfun.cpp -- functions with a structure argument
     2 #include <iostream>
     3 #include <cmath>
     4 
     5 // structure declarations
     6 struct polar
     7 {
     8     double distance;      // distance from origin
     9     double angle;         // direction from origin
    10 };
    11 struct rect
    12 {
    13     double x;             // horizontal distance from origin
    14     double y;             // vertical distance from origin
    15 };
    16 
    17 // prototypes
    18 polar rect_to_polar(rect xypos);
    19 void show_polar(polar dapos);
    20 
    21 int main()
    22 {
    23     using namespace std;
    24     rect rplace;
    25     polar pplace;
    26 
    27     cout << "Enter the x and y values: ";
    28     while (cin >> rplace.x >> rplace.y)  // slick use of cin
    29     {
    30         pplace = rect_to_polar(rplace);
    31         show_polar(pplace);
    32         cout << "Next two numbers (q to quit): ";
    33     }
    34     cout << "Done.
    ";
    35     return 0;
    36 }
    37 
    38 // convert rectangular to polar coordinates
    39 polar rect_to_polar(rect xypos)
    40 {
    41     using namespace std;
    42     polar answer;
    43 
    44     answer.distance =
    45         sqrt( xypos.x * xypos.x + xypos.y * xypos.y);
    46     answer.angle = atan2(xypos.y, xypos.x);
    47     return answer;      // returns a polar structure
    48 }
    49 
    50 // show polar coordinates, converting angle to degrees
    51 void show_polar (polar dapos)
    52 {
    53     using namespace std;
    54     const double Rad_to_deg = 57.29577951;
    55 
    56     cout << "distance = " << dapos.distance;
    57     cout << ", angle = " << dapos.angle * Rad_to_deg;
    58     cout << " degrees
    ";
    59 }
    View Code

     上面这段程序将直角坐标转换为极坐标,然后显示结果。

    假设我们要分解上述程序,将支持函数放在一个独立的文件中。我们不能简单地以main()之后的虚线为界,将原来的文件分为两个。

    问题在于,main()和其他两个函数使用了同一个结构声明,因此两个文件都应该包含该声明。简单地将它们输入进去无疑是自找麻烦,即使正确地复制了结构声明,如果以后要作修改,则必须记住对这两组声明都进行修改。简而言之,将一个程序放在多个文件中将引出新问题。

    显然,我们所有人都不希望麻烦,所以我们想出了提供#include来处理这种情况。哈哈!!与其将结构声明加入到每一个文件中,不如将其放在头文件中,然后在每一个源代码文件中包含该头文件。这样,要修改结构声明时,只需在头文件中做一次改动即可。另外,也可以将函数原型放在头文件中。

    所以,原来的程序将分为三部分:

    1. 头文件:包含结构声明和使用这些结构的函数的原型
    2. 源代码文件:包含与结构有关的函数的代码
    3. 源代码文件:包含调用与结构相关的函数的代码

    别小看它,这是一种非常有用的组织程序的策略。例如,如果编写另一个程序时,也需要使用这些函数,则只需包含头文件,并将函数文件添加到项目列表或make列表中即可。另外,这种组织方式也与OOP方法一致。一个文件(头文件)包含了用户定义类型的定义;另一个文件包含操纵用户定义类型的函数的代码。这两个文件组成了一个软件包,可用于各种程序中。

    请不要将函数定义或变量声明放到头文件中。因为这样做对于简单的情况可能是可行的,但通常会引来麻烦。例如,如果在头文件包含一个函数定义,然后在其他两个文件(属于同一个程序)中包含该文件,则同一个程序中将包含同一个函数的两个定义,除非函数是内联的,否则这将出错。

    下面列出了头文件中常包含的内容:

    • 函数原型
    • 使用#define或const定义的符号常量
    • 结构声明
    • 类声明
    • 模板声明
    • 内联函数

    将结构放在头文件中是可以的,因为它们不创建变量,而只是在源代码文件中声明结构变量时,告知编译器如何创建该结构变量。同样,模板声明不是将被编译的代码,它们指示编译器如何生成与源代码中的函数调用相匹配的函数定义。被声明为const的数据和内联函数有特殊的链接属性(下面将介绍),因此可以放在头文件中而不会引起问题。

    下面3个程序是将上面那个程序分成几个独立部分后得到的结果:

     1 // coordin.h -- structure templates and function prototypes
     2 // structure templates
     3 #ifndef COORDIN_H_
     4 #define COORDIN_H_
     5 
     6 struct polar
     7 {
     8     double distance;    // distance from origin
     9     double angle;        // direction from origin
    10 };
    11 struct rect
    12 {
    13     double x;        // horizontal distance from origin
    14     double y;        // vertical distance from origin
    15 };
    16 
    17 // prototypes
    18 polar rect_to_polar(rect xypos);
    19 void show_polar(polar dapos); 
    20 
    21 #endif
    coordin.h
     1 // file1.cpp -- example of a three-file program
     2 #include <iostream>
     3 #include "coordin.h" // structure templates, function prototypes
     4 using namespace std;
     5 int main()
     6 {
     7     rect rplace;
     8     polar pplace;
     9 
    10     cout << "Enter the x and y values: ";
    11     while (cin >> rplace.x >> rplace.y)  // slick use of cin
    12     {
    13         pplace = rect_to_polar(rplace);
    14         show_polar(pplace);
    15         cout << "Next two numbers (q to quit): ";
    16     }
    17     cout << "Bye!
    ";
    18 // keep window open in MSVC++
    19 /*
    20     cin.clear();
    21     while (cin.get() != '
    ')
    22         continue;
    23     cin.get();
    24 */
    25     return 0; 
    26 }
    file1
     1 // file2.cpp -- contains functions called in file1.cpp
     2 #include <iostream>
     3 #include <cmath>
     4 #include "coordin.h" // structure templates, function prototypes
     5 
     6 // convert rectangular to polar coordinates
     7 polar rect_to_polar(rect xypos)
     8 {
     9     using namespace std;
    10     polar answer;
    11 
    12     answer.distance =
    13         sqrt( xypos.x * xypos.x + xypos.y * xypos.y);
    14     answer.angle = atan2(xypos.y, xypos.x);
    15     return answer;      // returns a polar structure
    16 }
    17 
    18 // show polar coordinates, converting angle to degrees
    19 void show_polar (polar dapos)
    20 {
    21     using namespace std;
    22     const double Rad_to_deg = 57.29577951;
    23 
    24     cout << "distance = " << dapos.distance;
    25     cout << ", angle = " << dapos.angle * Rad_to_deg;
    26     cout << " degrees
    ";
    27 }
    file2

    将上面两个源代码文件(file1、file2)和新的头文件一起进行编译和链接将生成一个可执行程序。

    顺便说一句,虽然我们讨论的是根据文件进行单独编译,但为保持通用性,C++标准使用了术语翻译单元,而不是文件;文件并不是计算机组织信息时的唯一方式。出于简化的目的,我们使用术语文件,但我们可以将其解释为翻译单元。

    注意,在包含头文件时,我们使用“coordin.h”,而不是<coordin.h>。为啥呢?现在我们来复习一下这两种用法的区别。

    如果文件名包含在尖括号中,则C++编译器将在存储标准头文件的主机系统的文件系统中查找;但如果文件名包含在双引号中,则编译器将首先查找当前的工作目录或源代码目录(或其他目录,这取决于编译器)。如果没有在那里找到头文件,则将在标准位置查找。

    因此在包含自己的头文件时,应使用引号而不是尖括号。

    • 注意,只需将源代码文件加入到项目中,而不用加入头文件。这是因为#include指令管理头文件。另外,不要使用#include来包含源代码文件,这样做将导致多重声明。

    既然谈到这个方面了,我们就仔细探讨一下头文件管理。

    在同一个文件中只能将同一个头文件包含一次。记住这个规则很容易,但我们很可能在不知情的情况下将头文件包含多次。例如,可能使用包含了另外一个头文件的头文件。有一种标准的C/C++技术可以避免多次包含同一个头文件。它是基于预处理器编译指令#ifndef(即 if not defined)的。下面的代码片段意味着仅当以前没有使用预处理器编译指令#define定义名称COORDIN_H_时,才处理#ifndef和#endif之间的语句:

    #ifndef COORDIN_H_
    ...
    #endif

    我们通常使用#define语句来创建符号常量(#define MAXIMUM 4096),但只要将#define用于名称,就足以完成该名称的定义,如下所示:

    #define COORDIN_H_

    上面连续3个程序当中的第一个程序使用这种技术是为了将文件内容包含在#ifndef中:

    #ifndef COORDIN_H_
    #define COORDIN_H_
    //place include file contents here
    #endif

    编译器首次遇到该文件时,名称COORDIN_H_没有定义(我们根据include文件名来选择名称,并加上一些下划线,以创建一个在其他地方不太可能被定义的名称)。在这种情况下,编译器将查看#ifndef和#endif之间的内容(这正是我们希望的),并读取定义COORDIN_H_的一行。如果在同一个文件中遇到其他包含coordin.h的代码,编译器将知道COORDIN_H_已经被定义了,从而跳到#endif后面的一行上。注意,这种方法并不能防止编译器将文件包含两次,而只是让它忽略除第一次包含之外的所有内容。大多数标准C和C++头文件都使用这种防护方案。否则,可能在一个文件中定义同一个结构两次,这将导致编译错误。

    下图简要地说明了在UNIX系统中将程序组合起来的步骤:

    在这里,注意只需执行编译命令CC即可,其他步骤将自动完成。g++和gpp命令行编译器以及Borland C++命令行编译器(bcc32.exe)的行为类似。Apple Xcode、Microsoft Visual C++和Embarcadero C++ Builder基本上执行同样的步骤,但它们启动这个过程的方式不同——使用能够创建项目并将其与源代码文件关联起来的菜单。

    【存储持续性、作用域和链接性】

    上面介绍的是多文件程序,接下来我们讨论内存方案,即存储类别如何影响信息在文件间的共享。

    我们来复习一下有关内存的知识。C++使用三种(在C++11中是四种)不同的方案来存储数据,这些方案的区别就在于数据保留在内存中的时间。

    • 自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储持续性为自动的变量。
    • 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在。C++有3种存储持续性为静态的变量。
    • 线程存储持续性(C++11):当前,多核处理器很常见,这些CPU可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。我们将不探讨并行编程。
    • 动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被称为自由存储或堆。

    下面我们介绍其他内容(包括关于各种变量何时在作用域内或可见(可被程序使用)以及链接性的细节)。

    首先是作用域和链接。

    作用域不用多说,我们在C语言中学过。它描述了名称在文件(翻译单元)的多大范围内可见。

    链接性描述了名称如何在不同单元间共享。链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享。自动变量的名称没有链接性,因为它们不能共享。

    C++变量的作用域有多种。作用域为局部的变量只在定义它的代码块中可用。代码块是由花括号括起的一系列语句。例如函数体就是代码块,但可以在函数体内嵌入其他的代码块。作用域为全局(也叫文件作用域)的变量在定义位置到文件结尾之间都可用。自动变量的作用域为局部,静态变量的作用域是全局还是局部取决于它是如何被定义的。在函数原型作用域中使用的名称只在包含参数列表的括号内可用(这就是为什么这些名称是什么以及是否出现都不重要的原因)。在类中声明的成员的作用域为整个类。在名称空间中声明的变量的作用域为整个名称空间(由于名称空间已经引入到C++语言中,因此全局作用域是名称空间作用域的特例)。

    C++函数的作用域可以是整个类或整个名称空间(包括全局的),但不能是局部的,因为不能在代码块内定义函数,如果函数的作用域为局部,则只对它自己是可见的,因此不能被其它函数调用。这样的函数将无法运行。

    学到这里,我们要知道不同的C++存储方式是通过存储持续性、作用域和链接性来描述的。下面来看看各种C++存储方式的这些特征。

    首先探讨最常见的自动存储连续性:

    在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性(只能在当前函数或代码块中访问)。

    具体地说,当程序开始执行这些变量所属的代码块时,将为其分配内存(其作用域的起点为其声明位置);当函数结束后,这些变量都将消失。

    其他一些细节我们都已经耳熟能详了,在此不做赘述。我们只要知道,自动变量只在包含它们的函数或代码块中可见。

    自动变量存在栈中。

    如果使用关键字register,则表明将用CPU寄存器来存储自动变量,目的是提高访问变量的速度。只有自动变量可以使用register关键字。

    熟悉了自动变量,我们再来探讨静态持续变量:

    C++为静态存储持续性变量提供了3种链接性:外部链接性(可在其他文件中访问)、内部链接性(只能在当前文件中访问)和无链接性。

    这3种链接性都在整个程序执行期间存在,与自动变量相比,它们的寿命更长。

    由于静态变量的数目在程序运行期间是不变的,因此程序不需要使用特殊的装置(如栈)来管理它们。

    编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存在。

    另外,如果没有显示地初始化静态变量,编译器将把它设置为0。

    下面我们编写一段程序实现这3种变量:

    创建静态持续变量

    由程序段,我们获悉要想创建链接性为外部的静态持续变量,必须在代码块的外面声明它;要创建链接性为内部的静态持续变量,必须在代码块的外面声明它,并使用static限定符;要想创建没有链接性的静态持续变量,必须在代码块内声明它,并使用static限定符。

    几点说明:正如前面指出的,所有静态持续变量(程序段中的global、one_file和count)在整个程序执行期间都存在。在f1()中声明的变量count的作用域为局部,没有链接性,这意味着只能在f1()函数中使用它,就像自动变量llama一样。然而,与llama不同的是,即使在f1()函数没有被执行时,count也留在内存中。global和one_file的作用域都为整个文件,即在从声明位置到文件结尾的范围内都可以被使用。由于one_file的链接性为内部,因此只能在包含上述代码的文件中使用它;由于global的链接性为外部,因此可以在程序的其他文件中使用它。

    在此,我省略了一些静态变量相关的细节,如果日后我想再探索,自会补充。

    下面讲的就是静态持续性&外部链接性、静态持续性&内部链接性、静态存储持续性&无链接性

    具体内容在书本p310~p317。

    上面讲了一系列不同类型的变量以及变量的几种链接性。这些知识并不能完全覆盖变量的存储信息,我们接下来将学学说明符和限定符。

    有一些关键字,它们被称为存储说明符或cv-限定符,提供了其他有关存储的信息。

    首先列举存储说明符:

    • auto(在C++11中不再是说明符)
    • register
    • static
    • extern
    • thread_local(C++11新增的)
    • mutable

    这些说明符中的大部分我们已经接触过甚至掌握了,在同一个声明中不能使用多个说明符(thread_local除外,它可与static或extern结合使用)。

    auto:在C++11之前,可以在声明中使用关键字auto指出变量为自动变量;但在C++11中,auto用于自动类型推断。

    register:用于在声明中指示寄存器存储,在C++11中,它只是显示地指出变量是自动的。

    static:被用在作用域为整个文件的声明中时,表示内部链接性;被用于局部声明中,表示局部变量的存储持续性为静态的。

    extern:表明是引用声明,即声明引用在其他地方定义的变量。

    thread_local:指出变量的持续性与其所属线程的持续性相同。thread_local变量之于线程,犹如常规静态变量之于整个程序。

    mutable:指出即使结构(或类)变量为const,其某个成员也可以被修改。

    cv-限定符:

    • const
    • volatile

    const:表明内存被初始化后,程序便不能再对它进行修改。

    volatile:表明即使程序代码没有对内存单元进行修改,其值也可能发生变化。(具体解释可百度或参考书本p317)

    详细谈谈const:

    在C++中,const限定符对默认存储类型稍有影响。在默认情况下全局变量的链接性为外部的,但const全局变量的链接性为内部的。

    也就是说,在C++看来,全局const定义就像使用了static说明符一样:

    const int fingers = 10;		//等价于 static const int fingers = 10; 
    int main()
    {  ...
    

    C++修改了常量类型的规则,这让程序员更轻松。

    例如,假设将一组常量放在头文件中,并在同一个程序的多个文件中使用该头文件。那么预处理器将头文件的内容包含到每个源文件中后,所有的源文件都将包含类似下面这样的定义:

    const int fingers = 10;
    const char *warning = "Wak!";

    如果全局const声明的链接性像常规变量那样是外部的,则根据单定义规则,这将出错。也就是说,只能有一个文件可以包含前面的声明,而其他文件必须使用extern关键字来提供引用声明。另外,只有未使用extern关键字的声明才能进行初始化:

    //如果const有外部链接性,那么extern是必要的 
    extern const int fingers;		//不能被初始化 
    extern const char *warning;

    因此,需要为某个文件使用一组定义,而其他文件使用另一组声明。然而,由于外部定义的const数组的链接性是内部的,因此可以在所有文件中使用相同的声明。

    内部链接性还意味着,每个文件都有自己的一组常量,而不是所有文件共享一组常量。每个定义都是其所属文件私有的,这就是能够将常量定义放在头文件中的原因。这样,只要在两个源代码文件中包括同一个头文件,则它们将获得同一组常量。

    如果出于某种原因,程序员希望某个常量的链接性为外部的,则可以使用extern关键字来覆盖默认的内部链接性:

    extern const int states = 50;		//定义 with 外部链接性 

    在这种情况下,必须在所有使用该常量的文件中使用extern关键字来声明它。这与常规外部变量不同,定义常规外部变量时,不必使用extern关键字,但在使用该变量的其他文件中必须使用extern。然而,请记住,鉴于单个const在多个文件之间共享,因此只有一个文件可对其进行初始化。

    在函数或代码块中声明const时,其作用域为代码块,即仅当程序执行该代码块中的代码时,该常量才是可用的。这意味着在函数或代码块中创建常量时,不必担心其名称与其他地方定义的常量发生冲突。

    下面讲述函数链接性和语言链接性(p318-p319)

    最后一块骨头来了,这是大骨头啊。。关于存储方案和动态分配。

    前面介绍C++用来为变量(包括数组和结构)分配内存的5种方案(线程内存除外),它们不适用使用C++运算符new分配的内存,这种内存被称为动态内存。

    p320.

    温故而知新

    1.对于下面的情况,应使用哪种存储方案?

    a。homer是函数的形参。

    b。secret变量由两个文件共享。

    c。topsercret变量由一个文件中所有函数共享,但对于其他文件来说是隐藏的。

    d。beencalled记录包含他的函数被调用的次数。

     

    答:a。自动变量;b。外部变量;c。静态内部变量;d。无链接性的静态变量。

    c补充:也可以在一个未命名的名称空间中定义

     

    2。using声明和using编译指令有何区别?

     

    答:

    ①using声明只使用名称空间中的一个名称,using编译指令使用名称空间中的所有名称;

    ②using声明在遇见同名内部变量(但外部变量不会发生这种情况)时,可能导致名称冲突(使用外部时,同名外部会被隐藏);但using编译指令不会,他会隐藏外部,或者被内部隐藏。

    补充③using声明其作用域与using声明所在的声明区域相同(我知道,但我觉得没必要强调,就相当于声明了一个变量一样)。using编译指令就像在一个包含using声明和名称空间本身的最小声明区域中声明了这些名称一样。

     

    3。重新编写下面的代码,使其不使用using声明和using编译指令。

    #include<iostream>
    using namespace std;
    
    int main()
    {
        double x;
        cout << "Enter value: ";
        while (! (cin>>x) ){
            cout << "Bad input. Please enter a number: ";
            cin.clear();
            while (cin.get() != '
    ' )
                continue;
        }
        cout << "Value = " << x << endl;
        return 0;
    }
    View Code

    答:修改为:

    #include<iostream>
    int main()
    {
        double x;
        std::cout << "Enter value: ";
        while (! (std::cin>>x) ){
            std::cout << "Bad input. Please enter a number: ";
            std::cin.clear();
            while (std::cin.get() != '
    ' )
                continue;
        }
        std::cout << "Value = " << x << std::endl;
        return 0;
    }
    View Code 

    4。重新编写下面的代码,使之使用using声明,而不是using编译指令。

    #include<iostream>
    using namespace std;
    
    int main(){
        double x;
        cout << "Enter value: ";
        while (! (cin>>x) ){
            cout << "Bad input. Please enter a number: ";
            cin.clear();
            while (cin.get() != '
    ' )
                continue;
        }
        cout << "Value = " << x << endl;
        return 0;
    }
    View Code

    答:修改为:

    #include<iostream>
    using std::cin;
    using std::cout;
    using std::endl;
    int main()
    {
        double x;
        cout << "Enter value: ";
        while (! (cin>>x) ){
            cout << "Bad input. Please enter a number: ";
            cin.clear();
            while (cin.get() != '
    ' )
                continue;
        }
        cout << "Value = " << x << endl;
        return 0;
    }
    View Code

    5。在一个文件中调用average(3,6)函数时,它返回两个int参数平均值,在同一个程序的另一个文件中调用时,它返回两个int参数的double平均值。应如何实现?

     

    答:需要将两个函数的链接性变为内部。具体方式为:

    第一个文件,使用函数原型:static int average(int a,int b);然后在该文件中加入函数定义;

    第二个文件,使用函数原型:static double average(int a,int b); 然后写对应的函数定义,并加入到该文件之中。

    补充:也可以在未命名的名称空间中包含定义

     

    6.下面的程序由两个文件组成,该程序显示什么内容?

    //file1.cpp

    #include<iostream>
    using namespace std;
    
    void other();
    void another();
    int x = 10;
    int y;
    
    int main()
    {
        cout <<x <<endl;
        {
            int x = 4;
            cout << x << endl;
            cout << y << endl;
        }
        other();
        another();
        return 0;
    }
    
    void other()
    {
        int y = 1;
        cout << "Other: " << x << ", " << y << endl;
    }
    View Code 

    //file2.cpp

    #include<iostream>
    
    using namespace std;
    extern int x;
    
    namespace
    {
        int y = -4;
    }
    
    void another()
    {
        cout << "another() " << x << ", " << y << endl;
    }
    View Code 

    答:显示为:

    10

    4

    0

    Other: 10, 1

    another(): 10, -4

     

    7。下面的代码将显示什么内容?

    #include <iostream>
    using namespace std;
    void other();
    namespace n1
    {
    	int x = 1;
    }
    
    namespace n2
    {
    	int x = 2;
    }
    
    int main()
    {
    	using namespace n1;
    	cout << x << endl;
    	{
    		int x = 4;
    		cout << x << ", " << n1::x << ", " << n2::x << endl;
    	}
    	using n2::x;
    	cout << x << endl;
    	other();
    	return 0;
    }
    
     
    
    void other()
    {
    	using namespace n2;
    	cout << x << endl;
    	{
    		int x = 4;
    		cout << x << ", " << n1::x << ", " << n2::x << endl;
    	}
    	using n2::x;
    	cout << x <<endl;
    }

    显示:

    1

    4, 1, 2

    2

    2

    4, 1, 2

    2

  • 相关阅读:
    POJ 1149 PIGS(Dinic最大流)
    HDU 4514 湫湫系列故事——设计风景线(并查集+树形DP)
    [kuangbin带你飞]专题十一 网络流个人题解(L题留坑)
    BZOJ 3931 网络吞吐量(最短路+拆点最大流)
    学习RMQ-ST表
    `这个符号在mysql中的作用
    用一条mysql语句插入多条数据
    Leetcode 257 Binary Tree Paths 二叉树 DFS
    Leetcode 203 Remove Linked List Elements 链表
    Leetcode 38 Count and Say 传说中的递推
  • 原文地址:https://www.cnblogs.com/xzxl/p/7380708.html
Copyright © 2011-2022 走看看