VC2010中初学者常见错误、警告和问题

入门教程》系列四: 中初学者常见错误、 《Visual C++ 2010 入门教程》系列四:VC2010 中初学者常见错误、警 告和问题
这一章将帮助大家解释一些常见的错误、警告和问题,帮助大家去理解和解决一些 常见问题,并了解它的根本原因。 iostream.h 与<iostream> 下面的代码为什么在 VC2010 下面编译不过去? #include <iostream.h> int main() { cout<<"Hello World."<<endl; return 0; } 错误信息: fatal error C1083: 无法打开包括文件:“iostream.h”: No such file or directory 造成这个错误的原因在于历史原因,在过去 C++98 标准尚未订立的时候,C++的 标准输入输出流确实是定义在这个文件里面的,这是 C 风格的定义方法,随着 C++98 标 准的确定,iostream.h 已经被取消,至少在 VC2010 下面是这样的,取而代之的是我们 要用<iostream>头文件来代替,你甚至可以认为<iostream>是这样定义的: namespace std { #include "iostream.h" } 因此我们可以简单的修改我们的 Hello World。 #include <iostream> using namespace std;

int main() { cout<<"Hello World."<<endl; return 0; } iostream.h 是属于 C++的头文件,而非 C 的,因此标准订立的时候被改成了 <iostream>。而 C 的头文件 stdio.h 等依然可以继续使用,这是为了兼容 C 代码。但是 它们依然有对应的 C++版本,如<cstdio> <cstdlib>等。记住,在 VC2010 上面采用 C++风格的头文件而不是 C 风格的头文件,除非你是在用 C。 warning C4996 这是一个警告,请看下面的代码: #include <iostream> using namespace std; int main() { char sz[128] = {0}; strcpy( sz, "Hello World!" ); cout<< sz << endl; return 0; } 上面的 strcpy 会产生这个警告: warning C4996: 'strcpy': This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details. 这是因为 VC 从 2005 版本开始, 微软引入了一系列的安全加强的函数来增强 CRT (C 运行时) 这里对应的是 strcpy_s。 意为 safe 的意思, , _s 同样的道理, strcat 也是同样。 因此要解决这个问题,我们可以用 strcpy_s 来替换 strcpy,但是注意 strcpy_s 并非所有

编译器都提供,因此如果要跨编译器,请采用错误信息中所提示的方式,定义 _CRT_SECURE_NO_WARNINGS 宏来掩耳盗铃吧。另外注意并非所有的加强函数都是 在屁股后面加_s,比如 stricmp 这个字符串比较函数的增强版名字是_stricmp。下面,用 strcpy_s 来更改程序: int main() { char sz[128] = {0}; strcpy_s( sz, "Hello World!" ); cout<< sz << endl; char* pSz2 = new char[128]; strcpy_s( pSz2, 128, "hello"); cout<< pSz2 << endl; delete pSz2; return 0; } 注意,strcpy_s 有两个版本,一个可以帮助我们自动推断缓冲区的大小,而另外一 个不能帮助我们推断, 因此在编译器不能推断缓冲区大小的时候, 我们需要自己指定缓冲区 的大小,如上面的程序所演示的那样,关于增强版的函数请参考我写的《深入学习 C++ String2.1 版》。 TCHAR、wchar_t、char 请大家看下面这个程序: #include <iostream> #include <Windows.h> #include <tchar.h> using namespace std;

int main() { MessageBox( NULL, "你好 HelloWorld!", "Information", 0 ); return 0; } 貌似没什么问题吧?错了,如果你是按照我教你的方法创建的控制台空工程的话,那 么会有编译错误: error C2664: “MessageBoxW”: 不能将参数 2 从“const char [17]” 转换为“LPCWSTR” 这个问题太普遍了,几乎所有的初学者都会遇到而且感到难以应付,因为按照提示使 用(LPCWSTR)强制转型貌似并不能帮助我们解决问题,而且这个程序在 VC6 下面应该是 没有任何问题的, 那问题出现在哪里呢?问题在这里, 请右键单击解决方案浏览器下面的项 目,属性,

