-
第四章
VC2010
中初学者常见错误、警告和问题
这一章将帮助大家解释一些常见的错误、
警告和问题,
帮助大家去理解和解决一些常
见问题,并了解它的根本原因。
p>
iostream.h
与
下面的代码
为什么在
VC2010
下面编译不过去?
#include
int
main()
{
cout<<
return 0;
}
错误信息:
fatal error C1083:
无法打开包括文件
:“iostream.h”: No
such file or
directory
造成这个错误的原因在于历史原因,
在过去
C++98
标准尚未订立的时候,
C++
< br>的标准
输入输出流确实是定义在这个文件里面的,这是
C
风格的定义方法,随着
C++98
标准
的确
定,
iostream.h
已经被
取消,至少在
VC2010
下面是这样的,取而代之的是我们要
用
头文件来代替,你甚至可以认为
是这样定义的:
namespace std
{
#include
}
因此我们可以简单的修改我们的
Hello
World
。
#include
using
namespace std;
int main()
{
cout<<
return 0;
}
iostream
.h
是属于
C++
的头文件,
而非
C
的,
因此标准订
立的时候被改成了
。
而
C
的头文件
stdio.h
p>
等依然可以继续使用,
这是为了兼容
C
p>
代码。
但是它们依然有对应的
C++
版本,
如
等。
记住,
在
VC2010
上面采用
C++
风格
的头文件而不是
C
风格的头文件,除非你是在用
C
。
warning C4996
这是一个警告,请看下面的代码:
#include
using
namespace std;
int main()
{
char sz[128] = {0};
strcpy(
sz,
cout<< sz << endl;
return 0;
}
上面的
strcpy
会产生这个警告<
/p>
:
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
版本开始,
微软引入了一系列的安全加强的函数来增强
CR
T
(
C
运行时),这里对应的是
strcpy_s
。
_s
意为
safe
的意思,同样的道理,
strcat
也是同样。
因此要解决这个问题,我们可以用<
/p>
strcpy_s
来替换
strcpy<
/p>
,但是注意
strcpy_s
并非所有编
译器都提供,因此如果要跨编译器,请采用错误信息中所提示的方式,定义
_CRT_SECURE_NO_WARNINGS
宏来掩耳盗铃吧。
另外注意并非所有的加强函数都是在
屁股后面加
_s
,比如
stricmp
这个字符
串比较函数的增强版名字是
_stricmp
。下面,用
strcpy_s
来更改程序:
int main()
{
char
sz[128] = {0};
strcpy_s( sz,
cout<< sz << endl;
char*
pSz2 = new char[128];
strcpy_s( pSz2,
128,
cout<< pSz2 << endl;
delete pSz2;
return 0;
}
注意,
strcpy_s
有两个版本,一个可以帮助我们自动推断缓冲区的大小
,而另外一个
不能帮助我们推断,
因此在编译器不能推断缓冲区
大小的时候,
我们需要自己指定缓冲区的
大小,如上面的程序所
演示的那样,关于增强版的函数请参考我写的
《深入学习
C++
String2.1
版》。
TCHAR
、
wchar_t
、
char
请大家看下面这个程序:
#include
#include
#include
using namespace std;
int
main()
{
MessageBox( NULL, <
/p>
你好
HelloWorld
!
return 0;
}
貌似没什么问题吧?错了,如果你是按照我教你
的方法创建的控制台空工程的话,那
么会有编译错误:
error
C2664: “MessageBoxW”:
不能将参数
2
从
“const char
[17]”
转换为
“LPCWSTR”
这个问题太普遍了,几乎所有的初学者都会遇到
而且感到难以应付,因为按照提示使
用
(LPCWSTR)
p>
强制转型貌似并不能帮助我们解决问题,
而且这个程序在
VC6
下面应该是没
有任何问题的,
那问题出现在哪里呢?问题在这里,
请右键单击解决方案浏览器下面的项目,
属性,
问题的根本就是字符集问题,在
VC6
中,
我们默认使用的是多字节字符集,而现在我
们默认需要的是
UNICODE
字符集,简单的,我们把这个字符集改成多字节字符集这个
问
题就解决了:
再试试应该
就可以了吧?但是我并不推荐大家这么做,因为让自己的程序适应各种
字符集是我们写代
码的人义不容辞的义务。
我们把程序改成下面这样:
#include
#include
#include
using namespace std;
int
main()
{
MessageBox( NULL, T
EXT(
你好
HelloWorld
!
MessageBox( NULL, _T(
你好
HelloWorld
!
return 0;
}
用两个宏
TEXT
或者
_T
都可以解决这个问题,它们两个并没有太大区别,也许区别在
于前者是通过
windows.h
头文件引入的,而
_T
是通过
tchar.h
引入的,我推荐大家使用
_T
和
< br>tchar.h
,因为
tchar.h
< br>还帮助我们引入了其它一些很有用的宏,比如
_tcscpy_s
,这个宏
在使用
UNICODE
字符集的时候被替换成
wcscpy_s
,
< br>在使用多字节字符集的使用被替换成
strcpy_s
。
关于这部分的内容,请大家不要错过
《
Windows
核心编程》的第二章
(
第四版或
第五版都可以),
以及
《深入学习
C++ String2.1
版》。
它们都有提到。
p>
有人听说
_T
可以把多字节字符串转换成<
/p>
UNICODE
,因此他写了如下的代码:
const char* pStr =
哈哈
MessageBox(
NULL, _T(pStr), _T(
当然,除
非你运气好的抓狂,否则你是编译不过去的,为什么呢?我们现在应该知道对
于
这样的字符串,
VC2010
< br>会默认的将它视为
const char*
,即多字节字
符串,而
L
前面有个
L
前缀的被视为
UNICODE
字符串,这和
C#
是有区别的,因为
C#
的字符
串总是被视为
UNICODE
,
C++/CLI
下面编译器也会帮助我们做到这件事情,
p>
所以它们不需
要
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 ##
最终就是
L
,
因此它可以
把
转换成
UNICODE
字符串。那为什么上面的程序不行呢?让我们看看
_T(
会被替换成什么:
L ## pStr ->
LpStr
,哦,
LpStr
是一个新的标识符,
如果你没有定义过它,你当然不能通过编译啦。
因此我们可以了解到
_T
这样的宏只能
处理
直接的常量字符串
,不能处理其它的情况。
而我们上面演示的那种情况需要我们动态的去转换编码,
Windows
有
API
可以帮助我们做
到,
C
库也有函数可以帮助我们。恰好我曾经写过这样的代
码,欢迎大家参考:
ASCII/UNICODE/UTF8
字
符串互相转换的
C++
代码
对于
_T
< br>宏,
再说一点东西,
或许你会感到奇怪为什么
_T
不直接定义成
#define _T(x)
L ##
x
,
而要绕个圈子去调用<
/p>
wxCONCAT_HELPER
呢?这实际上涉及到宏展开顺序
和截断的问
题。在这里,我们需要说一个
宏参数
的概念,这和函数的参数是类似的,这里
_T(x)
的
x
就
是宏参数,好,记住下面一句话:
如果你定义的宏中使用了
#
或者是
##
的话,宏参数将不会被展开
,也就是说
_T(x)
如果直
接定义成
L##x
那
么在下面这种情况就会出错
( PS:
#
是给参数加引号的意思):
_T(__FUNCTION__)
,
__FUNCTION__
是一个预定义的宏,它代表了当前函数的名字,
这个展开会是什么呢?
L__FUNCTION__
。为什么间接调用
wxCONCAT_HELPER
就能得
p>
到正确的结果呢?因为当我们调用
wxCONCAT_HELPER
的时候,
__FUNCTION__
已
经被
_T
展开成了函数名。
/*
即参数如果是宏,则不能得到正确结果;如果是直接的参数,则可
以<
/p>
*/
说多了说多了,如
果你觉得复杂可以暂时跳过这些东西,我只是顺便说说。
重定义的编译错误和链接错误
让我们在项目里面再添加一个
Test.h
头文件,
方法是右击解决方案中的项目,添加,
新
建项,
C++
头文件,名称输入
test.h
。然后我们在
test.h
中输入:
/*#pragma once*/
void print()
{
}
回到
中:
#include
using
namespace std;
#include
#include
int main()
{
return 0;
}
编译一下我们会得到重定义的编译错误:
error C2084:
函数
“void
print(void)”
已有主体
或许你会说,
你引用
(#includ
e)
了两次,
我没你那么傻,
我只引用
一次不就好了么?是的。
你聪明,但是是小聪明哈,因为你不能保证每个人都不去引用它
。
这个问题演示的是
#pragma once
< br>的用处,让我们解开它的注释。编译成功!
#pragma
once
的作用就在于防止头文件被多次引用。你或许见过
#ifndef __TEST_H__
#define__TEST_H__
代码
#endif
这样的代码,它们的作用是一样的,如果你跟我一样懒
,那么就用
#pragma once
,如果
< br>你打算去没有这个指令的编译器上编译代码,那么还是用后面一种方式吧。
现在让我们来见识一个对初学者稍微复杂一点的链接
错误,用创建
的方法再
添加一个
test.h
头文件,输入
#include
即可。
让我们再编译一次。
1> :
error LNK2005:
已经在
中定义
1>e:documentsvisual studio : fatal
error LNK1169:
找到一个或多个多重定义的符号
如果说编译错误好找的话,链接错误对于初学者来说就有点麻烦了,聪明的初学者会
p>
去
Google
、百度寻找答案,笨的初学
者就会找所谓的高手、前辈问,而这些高手
Or
前辈
未必有心情为你解释。要解决这个错误有无数种方法。
1.
内联,把
print
声明为内联函数。
inline void
print()
{
}
这个方法的好处是简单,坏处是局限性太强,意味着你总是需要公开
的实现,
因为内联函数必须在编译时就知道实现
才行。
,把
print
声明为
sta
tic
函数:
static void
print()
。
这便告诉编译器,哥是唯一的,而且哥只能被本编译单元的代码调用,这和
extern
是对应的。简单来说,想要哥帮你做事,请先
< br>include
哥声明的头文件,也就是
#includ
e
。
3..h
头文件中只放声明,实现放到
.cpp
中去。
现在
test.h
中只有
void print();
,而实现在
中:
#include
void print()
{
int
a = 1;
cout<<
a++ << endl;
}
这个时候有意思的是我们在
无需包含
test.h
头文件
也可以引用
print
函数,
因为
p>
print
并非
static
的函数:
void print();
int main()
{
print();
print();
return 0;
}
但是声明一下是必须的。
由于百度空间的帖子的篇幅是有限制的,因此今天只好就说这么几点了。新的内容请
p>
大家等候下一章。
合理组织项目、使用外部工具让工作更
...
这一章跟大家分享一
些与
c++
项目管理、
VAX
、
SVN
、
VS
快捷键等方面的东西。
p>
有效的在项目中组织
C++
文件,分配各种
文件的目录对以后的维护会有好处的,至少
不会出现不知道什么东西在什么地方,特别是
大的项目,这里用
T
extSearcher
< br>来做例子。
使
用
SVN
来管理项目会让我们的工作更轻松,工作也会更简单容
易。
掌握常用的快捷键
和常用的
VS
功能让我们的工作更有效。
合理的组织文件体系
首先说在
IDE
中为我们的文件分类组
织,如下图所示:
我把不同功能的
代码和文件放在不同的
Filter
下面,如何添加这样的
p>
Filter
呢?
这样就可以添加筛选器
了,默认情况下
VS
为我们创建三个筛选器:头文件、源文件和
资源文件,实际上我们可以再增加很多。这样区分开的好处就是各个功能的代码被分开了
,
在文件很多的情况下不会造成混乱。如
TextSearch
er
,它的搜索算法、软件控制逻辑、自
定义控件、
GUI
模块、
线程化操作都是分开的,
这样我可以很容易找到我想找的文件,而且
还可以检视自己的模块划分是否
合理等等。
接下来推荐大家在项目资源浏览器中为不同的文件划分目录。
比如上图将头文件和源文件、
资源图
标文件、
本地化文件和配置文件分开组织,
这样也是为
了防止混乱。
值得注意的是当我们把文件用文件分开的时候,
需要在项目属性设置里面包含
我们的子目录,
否则
我们无法在源文件中直接用
#include
指令包含我们的头
文件。
如下图所
示这样的情况,如果不添加,无法找到头文件。
如下图所示,找到项目属性中,
C++
,常规中把我们的子目录作为附加路径添加到
“
附
加包含目录
”
中。
使用
SVN
或其它源代码管理工具管理我们的项目
如果你打算写一个比较大一点的项目,我推荐
你使用源代码管理工具来管理你的
C++
项目,你可以选择
p>
SVN
,也可以选择其它的,我推荐
SVN
,因为简单容易上手。
当你在做一个很复杂的东西的时候,花了两三天的时间去做修改,不过后来发现这个
p>
修改并不合适,
想还原到三天以前,
如果你
没有用源代码管理工具管理自己的项目也没有自
己手动的备份,
那恭喜你,你得开始人肉还原了,
这是多么悲剧的一件事情啊,然而如果你
使用了
SVN
管理的话,只需要在三天前开始修改前
的最后一次稳定版本
Commit
一次,三
天之后如果要还原,只需要使用工具
Revert
就好了,
而且不但可以回到三天前的版本,你
甚至可以回到以前每一次
C
ommit
的版本,
(
⊙
o
⊙
)
哇,这是多么好的工具啊!
要使用
SVN
,首先需要
SVN
客户端,
< br>SVN
服务器是可选的。我推荐大家使用
Tortois
eSVN
这个
SVN
客户端,因为它简
单易用、免费,支持
Windows32Bit
、
64Bit
,你
可以去他们的官方网站下载,地址点
我。有了这个工具当然还不够,为了让我们的
SVN
跟
VS2010
结合的更紧密,我们需要再下载一个
SVN For VS
的插件,我强烈推荐你使用它,
因为它
也非常简单易用,下载地址点我。最新版是支持
VS2010
的
。当你装了这个插件的
时候,如果你的项目是在
SVN
的管理之下,那么你的项目看起来会一些不同:
看
到
文
p>
件
左
边
的
勾
了
吗
?
灰
色
的
勾
< br>表
示
正
常
,
而
橙
色
的
勾
表
示
已
p>
经
更
改
了
,
而
PendingChanges
则告诉我们哪些文件时新加的,
哪些文件被改过了,
如果要还原的话,
只
需要选择该文件,右键点击,
Revert
就好了:
如果你已经安装好了
A
nkhSVN
插件但是又看不到的话,
那么请检查下系统选项卡
里面
是否选择了它作为默认的源代码管理工具:
回过头去继续说
SVN
,当我们安装好
TortoiseSVN
之后(安装后可能需要重启)
,我
们在资
源管理器中点击右键的时候就能看到它的菜单了:
虽然
< br>TortoiseSVN
有中文语言包,
但是我推荐大家
使用英文的,
就像古诗一定要用中文
来表达一样,没有比英语单
词表达
SVN
项目管理更恰当的词了。
SVN CheckOut
可以让你获取其它地方
SVN
服务器上面的某个项目的源码
,当然,前
提是你要有权限才行,现在让我们试试。随便找个盘符如
F
盘,点右键,选择
SVN
CheckOut
,然后
Url of
Repository
中输入
svn:///TextSear
cher
这个地址,
其它的不变,点击
OK
。如图所示:
如果不出意外你能看到:
如
果
你
p>
看
到
这
个
画
面
说
明
你
已
经
CheckOut<
/p>
成
功
了
,
那
么
恭
喜
你
,
你
已
经
取
到
了
TextSearcher
的源码,当然要说一点的是,这个源码可能你取到之后编
译不了,因为还有
另外一些依赖项如
dbsoft
、
boost
、
wxWid
gets
并不在这个
SVN
上面。
p>
这样大家就可以去一些开
源的网站上面
Check
你想要的东西了,哈哈。推荐大家去这
个网站找自己感兴趣的代码
CheckOut
< br>:
/
现在我们说
如何管理自己的项目,大家可以像我一样去一些提供免费
SVN
服务器服务
的网站上面注册一个账号建立项目就好了,比如说我使用的这个
,这个毕
竟是国内的,另外上面的
< br>
也可以。当你注册建立项目成功之后就可以通过
菜单
p>
Import
把需要导入的东西导入到
SV
N
服务器了:
导
入
< br>界
面
的
地
址
栏
输
入
我
们
的
SVN
服
务
器
地
址
p>
即
可
,
类
似
于
:
svn:///
TextSearcher
。导出的时候记得输入日志。除了可以导入
< br>SVN
服务
器之外,
我们还可以
使用文件协议在自己的电脑上管理我们的代码,
如果你不需要在多台电
< br>脑上面共同维护这个项目的话。
让我们随便找个地方新建一个文件夹,如
D:TestSVNServe
r
这个文件夹,然后对着这
个新建的文件夹点右键,选择
SVN>Create repository here
,
OK
,你的本地
SVN
服务器已
经建好了。如果创建成功,你会发现这个文件夹里面多了好多东西:
没关系,
这是
SVN
服务必须要的一些东西,现在让我们使用文件协议导
入我们的项目
到该
SVN
服务器中,找
到我们要导入的项目,跟导入网络上的
SVN
服务器一样,右击
项
目文件夹,
SVN>Import
。
在
Import
界面中这样填:
不出意外你会导入成功,
那么现在我们需要去其它地方重新获取这些东西了
,
换个地方,
比如
F
< br>盘根目录,选择
SVN
CheckOut
,然后地址输入刚才导入的地址:
注
意
如
上
图
这
样
是
< br>不
行
的
,
因
为
我
们
没
办
法
在
F
p>
盘
根
目
录
下
面
创
建
一
个
D:TestSVNServer
文件夹,把
D:
这个
SVN
帮我们自己填充的路径删掉然后点确定。
现在我们已经
CheckOut
了刚才导入的项
目了,
Planet
文件夹已经带上了一个绿色的勾。
点击这个
Planet
目录,选择
SVN>Show Log
,你会看到:
在这个
界面我们还可以检查代码的改变都是什么,这里由于是新的项目,所以没有更
改,我们可
以找到
T
extSearcher
的更
改来比较它们的改动都是什么:
-
-
-
-
-
-
-
-
-
上一篇:partial关键字的含义和使用
下一篇:八年级下册英语Unit_4教案