C++一些细节问题记录

来源:互联网 发布:eve数据地点 编辑:程序博客网 时间:2024/06/05 22:46

语法

一些库

ATL

图形图像相关

1. CImage

使用CImage需#include <atlimage.h>,使用语法参考GDI 总结三: CImage类使用、MSDN关于CImage class的说明,CImage和cv::Mat相互转换参考OPENCV的Mat和ATL/MFC的CImage相互转换。

std

数据类型

vector

1.以下程序运行完之后,aV结果并非想要的:

vector<int> iVec;vector<int> aV;for (int i = 0; i<10; i++)iVec.push_back(i);for (int i = 0; i < iVec.size(); i++)//即使这里iVec.size改成常量10也不行{    if ( (iVec.at(i)%2) ==0 )    {        iVec.erase(iVec.begin() + i);        continue;    }    aV.push_back(i);}

2.vector赋值方法汇总
可以通过数组赋值:

int myarray[5] = {1,3,5,7,9};  vector<int> myvector(myarray , myarray+5);

3.vector、map判断元素是否存在,查找指定元素
可用std::count计算出现的个数、使用std::find函数等。

特别注意

字符转换

_ttoi/_tcstoul/_ttof()/_tcstod()把CString转换成其他(参考获取EditCtrl中数字的方法)
(C++字符串完全指引)
(CString,string,char*之间的转换)
atoi
CString→char*:(LPSTR)(LPCTSTR)cstring
CString→string:std::string str(UnicodeToANSI(CString.GetBuffer()));
char*/int→CString nn; nn.Format(_T(“%d”), i);

