UP | HOME

cpp-primer-5th

Table of Contents

Chapter 01: Getting Started

Writing a Simple C++ Program

  • 每个cpp程序都有一个或者多个function, 而且其中一个function的名字必须是main,下 面就是一个最简单的cpp程序,什么都不做就是返回零
    int main() {
        return 0;
    }
    
  • 在大多数系统中, 返回值为0意味着success, 而nonzero意味着失败

Compiling and Executing Our Program

  • cpp文件可以使用如下的后缀:
    • cc
    • cxx
    • cpp
    • cp
    • C
  • 在类Unix系统中,我们使用如下的一条命令来把"源代码"编译成"可执行程序"
    CC prog1.cc
    
  • 编译完成的程序,会默认使用a.out这个名字(在windows上面mingw系统编译成a.exe)
  • 运行就直接调用这个a.out就可以了.使用echo $?可以查看main函数的返回值
    c:/code/cpp-in-action/primer5/ch01 >>> CC 01.cc
    c:/code/cpp-in-action/primer5/ch01 >>> ./a.exe
    c:/code/cpp-in-action/primer5/ch01 >>> echo $?
    0
    
  • Windows上面是查看ERRORLEVEL
    > echo %ERRORLEVEL%
    0
    
  • 如果你不想使用a.out或者a.exe,那么可以使用-o参数指定自己的"可执行程序"的名 字
    c:/code/cpp-in-action/primer5/ch01 >>> ls
    01.cc
    c:/code/cpp-in-action/primer5/ch01 >>> g++ -o 01 01.cc
    c:/code/cpp-in-action/primer5/ch01 >>> ls
    01.cc  01.exe
    

