最近遇到的C++问题小结
2007-12-13 20:14
477 查看
最近在将柑橘溃疡病系统由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
这几个星期的开发遇到了许多的问题,设计方面的,语言方面的,其中细微,唯有亲历,才能体会,记录下来当作经验积累吧。
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
相关文章推荐
- c++多线程编程遇到的问题小结
- C++ 遇到的问题小结
- C++ 最近面试题中遇到的一些问题
- 最近遇到的C++数字和字符串的转换问题
- Linux环境下段错误的产生原因及调试方法小结 最近在Linux环境下做C语言项目,由于是在一个原有项目基础之上进行二次开发,而且项目工程庞大复杂,出现了不少问题,其中遇到最多、花费时间最长的问题就是
- 总结一下最近将163邮箱拖动效果改成兼容Firefox遇到的问题
- 学习中遇到的c++问题,持续更新
- 软件项目开发过程中主要遇到的核心问题小结
- C#中调用C++写的com时遇到的问题与解决方案
- C/C++经典问题及自己所遇到的部分问题
- 最近遇到的问题
- 有关于野指针及其他c++问题小结(一)
- C++学习的时候遇到的问题
- 【最近面试遇到的一些问题】JAVA UTF-8 GB2312 编码互转
- EXTJS开发过程遇到的一些问题的小结(转自麦田守望者)
- ubuntu 下的C++多线程遇到的问题(2)--向线程传递参数
- 第一篇文章,不知写什么。就写一些我最近在trswcm里遇到的问题吧。(没大用只是为了写而写)
- apache2.2.19安装fastcgi遇到的问题小结
- Android studio 调试NDK C++ 代码,遇到的问题
- 安装WebStorm会遇到的问题小结