440 likes | 585 Views
C++. WindyWinter windy@ream.at. 课程适用性. 迫于时间所限,本课程只能面向 C 语言程序设计基础较好的 同学。 希望 经过短期训练, 让大家能 读 懂 WrightEagleBASE 中涉及语言特性的部分。 合格的 C++ 入门和参考书籍 有 C ++ Primer 、 The C++ Programming Language 和 Thinking in C ++ 。. C 语言复习. 整型. 浮点型. 浮点数也是离散的。
E N D
C++ WindyWinter windy@ream.at
课程适用性 • 迫于时间所限,本课程只能面向C语言程序设计基础较好的同学。 • 希望经过短期训练,让大家能读懂WrightEagleBASE中涉及语言特性的部分。 • 合格的C++入门和参考书籍有C++ Primer、The C++ Programming Language和Thinking in C++。
浮点型 • 浮点数也是离散的。 • 浮点数的内部表示也是二进制,比如float,一共32个二进制位,所以不可能表示出超过2^32个数字。 • IEEE 754 • 浮点数的比较 • if (a == b) X • if (fabs(a-b) < FLT_EPSILON)O • if (fabs(a-b) < DBL_EPSILON) O
其它类型 • _Bool • float _Complex / double _Complex • 衍生类型 • array、structure、union、function、pointer • 类型限定符 • const、volitile、restricted • void类型 • 取值集合为空集的类型
其它类型 • bool • complex<float> / complex <double> • 衍生类型 • array、structure、union、function、pointer • 类型限定符 • const、volitile、restricted • void类型 • 取值集合为空集的类型
指针 • float * const float指针常量类型 • const float * 常量float类型的指针类型 • DecisionDataDerived** 指针的指针 • E1[E2] (*((E1)+(E2))) • A[3] *(A+3) • 3[A] *(3+A) = *(A+3)
const和#define • #define属于预编译的范围,“符号替换”;const定义的是“不能修改的”变量。 • #define只能定义有“字面值”的常量;const可以定义数组、结构体和union常量。 • enum可以定义整数常量,一般用于互相关联的一组整数。 • enumPlayMode{}
预编译指令 • #include 后面不是“字符串”,是用引号或者<>括起来的文件名。 • 条件编译 • #define 符号 • #undef符号 • #if / #elif字面值常量表达式 • #ifdef / #ifndef / #else 符号 • 不符合#if条件的代码段编译器不编译
预编译指令 • #define中“#”的用法 • #x 将x变成字符串 • x##y 将x与y连接起来 • #define TeammateFormationTactic(TacticName) (*(FormationTactic##TacticName *)mFormation.GetTeammateTactic(FTT_##TacticName)) • TeammateFormationTactic(KickOffPosition) *(FormationTacticKickOffPosition*)mFormation.GetTeammateTactic(FTT_KickOffPosition))
其它 • sizeof运算符 • size_tfsize(int n) { char t[n+3]; return sizeof(t);}fsize(10) -> 13 • inline函数 • static变量
即时声明 • C语言要求所有变量的声明必须在实意语句之前,也就是在所有{}的外面,或者是每对{}的最前面。 • C++没有了这样的限制,变量只要遵循先声明后使用的原则就可以了,不再要求必须放在什么地方。我们可以在for语句头部塞上一个inti(0),for (inti(0); i < 10; ++i)。 • “inti(0)”里的(0)是指将i初始化为0,作用相当于inti=0。
引用 • 引用(reference)是C++新定义的一种复合类型,其本意可以理解为变量的“别名(alternate name)”。 • 声明/定义一个引用:int a;int & r = a; • r被定义为a的引用后,r和a可以被认为是同一个变量。 • 引用的主要用在函数形参中(作用与指针相仿): • 避免传递规模巨大的实参; • 将形参的值返回。
引用 • void BehaviorAttackPlanner::Plan(std::list<ActiveBehavior> & behavior_list) • PlayerState & player = const_cast<PlayerState &>(*mpWorldState->GetPlayerList()[i]);player.GetPos();
类型转换 • C++继承了原有的C语言的隐式类型转换; • 所有的类型都可以隐式转换为该类型的引用:int => int &,int * => int * &,所有的类型都可以隐式转换为该类型的常量; • 强制类型转换在C++中有了另一类写法: • (type) a xxx_cast<type> a; • static_cast<type>实现与C中类型转换相同的功能; • const_cast<type>去掉表达式的常量性; • 另外还有reinterpret_cast和dynamic_cast
输入输出 • std::cout << a << b << c << d <<std::endl; • Logger::instance().GetTextLogger(“test”) << a << b << c << d << std::endl; • 输出到Logfiles/WrightEagle-X-test.log中。
形参默认值 • 形参允许有默认值,即函数可以声明为如下形式:boolKickBall( Agent & agent, double angle, double speed_out,KickModemode = KM_Hard,int*cycle_left = 0,boolis_shoot = false);
形参默认值 • 调用的时候可以不写有默认值的参数 • KickBall(agent, 0.0, 3.0); • KickBall(agent, 0.0, 3.0, KM_Quick); • KickBall(agent, 0.0, 3.0, KM_Quick, &cycle); • KickBall(agent, 0.0, 3.0, KM_Quick, &cycle, true); • 都是合法的 • 没写的参数,实参值就是默认值。
函数重载 • 允许不同的函数有相同的函数名。 • “不同的函数”是指形参的类型、数目或返回值的类型不同的函数。 • boolGoToPoint(Agent & agent, Vector pos, double buffer = 0.5, double power = 100.0, boolcan_inverse = true, boolturn_first = false); • void GoToPoint(Agent & agent, AtomicAction & act, Vector pos, double buffer = 0.5, double power = 100.0, boolcan_inverse = true, boolturn_first = false);
new和delete运算符 • C语言用malloc和free。 • C++用new和delete。 • client = new Player; • delete client; • mWeight = new real**[mLayers]; • delete[] mWeight;
类 • 类是C++的新特性,为适应面向对象的程序设计而提出; • 在C中,已经有了结构体的概念; • 类与结构体的最大不同之处在于——不仅可以包含成员变量(常量),还可以包含成员函数。 • 当然,类还包括一些其他的特性: • 成员变量、成员函数的访问权限; • 构造函数; • 析构函数; • 拷贝构造函数; • 隐式类型转换; • ……
一个著名的类 class BaseState { public: BaseState(); BaseState(constBaseState & o) {} void UpdatePos(const Vector & pos , int delay = 0, double conf = 1); const Vector & GetPos() const { return mPos.mValue; } intGetPosDelay() const { return mPos.mCycleDelay; } const double & GetPosConf() const { return mPos.mConf; } void UpdatePosEps(double eps) { mPosEps = eps;} const double & GetPosEps() const { return mPosEps;} private: StateValue<Vector> mPos; double mPosEps; };
成员函数的定义 • 成员函数可以直接在类定义里定义,也可以单独在外面定义。 • void BaseState::UpdatePos(const Vector & pos, int delay, double conf){mPos.mValue = pos;mPos.mCycleDelay = delay;mPos.mConf = conf;}
this指针 • 每个类都有一个特殊的“成员”——this,表示对象自身; • this只能在该类的内部使用,与不指明this没有区别: • this->mPos mPos • this->mPosConf mPosConf • void UpdatePosEps(double mPosEps) { this->mPosEps= mPosEps;}
成员函数的const属性 • constVector & GetPos() const • GetPos不能更改任何成员变量的值,在函数内部 • this指针变成指向常量的指针; • 任何成员变量被附加const属性。 • 这种声明主要用于指明该函数不会更改成员变量的值。
构造函数 • 没有返回值类型,与类同名的函数被认为是构造函数。BaseState() • 它的作用就是——构造一个对象。 • BaseState() : mPosEps(10000);{mPos.mValue= Vector(10000 , 10000);}
构造函数 • BaseState(const BaseState & o) { mPos = o.mPos;} • 如果将某个构造函数声明为private,则这个构造函数将无法使用。一般来说,这样做的目的是阻止编译器生成缺省的构造函数。 • class Agent{ Agent(Agent &); ……};
构造函数 • class Line {public: Line(constRay &r); ……}; • 只带有一个参数的构造函数表明了一种可能的隐式类型转换。
析构函数 • 没有返回值,名字是~<class name>,没有参数的函数是析构函数。构造函数可以有多个,析构函数只能有一个。 • 它的作用是销毁一个对象。 • 如果没有声明析构函数,编译器将合成默认析构函数 • 对于内置类型,释放其空间; • 对于类类型,调用其析构函数。 • 实际上,上面两步是编译器附加在任何析构函数最后的两步。因为没有办法显式“释放空间”和调用析构函数。
new和delete运算符 • mallac/free和new/delete的区别在于 • 前者只涉及内存分配 • 后者不仅分配/释放空间,还要创建/销毁对象。 • new运算符首先分配对象占据的空间,然后在其上调用构造函数;delete首先完成对象本身的销毁步骤,然后释放空间。
静态成员 • static关键字也可以修饰类的成员 • class Dasher{public:static Dasher & instance();static Array<double, 8> DASH_DIR;static Array<int, 8> ANTI_DIR_IDX;static Array<double, 8> DIR_RATE;static intGetDashDirIdx(constAngleDeg & dir);}
静态成员 • 被修饰的成员叫做类的静态成员,是这个类的属性,不是某个对象的属性。 • 访问用:: • Dasher::instance() • Dasher::GetDashDirIdx()
运算符重载 • C++不仅提供了对函数的重载,也提供了对运算符的重载。运算符可以视为特殊的函数。 • 双目运算符 • Time Time::operator-(constinta); • int Time::operator-(const Time &a); • 单目运算符 • _Tp & ObjectArray::operator[](constObjectIndex & i) • 特别的运算符重载:++、--。
运算符重载 • 还有一类特殊的运算符也可以被重载: • opetator T()operator int();operator xxx(); • 这样的运算符必须是某个类的成员函数,它为这个类提供向特定类型的隐式类型转换。 • 更多的很多情况下,运算符重载是一个复杂的工程。在你真正掌握重载之前,请慎用。
继承与派生 class MobileState: public BaseState { void UpdateVel(const Vector & vel , int delay = 0, double conf = 1);const Vector & GetVel() const { return mVel.mValue; }intGetVelDelay() const { return mVel.mCycleDelay; } ……};
继承与派生 • 前面定义了BaseState类的一个派生类MobileState,BaseState是MobileState的基类。 • 它将获得BaseState类的一切成员,还另外附加了很多Vel相关的成员。 • “一切成员”,不包括基类的构造函数、析构函数、new运算符和=运算符。但派生类中可以访问他们。 • 派生类对象可以隐式转换为基类类型;派生类类型的指针可以隐式转换为基类类型的指针 • BaseState * object = new MobileState;
虚函数与多态 • 在声明某个成员函数时加上virtual修饰符,表示允许派生类重载该函数;在声明析构函数时加上virtual修饰符,产生特殊效果。 • class Client { virtual ~Client(); virtual void RunNormal(); ……};
虚函数与多态 • Client client= new Player;client->RunNormal(); • virtual void Run() = 0;
友元 • class Agent{ friend class Client; ……}; • 友元是一个声明。友元不是类的成员。