<@乌龟:>从c#到c++的一些容易出现的困惑(1)
2009-08-13 01:45
344 查看
0.前言
最近在学习用c++写一下3D引擎(废话一下,叫做WuguiEngine,首先Wugui是我的外号,也是代表这个引擎很粗糙,速度很慢,呵呵.之后等引擎成熟一点我再写写相关的一些文章).这几天写起来感觉c++好多地方和c#区别很大,这里大概写写这两天碰到的一些问题,也许是从c#转c++的时候的一些通病,对c++ OO新手也有一定的帮助.
另外在本文中,多数是将类拆分为.h文件和.cpp文件这样对于工程来说更好管理.
另外阅读本文需要一定的c++基础,本文主要是一些杂碎的心得,而不是完整的教学
1.头文件
头文件是c#没有的内容,所以用好头文件是学习c++首先需要解决的一个内容.头文件在正常的时候应该是只放上定义,实现代码应该写在同名的cpp文件里面
1) 重复包含:头文件A.H被B.H与C.H同时包含,则在编译的时候,A.H里面定义的代码将被定义两次,造成"Symbol already defined",在这种时候应该在.H文件头尾处加上下列的代码就可以解决这个问题:
其实#ifndef与#define的内容只要对于每个.h文件是唯一的就好了.
2)嵌套包含:在A.H(class A)中使用了class B,又在B.H(class B)中使用了class A,这个时候需要在类的前面加入前置定义,下面的代码:
这点说明,头文件并不能代替前置声明,对于有namespace的情况,前置声明更加复杂,下一小节将谈谈这点内容.
3)一些经验:(来自网上)
头文件包含其实是一想很烦琐的工作,不但我们看着累,编译器编译的时候也很累,再加上头文件中常常出现的宏定义。感觉各种宏定义的展开是非常耗时间的,远不如自定义函数来得速度。我仅就不同头文件、源文件间的句则结构问题提出两点原则,仅供参考:
第一个原则应该是,如果可以不包含头文件,那就不要包含了。这时候前置声明可以解决问题。如果使用的仅仅是一个类的指针,没有使用这个类的具体对象(非指针),也没有访问到类的具体成员,那么前置声明就可以了。因为指针这一数据类型的大小是特定的,编译器可以获知。
第二个原则应该是,尽量在CPP文件中包含头文件,而非在头文件中。假设类A的一个成员是是一个指向类B的指针,在类A的头文件中使用了类B的前置声明并便宜成功,那么在A的实现中我们需要访问B的具体成员,因此需要包含头文件,那么我们应该在类A的实现部分(CPP文件)包含类B的头文件而非声明部分(H文件)。
第三个原则是(我自己觉得的):尽量对引用的其他的类都加上前置声明.这样不仅使得程序的结构更加清楚,而且使得可以少出一些编译错误
2.namespace
上面的代码简单的说明了namespace的一些基本的用法,该段代码取自BaseGame.H的文件,在class BaseGame中,用到了来自不同的命名空间下的IUpdatable, GraphicsDevice等类.具体的内容可以先看看代码里面的注释
在需要在BaseGame.H中包含这些类的头文件的同时,需要加上这些类的前置声明,如果不加会出现一些诡异的编译错误(有些问题可能也是在于我比较不了解c++),反正我觉得使用using namespace ...并不是一个很好的做法,不如using XXX::YYY::ZZZ来得准确(这里也希望高手来拍砖指点一下.
3.继承
1)virtual继承
在继承接口(只具有纯虚函数的类)的时候最好为继承的方法加上virtual的说明(参考上面一段的代码,IUpdatable与IDisposed就是接口,主要的原因是在于c++的多重继承机制,假如A与B都继承自IInterface,C又继承自A,B,那么普通的继承方式则会在C类中保存两个IInterface的副本.
而virtual继承就有区别了,virutal继承告诉编译器:"我继承的是一个纯接口!",这样只会保存一个副本.
2)多种继承方式
大家知道,C++对于继承的方式有很多的修饰,有public, private, protected,而且还可以加入virtual关键字.在c++中,public继承与在c#中的继承是没有区别的,而其他几种继承是具有理论的价值,而实际中的应用得非常非常的少,这里就不再赘述了.
3)实验:重写虚函数
下面做一个简单的实验,看看c++与c#在重写虚函数时候调用基类的虚函数的时候的情况,大家自己可以对比一下c#.测试代码:
输出:
Hello Earth
Hello World
而在将Base的Func()改为纯虚函数之后,在Base* p1 = new Base()这句话上出现编译错误,提示不能实例化抽象类. 而对于p2的调用时正常的
下面继续深入我们的实验,三个类依次继承,爷爷类具有一个纯虚函数,爸爸类什么都不写,儿子类重写该函数,都使用virtual public继承:
输出结果都是"Hello World"
今天也有点晚了,明天还要坐火车,先写到这里,各位晚安,欢迎提出意见 :-D
另外请教大家一下,本文是liveWriter写的,使用了Code Snippet插件,在编辑的时候看起来是正常的,但是发布上来却是花花绿绿的,而且按编辑之后看起来也是正常的,请教一下达人这个怎么解决
最近在学习用c++写一下3D引擎(废话一下,叫做WuguiEngine,首先Wugui是我的外号,也是代表这个引擎很粗糙,速度很慢,呵呵.之后等引擎成熟一点我再写写相关的一些文章).这几天写起来感觉c++好多地方和c#区别很大,这里大概写写这两天碰到的一些问题,也许是从c#转c++的时候的一些通病,对c++ OO新手也有一定的帮助.
另外在本文中,多数是将类拆分为.h文件和.cpp文件这样对于工程来说更好管理.
另外阅读本文需要一定的c++基础,本文主要是一些杂碎的心得,而不是完整的教学
1.头文件
头文件是c#没有的内容,所以用好头文件是学习c++首先需要解决的一个内容.头文件在正常的时候应该是只放上定义,实现代码应该写在同名的cpp文件里面
1) 重复包含:头文件A.H被B.H与C.H同时包含,则在编译的时候,A.H里面定义的代码将被定义两次,造成"Symbol already defined",在这种时候应该在.H文件头尾处加上下列的代码就可以解决这个问题:
[code] #ifndef _NAME_H
#define _NAME_H
//add defination
#endif
其实#ifndef与#define的内容只要对于每个.h文件是唯一的就好了.
2)嵌套包含:在A.H(class A)中使用了class B,又在B.H(class B)中使用了class A,这个时候需要在类的前面加入前置定义,下面的代码:
[code] #ifndef _A_H
#define _A_H
#include "B.H"
//前置声明
class B;
class A
{
//add defination
}
#endif
这点说明,头文件并不能代替前置声明,对于有namespace的情况,前置声明更加复杂,下一小节将谈谈这点内容.
3)一些经验:(来自网上)
头文件包含其实是一想很烦琐的工作,不但我们看着累,编译器编译的时候也很累,再加上头文件中常常出现的宏定义。感觉各种宏定义的展开是非常耗时间的,远不如自定义函数来得速度。我仅就不同头文件、源文件间的句则结构问题提出两点原则,仅供参考:
第一个原则应该是,如果可以不包含头文件,那就不要包含了。这时候前置声明可以解决问题。如果使用的仅仅是一个类的指针,没有使用这个类的具体对象(非指针),也没有访问到类的具体成员,那么前置声明就可以了。因为指针这一数据类型的大小是特定的,编译器可以获知。
第二个原则应该是,尽量在CPP文件中包含头文件,而非在头文件中。假设类A的一个成员是是一个指向类B的指针,在类A的头文件中使用了类B的前置声明并便宜成功,那么在A的实现中我们需要访问B的具体成员,因此需要包含头文件,那么我们应该在类A的实现部分(CPP文件)包含类B的头文件而非声明部分(H文件)。
第三个原则是(我自己觉得的):尽量对引用的其他的类都加上前置声明.这样不仅使得程序的结构更加清楚,而且使得可以少出一些编译错误
2.namespace
[code] #ifndef _BASEGAME_H
#define _BASEGAME_H
namespace WuguiEngine
{
//前置声明
class IUpdatable;
//前置声明
namespace Graphics
{
class GraphicsDevice;
}
//前置声明
namespace Core
{
class TimeUsed;
}
//简化的写法,GraphicsDevice可以在下面的代码中直接使用
//GraphicsDevice与WuguiEngine::Graphics::GraphicsDevice等价
//注意:如果不加上下面这行,WuguiEngine::Graphics不能省略
using Graphics::GraphicsDevice;
class BaseGame : virtual public IDisposed,
virtual public IRenderable
{
// add definition
}
}
#endif
上面的代码简单的说明了namespace的一些基本的用法,该段代码取自BaseGame.H的文件,在class BaseGame中,用到了来自不同的命名空间下的IUpdatable, GraphicsDevice等类.具体的内容可以先看看代码里面的注释
在需要在BaseGame.H中包含这些类的头文件的同时,需要加上这些类的前置声明,如果不加会出现一些诡异的编译错误(有些问题可能也是在于我比较不了解c++),反正我觉得使用using namespace ...并不是一个很好的做法,不如using XXX::YYY::ZZZ来得准确(这里也希望高手来拍砖指点一下.
3.继承
1)virtual继承
在继承接口(只具有纯虚函数的类)的时候最好为继承的方法加上virtual的说明(参考上面一段的代码,IUpdatable与IDisposed就是接口,主要的原因是在于c++的多重继承机制,假如A与B都继承自IInterface,C又继承自A,B,那么普通的继承方式则会在C类中保存两个IInterface的副本.
而virtual继承就有区别了,virutal继承告诉编译器:"我继承的是一个纯接口!",这样只会保存一个副本.
2)多种继承方式
大家知道,C++对于继承的方式有很多的修饰,有public, private, protected,而且还可以加入virtual关键字.在c++中,public继承与在c#中的继承是没有区别的,而其他几种继承是具有理论的价值,而实际中的应用得非常非常的少,这里就不再赘述了.
3)实验:重写虚函数
下面做一个简单的实验,看看c++与c#在重写虚函数时候调用基类的虚函数的时候的情况,大家自己可以对比一下c#.测试代码:
[code] #include <iostream>
using namespace std;
class Base
{
public:
virtual void Func(){ cout << "Hello Earth" << endl;}
};
class Derived : public Base
{
public:
virtual void Func(){ cout << "Hello World" << endl;}
};
int main()
{
Base* p1 = new Base();
p1->Func();
Base* p2 = new Derived();
p2->Func();
}
输出:
Hello Earth
Hello World
而在将Base的Func()改为纯虚函数之后,在Base* p1 = new Base()这句话上出现编译错误,提示不能实例化抽象类. 而对于p2的调用时正常的
下面继续深入我们的实验,三个类依次继承,爷爷类具有一个纯虚函数,爸爸类什么都不写,儿子类重写该函数,都使用virtual public继承:
[code] #include <iostream>
using namespace std;
class Base
{
public:
virtual void Func() = 0 ;
};
class Derived : virtual public Base
{
};
class DerivedDerived : virtual public Derived
{
public:
virtual void Func(){ cout << "Hello World" << endl;}
};
int main()
{
Base* p1 = new DerivedDerived();
Derived* p2 = new DerivedDerived();
p1->Func();
p2->Func();
}
输出结果都是"Hello World"
今天也有点晚了,明天还要坐火车,先写到这里,各位晚安,欢迎提出意见 :-D
另外请教大家一下,本文是liveWriter写的,使用了Code Snippet插件,在编辑的时候看起来是正常的,但是发布上来却是花花绿绿的,而且按编辑之后看起来也是正常的,请教一下达人这个怎么解决
相关文章推荐
- <@乌龟:>从c#到c++的一些容易出现的困惑(2)
- C#的一些小技术 <1>
- iOS开发中,对请求数据出现<null>的一些简单处理
- <From C++ To C#> 更强大的值类型
- 在C#里调用C++的dll时需要注意的一些问题<转>
- <转>c#调用C++DLL类型转换
- <转>C# 4.0 为泛型编程引入了 协变 和 逆变 支持,这是个不错的福利,能省掉以往的一些麻烦。不过当前(Beta2)仅支持泛型接口和泛型委托。
- <@乌龟:>C++/CLI语言Specification阅读笔记(1)
- <android> 常用但容易忘记的一些代码和技巧 汇总(个人笔记)
- <C#> 泛型、委托和一些易混淆的定义(1)
- <From C++ To C#> 做猜数一个小游戏
- private:c/c++ 我的一些容易出错的地方 =>持续更新
- <转>C# 4.0 为泛型编程引入了 协变 和 逆变 支持,这是个不错的福利,能省掉以往的一些麻烦。不过当前(Beta2)仅支持泛型接口和泛型委托。
- <From C++ To C#> 我们先定义一些局部变量
- <3>C++ Primer字面值常量
- <constructor-arg>标签不可出现属性“name”可能是你spring版本过低
- C# 调用C++dll出现的问题。
- c#中的一些容易混淆的概念
- <转>C++学得再好,也无法凭这个找到好工作
- <<C++ Primer>>中文版 重载箭头运算符的理解