最近遇到的C++问题小结

来源:互联网 发布:商业银行网络专线 编辑:程序博客网 时间:2024/05/24 06:29

    最近在将柑橘溃疡病系统由MATLAB平台迁移至VC平台,本可以通过其他工具来完成,但想到自己C++尤其磋,便想借此机会学习一下C++。于是下定决定核心代码全部重写。现在已经完成了一小部分的工作,包括核心类矩阵与向量的封装,并完成了部分特征提取的算法。尽管以前看过一些C++的书籍,也在VC平台上写过一些程序,但以前都是与杨世泉合作,他做的设计;只有这一次是,自己设计并实现之。

    这几个星期的开发遇到了许多的问题,设计方面的,语言方面的,其中细微,唯有亲历,才能体会,记录下来当作经验积累吧。

1、类型转换问题
      由高精度的数据转换成低精度的数据会产生数据丢失;低精度转换成高精度就不会有问题。
比如float转换成int会产生数据丢失,int转换成float就不会有问题。
    但如果将一个int数据a,先将其转换成float,再转换成int,会不会与原始的数据项等呢?如下例:

int a=5;
float b=a;
cout
<<a-(int)b<<endl;

测试一下,输出为0。转换成float再转换成int与原始数据是相等的。

int a=2523456789;
float b=a;
cout
<<a-(int)b<<endl;

再测一下,输出为 21!不相等了!把整形数往float上换一下,再换回来,就不等了!
咨询了一下高人。原因在于:
数据转换时的信息损失包括两种,一种是总值损失,一种是精度损失。
由int转换至float不可能再量上损失,但可能损失精度。
int->float->int,转换后,最低位有可能不同。因此,一个很大的数据经两次转换后就有很微小的差异了。

2、无符号数做索引产生的问题

typedef unsigned char BYTE;
typedef unsigned 
long DWORD;
for( DWORD i = 5; i >=0; i--)
{
//死循环,因为i永远不可能小于0
}

同理,如下也是死循环:

for( BYTE i = 5; i >=0; i--)
{
//...
}


3、const 成员函数 调用 非const成员函数
类 IVector 的两个函数:

   /**
  * 计算向量的均值
  
*/

 
double mean();               // 非 const 成员函数
 /**
  * 计算向量的方差
  
*/

 
double variance() const// const 成员函数

计算方差 variance 的函数中,需要调用 mean 函数计算向量的均值,调用处编译错误:

 error C2662: 'mean' : cannot convert 'this' pointer from 'const class IVector' to 'class IVector &'

   试图对一个const类型的类对象调用非const型的成员函数(mean)。  
   对于const型的成员函数,编译器会将其引用的任何成员变量标记为const类型——虽然你在类的声明中该成员变量可能不是,从而导致此错误。  
  解决方法1:将 mean 函数申明为 const 成员函数
  解决方法2:将 variance函数去掉 const 申明

  《高质量C++-C编程指南》:任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性。

4、构造函数中调用构造函数时,有一个析构函数被调用的过程
类 A 构造函数 1:

IVector::IVector( DWORD _size,double defaultValue = 0.0 )
{
//...

 类 A 构造函数 2:

IVector::IVector( ... )
{
     IVector( 
1,0 );//调用构造函数1
}

构造函数 2在 调用构造函数1 后会马上调用析构函数
因此 该构造函数会完全不起作用。
这与 Java 不同,Java类的构造函数是完全可以相互调用的。
   
  
5、C++ 的 const 与 Java 的 final
在修饰变量时,
如果变量是基本类型,那么两者是完全一样的  
但是如果变量是对象,那就不同了  
const是指对象不可改变;而final表示对象的句柄(对象的引用)是不可改变的   
C++:

void g( const IVector vec)
{
 vec._size
=5;//错误! error C2166: l-value specifies const object
}

Java:

public void f( final IVector vec)
 
{
    vec._size 
= 5;//OK
 }

C++中,变量vec是IVector对象本身,vec被申明为const后,则其不可修改;
Java中,变量vec保存的IVector对象的引用,并非对象本身。 vec被申明为final后,引用不可修改,但对象本身却是可以修改的。
 
6  内存越界访问造成数据删除时发生错误
边缘检测的模板类IEdgeMask,其有三个成员变量:

 BYTE _height;  
 BYTE _width;   
 
double** _mask; 

构造模板函数的时候

 _height = 3;
    _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!

 for ( BYTE i=0; i<_height; i++ )
 {
  
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
  

原创粉丝点击