C/C++犯错集锦
1、关于模板
模板的定义和声明最好放在一起。
模板实例化
程序员在使用模板类时最常犯的错误是将模板类视为某种数据类型。所谓类型参量化(parameterized types)这样的术语导致了这种误解。模板当然不是数据类型,模板就是模板,恰如其名:
编译器使用模板,通过更换模板参数来创建数据类型。这个过程就是模板实例化(Instantiation)。
从模板类创建得到的类型称之为特例(specialization)。
模板实例化取决于编译器能够找到可用代码来创建特例 (称之为实例化要素, point of instantiation)。
要创建特例,编译器不但要看到模板的声明,还要看到模板的定义。
模板实例化过程是迟钝的,即只能用函数的定义来实现实例化。
模板的实例化需要获得模板参数,而模板参数需要从函数处获得。如果模板的声明和定义放在了不同的地方,那么在使用模板时编译器会认为模板函数的实现在其他文件中。
问题是在其他文件中的模板定义在编译时无法获得模板参数,无法得到对应的函数实现,故在连接时会出现错误。
认识了问题,就能够解决问题:
在实例化要素中让编译器看到模板定义。
用另外的文件来显式地实例化类型,这样链接器就能看到该类型。
参考:http://blog.csdn.net/lqk1985/article/details/3136364
2、类型转换中隐藏的问题
今天写了一段代码,逻辑上一直没有找到错误,可以通过编译,但执行一段时间后就说提示错误:Access violation reading location ****
代码如下(主要将错误的地方贴出):
1 /*************************HSVtoColor*******************************/ 2 enum { Red,Orange, Yellow, Green, Cyan, Blue,Violet , White, Dark } Color; 3 int HSVtoColor(float H, float S, float V) 4 { 5 if (!(H >= 0 && H <= 360 && S <= 1 && S >= 0 && V >= 0 && V <= 1)) 6 { 7 printf("HSV is invalid !\n"); 8 getchar(); 9 exit(0); 10 } 11 12 if (V <= 0.2)return Dark; 13 else{ 14 if (S <= 0.1)return White; 15 /*(330,20).(20。45),(45.75),(75,165),(165,200),(200,270), 16 (270,330),H分别取值为0,I,2,3,4,5,6代表红、橙、黄、绿、 17 青、蓝、紫。*/ 18 /*****************************************************************/ 19 if (H >= 330 && H <=359 || H >= 0 && H < 20) return Red; 20 if (H >= 20 && H < 45)return Orange; 21 if (H >= 45 && H < 75)return Yellow; 22 if (H >= 75 && H < 165)return Green; 23 if (H >= 165 && H < 200)return Cyan; 24 if (H >= 200 && H < 270)return Blue; 25 if (H >= 270 && H < 330)return Violet; 26 //return Red; 27 } 28 } 29 30 31 /****************************GetInfoOfJpeg***********************************/ 32 int GetInfoOfJpeg(char *file_path, float info[9], int gap , int mode /* = 1*/ ) 33 { 34 for (int i = 0; i < 9; i++)info[i] = 0; 35 FIBITMAP *bitmap = FreeImage_Load(FIF_JPEG, file_path); 36 if (bitmap) 37 { 38 int BPP = FreeImage_GetBPP(bitmap); 39 if (BPP < 16){ cout << "invalid JPEG -----GetInfoOfJpeg()\n"; return 0; } 40 41 int height = FreeImage_GetHeight(bitmap); 42 int weight = FreeImage_GetWidth(bitmap); 43 int x, y; 44 RGBQUAD color; 45 uchar *pcolor = (uchar *)&color; 46 float hsv[3]; 47 for (y = 0; y < height; y += gap) 48 for (x = 0; x < weight; x += gap) 49 { 50 FreeImage_GetPixelColor(bitmap, x, y, &color); 51 RGBtoHSV (pcolor[FI_RGBA_RED], pcolor[FI_RGBA_GREEN], pcolor[FI_RGBA_BLUE], hsv); 52 cout<<"HSV :"<<HSVtoColor(hsv[0], hsv[1], hsv[2])<<endl; 53 info[HSVtoColor(hsv[0], hsv[1], hsv[2])]++; 54 } 55 int sample_pixels = (weight*height) / (gap*gap);//the number of sample pixels 56 if (mode == 1)for (int i = 0; i < 9; i++)info[i] = info[i] / sample_pixels; 57 58 return 1; 59 //cout << "color:" << HSVtoColor(hsv[0], hsv[1], hsv[2]); 60 } 61 62 63 }//GetInfoOfJpeg
这里不介绍HSV颜色空间,只说明其取值范围:H∈N,且H∈[0 , 359] , S,V∈R 且∈[0,1],注意到上面第三行参数都是float型。传入的数据H = 359.01(可能舍入运算时出现这种情况)那么19到25行的区间就无法包括这一H值,程序在执行的时候可能没有返回值。当把这个函数的返回值作为其他操作的参数时(例如53行)就会出现问题。下面的图片的最后一行数据显示了当H不在19到25行的数据区间时内存中的数据。很显然将这个数据作为info[]的下标就溢出了,所有才会出现上面的错误。
总结一下,比较大小是还是将关系运算符两边的数据的类型转化为一致,当数据进行类型转化时,最好做个标记,防止自己忘了。
当出现多个并列的条件语句时一定要注意是否我考虑了所有的可能性(考虑一下switch语句中default的作用)。
3、头文件包含的问题
头文件包含的顺序对预处理的影响是很大的,特别是使用第三方库时包含的头文件。例如:
a:
#include<windows.h>
#include"FreeImages.h"//FreeImages.h是一个图片处理库的头文件
b:
#include"FreeImages.h"
#include<windows.h>
a和b预处理之后的结果是不同的。
其中你可以在a后面的使用WCHAR,不会出现问题。
但在b后面你无法使用WCHAR,编译时会提醒未定义WCHAR。
一般如果你对一个库不是很了解,就将其放置在最后。
注意new int(n)和new int[n]的区别 ,前面是在堆中建立一个值为n的int型变量而后面一个则是在堆中分配n个int型变量空间。
4、堆中内存越界的问题:
VS2013中的提示: Heap block at *** modified at *** past requested size of ***
部分代码:
m_pVdistribution = new double[5]();
m_pVdistribution[(int)(hsv[2] / (1.0 / 5))]++;
delete [] m_pVdistribution;
因为没有注意到hsv[2]∈[0 , 1],那么在hsv[2] == 1时,(int)(hsv[2] / (1.0 / 5)) == 5这样m_Vdistribution指向的数组就越界了。
一般而言,越界之后编译器是无法发现的,在C/C++中有这样一句话:当把数组名作为函数参数时,数组名会自动退化为某种类型的指针。这说明一般情况下编译器并不知道指针所指向的数组的大小。
上面的代码可以通过编译但绝大部分时间会在执行到第三行的时候会出现运行时错误,但不是每次都出现。在释放堆中的内存时,系统(或者系统函数,或者其他的实现方式)会自动计算m_pVdistribution所指向的内存的大小,这时会发现你更改了不属于你的内存空间,这时就会自动触发中断。
关于运算符重载的问题 20170830
今天需要在set中使用pair,定义了operator<(const &....),但不能通过编译。
MD,pair已经定义了比较运算符,编译器居然自己去找标准库中的那个比较函数而不用我写的那个。可能我写的那个没法实现精确匹配吧。后来继承了pair,写了一个类,并在类里实现了比较。根据C++的符号匹配规则,一般先找最近的范围。还要注意一点:一定要在成员比较函数后加const,这样右值也可以使用。