c++ while应输入声明明怎么解决

关于接口的设计与声明--对封装性的理解[C++]
设计与声明
所谓软件设计,是&令软件做出你希望它做的事情&的步骤和方法,通常以颇为一般性的构想开始,最终十足的细节,以允许特殊接口(interface)的开发。这些接口而后必须转换为C++声明式。本文讨论对良好C++接口的设计和声明。
1. 让接口容易被正确使用,不易被误用
C++拥有许多的接口,function接口,class接口,template接口&.每一种接口实施客户与你的代码互动的手段。理想情况下,客户总是会准确的使用你的接口并获得理想的结果,而如果客户错误的使用了接口,代码就不应该通过编译。
用结构体限制参数类型
假设我们现在需要做一个表示时间的class
class Date {
Date(int month, int day, int year);
乍看起来,这个类的构造函数并没有什么问题,但其实存在着很多的隐患。我们当然希望用户可以准确的使用我们的类,但用户却有可能因为某些特定的原因无法正确使用我们的类,例如没有按照月,天,年的顺序来完成构造。而此时,为了避免用户犯错,我们需要强制用户按照我们的设计来用这个类:
special design
缺省情况下,struct内部都是public访问限制。
struct Day {
explicit Day(int d) : val(d) { }
struct Month {
explicit Month(int m) : val(d) { }
struct Year {
explicit Year(int y) : val(d) { }
class Date {
Date(const Month &m, const Day &d, const Year &y);
Date d1(30, 3, 1996); // error!
Date d2(Month(3), Day(30), Year(1996)); // right!
用struct来封装数据,可以明智而审慎地导入新类型并预防&接口被误用&。
一旦类型限定了,限定其值也是合情合理的了。例如一年只有12个月,所以Month应该反映这一点。办法之一就是用enum表现月份,但enum不具备我们希望的类型安全性,例如enum可以被当做一个int使用。比较安全的做法是:预先定义所有有效的Month。
class Month {
static Month Jan() { return Month(1); }
static Month Feb() { return Month(2); }
static Month Dec() { return Month(12); }
explicit Month(int m);
Date d(Month::Mar(), Day(30), Year(1996));
以函数替换对象,表现某个特定的月份是一种相当不错的方法。
限制类型内什么能做,什么不能做
除非有更好的理由,否则尽量让你的type的行为与内置type一致!
用户很清楚像int这样的type有什么行为,所以你应该努力让你的type在合情合理的前提下也有相同的操作。例如,如果a和b都是int,那么对a*b赋值就是不合法的。
避免无端与内置类型不兼容,真正的理由是为了==提供行为一致的接口==。很少有其他性质比&一致性&更能导致&接口被正确使用&,也很少有性质比得上&不一致性&更加剧接口的恶化。
2. 设计class犹如设计type
C++就像其他OOP语言一样,当你定义一个新class,也就定义了一个新的type。包括,重载函数和操作符、控制内存的分配和归还、定义对象的初始化和析构&&全都在你控制,因而你应该带着和&语言设计者当初设计语言内置类型时&一样的谨慎来设计class。以下给出了部分class设计规范。
新type的对象应该如何被创建和销毁?这回应该到你如何设计class的构造函数和析构函数以及内存分配函数和释放函数。 对象的初始化和对象的赋值有什么样的差别?这决定了你如何设计构造函数和赋值操作符。最重要的是别混淆&初始化&和&赋值&,因为他们对应不同的函数调用。 新type对象如果被passed by value,意味着什么?记住,copy构造函数用来定义一个type的pass by value如何实现。 什么是新type的&合法值&?这意味你的成员函数必须进行错误检查工作,也影响了函数抛出的异常、以及函数异常明细列。 你的type需要配合某个继承体系吗?如果你继承自某些既有的class,你就会受到这些class设计的束缚,特别是受到他们的函数是virtual或non-virtual的影响。如果你允许你的class被其他class继承,那会影戏到你的析构函数是否会virtual。 你的新type需要什么样的转换?因为你的type存在于其他大量的type之间,这决定了你是否需要让自己type有途径转换为其他的type(隐式还是显式的?) 什么样的操作符和函数对此新type而言是合理的?这取决于你的成员函数的设计。 什么样的标准函数应该驳回?那些就是你声明为private的对象。 谁该取用新type的成员?这决定了如何安排函数是public,protected还是private,以及那些函数/类是friend。 什么是新type的&未声明接口&?他对效率、异常安全性以及资源运用提供何种保证? 你的新type有多么一般化?如果你并不是为了定义一个新type而是要定义一整个type家族,那么应该定义一个新的class template。 你是否真的需要一个新的type?如果你只是为了给base class添加某些功能,那么定义一个或多个non-member 函数或template,更好。
设计class是一件非常具有挑战的事情,所以如果你希望设计一个class,最好像设计一个type一样,把各种问题都思考一遍。
3. 宁以pass by reference to const替换pass by value
在缺省情况下C++总是以pass-by-value的方式传递对象至函数,实际上,就是传递复件,而这些复件都是由copy构造函数产生的,这可能使得pass-by-value称为昂贵而耗时的操作。
class Person() {
virtual ~Person();
class Student : public Person {
Student();
~Student();
std::string schoolN
std::string schoolA
// in main:
bool checkStudent(Student s);
bool whoh = checkStudent(one);
在checkStudent调用时,发生了什么?
这显然是一个pass-by-value的函数,也就意味着一定会出现copy构造函数,对于此函数而言,参数的传递成本是&一次student copy构造函数调用,加上一次student析构函数调用&。不仅如此,student还继承于person,所以还有一次person构造函数和person析构函数,以及student里面的两个string对象,和person里面的两个string对象,总而言之,总体成本就是&六次构造函数和六次析构函数!&多么可怕的开销!
解决这个问题非常的简单。只要使用pass by reference to const就可以了。因为by reference不会导致构造函数和析构函数的使用,节省了大量开销,同时因为是const,也保证了参数不会再函数内被更改。
bool checkStudent(const Student &s);
pass-by-value还会导致对象切割问题(slicing)。当一个dereived class对象以by value方式传递并被视为一个base class对象时,bass class的copy构造函数就会被调用,而&造成此对象的行为像个derived class对象&的那些特化性质全部被切割掉,只剩下base class对象。这并不奇怪。
class Window {
std::string name()
virtual void display()
class SpecialWindow {
virtual void display()
// in main:
void print(Window w) {
cout && w.name();
w.display();
当你把一个SpecialWindow对象传递给void print(Window w)函数时,就像前文所说的,会使得SpecialWindow的特化性质全部被切割掉,于是乎,你本想着输出SpecialWindow的特别内容结果只输出了Window内容。
解决这个问题仍然是使用reference。由此来引发动态绑定,从而使用SpecialWindow的display。
void print(const Window& w) {
cout && w.name();
w.display();
窥视C++编译器的底层就会发现,实际上reference就是以指针实现出来了,pass by reference通常意味着真正传递的是指针。因此,如果你有个对象属于内置类型(如int),pass-by-value通常来说效率会更好。这对于STL的迭代器和函数对象同样适用。因为习惯上他们都是设计为pass-by-value。迭代器和函数对象的实践者都有责任看看他们是否高效且不受切割问题。
有人认为,所有小型type对象都应该适用pass-by-value,甚至对于用户定义的class。实际上是不准确的。第一,对象小,并不意味着他的copy构造函数开销小;2)即使是小型对象并不拥有昂贵的copy构造函数,也可能存在效率上的问题,例如某些编译器不愿意把只由一个double组成的对象放进缓存器,但如果你使用reference,编译器一定会把指针(就是reference的实现体)放进缓存器。3)作为用户自定义类型,其大小是很容易被改变的。随着不断的使用,对象可能会越来越大。
一般而言,合理假设&pass-by-value更合适&的唯一对象就是内置类型和STL的迭代器和函数对象,其他的最好还是使用by reference。
4. 必须返回对象时,别妄想返回其reference
前面我们讨论了pass-by-reference可以提高效率,于是乎,有的人就开始坚定地使用reference,甚至开始传递一些refereence指向其实并不存在的对象。
此问题产生的理由非常的简单,就是作者希望可以节省开销提高效率。并因此而产生大量的错误。
class Rational {
Rational(int num1 = 0, int num2 = 1);
int n1, n2;
friend Rational& operator*(const Rational& lhs, const Rational& rhs);
operator*试图返回一个引用,并为此寻找合乎逻辑的实现代码。
尝试1:直接返回
Rational& operator*(const Rational& lhs, const Rational& rhs) {
Rational result(lhs.n1 * rhs.n1, lhs.n2 * rhs.n2);
问题显然。因为result是一个on the stack对象,在作用域结束后,对象就被销毁,于是返回了一个没有指向的reference。尝试失败!
尝试2:返回on the heap对象
Rational& operator*(const Rational& lhs, const Rational& rhs) {
Rational* result = new Rational(lhs.n1 * rhs.n1, lhs.n2 * rhs.n2);
此代码乍看起来似乎没什么问题,但其实隐含杀机。你在函数中动态申请了一块内存放这个变量,这也就意味着你必须管理这块资源(见前文:资源管理)。然而管理这块资源几乎不可能,因为你不可能希望在main函数里一直有一个变量在守着这块资源并且及时的delete掉。而且当大量使用*操作符时,管理大量的资源根本不可能!就算你有这样的毅力这么管理,也不可能希望有用户愿意做这样的体力活。
尝试3:使用static变量
Rational& operator*(const Rational& lhs, const Rational& rhs) {
static Rational result(lhs.n1 * rhs.n1, lhs.n2 * rhs.n2);
这代码乍看起好像又要成功了?!其实并没有。问题出现的十分隐蔽:
bool operator == (const Rational& lhs, const Rational& rhs);
if ((a*b) == (c*d)) {
问题就出在等号操作,等号永远会成立!因为,在operator == 被调用前,已有两个操作符被调用,每一个都返回操作函数内部的static对象,而这两个对象实际上就是一个对象!(对于调用端来说,确实如此!)于是乎,你根本就没有完成*操作符所应该具备的功能。
问题的解决就是,别挣扎了!使用pass-by-value吧。不就是一点构造函数和析构函数的开销嘛。比起大量的错误和内存的管理。这点开销还是很划算的。
class Rational {
Rational(int num1 = 0, int num2 = 1);
int n1, n2;
friend Rational operator*(const Rational& lhs, const Rational& rhs) {
return Rational(lhs.n1*rhs.n1, rhs.n2*rhs.n2);
5. 将成员变量声明为private
在我们最初学习C++ OOP时就有一天准则,成员变量总是要声明为private。本节我们来讨论为何成员变量要被声明为private。
理由一:语法一致性。
因为成员变量不是public,客户唯一能够访问对象的办法就是通过成员函数。如果public接口内的每一样东西都是函数,客户就不用纠结调用他时是否需要使用小括号。如此便能省下大量的时间。 理由二:使用函数可以让你对成员变量的处理有更准确的控制。
如果成员变量是public,那么每个人都可以对他进行读写,但如果你以函数取得或设定其值,就可以实现&不准访问&,&只读访问&,&读写访问&等访问控制。
如以下代码:
class AccessLevel {
int WriteO
int getReadOnly() {
return ReadO
void setWriteOnly(int i) {
WriteOnly =
void setreadWrite(int i) {
readWrite =
int readreadWrite() {
return readW
如此精细地对各个数据成员进行访问限制是有必要的。
理由三:封装!
这是最有说服力的理由了!C++ OOP其中最重要的一条性质就是封装性!将数据成员封装在接口的后面,可以为&所有可能的实现&提供弹性。
封装的重要性比我们最初见到它时更重要。如果我们对客户隐藏成员变量,就可以确保class的约束条件受到维护,因为只有成员函数可以影响他们。public意味着不封装,而几乎可以说不封装意味着不可改变,特别是对被广泛使用的class而言。被广泛使用的class是最需要封装的一个族群,因为他们能够从&改采用一个教佳实现版本&中获益。
我们继续来讨论protected的封装性。
一般人会认为protected比public更具有封装性。其实不然。更准确的判断方法是:某些东西的封装性与&当其内容改变时可能造成的代码破坏量&成反比。所谓改变,也许是从class中移除他。于是乎,我们可以进行以下分析。对于public的成员变量,如果我们移除他,意味着我们要破坏所有使用它的客户代码。(破坏量很大吧?)而对于protected的成员变量呢,如果我们移除它,意味着要破坏所有derived class(破坏量也很大吧?)因此protected和public的封装性其实是一样的。这也就意味着,一旦我们决定把某个成员变量声明为public或protected,就很难改变某个成员变量所涉及的一切。
结论就是,其实只有两种访问权限:private(实现封装)和其他(不实现封装)
6. 宁以non-member、non-friend替换member函数
面向对象守则要求,数据以及操作数据的那些函数应该被捆绑在一起,这意味着它建议所有操作数据成员的函数都应该是member函数。然而事实上是如此吗?
假设我们希望写一个类来描述网页:
class WebBrowser {
void clearCache();
void clearHistory();
void removeCookies();
// 用户希望有一个函数能够清楚所有信息
// 问题是,该函数是否应该声明为member?
void clearEverything();
// 也可以声明为non-member
void clearEverything(WebBrowser &web) {
那么哪种选择更好呢?
根据面向对象守则要求,声明为member函数应该是更好的选择。然而,这是对面向对象真实意义的一个误解。面向对象要求数据应该尽可能被封装,然而与直观相反地,member函数clearEverything带来的封装性比non-member函数的低。此外,提供non-member函数可允许对WebBrowser相关机能有更大的包裹弹性,从而最终导致较低的编译相依度,增加WebBrowser的可衍生性。以下我们给出理由。
封装性。愈多的东西被封装,越少人可以按到它,那么我们就有越大的弹性去改变它,而我们的改变只会影响看到改变的那些人和事物。这就是我们推崇封装性的原因:它使我们能够改变事物而只影响有限客户。 考虑对象内数据。越少代码可以看到数据,越多的数据可被封装,而我们也就越能自动地改变对象数据。越多的函数可以访问数据成员,数据的封装性就越差!
因此,因为non-member non-friend函数不能直接改变数据成员,因此他就可以最大限度的实现封装。
在C++中,最自然的做法,是让clearEverything称为一个non-member函数并且位于WebBrowser所在的同一个namespace内:
namespace WebBrowserStuff {
class WebBrowser {...};
void clearEverything(WebBroswer &web);
namespace和class是不用的!前者可以跨越多个文件而后者不能,这很重要!
像clearEverything这样的函数就是便利函数,虽然没有对WebBrowser有特殊的访问权限,但可以极大的便利客户。而实际上,我们会补充大量的类似的便利函数,并且他们可能分属于不同的模块,于是我们便采用把不同模块便利函数写于不同的头文件中,但他们都隶属于同一个命名空间:
#include &webbrowser.h& 提供class声明本身,以及其中核心机能
namespace WebBrowserStuff {
class WebBroser { ... };
// 核心机能,几乎所有用户都需要的non-member便利函数
// 头文件 &webbrowserbookmarks.h&
// 与标签相关
namespace WebBrowserStuff {
... // 与标签相关的便利函数
// 头文件 &webbrowsercookies.h&
namespace WebBrowserStuff{
... // 与cookie相关的便利函数
注意这是C++标准程序库的组织方式。标准程序库中并不是拥有单一、整体、庞大的
7. 若所有参数皆需类型转换,请为此采用non-member函数
令class支持隐式类型转换通常是个糟糕的注意。当然也有例外,例如你在建立数值类型时。
假设我们需要设计一个有理数类:
class Rational {
Rational(int numerator = 0, int denominator = 1);
int numerator()
int denominator()
class Rational {
const Rational operator*(const Rational& rhs)
// 于是乎可以轻松实现乘法
Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf * oneE
result = result * oneE
到目前为止还没有实现致命问题,然而:
result = oneHalf * 2;
result = 2 * oneH
// result = 2.operator*(oneHalf);
of course wrong!
第一个式子能够成立,是因为实现了隐式类型转换。编译器知道你在传递一个int,而函数需要的是rational,但它也知道只要调用Rational构造函数并赋予你所提供的int,就可以变出一个适当的rational出来,于是就这么做了。相当于:
const Rational temp(2);
result = oneHalf *
当然这只涉及non-explicit构造函数,才能这么做。如果是explicit构造函数,这个语句无法通过编译。
result = oneHalf * 2;
result = 2 * oneH
只有当参数被列于参数列内,这个参数才是隐式类型转换的合格参与者。地位相当于&被调用之成员函数所隶属的那个对象&-即this对象-那个隐喻参数,绝不是隐式转换的合格参与者。这就是为什么语句1能够通过编译而语句2不可以。
于是,方法就是,让operator*称为一个non-member函数,允许编译器在每一实参身上执行隐式类型转换。
const Rational operator*(const Rational& lhs, const Rational& rhs) {
Rational oneFourth(1, 4);
Rational result = oneFourth * 2; // right!
result = 2 * oneF // right!
补充思考:
是否应该把该operator*声明为friend?
答案是否定的!请注意,member的反面不是friend,而是non-member!在此代码中,operator*完全可以借由rational的public接口完成任务,于是便不必把他声明为friend。无论何时,如果可以避免friend函数就应该避免。
如果你需要为某个函数的所有参数(包括this)进行类型转换,那么这个函数必须是个non-member。
8. 考虑如何写出特化的swap函数
swap作为STL的一部分,而后成为异常安全性的脊柱,以及用来处理自我赋值可能性的一个常见机制。由于此函数如此有用,也意味着他具有非凡哥的复杂度。本节谈论这些复杂度以及相应处理。
namespace std {
void swap(T &a, T &b) {
T temp(a);
这是标准程序库提供的swap算法。非常地简单,只要T有copying相关操作即可。然而这个算法对于有些情况却显得不那么高效。例如,在处理&以指针指向一个对象,内含真正数据&的那种类型。(这种设计的常见形式是所谓&pimpl手法:pointer to implemention)
class WidgetImpl {
实现细节不重要。
针对Widget设计的class
std::vector
class Widget {
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs) {
*pImpl = *(rhs.pImpl);
WidgetImpl* pI
对此类调用算法库的swap就会非常低效。因为他总共要复制三个Widget和三个WidgetImpl对象!而事实上,只需要改变指针的指向就可以了。
我们可能尝试用以下方法解决,让swap针对Widget特化。
namespace std {
template&& // 表示他是std::swap的一个全特化
void swap(Widget &a, Widget &b) {
swap(a.pImpl, b.pImpl);
通常来说,我们是不能够改变std命名空间内的任何东西,但可以(被允许)为标准template制造特化版本的。
但实际上这个是无法通过编译的。因为他企图调用class的私有成员。
所以更合理的做法,是令他调用成员函数。
class Widget {
void swap(Widget& other) {
using std::
swap(pImpl, other.pInmpl);
WidgetImpl* pI
namespace std {
template&&
void swap(Widget &a,
Widget &b) {
a.swap(b);
这个做法不仅能够通过编译,而且与STL容器有一致性。
假设Widget和WidgetImpl都是class template而非class,也许我们可以试试把WidgetImpl内的数据类型加以参数化:
class WidgetImpl {...};
class Widget {...};
// 在Widget里面放入swap成员函数就像以往一样简单
// 但在写特化std::swap时出现了问题
namespace std {
void swap& Widget & (Widget& a, Widget& b) {
a.swap(b);
以上特化swap其实有问题的。我们企图偏特化这个function template,但C++只允许对class template偏特化。(随后会介绍全特化和偏特化)。当你尝试偏特化一个function template时,更常见的做法是添加重载函数:
namespace std {
void swap(Widget& a, Widget& b) {
a.swap(b);
但实际上,这也是不行的!因为std是个特殊的命名空间,其管理规则比较特殊,客户可以全特化std内的template,但不可以添加新的template到std里面。
解决这个问题的方法就是,声明一个non-member swap让它调用member swap,但不在将那个non-member swap声明为std::swap特化版或重载版本。
namespace WidgetStuff {
class WidgetImpl {...};
class Widget {...};
void swap(Widget& a, Widget& b) {
a.swap(b);
现在,任何时候如果打算置换两个Widget对象,因而调用swap,C++的名称查找法则都会找到WidgetStuff内的Widget专属版本。
这个做法对class和class template都行得通。如果你想让你的&class&专属版swap在尽可能多的语境下被调用,你需要同时在该class所在命名空间内写一个non-member版本以及一个std::swap特化版本。
另外,如果没有像上面那样额外使用某个命名空间,上述每件事情仍然使用。但你又何必再global命名空间里面塞这么多东西呢?
目前提到得都是和swap编写有关的。现在我们换位思考,从客户观点看看问题。假设我们需要写一个function template:
void doSomething(T& obj1, T& obj2) {
swap(obj1, obj2);
此时swap是调用哪个版本呢?我们当然希望是调用T专属版本,并且在该版本不存在的情况下,调用std内的一般化版本。
void doSomething(T& obj1, T& obj2) {
using std::
swap(obj1, obj2); // 为T类型对象调用最佳swap版本。
C++名称查找法则确保将找到global作用域或T所在命名空间内的任何T专属的swap。如果T是Widget并位于命名空间WidgetStuff内,编译器会使用&实参取决之查找规则&找出WidgetStuff内的swap。如果没有T专属之swap存在,编译器就使用std内的swap。
以下是我设计的一个不大合乎逻辑的代码,但证明了上述说法是合理的。
namespace test {
class trys {
void swap(trys &one, trys &two) {
cout && &yes!& &&
void swap(trys &one, trys &two) {
cout && &yes!& &&
int main(int argc, const char * argv[]) {
// insert code here...
int b = 12;
using std::
swap(b, b);
swap(a, a);
Program ended with exit code: 0
如果swap缺省实现版的效率不足,(那几乎意味着你的class或template使用了某种pimpl手法),可以试着做以下事情:
提供一个public swap成员函数,让他高效地置换你的类型的两个对象值。 在你的class或template所在的命名空间内提供一个non-member swap,并命它调用上述swap成员函数。 如果你在编写一个class,并为你的class特化std::swap,并令他调用你的swap成员函数。
最后,如果你调用swap,请确保包含一个using声明式。
补充内容:(全特化和偏特化)
模板为什么要特化,因为编译器认为,对于特定的类型,如果你能对某一功能更好的实现,那么就该听你的。
模板分为类模板与函数模板,特化分为全特化与偏特化。全特化就是限定死模板实现的具体类型,偏特化就是如果这个模板有多个类型,那么只限定其中的一部分。
先看类模板:
class Test
Test(T1 i,T2 j):a(i),b(j){cout&&&模板类&&
class Test
Test(int i, char j):a(i),b(j){cout&&&全特化&&
class Test
Test(char i, T2 j):a(i),b(j){cout&&&偏特化&&
那么下面3句依次调用类模板、全特化与偏特化:
Test t1(0.1,0.2);
Test t2(1,'A');
Test t3('A',true);
而对于函数模板,却只有全特化,不能偏特化:
//模板函数
void fun(T1 a , T2 b)
cout&&&模板函数&&
void fun(int a, char b)
cout&&&全特化&&
void fun(char a, T2 b)
cout&&&偏特化&&
至于为什么函数不能偏特化,似乎不是因为语言实现不了,而是因为偏特化的功能可以通过函数的重载完成。
(window.slotbydup=window.slotbydup || []).push({
id: '2467140',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467141',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467142',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467143',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467148',
container: s,
size: '1000,90',
display: 'inlay-fix'Visual&C++&2010中初学者常见错误、警告和问题
这部分将帮助大家解释一些常见的错误、警告和问题,帮助大家去理解和解决一些常见问题,并了解它的根本原因。
&iostream.h&与&iostream&
下面的代码为什么在VC2010下面编译不过去?
#include &iostream.h&
int main()
cout&&"Hello
错误信息:fatal error C1083:
无法打开包括文件:“iostream.h”: No such
file or directory
造成这个错误的原因在于历史原因,在过去C++98标准尚未订立的时候,C++的标准输入输出流确实是定义在这个文件里面的,这是C风格的定义方法,随着C++98标准的确定,iostream.h已经被取消,至少在VC2010下面是这样的,取而代之的是我们要用头文件来代替,你甚至可以认为是这样定义的:
namespace std
&&&&&&&&&&&&&
#include "iostream.h"
因此我们可以简单的修改我们的Hello World。
#include &iostream&
int main()
cout&&"Hello
&&&&&iostream.h是属于C++的头文件,而非C的,因此标准订立的时候被改成了。而C的头文件stdio.h等依然可以继续使用,这是为了兼容C代码。但是它们依然有对应的C++版本,如 等。记住,在VC2010上面采用C++风格的头文件而不是C风格的头文件,除非你是在用C。
warning C4996
这是一个警告,请看下面的代码:
&iostream&
int main()
char sz[128] = {0};
strcpy( sz, "Hello World!" );
&&&&上面的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。_s意为safe的意思,同样的道理,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!" );
char* pSz2 = new
char[128];
strcpy_s( pSz2, 128, "hello");
cout&& pSz2
delete pSz2;
&&&&注意,strcpy_s有两个版本,一个可以帮助我们自动推断缓冲区的大小,而另外一个不能帮助我们推断,因此在编译器不能推断缓冲区大小的时候,我们需要自己指定缓冲区的大小,如上面的程序所演示的那样,关于增强版的函数请参考我写的《深入学习C++ String2.1版》。
&&&&&TCHAR、wchar_t、char
&&&&请大家看下面这个程序:
&iostream&
#include &Windows.h&
#include &tchar.h&
int main()
MessageBox( NULL, "你好HelloWorld!", "Information", 0 );
&&&&&貌似没什么问题吧?错了,如果你是按照我教你的方法创建的控制台空工程的话,那么会有编译错误:
C2664: “MessageBoxW”: 不能将参数 2 从“const char [17]”转换为“LPCWSTR”
&&&&&这个问题太普遍了,几乎所有的初学者都会遇到而且感到难以应付,因为按照提示使用(LPCWSTR)强制转型貌似并不能帮助我们解决问题,而且这个程序在VC6下面应该是没有任何问题的,那问题出现在哪里呢?问题在这里,请右键单击解决方案浏览器下面的项目,属性,
问题的根本就是字符集问题,在VC6中,我们默认使用的是多字节字符集,而现在我们默认需要的是UNICODE字符集,简单的,我们把这个字符集改成多字节字符集这个问题就解决了:
再试试应该就可以了吧?但是我并不推荐大家这么做,因为让自己的程序适应各种字符集是我们写代码的人义不容辞的义务。
我们把程序改成下面这样:
&iostream&
#include &Windows.h&
#include &tchar.h&
int main()
MessageBox( NULL, TEXT("你好HelloWorld!"), TEXT("Information"), 0 );
MessageBox( NULL, _T("你好HelloWorld!"),
_T("Information"), 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
#ifndef _T
#if !wxUSE_UNICODE
#define _T(x) x
#define _T(x) wxCONCAT_HELPER(L, x)
_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中输入:
void print()
回到main.cpp中:
&iostream&
#include "Test.h"
#include "Test.h"
int main()
编译一下我们会得到重定义的编译错误:
&&&&&&&&&&&
error C2084: 函数“void print(void)”已有主体
或许你会说,你引用(#include)了两次,我没你那么傻,我只引用一次不就好了么?是的。你聪明,但是是小聪明哈,因为你不能保证每个人都不去引用它。
这个问题演示的是#pragma
once的用处,让我们解开它的注释。编译成功!#pragma once的作用就在于防止头文件被多次引用。你或许见过
#ifndef __TEST_H__
#define__TEST_H__
这样的代码,它们的作用是一样的,如果你跟我一样懒,那么就用#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
2010\Projects\HelloWorld\Debug\HelloWorld.exe : fatal error
LNK1169: 找到一个或多个多重定义的符号
如果说编译错误好找的话,链接错误对于初学者来说就有点麻烦了,聪明的初学者会去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++
这个时候有意思的是我们在main.cpp无需包含test.h头文件也可以引用print函数,因为print并非static的函数:
void print();
int main()
但是声明一下是必须的。
工程Setup发布:
1、制作安装文件的过程中,一般会出现几个warning,都是说某dll文件是系统自带的,不用加入安装包中之类的。建议把这些dll从你生成的filesystem中删除,否则有可能遇到系统版本问题。我遇到的相关具体问题是在win7下做的安装包到了xp下就无法安装和运行了。
2、关于快捷方式。我采用的方法是(不知道算不算一种猥琐的方法):先将你编译好的Realse文件夹下面的exe文件添加到你的file
system里。然后右键file
system里添加好的exe文件,会看到生成快捷方式的选项。生成快捷方式后,再将快捷方式剪切复制到user desktop等文件夹中去。
3、Logo。自己画,或者找个bmp转ico文件的转换器吧。不过转换完之后的效果都不怎么理想,毛刺挺多,需要进一步修改。
4、.最后,添加进来的依赖dll有一个选项,选择selfregister的话是可以在安装时刻自动注册的。另外,不要忘了将注册时需要用到的dll也包含进来。
5、.默认安装路径等,在setup工程的property里都可以调整。
主函数main
在VC++编程中,我随便选择了一个以前的程序准备编译,结果报错。一开始怀疑我自己的问题,于是写了一个最简单的程序,还是报错,如下:
好奇下,决定用微软自己的用例跑跑。于是新建了一个工程,并且选择Precompiled header,生成的工程如图:
这时恍然大悟,原来是入口函数有问题。正在做茅塞顿开状,一个学长提醒我到,从VS2005,微软定义的入口函数就是_tmain了,听了我还不相信,因为自己从来都是用main在VS2008中通过编译的。后来在VS2008中生成工程,果然如学长所说。正在郁闷中,学长又提醒我可以设置工程的预编译项,于是自己试了试,果然成功了,在改了以后的设置中,VS2010也可以跑通了。
方法如下:*代表当前工程名
project -&& properties
-&(选择Configuration
Properties,这个时候在顶部的Configuration选择Active(Debug),
再到Configuration
Properties中选择)C/C++
-& Precompiled Header -& Precompiled
Header值改为Use(/Yu),OK了!
总结:VS2008及以前的配置中,默认都是选择了类似的配置,VS2008中是:Use Precompiled Header (/Yu)
中怎么写个简单的C++程序?
#include &iostream&
void main(void)
count&&"hi";
在vs2010中编写以上程序
2&IntelliSense: identifier "count" is
undefined&c:\users\shiechian\documents\visual
2010\projects\test\test\test.cpp&4&2&test
运行结果:
1&------ Build started: Project: test,
Configuration: Debug Win32 ------
1&c:\users\shiechian\documents\visual studio
2010\projects\test\test\test.cpp(4): error C2065: 'count' :
undeclared identifier
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0
skipped ==========
还出现这个
这是什么原因?
相关答复:
cout写错了,同时还没有添加名字std(标准c++里是需要的,vc6与标准不符,后来新版的vs都与标准一样了)
#include &iostream&
void main(void)
cout&&"hi";
vs 2010中VC++的6个新特点
一、Visual
C++工程及其构建系统
(一)MSBuild
如今,Visual C++处理方案和项目都运用MSBuild执行构建,从而取代了原来的构建工具VCBUILD.exe。关于其他的Visual Studio言语和项目类型,MSBuild提供了一样的灵敏性,可扩展性和基于XML的构建支持工具。为此,Visual C++项目文件如今也运用了盛行的XML文件格式,并具有.vcxproj文件扩展名。另外,从前期版本的Visual Studio中提供的项目文件将被自动转换为新的文件格式。相关MSBuild工具的更多信息,请参见文章“MSBuild(Visual C++)”。
(二)VC++目录
如今,VC++目录配置位于两个地点。你可以运用项目属性页来配置每个项目对应的VC++目录,也可以够运用属性维护器和一个属性表来配置全局性的并且使每个配置值对应的VC++目录。
(三)工程依托性
在VC++前期版本中,您可以定义存储在同一个处理方案中的项目之间的依托性。如今在这些处理方案转换为新的项目文件格式后,相应的依托性也被转换为项目到项目的援用。这种改变将会影响使用顺序,由于处理方案依托性和项目到项目的援用是不一样的。
(四)宏与环境变量
新引入的_ITERATOR_DEBUG_LEVEL宏支持针对迭代器调用调试支持。你可以运用这个宏来替代较老的_SECURE_SCL和_HAS_ITERATOR_DEBUGGING宏。
二、Visual
(一)/analyze劝诫
绝大非少数的/analyze(企业代码剖析)劝诫如今都以前被从CRT,MFC和ATL库中移除。
(二)重启动维护器
假设您的使用系统出现不测关闭或重新启动情况的话,重启维护器支持自动保管文件偏重新启动您的使用顺序。比如,当您的使用顺序由于自动更新而关闭时您可以运用重启维护器来再次启动这个使用顺序。欲明白更多相关如何将使用顺序配置为运用启动维护器的信息,请参见《如何:添加剧启动维护支持》一文。
新引入的CTaskDialog类可用于替代规范的AfxMessageBox音讯框。CTaskDialog类可以显示和搜集比规范的音讯框更多的信息。
(四)新的ATL宏
又有新的宏被添加到ATL宏库中,以便进一步扩展原有的PROP_ENTRY_TYPE和PROP_ENTRY_TYPE_EX宏的现有功用。另外新添加的两个宏PROP_ENTRY_INTERFACE和PROP_ENTRY_INTERFACE_EX支持你添加一个有效的CLSID列表。开头一对新宏PROP_ENTRY_INTERFACE_CALLBACK和PROP_ENTRY_INTERFACE_CALLBACK_EX支持您指定一个回调函数,以确定某个CLSID能无法是有效的。
新引入的SafeInt库可以确保执行安全的算术运算,从而有效地防止了经常出现的整数溢出疑问。这个库还支持比拟不一样类型的整数。
三、集成开发环境(IDE)
(一)改进的错误智能感知支持
在Visual Studio 2010中,集成开发环境(IDE)支持更好地检测能够招致丧失智能感知支持的错误,并在这些内容下面显示以红色波浪下划线。此外,集成开发环境还支持把智能感知的错误输出到错误列表窗口中。要想观察招致疑问的相关代码,你只须要双击错误列表窗口中的错误即可。
(二)#include自动完成特征
集成开发环境还支持#include主要字的自动完成。当您键入#include时,集成开发环境将自动树立一个包括有效的头文件的下拉列表供您挑选。假设你继续输入一个文件名,集成开发环境将自动依据您的输入加以过滤。在任什么时辰候,你都可以依据这个列表来挑选你想要包括的文件。显然,这一功用可以让您高速地包括那些尚不确切知晓文件名的文件。
四、Visual
C++编译器和链接器
(一)auto主要字
如今,auto主要字有了新的用途。你可以运用默许的auto主要字来声明一个变量的类型是从此变量声明的原始化表达式中推导出的。而新的/Zc:auto编译器选项支持调用auto主要字的新意义或以前的意义。
(二)decltype操作符
decltype操作符可以前往一个指定表达式的类型。因而,你可以运用decltype操作符并结合运用auto主要字来声明一个庞杂类型或许是仅为编译器所知晓的类型。比如,你可以运用这样的组合来声明一个模板函数,而此模板函数的前往类型取决于其模板参数的类型。或许,你还可以声明一个模板函数,而此模板函数调用另一个函数,然后前往被调用函数的前往类型。
(三)Lambda表达式
Lambda函数有一个函数体,但没有函数名。Lambda函数把函数指针和函数对象两者的最好特征组合到了一同。
你可以运用一个lambda函数来作为模板函数参数以替代一个函数对象,或许结合运用auto主要字来声明一个lambda类型的变量。
(四)Rvalue援用
右值rvalue援用声明符(&&)可以声明对一个右值rvalue的援用。右值援用可以使你运用静态语义(move semantics)和完备转发(perfect
forwarding)来编写更有效的构造函数,普通函数和模板。
(五)static_assert声明
static_assert声明有助于在编译时测试软件中的断言,这不一样于其他那些在运转时执行测试的断言机制。假设断言失败,则编译失败并显示出指定的错误信息。
(六)nullptr和__nullptr主要字
Visual C++编译器准许您在本机代码或托管代码中运用nullptr主要字。nullptr主要字用于指出一个对象句柄、内部指针或本地指针类型并不指向一个对象。当您运用/clr编译器选项时,编译器将把nullptr解释为托管代码,而在不运用/clr选项时解释为本机代码。
微软特定的__nullptr主要字与nullptr主要字意思类似,但它只适用于本机代码。假设您运用/clr编译器选项编译本机C/C ++代码,那么编译器无法确定nullptr主要字是一个本地主要字照旧托管主要字。为了使编译器更清楚地了解你的意图,你可以运用nullptr主要字来指定现在操作为托管操作,而运用__nullptr主要字来指定现在操作为本地操作。
(七)/Zc:trigraphs编译器选项
默许情况下,三字符组(trigraphs)支持是被禁用的。在这种情况下,你可以运用/Z?:
trigraphs编译器选项来启用三字符组支持。
一个三字符组由两个延续的问号后面跟着一个奇特的字符组成。编译器可以运用相应的标点符号来取代这个三字符组。比如,编译器可以运用#(数字符号)字符替代三字符组??=。你还可以在C源文件中运用三字符组,由于这些文件中运用的是不会包括某些标点字符的字符集。
(八)新的基于配置的优化选项
PogoSafeMode主要字是一个新的基于配置的优化选项。你可以运用PogoSafeMode主要字来指定你想运用安全方式照旧高速方式来优化您的使用顺序。留意,安全方式是线程安全的,但它比高速方式慢一些。高速方式是默许的优化行为。
(九)新的通用言语运转时(CLR)选项/clr:nostdlib
新引入了一个通用言语运转时(CLR)选项/clr:nostdlib。假设你的系统中包括了类似库的不一样版本,那么编译器将显示错误提示。这个新的选项可以使你扫除默许的CLR库,从而使你的顺序可以运用一个特定的版本。
(十)新的pragma指令detect_mistmatch
新引入的pragma指令detect_mismatch可以支持您运用类似的称号来替换您的文件中的某个特定的标志(相关于其他的标志)。假设类似的称号拥有多个值,衔接器会发出错误提示。
(一)ATL控件向导
在ATL控件向导不再自动填充ProgID字段。假设一个ATL控件没有一个ProgID,那么其他工具能够无法运用这个控件。这样的一个工具的例子是“Insert Active
Control”对话框。相关此对话框的更多信息,请参考文章“插入ActiveX控件对话框”。
(二)MFC类向导
Visual Studio 2010中重新引入了MFC类向导。如今,您可以在处理方案的任何地点调用类向导。MFC类向导准许您添加类、音讯和变量,而不用手动修正单个的代码文件。
六、微软宏汇编器参考
新引入的YMMWord数据类型支持AVX(英特尔高级矢量扩展)指令中包括的256位的多媒体操作数。
Visual C++延伸阅读
Visual C++是微软公司开发的一个IDE(集成开发环境),换句话说,就是运用c++的一个开发平台.有些软件就是这个编出来的...另外尚有VB,VF.只是运用不一样言语...
但是,VC++是Windows平台上的C++编程环境,学习VC要明白许多Windows平台的特征并且还要掌握MFC、ATL、COM等的知识,难度比拟大。Windows下编程须要明白Windows的音讯机制以及回调(callback)函数的原理;MFC是Win32API的包装类,须要了解文档视图类的结构,窗口类的结构,音讯流向等等;COM是代码共享的二进制规范,须要掌握其基本原理等等。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

我要回帖

更多关于 while应输入声明 的文章

 

随机推荐