问题的根本就是字符集问题,在 VC6 中,我们默认使用的是多字节字符集 问题的根本就是字符集问题 我们默认使用的是多字节字符集,而现在我 们默认需要的是 UNICODE 字符集,简单的,我们把这个字符集改成多字节字符集这个问 我们把这个字符集改成多字节字符集这个问 题就解决了:

再试试应该就可以了吧?但是我并不推荐大家这么做, 再试试应该就可以了吧 因为让自己的程序适应各种 字符集是我们写代码的人义不容辞的义务。 字符集是我们写代码的人义不容辞的义务 我们把程序改成下面这样: 我们把程序改成下面这样

#include <iostream> #include <Windows.h> #include <tchar.h> using namespace std; int main() { MessageBox( NULL, TEXT("你好 HelloWorld!"), TEXT("Information"), 0 ); MessageBox( NULL, _T("你好 HelloWorld!"), _T("Information"), 0 ); return 0; } 用两个宏 TEXT 或者_T 都可以解决这个问题,它们两个并没有太大区别,也许区别在 于前者是通过 windows.h 头文件引入的,而_T 是通过 tchar.h 引入的,我推荐大家使用 _T 和 tchar.h,因为 tchar.h 还帮助我们引入了其它一些很有用的宏,比如_tcscpy_s, 这个宏在使用 UNICODE 字符集的时候被替换成 wcscpy_s,在使用多字节字符集的使用 被替换成 strcpy_s。关于这部分的内容,请大家不要错过《Windows 核心编程》的第二 章(第四版或第五版都可以),以及《深入学习 C++ String2.1 版》。 它们都有提到。 有人听说_T 可以把多字节字符串转换成 UNICODE,因此他写了如下的代码: const char* pStr = "haha 哈哈"; MessageBox( NULL, _T(pStr), _T("Information"), 0 ); 当然,除非你运气好的抓狂,否则你是编译不过去的,为什么呢?我们现在应该知道对 于"Hello"这样的字符串,VC2010 会默认的将它视为 const char*,即多字节字符串,而 L"Hello"前面有个 L 前缀的被视为 UNICODE 字符串,这和 C#是有区别的,因为 C#的字 符串总是被视为 UNICODE,C++/CLI 下面编译器也会帮助我们做到这件事情,所以它们 不需要 L(C++/CLI 兼容 L 这种写法)。 让我们看看_T 的定义吧: #define wxCONCAT_HELPER(text, line) text ## line

/* could already be defined by tchar.h (it's quasi standard) */ #ifndef _T #if !wxUSE_UNICODE #define _T(x) x #else /* Unicode */ /* use wxCONCAT_HELPER so that x could be expanded if it's a macro */ #define _T(x) wxCONCAT_HELPER(L, x) #endif /* ASCII/Unicode */ #endif /* !defined(_T) */ _T 在 UNICODE 下面最终会被替换成 L ## x。 ##是一个编译预处理指令,意味着 让 L 和 x 贴在一起,比如 L ## "Hello"最终就是 L"Hello",因此它可以把"Hello"转换成 UNICODE 字符串。那为什么上面的程序不行呢?让我们看看_T("pStr")会被替换成什么: L ## pStr -> LpStr,哦,LpStr 是一个新的标识符,如果你没有定义过它,你当然不能 通过编译啦。 因此我们可以了解到_T 这样的宏只能处理直接的常量字符串,不能处理其它的情况。 而我们上面演示的那种情况需要我们动态的去转换编码, Windows 有 API 可以帮助我们做 到,C 库也有函数可以帮助我们。恰好我曾经写过这样的代码,欢迎大家参考: ASCII/UNICODE/UTF8 字符串互相转换的 C++代码 对于_T 宏,再说一点东西,或许你会感到奇怪为什么_T 不直接定义成#define _T(x) L ## x,而要绕个圈子去调用 wxCONCAT_HELPER 呢?这实际上涉及到宏展开顺序和 截断的问题。 在这里, 我们需要说一个宏参数的概念, 这很函数的参数是类似的, 这里_T(x) 的 x 就是宏参数,好,记住下面一句话: 如果你定义的宏中使用了#或者是##的话,宏参数将不会被展开,也就是说_T(x)如 果直接定义成 L##x 那么在下面这种情况就会出错( PS: #是给参数加引号的意思): _T(__FUNCTION__),__FUNCTION__是一个预定义的宏,它代表了当前函数的名 字,这个展开会是什么呢?L__FUNCTION__。为什么间接调用 wxCONCAT_HELPER 就能得到正确的结果呢?因为当我们调用 wxCONCAT_HELPER 的时候, __FUNCTION__已经被_T 展开成了函数名。

