opencv(十一)——core:file input and output


Goal

  • How to print and read text entries to a file and OpenCV using YAML or XML files?
  • How to do the same for OpenCV data structures?
  • How to do this for your data structures?
  • Usage of OpenCV data structures such as cv::File Storage,cv::FileNode or cv::FileNodeIterator

Explanation

what is XML/YAML

  没错,我就是连这个都不知道的人,知道的跳过这一part吧。。。

  XML:Extensible Markup Language(可扩展标记语言)。和HTML、CSS一样,是一种标记语言(标记语言不属于传统意义上的编程语言),且是一种具备结构化特征的数据交换语言。

  XML是一种简单的数据格式,是纯100%的ASCII文本,而ASCII的抗破坏能力是很强的。不像压缩数据和java对象,只要破坏一个数据文件数据就不可阅读。

  从高级的角度来看,XML是一种自描述语言。

  举例:

<person age="too young" experience="too simple" result="sometimes naive" />

  附带了对数据的说明,并且具备通用的格式规范可以让程序来做解析。

  XML是被设计用来存储数据、携带数据和交换数据的,它不是为了显示数据而设计的。

  1. XML可以从HTML中分离数据,通过XML,我们可以在HTML文件之外存储数据。
  2. XML用于交换数据,通过XML,我们可以在不兼容的系统之间交换数据。把数据转换为XML格式存储将大大减少交换数据的复杂性,并且还可以使得这些数据能被不同的程序读取。
  3. XML可以用于存储数据,利用XML,纯文本文件可以用来存储数据。大量的数据可以存储到XML文件中或者数据库中。应用程序可以读写和存储数据,一般的程序可以显示数据。
  4. XML可以用于共享数据。通过XML,纯文本文件可以用来共享数据。XML提供了一种与软硬件均无关的共享数据方法,这样创建一个能够被不同的应用程序读取的数据文件就变得简单了。
  5. XML可以充分利用数据,使用XML,数据可以被更多的用户使用,而不仅仅是基于HTML标准的浏览器。
  6. XML可以用于创建新的语言。

  YAML:YAML Ain’t a Markup Language(YAML不是一种标记语言)。这个名字有趣极了,一看就是程序员:)

  在开发这种语言时,YAML的意思其实是:Yet Another Markup Language(仍是一种标记语言)

  YAML的语法和其他高级语言类似,并且可以简单表达清单、散列表、标量等数据形态。它使用空白符号缩进和大量依赖外观的特色,特别适合用来表达或编辑数据结构、各种配置文件、倾印调试内容、文件大纲(例如:许多电子邮件标题格式和YAML非常接近)

  YAML支持的数据结构有三种:

  • 对象:键值对的集合,又称为映射(mapping)/哈希(hashes)/字典(dictionary)
  • 数组:一组按次序排列的值,又称为序列(sequence)/列表(list)
  • 纯量(scalars):单个的,不可再分的值

  YAML的优点:

  • YAML的可读性好
  • YAML和脚本语言的交互性好
  • YAML使用实现语言的数据类型
  • YAML有一个一致的信息模型
  • YAML已于实现
  • YAML可以基于流来处理
  • YAML表达能力强,扩展性好

  总之,YAML试图用一种比XML更敏捷的方式,来完成XML所完成的任务。

main

  Here we talk only about XML and YAML file inputs. Your output (and its respective input) file may have only one of these extensions and the structure coming from this. They are two kinds of data structures you may serialize: mappings (like the STL map) and element sequence (like the STL vector). The difference between these is that in a map every element has a unique name through what you may access it. For sequences you need to go through them to query a specific item.

  在这里,我们仅讨论XML和YAML文件的输入。这两种文件可以序列化为两种数据结构:mappings(like the STL map)和sequence(like the STL vector)。

  两者区别在于map的每个元素都有一个唯一的名称可供访问,而sequences需要通过遍历来进行访问和查询。

XML/YAML File Open and Close

#include <opencv2/core.hpp>
#include <iostream>
#include <string>
using namespace cv;
using namespace std;
int main(int ac, char** av)
{
    string filename = "I.xml";
    { //write
        FileStorage fs(filename, FileStorage::WRITE);
        fs.release();                                       // explicit close
        cout << "Write Done." << endl;
    }
    return 0;
}

  Either one of this you use the second argument is a constant specifying the type of operations you'll be able to on them: WRITE, READ or APPEND. The extension specified in the file name also determinates the output format that will be used. The output may be even compressed if you specify an extension such as *.xml.gz*.

  使用第二个参数的其中之一是一个常量,该常量指定了我们可以执行操作的类型:WRITE、READ或者APPEND。在filename中指定的扩展名也决定了将要使用的输出格式。举个例子,如果扩展名是".xml.gz",那么输出的文件甚至可能被压缩。