std::string CMeasDlg::UnicodeToANSI( const wstring& str ){    char*  pElementText;    int    iTextLen;    // wide char to multi char    iTextLen = WideCharToMultiByte( CP_ACP, 0,  str.c_str(),-1, NULL,   0,  NULL,NULL );    pElementText = new char[iTextLen + 1];    memset( ( void* )pElementText, 0, sizeof( char ) * ( iTextLen + 1 ) );    ::WideCharToMultiByte( CP_ACP,  0,  str.c_str(),-1, pElementText,   iTextLen,NULL,NULL );    string strText;    strText = pElementText;    delete[] pElementText;    return strText;}

文件操作

1. C/C++文件剪切复制删除

复制文件:CopyFileA("C:\\1.txt", "F:\\1.txt", false),函数原型:

BOOL WINAPI CopyFile(  //成功则返回非0数,失败返回0,并且调用GetLastError()可以获取错误信息  _In_ LPCTSTR lpExistingFileName,  //一个存在文件的名字  _In_ LPCTSTR lpNewFileName,  //新文件的名字  _In_ BOOL    bFailIfExists  //如果有同名的文件true则不进行复制,false为覆盖);  

删除文件:DeleteFileA("C:\\1.txt")
剪切文件:MoveFileA("C:\\1.txt", "F:\\1.txt")

2. 检查文件是否存在

使用_access(const char*, int mode)

CString filename = ...;int res = _access( (LPSTR)(LPCTSTR)filename, 0);//参数0就用于检测文件是否存在

其他模式说明参考C中的access函数– 判断文件是否存在及可读写等属性。

3. CFile打开文件后写入时,显示类似无权限的错误

可能是因为mode不正确,例如

CFile f; //20170919,发现有段程序利用CFile指针报错f.Open(_T("t.txt"), CFile::modeCreate); //如果t.txt不存在就新建,如果存在擦除

之后向f中写入内容时,出现权限错误,应该改为:

f.Open(_T("t.txt"), CFile::modeCreate | CFile::modeWrite);

4. fstream出错

查看是否包含头文件#include和使用std::命名空间限定。(fstream详细信息参考链接)
另外使用fstream可以较好写入、写出数据
用fstream读取按行读取getline,并把每行赋值给不同的变量(参考链接)

5. FILE

FILE* fs;fopen_s(&fs, "D:\\circle.txt","a+");fprintf(fs,"\n");for (int i=0;i<lengths.size();++i){    fprintf(fs,"%d;  ",lengths[i]);}fclose(fs);

内存管理

new手动申请的内存应该手动释放,但是如下如果不设置申请内存大小(删除”[10]”),而后delete释放这块内存时可能删除过多(应该是找到第一个’\0’之前的内存都释放掉)导致意外错误

char* name = new char[10];...delete name

动态库

动态链接库(参考深入理解动态库)的重要性不言而喻。为了实现混合编程,(动态库和具体的编程语言无关,只要种种语言支持动态库技术)动态库输出函数有两种约定:调用约定和名字修饰约定。

1. 调用约定(调用方式):主要有__stdcall调用约定、__cdecl调用约定、__fastcall等。

__stdcall:standard call,参数由右向左压入堆栈;堆栈由函数本身清理;C++的标准调用方式,如果是调用类成员的话,最后一个入栈的是this指针;函数在编译的时候就必须确定参数个数,不能多或少,否则返回后会出错。这些堆栈中的参数由被调用的函数在返回后清除,使用的指令是 retn X,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间,称为自动清栈。
__cdecl:C declaration,参数由右向左压入堆栈;堆栈由调用者清理;C语言默认的函数调用方式;调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。
__fastcall调用约定:它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈)。

以上可参考关于__stdcall和__cdecl调用方式的理解中的汇编代码示例进行理解

C中不加说明默认函数为__cdecl方式(C中也只能用这种方式),C++也一样,但是默认的调用方式可以在IDE环境中设置。带有可变参数的函数(是指参数个数可变,可通过int sum(int count, …)这种省略点的方法或其他方法定义,具体参考CSDN博客C++可变参数函数)必须且只能使用__cdecl方式。

设置方法:①直接在代码中添加__cdecl 等调用约定;②在IDE中设置,如下图:
这里写图片描述
其他参考资料包括:百度百科__cdecl。

2. 名字修饰约定:变量名、函数名等经过编译后重新输出名称的规则。函数名修饰约定随编译种类和调用约定的不同而不同。具体参考CSDN博客关于__stdcall和__cdecl调用方式的理解和名字修饰约定extern “C”与extern “C++”浅析。
编译C文件和编译CPP文件,不需加extern “C”和extern “C++”,因为编译C文件当然默认的是extern “C”,而编译CPP文件则默认的是extern “C++”。这样解释了为什么导出DLL时通常需要加上extern “C”。试想,如果一个C++导出的dll,没有加extern “C”,则导出的名称为extern “C++”约定下的名称。如果这个dll需要提供给用C编写的程序使用,那么这个程序是无法调用这个dll的,因为C写的程序遵循的是extern “C”约定,链接时链接器将按照extern “C”约定的名称去寻找外部名称,这当然找不到。

特殊功能要求

修改MessageBox默认的按钮文本

参考MessageBox参数说明及如何修改其按钮文字,但是总是不能成功修改,应该在使用MessageBox之前添加

    hHook = SetWindowsHookEx(WH_CBT, (HOOKPROC)CBTHookProc, AfxGetInstanceHandle(), GetCurrentThreadId());

设置桌面背景

http://blog.csdn.net/zy_dreamer/article/details/8877857?utm_source=tuicool,重启后桌面好像恢复了~不知道是否因为图片删除或是非正常关机???必须要设计绝对路径,例如以下设置:

    CString cName = _T("……\\image.bmp");    char tmp[1024];    ::GetModuleFileNameA(NULL, tmp, sizeof(tmp));    CString cCurrentDir = (CString)tmp;    int ind = cCurrentDir.ReverseFind('\\');    cCurrentDir = cCurrentDir.Left(ind);    CString cFullName;    cFullName = cCurrentDir + L"\\" + cName;    SetDesktopWallpaper(cFullName.GetBuffer(), WallpaperStyle::Stretch);

程序保护

在程序中检查计算机MAC地址可以对程序保护,使其在其它计算上不能运行???

对象构造时的异常处理

在《程序员面试宝典》P132看到一道题目:

In C++, you should NOT throw exceptions from:
A. Constructor B. Deconstructor C. Virtual function D.None of the above

答案是B。不久后就遇到了类似应用(构造函数中打开一个传感器,若打开失败退出构造函数,用return是不行的因为构造函数没有返回值,应该抛出异常)……
1.C++中通知对象构造失败的唯一方法就是在构造函数中抛出异常;

class Base{    Base(){        int* p = new int();        try{            int* q = new int();//假如失败            //throw 2... //如果直接抛出异常,构造函数失败,不执行析构函数        }        catch(...)//catch(int e)        {            delete p;            throw;        }    }}

2.对象的部分是常见的,异常的发生点也是随机的,要谨慎处理这种情况。构造函数失败,对象本身不执行析构函数,要防止内存泄露。
3.当对象发生部分构造时,已构造完成的基类对象和数据成员会按照构造逆序逐个进行析构;异常发生点后面的对象不会被构造;正在构建的子对象和对象自己本身将停止构建,且它的析构不会被执行。
语法上,构造函数和析构函数都可以抛出异常(参考C++构造函数和析构函数中抛出异常的注意事项)。但从逻辑上和风险控制上,构造函数和析构函数中尽量不要抛出异常,万不得已,一定要注意防止资源泄露。在析构函数中抛出异常还要注意栈展开带来的程序崩溃。Effective C++建议析构函数尽可能不要抛出异常。

异常处理语法不熟悉!!!!

使用ATL::CImage加载并显示图片

CImage使用参考MSDN关于CImage class说明,同时为了解决图像失真问题,参考解决使用CImage类的Draw函数在显示图片时引起的图像失真问题、MSDN关于SetStretchBltMode的说明

CString filename = ...;ATL::CImage m_0Img;//m_0Img.Destroy();if (S_OK!=m_0Img.Load(filename)){    AfxMessageBox(_T("图像加载出错"));}CDC* pDC = GetDlgItem(IDC_SHOW_PICTURE)->GetDC();HDC hDC = pDC->GetSafeHdc();CRect rect;GetDlgItem(IDC_SHOW_PICTURE)->GetClientRect(&rect);pDC->SetStretchBltMode(COLORONCOLOR);//防止失真m_0Img.Draw(hDC, rect);ReleaseDC(pDC);

鼠标绘图

1. 动态绘制四边形

关键添加左键down、up和鼠标move事件响应,其他关键函数如下:

DC.SetROP2(R2_NOT);//逆转当前屏幕颜色来画线的绘图方式m_HCross = AfxGetApp()->LoadStandardCursor(IDC_CROSS);SetCursor(m_HCross);//设置鼠标样式SetCapture();//捕获鼠标,充分控制鼠标,这一步好像不必须,好处暂为体现ClientToScreen(&Rect);//转换为屏幕坐标ClipCursor(&Rect);//限定光标在指定矩形m_PictureControl.MapWindowPoints((CWnd *)this, re);////鼠标移动时实时绘制直线,但是要擦除上一步的直线

参考MFC 怎么动态的画线 画当前的线是清除上一次画的线。以下演示程序地址:C++ MFC四边形圈图测试。
这里写图片描述


IDE

项目设置

$(OutDir)宏

找不到’resource.h’

在属性页中“configuration properties”→“Resources”→“General”→“Additional include directories”中添加resource.h文件路径。

找不到“XXX.pch”

PreCompiled Header = pch(参考解决 Cannot open precompiled header file: ‘Debug/**.pch’),生成这个pch文件与stdafx.h和stdafx.cpp文件有关;在做大的C++工程时,使用pch预编译头可提高编译速度。
这个问题的解决方法是:

右键stdafx.cpp→Properties→C/C++→Precompiled Header→Create Compiled Header(/Yc)
其他的cpp按照上面的方法设置成使用编译头(/Yu)
重新生成项目,就能使用预编译头了

调试技巧

DLL错误

程序编译链接没有问题,但是运行时出现类似找不到DLL入口的错误,可能是因为编译环境里设置好了DLL链接库的路径,但是这些路径没有添加到系统环境变量中,所以在运行exe时找不到,可以把dll直接复制到exe目录下

程序退出时崩溃

析构时,发生越界访问、重复删除等。例如new char没有设置内存大小,删除时可能释放过多导致出错

对于程序崩溃,CSDN用户赵4老师在给别人的回复中多次提到:

崩溃的时候在弹出的对话框按相应按钮进入调试,按Alt+7键查看Call Stack里面(VS中”调用堆栈”窗口)从上到下列出的对应从里层到外层的函数调用历史。双击某一行可将光标定位到此次调用的源代码或汇编指令处。

还指出利用AppCrashView工具查看程序崩溃情况。对于多线程调试,有时不能解决的问题,可以写入日志(参考VS 2010的调用堆栈怎么看)。(不太理解,需要多多实践!)

利用反汇编分析crash原因示例如:反汇编程序导致程序crash的解决思路。

插件、功能包

可视化和建模功能包

VS2010 ultimate 旗舰版菜单中有Architecture→Generate Dependency Graph选项,但是对于C++功能建图时显示错误,这是因为You must install a Visual Studio 2010 Feature Pack to use this feature.
通过可视化和建模功能包,可以扩展 Visual Studio 2010的功能,可以从 UML 类关系图生成代码、从代码创建 UML 类关系图、创建和验证 C 和 C++ 代码的层关系图、编写自定义代码以创建、修改和验证层关系图等。参见官网功能包介绍和使用方法,VS2010版本功能包下载链接。

MFC

MFC的Toolbox中没有控件

There are no usable controls in this group,可以右键Reset Toolbox。
Spin control和Edit control自动联合方法**:
- Fromat→Tab order(Ctrl+D)设置spin和edit相邻,且edit在前;
- spin设置属性:Auto Buddy→true,Set Buddy Integer→true,Alignment→Right align使spin在edit的右边并对齐(可选),Wrap→true设置spin的取值范围后可循环改变值(可选);
- 以上自动buddy后,发现按上下键(或者滚轮上下)和edit的值改变方向相反,在通过CSpinButtonCtrl::SetRange后改变方向相同;
- spin在程序中通过GetPos和SetPos获取值和设置值

Edit控件设置只输入数字

可通过重写窗口类的PreTranslateMessage虚函数:

BOOL CMFCApplication1Dlg::PreTranslateMessage(MSG* pMsg){    CEdit* m_edit2 = (CEdit*)GetDlgItem(IDC_EDIT2);//需要设置Edit控件ID    if ((GetFocus() == m_edit2) && (pMsg->message == WM_CHAR))    {        if (pMsg->wParam<'9' && pMsg->wParam>='0' || pMsg->wParam == '-'             || pMsg->wParam == '.' || pMsg->wParam == 8/*backspace*/)        {            return 0;        }        else            return 1;    }    return CDialogEx::PreTranslateMessage(pMsg);}

EDIT获取内容

应该使用GetDlgItemText,而非GetEWindowText(获取的是窗口文本,如对话框标题)。
EDIT获取数字(参考 获取EditCtrl中数字的方法)可以有多种方法:
- 先用GetDlgItemText获取文本,在用atoi、atod等转换成数字(vs2013中可用_tcstof把CString转换成float,vs2010中没有该函数??)
- 如果空间中是整型,可直接利用GetDlgItemInt获取

从项目资源rc文件中加载字符串

CString::LoadString(ID_STRING);

发送接收消息

①定义消息#define WM_MYMESSAGE WM_USER+100
②声明和定义相应函数
afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
③消息映射

BEGIN_MESSAGE_MAP(CMyClass, CDialogEx)      ON_MESSAGE(WM_MYMESSAGE,&CLightSensor::OnMyMessage)END_MESSAGE_MAP()

④发出消息
PostMessage(WM_MYMESSAGE,WPARAM(0),LPARAM(0));
在MFC中PostMessage有三个参数,Windows API中该函数有四个参数(参考PostMessage到底是几个参数?)

MFC工程中利用控制台输出信息

项目属性→Configure properties→Build Events→Post-Build Event的Command line中输入:

editbin /SUBSYSTEM:CONSOLE "$(SolutionDir)\$(TargetName).exe"

其中$(SolutionDir)是exe文件路径,可能根据实际情况有所不同。路径和文件名最好加上双引号,以防路径中存在空格造成命令行运行错误。
其他方法可参考MFC如何在有界面的应用程序中开启控制台窗口。
如何取消显示控制台呢?

原创粉丝点击