你开发的软件安装在C盘Program Files (x86)下产生的异常
最后更新于:2022-04-01 06:41:06
## 没有躲过的坑--你开发的软件安装在C盘Program Files (x86)下产生的异常
今天偶然发现的问题,就是自己写的win32程序安装在C盘Program Files (x86)文件夹下就会产生异常,而安装在其他文件夹,即使是D盘的Program Files (x86)下,程序也可以完美运行。
引起这个,肯定是权限的问题。这个软件运行时,需要读写数据库,以及各种缓存数据。
这就是我给自己挖的一个坑儿,相信很多人也会遇到这个问题。
就是在开发程序、VS调试的时候,总喜欢使用当前路径,或者是相对路径。即把一些数据与.exe文件放在一起。
但是安装的时候,选择的是C盘Program Files (x86)文件夹,那么就意味着数据库、以及各种缓存数据的读写也都在文件夹Program Files (x86)中进行,这样有时候就会产生权限的问题。
静下来,沉思一下。看了看QQ,就算是把QQ安装在Program Files (x86)文件夹下,你会发现,在“文档”(XP是“我的文档”)下面会产生一个Tencent Files文件夹,用于存放各种数据。
我们知道,读写“文档”下的内容是不需要权限的。
这就指导我们在存在数据库等缓存文件时,最好放在“文档”下面。
接下的问题就是程序中如何获得“文档”文件夹呢?
使用函数**SHGetFolderPath**:
~~~
std::wstring GetDocumentsPath() {
TCHAR my_documents[MAX_PATH];
HRESULT result = SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, my_documents);
return std::wstring(my_documents);
}
~~~
HRESULT SHGetFolderPath(
HWND hwndOwner,
int nFolder,
HANDLE hToken,
DWORD dwFlags,
LPTSTR pszPath
);
以上为函数原型,其中nFolder指定要获取的目录(具体参考MSDN说明)
CSIDL_BITBUCKET 回收站
CSIDL_CONTROLS 控制面板
CSIDL_DESKTOP Windows 桌面Desktop
CSIDL_DESKTOPDIRECTORY Desktop的目录
CSIDL_DRIVES 我的电脑
CSIDL_FONTS 字体目录
CSIDL_NETHOOD 网上邻居
CSIDL_NETWORK 网上邻居虚拟目录
**CSIDL_PERSONAL 我的文档**
CSIDL_PRINTERS 打印机
CSIDL_PROGRAMS 程序组
CSIDL_RECENT 最近打开的文档
CSIDL_SENDTO “发送到”菜单项
CSIDL_STARTMENU 任务条启动菜单项
CSIDL_STARTUP 启动目录
CSIDL_TEMPLATES 文档模板
std::string初始化、最快速判断字符串为空
最后更新于:2022-04-01 06:41:04
##没有躲过的坑--std::string初始化、最快速判断字符串为空
之前说过,记得给变量初始化。
今天突然想到了一个问题,如果声明了一std::string类型,怎么初始化呢?
~~~
std::string test_string;
std::string test_string_empty = "";
std::string test_string_null = NULL;//运行错误,而非编译错误
~~~
简单测试:
~~~
#include<iostream>
int main()
{
std::string test_string;
std::string test_string_empty = "";
// std::string test_string_null = NULL;
if (test_string == "")
{
std::cout << "test_string is empty!" << std::endl;
}
if (test_string_empty == "")
{
std::cout << "test_string_empty is empty!" << std::endl;
}
return 0;
}
/*---------------------------------
输出:
test_string is empty!
test_string_empty is empty!
-----------------------------------*/
~~~
由此可见,声明一个std::string对象,默认变为空。
**更重要的是 std::string不能与null进行比较!!**
那么判断一个std::string 为空 是使用empty还是“”呢?
~~~
#include<iostream>
int main()
{
std::string test_string;
std::string test_string_empty = "";
// std::string test_string_null = NULL;
if (test_string == "")
{
std::cout << "test_string is empty!" << std::endl;
}
if (test_string_empty.empty())
{
std::cout << "test_string_empty is empty!" << std::endl;
}
if (test_string_empty.length() == 0)
{
std::cout << "test_string_empty is empty!" << std::endl;
}
return 0;
}
//输出:
//test_string is empty!
//test_string_empty is empty!
//test_string_empty is empty!
~~~
三种方法都可以,但是谁更优越呢?
之前搞过一段C Sharp, 犹记得比较长度最快,不知道C++中是不是也是通过长度来判断字符串是否为空的方法最为效率呢。
太晚了,明天继续,欢迎指导:
~~~
#include<iostream>
#include <ctime>
#include<Windows.h>
int main()
{
std::string test_string;
std::string test_string_empty = "";
unsigned int ticks1 = 0;
unsigned int ticks2 = 0;
unsigned int ticks3 = 0;
unsigned int ticks4 = 0;
unsigned int ticks5 = 0;
unsigned int ticks6 = 0;
ticks1 = GetTickCount();
if (test_string_empty == "")
{
for (int i = 0; i < 100000; i++)
{
}
ticks2 = GetTickCount();
}
ticks3 = GetTickCount();
if (test_string_empty.empty())
{
for (int i = 0; i < 100000; i++)
{
}
ticks4 = GetTickCount();
}
ticks5 = GetTickCount();
if (test_string_empty.length() == 0)
{
for (int i = 0; i < 100000; i++)
{
}
ticks6 = GetTickCount();
}
std::cout << ticks2 - ticks1 << std::endl;
std::cout << ticks4 - ticks3 << std::endl;
std::cout << ticks6 - ticks5 << std::endl;
return 0;
}
//test_string is empty!
//test_string_empty is empty!
~~~
0xC0000005: 读取位置 xxx时发生访问冲突
最后更新于:2022-04-01 06:41:01
##没有躲过的坑--0xC0000005: 读取位置 xxx时发生访问冲突
Bjarne Stroustrup老爷子说过:
**“C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off.”**
最近的工程时不时的出现0xC0000005: 读取位置 xxx时发生访问冲突,导致崩溃。
先看看下面的程序:
~~~
#include<iostream>
#include <string>
int main()
{
char * p1 = (char *)malloc(sizeof(char) * 20);
char p3[20] = "igkl";
char * p2 = "efgh";
p1 = "abcd"; //p1指向字符串"abcd"的首地址, 而不是把"abcd"拷贝到malloc开辟的内存块中
strcat(p1, p2); //报错: 0xC0000005异常,访问冲突
strcat(p3, p2); //正确
std::cout << p1 << std::endl;
std::cout << p2 << std::endl;
std::cout << p3 << std::endl;
std::cout << p2 << std::endl;
//释放内存
free(p1);
p1 = NULL;
return 0;
}
~~~
上面的程序在运行的时候就会报错,调试分析:
因为指针变量p1(0x00d7576c)所指向的字符串常量“abcd”后面,也即字符’d’的地址是 0x00d7576c+3=0x00d7576f 。而它后面以地址 0x00d75770 开始的内存块是不属于指针变量p1的,没有访问权限,所以把拷贝自指针变量p2(0x00d75774)指向的字符串“efgh”到以 0x00d75770 为起始地址的内存块中会报错。
如果这样修改:
~~~
char p3[20] = "igkl";
char * p1 = (char *)malloc(sizeof(char) * 20);
p1 = p3;
char * p2 = "efgh";
strcat(p1, p2); //正确
strcat(p3, p2); //正确
~~~
可以把语句 p1 = “abcd”; 改成 p1 = p3;,然后直接使用 strcat( p1, p2 );,这样是合法的。
博客[C++“读取位置 0x****** 时发生访问冲突”的可能原因](http://blog.csdn.net/pkueecser/article/details/6066850 "0xC0000005可能的错误")
有指出了一种情况:
malloc一个100内存的空间用于读入文件,当文件的大小超过100时,并且程序中再使用100之后的内存时,就会产生“0xC0000005: 读取位置 xxx时发生访问冲突”这样的错误。
还有可能产生的原因:
**申请的内存没有释放**
new后必须delete
malloc后必须free
创建内核对象(比如CreateFile,CreateMutex,CreateThread),后必须释放内核对象句柄.
创建内存映射文件,CreateFileMapping,MapViewOfFile后必须CloseHandle(),UnMapviewofFile
创建GDI对象后,比如LoadIcon,LoadImage,CreateImageList等等,必须Destroy掉
创建DC后,比如GetDC(), 必须释放DC句柄
保留虚拟地址空间 VirtualAlloc(),然后提交物理存储器后,必须释放掉
**出现死循环导致内存泄露**
**所有的第三方类库存在内存泄露相关的BUG**
常见的溢出主要有:
**内存分配未成功,却使用了它。**
常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p 是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc 或new 来申请内存,应该用if(p==NULL)或if(p!=NULL)进行防错处理。
**内存分配虽然成功,但是尚未初始化就引用它。**
内存分配成功并且已经初始化,但操作越过了内存的边界。
例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for 循环语句中,循环次数很容易搞错,导致数组操作越界。
**使用free 或delete 释放了内存后,没有将指针设置为NULL。导致产生“野指针”。**
程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
C++函数的默认参数(重新定义默认参数)
最后更新于:2022-04-01 06:40:59
## 没有躲过的坑--C++函数的默认参数(重新定义默认参数)
默认参数指的是当函数调用中省略了实参时,自动使用一个值。
这里首先需要注意的是:
**对于带参数列表的函数,必须从右向左添加默认值。**
也就是说,要为某个参数设置默认值,则必须为它右边的所有参数提供默认值。
今天遇到的坑儿,就是函数使用默认参数,并且函数的声明和定义是分开的。
~~~
char* left(const char* str, int n=1);
int main()
{
}
char* left(const char* str, int n = 1)//错误
{
}
~~~
上面代码可以有两种修改:
1声明时带有默认参数,实现时没有默认值
~~~
char* left(const char* str, int n=1);
int main()
{
}
char* left(const char* str, int n )
{
}
~~~
2实现时带有默认参数,声明时没有默认值
~~~
char* left(const char* str, int n);
int main()
{
}
char* left(const char* str, int n = 1)
{
}
~~~
还有一个我们经常犯的错误。
很多情况,我们可以使用默认参数来替代函数重载:
~~~
void point(int,int){//...}
void point(int a){return point(a,4);}
void point(){return point(3,4);}
~~~
可以用下面的默认参数的函数来替代:
~~~
void point(int=3,int=4);
~~~
当调用“point();”时,即调用“point(3,4);” 它是第3个声明的重载函数。
当调用“point(6);”时,即调用“point(6,4);”,它是第2个声明的重载函数。
当调用“point(7,8);”时,即调用第1个声明的重载函数
当你窃喜的时候,如果一组重载函数(可能带有默认参数)都允许相同实參个数的调用,将会引起调用的二义性。
~~~
void func(int); //重载函数之一
void func(int,int=4); //重载函数之二,带有默认参数
void func(int=3,int=4); //重载函数之三,带有默认参数
func(7); //error: 到底调用3个重载函数中的哪个?
func(20,30) //error:到底调用后面2个重载函数的哪个?
~~~
vector使用erase后迭代器变成野指针
最后更新于:2022-04-01 06:40:57
##没有躲过的坑--vector使用erase后迭代器变成野指针
vector上镜率非常高,但是最近又被他fuck了一下。使用的就是vector的erase方法。
**erase–return value**
首先需要明确一下vector的两种erase:
C++98中是这样的:
~~~
iterator erase (iterator position);
iterator erase (iterator first, iterator last)
~~~
C++11是这样的:
~~~
iterator erase (const_iterator position);
iterator erase (const_iterator first, const_iterator last);
~~~
我们使用下面的代码进行erase:
~~~
#include <iostream>
#include <vector>
int main ()
{
std::vector<int> myvector;
// set some values (from 1 to 10)
for (int i=1; i<=10; i++) myvector.push_back(i);
// erase the 6th element
myvector.erase (myvector.begin()+5);
// erase the first 3 elements:
myvector.erase (myvector.begin(),myvector.begin()+3);
std::cout << "myvector contains:";
for (unsigned i=0; i<myvector.size(); ++i)
std::cout << ' ' << myvector[i];
std::cout << '\n';
return 0;
}
/*--------------------------------------
Output:
myvector contains: 4 5 7 8 9 10
---------------------------------------*/
~~~
上面的代码非常的完美,但是当把ease用于for循环的时候,就完蛋了:
~~~
for(vector<int>::iterator iter=vector_database.begin(); vector_database!=veci.end(); iter++)
{
if( *iter == 10)
{
vector_database.erase(iter);
}
}
~~~
当执行veci.erase(iter)后,迭代器iter指向了哪里?
是时候关注一下erase方法的返回值了:
An iterator pointing to the new location of the element that followed the last element erased by the function call. This is the container end if the operation erased the last element in the sequence.
Member type iterator is a **random** access iterator type that points to elements.
看到random你就要疯掉了吧,野指针!!!!
也就是说veci.erase(iter)后,iter的状态是不确定的,再进行++,岂有不崩溃的道理!!
解决方法一,就是ease后对iter进行重新赋值。
解决方法二:再 使用一个迭代器。
~~~
vector<int>::iterator itor2;
for(vector<int>::iterator iter=vector_database.begin(); iter!=vector_database.end(); )
{
if( *iter == 10)
{
itor2=iter;
vector_database.erase(itor2);
}
else
iter ++ ;
}
~~~
**remove or erase?**
很多人还用到过remove,但是对于很多人不能分清楚remove和erase的区别?
STL中remove()只是将待删除元素之后的元素移动到vector的前端,而不是删除。若要真正移除,需要搭配使用erase()。
vector中的remove的作用是将等于value的元素放到vector的尾部,但并不减少vector的size
vector中erase的作用是删除掉某个位置position或一段区域(begin, end)中的元素,减少其size
**erase with remove_if**
~~~
vector_database.erase(
std::remove_if(vector_database.begin(),
vector_database.end(),
[this](const unique_ptr<lesschat::Channel>& vector_database) {
return this->current_channel_id_ == vector_database->channel_id();
}));
~~~
没有及时break出for循环
最后更新于:2022-04-01 06:40:55
##没有躲过的坑--没有及时break出for循环
break这个词都不陌生,最常用到的就是在switch语句中。
如果在switch的case后面忘写了break,就会顺序执行到下一个case的对应的语句。
我说的这种情况只是对于C++,对于C Sharp如果忘记了break,编译器是会报错的。
仔细想想,好像自己还真没有用过break在其他地方。导致自己又掉入了一个大坑中。
比如使用一个for循环查找数组中所要的元素中your_find对应的索引:
~~~
int index = 0;
for(int i=; i<1000; i++)
{
if(array[i] == your_find)
{
index = i;
}
}
~~~
这样呢,上诉的代码会把整个for循环都执行完成。
为了,提高效率,如果我们可以确定array数组中只有一个与your_find对应的索引,就可以这样修改代码:
~~~
int index = 0;
for(int i=; i<1000; i++)
{
if(array[i] == your_find)
{
index = i;
break;
}
}
~~~
好了,当if中语句为真的时候,跳出for循环。
下面延伸一下,那么对于二重循环,执行break的话,是跳出哪一个循环呢?
最简单的方法,写一段代码测试:
~~~
for(int j=0;j<=20;j++)
{
for(int i=0;i<=10;i++)
{
if(i==5) break;
}
if(j==20) cout<<" 'break' break from the nearest loop"<<endl;
}
cout<<j<<endl;
cout<<i<<endl;
~~~
上面代码的输出为:
break’ break from the nearest loop”
21
5
因此可以确定,break是跳出离他最近的一层循环。
使用太多的全局变量
最后更新于:2022-04-01 06:40:52
##没有躲过的坑--使用太多的全局变量
最近在工程中为了达到目的,使用了很多全局变量,导致程序运行效率降低。
在程序运行时,根据需要到内存中相应的存储单元中调用,如果一个变量在程序中频繁使用,例如循环变量,那么,系统就必须多次访问内存中的该单元,影响程序的执行效率。因此,C\C++语言还定义了一种变量,不是保存在内存上,而是直接存储在CPU中的寄存器中,这种变量称为寄存器变量。
总觉得有些太low,就Google了一番。
首先我们清楚,有个叫寄存器的东西,如果是变量存储在寄存器上,就相当于直接操作CPU,程序当然会运转的很流畅;计算速度也是很快。
全部变量不是分配在寄存器上的,并且全局变量可以通过指针,函数等对其进行修改。所以尤其对于一些循环,一定要避免频繁使用全局变量。
但是如果又避免不了使用全局变量,那该如何是好?
一个巧妙的方法就是把全局变量赋值给一个临时变量,对临时变量进行操作,最后再将临时变量的值赋给全局变量。
请看代码:
~~~
int f(void);
int g(void);
int errs;
void test1(void)
{
errs += f();
errs += g();
}
void test2(void)
{
int localerrs = errs;
localerrs += f();
localerrs += g();
errs = localerrs;
}
~~~
我们可以进行测试,test2的速度优于test1,尤其对于多次使用全局变量的时候。
既然提到了寄存器,现在就简单聊一聊关键字register。
关键字register是C语言引入的,就是建议编译器使用cpu寄存器来存储自动变量:
~~~
register int count_fast;
~~~
目的就是为了提高访问变量的速度。
在C++11之前,register在C++中扮演着同样的角色。
在C++11中,register关键字不再表示是寄存器变量,而只是显式指出变量是自动的。与auto关键字的作用相同。
但是为什么又不废除呢?
当然是为了兼容前人所写的代码而已。
new一个指针数组、以及创建动态二维数组
最后更新于:2022-04-01 06:40:50
##没有躲过的坑--new一个指针数组、以及创建动态二维数组
实际工作中,有一个类A,现在需要使用多个A对象的指针,这是时候,首先想到的就是指针数组。
————————————————————————————————————————————
**指针数组与数组指针**
首先,指针数组和数组指针有何区别是老生常谈的东西:
看中文不方便,看看英文描述:
指针数组:array of pointers,即用于存储指针的数组,也就是数组元素都是指针
数组指针:a pointer to an array,即指向数组的指针
还要注意的是他们用法的区别,下面举例说明。
int* a[4] 指针数组
表示:数组a中的元素都为int型指针
元素表示:*a[i] *(a[i])是一样的,**因为[]优先级高于***
int (*a)[4] 数组指针
表示:指向数组a的指针
元素表示:(*a)[i]
————————————————————————————————————————————
**new一个指针数组**
对于类A的指针,可以这样写,不仅仅new,还调用了A的构造函数。
~~~
A *P = new A();
~~~
但是对于new 一个指针数组,我们可以这么写:
~~~
A **P = new A *[10];
~~~
但是我们还没有进行构造呢?
~~~
for(int i=0; i<10; i++)
{
p[i] = new A();
}
~~~
还有一个问题就是释放:
~~~
for(int i=0; i<10; i++)
{
delete p[i];//为什么不是delete[]p[i]
}
delete [] p;
~~~
————————————————————————————————————————————
**创建动态二维数组**
**1 pointer to pointer(二维指针)**
~~~
int **dynamicArray = 0;
dynamicArray = new int *[ROWS] ;
for( int i = 0 ; i < ROWS ; i++ )
{
dynamicArray[i] = new int[COLUMNS];
}
for( int i = 0 ; i < ROWS ; i++ )
{
delete [] dynamicArray[i] ;
}
delete [] dynamicArray ;
~~~
**2 vector of vector**
~~~
#include <vector>
using namespace std;
#define ROWS 4
#define COLUMNS 4
vector<vector<int> > dynamicArray(ROWS, vector<int>(COLUMNS));
for(int i = 0;i < dynamicArray.size();++i)
{
for(int j = 0;j < dynamicArray[i].size();++j)
{
dynamicArray[i][j] = i*j;
}
}
for(int i = 0;i < dynamicArray.size();++i)
{
for(int j = 0;j < dynamicArray[i].size();++j)
{
cout << dynamicArray[i][j] << endl;
}
}
~~~
使用using namespace std的坏习惯
最后更新于:2022-04-01 06:40:48
##没有躲过的坑--使用using namespace std的坏习惯
使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突。
在C++中,变量、函数和类都是大量存在的。如果没有命名空间,这些变量、函数、类的名称将都存在于全局命名空间中,会导致很多冲突。比如,如果我们在自己的程序中定义了一个函数toupper(),这将重写标准库中的toupper()函 数,这是因为这两个函数都是位于全局命名空间中的。命名冲突还会发生在一个程序中使用两个或者更多的第三方库的情况中。此时,很有可能,其中一个库中的名 称和另外一个库中的名称是相同的,这样就冲突了。这种情况会经常发生在类的名称上。比如,我们在自己的程序中定义了一个Stack类,而我们程序中使用的某个库中也可能定义了一个同名的类,此时名称就冲突了。
Namespace 关键字的出现就是针对这种问题的。由于这种机制对于声明于其中的名称都进行了本地化,就使得相同的名称可以在不同的上下文中使用,而不会引起名称的冲突。或许命名空间最大的受益者就是C++中的标准库了。在命名空间出现之前,整个C++库都是定义在全局命名空间中的(这当然也是唯一的命名空间)。引入命名空间后,C++库就被定义到自己的名称空间中了,称之为std。这样就减少了名称冲突的可能性。我们也可以在自己的程序中创建自己的命名空间,这样可以对我们认为可能导致冲突的名称进行本地化。这点在我们创建类或者是函数库的时候是特别重要的。
C++标准程序库中的所有标识符都被定义于一个名为std的namespace中。
namespace是指标识符的各种可见范围。命名空间用关键字namespace 来定义。命名空间是C++的一种机制,用来把单个标识符下的大量有逻辑联系的程序实体组合到一起。此标识符作为此组群的名字。
C++标准程序库中的所有标识符都被定义于一个名为std的namespace中。 由于namespace的概念,使用C++标准程序库的任何标识符时,
可以有三种选择:
[1] 直接指定标识符
例如std::iostream而不是iostream。完整语句如下:
~~~
std::cout<<std::hex<<3.4<<std::endl;
~~~
[2]使用using关键字
~~~
using std::cout;
using std::endl;
using std::cin;
cout << hex << 3.4 << endl;
~~~
[3]使用using namespace std
~~~
#include<iostream>
#include<sstream>
#include<string>
using namespace std;
~~~
但是坑儿就再这里,从我们学习C++开始,很愿意写using namespace std,这样在实际工程中也许就会有所灾难。
看看下面的代码:
~~~
using namespace foo;
using namespace bar;
~~~
一切都有序进行,你可以从Foo中调用Blah,可以从Quux调用Bar。
但是有一天,你升级了Foo,在Foo中也声明了一个Quux函数,这样问题就来了。
也许是别人给你挖的坑儿,也许是你挖了坑在等别人。
所以千万不要使用using namespace std这样的语句在实际工程中,不能嫌麻烦,在调用的函数的时候,在前面冠以命名空间。
也许有的人对此感到不屑,但是对于一个优秀的程序员好习惯一定是必要的!!!
map查找结果处理
最后更新于:2022-04-01 06:40:45
##没有躲过的坑--map查找结果处理
通过键-值的方法进行搜索,可以使用map,极大的提高了速度。
下面代码就是使用map的find查找,通过键,找出对应的值。
~~~
map<std::string, int> string_int_map;
//对string_int_map进行初始化
map<std::string, int >::iterator iter_string_int;
string substring_to_find = ":who call who:";
iter_string_int = string_int_map.find(all_emoji_string[i]);
int result = iter_string_int ->second;
~~~
还是,上面的程序大多数情况下没问题,但是我没有考虑万一map中不存在我们查找的键呢?
再看了一下find函数的返回值:
**引用具有指定键的元素的位置的迭代器,如果找不到具有键的匹配项,则引用映射中 (map::end()) 最后一个元素后面的位置。**
如果你没考虑这个,那么当返回map::end()后:
~~~
iter_string_int ->second;
~~~
这个指向超尾的迭代器的second和first又是什么?
写个小程序测试一下:
~~~
#include<iostream>
#include<map>
int main()
{
std::map<std::string, int> test_map = {
{"how", 3},
{"are", 4},
{"you", 5}
};
std::map<std::string, int >::iterator iter_string_int;
std::string key_string = "shit";
iter_string_int = test_map.find(key_string);
std::cout << iter_string_int->second << std::endl;
return 0;
}
~~~
运行:
崩溃。
![这里写图片描述](http://img.blog.csdn.net/20151125224826360)
这是在控制台程序中,编译器会温柔的给你提示。
但是在一个大型的win32程序中,就不会这么轻易的给你提示了。
所以 我们要判断find函数的返回值:
find的返回值不等于map::end(),才可以往下进行操作。
~~~
#include<iostream>
#include<map>
int main()
{
std::map<std::string, int> test_map = {
{"how", 3},
{"are", 4},
{"you", 5}
};
std::map<std::string, int >::iterator iter_string_int;
std::string key_string = "shit";
iter_string_int = test_map.find(key_string);
if(iter_string_int == test_map::end())
{
std::cout << "error" << endl;
}
else
{
std::cout << iter_string_int->second << std::endl;
}
return 0;
}
~~~
[](http://blog.csdn.net/wangshubo1989/article/details/50043917#)[](http://blog.csdn.net/wangshubo1989/article/details/50043917# "分享到QQ空间")[](http://blog.csdn.net/wangshubo1989/article/details/50043917# "分享到新浪微博")[](http://blog.csdn.net/wangshubo1989/article/details/50043917# "分享到腾讯微博")[](http://blog.csdn.net/wangshubo1989/article/details/50043917# "分享到人人网")[](http://blog.csdn.net/wangshubo1989/article/details/50043917# "分享到微信")
有if就要有else(一定成对)
最后更新于:2022-04-01 06:40:43
##没有躲过的坑--有if就要有else(一定成对)
我们都很擅长流程控制,能写出很好很复杂的if语句。但是有时过于兴奋,更多的是过于自信而只写了if,而没有写else。
这是一个争论不休的话题,很多人,很多大师都认为有的情况下可以不写else,只写if。
支持 if 和 else 成对出现的人认为:不怕一万 就怕万一
认为 if 和 else 没必要成对出现的人认为:不便于理解,太多没用的东西。
我之前觉得也必须非要这样成对,但是今天在工程中遇到了问题。就是当if不成立的时候,没有相应的处理。
人非圣贤,硬件也不是永远精确的,所以我还是建议成对的使用if和else.
**还有呢,不管是if还是else,处理的代码一定要加{},即使处理只有一行代码。**
更重要的是:
**条件控制是编程中与生俱来的一种结构,但对于很多人来说,除了给带来麻烦外,没有发现任何的用处。一次又一次,不断发现,越少的if语句,越少的 switch语句,越少的循环,就会是越好的代码。通常这其中的原因是程序员用编程语言实现了更好的抽象归纳。他们并不是有意识的避免使用控制结构。但他 们确实做到了这些。**
**如果是使用一种面向对象编程语言,我们可以用多态(polymorphism)来代替switch。同样的技巧也能用在 if语句上,但如果逻辑太简单,这样做就有点得不偿失。当使用一种有函数式特征的编程语言时,大部分的循环执行任务我们都可以用 map,filter,fold等实现。控制结构最终从代码中消失,这是对代码大有好处的事。**
还有很长的路要走!!
类中的静态成员变量(static or const static)
最后更新于:2022-04-01 06:40:41
##没有躲过的坑--类中的静态成员变量(static or const static)
工作中是这样的,A类中有一个成员变量x,最开始声明为私有:
~~~
class A{
private:
int x;
};
~~~
现在需要在另一个cpp中使用这个x,所以要把它变为共有并且静态:
~~~
class A{
public:
static int x;
};
~~~
由此可见:
**静态数据成员和普通数据成员一样遵从public,protected,private访问规则。**
此时,编译,我们会得到一个错误,原因就是x没有被初始化。
**所以记得给静态成员变量初始化!**
好的,我们初始化:
~~~
class A{
public:
static int x = 0;
};
~~~
编译还是会产生错误,原因是这样:
静态成员属于类作用域,但不属于类对象,和普通的static变量一样,程序一运行就分配内存并初始化,生命周期和程序一致。
所以,**在类的构造函数里初始化static变量显然是不合理的。**
所以,我们需要在类外,对static成员变量进行初始化:
此时**要略去关键字static**:
~~~
class A{
public:
static int x ;
};
int A::X = 0;
~~~
**其实我们可以在类中对static成员变量初始化的,就是使用关键字const进行修饰**:
~~~
class A{
public:
const static int x = 0;// ok
};
~~~
这个时候不要纠结:
**static const 与const static是一样的,没有区别**
于是你突发奇想,那既然可以使用const就可以再类中对static变量进行初始化,那么我这么干:
~~~
class A{
public:
const static double x = 0;// error
};
~~~
抱歉,错误:
**只有静态整型常量才能在类中初始化**
那按照上面的逻辑,这样的代码会报错?
~~~
class A{
public:
const static char x = '1';// error or ok?
};
~~~
想多了,上面的代码可以正常运行,和解?
**char也是整型变量!!**
捕获窗口之外的鼠标消息(钩子还是??)
最后更新于:2022-04-01 06:40:39
## 没有躲过的坑--捕获窗口之外的鼠标消息(钩子还是??)
做一个发送表情的对话框,类似微信和QQ一样的,点击表情按钮,弹出表情框,鼠标点击表情框外时,这个表情框被kill。
说白了 就是在这个窗口上获得窗口外面的鼠标消息。
Google百度了一番,很多人说道使用钩子,才能获得窗口外面的click事件,这也未免太小题大做了吧。
还好发现了一个简简单单的函数:
**SetCapture(*this);**
函数功能:该函数在属于当前线程的指定窗口里设置鼠标捕获。一旦窗口捕获了鼠标,所有鼠标输入都针对该窗口,无论光标是否在窗口的边界内。同一时刻只能有一个窗口捕获鼠标。如果鼠标光标在另一个线程创建的窗口上,只有当鼠标键按下时系统才将鼠标输入指向指定的窗口。
所以在创建窗口的时候,使用这个函数,就使得这个窗口可以获得窗口之外的鼠标click消息:
~~~
int cx = 500;
int cy = 800;
SetWindowPos(*this, NULL, x_position_, y_position_, cx, cy, SWP_FRAMECHANGED);
SetCapture(*this);
~~~
接下来的工作就简单了,就是获得鼠标点击的位置,使用GetCursorPos()函数:
函数功能:该函数检取光标的位置,以屏幕坐标表示。
函数原型:BOOL GetCursorPos(LPPOINT lpPoint);
参数:
IpPoint:POINT结构指针,该结构接收光标的屏幕坐标。
这样就可以在这个窗口的消息队列中进行判断了:
~~~
POINT click_point;
GetCursorPos(&click_point);
if (x_position_ + 500 > click_point.x && click_point.x > x_position_
&& y_position_ + 800 > click_point.y
&& click_point.y> y_position_)
{
MessageBox(NULL, "表情", L"没有表情", NULL);
}
else
{
//delete 该窗口
}
~~~
正则表达式截取字符串
最后更新于:2022-04-01 06:40:36
##没有躲过的坑--正则表达式截取字符串
工程中,需要从字符串中匹配出以:开头,并以:结束的字符串。
Google还是百度,很多C++的正则表达式都是通过st::tr1或boost库中使用的,但是我们仅仅用一个小小的功能,就用一个库不是很好的办法。
对的,之前我的博客已经介绍了C++11的新特性-正则表达式。
所以可以不使用其他的库,来完成任务:
~~~
std::vector<string> all_sub_string = {};
std::string all_string = "12:wo:sfd:wom::sdf";
std::regex e(":[a-z0-9_+-]+:");//正则规则
const std::sregex_token_iterator end;
for (std::sregex_token_iterator i(all_string .begin(), all_string .end(), e); i != end; ++i)
{
all_sub_string .push_back(*i);
}
~~~
你可能会迷惑,什么是sregex_token_iterator?
不要着急,sregex_token_iterator其实就是字符串 regex_token_iterator 的类型定义。
~~~
typedef regex_token_iterator<string::const_iterator> sregex_token_iterator;
~~~
上面的方法很简单,就像使用迭代器一样。
其实regex还有其他的查找方法,现在介绍一下regex_search:
Returns whether some sub-sequence in the target sequence (the subject) matches the regular expression rgx (the pattern). The target sequence is either s or the character sequence between first and last, depending on the version used.
直接上代码:
~~~
// regex_search example
#include <iostream>
#include <string>
#include <regex>
int main ()
{
std::string s ("this subject has a submarine as a subsequence");
std::smatch m;
std::regex e ("\\b(sub)([^ ]*)"); // matches words beginning by "sub"
std::cout << "Target sequence: " << s << std::endl;
std::cout << "Regular expression: /\\b(sub)([^ ]*)/" << std::endl;
std::cout << "The following matches and submatches were found:" << std::endl;
while (std::regex_search (s,m,e)) {
for (auto x:m) std::cout << x << " ";
std::cout << std::endl;
s = m.suffix().str();
}
return 0;
}
~~~
输出:
~~~
Target sequence: this subject has a submarine as subsequence
Regular expression: /\b(sub)([^ ]*)/
The following matches and submatches were found:
subject sub ject
submarine sub marine
subsequence sub sequence
~~~
map的初始化(插入数据)
最后更新于:2022-04-01 06:40:34
##没有躲过的坑--map的初始化(插入数据)
最近工作中需要使用map,进行查询。
首先简单介绍一点map,也许是教科书里讲授最少的STL知识吧。但是在实际工作中map挺重要的,用于查找很方便快捷,尤其是以键和值的形式存在的!
**1、头文件**
~~~
#include<map>
~~~
**2、map的功能**
自动建立Key - value的对应。key 和 value可以是任意你需要的类型。
根据key值快速查找记录,查找的复杂度基本是Log(N),如果有1000个记录,最多查找10次,1,000,000个记录,最多查找20次。
快速插入Key - Value 记录。
快速删除记录
根据Key 修改value记录。
遍历所有记录。
**3、map的构造函数**
map共提供了6个构造函数,我们通常用如下方法构造一个map:
~~~
Map<int, string> mapStudent;
~~~
**4、插入数据**
(1)my_Map[“a”]=1;
改变map中的条目非常简单,因为map类已经对[]操作符进行了重载
enumMap[1] = “One”;
enumMap[2] = “Two”;
…..
这样非常直观,但存在一个性能的问题。插入2时,先在enumMap中查找主键为2的项,没发现,然后将一个新的对象插入enumMap,键是2,值是一个空字符串,插入完成后,将字符串赋为”Two”; 该方法会将每个值都赋为缺省值,然后再赋为显示的值,如果元素是类对象,则开销比较大。我们可以用以下方法来避免开销:
~~~
enumMap.insert(map<int, CString> :: value_type(2, "Two"))
my_Map.insert(map<string,int>::value_type("b",2));
my_Map.insert(pair<string,int>("c",3));
my_Map.insert(make_pair<string,int>("d",4));
~~~
起初我就是用上面愚蠢的方法进行了插入数据,但是我的数据量挺大的,需要近一千对,总不能一条一条的这样写吧。
于是想起了之前写的博客《[c++11特性之initializer_list](http://blog.csdn.net/wangshubo1989/article/details/49622871 "初始化列表")》,是不是可以使用初始化列表对map进行赋值呢,答案是肯定的。
~~~
std::map<int, string> int_to_string = {
{1, "what"},
{2, "a"},
{3, "fuck"},
{4, "day"},
....
~~~
意想不到的除数为零
最后更新于:2022-04-01 06:40:32
##没有躲过的坑--意想不到的除数为零
工程中有这样一个需求,需要获得一张图片的width和height,然后等比例的显示这张图片。
首先是获得得到一张图片的路径,然后计算出他的width和height,然后计算:
~~~
int resize_width = 160;
int resize_height = 160;
if (image_width > image_height) {
resize_width = 160;
resize_height = 160 * image_height / image_width;
}
else {
resize_height = 160;
resize_width = 160 * image_width / image_height;
}
~~~
很多情况下 ,我们都可以如愿以偿。
但是情况总有特俗,这就看你是如何计算一张图片的width和height的了,之前Google过一个方法只能计算24k大小以上的图片。
当图片过大或是过小,我们使用的方法无法计算这张图片的width和height时,情况出现了。
我们从小就知道除法中除数不能为零,但是在编程过程中往往忽略这一点。加之测试不够,往往会挖下一个很大的坑儿。
因此修改上面的代码:
~~~
int resize_width = 0;
int resize_height = 0;
if (image_width == 0 || image_height == 0)
{
resize_width = 160;
resize_height = 160;
}
else
{
if (image_width > image_height) {
resize_width = 160;
resize_height = 160 * image_height / image_width;
}
else {
resize_height = 160;
resize_width = 160 * image_width / image_height;
}
}
~~~
对width和height是否为零进行判断。
说道除法,那就继续写点。
C++中的大多数二元操作都要求两个操作数是同一类型。如果操作数的不同类型,其中一个操作数会提升到和另一个操作数相匹配的类型。在C++中,除法操作符可以被看做是2个不同的操作:其中一个操作于整数之上,另一个是操作于浮点数之上。如果操作数是浮点数类型,除法操作将返回一个浮点数的值:
~~~
float fX = 7;
float fY = 2;
float fValue = fX / fY;
// fValue = 3.5
~~~
如果操作数是整数类型,除法操作将丢弃任何小数部分,并只返回整数部分。
~~~
int nX = 7;
int nY = 2;
int nValue = nX / nY;
// nValue = 3
~~~
如果一个操作数是整型,另一个操作数是浮点型,则整型会提升为浮点型:
~~~
float fX = 7.0;
int nY = 2;
float fValue = fX / nY;
// nY 提升为浮点型,除法操作将返回浮点型值
// fValue = 3.5
~~~
有很多人会想当然:
~~~
int nX = 7;
int nY = 2;
float fValue = nX / nY;
// fValue = 3(不是3.5哦!)
~~~
这里的本意是nX/nY将产生一个浮点型的除法操作,因为结果是赋给一个浮点型变量的。但实际上并非如此。nX/nY首先被计算,结果是一个整型值,然后才会提升为浮点型并赋值给fValue。但在赋值之前,小数部分就已经丢弃了。
要强制两个整数采用浮点型除法,其中一个操作数需要类型转换为浮点数:
~~~
int nX = 7;
int nY = 2;
float fValue = static_cast<float>(nX) / nY;
// fValue = 3.5
~~~
因为nX显式的转换为float型,nY将隐式地提升为float型,因此除法操作符将执行浮点型除法,得到的结果就是3.5。
有关整数除法的另一个有趣的事情是,当一个操作数是负数时C++标准并未规定如何截断结果。造成的结果就是,编译器可以自由地选择向上截断或者向下截断!比如,-5/2可以既可以计算为-3也可以计算为-2,这和编译器是向下取整还是向0取整有关。大多数现代的编译器是向0取整的。
成对使用new和delete时要采取相同的形式
最后更新于:2022-04-01 06:40:30
##没有躲过的坑--成对使用new和delete时要采取相同的形式
new创建类对象与不new区别:
new创建类对象需要指针接收,一处初始化,多处使用
new创建类对象使用完需delete销毁
new创建对象直接使用堆空间,而局部不用new定义类对象则使用栈空间
new对象指针用途广泛,比如作为函数返回值、函数参数等
而且每个学习C++编程的人都知道成对的使用new和delete,也也就是new申请的内存用delete释放,new []申请的内存由delete []释放。
~~~
std::string* first_string = new std::string;
std::string* second_string = new std::string[100];
delete first_string;
delete [] second_string;
~~~
这一规则,在实际工作中也应该牢记于心。但是我还是没有躲过这个坑儿。
看看情况吧:
首先使用了typedef进行一些定义;
~~~
typedef std::string address_lines[4];
std::string *third_string = new address_lines;
delete third_string
~~~
这个就是遇到的坑儿,被表面所迷惑。
所以在Effectice C++中 item16就讲到了:
最好尽量不要对数组形式做typedef动作。
程序中的变量未初始化
最后更新于:2022-04-01 06:40:27
##没有躲过的坑--程序中的变量未初始化
变量未初始化是C++编程中最为常见和易犯的错误之一。
但是对于全局变量,我们可以不进行初始化,这个变量会默认的用零进行初始化,但是这通常不是一个好的习惯。我们今天先不探讨全局变量还是静态全局变量。那么对于一个全部变量来说,初始化为零和不初始化(编译器替我们初始化为零)又有什么区别吗?
**全局变量和局部变量初始化与不初始化的区别**
即int x 和 int x=0的区别。
int x =0; 跟 int x; 的效果看起来是一样的。但其实这里面的差别很大,强烈建议大家所有的全局变量都要初始化,他们的主要差别如下:
编译器在编译的时候针对这两种情况会产生两种符号放在目标文件的符号表中,对于初始化的,叫强符号,未初始化的,叫弱符号。连接器在连接目标文件的时候,如果遇到两个重名符号,会有以下处理规
则:
1、如果有多个重名的强符号,则报错。
2、如果有一个强符号,多个弱符号,则以强符号为准。
3、如果没有强符号,但有多个重名的弱符号,则任选一个弱符号。
例:
~~~
#include "stdafx.h"
int i;
int main(int argc, char* argv[])
{
printf(" i = %d\n",i);
int j;
printf(" j= %d\n",j);
return 0;
}
~~~
在Debug版下,i输出是0,j输出是-858993460,也就是0xCCCCCCCC。
至于为什么是这个值,有网友给出这个解释。(设计成0xcccccccc是有特殊用意的……这个好像叫做Poison,未初始化的Pointer去取值的话会出错。肯定有人问为什么不弄成0x00000000,因为空指针是指针的有效状态,可能会误导人,而0xCCCCCCCC在Windows下永远不可能是一个指针的有效状态(不是NULL,不指向一个对象,不指向一堆对象紧接之后的区域),这就是在模拟野指针……)
值得注意的是,同样的代码在Release版下,这段代码中未被初始化的变量最后打印出来的可能都是0。也有强大的网友给出解释。(重点在于vc的一个功能:Catch release-build errors in debug build用/GZ编译开关打开。debug版这个开关是开的,release版是关的(为了效率)。这个开关说白了就是把所有动态局部变量初始化成0xcccccccc,把所有动态堆变量初始化成0xcdcdcdcd。很多新手会忘记初始化这些本来应该初始化的变量(尤其是new出来的变量),有时他们会假定这些变量应该是0,这样就可能出现在release版正常而debug版不正常的程序,因为release版至少局部变量的初始值很可能就是0,而有时他们又会假定或者期望这些变量不是0,这样就带了一个最难发现的bug)
上面的内容出自:
[http://www.kingofcoders.com/viewNews.php?type=newsCpp&id=189&number=4836955386](http://www.kingofcoders.com/viewNews.php?type=newsCpp&id=189&number=4836955386)
================================================
在C++中,为变量所分配的内存空间并不是完全“干净的”,也不会在分配空间时自动做清零处理。其结果就是,一个未初始化的变量将包含某个值,但没办法准确地知道这个值是多少。此外,每次执行这个程序的时候,该变量的值可能都会发生改变。这就有可能产生间歇性发作的问题,是特别难以追踪的。看看如下的代码片段:
~~~
if (num_value)
{}
else
{}
~~~
如果num_value是未经初始化的变量,那么if语句的判断结果就无法确定,两个分支都可能会执行。在一般情况下,编译器会对未初始化的变量给予提示。下面的代码片段在大多数编译器上都会引发一个警告信息。
~~~
int foo()
{
int number;
return number;
}
~~~
但是,还有一些简单的例子则不会产生警告:
~~~
void increment(int &num_value)
{
++num_value;
}
int foo()
{
int number;
increment(number);
return number;
}
~~~
以上的代码片段可能不会产生一个警告,因为编译器一般不会去跟踪查看函数increment()到底有没有对nValue赋值。
未初始化变量更常出现于类中,成员的初始化一般是通过构造函数的实现来完成的。
~~~
class Foo
{
private:
int num_value_;
public:
Foo();
int GetValue() { return num_value_; }
};
Foo::Foo()
{
// Oops, 我们忘记初始化num_value_了
}
int main()
{
Foo cFoo;
if (cFoo.GetValue() > 0)
// do something
else
// do something else
}
~~~
注意,num_value_从未初始化过。结果就是,GetValue()返回的是一个垃圾值,if语句的两个分支都有可能会执行。
==================================================
新手程序员通常在定义多个变量时会犯下面这种错误:
~~~
int nValue1, nValue2 = 5;
~~~
这里的本意是nValue1和nValue2都被初始化为5,但实际上只有nValue2被初始化了,nValue1从未被初始化过。
**切记 无论什么变量,记得初始化!!!!**
重载赋值运算符的自我赋值
最后更新于:2022-04-01 06:40:25
##没有躲过的坑--重载赋值运算符的自我赋值
C++中有个很重要的事情,就是对于类重载赋值运算符,而达到我们想要的结果。
先看看这几行代码:
~~~
//Window 是一个类
Window w;
w = w; // 再傻的人也不会这么干
w[i] = w[j]; // 这个情况偶尔会发生
~~~
作为一个优秀的工程师,就要考虑到任何可能的情况。
看一段更加完整的代码:
~~~
class ScrollBar {};
class Window
{
ScrollBar *sb;
public:
Window(ScrollBar *s) : sb(s) {}
Window() = default;
Window& operator=(const Window&);
};
Window& Window::operator=(const Window& rhs)
{
delete sb;
sb = new ScrollBar(*rhs.sb);
return *this;
}
int main()
{
Window w(new ScrollBar);
Window w2(w);
}
~~~
**这段代码到底有什么坑儿呢?**
设想一下,如果 *this 和rhs 是同一个实例对象呢?
那么
~~~
delete sb;
sb = new ScrollBar(*rhs.sb);
~~~
就会造成严重的问题。
再delete sb后, 我们试图去访问一个已经被删除的rhs。这当然是致命的坑儿了。
跨越这个坑儿:
~~~
if (this == &rhs)
return *this;
delete sb;
sb = new ScrollBar(*rhs.sb);
return *this;
~~~
上面这段代码几乎所有的教科书都会这么讲,但是曾经一个arcserver的工程师跟我讲,这样同样存在危险,不是完美的:
试想一下,如果我们delete sb后发生了异常怎么办?这个时候,就会有存在一个没有指向任何东西的指针。所以下面这样写会更好:
~~~
Window& Window::operator=(const Window& rhs)
{
if (this == &rhs)
return *this;
ScrollBar *sbOld = sb;
sb = new ScrollBar(*rhs.sb);
delete sbOld;
return *this;
}
~~~
抽象类不能实例化对象(但是你明明定义的不是抽象类)
最后更新于:2022-04-01 06:40:23
##没有躲过的坑--抽象类不能实例化对象(但是你明明定义的不是抽象类)
今天在开发过程中遇到了一个错误:不能实例化抽象类
这个错误完全是自己疏忽大意造成的。
首先我们知道,一个类如果是抽象类,我们就不能用这个类实例化一个对象。
什么样的类是抽象类呢?
C++没有C Sharp中的关键字abstract,但是当一个类中有一个或多个纯虚函数的时候,这个类就自动成为了抽象类,即不可以实例化。
纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
在实际开发中,很多时候你都需要继承别人的类,或是同事写的类,或是一些开源库中的类。
也许是为了追求效率,我们很难认真阅读你要继承的基类,或者说你只关心基类中你需要的几个方法。
这个时候,就存在错误的风险。也许父类中有一个纯虚函数你完全不关系,用不到,你就会大摇大摆的忽略它。
但是它不会忽略你,记住:
**如果你的抽象类的派生类有任何一个纯虚函数没有实现,那么不管其他做的再多,这个类仍然是一个抽象类。**
所以用到你定义的子类实例化对象时候,会产生上述的错误。
好在编译器能够帮助我们,以至于可以及时改正。
所以,在继承一个类的时候,要确保实现了所有这个基类的纯虚函数。
~~~
//基类:
classA
{
public:
A();
virtual ~A();
void f1();
virtual void f2();
virtual void f3()=0;
};
//子类:
classB:publicA
{
public:
B();
virtual ~B();
void f1();
virtual void f2();
virtual void f3();
};
~~~