Input and Output of text and numbers

  The data structure uses the same << output operator that the STL library. For outputting any type of data structure we need first to specify its name. We do this by just simply printing out the name of this.

  数据结构使用与STL库相同的<<输出运算符。 如果要输出任何类型的数据结构,我们首先需要指定其名称,然后只要简单地打印出此名称即可~

fs << "iterationNr" << 100;

  
  Reading in is a simple addressing (via the [] operator) and casting operation or a read via the >> operator :

  read操作是一个简单的寻址过程(通过[]运算符)和强制转换过程,或者直接通过>>运算符进行读取。

int itNr;
fs["iterationNr"] >> itNr;
itNr = (int) fs["iterationNr"];

Input/Output of OpenCV Data structures

  Well these behave exactly just as the basic C++ types:

  他们的行为与基本的C++类型几乎完全相同。

Mat R = Mat_<uchar >::eye  (3, 3),
    T = Mat_<double>::zeros(3, 1);
fs << "R" << R;                                      // Write cv::Mat
fs << "T" << T;
fs["R"] >> R;                                      // Read cv::Mat
fs["T"] >> T;

Input/Output of vectors (arrays) and associative maps

  As I mentioned beforehand, we can output maps and sequences (array, vector) too. Again we first print the name of the variable and then we have to specify if our output is either a sequence or map.

  正如之前提到的,我们也可以输出maps和sequences(array,vector)。再次,我们首先打印变量的名称,然后我们必须明确我们的输出是map还是sequence。
  
  For sequence before the first element print the "[" character and after the last one the "]" character:

  如果是sequence,那么在第一个元素之前打印"[“字符,在最后一个元素之后打印”]"字符。

fs << "strings" << "[";                              // text - string sequence
fs << "image1.jpg" << "Awesomeness" << "baboon.jpg";
fs << "]";           

  如果是map,那么在第一个元素之前打印"{“字符,在最后一个元素后打印”}"字符。

fs << "Mapping";                              // text - mapping
fs << "{" << "One" << 1;
fs <<        "Two" << 2 << "}";

  
  To read from these we use the cv::FileNode and the cv::FileNodeIterator data structures. The [] operator of the cv::FileStorage class returns a cv::FileNode data type. If the node is sequential we can use the cv::FileNodeIterator to iterate through the items:

  要读取这些内容,我们使用cv::FileNodecv::FileNodeIterator数据结构。cv::FileStorage的[]运算返回cv::FileNode数据类型,如果节点是顺序的,我们可以使用cv::FileNodeIterator来遍历各项。

FileNode n = fs["strings"];                         // Read string sequence - Get node
if (n.type() != FileNode::SEQ)
{
    cerr << "strings is not a sequence! FAIL" << endl;
    return 1;
}
FileNodeIterator it = n.begin(), it_end = n.end(); // Go through the node
for (; it != it_end; ++it)
    cout << (string)*it << endl;

n = fs["Mapping"];                                // Read mappings from a sequence
cout << "Two  " << (int)(n["Two"]) << "; ";
cout << "One  " << (int)(n["One"]) << endl << endl;

Read and write your own data structures

  假设我们有下面这种数据类型:

class MyData
{
public:
      MyData() : A(0), X(0), id() {}
public:   // Data Members
   int A;
   double X;
   string id;
};

  菜如我,之前没怎么用过class,C++都是当C使的,所以以下有部分篇幅介绍C++class的概念,懂的可以跳过。。。

参考

C++类&对象

  C++在C语言的基础上增加了面向对象编程。C++支持面向对象程序设计。类是C++的核心特性,通常被称为用户定义的类型。

  类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中称为类的成员。

C++类定义

  定义一个类,本质上是定义一个数据类型的蓝图。这实际上并没有定义任何数据,但他定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。

class Box
{
   public:
      double length;   // 盒子的长度
      double breadth;  // 盒子的宽度
      double height;   // 盒子的高度
};

  关键词public确定了类成员的访问属性。在类对象作用域内,公共成员在类的外部是可访问的,也可以指定类的成员为private或protected。

访问数据成员
#include <iostream>
 
using namespace std;
 
class Box
{
   public:
      double length;   // 长度
      double breadth;  // 宽度
      double height;   // 高度
};
 
int main( )
{
   Box Box1;        // 声明 Box1,类型为 Box
   Box Box2;        // 声明 Box2,类型为 Box
   double volume = 0.0;     // 用于存储体积
 
   // box 1 详述
   Box1.height = 5.0; 
   Box1.length = 6.0; 
   Box1.breadth = 7.0;
 
   // box 2 详述
   Box2.height = 10.0;
   Box2.length = 12.0;
   Box2.breadth = 13.0;
 
   // box 1 的体积
   volume = Box1.height * Box1.length * Box1.breadth;
   cout << "Box1 的体积:" << volume <<endl;
 
   // box 2 的体积
   volume = Box2.height * Box2.length * Box2.breadth;
   cout << "Box2 的体积:" << volume <<endl;
   return 0;
}

  以上代码还在可理解的范围,下面进入正题。