说多了说多了,如果你觉得复杂可以暂时跳过这些东西,我只是顺便说说。 重定义的编译错误和链接错误 让我们在项目里面再添加一个 Test.h 头文件,方法是右击解决方案中的项目,添加, 新建项,C++头文件,名称输入 test.h。然后我们在 test.h 中输入: /*#pragma once*/ void print() { } 回到 main.cpp 中: #include <iostream> using namespace std; #include "Test.h" #include "Test.h" int main() { return 0; } 编译一下我们会得到重定义的编译错误: error C2084: 函数“void print(void)”已有主体 或许你会说,你引用(#include)了两次,我没你那么傻,我只引用一次不就好了么? 是的。你聪明,但是是小聪明哈,因为你不能保证每个人都不去引用它。 这个问题演示的是#pragma once 的用处, 让我们解开它的注释。 编译成功! #pragma once 的作用就在于防止头文件被多次引用。你或许见过 #ifndef __TEST_H__ #define__TEST_H__ 代码

#endif 这样的代码,它们的作用是一样的 它们的作用是一样的,如果你跟我一样懒,那么就用#pragma once #pragma once,如 果你打算去没有这个指令的编译器上编译代码,那么还是用后面一种方式吧 果你打算去没有这个指令的编译器上编译代码 那么还是用后面一种方式吧。 现在让我们来见识一个对初学者稍微复杂一点的链接错误, 现在让我们来见识一个对初学者稍微复杂一点的链接错误 用创建 main.cpp 的方法再 添加一个 test.h 头文件,输入 输入#include "Test.h"即可。

让我们再编译一次。 。 1>test.obj : error LNK2005: "void __cdecl print(void)" (?print@@YAXXZ) 已经 在 Main.obj 中定义 1>e:\documents\visual studio visual 2010\Projects\HelloWorld HelloWorld\Debug\HelloWorld.exe : fatal error LNK1169: 找到一 HelloWorld.exe 个或多个多重定义的符号 如果说编译错误好找的话, 如果说编译错误好找的话 链接错误对于初学者来说就有点麻烦了, , 聪明的初学者会 去 Google、百度寻找答案, ,笨的初学者就会找所谓的高手、前辈问,而这些高手 Or 前辈 而这些高手 未必有心情为你解释。要解决这个错误有无数种方法 要解决这个错误有无数种方法。 1.内联,把 print 声明为内联函数 声明为内联函数。

inline void print() { } 这个方法的好处是简单, 坏处是局限性太强, 意味着你总是需要公开 print 的实现, 因为内联函数必须在编译时就知道实现才行。 2.static,把 print 声明为 static 函数: static void print()。 这便告诉编译器,哥是唯一的,而且哥只能被本编译单元的代码调用,这和 extern 是对应的。简单来说,想要哥帮你做事,请先 include 哥声明的头文件,也就是#include "test.h"。 3..h 头文件中只放声明,实现放到.cpp 中去。 现在 test.h 中只有 void print();,而实现在 test.cpp 中: #include "Test.h" void print() { int a = 1; cout<< a++ << endl; } 这个时候有意思的是我们在 main.cpp 无需包含 test.h 头文件也可以引用 print 函 数,因为 print 并非 static 的函数: void print(); int main() { print(); print(); return 0; }

但是声明一下是必须的。

由于百度空间的帖子的篇幅是有限制的,因此今天只好就说这么几点了。新的内容请 大家等候下一章。


相关文档

VC2010中初学者常见错误、警告和问题
告诫新手:SEO常犯的十大错误
告诫装修新手的十一条禁忌
股市新手的警告
单片机初学者五大告诫
VC2010中初学者常见错误、警告和问题
C51-keil编译常见错误和警告处理
keil c编译器常见错误与警告的解决方法
ICCAVR使用常见错误和警告
keil c编译器常见错误与警告的解决方法
电脑版