620 likes | 766 Views
C++. 课程适用性. 迫于时间所限,本课程只能面向 C 语言程序设计基础较好的 同学。 希望 经过短期训练, 让大家能 读 懂 WrightEagleBASE 中涉及语言特性的部分。 合格的 C++ 入门和参考书籍 有 C ++ Primer 、 The C++ Programming Language 和 Thinking in C ++ 。. C 语言复习. 整型. 浮点型. 浮点数也是离散的。 浮点数的内部表示也是二进制,比如 float ,一共 32 个二进制位, 所以不可能 表示出超过 2^32 个数字。 IEEE 754 浮点数的比较
E N D
课程适用性 • 迫于时间所限,本课程只能面向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(复数) ——c99 • 衍生类型 • array、structure、union、function、pointer • 类型限定符 • const、volatile、restrict • void类型 • 取值集合为空集的类型
Union类型 union a{ double x; inti; }; ——X与i公用一个内存位置,同一时间只能保存一个成员。
Volatile限定符 • volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。 • 如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象.
Volatile代码示例 short flag;void test(){ do1(); while(flag==0); do2();}
Volatile代码示例 volatile short flag;void test(){ do1(); while(flag==0); do2();}
Const限定符 constint a=10; int * b=&a; 虽然,编译器会报警告“ 警告:初始化丢弃了指针目标类型的限定”,这个意思是,b失去了对目标对象的const的限定。但是通过,并且,可以通过指针b更改它们共同指向的空间。
Const限定符 constint a=10; int b=(int)&a; int * c=(int *)b; 这种写法和上面的效果一样,却连警告都不报,
Restrict限定符 restrict只可以用在指针身上。如果一个指针被restrict修饰,那么就在它(这个指针)和它所指向的对象之间建立了一种特殊的联系──只能用这个指针或者这个指针的表达式来访问这个对象的值.
指针 • 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{}
#define • #define SQUARE(x) x*x 错误 • #define SQUARE(x) (x)*(x) 正确 • 对于第一种:SQUARE(6-1),在编译时会被替换成了6-1*6-1
预编译指令 • #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();
i++与++i编译器实现 • i++的实现是: int temp; temp = i; i = i+1; return temp; • ++i的实现是: i = i+1; return i;
类型转换 • C++继承了原有的C语言的隐式类型转换; • 所有的类型都可以隐式转换为该类型的引用:int => int &,int * => int * &,所有的类型都可以隐式转换为该类型的常量; • 强制类型转换在C++中有了另一类写法: • (type) a xxx_cast<type> a; • static_cast<type>实现与C中类型转换相同的功能; • const_cast<type>去掉表达式的常量性; • 另外还有reinterpret_cast和dynamic_cast
Const_cast<type>解析 • #include <stdio.h> • int main(intargc, char* argv[]) • { • constintic = 100; • constint *pc=⁣ • const_cast<int &>(ic)++; • printf("%d,%d\n", ic, *pc); • return 0; • } 输出: ——100,101 慎用!!!
输入输出 • 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中,已经有了结构体的概念; • 类与结构体的最大不同之处在于——不仅可以包含成员变量(常量),还可以包含成员函数。 • 当然,类还包括一些其他的特性: • 成员变量、成员函数的访问权限; • 构造函数; • 析构函数; • 拷贝构造函数; • 隐式类型转换; • ……
成员属性 • private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问. • protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问 • public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问
静态成员 • 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()
一个著名的类 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: doubleStateValue<Vector> mPos; 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);}
构造函数 • 如果你只是声明一个空类,不做任何事情的话,编译器会自动为你生成一个默认构造函数、一个拷贝默认构造函数、一个默认拷贝赋值操作符和一个默认析构函数。 • 如果定义了构造函数(不管有参无参),则编译器不会生成默认构造函数 • Note:自己定义无参的默认构造函数
构造函数 • 拷贝构造函数与 = 运算符重载 • BaseState(const BaseState & o) { mPos = o.mPos;}//一个拷贝构造函数 • = 运算符重载 • BaseState operator = (const BaseState & o) • {}
构造函数 • 如果将某个构造函数声明为private,则这个构造函数将无法使用。一般来说,这样做的目的是阻止编译器生成缺省的构造函数,或用于singleton模式(一种设计模式,只允许某个类有单个实例)。 • class Agent{ Agent(Agent &); ……};
补充:singleton模式 • class Singleton{ • public: • static Singleton * Instance(){ • if( 0== _instance){ • _instance = new Singleton; • } • return _instance; • } • private: • Singleton(void){ • } • static Singleton* _instance; • };
构造函数 • class Line {public: Line(constRay &r); ……}; • 只带有一个参数的构造函数表明了一种可能的隐式类型转换。 • 例子: • Ray a; • Line b; • b = a;//合法,执行Line(a)转换
构造函数 • 在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数 • 例: • Box(int =10,int =10,int =10); 1Box(); 2Box(int,int); 3 • 若有以下定义语句:Box box1; //是调用上面的第一个默认参数的构造函数,还是第二个默认构造函数Box box2(15,30); //是调用上面的第一个默认参数的构造函数,还是第三个构造函数
析构函数 • 没有返回值,名字是~<class name>,没有参数的函数是析构函数。构造函数可以有多个,析构函数只能有一个。 • 它的作用是销毁一个对象。 • 如果没有声明析构函数,编译器将合成默认析构函数 • 对于内置类型,释放其空间; • 对于类类型,调用其析构函数。 • 实际上,上面两步是编译器附加在任何析构函数最后的两步。 • 显式调用的时候,析构函数相当于的一个普通的成员函数,只是单纯的执行函数体中的语句,不会销毁对象。