类成员函数

  类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。

class Box
{
   public:
      double length;         // 长度
      double breadth;        // 宽度
      double height;         // 高度
      double getVolume(void);// 返回体积
};

  成员函数可以定义在类内部,或者单独使用范围解析运算符::来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用inline标识符。

class Box
{
   public:
      double length;      // 长度
      double breadth;     // 宽度
      double height;      // 高度
   
      double getVolume(void)
      {
         return length * breadth * height;
      }
};

  我们也可以在类的外部使用范围解析运算符::来定义该函数。

double Box::getVolume(void)
{
    return length * breadth * height;
}
#include <iostream>
 
using namespace std;
 
class Box
{
   public:
      double length;         // 长度
      double breadth;        // 宽度
      double height;         // 高度
 
      // 成员函数声明
      double getVolume(void);
      void setLength( double len );
      void setBreadth( double bre );
      void setHeight( double hei );
};
 
// 成员函数定义
double Box::getVolume(void)
{
    return length * breadth * height;
}
 
void Box::setLength( double len )
{
    length = len;
}
 
void Box::setBreadth( double bre )
{
    breadth = bre;
}
 
void Box::setHeight( double hei )
{
    height = hei;
}
 
// 程序的主函数
int main( )
{
   Box Box1;                // 声明 Box1,类型为 Box
   Box Box2;                // 声明 Box2,类型为 Box
   double volume = 0.0;     // 用于存储体积
 
   // box 1 详述
   Box1.setLength(6.0); 
   Box1.setBreadth(7.0); 
   Box1.setHeight(5.0);
 
   // box 2 详述
   Box2.setLength(12.0); 
   Box2.setBreadth(13.0); 
   Box2.setHeight(10.0);
 
   // box 1 的体积
   volume = Box1.getVolume();
   cout << "Box1 的体积:" << volume <<endl;
 
   // box 2 的体积
   volume = Box2.getVolume();
   cout << "Box2 的体积:" << volume <<endl;
   return 0;
}
C++类访问修饰符

  数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主题内部对各个区域标志public、private、protected来指定的。关键字public、private、protected被称为访问修饰符。

class Base {
 
   public:
 
  // 公有成员
 
   protected:
 
  // 受保护成员
 
   private:
 
  // 私有成员
 
};

  公有成员在程序中类的外部是可以访问的。

  私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。默认情况下,类的所有成员都是私有的。

#include <iostream>
 
using namespace std;
 
class Box
{
   public:
      double length;
      void setWidth( double wid );
      double getWidth( void );
 
   private:
      double width;
};
 
// 成员函数定义
double Box::getWidth(void)
{
    return width ;
}
 
void Box::setWidth( double wid )
{
    width = wid;
}
 
// 程序的主函数
int main( )
{
   Box box;
 
   // 不使用成员函数设置长度
   box.length = 10.0; // OK: 因为 length 是公有的
   cout << "Length of box : " << box.length <<endl;
 
   // 不使用成员函数设置宽度
   // box.width = 10.0; // Error: 因为 width 是私有的
   box.setWidth(10.0);  // 使用成员函数设置宽度
   cout << "Width of box : " << box.getWidth() <<endl;
 
   return 0;
}

  保护成员变量或函数与私有成员十分类似,但保护成员在派生类中是可访问的。

C++类构造函数&析构函数

  类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。

  构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回void。构造函数可用于为某些成员变量设置初始值。

#include <iostream>
 
using namespace std;
 
class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line();  // 这是构造函数
 
   private:
      double length;
};
 
// 成员函数定义,包括构造函数
Line::Line(void)
{
    cout << "Object is being created" << endl;
}
 
void Line::setLength( double len )
{
    length = len;
}
 
double Line::getLength( void )
{
    return length;
}
// 程序的主函数
int main( )
{
   Line line;
 
   // 设置长度
   line.setLength(6.0); 
   cout << "Length of line : " << line.getLength() <<endl;
 
   return 0;
}

带参数的构造函数:

  默认的构造函数没有任何参数,但如果需要构造函数也可以带有参数。

#include <iostream>
 
using namespace std;
 
class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line(double len);  // 这是构造函数
 
   private:
      double length;
};
 
// 成员函数定义,包括构造函数
Line::Line( double len)
{
    cout << "Object is being created, length = " << len << endl;
    length = len;
}
 
void Line::setLength( double len )
{
    length = len;
}
 
double Line::getLength( void )
{
    return length;
}
// 程序的主函数
int main( )
{
   Line line(10.0);
 
   // 获取默认设置的长度
   cout << "Length of line : " << line.getLength() <<endl;
   // 再次设置长度
   line.setLength(6.0); 
   cout << "Length of line : " << line.getLength() <<endl;
 
   return 0;
}

使用初始化列表来初始化字段:

Line::Line( double len): length(len)
{
    cout << "Object is being created, length = " << len << endl;
}

  上述语法等同于下述语法:

