大纲
- 前置知识:C++中类(class)的相关知识
- 前置知识:C++中模板(template)的相关知识
- opencv中Vec类的定义及源码
- 引申:opencv中的宏定义如CV_8UC3
一、C++中的类
“类”是对对象的抽象,C++中的类实际上就是对一类具有相同特性的对象的交集,类相当于一种新的数据类型,数据类型不占用存储空间,用类型定义一个实体的时候,才会为它分配存储空间。
1.类的定义如下:
class 类名
{
成员类表
};
成员列表是类成员的集合,成员可以无限多,也可以没有;可以是数据,也可以是函数,成员函数如普通函数一样可以进行重载或者带默认参数。如果类成员中有函数的话,有以下两种定义方式:
在类内部定义函数体
class 类名
{
返回类型 函数名(形参列表)
{
函数体
} };
在类外部定义函数体
class 类名
{
返回类型 函数名(形参列表);
};
返回类型 类名 :: 函数名(形参列表)
{
函数体
}
前一种定义方式相当于内联函数,调用速度快,但是会占用额外的内存,后一种方式则为普通的外部函数,建议使用第二种方式定义。
如果使用后一种方式定义的话,需要注意C++中设置默认形参的动作之只能有一次,在类中声明时设置了默认参数,外部定义时就不能再重复设置。
class Test
{
public:
void Sum(int a=0,int b=0);
};
void Test::Sum(int a,int b)
//void Test::Sum(int a=0,int b=0) 错误
{
cout<<a+b;
}
2.类成员的访问控制
类的每个成员都有着自己的访问属性,包括“public"、”private"、"protected
“public"通常包含成员函数、”private“通常包括一般的变量。对于外部用户像是全局函数、另一个类的成员函数,只能通过"public"来访问类的成员。如:
#include <iostream>
using namespace std;
class Test
{
public:
Test() {}
Test(int x, int y) :a(x), b(y) {}
void Sum();
private:
int a, b;
void minus()
};
void Test::Sum()
{
cout << a + b;
}
void Test::minus()
{
cout << b - a;
}
int main()
{
Test test2(3, 4);
test2.Sum(); //可以运行
cout << test2.a; //无法访问
test2.minus() //无法访问
return 0;
}
从而类的定义扩展为:
class 类名
{
public:
公有的数据成员和成员函数
protected:
保护的数据成员和成员函数
private:
私有的数据成员和成员函数
};
3.类成员的初始化
建立一个类的对象时,需要对对象进行初始化,因为在类里面是不能对成员进行进行初始化的(类是数据类型,不占用存储空间),初始化的过程通过构造函数来实现。构造函数是一个没有返回值、和类同名的成员函数,由于创建对象通常是在外部进行的,所以构造函数申明为”public",例下:
#include <iostream>
using namespace std;
class Test
{
public:
Test ();
Test (int x,int y);
void Sum();
private:
int a,b;
};
Test::Test()
{
}
Test::Test(int x,int y)
{
a=x;
b=y;
}
void Test::Sum()
{
cout<<a+b;
}
int main()
{
Test test(3,4);
test.Sum();
return 0;
}
第一个“Test()“为默认构造函数也叫无参构造函数,它没有形参,可以在后续过程中赋值而不需要立即初始化。使用方法即:
TEST test
如果类中没有默认构造函数的话会报错,但IDE通常情况下会自动生成默认构造函数(实际上是4种特定情况,参见:自动生成默认构造函数情况),但如果自己定义了一个有参的构造函数就如“TEST (int x int y)",那IDE就不会自动再生成。也就是说,如果类中无默认构造函数,也可以通过”TEST test“初始化,一旦类中只存在有参构造函数,就必须带参初始化。、
构造函数初始化列表
之前介绍的构造函数是利用函数体内赋值的方式完成初始化,但更常用的一种方法其实是构造函数初始化列表,如:
#include <iostream>
using namespace std;
class Test
{
public:
Test ();
Test (int x,int y);
void Sum();
private:
int a,b;
};
Test::Test()
{
}
Test::Test(int x,int y):a(x),b(y) //构造初始化列表而非赋值
{
}
void Test::Sum()
{
cout<<a+b;
}
int main()
{
Test test(3,4);
test.Sum();
return 0;
}
也可以更简洁的表述为:
class Test
{
public:
Test () {}
Test (int x,int y):a(x),b(y) {} //直接在类内部完成定义
void Sum();
private:
int a,b;
};
这种方式较函数内赋值一般情况下没有区别,但如果需要初始化的是类类型的成员,就需要使用构建函数初始化列表的方法了。如:
#include <iostream>
using namespace std;
class Test
{
public:
Test () {}
Test (int x,int y):a(x),b(y) {}
void Sum();
private:
int a,b;
};
void Test::Sum()
{
cout<<a+b;
}
class AnotherTest
{
public:
AnotherTest(int i,int j):test(i,j) {test.Sum();}
private:
Test test;
};
int main()
{
AnotherTest test(3,4);
return 0;
}
这里Anothertest类中含有一个数据类型为Test的成员,因此需要使用
初始化列表,注意因为在AnotherTest的构造函数里调用了Test类中的Sum成员函数,所以会有输出”7“,如果直接写test.test.Sum()是不会有输出值的,第二个test属于private类型,无法访问。
复制构造函数
复制构造函数顾名思义,通过一个已经存在的对象对另一个对象赋值初始化,常用形式为:
类名 (const 类名& obj)
{
函数体
}
例如:
#include <iostream>
using namespace std;
class Test
{
public:
Test () {}
Test (int x ,int y):a(x),b(y) {}
Test (const Test& t):a(t.a),b(t.b) {}
void Sum();
private:
int a,b;
};
void Test::Sum()
{
cout<<a+b;
}
int main()
{
Test test1(3,4);
Test test2 = test1;
test2.Sum();
return 0;
}
如果不定义复制构造函数,以上对象也可以这样进行初始化,原因就是系统也会自己生成一个复制构造函数。
类的继承
类的继承允许在已有类的基础上建立新类,继承方式,c++提供了三中继承方式。
public(公有继承):基类中的公有和保护成员保持原属性,私有成员为基类私有。
private(私有继承):基类中的所有成员在派生类中都是私有的。
protected(保护继承):基类的公有成员和保护成员在派生类中成了保护成员,私有成员仍为基类私有。
继承的写法如下:
class 子类名:public/ptivate/prtected 父类名
{
成员
}
例矩形对平行四边形的继承:
#include <iostream>
using namespace std;
class Parallelogram
{
public:
Parallelogram(int a,int b):length(a),width(b) {}
int getLength(){return length;}
int getWidth() {return width;}
private:
int length,width;
};
class Rectangle : public Parallelogram //公有继承
{
public:
Rectangle(int a,int b):Parallelogram(a,b) {} //先对基类中的数据成员进行初始化
void Area() //计算面积
{
cout<<getLength()*getWidth();
}
};
int main()
{
Rectangle r(3,4);
r.Area();
return 0;
}
至此,“类”相关知识介绍完毕。
二、C++中的模板
如果说类是对对象的抽象,那么模板就是对类的抽象,模板相当于具有相同特性的类的交集,它声明了一种类的模板,并且提供了一个或多个的虚拟类型参数,从而给出不同数据类型的同一特性类。
模板的定义
常用形式为:
template<class/typename 虚拟类型1, class/typename 虚拟类型2…>
class 类名
{
成员
}
以下给出了一个适用于float和int型比较的类模板。
template <class numtype> //声明一个模板,虚拟类型名为numtype
class Compare //类模板名为Compare
{
public :
Compare(numtype a,numtype b)
{
x=a;y=b;
}
numtype max( )
{
return (x>y)?x:y;
}
numtype min( )
{
return (x<y)?x:y;
}
private :
numtype x,y;
};
需要注意这里的成员函数是在类里面定义的,如果是在类外定义的话不能使用一般类成员函数的定义方法
numtype Compare::max( ) {…}
而应该先声明类模板:
template <class numtype> numtype Compare<numtype>::max( ) { return (x>y)?x:y; }
模板的初始化
使用模板定义对象时的形式为:
类模板名<实际类型名> 对象名;
类模板名<实际类型名> 对象名(实参表列);
如:Compare<int> cmp; Compare<int> cmp(3,7);
至此,所需“模板”知识介绍完毕。
三、opencv中的 Vec类
opencv中经常使用Vec+数字+字母来定义变量,像是Vec4b,Vec3s,其实这就是一个向量模板类,首先它在任何时刻都是一个一维矩阵(即列向量),其次它的元素数据类型可变。这一模板类是对与Mat类的继承,其源码如下;
//【1】下面是OpenCv中定义---定义---向量模板类----的源代码
//【2】一个拥有非类型模板形参的---类模板-----向量模板类
template<typename _Tp, int cn>
class Vec : public Matx<_Tp, cn, 1>
{
public:
typedef _Tp value_type;
enum { depth = DataDepth<_Tp>::value, channels = cn,
type = CV_MAKETYPE(depth, channels) };
//! default constructor
//【1】向量类的默认构造函数
Vec();
//【1】向量类有参构造函数
Vec(_Tp v0); //!< 1-element vector constructor
Vec(_Tp v0, _Tp v1); //!< 2-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2); //!< 3-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2, _Tp v3); //!< 4-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4); //!< 5-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4, _Tp v5); //!< 6-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4, _Tp v5, _Tp v6); //!< 7-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4, _Tp v5, _Tp v6, _Tp v7); //!< 8-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4, _Tp v5, _Tp v6, _Tp v7, _Tp v8); //!< 9-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4, _Tp v5, _Tp v6, _Tp v7, _Tp v8, _Tp v9); //!< 10-element vector constructor
explicit Vec(const _Tp* values); //保证函数显式调用
Vec(const Vec<_Tp, cn>& v);
static Vec all(_Tp alpha);
//! per-element multiplication
Vec mul(const Vec<_Tp, cn>& v) const;
//! conjugation (makes sense for complex numbers and quaternions)
Vec conj() const;
/*!
cross product of the two 3D vectors.
For other dimensionalities the exception is raised
*/
Vec cross(const Vec& v) const;
//! convertion to another data type
template<typename T2> operator Vec<T2, cn>() const;
//! conversion to 4-element CvScalar.
operator CvScalar() const;
/*! element access */
const _Tp& operator [](int i) const;
_Tp& operator[](int i);
const _Tp& operator ()(int i) const;
_Tp& operator ()(int i);
Vec(const Matx<_Tp, cn, 1>& a, const Matx<_Tp, cn, 1>& b, Matx_AddOp);
Vec(const Matx<_Tp, cn, 1>& a, const Matx<_Tp, cn, 1>& b, Matx_SubOp);
template<typename _T2> Vec(const Matx<_Tp, cn, 1>& a, _T2 alpha, Matx_ScaleOp);
};
//【3】用typedef关键字给---向量类模板----template<typename _Tp, int cn> class Vec
//【1】向量模板类Vec的实例化,并且给相应实例的Vec向量模板类实例---指定新的名字
//【1】Vec2b--这是一个具体的--类类型---这个类类型实例话的类对象表示如下所示:
//【1】Vec2b---表示每个Vec2b对象中,可以存储2个char(字符型)数据
typedef Vec<uchar, 2> Vec2b; 、
//【2】Vec3b---表示每一个Vec3b对象中,可以存储3个char(字符型)数据,比如可以用这样的对象,去存储RGB图像中的一个像素点
typedef Vec<uchar, 3> Vec3b;
//【3】Vec4b---表示每一个Vec4b对象中,可以存储4个字符型数据,可以用这样的类对象去存储---4通道RGB+Alpha的图像中的像素点
typedef Vec<uchar, 4> Vec4b;
//【1】Vec2s---表示这个类的每一个类对象,可以存储2个short int(短整型)的数据
typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;
typedef Vec<ushort, 2> Vec2w;
typedef Vec<ushort, 3> Vec3w;
typedef Vec<ushort, 4> Vec4w;
typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<int, 6> Vec6i;
typedef Vec<int, 8> Vec8i;
typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;
typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;
至此,opencv中的Vec模板类介绍完毕。
四、Opencv中的宏定义
使用Opencv时常常看到的字母加数字缩写除了Vec模板类,还有就是宏定义了,它经常性的用于Mat的初始化,其具体形式及含义如下:
CV+位深+数据类型缩写+U+通道数目
数据类型缩写有:
S–代表—signed int—有符号整形
U–代表–unsigned int–无符号整形
F–代表–float---------单精度浮点型
其具体含义也就不难理解,CV16UC4就表示16位无符号整型三通道矩阵。
如果我们要使用到Vec类来对使用这种宏定义初始化的Mat赋值时,需要注意Vec类要和宏定义相互对应,像是:
Mat tempImg1(480,640,CV_8UC3);
Vec3b& test = temp.at<Vec3b>(i, j);
由于此处定义的Mat为8位深3通道,所以对应Vec模板类为可容纳3(3通道
个字符型数据(8位)的Vec3b
再如:
Mat tempImg1(480,640,CV_16UC4);
Vec4w& test = temp.at<Vec4w>(i, j);
由于此处定义的Mat为16位深4通道,所以对应Vec模板类为可容纳4个(4通道)无符号整型数据(16位)的Vec4w
参考文献:
C++中类的介绍
C++template模板使用方法
OpenCV 中的 Scalar 类、Vec类
详解 c++ 关键字 explicit