2020-08-27-C++编程思想+二叉树部分定义

Record

Posted by Jocelyn on August 27, 2020

2020-08-27 recording.

  • 当定义一个const时,必须赋一个值给它,除非用extern作出了清楚的说明: extern const int bufferSize;

  • 通常C++编译器并不为const创建存储空间,C是需要的。C++中是否为const常量创建内存空间取决于对它如何使用,比如,如果取const常量的地址或者把它定义成extern,需要为这个const创建内存空间。extern意味着使用外部连接,因此必须分配内存空间

    const int i = 100;
    const int j = i + 10;
    j + 10;也是一个常数表达式
        
    const char c = cin.get();在编译期间是不知道的,这意味着需要存储空间
    const char c2 = c + 'a';
      
    const int i[] = {1,2,3,4};
    float f[i[3]];❌ 
    const 意味着不能改变的一块存储空间,然而不能在编译期间使用它的值,因为编译器在编译期间不需要知道存储的内容
    
  • 因为C++中const默认为内部连接,所以不能在一个文件中定义一个const,而在另外一个文件中把它作为extern来引用,所以目标转换为将const转化为外部引用,以便让另外一个文件可以对它引用

    extern const int i = 100;
    通过对它赋值并指定为extern,强迫分配内存,初始化它成为一个定义而不是一个声明
    
  • 指向const的指针

    const int *u;从标识符的开始处读它,并从里向外读。
    u是一个指针,它指向一个const int.
    const修饰最靠近它的那个
    这里不需要初始化,因为u可以指向任何标识符
      
    int const* v;
    v恰好是一个指向const的int的普通指针
    上面两个是一样的,倾向于第一种形式
    
  • const指针

    为了使指针本身成为一个const指针,必须将*放在const的右边
    int d = 1;
    int* const w = &d;
    w是一个指针,这个指针是指向int的const指针,因为指针本身为const,所以需要给它一个初始值
    *w = 2;✅
    事实上,*是与标识符结合的,而不是与类型结合的
    
  • 赋值和类型检查

    可以将非const对象的地址赋给const指针,可以不改变某些可以改变的东西

    不能将一个const对象的地址赋给非const指针,因为可能通过被赋值的指针来改变这个对象的值

    可以用类型转换强制进行这样的赋值

  • 函数参数和返回值

    void f1(const int i){
      i++;
    }
    void f2(int i){
      const int &ic = i;
    }
    第二种会比第一种对于使用者来说读起来更友好
        
        
    const X f6(){
      return X();
    }
    f6() = X(1);❌
    因为f6为const,不能作为左值(左值可以被赋值,可以被修改)
      
    void f7(X& x){
      x.modify();
    }
    f7(f5());
    求f5()表达式期间,编译器会创建临时对象,有规定:临时变量会自动转为常量
    如果不是常量,因为f7的参数不是const引用,所以会对临时变量进行修改,表达式计算结束,临时变量也不存在,所以所做的操作也丢失。所以编译器会将临时变量自动转为const
    
  • 函数可以返回函数体内static变量的地址

int x = 0;
int *ip = &x;
const int *cip = &x;cip是个指针,指向const int
void t(int *){
  
}
void u(const int *){
  
}

t(ip);✅
t(cip);❌因为t可能对传进来的const int 无处下手
  
u(ip);✅
u(cip);✅因为const int*可以选择不改变传进来可以改变的东西
  
带const指针的函数参数比不带const指针的参数的函数更具一般性
  • 类里的const

    类里的const成员在构造函数初始化的时候进行,构造函数初始化列表
    class Fred{
      const int size;
    public:
      Fred(int sz);
      void print();
    }
    Fred::Fred(int sz):size(sz){...}初始化先于花括号执行
        
    内建类型也可以使用类似的初始化方式:
    class B{
      int i;
    public:
      B(int ii=0);
      void print();
    }
    B::B(int ii):i(ii){...}
      
    将内建类型封装在一个类里面保证构造函数从初始化,比如将上面的B看做Integer
    这样
    Integer test[100];
    会调用构造函数,因为有默认值0,会将所有的数组元素初始化为零,相比于for循环和memset可以节省开销
    
  • static关键字

    不管类的对象被创建多少次,都只有一个实例

  • const对象和成员函数

    如果声明一个成员函数为const,表示告诉编译器这个成员函数可以为一个const对象调用
    如果一个没有被明确声明为const的成员函数,会被认为它会修改对象中的数据成员,所以不允许被一个const对象调用
        
    前面带const的函数声明,表示函数的返回值为const
    class X{
      int i;
    public:
      X(int ii);
      int f() const;
    }
    X::X(int ii):i(ii){}
    int X::f() const{
      return i;
    }
    const也必须出现在定义中,否则编译器会将它看成另外一个函数
    因为f()是一个const成员函数,所以它试图以何种方式改变i或者调用另外一个非const成员函数,编译器都会报错
    const成员函数 调用 const 和 非const 对象 是安全的
    
  • 关键字mutable,修饰成员变量,在const对象中可改变

    class Z{
      int i;
      mutable int j;
    public:
      Z();
      void f() const;
    };
      
    Z::Z():i(0),j(0){}
    void Z::f()const{
      j++;✅
    }
    
  • volatile关键字:告诉编译器,不要擅自作出有关该数据的任何假定,要认认真真,不能偷懒,优化期间尤其如此,

    class Comm{
      const volatile unsigned char byte;
      volatile unsigned char flag;
      enum{bufSize = 100};
    public:
      Comm();
      void isr() volatile;
      char read(int index) const;
    }
    void Comm::isr() volatile{
      ....
    }
    int main(){
      volatile Comm Port;
      Port.isr();✅
      Port.read(0);❌
    }
    volatile 和 const 的语法一样,可以对数据成员,可以对成员函数,可以对对象本身使用,volatile对象调用volatile函数
    
  • 二叉树部分定义