Line::Line( double len)
{
    length = len;
    cout << "Object is being created, length = " << len << endl;
}

  假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所示:

C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
  ....
}

类的析构函数:

  类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。

  析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序前释放资源。

#include <iostream>
 
using namespace std;
 
class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line();   // 这是构造函数声明
      ~Line();  // 这是析构函数声明
 
   private:
      double length;
};
 
// 成员函数定义,包括构造函数
Line::Line(void)
{
    cout << "Object is being created" << endl;
}
Line::~Line(void)
{
    cout << "Object is being deleted" << endl;
}
 
void Line::setLength( double len )
{
    length = len;
}
 
double Line::getLength( void )
{
    return length;
}
// 程序的主函数
int main( )
{
   Line line;
 
   // 设置长度
   line.setLength(6.0); 
   cout << "Length of line : " << line.getLength() <<endl;
 
   return 0;
}
C++拷贝构造函数

  拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:

  • 通过使用另一个同类型的对象来初始化新创建的对象
  • 复制对象把它作为参数传递给函数
  • 复制对象,并从函数返回这个对象

  如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。

classname (const classname &obj) {
   // 构造函数的主体
}

  obj是一个对象引用,该对象是用于初始化另一个对象的。

#include <iostream>
 
using namespace std;
 
class Line
{
   public:
      int getLength( void );
      Line( int len );             // 简单的构造函数
      Line( const Line &obj);      // 拷贝构造函数
      ~Line();                     // 析构函数
 
   private:
      int *ptr;
};
 
// 成员函数定义,包括构造函数
Line::Line(int len)
{
    cout << "调用构造函数" << endl;
    // 为指针分配内存
    ptr = new int;
    *ptr = len;
}
 
Line::Line(const Line &obj)
{
    cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
    ptr = new int;
    *ptr = *obj.ptr; // 拷贝值
}
 
Line::~Line(void)
{
    cout << "释放内存" << endl;
    delete ptr;
}
int Line::getLength( void )
{
    return *ptr;
}
 
void display(Line obj)
{
   cout << "line 大小 : " << obj.getLength() <<endl;
}
 
// 程序的主函数
int main( )
{
   Line line(10);
 
   display(line);
 
   return 0;
}

  当上述代码被编译和执行时,它会产生下列结果:
在这里插入图片描述

#include <iostream>
 
using namespace std;
 
class Line
{
   public:
      int getLength( void );
      Line( int len );             // 简单的构造函数
      Line( const Line &obj);      // 拷贝构造函数
      ~Line();                     // 析构函数
 
   private:
      int *ptr;
};
 
// 成员函数定义,包括构造函数
Line::Line(int len)
{
    cout << "调用构造函数" << endl;
    // 为指针分配内存
    ptr = new int;
    *ptr = len;
}
 
Line::Line(const Line &obj)
{
    cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
    ptr = new int;
    *ptr = *obj.ptr; // 拷贝值
}
 
Line::~Line(void)
{
    cout << "释放内存" << endl;
    delete ptr;
}
int Line::getLength( void )
{
    return *ptr;
}
 
void display(Line obj)
{
   cout << "line 大小 : " << obj.getLength() <<endl;
}
 
// 程序的主函数
int main( )
{
   Line line1(10);
 
   Line line2 = line1; // 这里也调用了拷贝构造函数
 
   display(line1);
   display(line2);
 
   return 0;
}

在这里插入图片描述

C++友元函数

  类的友元函数是定义在类外部,但有权访问类的所有私有成员和保护成员。尽管友元函数的原型有在类的定义中出现过,但友元函数并不是成员函数。

  友元可以是一个函数,该函数被称为友元函数;友元函数也可以是一个类,该类被称为友元类。在这种情况下,整个类及其所有成员都是友元。

  如果要声明一个类的友元,需要在类定义中该函数原型前使用关键字friend。

class Box
{
   double width;
public:
   double length;
   friend void printWidth( Box box );
   void setWidth( double wid );
};
#include <iostream>
 
using namespace std;
 
class Box
{
   double width;
public:
   friend void printWidth( Box box );
   void setWidth( double wid );
};
 
// 成员函数定义
void Box::setWidth( double wid )
{
    width = wid;
}
 
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
   /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
   cout << "Width of box : " << box.width <<endl;
}
 
// 程序的主函数
int main( )
{
   Box box;
 
   // 使用成员函数设置宽度
   box.setWidth(10.0);
   
   // 使用友元函数输出宽度
   printWidth( box );
 
   return 0;
}
C++内联函数

  C++内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。

C++中的this指针

  在C++中,每一个对象都能通过this指针来访问自己的地址。this指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。

  友元函数没有this指针,因为友元不是类的函数,只有成员函数才有this指针。

#include <iostream>
 
using namespace std;
 
class Box
{
   public:
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
      }
      double Volume()
      {
         return length * breadth * height;
      }
      int compare(Box box)
      {
         return this->Volume() > box.Volume();
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};
 