A First Look at Input/Output

  • cpp是通过一个叫做iostream的standard library来处理IO.所谓stream就是一系列的 从IO设备读入或输出的字符串
  • 处理IO的object主要有:
    • istream类型的object : cin处理输入
    • ostream类型的object : cout处理输出
    • ostream类型的object : cerr处理warning和error的输出
    • ostream类型的object : clog处理总结信息等
  • 我们下面看一个结合input和output的例子
    #include <iostream>
    
    int main() {
        std::cout << "Enter two numbers:" << std::endl;
        int v1 = 0, v2 = 0;
        std::cin >> v1 >> v2;
        std::cout << "The sum of " << v1 << " and " << v2
                  << " is " << v1 + v2 << std::endl;
        return 0;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Enter two numbers:                             //
    // 3 7                                            //
    // The sum of 3 and 7 is 10                       //
    ////////////////////////////////////////////////////
    

A Word about Comments

  • c++ 兼容c的`/**/`注释方式
  • 同时提供了`//`的行注释方式

Flow of Control

  • flow of control和其他的语言类似,有两种:
    • 知道循环次数的时候,使用for
      #include <iostream>
      
      int main() {
          int sum = 0, val = 1;
          for (int val = 1; val <= 10; ++val) {
              sum += val;
              ++val;
          }
      
          std::cout << "Sum of 1 to 10 inclusive is "
                    << sum << std::endl;
          return 0;
      }
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // Sum of 1 to 10 inclusive is 55                 //
      ////////////////////////////////////////////////////
      
    • 不知道具体循环次数的时候,使用while
      #include <iostream>
      
      int main() {
          int currVal = 0, val = 0;
      
          if (std::cin >> currVal) {
              int cnt = 1;
              while (std::cin >> val) {
                  if (val == currVal){
                      ++cnt;
                  } else {
                      std::cout << currVal << " occurs "
                                << cnt << " times" << std::endl;
                      currVal = val;
                      cnt = 1;
                  }
              } // while loop ends here
              // remember to print the count for the last value in the file
              std::cout << currVal << " occurs "
                        << cnt << " times" << std::endl;
          }
          return 0;
      }
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // 42 42 42 42 42 55 55 62 100 100 100            //
      // 42 occurs 5 times                              //
      // 55 occurs 2 times                              //
      // 62 occurs 1 times                              //
      // 100 occurs 3 times                             //
      ////////////////////////////////////////////////////
      

Introducing Clases

  • 在cpp中,我们可以定义自己的类型:通过关键字class. cpp的特点是,我们自己定义的 类型,和内置类型拥有几乎一样的"性质"

Chapter 02: Variables and basic types

Primitive Build-in Types

  • cpp定义的内置类型(primitive type)包括了:
    • 算术类型(arithmetic type)
    • 特殊类型(void)

Arithmetic Types

  • 算术类型在规范里面,只给了"最小"size, 因为机器字符宽度不同,所导致的最大长度 并不相同.下面是规范规定的最小size
    Type Meaning Minimum Size
    bool boolean 8 bits
    char character 16 bits
    wchar_t wide character 16 bits
    char16_t Unicode character 16 bits
    char32_t Unicode character 32 bits
    short short integer 16 bits
    int integer 16 bits
    long long integer 32 bits
    long long long integer 64 bits
    float single-precision floating-point 6 significant digits
    double double-precision floating-point 10 significant digits
    long double extended-precision floating-point 10 significant digits

Type Conversions

  • type conversion在cpp中是非常容易发生的,比如
    bool b = 42;               // b is true
    int i = b;                 // i has value 1
    i = 3.14;                  // i has value 3
    double pi = i;             // pi has value 3.0
    unsigned char c = -1;      // assuming 8-bit chars, c has value 255
    signed char c2 = 256;      // assuming chars, the value of c2 is undefined
    

Variables

Variable Definitions

  • 变量的定义很简单
    int sum =0;
    Sales_item item; // item has type Sales_item
    
  • 如果一个变量在定义的时候还有有初始值,我们就说它被initialized了
    // ok: price is defined and initialized before it is used to initialize discount
    double price = 109.99;
    
    // ok: call applyDiscount and use the returen value to initialized salePrice
    double salePrice = applyDiscount(price, discount);
    
  • 需要注意的是Initialization和assignment并不相同
    Initialization happens when a variable is given a value when it is
    created. Assignment obliterates an object's current value and replaces
    that value with a new one.
    
  • initialization子所以复杂,是因为cpp定义了很多种"合理的"方法,比如,下面四种初 始化的方法都是合理的
    int units_sold = 0;
    int units_sold = {0};
    int units_sold{0};
    int units_sold(0);
    
  • {}来initialize的方式,是c++11才新近引入的, 名字叫做list initialization
  • list initialization看似不常用,却有一个特殊的作用:编译器会检查初始化是否会 损失精度
    #include <iostream>
    
    int main(int argc, char *argv[])
    {
        long double ld = 3.14159;
        int a{ld};
        int b = {ld};
        std::cout << a << std::endl;
        std::cout << b << std::endl;
        return 0;
    }
    
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // $ g++ -std=c++11 01.cc                         //
    // 01.cc: In function 'int main(int, char**)':    //
    // 01.cc:6:13: warning: narrowing conversion of   //
    // 'ld' from 'long double' to 'int' inside { }    //
    // [-Wnarrowing]                                  //
    //      int a{ld};                                //
    //              ^                                 //
    // 01.cc:7:16: warning: narrowing conversion of   //
    // 'ld' from 'long double' to 'int' inside { }    //
    // [-Wnarrowing]                                  //
    //      int b = {ld};                             //
    //                 ^                              //
    // $ ./a.out                                      //
    // 3                                              //
    // 3                                              //
    ////////////////////////////////////////////////////
    
  • 非list initialization通常是使用'='或者(), 都是可以通过编译并且没有warning的
    #include <iostream>
    
    int main(int argc, char *argv[])
    {
        long double ld = 3.14159;
        int c(ld), d = ld;
    
        std::cout << c << std::endl;
        std::cout << d << std::endl;
    
        return 0;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // $ g++ -std=c++11 02.cc                         //
    // $ ./a.out                                      //
    // 3                                              //
    // 3                                              //
    ////////////////////////////////////////////////////
    
  • 如果声明的时候没有给与一个initializer的话,那么会被给与一个default value:
    • 如果是built-in类型的话:
      1. 定义在function body之外的,全部会被初始化为zero
      2. 定义在function body之内的,会被设置为unitialized状态.local static object(built-in type)是唯一一个特例(虽然在某个function里面,但是在 声明的时候,必定初始化为0了)
        #include <iostream>
        
        int count_calls() {
            static int count;
            return ++count;
        }
        
        int main(int argc, char *argv[])
        {
            for (int i = 0; i < 3; i++) {
                std::cout << count_calls() << std::endl;
            }
            return 0;
        }
        
        ////////////////////////////////////////////////////
        // <===================OUTPUT===================> //
        // 1                                              //
        // 2                                              //
        // 3                                              //
        ////////////////////////////////////////////////////
        
    • 如果不是build-in类型的话:
      1. 如果class的定义允许我们不使用explicit initializer的话,当然可以直接定 义而不给initializer
        std::string empty; // empty implicitly initialized to the empty string
        
      2. 有些class需要explicitly initialized,所以必须给与initializer才可以.

Variable Declarations and Definitions

  • 为了能够让不同的文件来写不同的逻辑部分.c++支持separate compilation.也就是把 一个大的程序,分成多个不同的文件进行编译.
  • 为了支持separate compilation, cpp区分了以下两个概念:
    • 声明 (declaration) : 声明只会指出变量的名字和类型,不会分配内存. 可以简单 的理解为有extern而没有'='的语句.
      extern int i;     // declares but does not define i
      
    • 定义 (definition) : 定义不仅仅会指出变量的名字和类型, 而且会分配内存, 甚 至给出初始值. 简单理解就是1只要有'='或者2只要没有extern的语句都是定义
      // has '=', definition, memory is ready, has default value of 1
      int i = 1;
      // has '=', even extern shown, it is also a definition
      extern double pi = 3.1416;
      // no extern, definition. memory is ready, but no default value
      int j;
      
  • 一个变量可以被声明很多次,但是只能被定义一次. 如果我们要在多个文件里面使用 同一个变量,那么我们要:
    • 只在一个文件里面定义它(只一次)
    • 在其他的使用这个变量的文件中,声明它(多次)

Identifiers

  • 变量标示的要求在cpp里面和其他的语言没什么不同,不能以数字开头(防止和数字混淆)
  • 关键字不能作为变量标示

Scope of a Name

  • 作用域scope在cpp里面,大部分情况下是靠{}来分割的
  • 相同的名字在不同的作用域里面可能代表不同的变量.名字从declare开始,可以被使用, 直到这个作用域(包含declare的scope)结束.
    Names are visible from the point where they are declared until
    the end of the scope in which the declaration appears
    
  • 我们在cpp里面建议在第一次'使用'这个变量的时候,才去定义它, 因为这样容易找到 变量定义(而不是在程序开始的地方), 同时这样容易给变量一个有意义的初始值
  • scope也可以包含其他scope的:
    • 被包含的scope叫做inner scope
    • 包含其他scope的scope叫做outer scope
  • 定义在outer scope的name可能会被inner scope里面的相同名字的变量所掩盖
    #include <iostream>
    
    int reused = 42;      // resued has global scope
    int main() {
        int unique = 0;   // unique has block scope
        std::cout << "use global reused " << reused << " unique is " << unique << std::endl;
    
        int reused = 0;   // new, local object named reused hides global reused
        std::cout << "use local reused  " << reused << " unique is " << unique << std::endl;
    
        std::cout << "explicityly request global reused " << ::reused << " unique is " << unique << std::endl;
    
        return 0;
    }
    
    //////////////////////////////////////////////////////
    // <===================OUTPUT===================>   //
    // use global reused 42 unique is 0                 //
    // use local reused  0 unique is 0                  //
    // explicityly request global reused 42 unique is 0 //
    //////////////////////////////////////////////////////
    

Compound Types

  • 所谓compund type定义如下
    A compound type is a type that is defined in terms of another type.
    
  • cpp中有一些compund type,其中最著名的两个:引用和指针.

References

  • 首先要说明的是,一般来说reference就是指的lvalue reference, 虽然新出来一种rvalue reference
  • 然后我们来看看reference的本质:其本质就是为一个object寻找一个alternative name
    int ival = 1024;
    int &refVal = ival;   // refVal refers to (is another name for) ival
    
  • 我们来比较一下常规的definition和reference的definition之间的差距:
    • 常规的definition : 我们定义的时候, 这个initializer的值就会被"拷贝"到我们 申请的内存(object)里面
    • reference的definition: 我们定义的时候,是把reference给"bind"到initializer (也就是ival).而且reference是一旦初始化,就无法再refer给其他的object.正是因 为无法rebind,所以reference必须在定义的时候赋予初始化值.也就是说下面这行 是不行的
      //Error!
      int &refVal;
      
  • 其实reference说来说去,只是给object起了一个新的名字而已!
    A reference is not an object. Instead, a reference is just another
    name for an already existing object.
    
  • 正是由于reference不是一个object,所以我们不能定义reference to reference.
  • 在大多数情况下(两种特例是const和继承), ref自己的类型必须和initializer的类 型相同
    double dval = 3.14;
    // Error; initializer must be an int object!
    int &refVal5 = dval;
    
  • 而且不能把reference"绑定"道非object的对象, 比如literal或者是general expression 的结果
    // Error! initializer must be an object
    int &refVal4 = 10;
    

Pointers

  • Pointer是C时代的产物, Reference是cpp为了改变Pointer的某些危险特性而设计的, 所以,两者有很多异同点:
    • 相同点: 都是compunt type, 而且是用来indirect acces to other objects
    • 不同点: Pointer是一个object! 这就决定了很多point的"危险的"行为:
      1. pointer是可以被赋值和拷贝的, 而且可以被赋值很多次
      2. pointer定义的时候,甚至可以没有初始化值(因为可以被多次赋值)
      3. 如果pointer定义了以后,没有给与初始化值,那么这个pointer的值是undefined! 非常危险
  • Pointer是用来"保存另外一个object的地址"的object.所以我们要用到取地址符(&), 千万不要和reference前面的`&`混淆:
    • reference前面的&是为了区别'普通declarator'(比如ival) 和'reference declarator' (比如refVal)而加的,是一个语法层面的标记
      int ival = 1024;
      int &refVal = ival;
      
    • pointer是指向某个object的地址的, 所以要在那个object前面加`&`, 这个`&`是一 个操作符,就和`^`一样,进行的是"取地址"操作. 当然了,为了区分'普通declarator' (比如ival)和'pointer declarator'(比如p), 我们要在pointer 前面加个'*'
      int ival = 42;
      int *p = &ival;
      
  • 还是除了const和继承以外,pointer类型必须和initializer相同.
    double dval;
    double *pd = &dval;    // ok: initializer is the address of a double
    double *pd2 = pd;      // ok: initializer is a pointer to double
    
    int *pi = pd;          // error: types of pi and pd differ
    pi = &dval;            // error: assigning the address of a double to a pinter to int
    
  • Pointer的值是存储的是address,这个地址值可以有如下四种可能:
    • 这个地址可以是指向一个object
    • 这个地址可以指向一个object后面的位置
    • 这个地址可以为了null,表示pointer没有指向任何object
    • 这个地址可以是invalid的(比如定义没有给初始化的情况下), 这种情况是最危险的.
  • 前面介绍了取地址操作符`&`, 而把地址还原成object(dereference)的符号是`*` , 需要注意的是我们一定要对valid的地址进行derefernce操作
    #include <iostream>
    
    using namespace std;
    int main(int argc, char *argv[])
    {
        int ival = 42;
        int *p = &ival;
        // * yields the object to which p points
        cout << *p << endl;
    
        // * yields the object; we assign a new value to ival throught p
        *p = 0;
        cout << *p << endl;
        cout << ival << endl;
        return 0;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 42                                             //
    // 0                                              //
    // 0                                              //
    ////////////////////////////////////////////////////
    
  • 如果一个指针不指向任何的object,我们把这个指针叫做null poiner.可以在定义的 时候通过如下方法来定义null pointer
    #include <iostream>
    #include <cstdlib>
    
    int main(int argc, char *argv[])
    {
        //////////////////////////////////////////////////
        // use to include nullptr g++ --std=c++11 06.cc //
        //////////////////////////////////////////////////
        int *p1 = nullptr;
        int *p2 = 0;
        NULL is include in cstdlib
        ///////////////////////////////////////////////////////////
        // Modern C++ programs generally should avoid using NULL //
        // and use nullptr instead                               //
        ///////////////////////////////////////////////////////////
        int *p3 = NULL;
        return 0;
    }
    
  • 虽然NULL在预定义的时候,其值就是0, 但是pointer确不接受integer作为它的rvalue, 因为它会以为你是想要把'这个int的地址'赋予给这个pointer.所以上面三种才是给null pointer 赋值(或初始化的方式)
    #include <iostream>
    #include <cstdlib>
    
    int main(int argc, char *argv[])
    {
        int *p4 = NULL;
    
        int zero = 0;
        ///////////////////////////////////////////////////////////////////////////////
        // 07.cc:9:15: error: invalid conversion from 'int' to 'int*' [-fpermissive] //
        //  int *p5 = zero;                                                          //
        //            ^                                                              //
        ///////////////////////////////////////////////////////////////////////////////
        int *p5 = zero;
        return 0;
    }
    
  • 因为pointer是可以赋值的,所以任何NULL值的pointer都是false,否则都是true
  • 在范型还是不是很成熟的c时代,为了能够应对不同类型的pointer,发明了void* pointer 这种pointer可以指向任何的类型, 而且可以和其他pointer比较(因为都是地址么), 但是不可以对其进行dereference操作,进而控制响应object(因为不知道地址对应的 类型是什么,所以无法决断拿几个bytes的地址)
    #include <iostream>
    
    using namespace std;
    int main(int argc, char *argv[])
    {
        double obj = 3.14, *pd = &obj;
    
        void *pv = &obj;
        pv = pd;
        /////////////////////////////////////////////////////////////////
        // 08.cc:10:14: error: 'void*' is not a pointer-to-object type //
        //  cout << *pv << endl;                                       //
        //           ^                                                 //
        /////////////////////////////////////////////////////////////////
        cout << *pv << endl;
        return 0;
    }
    

Understanding Compound Type Declarations

  • pointer和ref的定义方式非常令人困惑,是因为如下两种方式都是合理的
    int *p1;  // Way1: p1 is a pointer to int
    int* p2;  // Way2: p2 is a pointer to int
    
  • 我们认为第二种定义(Way2)方式不好,因为它看起来好像definition的任务是定义了 一个int* 类型,其实不是的`*`是用来形容p2的, 这个definition的主体是一个int类 型.
  • 第二种定义方式在一次定义超过一个变量的时候会更加的难以区别
    int* p1, p2; // p1 is a pinter to int; p2 is an int
    
  • 所以我们决定在后面都使用第一宠定义方式(Way1).
  • 因为pointer是一个object, 所以它自己也是有地址的,所以我们也可以让另外一个 pointer指向它, 也就是我们常说的pointer to pointer
  • 既然我们使用`*`来标示此变量为指针,那么我们也可以连续的使用多个`*`来标示"指针 的指针",甚至"指针的指针的指针"
    #include <iostream>
    
    using namespace std;
    int main(int argc, char *argv[])
    {
        int ival = 1024;
        int *pi = &ival;        // pi points to an int
        int **ppi = &pi;        // ppi points to a pointer to an int
    
        cout << "The value of ival\n"
             << "direct value:   " << ival << "\n"
             << "indirect value: " << *pi  << "\n"
             << "doubly indirect value: " << **ppi
             << endl;
        return 0;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // The value of ival                              //
    // direct value:   1024                           //
    // indirect value: 1024                           //
    // doubly indirect value: 1024                    //
    ////////////////////////////////////////////////////
    
  • 因为reference不是一个object,所以我们不能让pointer指向reference, 但是我们却 可以为pointer创建reference

const Qualifier

  • const的作用是防止变量被更改, 因为无法被更改,所以定义的时候,必须给与初始值
    const int i = get_size();    // ok: initialized at run time
    const int j = 42;            // ok: initialized at compile time
    const int k;                 // error: k is uninitialized const
    
  • const所赋予变量的"常量性"只在它被改变的那个时候,才起作用,平时const int变量 和int变量并没有什么不同, 比如,我们可以给int类型j赋值,initializer为一个const int 类型
    int i = 42;
    const int ci = i;          // ok: the value in i is copied into ci
    int j = ci;                // ok: the value in ci is copied into j
    
  • 鉴于const常量在定义的时候就必须赋予initializer值,那么单纯的"声明"一个常量就 不可能了, 所以在多个文件里面使用常量是不现实的,
  • 默认情况下, 常量是local to a file的. 所以在不同文件里面的const常量如果重名 了也没有关系,因为它们相当于file_name::const_name
  • 如果你想要在多个文件里面share常量,我们肯定要使用extern
  • 但是我们不光要在"declare"里面使用extern,在"definition"那里也必须使用extern! 否则编译器会把你的const变量直接替换成数值(比如下例中,所有bufSize都直接替换成 1001), 下面就是definition里面也使用extern const的例子
    extern const int bufSize = 1001;
    
    int getSize() {
        return bufSize + 1;
    }
    
  • "declare"里面当然也得使用extern了.
    #include <iostream>
    
    using namespace std;
    int main(int argc, char *argv[])
    {
        extern const int bufSize;
        cout << bufSize << endl;
        return 0;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // $ g++ -o a.out 11.cc 12.cc                     //
    // $ ./a.out                                      //
    // 1001                                           //
    ////////////////////////////////////////////////////
    

References to const

  • const type的object也是object,我们当然可以给一个object绑定一个reference啦, 但是这个reference既然绑定的是const变量,那么它本身是const变量的"另外一个名字" 也就必然无法被赋值了
    const int ci = 1024;
    const int &r1 = ci;     // OK: both reference and underlying object are const
    r1 = 42;                // error: r1 is a reference to const
    
  • 同样的,我们在初始化的时候,reference的类型也必须是const type的,否则无法初始 化成功
    const int ci = 1024;
    int &r2 = ci;        // error: non const reference to a const object!
    
  • 如果铭记reference不是object这个事实,我们就很容易理解, const是无法修饰reference 的.
  • 而reference无法改变初始化binding的对象这个特点,其实让reference本身具有了"const" 属性, 所以reference to什么类型只影响到我们能对reference做什么.而不影响reference binding的这个过程.
  • 前面我们说过,有两种情况下,reference的类型和它要bind的类型可以不"完全match" 这里我们就遇到了第一种: reference to const X类型可以接受"所有能够converted to X类型"的类型作为自己的initializer, 比如
    #include <iostream>
    
    using namespace std;
    int main(int argc, char *argv[])
    {
        double dval = 3.14;
        ////////////////////////////////////////////////////////////////
        // We can initialize a reference to const from any expression //
        // that can be converted to the type of reference             //
        ////////////////////////////////////////////////////////////////
        const int &ri = dval;
    
        cout << "ri is " << ri << endl;
        return 0;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // ri is 3                                        //
    ////////////////////////////////////////////////////
    
  • 如果深究这个问题, 我们可以如下理解, 初始化一个refer to const, 如果类型不符 的话,编译器会做如下的转换,以使得reference to const int 确实bind了一个const int类型
    const int temp = dval;      // create a temporary const int from the double
    const int &ri = temp;       // bind ri to that temporary
    
  • 也就是说编译器创建了一个新的"临时变量". 然后让这个ref to const 来bind这个 临时变量. 这个临时变量是永远找不到了,但是我们有它的ref,也就相当于有它了.
  • 这种赋值,只有在reference to const的时候才有意义. 假设我们的ref不是const的, 那么我们的const ri bind了一个double的变量, 如果我们ri变成了4(本来是3), double 应该怎么变呢?变成4.14? 说不清楚,所以只有ref to const有这种便利.
  • 正是这种便利,让函数的参数更喜欢用const Type &作为参数类型, 因为:
    • 如果实参不需要转换成Type,那么正好减少一次对象复制. 而且这里还用到ref to const 的另外一个特性: ref to const 可以bind一个根本就不是const的对象. 实参不是 const的, 我参数也给它搞成ref to const. 这样可以保证不更改原来实参的值(同 时可以减少一次对象拷贝)!
      #include <iostream>
      
      using namespace std;
      int main(int argc, char *argv[])
      {
          int i = 42;
          int &r1 = i;
          const int &r2 = i;
      
          cout << "r1 is " << r1 << " and r2 is " << r2 << endl;
      
          r1 = 0;
          cout << "r1 is " << r1 << " and r2 is " << r2 << endl;
      
          ///////////////////////////////////////////////////////////////
          // 14.cc:15:8: error: assignment of read-only reference 'r2' //
          //  r2 = 23;                                                 //
          //     ^                                                     //
          ///////////////////////////////////////////////////////////////
          // r2 = 23;
          return 0;
      }
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // r1 is 42 and r2 is 42                          //
      // r1 is 0 and r2 is 0                            //
      ////////////////////////////////////////////////////
      
    • 如果实参需要转换成Type, 那么就产生一个临时的const变量.

Pointers and Const

  • 就像reference一样,我们可以定义pointer to const来bind任意一个类型的变量(只要 可以convert).
  • pointer (ref) to const绑定了某个变量后, 这个变量是可以变的.但是无法通过ref 或者pointer来改变. 也就是说,ref(pointer)'认为'自己绑定了const变量(虽然并不 一定是)
    It may be helpful to think of pointers and references to const as
    pointers or references "that *think* they point or refer to const"
    
  • pointer是object这个悲催的事实,让pointer自己也有可能是const的.(也就是说初始 化的时候绑定了什么变量,就不能再变了). 我们是通过把const放到*后面来强调pointer 本身的const性的(因为const离pointer更近了, 从右往左念就是"p是一个const pointer")
    #include <iostream>
    
    using namespace std;
    int main(int argc, char *argv[])
    {
        int a1 = 0;
        int a2 = 1;
        const int *p1 = &a1;
        cout << *p1 << endl;
        p1 = &a2;
        cout << *p1 << endl;
    
        int *const p2 = &a1;
        cout << *p2 << endl;
    
        //////////////////////////////////////////////////////////////
        // 15.cc:16:8: error: assignment of read-only variable 'p2' //
        //  p2 = &a2;                                               //
        //     ^                                                    //
        //////////////////////////////////////////////////////////////
        // p2 = &a2;
        return 0;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 0                                              //
    // 1                                              //
    // 0                                              //
    ////////////////////////////////////////////////////
    

Top-Level const

  • 从一个更高的高度来审视const,我们会发现const其实是有两种:
    • top-level const: 表示object本身是const, 这种const可以对任何一种object有效 比如: int类型, string类型, class类型,都可以自身变成const
    • low-level const: 表示compound type类型所指向的object是const的.既然是为compound type类型所准备的,所以自然只能适用于reference和pointer
  • pointer是唯一一个可以同时拥有top-level和low-level const属性的类型
    • pointer自己是const的: top-level const
      int i = 0;
      int *const p1 = &i;
      
    • pointer指向的object是const的: low-level const
      const int ci = 42;
      const int *p2 = &ci;
      
    • 同时具有top-level和low-level const
      const int ci = 42;
      const int *const p3 = &ci;
      
  • 区分top-level和low-level const的原因:是在于拷贝的时候:
    • 当我们进行拷贝的时候, top-level const一直是被ignore的
      const int ci = 42;
      int i = ci;   // ok: copying the value of ci; top-level const in ci is ignored
      
      const int *const p3 = &ci;
      const int *p2 = p3;   // ok: pointed-to type matches; to-level const in p3 is ignored
      
    • 当我们进行拷贝的时候, low-level const是绝对不能忽视的!
      #include <iostream>
      
      using namespace std;
      int main(int argc, char *argv[])
      {
          int i = 11;
          const int *p1 = &i;
      
          ////////////////////////////////////////////////
          // 16.cc:8:15: error: invalid conversion from //
          // 'const int*' to 'int*' [-fpermissive]      //
          //  int *p2 = p1;                             //
          //            ^                               //
          ////////////////////////////////////////////////
          // int *p2 = p1;
          return 0;
      }
      

constexpr and Constant Expressions

  • 所谓constant expression就是value无法改变,而且在编译阶段就可以计算值的表达式
  • literal肯定是constant expression
  • 一个object(或者expression)是不是const expression主要取决于两点:
    • object的类型:
      //////////////////////////////////////////////////////////////////
      // alghough staff_size is initialized from a literal, it is not //
      // a const because it is a plain int, not a const int           //
      //////////////////////////////////////////////////////////////////
      int staff_size = 27;
      
    • initializer
      //////////////////////////////////////////////////////////////////////////
      // even though sz is a const, the value of its initializer is not known //
      // until run time, hence, sz is not a constant expression               //
      //////////////////////////////////////////////////////////////////////////
      const int sz = get_size();
      
  • 新的时代,如果我们认为我们的initializer是一个const expression的话,我们可以 使用constexpr来代替const来修饰我们的类型
    Variables declared as constexpr are implicitly const and must be
    initialized by const expressions.
    
  • 一般来说constexpr的initializer必须是const expression, 但是同时也可以是新规 范里面引入的constexpr function
    #include <iostream>
    
    using namespace std;
    //constexpr function
    constexpr int size() {
        return 15;
    }
    
    int main(int argc, char *argv[])
    {
        constexpr int mf = 50;
        // constexpr
        constexpr int sz = size();
    
        cout << mf << endl;
        cout << sz << endl;
        return 0;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 50                                             //
    // 15                                             //
    ////////////////////////////////////////////////////
    
  • 一般来说,能够组成constexpr的initializer的类型都很有限, 绝大多数情况下就是 能够产生literal value的类型, 所以我们把constexpr里面使用的类型(除了类型,当 然也可以直接使用数字)叫做literal type
    The types we can use in a constexpr are known as "literal types"
    because they are simple enough to have literal values.
    
  • 我们目前了解的类型里面, 如下三种就是literal 类型
    • arithmetic
    • reference
    • pointer
  • 如果我们想把一个pointer定义为一个constexpr的话,在语法方面是没有问题的,但是 要注意选对initializer:
    • 0 或者 nullptr肯定是可以的
    • 定义在function内部的变量并不是存在于固定地址的, 所以其地址不可以作为constexpr的初始值
    • 定义在所有function之外的变量地址固定,所以可以使用
    • 还有第六章会介绍的variable that exist across calls to that function#TODO#
  • 还有一个非常人格分裂的地方要解释,那就是 constexpr如果修饰一个指针的话,它给 与的是top-level的const(也就是相当于在*后面的const, 但是由于各种历史原因, constexpr只能放倒最前面)
    const int *p = nullptr;            // p is a pointer to a const int
    constexpr int *q = nullptr;        // q is a const pointer to int
    

Dealing with Types

  • c语言中有时候类型过于复杂,我们需要一些简化手段,让读代码的人更容易看清楚类型

Type Aliases

  • type alias是用来给与变量新名字的方法
  • 老的c语言使用了typedef
    typedef double wages;   //wages is a synonym for double
    
  • 新的规范使用了using来进行alias declaration
    using SI = Sales_item;     // SI is a synonym for Sales_item
    
  • typedef是c语言引入的,typedef和compund type结合, 再加上const参与的话,会有很 多意想不到的结果
    #include <iostream>
    
    using namespace std;
    
    int main(int argc, char *argv[])
    {
        typedef char *pstring;
        const pstring cstr = 0; // cstr is a constnt pointer to char!!
    
        // NOT equal with previous one
        const char* cstr2 = 0;  // cstr2 is a pointer to const char
        return 0;
    }
    

The auto Type Specifier

  • 有时候,我们在程序中很难判断一个对象的真实类型,我们就会使用新规范引入的auto 关键字类让编译器帮助我们判断类型. 编译器判断的依据是initializer, 所以auto 修饰的变量必须得提供initializer
    #include <iostream>
    
    using namespace std;
    
    int main(int argc, char *argv[])
    {
        int val1 = 3;
        int val2 = 4;
        auto item = val1 + val2;
        cout << item << endl;
        return 0;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 7                                              //
    ////////////////////////////////////////////////////
    
  • auto在同一行,只能代替一种类型.不能既修饰int又修饰double
    auto sz = 0, pi = 3.14;    // error: inconsistent types for sz and pi
    
  • auto的行为和普通int没区别,也是会ignore top-level const, 除非你指定
    const int ci = i;
    auto b = ci;         // b is an int (top-leve const in ci is dropped)
    const auto f = ci;   // deduced type of ci is int; f has type const int
    

The decltype Type Specifier

  • decltype是用来让编译器判断类型的#TODO#

Chapter 03: Strings, Vectors, and Arrays

Namespace using Declarations

  • 每次我们输入cout, endl这种函数或者变量的时候, 其实我们都是在std这个namespace 里面的. 所以我们可以使用using来默认使用某个namespace
    using std::cin;
    
  • 如果我们只想使用std里面的某几个函数或者变量,我们可以分别指定,而不至于引入std 里面的所有的名字
    #include <iostream>
    
    using std::cin;
    using std::count;
    using std::endl;
    
    int main(int argc, char *argv[])
    {
        int val1 = 3;
        int val2 = 4;
        auto item = val1 + val2;
        cout << item << endl;
        return 0;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 7                                              //
    ////////////////////////////////////////////////////
    
  • 为了防止命名冲突, 在header里面不要使用using

Library string Type

  • string是cpp里面的变长字符串,需要下面两行引入得以运行
    #include <string>
    using std::string;
    

Defining and Initializing strings

  • string常见的初始化方式如下
    string s1;                 // default initialization; s1 is the empty string
    string s2 = s1;            // s2 is a copy of s1
    string s3 = "hiya";        // s3 is a copy of the string literal
    string s4(10, 'c');        // s4 is cccccccccc
    
  • 初始化方式其实是包含了两种:
    • copy initialization:
      string s5 = "hiya";
      
    • direct initialization:
      string s6("hiya");
      string s7(10, 'c');
      

Operations on strings

  • 我们使用std::cin, std::cout来读取string
    std::string s;
    std::cin >> s;
    std::cout << s;
    
  • 要读取不定数量的字符串使用while循环
    #include <iostream>
    
    using std::cin;
    using std::cout;
    using std::endl;
    using std::string;
    
    int main(int argc, char *argv[])
    {
        string word;
        while (cin >> word) {
            cout << word << endl;
        }
        return 0;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // > ./a.out                                      //
    // 123 456 789                                    //
    // 123                                            //
    // 456                                            //
    // 789                                            //
    ////////////////////////////////////////////////////
    
  • 上面方法的坏处是,遇到空格就认为是一次输入的结束,然后就会打印一行,我们有时候 还是希望留有输入的空格的,那么我们就要配合上getline
    #include <iostream>
    
    using std::cin;
    using std::cout;
    using std::endl;
    using std::string;
    
    int main(int argc, char *argv[])
    {
        string word;
        while (getline(cin, word)) {
            ////////////////////////////////////////////////////////////////
            // The newline that cuses getline to return is discarded; the //
            // newline  is not stored in the string                       //
            ////////////////////////////////////////////////////////////////
            cout << word << endl;
        }
        return 0;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // > ./a.out                                      //
    // 123 456 789                                    //
    // 123 456 789                                    //
    ////////////////////////////////////////////////////
    
  • string的empty函数用来判断字符串是否为空, size来返回字符串的长度
    #include <iostream>
    
    using std::cin;
    using std::cout;
    using std::endl;
    using std::string;
    
    int main(int argc, char *argv[])
    {
        string word = "hello world";
        cout << word.empty() << endl;
        cout << word.size() << endl;
    
        return 0;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 0                                              //
    // 11                                             //
    ////////////////////////////////////////////////////
    
  • 历史上string的size()返回的是string::size_type, 这是为了跨平台, 而且可以能够 容纳任意字符串的长度,其类型也是unsigned的
  • 新规范让我们可以使用auto来存储返回值(自动判断成为size_type)
    auto len = line.size();   // len has type string::size_type
    
  • 因为size()返回值是unsigned的,而unsigned类型和signed类型混用的话,会有很多隐 含的问题,比如无符号和有符号数比较,总是小于.
    #include <iostream>
    
    using std::cin;
    using std::cout;
    using std::endl;
    using std::string;
    
    int main(int argc, char *argv[])
    {
        int n = -5;
        string word = "hello world";
        cout << word.size() << endl;
        cout << (word.size() > n ? "size is bigger than -5 !": "size is small than -5!")
             << endl;
        return 0;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 11                                             //
    // size is small than -5!                         //
    ////////////////////////////////////////////////////
    
  • cpp中的字符串赋值, 是和java不一样的,赋值是一种内容的拷贝,而不是多个ref指向 一个object
    string st1(10, 'c'), st2;
    //assignment : replace contents of st1 with a copy of st2
    // both st1 and st2 are now the empty string
    st1 = st2;
    
  • 把两个string 对象加起来会形成一个新的string对象
    #include <iostream>
    
    using std::cin;
    using std::cout;
    using std::endl;
    using std::string;
    
    int main(int argc, char *argv[])
    {
        string s1 = "hello", s2 = "world\n";
        string s3 = s1 + s2;
        cout << s3;
        return 0;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // helloworld                                     //
    ////////////////////////////////////////////////////
    
  • 这个道理看起来简单,但是由于历史原因(主要为了兼容c), string literals竟然"不 是" standard library strings!!, 所以在使用'+'号的时候,'+'左右两边必须有一 边不是string literal
    string s4 = s1 + ", ";       // ok: adding a string and a literal
    string s5 = "hello" + ", ";  // error! no string operand
    string s6 = s1 + ", " + "world"; // ok: each + has a string operand
    string s7 = "hello" + ", " + s2; // error! can't add string literals
    

Dealing with the Characters in a string

  • 我们在处理字符串的时候,很经常的情况,就是处理字符串里面的每一个字符:
    • 如果需要处理每一个字符的话,可以用'foreach'类型的for:
      1. for的每一个成员都是普通auto类型
        #include <iostream>
        
        using namespace std;
        
        int main(int argc, char *argv[])
        {
            string str("some s");
            for (auto c: str) {
                cout << c << endl;
            }
            return 0;
        }
        
        ////////////////////////////////////////////////////
        // <===================OUTPUT===================> //
        // s                                              //
        // o                                              //
        // m                                              //
        // e                                              //
        //                                                //
        // s                                              //
        ////////////////////////////////////////////////////
        
      2. for的每一个成员都是auto refrence类型, 这样可以用来更改字符串!
        #include <iostream>
        
        using namespace std;
        
        int main(int argc, char *argv[])
        {
            string str("hello world");
            for (auto &c: str) {
                // c is a reference, the assignment changes the char in s
                c = toupper(c);
            }
            cout << str << endl;
        
            return 0;
        }
        
        ////////////////////////////////////////////////////
        // <===================OUTPUT===================> //
        // HELLO WORLD                                    //
        ////////////////////////////////////////////////////
        
    • 如果不需要处理某个字符的话,可以使用operator[]
      #include <iostream>
      using namespace std;
      
      int main(int argc, char *argv[])
      {
          string str("hello world");
      
          if (!str.empty()) {
              str[0] = toupper(str[0]);
          }
      
          cout << str << endl;
      
          return 0;
      }
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // Hello world                                    //
      ////////////////////////////////////////////////////
      

Library vector Type

  • vector是一系列object的collection. 每个object的类型是一致的.这也就是我们常说 的container. 使用vector需要下面两个头文件
    #include <vector>
    using std::vector;
    
  • c++中的template有两类:
    • class template: vector就是class template
    • function template
  • template本身不是class或者function, 但是它们是一种"指导"编译器生产class或者 function的办法!而编译器生成class或者function的这个过程叫做instantiation.
  • 对于class template来说, 你可以通过在<>里面提供额外的信息来"指导"compiler针对 某个class来instantiate. 对于vector来说,就是在<>里面提供我们想要在里面存储的 类型
    vector<int> ivec;                // ivec holds objects of type int
    vector<Sales_item> Sales_vec;    // holds Sales_items
    /////////////////////////////////////////////////////////////////////////
    // old compiler may require old-style declaration for vector of vector //
    // like -> vector<vector<int> >                                        //
    /////////////////////////////////////////////////////////////////////////
    vector<vector<string>> file;     // vector whose elements are vectors
    
  • 因为reference不是一种类型,所以无法定义保存reference的vector

Defining and Initializing vectors

  • 最简单的是定义一个空的vector
    vector<string> svec;    // default initialization; svec has no elements
    
  • 当然也可以使用operator=或者copy构造函数来初始化
    vector<int> ivec;
    
    vector<int> ivec2(ivec);
    vector<int> ivec3 = ivec;
    
  • 新规范提供了list initialization,就是在{}里面提供多个初始化值
    vector<string> articles = {"a", "an", "the"};
    
  • 如果某个vector里面的值是一样的,我们可以使用如下方法定义
    vector<int> ivec(10, -1);        // ten int elements, each initialized to -1
    vector<string> svec(10, "hi!");  // ten strings; each element is "hi!"
    
  • 如果<>里面的类型允许"default value"的话,我们可以不提供初始化值,而只提供容器 大小
    vector<string> svec(10);   // ten elements, each an empty string
    
  • 在vector初始化的时候,使用{}还是()有很大的学问, 在不同类型中情况也不一样:
    • 比如在int类型中:
      vector<int> v1(10);    // v1 has then element with value 0
      vector<int> v2{10};    // v2 has one element with value 10
      vector<int> v3(10, 1); // v3 has ten elements with value 1
      vector<int> v4{10, 1}; // v4 has two elements with values 10 and 1
      
    • 如果你就此认为{}就会是list initialization的话,那就大错特错了. 比如类型是 string的话, {}在无法转换成list initialization的话, 也是可能会转换成()的
      vector<string> v5{"hi"};    // list initialization
      vector<string> v6("hi");    // error: can't construct a vector from astring literal
      vector<string> v7{10};      // v7 has ten default-initialized elements
      vector<string> v8{10, "hi"} // v8 has ten elements with value "hi"
      

Adding Elements to a vector

  • 除了某些情况(比如所有vector的成员都是一个值),大多数情况下最有效率的增加vector 成员的方法就是先初始化一个空的vector,然后往里面加
    vector<int> v2;
    
    for (int i = 0; i != 100; ++i) {
        v2.push_back(i); // append sequential integers to v2
    }
    
  • 而在for的里面对vector 的size进行更改的做法通常都会引入问题#TODO

Other vector Operations

  • vector的每一个element就像string的每一个character一样可以访问, 比如下面的例 子就是更改vector的每个成员并打印
    #include <iostream>
    #include <vector>
    
    using std::cout;
    using std::endl;
    using std::vector;
    
    int main(int argc, char *argv[])
    {
        vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};
        for (auto &i : v) {
            i *= i;
        }
        for (auto i : v) {
            cout << i << " ";
        }
        cout << endl;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 1 4 9 16 25 36 49 64 81                        //
    ////////////////////////////////////////////////////
    
  • vector也有empty和size, 而且特别要注意的是vector的返回值不是简单的size_type 而是带有<>类型的size_type
    vector<int>::size_type //ok
    vector::size_type      //error
    
  • 我们可以使用[]来取用"已有"的vector数据, 但是绝对不能依靠[]来增加数据,比如下 面的例子就是错误的ivec是空的,for内部应该使用push_back,而不是[]
    vector<int> ivec;      // empty vector
    for (decltype(ivec.size()) ix = 0; ix != 10; ++ix) {
        ivec[ix] = ix;     // disaster: ivec has no elements
    }
    

Introducing Iterators

  • 虽然我们可以使用[]来遍历string或者vector, 但是我们可以使用更加"通用"的遍历 方法iterator.
  • 以后我们会发现,std实现了一系列的容器,所有的容器都支持iterator,但是只有个别 的容器支持[]
  • iterator和pointer类似,给了我们indirect访问object的能力,也有valid和invalid之分
  • string不算一种容器,但是string的iterator和容器的iterator相似,我们后面的讨论 也都适用于string

Using Iterators

  • iterator不像pointer一样,使用取地址符来赋值,它使用一些容器的成员函数来赋值:
    • begin()返回:容器的第一个成员(如果存在的话)位置
    • end()返回:容器的最后一个成员后面的位置
    • 如果一个容器是空的话begin()和end()返回同一个值"容器最后一个成员后面的位 置"
  • 和指针一样, iterator也支持一些个比较操作符比如"=="和"!="
  • 和指针一样, iterator也支持"解引用"来获取指其所向位置的object. 当然我们的解 引用(dereference)也必须是valid的iterator
    #include <iostream>
    #include <vector>
    
    using std::cout;
    using std::endl;
    using std::string;
    
    int main(int argc, char *argv[])
    {
        string s("some string");
        if (s.begin() != s.end()) {
            auto it = s.begin();
            *it = toupper(*it);
        }
        cout << s << endl;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Some string                                    //
    ////////////////////////////////////////////////////
    
  • 移动一个iterator是依靠"++", 或者"–"和pointer也类似
  • 但是特别要注意的是因为end()所返回的iterator并不指向某个object,所以对它进行 "++"或者"–"都是逻辑不正确的.
  • 这也是为什么cpp的iterator循环,使用的是'!=' 而不是'<'的原因: end()的位置"飘 忽"无法判断其大小. 另外的原因是某些iterator就没有支持operator<
    #include <iostream>
    #include <vector>
    
    using std::cout;
    using std::endl;
    using std::string;
    
    int main(int argc, char *argv[])
    {
        string s("some string");
         for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it) {
            *it = toupper(*it);
        }
        cout << s << endl;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // SOME string                                    //
    ////////////////////////////////////////////////////
    
  • 因为iterator也是object,所以它其实也是有const性的, 一个const的iterator是只 可以读不可以写的.就像const pointer一样:
    • 如果一个容器是const的,那么'只能'使用const_iterator
      vector<int>::const_iterator it;  // const iterator
      
    • 如果一个容器不是const的,那么'既'使用const_iterator, '又'可以使用普通iterator
      vector<int>::iterator it;        // common iterator
      
  • begin() end()也会根据自己容器的类型来返回不同类型的iterator
    vector<int> v;
    const vector<int> cv;
    auto it1 = v.begin();          // it1 has type vector<int>::iterator
    auto it2 = cv.begin();         // it2 has type vector<int>::const_iterator
    
  • 这种设计非常不人性,很容易用错. 遍历的时候,我们通知都会喜欢使用const iterator, 而无论容器是否const,都可以使用const iterator, 所以新规范设计了新的cbegin和cend 来"必定"返回const iterator
    vector<int> v;
    auto it3 = v.cbegin();   // it3 has type vector<int>::const_iterator
    
  • 调用iterator指向的object的对象的函数有两种方法:
    • 把iterator看成指针,可以使用*iterator来找到相应object,并调用.但是要记得加 括号
      (*it).empty()
      
    • 可以使用->来简化这个过程
      it->empty()
      
  • 我们前面说过,不要再for里面增加数据, 现在我们还要告诉大家,一旦增加vector的 size(比如通过push_back),所有的iterator都失效.所以,所有的loop一旦使用了iterator 那么就不要add element.

Iterator Arithmetic

  • 和指针一样.iterator是可以进行加减法的.
  • 一个利用iterator来进行二分查找的例子
    // text must be sorted
    // beg and end will denote the range we're searching
    auto beg = text.begin(), end = text.end();
    auto mid = text.begin() + (end - beg) / 2;
    
    while (mid != end && *mid != sought) {
        if (sought < *mid) {
            end = mid;
        } else {
            beg = mid + 1;
        }
        mid = beg + (end - beg) / 2;
    }
    

Arrays

  • 和vector不同的是, array是fix-sized, 因为array的size fix,所以性能会比较有优势

Defining and Initializing Built-in Arrays

  • 和reference以及pointer一样, 数组是一种compound type,形如a[d],其中d必须是 constant expression
    unsigned cnt = 42; // not a constant expression
    constexpr unsigned sz = 42; // const expression
    
    int arr[10];       // array of ten ints
    int *parr[sz];     // array of 42 pointers to int
    string bad[cnt];   // error: cnt is not a constant expression
    string strs[get_size()]; // ok if get_size is constexpr, error otherwise
    
  • array里面的成员都是default initialized的,所以在function里面的定义的built-in 类型数组是非常危险的,因为
    As with variables of built-in type, a default-initialized array of
    built-in type that is defined inside a function will have undefined
    values
    
  • array包含的必须是object,所以reference是没有数组的
  • 除了字符数组以外的数组初始化方式如下
    const unsigned sz = 3;
    int ial[sz] = {0, 1, 2};      // array of three ints with values 0, 1, 2
    int a2[] = {0, 1, 2};         // anarray of dimension 3
    int a3[5] = {0, 1, 2};        // equivalent to a3[] = {0, 1, 2, 0, 0}
    string a4[3] = {"hi", "bye"};  // same as a4[] = {"hi", "bye", ""}
    int a5[2] = {0, 1, 2};        // error: too many initializers
    
  • 字符数组的初始化方式的一个特别之处是string literals会在最后加一个'\0'
    char a3[] = "C++";     // null terminator added automatically
    const char a4[6] = "Daniel";   // error:no spaces for the null!
    
  • 字符串数组非string literals的话,和普通数组没啥区别
    char a1[] = {'c', '+', '+'};
    char a2[] = {'c', '+', '+', '\0'};
    
  • 我们不能通过一个数组来初始化另外一个数组, 也不能数组间相互赋值
    int a[] = {0, 1, 2};
    int a2[] = a;          // error: can not initialize one array with another
    a2 = a;                // error: cannot assign one array to another
    
  • 数组的declaration是非常非常的多变和难以理解, 指针所谓的从右到左变得不再那 么好用. 数组是从'数组名'开始, 从里向外读取比较合适:
    • array of pointers
      int *ptrs[10];
      
    • NO array of references!
      // error!!
      int &refs[10] = /* ? */
      
    • Parray pionts to an array of ten ints
      int (*Parray)[10] = &arr;
      
    • arrRef refers to an array of ten ints
      int (&arrRef)[10] = arr;
      

Access the Element of Array

  • cpp是不会去检查数组越界的,而这是很多错误的源泉.

Pointers and Arrays

  • 在cpp中, 编译器通常是将array转换成pointer去操作, 比如下面的例子nums即是一个 数组名,同时也是一个指针名
    string nums[] = {"one", "two", "three"};
    string *p2 = nums;     // equivalent to p2 = &nums[0]
    
  • 在c++11里面,下面的例子可以看出,编译器就是把数组转化成指针处理的
    int ia[] = {0, 1, 2, 3, 4};    // ia is an array of ten ints
    auto ia2(ia);                  // ia2 is an int* that points to the first element in ia
    ia2 = 42;                      // error: ia2 is a pointer, and we can't assign an int to a pointer
    
  • 值得注意的是decltype是不会默认把数组转换成指针的
    decltype(ia) ia3 = {0, 1, 2, 3, 4};
    ia3 = p;       // error: can't assign an int* to an array
    ia3[4] = i;    // ok: assign the value of i to an element in ia3
    
  • 指针至于数组就好像iterator至于vector
  • 新规范引入了std::begin和std::end来返回一个数组的begin pointer和end pointer(最后一个数组后一个位置)
    #include <iostream>
    
    using namespace std;
    
    int main(int argc, char *argv[])
    {
        int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        int *pbeg = begin(arr);
        int *pend = end(arr);
        while (pbeg != pend && *pbeg >=0) {
            cout << *pbeg << " ";
            ++pbeg;
        }
        cout << endl;
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // $ g++ --std=c++11 13.cc                        //
    // $ ./a.out                                      //
    // 0 1 2 3 4 5 6 7 8 9                            //
    ////////////////////////////////////////////////////
    
  • 和vector以及string不同的是, 内置数组的index的类型不是unsigned type

C-Style Character Strings

  • 虽然c++支持c-style string,但是以后不应该再使用这些string, 因为它们基本就是 bug最多的来源.
  • TODO

Interfacing to Older Code

  • 我们是不可以通过一个array来初始化另外一个array, 但是我们可以使用一个array来 初始化一个vector, 而且在新规范引入了begin()和end()之后,变得更简单了
    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    void display(std::vector<int> someV) {
        for (std::vector<int>::const_iterator ite = someV.begin(); ite < someV.cend(); ++ite) {
            std::cout << *ite << "  ";
        }
        std::cout << std::endl;
    }
    
    int main(int argc, char *argv[])
    {
        int int_arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    
        vector<int> ivec(begin(int_arr), end(int_arr));
        display(ivec);
    
        vector<int> subVec(int_arr + 1, int_arr + 4);
        display(subVec);
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 0  1  2  3  4  5  6  7  8  9                   //
    // 1  2  3                                        //
    ////////////////////////////////////////////////////
    
  • 由于c-style string和指针是如此的容易出错,所以新代码推荐使用vector和strings

Multidimensional Arrays

  • 其实在cpp中是不存在多维数组的, 多维数组的本质就是arrays of arrays
    What are commonly referred to as multidimensional arrays are actually arrays of arrays
    
  • 对于多维数组的理解,从第一dimension开始"读取"比较容易, 比如
    int ia[3][4]; // array of size 3; each element is an array of ints of size 4
    
  • TODO

Chapter 04: Expressions

Fundamentals

Basic Concepts

  • 操作符方面,cpp分为两种操作符:
    • unary operators: 操作一个数字
    • binary operators: 操作两个数字
  • 而expression在cpp中有两种分发:
    • lvalue: 可以放在assignment的左边的expression
    • rvalue: 不可以放在assignment左边的expression
  • lvalue和rvalue的区分非常复杂,但是从直观上来说就是:
    • 我们在把一个object当做lvalue来使用的时候, 我们使用的是它的identity(location in memory)
    • 我们在把一个object当做rvalue来使用的时候, 我们使用的是它的value (its content)
  • 总体上来说lvalue"能力更强"
    We can use an lvalue when an rvalue is rquired, but we cannot use
    an lvalue in place of an rvalue.
    
  • lvalue作为rvalue使用的时候,其只需要把其内容"贡献"出来即可.
  • TODO

Precedence and Associativity

  • 括号的优先级最大

The Member Access Operators

  • 因为"解引用"符号的优先级是比dot低的,所以我们必须在"指针引用"外面加上括号,然 后再去使用, 比如下面的使用就是错误的.
    // run the size member of p, then dereference the result!
    *p.size() // error: p is a pointer and has not member named size
    

Chapter 05: Statements

Simple Statements

  • 大部分的c++ statement是以分号结尾的. 所谓的expression statement就是说一个 expression会被evaluated,然后它的结果被丢弃了:
    • 如下的statement是没有意义的, 因为它的结果在被丢弃前没有保存
      ival + 5;
      
    • 下面是一个在丢弃前保存的例子,不过保存的方式是打印
      cout << ival;
      

Null Statements

  • 如果需要什么都不做的语句,那么直接一个分号就好了,注意这种情况下要加上一些 comment
    while (cin >> s && s != sought)
        ; // null statment
    

Beware of Missing or Extraneous Semicolons

  • 多余的分号并不是总没有危害,比如在if或者while后面跟了分号的话,可能会导致无线 循环
    while (iter != save.end());
        ++iter;
    

Compound Statements(Blocks)

  • 复合语句(Compound Statement)是指的大括号括起来的语句, 也叫做块(block)
  • 复合语句自己本身就是一个作用域(scope), 也就是说,在这个block中引入的名字只 能在这个block(或其子block)中使用.
  • 复合语句说起来高冷,却是我们最常用的语法:对于while for等控制语句来说,后面本 来只能跟一行语句,如果需要多行语句的话,要把这个几个语句加入到一个block
    while (val <= 10) {
        sum += val;
        ++val;
    }
    

Statement Scope

  • 我们可以在while if等语句的control structure里面来定义变量.这样定义的变量在 只在while if的statement里面起作用.
    while (int i = get_num())  // i is created and initialized on each iteration
        cout << i << endl;
    i = 0;         // error: i is not accessible outside the loop
    

Conditional Statements

The if Statement

  • if 后面跟的condition必须是能够转化成bool类型的类型

The switch Statement

  • case label后面必须跟常量表达式
    char ch = getVal();
    int ival = 42;
    
    switch(ch) {
    case 3.14: // error: noninteger as case label
    case ival: // error: nonconstant as case label
    // ...
    

Iterative Statements

The while Statement

  • while是不停执行目标statement,只要condition还为true
    while (condition)
        statement
    
  • 需要注意的是,我们在condition或者statement部分定义的变量,每次都会经历created 和destroyed的过程.

Traditional for Statement

  • 传统的for循环要求如下:
    for (initializer; condition; expression)
        statement
    
  • 上述三个部分initializer, condition, expression都可以省略
  • initializer里面的定义只运行一遍,但是如果变量定义于此的话,其作用域就是for语 句结束之前

Range for Statement

  • c++11 引入了range for
    for (declaration : expression)
        statement
    
  • 这种for的特点是:
    • declaration 会定义一个变量, 这个变量必须能转化为expression里面的容器的成 员变量的类型. 最简单的方法是使用auto让编译器为我们来判断.如果想更改容器里 面成员的值,那么declaration的这个变量必须是引用类型
    • expression必须是一种列表,比如:
      1. braced initializer list
      2. array
      3. 能够支持返回iterator的begin和end的容器
  • 下面是一个使用range for来把成员变量大小翻倍的方法
    vector<int> v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    // range variable must be a reference so we can write to the elements
    for (auto &r : v)
        r *= 2
    

The do while Statement

  • do while statement就是必须先执行一次的while循环,无论condition是否成立,需要 注意的是while后面要以分号结尾
    do
        statement;
    while (condition);
    

Jump Statements

The break Statement

  • break 会结束如下'最近'的一个语句:
    • while
    • do while
    • for
    • switch

The continue Statement

  • continue会结束循环的最近一次,进入下一次,所以只能出现在循环内部

The goto Statment

  • 随意跳转,最好不用.

try Blocks and Exception Handling

  • exception是运行期(runtime)出现的反常行为, 比如数据库连接丢失,或者是意外输入 (unexpected input)
  • 异常处理(exception handling)是指:
    • 程序的一部分遇到了一个它无法解决的问题, 而且这个问题使得它无法继续运行
    • 遇到问题的这部分程序只需要把问题"声明"出来,不需要知道如何解决
    • 一旦"声明"出问题, 这部分程序就完成了使命.
  • 异常的处理在cpp中包含如下几个部分:
    • detecting: 遇到问题的部分,会使用throw expression来"声明"自己遇到的问题
    • handling: 解决问题的代码会使用try后面的block来"等待"block里面的代码"声明" 问题,然后在catach里面处理
    • exception class: 用来在throw和catch里面"交流"问题的类型

A throw Expression

  • 遇到问题的部分使用的代码
    Sales_item item1, item2;
    
    cin >> item1 >> item2;
    
    // first check that item1 and item2 represent the same book
    if (item1.isbn() != item2.isbn()) {
        throw runtime_error("Data must refer to same ISBN");
    }
    
    // if we're still here, the ISBN are the same
    cout << item1 + item2 << endl;
    
  • runtime_error是stdexcept header里面定义的standard library exception

The try Block

  • try block是代码解决问题的部分, 其中
    • try后面跟的block里面是"可能出问题"的代码, 这些代码可能调用了函数,或者函 数里面调用了其他函数,只要是从这个block里面出去的,都可以被"捕捉"到
    • catch是处理exception的部分, catch可以指定只处理某种类型的exception
      #include <iostream>
      
      using namespace std;
      
      int main(int argc, char *argv[])
      {
          int item1 = 0;
          int item2 = 0;
      
          while (cin >> item1 >> item2) {
              try {
                  if (item1 != item2) {
                      throw runtime_error("Data must refer to same ISBN");
                  }
      
                  cout << "The ISBNs are the same" << endl;
                  break;
              } catch (runtime_error err) {
                  cout << err.what()
                       << "\nTry Again? Entry y or n" << endl;
                  char c;
                  cin >> c;
                  if (!cin || c == 'n') {
                      break;       // break ou of the while loop
                  }
              }
          }
          return 0;
      }
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // $ ./a.out                                      //
      // 111 222                                        //
      // Data must refer to same ISBN                   //
      // Try Again? Entry y or n                        //
      // y                                              //
      // 333 333                                        //
      // The ISBNs are the same                         //
      ////////////////////////////////////////////////////
      

Standard Exceptions

  • cpp在如下的头文件里面定义了一些exception类:
    • excepition header: 定义了名叫excepiton的异常
    • stdexcept header: 定义了常用异常
    • new header: 定义了bad_alloc 异常
    • type_info header: 定义了bad_cast 异常
  • exception异常, bad_alloc异常和bad_cast异常是不需要字符串做为初始化参数的,除 此之外其他的异常都需要字符串初始化参数