• C++ 程序设计语言


    好记性不如烂笔头。

    第六章

    • 标准库给出了静态断言,形式类似如下:
    stastic_assert(A,S);//当A不为true时,把S作为一条编译器错误信息输出

    其最重要的用途是为泛型编程中作为形参的类型设置断言

    • void有两个作用:1,作为函数的返回类型用以说明函数不返回任何实际的值。2,作为指针的基本类型部分以表明指针所指对象的类型未知。
    • 在一些机器体系结构中,存放变量的字节必须保持一种良好的对齐方式,以便硬件在访问数据结构资源的时候足够高效。alignof()运算符返回实参表达式的对齐情况,例如:
    auto ac = alignof('c');//char的对齐情况
    auto al = alignof(1);    //int的对齐情况
    • 在为实体命名时,应尽量让名字反应实体含义而非实现细节。在C++中,以下形式并不被建议使用,pcname作为某个char*的名字,或者icount作为某个int计数变量的名字。原因如下:

    1. 把类型信息加到名字中降低了程序的抽象水平,尤其不利于泛型编程。
    2. 编译器比程序员更擅长记录和追踪类型信息。
    3. 一旦改变某个名字的类型,所有都要改。
    4. 随着用到的类型越来越多,设计的缩写集越来越大,有时含糊不清,有时过于啰嗦。
    • 初始化

      初始化的形式大概有4种:

    X a1{v};  //C++11中提出
    X a2 = {v};    //继承自C语言
    X a3 = v;    //继承自C语言
    X a4(v);

    以上4种形式的初始化,只有第一种不受限制使用。一般推荐使用{}方式初始化,使用{}初始化称之为列表初始化(list initialization),好处是防止窄化转换。这句话的意思是:如果一种整型(浮点型)不能存放下另一种整型(浮点型)的值,则后者不会被转化为前者。(另外,整型值不能转化为浮点型的值)。因此,没有特别的理由,最好使用{}进行初始化。

    • 初始化器缺失时:

      若是未指定初始化器,则全局变量,名字空间变量,局部static变量,和static成员会执行相应数据类型的列表{}初始化。对于局部变量,和自由存储上的对象(动态对象),除非它们位于用户自定义类型的默认构造函数中,否则,不会执行默认初始化。若相对内部局部变量或者new创建的内置类型的对象进行初始化,使用{}的形式,例如:

    void ff()
    {
      int x{};
      char buf[12]{};
      int* p{new int{10}};
      char*q {new char[1024]{}};
    }
    • 推断类型:

      c++提供两种从表达式中推断数据类型的机制。1.auto根据对象的初始化器推断对象的数据类型。可能是变量,const或者constwxpt的类型。2.decltype(expr)推断的对象不是一个简单的初始化器,有可能是函数的返回类型或者类成员类型。

    • 右值

      当考虑对象的寻址,拷贝,移动等操作时,有两种属性很关键:

      1.有身份(has identity):在程序中有对象的名称,或指向该对象的指针,或该对象的引用。这样我们就能判断两个对象是否相等,或者对象的值是否发生改变。

      2. 可移动(is movable):能把对象的内容移动出来。

      在上述2中属性的四种组合形式中,有3种需要C++语言规则精确的描述(没有身份又不能移动的对象不重要)。用m表示可移动,用i表示有身份,分类如下:

      左值有身份,但不能移动;右值是允许执行移出操作的对象。

    •  类型别名:

      

    typedef void(*PtoF)(int); //等价于 “using PtoF = void(*)int;”
    • 指针:

      大多数机器支持逐字节访问内存,其他机器则是从字中抽取字节,很少有机器支持直接寻址到二进制位。能独立分配且用内置指针指向的最小单位位char类型的对象。(bool类型占用的内存空间至少与char类型一样)。如果想要把更小的值存的更紧密,可使用位逻辑操作,结构中的位域,或者bitset。

    • void* :

      指向未知类型的对象的指针。除了函数指针(Why)和指向类成员的指针(why 和 why2)外,指向其他任意类型对象的指针都能赋值给void*类型指针。两个void* 可以比较是否相等,还能显式的把他转化为其他类型。

      函数指针转换会带来不可预知的风险。 member functions中则隐含了this指针。具体参考链接中说明。

    • 字符串字面常量:

      字符串字面常量的类型是“若干个const字符组成的字符数组”,“abcd”的类型是const char[5]。下述代码是不安全的:

    char* p = "abcd"; //错误,但C++11之前被接受
    p[3] = "d";    //错误,试图为常量赋值
    • 原始字符串:

      当字符串中出现大量的反斜线和双引号,原始字符串就避免了转义字符中的反斜线的重复出现,避免了书写错误。

    string s = R"(w\ww\)"; //s的内容是 w\ww\
    string s = R"("strings")";    //s的内容是 “strings”
    •  指针与const

      一个指针牵涉到两个对象,即指针本身和所指向的对象。在指针声明中,前置const关键字将令所指的对象而非指针本身成为常量。若要指针本身成为常量,则需要用 *const 代替 *  。

      

    char * s = "abcd";
    
    const char * p = s;    //指向的内容为常量,p是指向const char的指针
    
    char *const p2 = s; //指针为常量, p2是指向char的const指针

    技巧是:从右向左读。

    • 右值引用:

      非const左值引用的对象可以由用户写入内容。const左值引用的对象从用户角度来看是不可修改的。右值引用对应一个临时对象,用户可以修改这个对象,并且认为这个对象以后不会被用到了。参考链接

    •  返回值

      下面两种返回值形式是一样的

    string to_string(int a);     //前置返回类型
    auto to_string(int a) -> string;  //后置返回类型
    • 局部变量:

      定义在函数内部的名字通常称为局部名字,当线程执行到该定义处,它们被初始化。除非我们把变量声明为static,否则函数每次调用都会拥有该变量的一份拷贝。相反,如果我们把局部变量声明为static,则函数的所有调用中将使用唯一的一份静态分配的对象,该对象在线程第一次到达的时候进行初始化。

      static局部变量有一重要作用:它可以在函数的多次调用间维护一份公共的信息,而无须使用全局变量。如果使用了全局变量,则有可能会被其他不想关的函数访问甚至干扰。

      递归的初始化一个局部static变量会产生未定义的结果。

    •  重载函数 

      重载函数并不考虑返回值,其和作用域有着密切关系。基类和派生类的作用域不同,因此默认情况下基类函数和派生类函数不会发生重载。如果想要实现跨类作用域或者跨命名空间作用域的重载,应该使用using声明或者using指示。依赖于参数的查找也会导致跨名字空间重载。

    • 前置与后置条件

        函数调用时应遵循的约定称为"前置条件",函数返回时应遵循的条件称为“后置条件”。

    • 函数指针

      与数据对象类似,由函数体生成的代码也置于某块内存区域中,因此它也有自己的地址。函数指针并不能修改所指向的代码(与机器体系有关,于系统设计有关)。调用者只能对函数指针做两种操作:调用它或者获取它的地址。通过获取地址得到的指针能被用来调用该函数。

    void error(string s) {/* ... */}
    
    void (*efct)(string); //指向函数的指针,该函数接受一个字符串参数
    
    void f()
    {
      efct = &error; //efet 指向error
      efct("error");    //通过efct调用error
    }

    编译器发现efct是函数指针,因此会调用它所指的函数。即,解引用函数指针时可用 * ,也可不用。同样,获取函数地址时可以用&,也可以不用:

    void(*f1)(string) = &error;  //OK: 等价于 = error
    void(*f2)(string) = error;    //OK: 等价于 = &error
    
    void g()
    {
      f1("Vasa");    //OK: 等价于(*f1)("Vasa")
      (*f1)("Mary Rose");    //OK: 等价于f1("Mary Rose")
    }

    函数指针的参数类型声明与函数本身类似,进行指针赋值操作时,要求完整的函数类型都必须精确匹配。

    C++允许把一个函数指针类型转换成别的函数指针类型,但之后必须把得到的结果指针转换为其原来的类型,否则会出现意外情况:

    using p1 = int(*)(int*);
    using p2 = void(*)(void);
    
    void f(p1 pf)
    {
      p2 pf2 = reinterpret_cast<p2>(pf);
      pf2();                                                  //可能会出现严重错误
      p1 pf1 = reinterpret_cast<p1>(pf2);   //把pf2  转换回来
      int x = 7;
      int y = pf1(&x);
      //...
    }
    • 异常处理:

      函数如果希望处理某个问题,可以捕获相应的异常:

      1 主调组件如果想处理某些失败的情形,可以把这些异常置于try块的catch从句中。

      2 被调组件如果无法完成既定的任务,可以用throw表达式抛出一个异常来说明这一情况。

    void taskmaster()
    {
      try{
          auto result = do_task();
          //使用result    
        }  
        catch(Some_error)
        {
          //执行do_task时发生错误:处理该错误
        }
    }
    
    int do_task()
    {
      //...
      if(/*能够执行该任务*/)
          return result;
      else
          throw Some_error;
    }

      成功的容错系统都是多层级的。按照自底而上的顺序,每一层级在它力所能及的范围内处理更多的错误,把剩下的错误留给更高层级处理。

  • 相关阅读:
    命令实现linux和客户端文件上传下载
    python--linux上如何执行shell命令
    Eureka系列(一)Eureka功能介绍
    Eureka系列(七) 服务下线Server端具体实现
    编译时多态 与 运行时多态
    静态绑定(前期绑定) 与 动态绑定(后期绑定)
    重载 与 重写
    热点检测、方法内联、动态反优化
    数据库日志
    单例模式
  • 原文地址:https://www.cnblogs.com/gardenofhu/p/7507886.html
Copyright © 2020-2023  润新知