int main(void)
{
   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2
 
   if(Box1.compare(Box2))
   {
      cout << "Box2 is smaller than Box1" <<endl;
   }
   else
   {
      cout << "Box2 is equal to or larger than Box1" <<endl;
   }
   return 0;
}
C++指向类的指针

  一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 ->,就像访问指向结构的指针一样。与所有的指针一样,您必须在使用指针之前,对指针进行初始化。

#include <iostream>
 
using namespace std;

class Box
{
   public:
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
      }
      double Volume()
      {
         return length * breadth * height;
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

int main(void)
{
   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2
   Box *ptrBox;                // Declare pointer to a class.

   // 保存第一个对象的地址
   ptrBox = &Box1;

   // 现在尝试使用成员访问运算符来访问成员
   cout << "Volume of Box1: " << ptrBox->Volume() << endl;

   // 保存第二个对象的地址
   ptrBox = &Box2;

   // 现在尝试使用成员访问运算符来访问成员
   cout << "Volume of Box2: " << ptrBox->Volume() << endl;
  
   return 0;
}
C++类的静态成员

  我们可以使用static关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。

  静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符:: 来重新声明静态变量从而对它进行初始化.

#include <iostream>
 
using namespace std;
 
class Box
{
   public:
      static int objectCount;
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
         // 每次创建对象时增加 1
         objectCount++;
      }
      double Volume()
      {
         return length * breadth * height;
      }
   private:
      double length;     // 长度
      double breadth;    // 宽度
      double height;     // 高度
};
 
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
 
int main(void)
{
   Box Box1(3.3, 1.2, 1.5);    // 声明 box1
   Box Box2(8.5, 6.0, 2.0);    // 声明 box2
 
   // 输出对象的总数
   cout << "Total objects: " << Box::objectCount << endl;
 
   return 0;
}

  如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。

  静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。

  静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。

  静态成员函数与普通成员函数的区别:

  • 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
  • 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。
#include <iostream>
 
using namespace std;
 
class Box
{
   public:
      static int objectCount;
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
         // 每次创建对象时增加 1
         objectCount++;
      }
      double Volume()
      {
         return length * breadth * height;
      }
      static int getCount()
      {
         return objectCount;
      }
   private:
      double length;     // 长度
      double breadth;    // 宽度
      double height;     // 高度
};
 
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
 
int main(void)
{
  
   // 在创建对象之前输出对象的总数
   cout << "Inital Stage Count: " << Box::getCount() << endl;
 
   Box Box1(3.3, 1.2, 1.5);    // 声明 box1
   Box Box2(8.5, 6.0, 2.0);    // 声明 box2
 
   // 在创建对象之后输出对象的总数
   cout << "Final Stage Count: " << Box::getCount() << endl;
 
   return 0;
}
运算符重载

参考

什么是运算符重载?

  运算符重载是通过创建运算符函数实现的,运算符函数定义了重载的运算符将要进行的操作。运算符函数的定义与其他函数的定义类似,唯一的区别是运算符函数的函数名是由关键字operator和其后要重载的运算符符号构成的。运算符函数定义的一般格式如下:

<返回类型说明符> operator <运算符符号>(<参数表>)
{

     <函数体>

}

为什么要重载运算符?

  C++中预定义的运算符的操作对象只能是基本数据类型。但实际上,对于许多用户自定义类型(例如类),也需要类似的运算操作。这时就必须在C++中重新定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型执行特定的操作。运算符重载的实质是函数重载,它提供了C++的可扩展性,也是C++最吸引人的特性之一。比如,我们定义两个string类对象a,b后我们之所以可以使用+运算,是因为string类重载了+运算符。

我们应该了解哪些运算符可以被重载,哪些运算符不能被重载

【可以被重载的运算符】

  • 算术运算符:+、-、*、/、%、++、–
  • 位操作运算符:&、|、~、^、<<、>>
  • 逻辑运算符:!、&&、||
  • 比较运算符:<、>、>=、<=、==、!=
  • 赋值运算符:=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=
  • 其他运算符:[]、()、->、,(逗号运算符)、new、delete、new[]、delete[]、->*

【下列运算符不允许重载】

.、.*、::、?:、sizeof

如何重载运算符?

  运算符重载一般有两种形式:重载为类的成员函数和重载为类的非成员函数。非成员函数通常是友元。

  如果一个运算符函数是成员函数,则它的第一个(左侧)运算对象绑定到隐式的this指针上,因此,成员运算符函数的(显式)参数数量比运算符的运算对象总少一个(后置单目运算符除外,后面会介绍到)

class A
{
    private:
    int a;
    public:
    A operator+(A&);
    A()=default;
    A(int x):a(x){}
};
A A::operator+(A& d)
{
    return A(a+d.a);
}

