文章目录
一、VS2022创建C++项目
1. 相关工具安装
2. 测试
二、【快速】面向对象
1. 标准C++代码
2. 简单的面向对象示例
3. 类与对象
4. 访问成员
5. this指针
6. 构造函数
7. 析构函数
8. 函数重载
9. 应用:老王装枪
三、【快速】封装
1. 三种权限
2. 封装的思想
3. 示例
四、【快速】继承
1. 继承
2. 子类中使用继承的属性
3. 重写
4. 多层继承
5. 多继承
五、【快速】多态
1. 多态
2. 应用:老王开枪
六、面向对象进阶
1. 再论三种权限
2. 构造函数在继承时的注意点
3. 初始化列表
4. override关键字
5. final关键字
6. 友元
7. 纯虚函数和抽象类
8. 类成员函数
9. 拷贝构造函数
10. 内联函数
11. 静态成员
12. 封闭类
七、【快速】泛型编程
1. 泛型编程的概念
2. 模板的概念
3. 函数模板
4. 类模板
八、其他知识点
1. const关键字
2. 引用
3. 容器
4. lambda表达式
5. 异常处理
6. 命名空间
7. 智能指针
8. 运算符重载
9. io操作
九、SFML库相关
1. SFML库的配置
2. 创建图形化窗口
3. 处理事件
4. 显示图形
5. 键盘控制
6. 显示图片
7. 碰撞
十、SFML游戏:狗头大战
一、VS2022创建C++项目
1. 相关工具安装
详见在VS2022中创建一个C++项目_vs2022新建c++项目-CSDN博客。
2. 测试
#include <iostream>
int main()
{
std::cout << "Hello, World!\n" << std::endl;
return 0;
}
二、【快速】面向对象
1. 标准C++代码
// 此处的“标准”说法见仁见智
#include <iostream>
using namespace std;
int main()
{
cout << "Hello, World!\n" << endl;
return 0;
}
2. 简单的面向对象示例
【1】程序说明:定义一个Stu类:包含age和name两种属性、一个set两种属性的方法。在main函数中创建Stu实例对象,调用set方法来测试该方法是否能改变属性。
【2】代码:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string.h> using namespace std; // 定义Stu类 class Stu { public: int age; char name[30]; void set_age_name() { age = 18; strcpy(name, "Swear"); } }; int main() { class Stu boy; boy.set_age_name(); cout << "boy对象的年龄:" << boy.age << ", 姓名:" << boy.name << "\n"; return 0; }
【3】结果:
3. 类与对象
3.1 定义类的格式如下:
class 类名{ 权限: //成员变量 int a; char b; //成员函数 void setValue(){ } } //注意:类名最好用大驼峰,例如MyClass
3.2 创建对象
类似C语言中结构体变量的创建,创建对象也称为“类的实例化”。例如:
// 方式一: class Myclass a; //创建单个对象 class Myclass stu[100]; //创建对象数组 class Myclass* p; //创建指定类的指针变量 // 方式二:使用new,与C语言中的malloc类似 // 该方式创建的返回值是指针类型,需要指针变量来接收 // 该方式创建的对象位于堆区,需要手动delete class MyClass* p = new MyClass(); delete p;
4. 访问成员
存在以下两种方式:
(1) 使用.来访问(当主体是个对象时)
class MyClass a; a.age = 10; //主体为对象,通过.来访问成员变量 a.set_age_name(); //主体为对象,通过.来访问成员函数
(2) 使用->来访问(当主体是个地址时)等价于(*p).age的方式
class MyClass a; class MyClass* p = &a; p->age = 10; //主体为地址,通过->来访问成员变量 p->set_age_name(); //主体为地址,通过->来访问成员函数
5. this指针
(1) 说明:this是一个指针变量,指向被调用的对象本身。
(2) 注意:当成员函数的形参与成员变量同名时,必须使用this才能给成员变量赋值!
(3) 代码示例:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Stu { public: int age; char* name; void setAgeName(int age, char* name) { this->age = age; this->name = name; } }; int main() { class Stu* p = new Stu(); p->setAgeName(17, (char*)"张三"); cout << "姓名:" << p->name << ", 年龄:" << p->age << endl; return 0; }
6. 构造函数
(1) 概念:构造函数是一类特殊的成员函数,会在对象创建时自动调用,一般用于对成员变量的初始化。
(2) 注意:构造函数的函数名与类名相同,且没有返回值,允许被重载。
(3) 代码示例:由于在构造函数中使用了cout,因此在创建对象时会打印相关信息。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Stu { public: char* name; Stu() { name = (char*)"张三"; cout << this->name << endl; } }; int main() { class Stu s; return 0; }
7. 析构函数
(1) 概念:析构函数是一类特殊的成员函数,会在对象销毁时自动调用,一般用于对某些资源的释放,例如socket、打开的文件close操作等。
(2) 注意:构造函数的函数名与类名相同,同时需要在函数名前~,其没有返回值,没有形参,不允许被重载。
(3) 代码示例:在main函数结束前,创建的s对象会被销毁,此时就会调用析构函数。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Stu { public: char* name; // 构造函数 Stu() { name = (char*)"张三"; cout << this->name << endl; } // 析构函数 ~Stu() { cout << "析构函数:销毁对象前的操作" << endl; } }; int main() { class Stu s; return 0; }
8. 函数重载
(1) 概念:在C++中,可以存在同名函数,但形参列表不同,这样的形式就是重载。在调用函数时,程序会根据形参列表调用不同的函数。
(2) 注意:C语言中不支持重载。
(3) 代码示例:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; // 函数的重载 void fun() { cout << "我是fun()" << endl; } void fun(int i) { cout << "我是fun(int i)" << endl; } int main() { int i = 0; fun(); fun(i); return 0; }
9. 应用:老王装枪
(1) 来源:C++ 快速上手 配套教程。
(2) 实现效果:老王手拿子弹和枪,将子弹装进枪内,手里只有枪没有子弹。
(3) 算法设计:
- 步骤:
- ①老王拿起枪
- ②老王拿起子弹
- ③老王将子弹装进枪里
- 对象:
- 子弹:【属性】威力【方法】显示威力
- 枪:【属性】型号、子弹【方法】显示型号、装填子弹
- 人:【属性】名字、枪、子弹【方法】显示名字+有无枪+有无子弹、拿起枪、拿起子弹、将子弹装进枪内
(4) 代码:
/* 1.定义子弹类 */ class Bullet { public: // 成员变量 int kill_power; // 成员函数 Bullet(int kill_power) { this->kill_power = kill_power; } void display_info() { cout << "Bullet: 威力为" << this->kill_power << endl; } };
/* 2.定义枪类 */ class Gun { public: // 成员变量 char* type; class Bullet* bullet; // 成员函数 Gun(char* type) { this->type = type; this->bullet = NULL; } void display_info() { cout << "Gun: " << this->type; if (bullet) { cout << ", 子弹: 威力为" << this->bullet->kill_power << endl; } else { cout << ", 子弹: 无" << endl; } } };
/* 3.定义人类 */ class People { public: // 成员变量 char* name; class Gun* gun; class Bullet* bullet; // 成员函数 People(char* name) { this->name = name; this->gun = NULL; this->bullet = NULL; } void display_info() { cout << "name: " << this->name; if (gun) { cout << ", 枪: " << this->gun->type; } else { cout << ", 枪: 无"; } if (bullet) { cout << ", 子弹: 威力为" << this->bullet->kill_power << endl; } else { cout << ", 子弹: 无" << endl; } } void takeGun(class Gun* gun) { this->gun = gun; cout << "老王拿起了枪: " << this->gun->type << endl; } void takeBullet(class Bullet* bullet) { this->bullet = bullet; cout << "老王拿起了子弹: 威力为" << this->bullet->kill_power << endl; } void loadBulletToGun() { // 枪和子弹二缺一都不行 if (this->bullet == NULL || this->gun == NULL) { cout << "装填失败:缺少子弹或枪!" << endl; return ; } // 将子弹的地址赋值给枪,然后将this->bullet置空 cout << "装填成功!老王将威力为" << this->bullet->kill_power << "的子弹装进" << this->gun->type << endl; this->gun->bullet = this->bullet; this->bullet = NULL; } };
/* 主程序 */ #define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; int main() { /******************初始*******************/ cout << "******************初始*******************" << endl; // 1.显示老王信息 class People hero("老王"); hero.display_info(); // 2.显示枪信息 class Gun gun("加特林"); gun.display_info(); // 3.显示子弹信息 class Bullet bullet(10); bullet.display_info(); /******************启动*******************/ cout << "******************启动*******************" << endl; // 1.老王拿起枪 hero.takeGun(&gun); hero.display_info(); // 2.老王拿起子弹 hero.takeBullet(&bullet); hero.display_info(); // 3.老王将子弹装进枪内 hero.loadBulletToGun(); return 0; }
(5) 运行结果:
三、【快速】封装
1. 三种权限
- public:公有的。成员在任何位置可被访问。
- private:私有的。成员仅在当前类中可被访问。
- protected:受保护的。成员在当前类与派生类中可被访问。
- 注意:在定义类时若未写出具体哪种权限,默认为private!
2. 封装的思想
- 封装是C++面向对象的三大特性之一。
- C++提供了三种权限,能够将属性声明为private和protected来避免某些属性的暴露。相应地,通过提供public的set和get方法,外部程序能够修改和获取这些属性。
3. 示例
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class People {
private: //私有属性与方法
double money;
public: //公有属性与方法
int age;
char* name;
void setMoney(double money) {
this->money = money;
}
double getMoney() {
return this->money;
}
};
int main()
{
class People p;
//设置公有属性,直接采用"对象."的方式
p.age = 18;
p.name = "Swear";
//设置私有属性,调用公有的set接口
p.setMoney(666.3);
//获取私有属性,调用公有的get接口
cout << "姓名: " << p.name << " 年龄: " << p.age << " 存款: " << p.getMoney() << endl;
return 0;
}
四、【快速】继承
1. 继承
- 继承是C++面向对象的三大特性之一。
- 子类(派生类)可以继承父类(基类)的属性和方法。
- 优点:减少代码冗余,提高开发效率。
- 子类继承了父类所有的方法,但下列方法除外:
- 父类的构造函数、析构函数和拷贝构造函数。
- 父类的运算符重载。
- 父类的友元函数。
- 示例代码:
-
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; //父类 People class People { private: int age; char* name; public: void setAge(int age) { this->age = age; } int getAge() { return this->age; } void setName(char* name) { this->name = name; } char* getName() { return this->name; } }; //子类 Student class Student : public People { private: double score; public: void setScore(double score) { this->score = score; } double getScore() { return this->score; } }; //子类 Staff class Staff : public People { private: double money; public: void setMoney(double money) { this->money = money; } double getMoney() { return this->money; } }; int main() { //父类People相关操作 class People p; p.setAge(65); p.setName("张三"); cout << "父类:" << p.getName() << ", " << p.getAge() << endl; //子类Student相关操作 class Student s; s.setAge(18); s.setName("学生甲"); s.setScore(96.5); cout << "子类:" << s.getName() << ", " << s.getAge() << ", " << s.getScore() << endl; //子类Staff相关操作 class Staff sta; sta.setAge(35); sta.setName("员工甲"); sta.setMoney(5000.6); cout << "子类:" << sta.getName() << ", " << sta.getAge() << ", " << sta.getMoney() << endl; return 0; }
2. 子类中使用继承的属性
- 子类继承父类后,无需显式写出父类中的属性,也能够调用这些属性(简单理解为:将父类中的属性和方法复制粘贴到子类中)。
- 在定义类时,其内部的方法可以通过"this->xxx"的形式调用父类中的属性。
- 在创建对象后,可以通过"对象."的形式调用public权限的属性和方法。
- 示例代码:
-
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; //父类A class A { private: int num; public: void setNum(int num) { this->num = num; } int getNum() { return this->num; } }; //子类B class B : public A { public: void test() { //子类中调用父类中的public方法 this->setNum(10); cout << this->getNum() << endl; } }; int main() { class B b; b.test();//通过"对象."的方式调用父类中的方法 return 0; }
3. 重写
- 子类继承父类之后,可以对父类中的方法进行重写。
- 重写要求:函数的返回类型、函数名、参数列表完全相同。
- 注意:父类中被重写的函数依然会继承给子类。虽然被子类的重写覆盖了,但通过作用域分辨符(::)可以访问到父类中的函数。
- 示例代码:
-
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; //父类A中存在test()和test(char str)两个函数 //其中两个函数是重载的关系 class A { public: void test() { cout << "A类的test()" << endl; } void test(char str) { cout << "A类的test(char str)" << endl; } }; //子类B继承了A类 //子类B中对父类A中的test()函数进行重写 //子类B一旦重写了父类A中的test()函数,就将父类A中与test()重载的所有函数均覆盖了 class B : public A { public: void test() { cout << "B类的test()" << endl; } }; int main() { class B b; b.test(); //b.test('A'); //无法通过"对象."来调用父类A的test(char str)函数 b.A::test('A');//通过"::"可以调用父类A的test(char str)函数 return 0; }
4. 多层继承
- "C继承B,B继承A"就是多层继承的例子,继承多少代没有限制。
- 示例代码:
-
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; /* A类 */ class A { public: void test() { cout << "A::test()" << endl; } void test1() { cout << "A::test1()" << endl; } }; /* B类,继承A类 */ class B : public A{ public: void test() { cout << "B::test()" << endl; } void test2() { cout << "B::test2()" << endl; } }; /* C类,继承B类 */ class C : public B{ public: void test() { //调用test2(),当前没有从父类B中找 this->test2(); //调用test1(),当前没有从父类B中找,B中没有从父类B的父类A中找 this->test1(); //调用父类B中的test() B::test(); //调用父类B的父类A的test() A::test(); } }; int main() { class C c; c.test(); return 0; }
5. 多继承
- 概念:一个子类可以继承多个父类。
- 注意:当继承的多个父类中存在同名函数时,该子类必须重写该函数,不然会有二义性,导致编译出错。
- 示例代码:
-
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; /* Father类*/ class Father { public: void makeMoney() { cout << "Father::MakeMoney" << endl; } }; /* Mother类*/ class Mother { public: void makeMoney() { cout << "Mother::MakeMoney" << endl; } void doHomeWork() { cout << "Mother::doHomeWork" << endl; } }; /* Son类*/ class Son : public Father, public Mother { public: void makeMoney() { cout << "Son::MakeMoney" << endl; } }; int main() { class Son s; s.doHomeWork();//Son类中没有,去Father和Mother类中找 s.makeMoney(); //由于Son类重写了makeMoney函数,没有二义性 return 0; }
五、【快速】多态
1. 多态
(1) 概念:父类的指针变量指向子类对象,根据子类对象的不同调用不同的方法。
(2) 多态的条件:
- 存在子父类的继承关系。
- 子类重写了父类的方法,且父类中该方法用"virtual"关键字修饰。
- 父类的指针指向子类的对象。
(3) 注意:多态的运行结果在编译器无法确定,只能在运行时确定。
(4) 示例代码:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Father { public: virtual void test() { cout << "Father:test()" << endl; } }; class Son : public Father { public: void test() { cout << "Son:test()" << endl; } }; int main() { //父类指针指向父类对象,则调用父类的test() class Father* p1 = new Father(); p1->test(); //父类指针指向子类对象,则调用子类的test() class Father* p2 = new Son(); p2->test(); return 0; }
2. 应用:老王开枪
(1) 实现效果:老王拥有M16和G36两把武器。老王先使用M16开火,然后使用G36开火。
(2) 对象分析:
- 父类Gun:【方法】开火、弹出子弹
- 子类M16:【方法】弹出子弹时说明是M16枪
- 子类G36:【方法】弹出子弹时说明是G36枪
(3) 代码:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; /* 父类Gun */ class Gun { public: void fire() { cout << "开火" << endl; this->popBullet(); } virtual void popBullet() { cout << "弹出一颗子弹" << endl; } }; /* 子类M16 */ class M16 : public Gun { public: //重写父类的popBullet方法 virtual void popBullet() { cout << "M16:弹出一颗子弹" << endl; } }; /* 子类G36 */ class G36 : public Gun { public: //重写父类的popBullet方法 virtual void popBullet() { cout << "G36:弹出一颗子弹" << endl; } }; /* 人类 */ class People { private: char* name; public: //构造函数 People(char* name) { this->name = name; } //扣动扳机,根据不同的枪执行不同的开火 void pressTrigger(class Gun* gun) { cout << "扣动扳机" << endl; gun->fire(); } }; int main() { /* 1.创建对象 */ class People hero("老王"); class Gun* m16 = new M16(); class Gun* g36 = new G36(); /* 2.老王使用M16开火 */ hero.pressTrigger(m16); cout << "--------------------" << endl; /* 3.老王使用G36开火 */ hero.pressTrigger(g36); return 0; }
(4) 运行结果:
六、面向对象进阶
1. 再论三种权限
1.1 调用成员变量或成员函数时不同:
- private和protected权限修饰的属性和方法无法被主函数中的对象调用。
- public权限修饰的属性和方法在工程内的任意位置都能调用。
1.2 继承时不同:
- private修饰的属性和方法不能被访问。
- public和protected修饰的属性和方法会被继承。
1.3 继承时成员权限被修改:
- 在写类继承语句时,针对"class B : xxxxx A"当中的xxxxx权限,继承父类中的属性和方法权限时会与该权限进行比较。若xxxxx权限更加严格,那么所继承的属性和方法的权限就会被修改为xxxxx权限。
- 例如:class B : private A 中的private最严格,无论所继承B中的属性和方法是什么权限的,都会被修改为private。
2. 构造函数在继承时的注意点
创建子类对象时会先调用父类无参数的构造函数,再调用子类的构造函数。这是因为子类继承父类时,父类中的private属性和方法无法被使用,但是父类中可能存在某些protcted和public方法调用了这些属性和方法。若不初始化这些属性,可能导致一些错误。子类构造函数总是需要调用一个父类构造函数,当父类没有无参数的构造函数时,就必须显示指明调用哪一个构造函数。
3. 初始化列表
3.1 初始化列表:在构造函数中,一种对成员变量初始化的特殊写法。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class B { private: int a; int b; public: //初始化列表写法如下,将形参的num1和num2给属性a和b赋值 B(int num1, int num2) : a(num1), b(num2){ cout << "B类: " << this->a << ", " << this->b << endl; } }; int main() { class B boy(11,12); return 0; }
3.2 调用父类的构造函数:在创建子类对象时,会调用父类构造函数。若父类没有无参的构造函数,需要指定对应的构造函数。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class A { public: A(int i) { cout << "A(int i)" << endl; } A(int a, int b) { cout << "A(int a, int b)" << endl; } }; class B : public A { public: //调用父类中指定的构造函数 B() : A(11){ cout << "B()" << endl; } B(int i) : A(11, 22) { cout << "B(int i)" << endl; } }; int main() { class B boy; return 0; }
3.3 注意点:
- 成员变量的初始化顺序与初始化列表中列出的变量顺序无关,它只与成员变量在类中定义的先后顺序有关。
- 在多继承中,使用初始化列表调用多个父类的构造函数时,其调用顺序与继承时的父类顺序有关,与初始化列表中的调用顺序无关。
4. override关键字
(1) 作用:子类重写父类的方法时可以添加override,在编译阶段检查是否真的重写了父类函数,起到监督性作用。
(2) 示例代码:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Father { public: virtual void test() { cout << "Father类" << endl; } }; class Son : public Father{ public: virtual void test() override { //表示重写父类的方法 cout << "Son类" << endl; } }; int main() { class Son s; s.test(); return 0; }
5. final关键字
(1) 功能:
- final修饰类:该类为最终类,其他类不能继承该类。
- final修饰虚函数:虚函数不能被重写。
(2) 注意:只有虚函数才能标记为final,其他普通函数不能使用final
(3) 示例代码:
/* 1.final修饰类:其他类不能继承该类 */ class Father final { }; class Son : public Father {//出错 }; /* 2.final修饰虚函数:该函数无法被重写 */ class A { public: virtual void test() final { } }; class B : public A { public: virtual void test() {//出错 } };
6. 友元
(1) 概念:是一种可以访问private和protected成员的方法。"友元"能看成是好朋友、好兄弟。
(2) 使用方式:
- friend关键字修饰函数:那么该函数就是友元函数,能够访问类里private权限的成员变量和成员函数。注意:友元函数不是成员函数。
- friend关键字修饰类:那么该类就是友元类,能够访问private权限的成员变量和成员函数。
(3) 示例代码:
class A { private: int num; public: friend class B; //声明友元类 friend void test(); //声明友元函数 }; void test() { class A a; a.num = 100;//可行,因为A类中将该函数声明为友元函数 } class B { public: void Btest(class A* p) { p->num = 100;//可行,因为A类中将该类声明为友元类 } };
7. 纯虚函数和抽象类
(1) 纯虚函数:在虚函数的基础上,不具体写出函数体的函数就是纯虚函数。其定义方式如下:
virtual void base() = 0;//纯虚函数
(2) 抽象类:包含纯虚函数的类就是抽象类。抽象类不可实例化,必须由子类重写了所有纯虚函数才能实例化子类。例如:
class A {//抽象类,不可实例化 public: virtual void base() = 0;//纯虚函数 }; class B : public A{ public: virtual void base() override {//重写后才可实例化 cout << "hello" << endl; } };
(3) 纯虚函数的作用:在开发团队中,上级只写函数声明,具体实现留给下级人员。
8. 类成员函数
【1】尽管没有使用inline修饰,类成员函数声明时就是内联。
【2】成员函数可以在类中声明,在类外定义。但是在定义时必须使用"类::函数名"的形式。
class Box {
public:
int length;
int width;
int height;
// 成员函数
int getVolume(void);
};
int Box::getVolume() {
return this->length * this->width * this->height;
}
9. 拷贝构造函数
【1】拷贝构造函数是一种特殊的构造函数,它能将已有的同类的对象复制给新创建的对象。
【2】若拷贝构造函数没有定义,编译器会自行定义一个。
【3】如果类中有指针变量,并有动态内存分配,则必须有一个拷贝构造函数。
【4】拷贝构造函数定义形式如下:
class Box {
public:
int length;
//构造函数
Box(int length) : length(length) {
cout << "构造" << endl;
}
//拷贝构造函数
Box(class Box &b) {
this->length = b.length;
cout << "拷贝" << endl;
}
};
10. 内联函数
【1】内联函数的目的:提高函数执行效率。如果一个函数是内联的,那么在编译时会把该函数的代码副本放置在每个调用该函数的地方。
【2】内联函数的定义:使用inline关键字修饰函数。
inline int Max(int a, int b){
return (a>b)?a:b;
}
【3】如果已定义的函数多于一行,编译器会忽略inline限定符。
【4】类中的成员函数默认为内联函数。
【5】由于内联函数是将函数代码副本放置在调用处,因此功能复杂的函数不能被作为内联函数,否则占用的空间很大,并且执行效率也不高。
11. 静态成员
【1】使用static修饰类中的成员变量,则无论创建多少个对象,该成员变量只有一份。
【2】在类中仅声明了静态变量,其定义/初始化在类的外部。例如:
class A {
public:
static int share;//仅声明静态成员变量
};
//初始化静态成员变量
int A::share = 1;
【3】static修饰成员函数,则该函数可以直接通过"类::函数名"来调用。此外,该静态成员函数内部只能使用静态成员变量,不能使用其他静态成员函数和类外部的函数。
12. 封闭类
【1】封闭类是指有对象成员的类。例如:
//封闭类
class Phone {
public:
int price; //普通成员变量
Screen s; //Screen类变量
};
【2】注意事项:
- 封闭类实例化时,先执行所有成员对象的构造函数,再执行封闭类的构造函数。
- 成员对象的构造函数的调用次序,与对象成员在类中的顺序一致。
- 封闭类对象消亡时,先执行封闭类的析构函数,在执行成员对象的析构函数。
【3】示例代码:
#include <iostream>
using namespace std;
class Screen {
public:
int size;
int price;
Screen() : size(0), price(0) {
cout << "Screen构造函数:Screen()" << endl;
}
Screen(int size, int price) : size(size), price(price) {
cout << "Screen构造函数:Screen(int size, int price)" << endl;
}
~Screen() {
cout << "Screen析构函数" << endl;
}
};
class Phone {
public:
int price; //普通成员变量
Screen s; //Screen类变量
Phone() : price(0), s(){
cout << "Phone构造函数:Phone()" << endl;
}
~Phone() {
cout << "Phone析构函数" << endl;
}
};
int main()
{
class Phone p;
return 0;
}
七、【快速】泛型编程
1. 泛型编程的概念
(1) 泛型编程是一种编程范式。泛型编程核心在于编写与特定数据类型无关的代码,使代码能够适用于多种数据类型。
(2) 优点:提高代码的可重用性和灵活性。
(3) 示例代码:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; template <typename XXX> XXX add(XXX a, XXX b) { return a + b; } int main() { cout << add(1, 1) << endl; cout << add(1.5, 2.6) << endl; return 0; }
2. 模板的概念
- 模板是一种将类型参数化的工具。
- 可以用来定义通用的类、函数和算法。
- C++提供了类模板(Class Templates)和函数模板(Function Templates)来支持泛型编程。
3. 函数模板
(1) 函数模板:提供了函数定义的一种模板,并未给出具体的定义。而在编译阶段,编译器会根据该模板以及调用的函数定义出具体数据类型的函数。
(2) 注意:函数模板也支持重载。
(3) 示例代码:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; /* 函数模板一:仅形参中有泛型(编译器可以自动识别) */ template <typename T> T add(T a, T b) { return a + b; } /* 函数模板二:没有形参,函数内部才有泛型(调用时需要手动传入T和U) */ template <typename T, typename U> void test() { T num1; U num2; cout << "模板二" << endl; } int main() { //对于能自动识别参数的模板,调用时可以省略<> cout << add<int>(10, 20) << endl; cout << add<double>(1.5, 25.6) << endl; //对于不能识别参数的模板,调用时必须传入指定类型 test<int, double>(); return 0; }
4. 类模板
与函数模板类似!当多个类功能相同,但是数据类型不同时就可以考虑采用类模板。示例代码如下:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; /* 类模板 */ template <typename T> class A { public: T a; T b; void test() { cout << "test" << endl; } }; int main() { /* 实例化类时需要<>传入类型*/ class A<int> boy; boy.test(); return 0; }
八、其他知识点
1. const关键字
1.1 const修饰变量:该变量就被声明为常量,不允许修改。该变量声明时就要初始化。
const int num = 100;
1.2 const修饰函数形参:该形参不允许修改。
void test(const int a) { a = 100; //报错,因为不能修改 }
1.3 const修饰函数返回值:该返回的变量或对象不允许修改。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class A { public: int age; }; class B { public: //const修饰函数返回值,其指向的对象属性和方法均无法修改 const class A* modi() { return new class A(); } }; int main() { class B b; b.modi()->age = 100; //报错,无法修改 return 0; }
1.4 const修饰类成员:
- 修饰成员变量:该变量不能被修改。通过初始化列表来初始化const修饰的成员变量。
- 修饰成员函数:该函数不能对成员变量进行修改。
class A { public: const int age; int hhh; A(int x) : age(x){}//只能通过初始化列表来对const修饰的成员变量初始化 void setAge() { this->age = 100;//报错,age被const修饰,无法被修改 } void setHHH() const { this->hhh = 100;//报错,该函数不能修改成员变量 } };
1.5 const修饰指针:
- const在*左边:指针变量指向的对象的值不能变。
- const在*右边:指针变量保存的地址不能变。
- const在*两边:指针变量保存的地址不能变,且指向对象的值也不能变。
/* 情况一:const在*左边 */ int num = 10; const int* p = # *p = 5; //错误,p指向对象的值不能变 p++; //正确,p保存的地址可以改变 /* 情况二:const在*右边 */ int num = 10; int* const p1 = # *p1 = 5; //正确,p指向对象的值可以变 p1++; //错误,p保存的地址不能改变 /* 情况一:const在*两边 */ int num = 10; const int* const p2 = # *p2 = 5; //错误,p指向对象的值不能变 p2++; //错误,p保存的地址不能改变
2. 引用
2.1 引用的概念:引用就是给变量起个别名,并没有开辟新的内存空间。因此,对别名的操作实际上就是对原始变量的操作。
int num = 10; int& ret = num; cout << "ret=" << ret << ", num=" << num << endl;//ret=10, num=10
2.2 注意点:
(1) 引用与数组搭配使用时,需要注意形参的写法:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; void test(char (&arr)[4], int size) { for (int i = 0; i < size; i++) { cout << arr[i] << " "; } } int main() { char array[4] = "hhh"; test(array, 3); return 0; }
(2) 引用一旦定义,就无法再更改为其他引用。
(3) 引用与指针的区别:
- 引用在定义时就必须初始化,且不能为NULL;指针可以先声明再初始化,且可以指向NULL。
- 引用一旦初始化完成,就不能改变引用的对象;指针可以改变指向的对象。
- 引用访问对象不需要使用解引用操作符*;指针需要使用*来访问对象。
- 引用不需要手动申请和释放空间;指针需要手动进行。
3. 容器
3.1 容器的概念:容器是一种用于存储和管理数据的对象。其内部定义了大量的方法来实现增删改查。示例如下:
vector<int> scores; scores.push_back(11);//往scores中添加int型数据11
3.2 string容器:用于存储字符串数据,同时也提供了许多方法来对字符串进行操作。示例如下:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; int main() { /* 创建string类型的对象name */ string name = "hhh"; /* 操作一:增加 */ name.append("Swea"); //在name后面增加字符串 name.push_back('r'); //在name后面增加单个字符 name.insert(3, ":"); //在name中下标3的前面插入字符串 cout << name << endl; /* 操作二:删除 */ name.erase(0,3); //从0下标开始删除3个字符 cout << name << endl; /* 操作三:修改 */ name[1] = 'W'; //按照下标修改单个字符 cout << name << endl; /* 操作四:查找 */ int index = name.find("ear", 0); //从0下标开始查找字符串,找到返回下标,未找到返回-1 cout << index << endl; return 0; }
3.3 vector容器:可以看成是数组的升级版。数组大小一旦确定不能修改,而vector会自动扩容为原空间的2倍。相关操作详见C++中的vector容器-CSDN博客。
3.4 其他容器:
- std::array:固定大小的数组。
- std::set:有序集合,存储唯一值。
- std::map:通过键值对key-value的方式来存储数据。
- std::stack:栈,后进先出,只能操作栈顶元素。
- std::queue:队列,先进先出,队头删除,队尾插入。
4. lambda表达式
(1) 功能:用于定义匿名函数。
(2) 格式:
auto 标识符 = [表达式] (形参列表) -> 返回值类型 { 函数体 };
(3) 说明:
- 匿名函数可以在主函数中定义和调用。
- [表达式]中的捕获列表如下:
[age] 表示值传递方式捕获变量age(不能修改age,只能打印) [this] 表示值传递方式捕获当前的this指针 [=] 表示值传递方式捕获所有父作用域的变量(包括this) [&age] 表示引用传递方式捕获变量age(能修改age,也能打印) [&] 表示引用传递方式捕获所有父作用域的变量(包括this)
- -> 返回值类型可写可不写。若不写,C++会自动识别return的类型。
- 匿名函数一般不写复杂的代码,理论上比调用外部函数要快。
(4) 示例代码:
//示例一:定义匿名函数并调用,其中}和;之间的()表示调用 []() { cout << "Hello, world" << endl; }();
//示例二:匿名函数的定义与调用分开进行 int age = 100; //定义匿名函数,并由f接收 auto f = [&age](int a, int b) { age = 56; cout << "age: " << age << endl; cout << a << " " << b << endl; }; //调用该匿名函数 f(11, 26);
5. 异常处理
(1) 语法:
// 1.抛出异常给上一级 throw MyException("错误信息"); // 2.捕获异常并处理 try{ //监视的代码 } catch (Exception1 &ex1) { //异常处理代码 } catch (Exception2 &ex2) { //异常处理代码 } catch (...) { //异常处理代码 }
(2) 自定义异常类:需要继承std::exception,且要重写what方法。其中noexcept表示该函数不会出现异常。
class MyException : public std::exception { private: const char* m_message; public: MyException(const char* message) :m_message(message) {}; const char* what() const noexcept override { return m_message; } };
(3) 示例代码:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <stdexcept> using namespace std; double calcuAver(int* num, int size) { if (num == NULL || size <= 0) { throw invalid_argument("Invalid input");//抛出异常给上一级 } //无异常:计算平均值 double sum = 0.0; for (int i = 0; i < size; i++) { sum += num[i]; } return sum / size; } int main() { int num[] = {1, 2, 3, 4}; try { double average = calcuAver(num, 0); cout << "平均值为:" << average << endl; } catch (exception &ex) { cout << "错误: " << ex.what() << endl; } return 0; }
6. 命名空间
(1) 概念:将一系列代码实体组织在一起的机制。它提供了一个独立、隔离的区域,以防止命名冲突。
(2) 语法:如下代码定义命名空间,并用作用限定符(:)来调用这个空间内的变量、函数和类。
namespace 空间名 { //代码 }
(3) 示例代码:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> namespace k203 { void test() { std::cout << "k203" << std::endl; } } namespace k204 { void test() { std::cout << "k204" << std::endl; } } int main() { k203::test(); k204::test(); return 0; }
7. 智能指针
(1) 概念:C++提供了一个类来帮忙管理指针的申请与释放。若你的代码写得很规范,无需使用智能指针。
(2) 三种智能指针:
- unique_ptr类:实例化的对象能独享所有权,使得其他智能指针无法再指向这一个空间。(需要添加头文件memory)
- shared_ptr类:允许多个智能指针指向同一块内存区域。为了保证最后的空间释放,采用了计数的方式,每多一个指向计数+1,每次reset计数-1,直到最后计数为0才真正释放空间。通过"对象.use_count"来查看计数值。
- weak_ptr类:使用shared_ptr时可能会出现循环引用的问题,从而导致内存空间无法被正常释放,因此可以在自定义类里使用weak_ptr类来避免这一问题。
8. 运算符重载
(1) 概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。但是,运算符重载不能改变它本身的寓意(+不能变更为-)。
(2) 语法格式如下示例:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> class Stu { public: int age; //+的运算符重载,实现两个Stu对象之间年龄相加 int operator+(Stu& s) { return this->age + s.age; } }; int main() { class Stu s1, s2; s1.age = 100; s2.age = 18; //s1+s2等价于s1.xxxx(s2) std::cout << s1 + s2 << std::endl; return 0; }
(3) 拷贝构造函数和拷贝赋值函数:
- C++默认会提供一个拷贝构造函数(当我们没有主动写的时候)。其能够将传入的自身类型对象的属性复制到新的对象。
- C++默认会提供一个拷贝赋值函数(当我们没有主动写的时候)。"s2 = s1"将s1对象的属性赋值到s2。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> class A { public: int age; //构造函数 A(int age) :age(age) { std::cout << this << ": age " << this->age << "构造函数" << std::endl; } //拷贝构造函数 A(const class A &tmp) { this->age = tmp.age; std::cout << this << ": age " << this->age << "拷贝构造函数" << std::endl; } //拷贝赋值函数 void operator=(const class A &tmp) { this->age = tmp.age; std::cout << this << ": age " << this->age << "拷贝赋值函数" << std::endl; } }; int main() { class A a(15);//调用构造函数 class A b(&a);//调用拷贝构造函数 class A c(18); c = b; //调用拷贝赋值函数 return 0; }
9. io操作
9.1 基本输入输出:
/* 基本输出 */ std::cout << "Hello" << endl; //打印"Hello"并换行 /* 基本输入 */ int num1, num2; std::cin >> num1 >> num2; //键盘输入两个数,保存到num1和num2中(以空格或回车间隔)
9.2 文件操作
(1) 读入文件内容:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <fstream> using namespace std; int main() { // 打开文件 fstream file("./main.cpp", ios::in); //文件是否正常打开 if (file.is_open()) { string line; while (getline(file, line)) {//getline函数将file中的一行元素赋值给line cout << line << endl; } //关闭文件 file.close(); } else { cout << "文件无法打开" << endl; } return 0; }
(2) 将内容写入文件:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <fstream> using namespace std; int main() { /* 若执行写操作,当文件不存在时会自动创建 * out:每次写入都会覆盖原来内容 * app:在原有内容基础上增加 */ // 写操作 fstream file("test.txt", ios::app); if (file.is_open()) { file << "This is a test.\n"; //关闭文件 file.close(); } else { cout << "文件无法打开" << endl; } return 0; }
九、SFML库相关
1. SFML库的配置
详见【快速解决】在vs2022中配置SFML图形库_sfml库的安装-CSDN博客
2. 创建图形化窗口
//创建窗口对象:宽度800,高度600,标题为"Fighters",风格默认
RenderWindow window(VideoMode(800, 600), "Fighters", Style::Default);
3. 处理事件
上述窗口出现后,点击该窗口会没有反应,这是因为我们没有处理这些事件。为了使点击窗口有效果,需要进行事件的处理。
//主循环:处理事件
while (window.isOpen()) {
Event event;
while (window.pollEvent(event)) {//从键盘或鼠标获取事件,保存于event中
if (event.type == Event::Closed) {//处理事件:关闭窗口
window.close();
}
}
}
4. 显示图形
在上述窗口中显示圆形,并使其水平向右移动。
#include <iostream>
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <SFML/System.hpp>
using namespace sf;
int main()
{
//创建窗口对象:宽度800,高度600,标题为"Fighters",风格默认
RenderWindow window(VideoMode(800, 600), "Fighters", Style::Default);
//设置刷新速率
window.setFramerateLimit(60);
//创建圆对象
CircleShape shape(50.0f);
/* 主循环 */
while (window.isOpen()) {
//处理事件
Event event;
while (window.pollEvent(event)) {
if (event.type == Event::Closed) {
window.close();
}
}
//清屏,背景为红色
window.clear(Color::Red);
//移动圆:每次往x轴正方面移动0.4,y轴不变
shape.move(0.4f, 0.0f);
//将圆显示到窗口中
window.draw(shape);
//显示窗口中的内容
window.display();
}
return 0;
}
5. 键盘控制
在主循环中加入键盘监测等相关语句,能够实现手动控制图形的移动。
//键盘控制
if (Keyboard::isKeyPressed(Keyboard::Escape))
window.close(); //退出
if (Keyboard::isKeyPressed(Keyboard::Up))
shape.move(0.0f, -2.0f); //上
if (Keyboard::isKeyPressed(Keyboard::Down))
shape.move(0.0f, 2.0f); //下
if (Keyboard::isKeyPressed(Keyboard::Left))
shape.move(-2.0f, 0.0f); //左
if (Keyboard::isKeyPressed(Keyboard::Right))
shape.move(2.0f, 0.0f); //右
6. 显示图片
在上述代码基础上,在创建图形圆时,将图片作为Texture与圆绑定在一起。若无法打开文件,可以尝试解决方法:属性->链接器->输入->附加依赖项,把里面的和SMFL相关的lib文件后面加上-d就可以了。例如:sfml-grapgics-d.lib。
//创建圆对象,纹理为图片
CircleShape shape(50.0f);
Texture dog_img;
if (!dog_img.loadFromFile("你自己的图片路径"))
throw "无法打开该文件";
else
shape.setTexture(&dog_img);//将纹理与圆绑定在一起
7. 碰撞
通过创建Sprite对象,将图片作为纹理绑定,Sprite对象提供了碰撞检测相关的函数。
//碰撞检测:检测dog1的边界和dog2的边界是否有交集
if (dog1.getGlobalBounds().intersects(dog2.getGlobalBounds())) {
std::cout << "碰撞了!" << std::endl;
}
#include <iostream>
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <SFML/System.hpp>
using namespace sf;
int main()
{
//创建窗口对象:宽度800,高度600,标题为"Fighters",风格默认
RenderWindow window(VideoMode(800, 600), "Fighters", Style::Default);
//设置刷新速率
window.setFramerateLimit(60);
//创建精灵对象1
Sprite dog1;
Texture dog1_img;
if (!dog1_img.loadFromFile("自己的路径/dog.png"))
throw "无法打开该文件";
else {
dog1.setTexture(dog1_img);//将纹理与精灵对象绑定在一起
dog1.setScale(Vector2f(0.4f, 0.4f));//缩放图片
}
//创建精灵对象2
Sprite dog2;
Texture dog2_img;
if (!dog2_img.loadFromFile("自己的路径/dog2.jpg"))
throw "无法打开该文件";
else {
dog2.setTexture(dog2_img);//将纹理与精灵对象绑定在一起
dog2.setScale(Vector2f(0.3f, 0.3f));//缩放图片
dog2.setPosition(200.0f, 200.0f);//设置坐标位置
}
/* 主循环 */
while (window.isOpen()) {
//处理事件
Event event;
while (window.pollEvent(event)) {
if (event.type == Event::Closed) {
window.close();
}
}
//键盘控制
if (Keyboard::isKeyPressed(Keyboard::Escape))
window.close(); //退出
if (Keyboard::isKeyPressed(Keyboard::Up))
dog1.move(0.0f, -2.0f); //上
if (Keyboard::isKeyPressed(Keyboard::Down))
dog1.move(0.0f, 2.0f); //下
if (Keyboard::isKeyPressed(Keyboard::Left))
dog1.move(-2.0f, 0.0f); //左
if (Keyboard::isKeyPressed(Keyboard::Right))
dog1.move(2.0f, 0.0f); //右
//碰撞检测
if (dog1.getGlobalBounds().intersects(dog2.getGlobalBounds())) {
std::cout << "碰撞了!" << std::endl;
}
//清屏,背景为红色
window.clear(Color::White);
//将对象显示到窗口中
window.draw(dog1);
window.draw(dog2);
//显示窗口中的内容
window.display();
}
return 0;
}
十、SFML游戏:狗头大战
改编自08-飞机大战-01-修改图片_哔哩哔哩_bilibili
#include <iostream>
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <SFML/System.hpp>
#include <vector>
#include <stdlib.h>
using namespace sf;
/* 类名:Bullet
* 父类:Sprite
*/
class Bullet : public Sprite {
public:
Bullet(float x, float y) {
Texture* bullet_img = new Texture();
if (!bullet_img->loadFromFile("自己的路径/bullet.png"))
throw "无法打开该文件";
else {
this->setTexture(*bullet_img);
this->setScale(Vector2f(0.1f, 0.1f));
this->setPosition(x, y);
}
}
};
/* 类名:Playern
* 父类:Sprite
*/
class Playern : public Sprite {
private:
RenderWindow& window;
long int last_create_bullet;
public:
static std::vector<Sprite> bullets;
Playern(RenderWindow& window) : window(window){
Texture* player_img = new Texture();
if (!player_img->loadFromFile("自己的路径/kjc.png"))
throw "无法打开该文件";
else {
this->setTexture(*player_img);
this->setScale(Vector2f(0.5f, 0.5f));
this->setPosition(window.getSize().x / 2, window.getSize().y - this->getGlobalBounds().height);
}
this->last_create_bullet = time(NULL);
}
/* 函数:玩家控制 */
void player_control() {
if (Keyboard::isKeyPressed(Keyboard::Escape))
window.close(); //退出
if (Keyboard::isKeyPressed(Keyboard::Up))
this->move(0.0f, -3.0f); //上
if (Keyboard::isKeyPressed(Keyboard::Down))
this->move(0.0f, 3.0f); //下
if (Keyboard::isKeyPressed(Keyboard::Left))
this->move(-3.0f, 0.0f); //左
if (Keyboard::isKeyPressed(Keyboard::Right))
this->move(3.0f, 0.0f); //右
//按下空格,当前位置创建导弹
if (Keyboard::isKeyPressed(Keyboard::Space)) {
//按键防抖
long int current_time = time(NULL);
if (current_time > this->last_create_bullet) {
this->bullets.push_back(Bullet(this->getPosition().x, this->getPosition().y));
this->last_create_bullet = current_time;
}
}
}
/* 函数:玩家显示*/
void display() {
//显示玩家
window.draw(*this);
//移动导弹
for (int i = 0; i < this->bullets.size(); i++) {
this->bullets[i].move(0.0f, -3.0f);
if (this->bullets[i].getPosition().y < 0 - this->bullets[i].getGlobalBounds().height) {
//超出边界销毁
this->bullets.erase(this->bullets.begin() + i);
}
}
//显示所有子弹
for (int i = 0; i < this->bullets.size(); i++) {
window.draw(this->bullets[i]);
}
}
};
/* 类名:Enemy
* 父类:Sprite
*/
class Enemy : public Sprite {
private:
RenderWindow &window; //引用的窗口对象
int enemy_create_time = 0; //创建敌军的间隔
public:
static std::vector<Sprite> enemies; //多个对象共享属性,仅声明
Enemy(RenderWindow& window) : window(window) {
Texture* enemy_img = new Texture();
if (!enemy_img->loadFromFile("自己的路径/dog2.png"))
throw "无法打开该文件";
else {
this->setTexture(*enemy_img);
this->setScale(Vector2f(0.1f, 0.1f));
}
}
/* 函数:每隔一定时间创建敌军 */
void createEnemy() {
if (this->enemy_create_time < 60) {
this->enemy_create_time++;
}
else {
this->setPosition(rand() % int(this->window.getSize().x - this->getGlobalBounds().width), 0 - this->getGlobalBounds().height);
this->enemies.push_back(Sprite(*this));
this->enemy_create_time = 0;
}
}
/* 函数:所有敌军的移动 */
void moveEnemy() {
for (int i = 0; i < this->enemies.size(); i++) {
this->enemies[i].move(0.0f, 2.0f);
//敌军超出屏幕,销毁敌军
if (this->enemies[i].getPosition().y > this->window.getSize().y + this->enemies[i].getGlobalBounds().height) {
this->enemies.erase(this->enemies.begin() + i);
}
}
}
/* 函数:屏幕上显示所有敌军 */
void displayEnemy() {
for (int i = 0; i < this->enemies.size(); i++) {
this->window.draw(this->enemies[i]);
}
}
};
/* 类名:Game
* 父类:无
*/
class Game {
private:
static int score;//记录游戏得分
public:
/* 函数:返回玩家与敌军是否碰撞 */
static bool isCollision(Playern &player) {
for (int i = 0; i < Enemy::enemies.size(); i++) {
bool flag = false;//记录敌机是否被消灭
//敌军与子弹碰撞
for (int j = 0; j < Playern::bullets.size(); j++) {
if (Enemy::enemies[i].getGlobalBounds().intersects(Playern::bullets[j].getGlobalBounds())) {
Enemy::enemies.erase(Enemy::enemies.begin() + i);//敌军消失
Playern::bullets.erase(Playern::bullets.begin() + j);//子弹消失
flag = true;
score++;
showScore();
break;
}
}
//敌军与玩家是否碰撞
if (flag == false && Enemy::enemies[i].getGlobalBounds().intersects(player.getGlobalBounds())) {
std::cout << "游戏结束" << std::endl;
return true;
}
}
return false;
}
/* 函数:显示游戏得分 */
static void showScore() {
system("cls");
std::cout << "游戏得分为: " << score << std::endl;
}
};
//定义Enemy类中的静态成员
std::vector<Sprite> Enemy::enemies;
std::vector<Sprite> Playern::bullets;
int Game::score = 0;
int main()
{
//创建窗口对象
RenderWindow window(VideoMode(500, 600), "Fighters", Style::Default);
//设置刷新速率
window.setFramerateLimit(60);
//创建玩家对象
class Playern player(window);
//创建敌机对象(相当于一个模板)
class Enemy enemy(window);
/* 主循环 */
while (window.isOpen()) {
//处理事件
Event event;
while (window.pollEvent(event)) {
if (event.type == Event::Closed) {
window.close();
}
}
//键盘控制玩家行动
player.player_control();
//每过一段时间,生成一个敌军对象
enemy.createEnemy();
//敌军自动移动(从上往下)
enemy.moveEnemy();
//判断玩家是否与敌军碰撞
if (Game::isCollision(player)) {
window.close();
}
//清屏,背景为红色
window.clear(Color::White);
//将所有对象显示到窗口中
player.display();
enemy.displayEnemy();
//显示窗口中的内容
window.display();
}
return 0;
}