精华内容
下载资源
问答
  • 对于使用OpenCV提取图像特征点的方法,全部分为五部分,第一步:创建特征...第三步:创建Mat descriptors1, descriptors2来存储compute到的描述子;第四步:创建vector matches来存储match到的匹配点对;第五步:限...

    对于使用OpenCV提取图像特征点的方法,全部分为五部分,第一步:创建特征检测算子,如ORB,SURF,SIFT等;第二步:创建vector keyPoints1, keyPoints2来存储detect到的特征点;第三步:创建Mat descriptors1, descriptors2来存储compute到的描述子;第四步:创建vector matches来存储match到的匹配点对;第五步:限制描述子之间的距离,选择距离较小的匹配点对。

    说明:
    1.关于vector keyPoints1, keyPoints2,可以理解其为两个具有500行(默认检测500个特征点)的结构体,结构体中包含有的成员有pt、size、angle、response、octave和class_id。使用下列方法进行访问:keyPoints1[0–499].pt,表示特征点的坐标,还有keyPoints1[0–499].pt.x和keyPoints1[0–499].pt.y来分别表示横纵坐标;keyPoints1[0–499].size用来返回特征点的大小;keyPoints1[0–499].angle返回特征点的角度;keyPoints1[0–499].response返回各特征点的响应值;keyPoints1[0–499].octave返回该点位于哪一层金字塔;最后的keyPoints1[0–499].class_id返回特征点的标签。
    2.关于Mat descriptors1, descriptors2,这两个矩阵用来存储两个500行32列(ORB特征)的描述子,即每行代表一个特征点的描述子。
    3.关于vector match,理解与keyPoints1一样。其中:match.size()返回match的行数;match[0–499].queryIdx返回当前“匹配点”在查询图像的特征在KeyPoints1向量中的索引号,可以据此找到匹配点在查询图像中的位置;match[0–499].trainIdx返回当前“匹配点”在训练(模板)图像的特征在KeyPoints2向量中的索引号,可以据此找到匹配点在训练图像中的位置;match[0–499].imgIdx返回当前匹配点对应训练图像(如果有若干个)的索引,如果只有一个训练图像跟查询图像配对,即两两配对,则imgIdx=0;match[0-499].distance返回特征点之间的欧氏距离,越小表明匹配度越高。

    展开全文
  • 类的默认成员函数

    千次阅读 2018-07-31 01:37:26
    类默认生成的成员函数有六个,它们分别是:构造函数、拷贝构造函数、析构函数、赋值操作符重载、取地址操作符重载和const修饰的取地址操作符重载。 一、构造函数 1、什么是构造函数? 初始化对象,有且仅在定义一...

    类默认生成的成员函数有六个,它们分别是:构造函数、拷贝构造函数、析构函数、赋值操作符重载、取地址操作符重载和const修饰的取地址操作符重载。

    一、构造函数
    1、什么是构造函数?
    初始化对象,有且仅在定义一个对象时自动执行一次的函数,就称为构造函数。据悉:类的数据成员是不能在声明类的时候初始化的,因为类并不是一个实体,而是一种抽象的数据类型,并不占据存储空间。

    2、构造函数的特性
    (1)函数名与类名相同。
    (2)无返回值。
    (3)实例化对象时系统会自动调用对应的构造函数。
    (4)构造函数可以重载。
    (5)构造函数可以在类内定义,也可以在类外定义。在类外定义时的格式:类名+“::”+函数名。
    (6)如果类定义中没有给出构造函数,则C++编译器会自动生成一个缺省的构造函数;如果我们定义了一个构造函数,系统就不会生成缺省的构造函数。
    (7)无参的构造函数和缺省的构造函数都认为是缺省的构造函数,所以缺省的构造函数只能有一个。
    【例1】

    #include<iostream>
    using namespace std;
    
    class Date
    {
    public:
        Date() //构造函数
        {}
    
        Date(int year, int month, int day); //在类内声明一个构造函数,在类外定义
    
        在类内定义构造函数
        //Date(int year, int month, int day) //构造函数重载
        //{
        //  _year = year;
        //  _month = month;
        //  _day = day;
        //}
    
        void PrintfDate()
        {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
    private:
        int _year;
        int _month;
        int _day;
    };
    
    //在类外定义构造函数
    Date::Date(int year = 2018, int month = 7, int day = 30) //构造函数重载
    {
        _year = year;
        _month = month;
        _day = day;
    }
    
    int main()
    {
        Date date1(2018,7,30);
        date1.PrintfDate();
        system("pause");
        return 0;
    }

    运行结果:
    这里写图片描述

    3、构造函数的类型
    (1)无参构造函数和有参构造函数
    当用户希望对不同的对象赋予不同的初始值时,就需要用到带参的构造函数,实现不同的初始化。因为用户不能调用构造函数,所以其对应的实参需要在定义对象的时候给定。
    【例2】

    #include<iostream>
    using namespace std;
    
    class Date
    {
    public:
        //无参的构造函数
        Date() 
        {}
    
        //带参的构造函数
        Date(int year, int month, int day) 
        {
            _year = year;
            _month = month;
            _day = day;
        }
    
        void PrintfDate()
        {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
    private:
        int _year;
        int _month;
        int _day;
    };
    
    int main()
    {
        Date date1;//调用无参的构造函数
        date1.PrintfDate();
    
        Date date2(2018, 7, 30);//调用带参的构造函数
        date2.PrintfDate();
    
        /*Date date3();    //注意:这样调用无参的构造函数是错的
        date3.PrintfDate();*/  
    
        system("pause");
        return 0;
    }

    运行结果:
    这里写图片描述
    释:因为无参的构造函数没有初始化,所以打印出来是随机值。

    2、带缺省的构造函数
    构造函数中参数的值既可以通过实参传递,也可以被指定为某些默认值。如果用户不指定实参值,编译系统就使用形参的默认值。
    【例3】

    #include<iostream>
    using namespace std;
    
    class Date
    {
    public:
        //带缺省的构造函数
        Date(int year = 2018, int month = 7, int day = 30)
        {
            _year = year;
            _month = month;
            _day = day;
        }
    
        //半缺省的构造函数(不常用)
        Date(int year, int month = 7)
        {
            _year = year;
            _month = month;
        }
    
        void PrintfDate()
        {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
    private:
        int _year;
        int _month;
        int _day;
    };
    
    int main()
    {
        Date date1;//调缺省的构造函数
        date1.PrintfDate();
    
        Date date2(2018, 7, 30);//调缺省的构造函数
        date2.PrintfDate();
    
        system("pause");
        return 0;
    }

    运行结果:
    这里写图片描述
    注:无参的构造函数和缺省的构造函数都认为是缺省的构造函数,所以缺省的构造函数不能同时存在。

    3、用参数列表对数据成员初始化
    C++还提供了一种初始化数据成员的方法——初始化列表。初始化列表位于构造函数参数列表之后,在函数体“{}”之前。该列表内的初始化工作发生在函数体内的任何代码被执行之前。
    【例4】

    class Date
    {
    public:
        Date(int year, int month, int day)
            :_year(year)
            , _month(month)
            , _day(day)
        {
        }
    private:
        int _year;
        int _month;
        int _day;
    };

    注:使用初始化列表的优点
    (1)如果类存在继承关系,派生类可以直接在其初始化列表里调用基类的特定构造函数以向它传递参数,因为不能在初始化对象时访问基类的数据成员。
    (2)类的非静态const数据成员和引用成员只能在初始化列表里初始化,因为它们只存在初始化语义,而不存在赋值语义。
    (3)类的数据成员的初始化可以采用初始化列表或函数体内赋值两种方式,但是使用初始化列表的方式效率更高。

    二、拷贝构造函数
    1、什么是拷贝构造函数?
    创建对象时使用同类对象来进行初始化,这时所用的构造函数就是拷贝构造函数(Copy Constructor)。拷贝构造函数也是构造函数,但它只有一个参数,这个参数只能是本类的一个对象,而且采用对象的常引用形式。拷贝构造函数的作用就是将实参对象的各成员值一一赋给新的对象中对应的成员。

    2、拷贝构造函数的特征
    (1)拷贝构造函数其实是一个构造函数的重载。
    (2)拷贝构造函数的参数必须使用引用传参,使用传参方式会引发无穷递归调用。
    (3)若为显示定义,系统默认生成缺省的拷贝构造函数,缺省的拷贝构造函数会按照成员的声明顺序依次拷贝类成员进行初始化。
    【例5】

    #define _CRT_SECURE_NO_WARNINGS 1
    #include<iostream>
    
    using namespace std;
    
    class Date
    {
    public:
        //带缺省的构造函数
        Date(int year = 2018, int month = 7, int day = 30)
        {
            _year = year;
            _month = month;
            _day = day;
        }
    
        //拷贝构造函数
        Date(const Date& d)
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
    
        void PrintfDate()
        {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
    private:
        int _year;
        int _month;
        int _day;
    };
    
    int main()
    {
        Date date1;
        date1.PrintfDate();
        //下面调用的两种方法都是拷贝构造函数,是等价的
        Date date2(date1);//调缺省的构造函数
        date2.PrintfDate();
    
        Date date3 = date1;
        date3.PrintfDate();
    
        system("pause");
        return 0;
    }

    运行结果:
    这里写图片描述
    释:
    (1)为什么这里的对象可以直接访问私有成员变量?
    1)在类的成员函数里可以直接访问同类对象的私有或保护的成员。
    2)C++的访问限定符是以类为单位的,即:在这个单位内的成员可以互相访问。
    (2)为什么拷贝构造函数的参数使用传值方式会引起无穷递归调用?如下图:
    这里写图片描述

    三、析构函数
    1、什么是析构函数?
    当一个对象的生命周期结束时,C++编译器会自动调用一个成员函数,这个成员函数即为析构函数,它的作用与构造函数相反。

    2、析构函数的特征
    (1)析构函数名是类名前加一个“~”字符。
    (2)无参数,无返回值,所以不能重载。其实,构造函数和析构函数都没有返回值,就像刚出生的娃娃和即将离世的人,生不带来,死不带去。
    (3)一个类有且只有一个析构函数,对象生命周期结束时,若未定义,C++编译系统会自动生成缺省的析构函数。
    (4)调用构造函数和析构函数的顺序
    (5)因为函数压栈的关系,所以先构造的后析构,后构造的先析构。如果有全局对象或者静态局部对象,则它们在main函数结束或者调用exit函数时才被析构。
    (6)析构函数的作用并不是删除对象,而是在撤销对象时做一些清理工作,比如关闭打开的文件,释放开辟的动态内存等。
    【例6】

    class Array
    {
    public:
        Array(int size)
        {
            _ptr = (int*)malloc(size*sizeof(int));
        }
        //这里的析构函数需要完成清理工作
        ~Array()
        {
            if (_ptr)
            {
                free(_ptr);//释放堆上内存
                _ptr = NULL;//将指针置空
            }
        }
    private:
        int* _ptr;
    };

    四、赋值运算符重载
    如果已经定义了两个或多个对象,则这些同类的对象之间可以相互赋值,即一个对象的值可以赋给另一个同类的对象。这里所指的对象的值是指对象中所有数据成员的值。对象之间的赋值是通过赋值运算符“=”重载实现的,即:将一个对象的值一一复制给另一对象的对应成员。
    【例7】

    class Date
    {
    public:
        Date()
        {}
    
        //拷贝构造函数
        Date(const Date& d)
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
    
        //赋值运算符重载
        Date& operator = (const Date& d)
        {
            if (this != &d)
            {
                this->_year = d._year;
                this->_month = d._month;
                this->_day = d._day;
            }
            return *this;
        }
    
        void PrintfDate()
        {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
    private:
        int _year;
        int _month;
        int _day;
    };

    问:
    (1)为什么operator=赋值运算符需要一个Date&的返回,使用void做返回值可以吗?
    答:可以,只是返回一个引用效率更高。
    (2)赋值运算符重载函数里的if条件判断是在检查什么?

    五、取地址操作符重载和const修饰的取地址运算符重载
    这两个默认成员函数一般不用重新定义,编译器默认会自动生成。
    【例7】

    class Date
    {
    public:
        Date* operator&()
        {
            return this;
        }
        const Date* operator&()const
        {
            return this;
        }
    private:
        int _year;
        int _month;
        int _day;
    };

    释:只有一种情况才会需要自己重载这两个操作符,那就是你只想让别人获取你指定的内容。

    展开全文
  • 第17章 特殊成员函数

    千次阅读 2005-03-16 10:57:00
    第17章 特殊成员函数C++定义了几种只能作为类成员函数说明的函数,它们称为“特殊...这些特殊的函数在下表简要描述:* 构造函数* 析构函数* 临时对象* 转换* new和delete运算符* 用特殊成员函数进行初始化* 拷贝类对

    第17章 特殊成员函数

    C++定义了几种只能作为类成员函数说明的函数,它们称为“特殊成员”函数。这些函数影响着给定类对象创建、删除、拷贝以及转换成其它类型对象的方法。这些函数的另一个重要的特性是它们可以由编译器隐含调用。

    这些特殊的函数在下表中简要描述:

    * 构造函数

    * 析构函数

    * 临时对象

    * 转换

    * new和delete运算符

    * 用特殊成员函数进行初始化

    * 拷贝类对象

    所有上面列出的项目在每个类中可以由用户自定义。

    特殊成员函数同其它成员函数一样遵循相同的访问规则。这些访问规则在第10章“成员访问控制”中描述。表11.1总结了成员函数和友元函数的行为。

    表11.1 函数行为总结

    函数类型 函数是否从基类中继承 能否为虚拟函数 能否有返回值 成员函数还是友元函数 如果用户不提供此函数,编译器能否生成
    构造函数 成员函数
    拷贝构造函数 成员函数
    析构函数 成员函数
    转换 成员函数
    赋值(=) 成员函数
    new void* 静态成员函数
    delete void 静态成员函数
    其它成员函数 成员函数
    友元函数 友元函数

    构造函数

    与类名称具有一样名称的成员函数是构造函数。构造函数不能有返回值,甚至不能有return语句。说明一个有返回值的构造函数是错误的,取构造函数的地址也是错误的。

    如果一个类有构造函数,在程序中每个该类类型的对象在使用之前由此构造函数进行初始化(有关初始化的更多信息参见本章后面的“用特殊成员函数进行初始化”)。

    构造函数是在对象的创建点上被调用的。创建对象可以是:

    * 全局对象(文件范围或外部链接的)。

    * 在一个函数或者小的封闭块中的局部变量。

    * 用new运算符创建的动态对象。new操作在程序的堆或自由存储区中分配一个对象。

    * 因显式调用构造函数而创建的临时对象(详见本章后面的“临时对象”)。

    * 因编译器隐含调用构造函数而创建的临时对象(详见本章后面的“临时对象”)。

    * 其它类的数据成员。在创建类类型的对象时,若此类类型由其它类类型变量组成,将会引起该类中每个对象的创建。

    * 一个类的基类子对象。创建派生类类型的对象时会引起基类构件的创建。

    构造函数的作用

    一个构造函数执行各种任务,但对于程序员来说,这些任务是不可见的,你甚至可以不必为构造函数写任何代码。这些任务都同建立一个完全的、正确的类类型对象实例有关。

    在MS C++中(同样也在很多其它C++中)一个构造函数:

    * 初始化对象的虚拟基指针(vbptr)。如果该类是由虚拟基类派生出的,则这一步要执行。

    * 按说明的顺序调用基类和成员的构造函数。

    * 初始化对象的虚拟函数指针(vfptr)。如果该类有或者继承了虚拟函数,则这一步要执行,虚拟函数指针指向类的虚拟函数表(v-table),并且使虚拟函数的调用同代码正确绑定(binding)。

    * 在构造函数体中执行可选的代码。

    当构造函数结束以后,所分配的存储器就是一个给定类类型的对象。因为构造函数执行这些步骤,故虚拟函数的“迟后绑定”形态可以在虚拟函数的调用点得以解决,构造函数也要构造基类以及构造组合对象(作为数据成员的对象),迟后绑定是C++实现对象的多态行为的机制。

    说明构造函数的规则

    构造函数具有同类名相同的名称。只要遵守重载函数的规则(有关详情参见第12章“重载”),可以说明多个构造函数。

    语法

    类名称(参量说明表opt) cv-修饰符表opt

    C++定义了两种类型的构造函数,缺省的和拷贝的构造函数。如表11.2所述。

    表11.2 缺省的和拷贝构造函数

    构造函数的种类 参量 目的
    缺省构造函数 可以无参量调用 构造一个类类型的缺省对象
    拷贝构造函数 可以接受对同种类类型的引用作为唯一参量 拷贝类类型的对象

    缺省构造函数不要参量即可调用,但你可以说明一个带有参量表的缺省构造函数,只要让所有的参量有缺省值即可。同样,拷贝构造函数必须接受同一类类型的引用作为唯一参量。但可以提供更多的参量,只要后续的参量具有缺省值即可。

    如果你不提供任何构造函数,编译器会试图生成一个缺省的构造函数。同样,如果你没有提供拷贝构造函数,编译器也会试图产生一个。编译器产生的构造函数视为公有的成员函数。如果你说明一个拷贝构造函数,而其第一个参量是一个对象而不是一个引用,则会产生错误。

    编译器生成的缺省构造函数建立对象(初始化vftables和vbtables,如前面所述),并调用基类及成员的缺省构造函数,但不会采取其它的行动。基类和成员的构造函数只要存在,是可访问的,并且是无二义性的就会被调用。

    编译器生成的拷贝构造函数建立一个对象,并且采用一个成员方式的拷贝来复制要被拷贝的对象的内容。如果基类或成员的构造函数存在,则它们将被调用,否则,就采取位方式的拷贝。

    如果所有的基类和该类类型的成员类具有接受一个常量参量的构造函数,则编译器生成的拷贝构造函数接受一个唯一的参量的类型是const type&;否则,编译器生成的拷贝构造函数接受的唯一参量的类型是type &。

    你可以用一个构造函数去初始化一个const和volatile对象,但构造函数本身不能说明为const和volatile的。对于构造函数唯一合法的存储类型inline,对于构造函数使用任何其它的存储类修饰符,包括__declspec关键字都会引起错误。构造函数和析构函数除了__stdcall外不能说明为其它的调用约定。

    派生类是不能继承基类中的构造函数的。当一个派生类的对象在创建的时候,它是从基类构件开始构造的,然而才进入派生类构件。编译器使用每个基类的构造函数作为完整对象初始化的一部分(除了虚拟派生有所不同,参见本章后面的“初始化基类”)。

    显式地调用构造函数

    在程序中,为创建给定类型的对象,可以显式地调用构造函数。例如:创建两个Point型对象的描述一条线段的端点,可以写如下代码:

    DrawLine(Point(13,22),Point(87,91);

    创建了两个Point对象,传递给DrawLine函数,并在该表达式(函数调用)结束后拆除。

    另外一个显式地调用构造函数的情况是在一个初始化中:

    Point pt=Point(7,11);

    创建了一个Point类型的对象,并用接受两个整形参量的构造函数进行初始化。如前面的两个例子中,通过显式地调用构造函数创建的对象是无名的对象,并在它们所创建的表达式中是有生存期的。在本章后面的“临时对象”中将对这一点进行深入的讨论。

    在构造函数内调用成员函数和虚拟函数

    在构造函数里面调用任何成员函数通常是很安全的,因为在执行第一行用户代码之前,对象已经完全建立起来了(已经初始化了虚表等等)。但是当成员函数调用了其抽象基类的虚拟成员函数时,在构造函数和析构函数调用此成员函数存在着潜在的不安全性。

    构造函数可以调用虚拟函数。在调用虚拟函数时,被调用的是在构造函数自身的类中定义的函数(或者从其基类中继承的函数)。下面的例子显示了一个虚拟函数在一个构造函数中被调用时发生的情况。

    #include <iostream.h>
    class Base
    {
    public:
       Base();//缺省构造函数
       virtual void f();  //虚拟成员函数
    };
    Base::Base()
    {
        cout<<"Constructing Base sub-object/n ";
        f();//在构造函数中调用虚拟成员函数
    }
    void Base::f()
    {
        cout<<"called Base::f()/n";
    }
    class Derived:public Base
    {
    public:
        Derived();//缺省构造函数
        void f(); //该类虚拟函数的实现
    };
    Derived::Derived()
    {
        cout<<"constructing Derived object/n";
    }
    void Derived::f()
    {
    	cout<<"Called Derived::f()/n";
    }
    void main()
    {
        Derived d;
    }

    在上面的程序运行的时候,Derived d的说明会引发下列一系列事件:

    1. 类Derived的构造函数(Derived::Derived)被调用。

    2. 在进入到Derived类的构造体之前,基类Base的构造函数被调用。

    3. Base::Base调用函数f,它是一个虚拟函数。通常被调用的函数会是Derived::f,因为对象d是Derived类型的对象。但因为Base::Base是一个构造函数,此时的对象是一个Derived类型的对象,故Base::f将会被调用。

    构造函数与数组

    数组的构造只能使用缺省的构造函数。缺省构造函数要么不接受任何参量,要么对于它的所有参量都有缺省的值。数组通常是按升序来构造的,该数组的每一个成员的初始化都是使用同一构造函数。

    构造的次序

    对于派生类或其成员数据是类类型的类,构造发生的顺序有助于你理解,在任一给定的构造函数中你能够使用对象的哪一部分。

    构造与继承

    一个派生类的对象是从基类到派生类通过按次序为每个类调用构造函数来构造的。

    每个类的构造函数能仅依赖于被完全构造好的它的基类。

    有关初始化的完整描述,包括初始化的顺序,见本章后面的“初始化基类及成员”。

    构造与组合类型

    含有类类型数据成员的类称为组合类。当创建一个组合类类型的对象时,含有类的构造函数在该类的构造函数之前调用。

    有关这种情况的初始化,见本章后面的“初始化基类及成员”。


    析构函数

    析构函数是“反向”的构造函数。它们在对象被销毁(回收)时调用。设计一个函数为类的析构函数只要在类名之前加上(~)号。例如,类string的析构函数是~string()。

    析构函数通常是在一个对象不再需要时,完成“清除”。考虑一下下面类string的说明:

    #include <string.h>
    class String
    {
    public:
      String(char *ch);   //说明构造函数
      ~String();          //以及析构函数
    private:
      char *_text;
    };

    //定义构造函数

    String::String (char *ch)
    {
       //动态分配正确数量的存储器 
       _text=new char[strlen(ch)+1];
       //如果分配成功,则拷贝初始化字符串
       if(_text)
         strcpy(_text,ch);
    }
    //定义析构函数String::~String()
    {
       //回收先前为此字符串保留的存储器
       delete[] _text;
    }

    在前面的代码上。析构函数String::~String 使用delete运算符回收(deallocate)动态分配给text的存储空间。

    说明析构函数

    析构函数与类名称有相同的名称,并且前缀有~号。

    语法

    ~类名称()

    类名称::~类名称()

    语法的第一种形式用于析构函数说明或定义于类说明之中;第二种形式用于析构函数定义于类说明之外。

    有几条规则约束着析构函数的说明。析构函数:

    * 不能接受参量

    * 不能说明有任何返回类型(包括void)l不能用return语句返回值

    * 不能说明为const,volatile或static,但析构函数可以因说明为const,volatile或static的对象的析构而被调用。

    * 可以说明为虚拟的。使用虚析构函数,你可以折除对象而不必知道该对象的类型。由于使用虚拟函数机制,将调用该对象的正确的析构函数。注意,在一个抽象类中,析构函数可以说明为纯虚拟函数。

    使用析构函数

    当下列事件发生时将调用析构函数:

    * 一个用new运算符分配的对象显式地使用delete运算符回收。在用delete运算符对一个对象进行回收时,释放的存储器是“最外派生对象”的(most derived object),或者是一个完整对象的而不是代表基类的子对象。对于“最外派生对象”的回收只是在同虚拟析构函数一起工作时才有保证的。回收可能在多重继承的情形下失败,因为在此情形下,类类型信息并不同实际对象所信赖的类型相一致。

    * 一个在块范围中的局部(自动)对象超出了其范围。

    * 临时对象生存期的结束。

    * 全局或静态成员还存在,但程序已经结束。

    * 用析构函数的全限定名来显式地调用析构函数(详情见本章后面的“显式的析构函数的调用”)。

    前面列表中所描述的情况保证了所有的对象可以用用户自定义的方法折除。如果一个基类或者数据成员有一个可访问的析构函数,同时如果一个派生类没有说明析构函数,编译器会生成一个。编译器产生的析构函数调用基类的析构函数和派生类成员的析构函数。缺省的析构函数是公有的(有关访问属性的详情见第10章“成员访问控制”中的“基类访问说明”)。

    析构函数可以自由地调用类成员函数以及访问类成员数据。当在析构函数中调用虚函数时,该函数是在当前正被折除的类的函数(有关详情见下一节,“析构的次序”)。

    使用析构函数有两条原则,第一是不能取析构函数的地址。第二是派生类不能继承它的基类的析构函数。相反,如前面所解释的,它们总是覆盖了基类的析构函数。

    析构的次序

    当某个对象超出了其范围或被删除时,在其完整的析构中会发生如下一些系列的事件:

    1. 调用该类的析构函数,执行析构函数体的代码。

    2. 非静态成员按它出现在类说明中的反序调用其析构函数。在构造函数中使用的可选成员初始化表的运用不会影响构造或析构的次序(有关初始化成员的详情见本章后面的“初始化基类和成员”)。

    3. 非虚拟基类的析构函数按其说明的反序调用。

    4. 虚拟基类的析构函数按其说明的反序调用。

    非虚拟基类的析构函数

    非虚拟基类的析构函数是按基类名说明的反序调用的。考虑下面的类说明:class MultInherit: public Base1,public Base2...

    在上面的例子中,Base2的析构函数在Base1的析构函数之前调用。

    虚拟基类的析构函数

    虚拟基类的析构函数是按它们出现在有向无环图中的反序被调用的(按深度优先,从左到右,后序遍历)。如图11.1所示描绘了一幅继承图。

    下面列出了图11.1中各类的类头:

    class A

    class B

    class C:virtual public A,virtual public B

    class D:virtual public A,virtual public B

    class E:public C,public D,virtual public B

    对于类型E的对象要确定虚拟基类析构函数的顺序。编译器用如下算法编一个列表:

    1. 从图中的最深点开始(在此例中是E点)遍历左子图。

    2. 而左一直遍历到所有的结点都被访问,记下当前结点的名称。

    3. 再次访问前一个结点(向下并向右)去证实正在标记的结点是否是一个虚拟基类。

    4. 如果被标记的结点是一个虚拟基类,则搜索该列表看是否该结点已经在列表之中了。如果被标记的结点不是一个虚拟基类,则忽略它。

    5. 如果该标记的结点还不在列表中,则把它加入到列表的尾部。

    6. 返回到图的上一层,并沿着右边的下一条路径遍历图。

    7. 转向2继续。

    8. 当本结点所有可能遍历的路径用完后,记下该结点。

    9. 转向3继续。

    10. 继续该过程直到底部的结点再次成为当前结点。

    因此对于类E,析构的顺序是:

    1. 非虚拟基类E。

    2. 非虚拟基类D。

    3. 非虚拟基类C。

    4. 虚拟基类B。

    5. 虚拟基类A。

    这一过程产生一个单一条目的有序列表,类名不会出现两次。一旦该列表建成以后,按反序遍历该列表,从最后到最前将调用每个类的析构函数。

    当一个类的构造函数和析构函数依赖于其它正在被创建的构件或要长期被保留的构件时,例如,(在图11.1中)当A的析构函数(若其代码的执行)依赖于B的仍然存在时,或反过来构造和析构的次序是非常重要的。

    在继承图中,类之间的这种交互依赖具有固有的危险性,因为其后派生的类会改变最后的路径,与此相连也改变了构造和析构的次序。

    显式的析构函数的调用

    显式地调用析构函数通常是不必要的,但它们在执行放在绝对地址中的对象是很有用的。这些对象通常是用带有一个位置参量的用户自定义的new运算符分配的。delete操作不能回收这一存储器,因为它不是从自由存储区中分配的(有关详情见本章后面的“new和delete运算符”)。然而一个对析构函数的调用能够完成合适的清除工作。要显式调用类String的对象s析构函数,可采用下列语句之一:

    s.String::~String();//非虚拟的调用

    ps->String::~String(); //非虚拟的调用

    s.~String();//虚拟调用

    ps->~string(); //虚拟调用

    在上面的代码中显示的显式调用析构函数的符号可以直接使用,而无视是否该类型定义了一个析构函数。这使你能用这种显式的调用,而又不必了解该类型是否定义了一个析构函数。如果一个析构函数未定义,则对该析构函数的显式调用不会产生效果。


    临时对象

    在某些情况下,对于编译器来说必须创建临时对象。因以下原因要创建这些临时变量:

    * 要用一个不同于正被初始化的引用类型的另一类型去初始化一个常量引用。

    * 要保存一个函数返回的用户自定义类型的返回值。如果用户程序没有把返回值拷贝到另一对象中时,这一临时对象才会被创建:

    UDT Func1(); //说明一个返回用户自定义类型的函数

    ...

    Func1(); //调用Func1函数,但忽略其返回值

    //创建一个临时对象以保存返回值

    因为返回值并未拷贝到其它对象,故创建一个临时对象。一个要创建临时对象的更普遍的情形是要调用重载运算符函数的表达式求值中。这些重载的运算符返回用户自定义的类型的值(通常不拷贝到其它对象之中)。考虑表达式:

    ComplexResult=Complex1+Complex2+Complex3

    求出表达式Complex1+Complex2的值,结果存放在临时对象中。接着求表达式temporary+complex3的值,并把结果存放到complexresult中(假定赋值运算符没有被重载)。

    * 存一个造型转换为用户自定义类型的转换结果。当一个给定类型的对象显式地转换为用户自定义类型时,新的对象是作为临时对象来构造的。

    临时对象有一个生存期,在其创建点和拆除点之间有定义。任何一个创建了多个临时对象的表达式最后是按创建它们的相反的顺序拆除的。析构的发生点如表11.3所示。

    表11.3 临时对象的析构点

    创建临时对象的原因 析构点
    表达式求值的结果 所有因为表达式求值的结果而创建的临时对象在表达式求值结束时(也即分号处)或者在控制表达式(for、if、while、do和switch语句)结束时被拆除。
    内置的(非重载的) 符(||和&&) 在右操作数结束之后。在这一析构点,所有因右逻辑运算操作数求值而创建的临时对象被拆除。
    初始化常量引用 如果一个初始化符并不是同要被初始化的引用有相同的l值类型,则创建基于此对象类型的临时对象并用此初始化表达式初始化。这一临时对象在同它绑定在一起的引用对象被拆除以后马上拆除。

    转 换

    给定类类型的对象可以转换为其它类型的对象。这可以按以下来完成:

    从源类类型构造一个目标类类型的对象,并把结果拷贝到目标对象。这一过程称为构造函数式转换。对象也可以由用户提供的转换函数转换。

    当标准转换(在第3章“标准转换”中描述)不能完成从一个给定类型转换到一个类类型时,编译器会选择用户自定义的转换以帮助完成这一工作。除了显式的类型转换,转换还发生在:

    * 一个初始化表达式同被初始化的对象有不同的类型。

    * 在函数调用中使用的参量类型同函数说明中所说明的参量不匹配 。

    * 从函数中返回的对象的类型同函数说明中说明的返回类型不匹配。

    * 两个表达式操作数必须有同样的类型。

    * 一个控制复述或选择语句的表达式需要从提供的表达式中得到不同的类型。

    用户自定义的转换仅用于它没有二义性时,否则会产生一个错误消息。在使用点上将检查二义性,因而,如果可以引起二义性的特征未被使用,只能标明一个类有潜在的二义性,并不会产生任何错误。尽管有很多情形会引起二义性,下面两条是最会引起二义性的原因。

    * 一个使用多重继承派生的类,没有说清楚从哪一个基类选择此转换(见第9章“派生类”中的“二义性”)。

    * 一个显式的类型转换运算符和为此同一目的构造函数同时存在(见本章后面“转换函数”)。

    * 构造函数方式的转换和转换函数方式的转换两者都要遵循第10章“成员访问控制”中所描述的访问控制规则。访问控制仅在转换发现无二义性以后才进行检测。

    转换构造函数

    可以用单一参量调用的构造函数是用于把参量类型转变成类类型的转换。这种构造函数称为转换构造函数。考虑下面的例子:

    class Point
    {
    public;
        Point();
        Point(int);
       ...
    };

    有时需要一种转换,但在类中又不存在转换构造函数。这种转换不能由构造函数完成,编译器不会寻找可以完成该转换的中间类型。比如:假设存在着一个从Point类型到Rect类型的转换以及一个从int到Point类型的转换,但编译器不会通过构造一个中间的Point类型对象来提供一个从int到Rect类型的转换。

    转换和常量

    尽管内置类型的常量如(int,long和double)可以出现在表达式中,但类类型的常量是不允许的(部分是因为,类通常用来表示复杂的对象,用符号不便于表示)。但是如果提供了对内置类型进行转换的转换构造函数,这些内置类型的常量可以用于表达式,并且转换会引出正确的行为。如下例子,一个Money类具有从long和double类型的转换:

    class Money
    {
    public;
        Money(long)
        Money(double)
        ...
        Money operator+(const Money&);  //重载加法运算符
    };

    因此,像下面的表达式可以说明常量值:

    Money AccountBalance=37.89;

    Money NewBalance=AccountBalance+14L;

    第二个例子引起了重载加法运算符的调用(在下一章中讨论)。两个例子都会令编译器在表达式中使用常量以前把它们转换成Money类型。

    转换构造函数的缺点

    因为编译器会隐含地选择一个转换构造函数,故你将放弃对具体调用函数的控制。如果保留全面的控制是很重要的,就不要说明任何接受单一参量的构造函数。相反,定义一个“帮助”函数以完成这些转换,看如下代码:

    #include <stdio.h>
    #include <stdlib.h>
    //说明Money类
    class Money
    {
    public;
    	Money();
    	//定义只能显式调用的转换函数
        static Money Convert(char * ch) {return Money(ch);};
        static Money Convert(double d) {return Money(d);};
        void Print (){printf("/n%f",_amount);}
    private:
        Money(char * ch) {_amount=atof(ch);}
        Money(double d)  {_amount=d;}
        double _amount;
    };
    void main()
    {
        //完成一个从char*到Money 的转换
    	Money Acct=Money::Convert("57.29");
        Acct.Print();
        //完成一个从double到Money的转换
        Acct=Money::Convert(33.29);
        Acct.Print();
    }

    在上面的代码中,转换构造函数是私有的,并且不能在类型转换中使用。但它们可以显式地通过调用Convert函数来激活。因为Convert函数是静态的,它们的访问不需要特别的对象。

    转换函数

    前一节中介绍,用构造函数进行转换中一种类型的对象可以隐含地转换成特殊的类类型。这一节介绍一种方法,通过它可以提供一个显式的转换方法把给定的类类型转换成其它类型。从某种类类型的转换经常是使用转换函数完成的。转换函数使用下面的语法:

    语法

    转换函数名称:

    operator 转换类型名称()

    转换类型名称:

    类型指示符表 指针运算符opt

    下面的例子说明了一个转换函数把Money类型转换成double类型:

    class Money
    {
    public:
       Money();
       operator double() { return _amount;}
    private:
       double _amount;
    };

    一旦给出了前面的类说明,则可以编写下面的代码:

    Money Account;

    ..

    .double CashOnHand=Account;

    把CashOnHand用Account进行初始化会引起从类型Account到double的转换。转换函数通常被称为“造型运算符”。 因为它们(如同构造函数的叫法)是在造型类型转换时调用的函数。下面的例子使用了造型或显式的类型转换打印一个Money型对象的当前值:

    cout <<(double)Account<<endl;

    转换函数可以在派生类中被继承。转换运算符仅仅隐藏基类中转换完全相同类型的转换运算符。因此一个用户自定的operator int函数不会隐藏一定义于基类中的operator short函数。

    在进行隐含转换时仅有一个用户自定义的类型转换函数适用。如果没有显式的定义转换函数,编译器不会寻找一个中间类型以通过它进行对象的转换。如果需要的转换引起了二义性,则会产生一个错误。在多个用户自定义的转换可以适用,或在用户自定义的转换和内置的转换同时存在时产生二义性。

    下面的例子示例了一个存在潜在二义性的类说明:

    #include <string.h>
    class String
    {
    	//定义从char * 类型转换的构造函数
        String(char *) {strcpy(_text,s);}
        //定义char * 的转换
        operator char* ( ) {return_text,}
        int operator==(const String& s)
       {return !strcmp(_text,s._text);}
    private:
        char _text[80];
    };
    int main()
    {
       String s("abcd");
       char *ch="efg";
       //使编译器选择一个转换
       return s==ch;
    }

    在表达式s==ch上,编译器有两种选择,并且没有办法决定哪一种更好。它可以使用构造函数把ch转换为一个String类型的对象,然后采用用户自定义的operator==进行比较。

    然而它也可以把s转换成一个char* 型的指针(使用转换函数),然后采用指针之间的比较。

    因为两种方法之中没有哪一个更好一些,编译器也不能决定比较表达式的意义, 故而产生一个错误。

    说明转换函数的规则

    下面四条规则适用于说明转换构造函数(参见转换函数语法):

    * 类,枚举和typedef名不能在类型指示符表中说明,因而下面的代码会产生错误:

    operator struct String {char string_storage;}();

    相反,结构String的说明应先于转换函数的说明。

    * 转换函数不能带参量,说明参量会引起错误。

    * 转换函数已说明了转换类型名称的返回类型;故为转换函数说明任何返回类会产生错误。

    * 转换函数可以说明为虚拟的。


    new和delete运算符

    C++支持使用new和delete运算符动态分配和回收对象,这些操作从一个称为“自由区”的池中为对象分配存储器。new运算符调用operator new函数,delete运算符调用operator delete函数。

    operator new函数

    当编译器在程序中碰到如下的一个语句时,它就翻译为一个对operator new的调用。

    char *pch=new char[BUFFER_SIZE]

    对于分配0字节存储区的要求,operator new返回一个不同对象的指针(也就是说,反复调用operator new会返回不同的指针)。如果没有足够的存储器满足分配的要求,缺省时,通常operator new返回NULL。当然你可以通过写一个定制的例外处理例程来改变这种缺省的行为。然后调用_set_new_handler运行时间库函数并把你的例外处理例程函数的名称作为其参量。有关恢复机制的细节参见下一节“处理无足够存储器的条件”。

    operator new的两种范围在表11.4中描述。

    表11.4 operator new函数的范围

    运算符 范围
    ::operator new 全局的
    class_name::operator new 类的

    operator new函数的第一个参量必须是类型size_t(在STDDEF.H中定义),其返回值总是void*。

    在用new运算符分配内部数据类型对象时,或者分配不包含用户自定义型operator new函数的类类型对象时,以及分配任意类型的数组时,调用的是全局operator new函数。当operator new用在定义operator new函数的类类型对象对,调用的是类的operator new函数。

    为类定义的operator new函数是一个静态成员函数(因此它不可能是虚拟的)。它对于该类的对象来说隐藏了全局的operator new函数。研究下面的情况,其中new用于分配存储器,并把存储器设为给定的值:

    #include <malloc.h>
    #include <memory.h>
    class Blanks
    {
    public:
      Blanks() { }
      void* operator new(size_t stAllocateBlock,char chInit);
    };
    void * Blanks::operator new(size_t stAllocateBlock,char chInit);
    {
      void * pvTemp=malloc(stAllocateBlock);
      if (pvTemp!=0)
         memset (pvTemp,chInit,stAllocateBlock);
      return pvTemp;
    };

    对于不同的Blanks类型的对象来说,全局operator new函数被隐藏了。因此下面的代码分配一个Blanks型的对象并初始化为0xa5:

    int main()
    {
       Blanks *a5=new (0Xa5) Blanks;
       return a5!=0;
    };

    在括号中提供给new的参量是传递给Blank::operator new作为chInit参量的。然而全局operator new函数是隐藏了的,故按如下调用全局operator new的代码会引起错误。

    Blanks *SomeBlanks=new Blanks:

    在以前的编译器版本中,非类类型和所有的数组(无论它们是否是类类型数组)使用new运算符分配存储器总是使用全局operator new函数。

    从Visual C++5.0开始,编译器开始在类说明中支持成员数组new和delete运算符。

    例如:

    class X 
    {
    public:
       void * operator new[](size_t);
       void   Operator delete[](void*)
    };

    void f(){

    X *pX=new X[5],

    delete []pX;}

    处理无足够存储器条件

    对于失败的存储器分配可以采用如下的代码:

    int *pi=new int[BIG_NUMBER];
    if (pi==0)
    {
    	 cerr<<"Insufficient memory"<<endl;
         return -1;
    }

    当然有其它的办法处理失败的存储器分配请求,即写一个定制的例程去处理这种失败。然后通过调用_set_new_handler运行函数注册你的处理例程。这种方法在下一节介绍。

    使用_set_new_handler

    在某些情况下,可以在分配存储器中采取一些矫正的办法使分配要求可得以满足。为在全局operator new函数失败时获得控制,使用_set_new_handler函数(在NEW.H中定义)如下:

    #include

    #include /

    /定义一个在new分配存储器失败时调用的函数

    int MyNewHandler(size_t size)
    {
    	clog<<"Allocation failed.Coalescing heap."<<endl;
        //调用一个工具函数以恢复一些堆的空间
        return CoalesceHeap();
    }
    void main()
    {
      //把new失败处理函数设为MyNewHandler
      _set_new_handler(MyNewHandler);
      int *pi=new int[BIG_NUMBER];
    }

    在上面的例子中,main函数的第一个语句是把new的处理函数设为MyNewHandler。第二条语句是使用new运算符分配一大块存储器。当分配失败的时候,控制转向MyNewHandler。

    传递给MyNewHandler的参量是要求分配的存储器的字节大小。从MyNewHandler返回的是一个标识以表明是否要再进行一次分配操作。非0值表示应再进行一次,而0值则表示分配失败了。MyNewHandler打印一条警号消息并采取矫正的步骤。如果MyNewHandler返回一个非0值,new运算符再试图分配一次;当MyNewHandler返回0值时,new操作分配企图,把一个0值返回给程序。

    _set_new_handler返回的是一个老的new处理函数的地址。因此如果一个新的new处理函数只在短时间内使用,则老的处理函数可以按如下代码还原:

    #include <new.h>

    ..._

    PNH old_handler=_set_new_handler(MyNewHandler);

    //要求使用MyNewHandler的代码

    ...//重新安装以前的处理函数_set_new_handler(old_handler);用0参量调用_set_new_handler会引起移去new处理函数,也就是没有缺省的处理函数了。

    你可以用任何名称说明new处理函数,但它必须是一个返回int型的函数(非0表示new处理函数继续,0表示失败)。

    如果提供了一个用户自定义的operator new函数,该new处理函数不会在失败时自动调用。

    _set_new_handler的函数原型以及_PNH在NEW.H中定义:

    _PNH_set_new_handler(_PNH);

    类型_PNH是一个指向函数的指针,该函数的类型size_t作为唯一的参量并返回int型。

    operator delete函数

    用new操作动态分配的存储器可以用delete操作释放。delete运算符调用operator delete函数把存储器释放回可用的存储区池中。使用delete操作也会引起类的析构函数的调用(如果有的话)。

    operator delete函数也有全局和类范围的两种。对于给定的类只能为其定义一个operator delete函数;一旦定义以后,它会隐藏全局operator delete函数。全局的operator delete函数总是可以为任意类型的数组所调用。

    全局operator delete函数在说明中带一个void类型的单一参量,它含有要释放对象的指针。它的返回类型是void(operator delete函数不能有返回值)。类成员operator delete函数有两种形式:

    void operator delete(void *);

    void operator delete(void *,size_t);

    对于给定的类只能提供上述两种不同形式中的一种,第一种形式就像全局operator delete一样工作;第二种形式有两个参量。第一个参量是要释放存储器块的指针,第二个参量是要释放的存储器字节大小。当一个基类中的operatordelete函数用于释放派生类对象时,第二种形式特别有用。

    operator delete函数是静态的,因而它不能是虚拟的,operator delete函数必须遵循第10章“成员访问控制”中描述的访问控制。

    下面的例子显示了用户设计的operator new和operator delete函数用于记录分配和释放存储器:

    #include <iostream.h>
    #include <stdlib.h>
    int fLogMemory=0;//是否记录(0=否;非0=是)
    int cBlocksAllocated=0;  //分配的块数
    //用户自定义的new操作
    void *operator new(size_t stAllocateBLock)
    {
    	static fInOpNew=0,//保护标志
    	if(fLogMemory && !fInOpNew)
        {
           fInOpNew=1;
           clog<<"Memory Block"<<++cBlocksAllocated
               <<"allocated for"<<stAllocateBlcok
    	       <<"bytes/n";
           fInOpNew=0;
         }
         return malloc(stAllocateBlock);
    }
    //用户定义的operator_deletevoid operator delete(void *pvMem)
    {
       static fInOpDelete=0;  //保护标志
       if (fLogMemory &&!fInOpDelete)
       {
          fInOpDelete=1;
          clog<<"Memory block"<<--cBlocksAllocated
              <<"deallocated/n";
          fInOpDelete=0;
    	}
    free (pvMem);
    }
    int main (int argc,char*argv[])
    {
        fLogMemory=1;  //打开log标识
        if (argc>1)
        for (int i=0; i<atoi(argv[1]);++i )
        {
           char *pMem=new char[10];
           delete [] pMem;
        }; 
        return cBlocksAllocated;
    }

    上面的代码可以用来检查“存储器漏损”,即从自动堆中分配以后就没有释放的存储器。为完成这一检查,全局new和delete操作被重新定义以统计分配和释放的存储器。

     

    从Visual C++5.0开始,编译器在类说明中支持成员数组new和delete操作,例如:

    class X
    {
    public:
        void * operator new[] (size_t);
    	void   operatot delete[](void*);
    };
    void f()
    {
        X* pX=new X[5];
        delete[] pX;
    }

    用特殊成员函数初始化

    这一节描述使用特殊成员函数的初始化,它是下面一些初始化论题的扩展。

    * 第7章“说明”中的“初始化集合”,描述了如何初始化非类类型数组以及简单类类型对象。这些简单的类类型不可有私有的及保护的成员,它们也不能有基类。

    * 构造函数。它解释了如何使用特殊的构造函数初始化类类型对象。

    缺省的初始化方法是执行一个位方式的拷贝,把初始化器拷贝给要被初始化的对象。这种技术仅适用于:

    * 内部类型的对象。例如:

    int i=100;

    * 指针。例如:

    int iint *pi=&i;

    * 引用。例如:

    String sFileName("FILE.DAT");

    String &rs=sFileName;

    * 类型对象,该类不可有保护的或私有的成员,不能有虚拟函数也不能有基类,例如:

    struct Point
    {
        int x,y;
    }

    Point pt={10,20}; //static storage calss only

    通过说明构造函数(有关说明这种函数的详情见本章开始的“构造函数”),类可以说明更多精确的初始化。如果一个对象的类型是有构造函数的类类型,该对象一定会被初始化,否则一定存在着缺省的构造函数。没有特别初始化的对象会激活类的缺省构造函数。

    显式的初始化

    C++支持两种形式的显式初始化。

    * 支持在括号中提供初始表:

    String sFileName("FILE.DAT);

    在括号中的初始化器表的项是作为类构造函数的参量。这种形式的初始化使得对一个对象使用多个值进行初始化成为可能,并且也可以同new运算符联合使用。如:

    Rect *pRect=new Rect(10,15,24,97);

    * 支持使用等号初始化语法提供单一初始化器,例如:

    String sFileName="FILE.DAT";

    尽管前面的例子同第一种形式中的String例子以同样的方式工作,但该语法并不适用于同自由堆中分配的对象一起使用。在等号右边的单一表达式是作为类的拷贝构造函数的参量,因此它必须是能够转换成类类型的某种类型。

    注意因为在初始化的上下文中等号(=)同赋值号是不同的,故重载operator=对于初始化没有影响。

    等号初始化语法同函数式语法是不同的,尽管在大多数情况下产生的代码是相同的。它们之间的不同在于:当使用等号语法,编译器的行为就像发生了如下的顺序事件:

    * 创建一个同要被初始化的对象同一类型的临时对象。

    * 把临时对象拷贝到要被初始化的对象之中。

    在编译器执行这些步骤之前,构造函数必须是可访问的。尽管编译器在大多数情况下可以忽略临时对象的创建和拷贝步骤,然而一个不可访问的拷贝构造函数会引起等号初始化的失败。考虑下面的例子:

    class anInt
    {
        anInt (const anInt&);   //私有拷贝构造函数
    public:
        anInt(int);   //公有的int构造函数
    };

    ...

    anInt myInt=7; //破坏了访问控制,试图引用私有拷贝构造函数

    anInt myInt(7); //正确;设有调用拷贝构造函数

    在函数调用中,传值形式的类型参量以及传值形式的返回对象用下面的代码概念上地进行初始化。

    type-name name=value

    例如:

    String s="C++";

    因此,参量类型必须是可以转换为作为参量进行传递的类类型。该类的拷贝构造函数,以及自定义转换运算符或接受实际参量的构造函数必须是公有的。

    在使用new运算符的表达式中,从自由堆中分配的对象概念性地用以下形式进行初始化:

    type-name name(initializer1,initializer2,...iniatializern)

    例如:

    String *ps=new String("C++");

    对于基类构件和类成员对象的初始化也是概念性地用这种方法初始化的(详见本章后面的“初始化基类及成员”)。

    初始化数组

    如果一个类有一个构造函数,此类的数组由一构造函数进行初始化。若初始化器表中的项数小于数组元素的个数,则缺省构造函数将用于剩下的数组元素。若此类并未定义缺省构造函数,则初始化器表必须是完全的,也就是数组中的每一个元素必须有一个初始化器。

    考虑定义了两个构造函数的Point类:

    class Point
    {
    public:
       Point();//缺省构造函数
       Point(int,int); //从两个int型的构造函数
       ...
    }

    一个Point对象数组可以按如下说明:

    Point aPoint[3]=

    { Point(3,3) //使用int,int构造函数

    }

    aPoint数组的第一个元素使用构造函数Point(int,int)来构造,剩余的两个元素使用缺省构造函数来构造。

    静态成员数组(无论是否为const)都可以在它们的定义中(在类说明之外)进行初始化。例如:

    class WindowColors
    {
    public:
      static const char *rgszWindowPartList[7];
      ...
    };

    const char * WindowColors::rgszWindowPartList[7]=

    { "Active Title Bar","Inactive Title Bar", "Title Bar Text", "Menu Bar", "Menu Bar Text", "Window Background", "Frame" };

    初始化静态对象

    全局静态对象是按它们出现在源代码中的次序进行初始化,它们按此相反的次序拆除。然而,交叉转换单元中的初始化的次序依赖于连接器如何组织目标文件,析构的次序仍然按对象创建次序的相反次序发生。

    局部静态对象的初始化发生在当它们第一次出现在程序流中时,而且它们在程序结束时也是按此相反的次序被拆除的。局部静态对象的析构仅发生在程序流中发现此对象,且此对象被初始化时。

    初始化基类及成员

    一个派生类的对象是由代表每个基类的构件以及对于此特殊类唯一的一个构件组成。含有成员对象的类对象也可包含其它类的实例。这一节描述当这种类型的类对象在创建时,这些构件对象是如何初始化的。

    为了完成初始化,得运用构造函数初始化或ctor初始化语法。

    语法

    ctor-初始化器

    成员初始化器表

    成员初始化器表:

    成员初始化器

    成员初始化器,成员初始化器表

    成员初始化器:

    完整的类名称(表达式表opt)

    标识符(表达式表opt)

    用于构造函数中的这一语法,在下一节“初始化成员对象”和“初始化基类”中更详尽介绍。

    初始化成员对象

    类可以含有类类型成员对象,但要保证满足对这些成员对象的初始化,并要满足如下之一的条件。

    * 所含对象的类不要求有构造函数。

    * 所含对象的类有一可访问的缺省构造函数。

    * 包容类的构造函数显式初始化所含的对象。

    下面的例子给出了如何完成这样的初始化:

    //说明一个Point类

    class Point
    {
     public:
       Point (int x,int y) {_x=x;_y=y;}
     private:
       int _x,_y;
    }

    //说明一个包含有Point类型对象的rectangle类

    class Rect
    {
    public:
       Rect (int x1,int y1,int x2,int y2);
    private:   Point _topleft,_bottomright;
    }

    //定义类Rect的构造函数,这一构造函数显式地初始化类Point对象

    Rect :: Rect(int x1,int y1,int x2, int y2):

    _topleft(x1,y1),_bottomright(x2,y2)

    {

    }

    在前面例子中显示的Rect类中含有两个Point类成员对象。它的构造函数显式地初始化了对象_topleft和_bottomright。注意跟在构造函数的后括号后面的是冒号。跟在冒号后面的是成员名和参量,该参量是用来初始化类对象的。

    警告:在构造函数中说明的成员初始化顺序不会影响这些成员被构造的次序。成员是按它们在类说明中的说明顺序被构造的。

    引用及常量成员对象必须用成员初始化语法(在“初始化基类及成员”中描述) 来初始化。没有其它的办法来初始化这些对象。

    初始化基类

    直接基类的初始化同成员对象的初始化以同样的方式进行。考虑下面的代码:

    //说明类窗口

    class Window
    {
    public:
        Windodw (Rect rSize);
    	...
    }

    //说明类DialogBox直接派生于类Window

    class DialogBox:public Window
    {
    public:
       DialogBox(Rect rSize);
       ...
    }

    //定义DialogBox构造函数。这一构造函数显式地初始化Window子对象

    DialogBox:: DialogBox(Rect rSize):Window(rSize)

    {

    }

    注意在DialogBox的构造函数中,Windows基类是使用参量rSize进行初始化的。这一初始化由要初始化的基类名称组成,基类名称后跟随的是由圆括号括起来的传递给基类构造函数的参量表。

    在基类的初始化中,不代表基类构件的子对象的对象称为一个“完整对象”。该“完整对象”的类视为该对象的“最外派生类”(most derived)。

    代表虚拟基类的子对象是由“最外派生类”的构造函数进行初始化的。这意味着只要说明了虚拟基类的派生类,则最外派生类必须显式初始化虚拟基类,否则虚拟基类必须有一个缺省的构造函数。在最外派生类以外的其它类的构造函数中出现的虚拟基类的初始化将被忽略。

    尽管对基类的初始化通常限于直接基类的初始化,但一个类的构造函数可以初始化一个非直接的虚拟基类。

    基类及成员的初始化次序

    基类和成员对象按如下次序进行初始化:

    1. 虚拟基类的初始化按它们出现在有向无环图中的次序。有关使用有向无环图去构造一个唯一子对象列表的详情见第9章“派生类”中的“虚拟基类”(注意,这些子对象的析构是按反序遍历该列表)。有关如何遍历有向无环图的情况见本章前面的“析构的次序”。

    2. 非虚拟基类按它们在类说明中出现的次序进行初始化

    3. 成员对象的初始化是按它们在类中说明的次序进行初始化。

    在构造函数的成员初始化器表中说明的成员初始化器和基类初始化器的次序不会影响到基类及成员对象进行初始化的次序。

    初始化的范围

    对基类和成员对象的初始化定值在与其说明在一起的构造函数的范围内,因此,它们能够隐含地引用类成员。


    拷贝类对象

    两种操作会引起对象的拷贝:

    * 赋值。在一个对象的值被赋给另一个对象时,第一个对象是被拷贝给第二个对象的。因此:

    Point a,b;

    ...

    a=b;

    会引导起b的值拷贝给a。

    * 初始化。初始化发生在说明一个新对象时,发生在给函数传递参量的值时,也生在以传值形式从函数返回参量时。

    程序员可以为类类型对象定义拷贝的意义。考虑下面的代码:

    TextFile a,

    b;a.Open("FILE1.DAT");

    b.Open("FILE2.DAT");

    b=a;

    上面的代码可以意味着:“把FILE1.DAT的内容拷贝到FILE2中”;或者它也可意味着:“忽略FILE2.DAT,并把b作为FILE1.DAT的第二个句柄”。程序员有责任为每个类赋以合适的拷贝语义。

    拷贝由下列两种方法完成:

    * 赋值(使用赋值运算符,operator=)

    * 初始化(使用拷贝构造函数)(有关拷贝构函数的详情见本章开头的“说明构造函数的规则”)。

    任一给定的类可以实现一种或以上两种拷贝方式。如果两种方法都未实现,赋值将作为一个成员方式(memberwise)赋值,初始化也作为成员方式初始化。在本章后面会讨论有关“成员方式的赋值和初始化”的细节。

    拷贝构造函数带有一个类型class-name&的参量,其中class-name是定义拷贝构造函数的类的名称。例如:

    class Window
    {
    public:
        Window (const Window&);   //定义拷贝构造函数
        ...
    };

    注意:只要可能,拷贝构造函数的参量都应该是const class-name&。这可以防止拷贝构造函数无意中修改了要拷贝的对象。这也使得拷贝const对象成为可能。

    编译器生成的拷贝

    编译器生成的拷贝构造函数,就像用户自定义的拷贝构造函数,带有一个“对类类型引用”的参量类型。只有一个例外是:当所有的基类及成员类都说明有拷贝构造函数,并且该函数带有一个具有const class-name& 型的参量时。在这种情况下,编译器生成的拷贝构造函数的参量是const型。

    当拷贝构造函数的参量不是const型时,通过拷贝一个const对象进行初始化会产生一个错误。但反过来就不同:如果参量类型是const型的,通过拷贝一个非const型对象进行初始化不会产生错误。

    关于const,编译器产生的赋值运算符也遵循同样的方式。它们带有一个唯一的class-name&类型的参量,除非赋值运算符在所有的基类及成员类中采用的参量类型是const class-name& 。在这种情况下,编译器为该类产生的赋值运算符采用count型参量。

    注意:当虚拟基类由拷贝构造函数(无论是编译器产生的,还是自定义的)进行初始化时,它们仅在被构造时初始化一次。

    这些含义同拷贝构造函数很相似。当参量类型不是const型时,从一个const型对象赋值会产生一个错误。反之则不同:如果一个const型值赋给一个非const型值,赋值可以进行。

    有关重载赋值运算符的更多的情况见第12章“重载”中的“赋值”。

    成员方式的赋值与初始化

    缺省的赋值和初始化的方法分别是“成员方式”的赋值和“成员方式的初始化”。

    “成员方式的赋值”由从一个对象到另一个对象的拷贝过程组成。每次赋值一个成员,就像每个成员单独赋值一样,“成员方式的初始化”也是由从一个对象拷贝到另一个对象的拷贝过程组成。每次初始化一个对象,就像是每个成员单独被初始化。这两种方式的主要区别是:成员方式赋值激活的是每个成员的赋值运算符(operator=),而成员方式初始化激活的是每个成员的拷贝构造函数。成员方式的赋值仅由说明为如下方式的赋值运算符来实现的。

    type& type::operator=([const|volatile]type&)

    如果下列任何条件存在,则成员方式赋值的缺省运算符不能出现:

    * 某成员类中有const成员。

    * 某成员类有引用成员。

    * 某成员类或其基类有一个私有的赋值运算符(operator=)。

    * 某成员类或其基类没有赋值运算符(operator=)。

    如果一个类或其基类有一个私有的拷贝构造函数或者是下列任何条件存在,则该类的对于成员方式初始化的缺省构造函数不可能生成:

    * 某成员类有const成员。

    * 某成员类有引用成员。

    * 某成员类或其基类有一个私有的拷贝构造函数。

    * 某成员类或基类没有拷贝构造函数。

    对于一个给定的类缺省的赋值运算符和拷贝函数总是已经说明了的。除非下面的两个条件都得以满足,否则它们是未定义的。

    * 该类并没有为这一拷贝提供自定义的函数

    * 程序要求提供此函数。如果碰到一个要求成员方式拷贝的赋值运算符或者初始化符,或者取了类的operator=函数的地址则这一要求是存在的。

    若上述两个条件都没有满足,则编译器不必为缺省的赋值运算符和拷贝构造函数产生代码(MSC的编译器采用优化可以去掉这些代码)。尤其当类说明了一个自定义的operator=函数,并以一个对类类型的引用为参量,则不会产生缺省的赋值运算符函数。若类说明了一个拷贝构造函数,则也不会产生缺省的拷贝构造函数。

    因此对于给定的类A,下面的说明总是存在的。

    //拷贝构造函数和赋值操作的隐含说明

    A::A(const A&)A&

    A::operator=(const A&)

    按上面的标准,这一定义只是在需要的时候才要提供。上面例子中的拷贝构造函数被认为是类的公有成员函数。

    缺省的赋值运算符使得一给定类的对象可以赋值给其公有基类型的对象。考虑下面的代码:

    class Account
    {
    public:
       //公有成员函数
       ..
    private:
       double _balance;
    };
    class Checking:public Account
    {
    private:
       int _fOverdraftProtect;
    };

    ...

    Account account;

    Checking checking;

    account=checking;

    在上面的例子中,选用的赋值运算符是Account:operator=。因为缺省的operator=函数带有一个类型Acconut&的参量,checking的Account型子对象拷贝给account;而fOverdraftProtect没有拷贝。

    展开全文
  • 在类体说明的函数作为类的成员,称为成员函数。一般的成员函数,它是根据某种类的功能的需要来定义的。除此之外,又讨论了一些特殊的成员函数:构造函数、析构函数、拷贝初始化构造函数等。本节讨论除成员函数定义...
    在类体中说明的函数作为类的成员,称为成员函数。一般的成员函数,它是根据某种类的功能的需要来定义的。除此之外,又讨论了一些特殊的成员函数:构造函数、析构函数、拷贝初始化构造函数等。本节讨论除成员函数定义与说明之外的其它一些特殊属性。

    一.内联函数和外联函数

    类的成员函数可分为内联函数与外联函数。内联函数是指定义在类体内的成员函数,即该函数的定义放在类的体内。而对成员函数的说明放在体内,其函数的定义放在体外称之为外联函数。如果使外联函数转变为内联函数,只须在函数头部左端加上关键字inline即可。
    内联函数在调用时并不发生程序执行的转移,而是在调用内联函数处用内联函数体的代码来替换,以节省调用开销,提高运行效率。

    【说明】:函数是一种更高级的抽象。它的引入使得编程者只关心函数的功能和使用方法,而不必关心函数功能的具体实现;函数的引入可以减少程序的目标代码,实现程序代码和数据的共享。但是,函数调用也会带来降低效率的问题,因为调用函数实际上将程序执行顺序转移到函数所存放在内存中某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方。这种转移操作要求在转去前要保护现场并记忆执行的地址,转回后先要恢复现场,并按原来保存地址继续执行。因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率。特别是对于一些函数体代码不是很大,但又频繁地被调用的函数来讲,解决其效率问题更为重要。引入内联函数实际上就是为了解决这一问题。 
    在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来进行替换。显然,这种做法不会产生转去转回的问题,但是由于在编译时将函数休中的代码被替代到程序中,因此会增加目标程序代码量,进而增加空间开销,而在时间代销上不象函数调用时那么大,可见它是以目标代码的增加为代价来换取时间的节省。 
    在程序中,调用其函数时,该函数在编译时被替代,而不是像一般函数那样是在运行时被调用

    使用内联函数应注意的事项 
    内联函数具有一般函数的特性,它与一般函数所不同之处公在于函数调用的处理。一般函数进行调用时,要将程序执行权转到被调用函数中,然后再返回到调用它的函数中;而内联函数在调用时,是将调用表达式用内联函数体来替换。在使用内联函数时,应注意如下几点: 

    1.一个函数可以自已调用自已,称为递归调用(后面讲到),含有递归调用的函数不能设置为inline;

    2.使用了复杂流程控制语句:循环语句和switch语句,无法设置为inline;

    3.由于inline增加体积的特性,所以建议inline函数内的代码应很短小。最好不超过5行。

    4.inline仅做为一种“请求”,特定的情况下,编译器将不理会inline关键字,而强制让函数成为普通函数。出现这种情况,编译器会给出警告消息。

    5.在你调用一个内联函数之前,这个函数一定要在之前有声明或已定义为inline,如果在前面声明为普通函数,而在调用代码后面才定义为一个inline函数,程序可以通过编译,但该函数没有实现inline。

    总结为几句话

    1.在内联函数内不允许用循环语句和开关语句。 
    2.内联函数的定义必须出现在内联函数第一次被调用之前。

        只要声明为内联,编译器就不把它编译成一次函数调用,而只是类似于把函数的代码拷贝到被调用的地方,而且这完全是编译器私下里完成的,原来的访问权限等问题丝毫不受影响。这不是两全齐美了吗:在保证代码的面向对象性和结构化不受损失的条件下,程序的效率也没有损失。

    Class   MyClass  
    {  
    public:  
    	inline   int   GetState();  
    private:  
    	int   m_iState;  
    }  
       
    int inline  MyClass::GetState()  
    {  
    	return   m_iState;  
    }  
    

    内联函数还有另外一种写法,就是直接写在类中,此时,不必使用“inline”关键字。

    class   MyClass  
    {  
    public:  
    	int   GetState()
    	{   
    		return   m_iState;   
    	}  
    private:  
    	int   m_iState;  
    }; 
        内联函数只是一种编译机制,用上面两种形式声明的函数仅仅是建议编译器进行内联,而编译器是否内联不一定。正如前面所说,函数调用的开销只是对小的函数不可忽略,对于重量级的函数还是可以忽略的,而且在绝大多数的场合,函数调用才是人间正道,才是解决问题的最佳。所以大多数编译器并不把带有循环、递归等或者代码比较多的函数进行内联编译,有的甚至不允许声明成内联的。

    二.静态成员

    类是类型而并非数据对象,每个类的对象都是该类数据成员的拷贝。然而,在有的时候,需要类的所有对象在类的范围内共享某个数据。声明为static的类成员便能在类的范围内中共享,称之为静态成员。因此,静态成员的提出是为了解决数据共享问题。
    http://blog.csdn.net/skyereeee/article/details/8000512

    static主要有三个作用:

    (1)局部静态变量

    (2)外部静态变量/函数

    (3)静态数据成员/成员函数
    由于全局变量不属于类的成员,它的访问权限是完成开放的,因此,既不安全,又影响了重用性。
    而静态成员具有上述问题的双重属性,不但数据可以得到封装,又可为所有的对象所共享。
    静态数据成员是类中所有对象所共享的成员,而不是某个对象的成员。
    静态数据成员的另一个优势是可以节省内存,对多个对象来说,静态数据成员只存储一处,为所有的对象所共用。
    静态数据成员的值对每个对象都是一样的,但它的值可以更新。只要对静态数据成员的值更新一次,可保证所有对象存取更新后的相同值,这样可提高时间效率。
    2.1静态成员的初始化在类的体外进行,而值得注意的是前面不加关键字static的意义在于避免与一般静态变量或对象相混淆。
    class   MyClass  
    {  
    public:  
    	int   GetState()
    	{   
    		return   m_iState;   
    	}  
    private:  
    	int   m_iState;  
    	static int a;//定义
    } ;
    <span style="color:#ff0000;">
    </span>int MyClass::a = 0;//初始化

    2.2 静态数据成员被 类 的所有对象所共享,包括该类派生类的对象。即派生类对象与基类对象共享基类的静态数据成员
    // StaticForClass.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    class   MyClass  
    {  
    public:  
    	int   GetState()
    	{   
    		return   m_iState;   
    	}  
    	static int a;	//定义
    private:  
    	int   m_iState;  	
    }; 
    int MyClass::a = 0;//初始化
    
    class DerivedClass:public MyClass
    {
    	
    };
    int _tmain(int argc, _TCHAR* argv[])
    {
    	MyClass A;
    	DerivedClass B;
    	printf("%d\n",A.a);
    	A.a++;
    	printf("%d\n",B.a);
    	getchar();
    	return 0;
    }<span style="color:#ff0000;">
    </span>

    三、静态成员函数

    3.1 静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用 类成员函数指针来储存
    // StaticForClass.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    
    
    class   MyClass  
    {  
    public:  
    	static int   GetState();
    	void print();
    	static int a;	//定义
    private:  
    	int   m_iState;  
    	
    }; 
    int MyClass::a = 0;//初始化
    
    int MyClass::GetState()//静态函数的调用
    {
    	return a;
    	//return m_iState;/* error C2597: 对非静态成员“MyClass::m_iState”的非法引用*/
    }
    void MyClass::print()
    {
    	printf("%d",a);
    }
    
    class DerivedClass:public MyClass
    {
    	
    };
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    #if 0
    	MyClass A;
    	DerivedClass B;
    	printf("%d\n",A.a);
    	A.a++;
    	printf("%d\n",B.a);
    #endif
    	int (*pf1)() = &MyClass::GetState;//普通的函数指针 
    	//int (MyClass::*pf2)() = &MyClass::GetState;
    	/*error C2440: “初始化”: 无法从“int (__cdecl *)(void)”转换为“int (__thiscall MyClass::* )(void)”*/
    	void (MyClass::*pf3)() = &MyClass::print;//普通的函数指针 
    	
    	getchar();
    	return 0;
    }
    

    3.2 静态成员函数没有this指针,.静态成员函数不可以调用类的非静态成员,它不能返回非静态成员,因为除了对象会调用它外,类本身也可以调用。
    class   MyClass  
    {  
    public:  
    	static int   GetState();//静态函数
    	static int a;	//定义
    private:  
    	int   m_iState;  	
    }; 
    int MyClass::a = 0;//初始化
    int MyClass::GetState()//静态函数的调用
    {
    	return a;
    	//return m_iState;/* error C2597: 对非静态成员“MyClass::m_iState”的非法引用*/
    }

    关于静态成员函数,可以总结为以下几点:
    1、出现在类体外的函数定义不能指定关键字static;
    2、
    静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
    3、非静态成员函数可以任意地访问静态成员函数和静态数据成员;
    4、静态成员函数不能访问非静态成员函数和非静态数据成员;
    5、由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;
    6、调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数
    7、当同一类的所有对象使用一个量时,对于这个共用的量,可以用静态数据成员变量,这个变量对于同一类的所有的对象都取相同的值。静态成员变量只能被静态成员函数调用。静态成员函数也是由同一类中的所有对象共用。只能调用静态成员变量和静态成员函数。




    展开全文
  • PCL 点云特征描述与提取

    万次阅读 2018-07-10 15:28:23
    说明以下均为 Being_young 前辈所写,现转载过来,再加上自己的理解,重新写了一遍,方便自己日后使用博客...· 目录索引PCL 点云特征描述与提取PCL 描述三维特征相关基础PCL 法线估计实例 —— 估计某一点的表面...
  • · 说明 以下均为 Being_young 前辈所写,现转载过来,再加上自己的理解,重新写了一遍,方便自己日后使用 ...或者参考另一篇博文:[PCL系列——从深度图像...PCL 点云特征描述与提取 PCL 描述三维特征相关基础 PCL
  • Python函数的基本特征详解

    千次阅读 2018-04-25 00:00:00
    Python中文社区全球Python中文开发者的精神部落要点抢先看1、开始编写一个简单完整的函数 2、函数也是对象 3、函数的多态内涵今天开始,我们来讲讲函数,简而言之一...
  • 最近在学习关于图像匹配拼接部分内容,在学习SIFT特征匹配方法时,本来想着先尝试着用opencv整合后的features2d类来创建一个生成特征点的简单小程序,然而却是在关键函数detectAndCompute()处遇到了麻烦,无论怎么s...
  • 关于SHFileOperation函数的补充

    万次阅读 2011-09-09 10:23:09
    今天看到一个关于SHFileOperation函数使用的介绍,发现下面一些有用的资料,都是些之前未仔细了解的方面,这里把他们记录下来,留待以后查阅参考。 fFlags成员标志间的关系 标志 抑制的对话框 ...
  • 什么叫特征检测?就是检测图像目标的特征呗,所谓特征,不管你怎么...对特征的描述有很多种方法和算子,常见的有SIFT特征描述算子、SURF特征描述算子、ORB特征描述算子、HOG特征描述、LBP特征描述以及Harr特征描述...
  • 关于C++函数重载及虚函数

    千次阅读 2006-01-26 22:03:00
    关于C++函数重载及虚函数 学校要搞什么知识点精讲,今天下午临时写了两篇关于C++中函数重载及虚函数的文章。只是打的讲义草稿,所以很多用语都很随便。 函数重载如何讲函数重载:What——函数重载是什么?why——...
  • 什么叫特征检测?就是检测图像目标的特征呗,所谓特征,不管你怎么...对特征的描述有很多种方法和算子,常见的有SIFT特征描述算子、SURF特征描述算子、ORB特征描述算子、HOG特征描述、LBP特征描述以及Harr特征描述...
  • 常用概率分布函数及随机特征

    万次阅读 2018-02-08 20:28:52
    常见分布的随机特征离散随机变量分布伯努利分布(二点分布)伯努利分布亦称“零一分布”、“两点分布”。称随机变量X有伯努利分布, 参数为p(0&lt;p&lt;1),如果它分别以概率p和1-p取1和0为值。EX= p,DX=p(1-p...
  • SIFT(Scale-invariant feature transform)是一种检测局部特征的算法,该算法通过求一幅图特征点(interest points,or corner points)及其有关scale 和 orientation 的描述子得到特征并进行图像特征点匹配 ...
  • 在基于对象的程序设计方法,我们通过一组数据抽象来建立问题的模型,在C++我们把这些抽象称为类。比如图书馆资料登记/借阅系统就是由类的抽象实例(比如书名、借阅者、还书时间等)之间的相互作用表现出来,以此...
  • 1.函数接口说明 ...(4) 纯虚成员函数还是非纯虚成员函数? (5) 静态成员函数还是非静态成员函数? (6) 常量成员函数还是非常量成员函数? (7) public、protected 还是 private 成员函数? (8)...
  • Linux---exec族函数解析

    万次阅读 多人点赞 2016-10-18 20:17:22
    fork函数是用于创建一个子进程,该子进程几乎是父进程的副本,而有时我们希望子进程去执行另外的程序,exec函数族就提供了一个在进程启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,...
  • //这个成员函数重载了函数括号运算符,让他具有函数的特点 //但是还不知道在其他程序块是如何应用这块代码的。 //InputArray和OutputArray是opencv的两个函数接口 void ORBextractor::operator()( InputArray _...
  • 对C++类的一些特性做些常识性的总结,增强...分析了构造函数和析构函数的性质,函数重载以及this指针。需要注意理解的是 类 以及 类的对象 两者之间的关系,希望能更好的体会面向对象以及提升编程抽象事物的能力。
  • 深度学习常见的loss函数汇总

    千次阅读 2020-05-19 21:22:18
      损失函数(Loss Function)分为经验风险损失函数和结构风险损失函数,...在此基础上,在深度学习任务又发展了很多不同的损失函数,由于在网络训练过程损失函数指导着网络的学习,因此选择合适的损失函数也很重
  • opencv之SURF特征点提取及匹配

    万次阅读 多人点赞 2017-04-18 18:54:16
    通过Surf算法检测到的特征点其描述符包含了这个点的位置和尺度信息,故对两幅图片进行匹配时可以通过两幅图中特征点匹配对进行匹配。即使物体位置和光照的改变也能够有良好的匹配效果。 在本片文章中将使用...
  • 本身我书上也有这样的教程,... Delphi - 关于钩子函数HOOK (1)基本概念钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的
  • opencv HOGdetectMultiScale函数详解

    万次阅读 2014-12-31 14:05:01
    参考:... 函数作用:进行多尺度目标检测 函数接口 void HOGDescriptor::detectMultiScale( const Mat& img, vector& foundLocations, vector& foundWeig
  • 什么叫特征检测?就是检测图像目标的特征呗,所谓特征,不管你怎么...对特征的描述有很多种方法和算子,常见的有SIFT特征描述算子、SURF特征描述算子、ORB特征描述算子、HOG特征描述、LBP特征描述以及Harr特征描述...
  • 《APUE》函数整理

    千次阅读 2014-12-24 09:28:34
    第1章 unix基础知识 ...1. char *strerror(int errnum) ...该函数将errnum(就是errno值)映射为一个出错信息字符串,返回该字符串指针。声明在string.h文件。 2.void perror(const char *s) 该函数
  • MFC 函数

    千次阅读 2007-11-23 22:39:00
    CSize说明一个矩形的高和宽,调用CDC::GetTextExtent成员函数计算使用当前字体的一行文字的高和宽决定的尺寸。CString::LoadString把一个由nID标识的字符串资源读取到现有的CString对象。 CDC::BeginPath:打开设备...
  • exec函数详解

    万次阅读 多人点赞 2018-05-17 17:30:59
    (1)exec函数说明fork函数是用于创建一个子进程,该子进程几乎是父进程的副本,而有时我们希望子进程去执行另外的程序,exec函数族就提供了一个在进程启动另一个程序执行的方法。它可以根据指定的文件名或目录名...
  • 关于C#中派生类调用基类构造函数的理解

    千次阅读 热门讨论 2010-08-26 11:01:00
    (1)当基类没有自己编写的构造函数时,派生类默认条用基类的构造函数 (2)当基类有自己编写的构造函数时,要在基类添加无参的构造函数 (3)在基类有自己编写的构造函数并且在基类没有...
  • C++ 虚函数、静态联编、动态联编

    千次阅读 2016-01-05 17:12:04
    若在子类重写成员函数,将不是使用相同的函数特征标覆盖基类声明,而是隐藏 纯虚函数在基类是没有定义的,必须在子类加以实现,很像java的接口函数! 虚函数 引入原因:为了方便使用多态特性,我们常常...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 60,331
精华内容 24,132
关键字:

关于成员函数特征的描述中