int main()
{
    A b,c,d;
    d=b+c;  //等价于d=b.operator+(c);
    return 0;
}

  operator是关键字,专门用于定义重载运算符的函数。我们可以将operator运算符名称这一部分看作函数名,对于上面的代码,函数名就是operator+

C++ const用法详解

参考

const基本概念

  const名叫常量限定符,用来限定特定变量,以通知编译器该变量是不可修改的。习惯性的使用const,可以避免在函数中对某些不应修改的变量造成可能的改动。

const的用法:

  1. const修饰基本数据类型
  2. const应用到函数中
  3. const在类中的用法
  4. const修饰类对象,定义常量对象
const修饰基本数据类型

const修饰一般常量及数组

const int a=10;               
int const a=10;
const int arr[3]={1,2,3};                        
int const arr[3]={1,2,3};

  修饰符const可以用在类型说明符前,也可以用在类型说明符后,其结果是一样的。在使用这些常量的时候,只要不改变这些常量的值便好。

const修饰指针变量*及引用变量&

指针(pointer)是用来指向实际内存地址的变量,一般来说,指针是整型,而且一般是十六进制的。

引用(reference)是其相应变量的别名,用于向函数提供直接访问参数(而不是参数的副本)的途径,与指针相比,引用时一种受限制的指针类型,或者说是指针的一个子集,而从功能上看,似乎可以说引用是指针功能的一种高层实现。

在定义变量的引用的时候,&只是个定义引用的标志,不代表取地址。

const修饰指针(*):

  当const修饰指针时,由于const的位置不同,它的修饰对象会有所不同。

  如下代码,int *const p2中const修饰p2的值,所以理解为p2的值不可以改变,即p2只能指向固定的一个变量地址,但可以通过 *p2读写这个变量的值。

	int a = 0;
	int b = 100;
	int *const p2 = &a;//p2指向了a的地址,p2只能指向a的地址
	//p2 = &b;错误
	//p2是一个常量指针,p2只能指向固定的一个变量地址,但可以通过*p2读写这个变量的值
	*p2 = 7;
	printf("b = %d\n", a);

  如下代码,int const *p1或者const int *p1两种情况中const修饰 *p1,所以理解为 *p1的值不可以改变,即不可以给 *p1赋值改变p1指向变量的值,但可以通过给p赋值不同的地址改变这个指针指向。

	int a = 0;
	int b = 9;
	const int *p = &a;//p可以指向一个int类型的地址,但不可以用*p的方式修改这个内存的值
	//*p = 10;错误
	printf("%d\n", *p);
	p = &b;
	printf("%d\n", *p);

const修饰引用(&):

   int const &a=x;
   const int &a=x;
   int &const a=x;//这种方式定义是C、C++编译器未定义,虽然不会报错,但是该句效果和int &a一样。   

  这三种方式是等价的,此时的引用a不能被更新。如:a++这是错误的。

const应用到函数中
  1. 作为参数的const修饰符
  2. 作为函数返回值的const修饰符

  无论是参数还是返回值,参数传入时候和函数返回的时候,初始化const变量。

类中定义常量(const的特殊用法)
  1. 使用枚举类型
class test
{
     enum { SIZE1 = 10, SIZE2 = 20}; // 枚举常量
     int array1[SIZE1];  
     int array2[SIZE2];
};
  1. 使用const或static

  C++仅不允许类声明中初始化static非const类型的数据成员。

// using c++11 standard
class CTest11
{
public:
    static const int a = 3; // Ok in C++11
    static int b = 4;       // Error
    const int c = 5;        // Ok in C++11
    int d = 6;              // Ok in C++11
public:
    CTest11() :c(0) { }     // Ok in C++11
};
 
int main()
{
    CTest11 testObj;
    cout << testObj.a << testObj.b << testObj.c << testObj.d << endl;
    return 0;
}
continue

  It's possible to serialize this through the OpenCV I/O XML/YAML interface (just as in case of the OpenCV data structures) by adding a read and a write function inside and outside of your class.

  通过在你自己class的内部和外部添加read和write功能模块,我们可以通过opencv的I/O、XML/YAML接口完成序列化。

void write(FileStorage& fs) const                        //Write serialization for this class
{
  fs << "{" << "A" << A << "X" << X << "id" << id << "}";
}
void read(const FileNode& node)                          //Read serialization for this class
{
  A = (int)node["A"];
  X = (double)node["X"];
  id = (string)node["id"];
}

  
  Then you need to add the following functions definitions outside the class:

  然后我们需要在class外添加以下函数定义:

void write(FileStorage& fs, const std::string&, const MyData& x)
{
x.write(fs);
}
void read(const FileNode& node, MyData& x, const MyData& default_value = MyData())
{
if(node.empty())
    x = default_value;
else
    x.read(node);
}

  
  Here you can observe that in the read section we defined what happens if the user tries to read a non-existing node. In this case we just return the default initialization value, however a more verbose solution would be to return for instance a minus one value for an object ID.

  在这里我们可以观察到,在读取部分中,我们定义了如果用户尝试读取不存在的节点的情况。
  
  Once you added these four functions use the >> operator for write and the << operator for read:

  添加完这四个函数后,我们需要使用>>和<<运算符进行写入和读取。

