最近在将柑橘溃疡病系统由MATLAB平台迁移至VC平台,本可以通过其他工具来完成,但想到自己C++尤其磋,便想借此机会学习一下C++。于是下定决定核心代码全部重写。现在已经完成了一小部分的工作,包括核心类矩阵与向量的封装,并完成了部分特征提取的算法。尽管以前看过一些C++的书籍,也在VC平台上写过一些程序,但以前都是与杨世泉合作,他做的设计;只有这一次是,自己设计并实现之。
这几个星期的开发遇到了许多的问题,设计方面的,语言方面的,其中细微,唯有亲历,才能体会,记录下来当作经验积累吧。
1、类型转换问题
由高精度的数据转换成低精度的数据会产生数据丢失;低精度转换成高精度就不会有问题。
比如float转换成int会产生数据丢失,int转换成float就不会有问题。
但如果将一个int数据a,先将其转换成float,再转换成int,会不会与原始的数据项等呢?如下例:
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
测试一下,输出为0。转换成float再转换成int与原始数据是相等的。
float b = a;
cout << a - ( int )b << endl;
再测一下,输出为 21!不相等了!把整形数往float上换一下,再换回来,就不等了!
咨询了一下高人。原因在于:
数据转换时的信息损失包括两种,一种是总值损失,一种是精度损失。
由int转换至float不可能再量上损失,但可能损失精度。
int->float->int,转换后,最低位有可能不同。因此,一个很大的数据经两次转换后就有很微小的差异了。
2、无符号数做索引产生的问题
typedef unsigned long DWORD;
for ( DWORD i = 5 ; i >= 0 ; i -- )
{
// 死循环,因为i永远不可能小于0
}
同理,如下也是死循环:
{
//...
3、const 成员函数 调用 非const成员函数
类 IVector 的两个函数:
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1327ab569c1ae82736693a50b8e33378.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/0196c3df5ea9e936f21e9932cca91014.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1327ab569c1ae82736693a50b8e33378.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/0196c3df5ea9e936f21e9932cca91014.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
计算方差 variance 的函数中,需要调用 mean 函数计算向量的均值,调用处编译错误:
试图对一个const类型的类对象调用非const型的成员函数(mean)。
对于const型的成员函数,编译器会将其引用的任何成员变量标记为const类型——虽然你在类的声明中该成员变量可能不是,从而导致此错误。
解决方法1:将 mean 函数申明为 const 成员函数
解决方法2:将 variance函数去掉 const 申明
《高质量C++-C编程指南》:任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性。
4、构造函数中调用构造函数时,有一个析构函数被调用的过程
类 A 构造函数 1:
{
// ...
}
类 A 构造函数 2:
{
IVector( 1 , 0 ); // 调用构造函数1
}
构造函数 2在 调用构造函数1 后会马上调用析构函数
因此 该构造函数会完全不起作用。
这与 Java 不同,Java类的构造函数是完全可以相互调用的。
5、C++ 的 const 与 Java 的 final
在修饰变量时,
如果变量是基本类型,那么两者是完全一样的
但是如果变量是对象,那就不同了
const是指对象不可改变;而final表示对象的句柄(对象的引用)是不可改变的
C++:
{
vec._size = 5 ; // 错误! error C2166: l-value specifies const object
}
Java:
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1327ab569c1ae82736693a50b8e33378.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/0196c3df5ea9e936f21e9932cca91014.gif)
C++中,变量vec是IVector对象本身,vec被申明为const后,则其不可修改;
Java中,变量vec保存的IVector对象的引用,并非对象本身。 vec被申明为final后,引用不可修改,但对象本身却是可以修改的。
6 内存越界访问造成数据删除时发生错误
边缘检测的模板类IEdgeMask,其有三个成员变量:
BYTE _width;
double ** _mask;
构造模板函数的时候
_width = 2 ; // 此处本应为3,但误写为2
_mask = new double * [ _height ];
for ( BYTE i = 0 ; i < _height; i ++ )
{
_mask[i] = new double [ _width ];
}
_mask[ 0 ][ 0 ] = 0.16666666667 ;
_mask[ 0 ][ 1 ] = 0.0000 ;
_mask[ 0 ][ 2 ] = - 0.16666666667 ; // 越界访问数据
_mask[ 1 ][ 0 ] = 0.16666666667 ;
_mask[ 1 ][ 1 ] = 0.0000 ;
_mask[ 1 ][ 2 ] = - 0.16666666667 ; // 越界访问数据
_mask[ 2 ][ 0 ] = 0.16666666667 ;
_mask[ 2 ][ 1 ] = 0.0000 ;
_mask[ 2 ][ 2 ] = - 0.16666666667 ; // 越界访问数据
能够越界对数据进行访问,执行对应的程序也没有任何问题。当函数体离开对象,执行对象的析构函数时,就出现问题了,无法删除数据成员_mask!
{
if ( _mask[i] != NULL )
{
delete [] _mask[i]; // 此处报出错误~
_mask[i] = NULL;
}
}
if ( _mask != NULL )
delete [] _mask;
_mask = NULL;
_height = 0 ;
_width = 0 ;
调试过程中会发现,所有的数据都是存在的。但就是无法删除该区域。
内存越界访问造成的后果非常严重。它造成的后果是随机的,表现出来的症状和时机也是随机的,调试起来非常困难。需谨慎谨慎再谨慎。
7 永远的话题:传值、传引用
细节就不写了。只记录几个点。
传引用的场景:
a、需要改变实参的值
b、向主调函数返回额外的结果
c、向函数传递大型对象
比如函数重载中用到的传引用:
* 操作符重载 * : 矩阵与矩阵点乘
*/
IMatrix & operator * ( const IMatrix & other );
参数传递时传引用时为了函数传递大型对象,减少不必要的对象拷贝
返回值传引用时为了实现链式表达式
C++传值,传对象本身的拷贝
Java传值,传对象引用的拷贝
C++ 遇到问题最严重的并不是传说中的内存泄漏,而是越界访问、指针运算、临时指针这几个问题,错误隐藏的很深而不容易发现,调试过程真是太郁闷老。
附带印象中 Java 常出现的Exception:
NullPointerException
NumberFormatException
NoClassDefFoundError
ClassCastException
IOException
FileNotFoundException
SQLException
JDBCException
HibernateException
LazyException
SocketException
ServletException