C++ part one

基本特性

程序的执行过程

  • 内核虚拟内存——内核使用
  • 栈区——程序运行时用于存放局部变量,可向下延展空间
  • 内存库的内存映射
  • 堆区——程序运行时用于分配malloc和new申请的区域
  • 可读写区——用于存放全局变量和静态变量
  • 只读区——存放程序和常量等
  • 未使用
  • 静态变量区
    • 静态变量:一些局部作用,寿命很长的变量
    • 全局变量:字如其名,全局作用

new 关键字

  • 在分配单个对象的内存时:
    • 当对象是普通变量时,可以分配对应的内存
    • 当对象是类对象时,会调用构造函数,如果没有对应的构造函数,就会报错。
  • 在分配数组对象内存时:
    • 对于普通变量:可以使用“()”将所有对象全部初始化为0。
    • 对于类对象,有没有“()”都一样,均使用默认构造函数,如果没有默认构造函数就会报错。
      1
      2
      3
      4
      5
      6
      7
      8
      int* a =  new int(100); //分配为普通变量,同时初始化
      delete a;//记得释放空间

      std::string* hello = new std::string("你好,世界");
      delete hello

      int* b = new int[5](); //全部初始化为零
      delte[] b;

命名空间

  • 命名空间用来解决大型项目的命名重复问题
  • C++最后都要转化为C来执行程序,在namespace A中定义的Test类,其实全名是A::Test。
  • 头文件中不能使用using关键字,极易造成头文件污染

const关键字

  • const是让编译期将变量视为常量,用const修饰的变量和真正的常量有本质的区别。

  • 真正的常量存储在常量区或代码区,比如“abcdefg”这个字符串就存储在常量区,而“3”,“100”这些数字就存储在代码区中,这些都是真正的常量,无法用任何方式修改。

  • const修饰的变量仍然存储在堆区或栈区中,从内存分布的角度讲,和普通变量没有区别。const修饰的变量并非不可更改的,C++本身就提供了mutable关键字,用来修改const修饰的变量,从汇编的角度讲,const修饰的变量也是可以修改的。

  • 常量指针与指针常量

    • 常量指针:指针指向的内容是常量,常量不能改变
    • 指针常量:指针本身就是常量,不能再指向其他地址
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const char *p  = "abcdef";   //指针常量,此处指针指向的字符串abcdef无法改变

      int a = 8;
      int* const p = &a;
      *p = 9; // 正确
      int b = 7;
      p = &b; // 错误,此处指针指向的地址无法改变
      int a = 8;
      const int * const p = &a;
      //指针指向的内容和指针都无法改变

auto关键字

  1. auto只能推断出类型:引用不是类型,引用只是别名,所以auto无法推断出引用,要使用引用只能自己加引用符号。

  2. auto关键字在推断引用的类型时:会直接将引用替换为引用指向的对象。其实引用一直是这样的,引用不是对象,任何使用引用的地方都可以直接替换成引用指向的对象。

  3. auto关键字在推断类型时,如果没有引用符号,会忽略值类型的const修饰,而保留修饰指向对象的const。

  4. auto关键字在推断类型时,如果有了引用符号,那么值类型的const和修饰指向对象的const都会保留。

  • 示例三和四的原因。在传递值时,修改这个值并不会对原有的值造成影响。而传递引用时,修改这个值会直接对原有的值造成影响.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //示例一
    int a = 10;
    auto i = a; //auto推导出int
    auto& i = a;//auto推导出int&

    //示例二
    int a = 10;
    int& b = a;
    auto c = b; //相当于auto c =a;

    //示例三
    const int* const a = 10
    auto b = &a; //变量b的类型为int* const

    //示例四
    const int* const a = 10
    auto& b = &a; //变量b的类型为const int* const &

静态变量,指针与引用

  • 变量的存储位置有三种,分别是静态变量区,栈区,堆区
  • 静态变量区在编译时就已经确定地址,存储全局变量与静态变量
  • 指针都是存储在栈上或堆上,不管在栈上还是堆上,都一定有一个地址
  • &a代表的a这个变量的地址,a代表的a对应地址存储的值,* a对应指针指向的内存地址中存放的值

