C++学习总结(二)

来源:互联网 发布:知之深爱之切在线阅读 编辑:程序博客网 时间:2024/05/12 05:09

包括以下内容:

  • 红黑树
  • 函数对象的函数适配器
  • explicit关键字
  • const关键字
  • 类类型转换
  • 虚析构函数

红黑树

红黑树(RB-tree)是一个平衡二叉搜索树。

定义:
每个节点不是红色就是黑色;根节点是黑色;如果节点为红,其子节点必须为黑;任一节点至空节点的任何路径,所含之黑节点数必须相同。

函数对象的函数适配器

标准库提供了一组函数适配器(function adapter),用于特化和扩展一元和二元函数对象。

  1. 绑定器(binder):通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象。
  2. 求反器(negator):讲谓词函数对象的真值求反。

例:计算一个容器中所有小于或等于10的元素的个数,可以这样给count_if传递值:

标准库定义了两个binder

  • bind1st,将给定值绑定到二元函数对象的第一个实参;
  • bind2nd,将给定值绑定到二元函数对象的第二个实参。
//对小于等于10的值计数count_if(vec.begin(), vec.end(), bind2nd(less_equal<int>(), 10));

标准库定义了两个negator

  • not1,将一元函数对象的值求反;
  • not2,将二元函数对象的值求反。
//对不小于等于10的值计数count_if(vec.begin(), vec.end(), bot1(bind2nd(less_)));

explicit关键字

explicit的意思是明显的,和它相对应的一个词是implicit,意思是隐藏的

explicit只能用在类构造函数,它的作用是不能进行隐式转换

举例说明:

当不用explicit时,默认是implicit

class myclass{public:    myclass(int size)    {        _size = size;    }private:    int _size;}

在调用时:

myclass a(1);   //这样是没有问题的muclass a = 1;  //这样是没有问题的

第二条看起来很奇怪,但确实是合法的,因为编译器默认有隐式转换,在构造函数只有一个参数时第二条就被转换成和第一条一样。

当加上explicit时:

class myclass{public:    explicit myclass(int size)    {        _size = size;    }private:    int _size;}

在调用时:

myclass a(1);   //这样是没有问题的myclass a = 1;  //编译器报错

这时第二条不会转换,所以编译器报错。

Tips:

当构造函数的实际参数超过一个时,默认是explicit


explicit myclass(int size, int age) {}

myclass(int size, int age) {}
是等效的。

但是有一个例外,如果只需要一个参数,而其他参数都有默认值时,默认的是implicit


myclass(int size, int age = 0) {}

explicit myclass(int size, int age = 0) {}
是不一样的。

const关键字

const需要注意以下几点:

  • 声明的变量只能被读,不能被赋值;

  • 声明后必须初始化;

  • extern const在另一个文件引用不能再次赋值;

  • 其实可以强制类型转换为指针,通过指针修改const常量,但是要慎用;

  • const int*表示指针所指内容是常量,int* const表示指针是常量,const int* const表示指针和所指内容都是常量。

用法:
1、const定义常量,比宏常量的优点是有类型,可以方便编译器检查,减少一些bug;

2、修饰类的数据成员,不能在类声明时初始化,只能在类的构造函数的初始化表中初始化;

3、修饰指针

  • 指针本身是常量不可变
    (char*) const pContent;
    const (char*) pContent;

  • 指针所指向的内容是常量不可变
    const (char) *pContent;
    (char) const *pContent;

  • 两者都不可变
    const char* const pContent;

4、 修饰函数参数,传递过来的参数在函数内不可改变,一般和引用&一起使用;

5、 修饰函数返回值

6、 const常量与define宏定义的区别

  • 编译器处理方式不同
    define宏是在预处理阶段展开。
    const常量是编译运行阶段使用。

  • 类型和安全检查不同
    define宏没有类型,不做任何类型检查,仅仅是展开。
    const常量有具体的类型,在编译阶段会执行类型检查。

  • 存储方式不同
    define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。
    const常量会在内存中分配(可以是堆中也可以是栈中)。

类类型转换

作用:转换可以减少所需要操作符的数目。

转换操作符定义:

class{public:    operator int() const { return val; }    //表示转换为int    //转换函数一般不应该改变被转换对象,所以通常用constprivate:    std::size_t val;}

更一般的形式:

operator type();//type表示内置类型名、类类型名或由类型别名所定义的名字

Tips:

  • 被转换的类型不必与所需要的类型完全匹配,可以在类类型转换后跟随标准转换;

  • 但是只允许一次类类型转换,不能一次类类型转换后跟另一个类类型转换;

  • 避免转换函数的过度使用,避免编写互相提供隐式转换的成对的类。

关于重载转换

  • 如果类既定义了转换操作符又定义了重载操作符,容易产生二义性;

  • 不要定义相互转换的类;

  • 不要定义接受算术类型的操作符的重载版本;

  • 不要定义转换到一个以上算术类型的转换。

虚析构函数

一般来说,基类的析构函数多为虚析构函数。

这样做的好处就是,当一个基类指针指向派生类时,释放指针时会调用派生类的析构函数,如果基类的析构函数不是虚析构函数,那么就不会调用,可能导致内存泄露。

例:

class A{    A();    virtual ~A() { cout << "~A()" << endl; }    virtual void foo() { cout << "A::foo" << endl; }}class B : public A {    ~B() { cout << "~B()" << endl;}       void foo() { cout << "B::foo" << endl;}}

运行:

A *p = new B;p->foo();delete p; 

结果:

B::foo
~B()
~A()

如果把 ~A()前面的virtual去掉的话,结果:

B::foo
~A()


本文来自我的独立博客,欢迎访问。

0 0
原创粉丝点击