MyData m(1);
fs << "MyData" << m;                                // your own data structures
fs["MyData"] >> m;                                 // Read your own structure_

  或者尝试读取不存在的内容:

fs["NonExisting"] >> m;   // Do not add a fs << "NonExisting" << m command for this to work
cout << endl << "NonExisting = " << endl << m << endl;

result

#include <opencv2/core.hpp>
#include <iostream>
#include <string>
using namespace cv;
using namespace std;
//static void help(char** av)
//{
//    cout << endl
//        << av[0] << " shows the usage of the OpenCV serialization functionality." << endl
//        << "usage: " << endl
//        << av[0] << " outputfile.yml.gz" << endl
//        << "The output file may be either XML (xml) or YAML (yml/yaml). You can even compress it by "
//        << "specifying this in its extension like xml.gz yaml.gz etc... " << endl
//        << "With FileStorage you can serialize objects in OpenCV by using the << and >> operators" << endl
//        << "For example: - create a class and have it serialized" << endl
//        << "             - use it to read and write matrices." << endl;
//}
class MyData
{
public:
    MyData() : A(0), X(0), id()
    {}
    explicit MyData(int) : A(97), X(CV_PI), id("mydata1234") // explicit to avoid implicit conversion
    {}
    void write(FileStorage& fs) const                        //Write serialization for this class
    {
        fs << "{" << "A" << A << "X" << X << "id" << id << "}";
    }
    void read(const FileNode& node)                          //Read serialization for this class
    {
        A = (int)node["A"];
        X = (double)node["X"];
        id = (string)node["id"];
    }
public:   // Data Members
    int A;
    double X;
    string id;
};
//These write and read functions must be defined for the serialization in FileStorage to work
static void write(FileStorage& fs, const std::string&, const MyData& x)
{
    x.write(fs);
}
static void read(const FileNode& node, MyData& x, const MyData& default_value = MyData()) {
    if (node.empty())
        x = default_value;
    else
        x.read(node);
}
// This function will print our custom class to the console
static ostream& operator<<(ostream& out, const MyData& m)
{
    out << "{ id = " << m.id << ", ";
    out << "X = " << m.X << ", ";
    out << "A = " << m.A << "}";
    return out;
}
int main(int ac, char** av)
{
    /*if (ac != 2)
    {
        help(av);
        return 1;
    }
    string filename = av[1];*/
    string filename = "I.xml";
    { //write
        Mat R = Mat_<uchar>::eye(3, 3),
            T = Mat_<double>::zeros(3, 1);
        MyData m(1);
        FileStorage fs(filename, FileStorage::WRITE);
        fs << "iterationNr" << 100;
        fs << "strings" << "[";                              // text - string sequence
        fs << "image1.jpg" << "Awesomeness" << "../data/baboon.jpg";
        fs << "]";                                           // close sequence
        fs << "Mapping";                              // text - mapping
        fs << "{" << "One" << 1;
        fs << "Two" << 2 << "}";
        fs << "R" << R;                                      // cv::Mat
        fs << "T" << T;
        //fs << "MyData" << m;                                // your own data structures
        fs.release();                                       // explicit close
        cout << "Write Done." << endl;
    }
    {//read
        cout << endl << "Reading: " << endl;
        FileStorage fs;
        fs.open(filename, FileStorage::READ);
        int itNr;
        //fs["iterationNr"] >> itNr;
        itNr = (int)fs["iterationNr"];
        cout << itNr << endl;
        //if (!fs.isOpened())
        //{
        //    cerr << "Failed to open " << filename << endl;
        //    help(av);
        //    return 1;
        //}
        FileNode n = fs["strings"];                         // Read string sequence - Get node
        if (n.type() != FileNode::SEQ)
        {
            cerr << "strings is not a sequence! FAIL" << endl;
            return 1;
        }
        FileNodeIterator it = n.begin(), it_end = n.end(); // Go through the node
        for (; it != it_end; ++it)
        {
            cout << "string:";
            cout << (string)*it << endl;
        }
        n = fs["Mapping"];                                // Read mappings from a sequence
        cout << "Two  " << (int)(n["Two"]) << "; ";
        cout << "One  " << (int)(n["One"]) << endl << endl;
        MyData m;
        Mat R, T;
        fs["R"] >> R;                                      // Read cv::Mat
        fs["T"] >> T;
        fs["MyData"] >> m;                                 // Read your own structure_
        cout << endl
            << "R = " << R << endl;
        cout << "T = " << T << endl << endl;
        cout << "MyData = " << endl << m << endl << endl;
        //Show default behavior for non existing nodes
        cout << "Attempt to read NonExisting (should initialize the data structure with its default).";
        fs["NonExisting"] >> m;
        cout << endl << "NonExisting = " << endl << m << endl;
    }
    //cout << endl
    //    << "Tip: Open up " << filename << " with a text editor to see the serialized data." << endl;
    return 0;
}

  教程的测试代码有一点点不太适合理解,因此进行了如下改动:

#include <opencv2/core.hpp>
#include <iostream>
#include <string>
using namespace cv;
using namespace std;

class MyData
{
public:
    MyData() : A(0), X(0), id("0")
    {}
    explicit MyData(int) : A(97), X(CV_PI), id("mydata1234") // explicit to avoid implicit conversion
    {}
    void write(FileStorage& fs) const                        //Write serialization for this class
    {
        fs << "{" << "A" << A << "X" << X << "id" << id << "}";
    }
    void read(const FileNode& node)                          //Read serialization for this class
    {
        A = (int)node["A"];
        X = (double)node["X"];
        id = (string)node["id"];
    }
public:   // Data Members
    int A;
    double X;
    string id;
};
//These write and read functions must be defined for the serialization in FileStorage to work
static void write(FileStorage& fs, const std::string&, const MyData& x)
{
    x.write(fs);
}
static void read(const FileNode& node, MyData& x, const MyData& default_value = MyData()) {
    if (node.empty())
        x = default_value;
    else
        x.read(node);
}
// This function will print our custom class to the console
static ostream& operator<<(ostream& out, const MyData& m)
{
    out << "{ id = " << m.id << ", ";
    out << "X = " << m.X << ", ";
    out << "A = " << m.A << "}";
    return out;
}

int main(int ac, char** av)
{
    string filename = "I.xml";
    { //write
        FileStorage fs(filename, FileStorage::WRITE);

        //basic structure
        fs << "iterationNr" << 100;

        //Mat
        Mat R = Mat_<uchar>::eye(3, 3),
            T = Mat_<double>::zeros(3, 1);
        fs << "R" << R;                                      // cv::Mat
        fs << "T" << T;

        //sequence
        fs << "strings" << "[";                              // text - string sequence
        fs << "image1.jpg" << "Awesomeness" << "../data/baboon.jpg";
        fs << "]";                                           // close sequence

        //mapping
        fs << "Mapping";                              // text - mapping
        /*fs << "{" << "One" << 1;
        fs << "Two" << 2 << "}";*/
        //fs << "{" << "One" << 1;
        //fs << "Two" << 2 << "}";
        fs << "{" << "One" << 1 << "Two" << 2.5 << "}";

        //your own data structures
        MyData m(1);
        fs << "MyData" << m;

        MyData se;
        fs << "MyData4";
        se.write(fs);

        fs.release();                                       // explicit close
        cout << "Write Done." << endl;
    }

    {//read
        cout << "Reading: " << endl;
        FileStorage fs2;
        fs2.open(filename, FileStorage::READ);
        if (!fs2.isOpened())
        {
            cout << "Failed to open " << filename << endl;
            return 1;
        }

        //basic structure
        cout << endl << "basic structure——" << "\t";

        int itNr;
        fs2["iterationNr"] >> itNr;
        cout << "itNr:" << itNr << "\t";

        int itNr2;
        itNr2 = (int)fs2["iterationNr"];
        cout << "itNr2:" << itNr2 << endl;

        //Mat
        cout << endl << "Mat——" << endl;

        Mat R2, T2;
        fs2["R"] >> R2;                                      // Read cv::Mat
        fs2["T"] >> T2;

        cout << endl
            << "R = " << R2 << endl;
        cout << "T = " << T2 << endl << endl;

        //sequence
        cout << endl << "Sequence——" << "\t";
        FileNode n = fs2["strings"];                         // Read string sequence - Get node
        if (n.type() != FileNode::SEQ)
        {
            cerr << "strings is not a sequence! FAIL" << endl;
            return 1;
        }
        FileNodeIterator it = n.begin(), it_end = n.end(); // Go through the node
        int number = 0;
        for (; it != it_end; ++it)
        {
            cout << "string[" << number << "]:";
            cout << (string)*it << "\t";
            number++;
        }
        cout << endl;

        //mapping
        cout << endl << "Mapping——" << "\t";
        n = fs2["Mapping"];                                // Read mappings from a sequence
        cout << "Two  " << (float)(n["Two"]) << "; ";
        cout << "One  " << (int)(n["One"]) << endl << endl;

        //your own structure
        MyData m2;
        read(fs2["MyData"], m2);
        cout << "MyData1 = " << endl << m2 << endl << endl;

        MyData sec;
        fs2["MyData"] >> sec;                                 // Read your own structure_
        cout << "MyData2 = " << endl << sec << endl << endl;

        MyData thi;
        thi.read(fs2["MyData"]);
        cout << "MyData3 = " << endl << thi << endl << endl;

        MyData four;
        four.read(fs2["MyData4"]);
        cout << "MyData4 = " << endl << four << endl << endl;
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值