左值,右值

  • 左值、右值的纯右值、亡值、右值

    • 左值(lvalue, left value),顾名思义就是赋值符号左边的值。准确来说, 左值是表达式(不一定是赋值表达式)后依然存在的持久对象。(注意:字符串字面量为左值,如"hello")
    • 右值(rvalue, right value),右边的值,是指表达式结束后就不再存在的临时对象(或引用)。而 C++11 中为了引入强大的右值引用,将右值的概念进行了进一步的划分,分为:纯右值、将亡值。
      • 纯右值(prvalue, pure rvalue),纯粹的右值,要么是纯粹的字面量,例如 10 , true ; 要么是临时对象,例如 -1 , 1+2 ,a++的结果。非引用返回的临时变量、运算表达式产生的临时变量、Lambda 表达式都属于纯右值。
      • 亡值(xvalue, expiring value),是 C++11 为了引入右值引用而提出的概念(因此在传统 C++ 中,只有右值这一个概念),也就是即将被销毁、却能够被移动(move)的值。右值引用和左值引用
    • 顾名思义,右值引用可以引用一切右值(包括纯右值和亡值): T && ,其中 T 为非左值引用类型(若 T 为左值引用类型,则结果仍是左值引用)。 右值引用的声明让这个临时对象的生命周期得以延长,只要右值引用生命期还未结束,那么这个临时对象的生命期也不会结束。
  • 左值:拥有地址属性的对象就叫左值

  • 右值:没有地址属性的对象就叫右值

  • C++11 提供了 std::move 这个方法将左值参数无条件的转换为右值, 有了它我们就能够方便的获得一个右值引用(匿名右值引用)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include <iostream>
    #include <utility> // std::move
    void reference(std::string& str) {
    std::cout << "左值" << std::endl;
    }
    void reference(std::string&& str) {
    std::cout << "右值" << std::endl;
    }
    int main(){
    std::string lv0 = "string,"; // lv0 是一个左值
    std::string& lv1 = lv0; // lv1 是左值引用
    // std::string&& r1 = lv0; // 非法, 右值引用不能引用左值
    std::string&& rv1 = std::move(lv0); // 合法, std::move可以将左值转换为右值
    const std::string& lv2 = lv0 + lv0; // 合法, 常量左值引用能够延长临时变量的生命周期
    // lv2 += "Test"; // 非法, 常量引用无法被修改
    std::string&& rv2 = lv0 + lv2; // 合法, 右值引用延长临时对象生命周期
    rv2 += "Test"; // 合法, 非常量引用能够修改临时变量
    reference(lv1); // lv1是左值引用,为左值
    reference(rv2); // rv2是一个具名右值引用,为左值,见完美转发
    reference(std::move(lv0)); // 匿名右值引用,为右值
    return 0;
    }

lambda 表达式

  • lambda表达式就是匿名函数,普通的函数在使用前需要找个地方将这个函数定义,于是C++提供了lambda表达式,需要函数时直接在需要的地方写一个lambda表达式,省去了定义函数的过程,增加开发效率。
  • lambda表达式的格式:最少是“[] {}”,完整的格式为“[] () ->ret {}”。
  • lambda各个组件介绍
  1. []代表捕获列表:表示lambda表达式可以访问前文的哪些变量。 (1)[]表示不捕获任何变量。 (2)[=]:表示按值捕获所有变量。 (3)[&]:表示按照引用捕获所有变量。=,&也可以混合使用 (4)[=, &i]:表示变量i用引用传递,除i的所有变量用值传递。 (5)[&, i]:表示变量i用值传递,除i的所有变量用引用传递。 当然,也可以捕获单独的变量 (6)[i]:表示以值传递的形式捕获i (7)[&i]:表示以引用传递的方式捕获i
  2. ()代表lambda表达式的参数,函数有参数,lambda自然也有。
  3. ->ret表示指定lambda的返回值,如果不指定,lambda表达式也会推断出一个返回值的。
  4. {}就是函数体了,和普通函数的函数体功能完全相同。