26-29继承_虚函数_接口_可见性

继承#

例子#

#include <iostream>

class Entity
{
public:
	float X, Y;
	void Move(float xa, float ya)
	{
		X += xa;
		Y += ya;
	}
};

//public表示子类继承的父类的成员变量/方法,
//最高只能是public。如果是class Player :protected Entity
//则原来父类中的public成员变量变成protected,但是原来的protected
//和private成员变量则不变
class Player :public Entity
{
public:
	const char* Name;

	void PrintName()
	{
		std::cout << Name << std::endl;
	};
};
int main()
{
	//64位系统,Entity两个float,占用了16字节
	std::cout << sizeof(float) << std::endl;//4
	std::cout << sizeof(char*) << std::endl;//8
	std::cout << sizeof(Entity) << std::endl;//4+4=8
	std::cout << sizeof(Player) << std::endl;//4+4+8=16
	Player player;
	player.Move(5, 5);
	player.X = 2;

	std::cin.get();
}

多态:如果我现在创建一个独立的函数来打印Entity对象,例如通过访问X和Y变量并将它们打印出来。那我其实也可以将Player对象传递给该函数

24-25构造函数和析构函数

构造函数#

我们每次实例化一个对象时都会运行的一个特殊函数

例子#

#ifdef LY_EP24

#include <iostream>

class Entity
{ 
public:
	//C++中不处理的话默认不初始化任何成员变量
	float X, Y;

	//默认的构造函数,什么都不做
	//不写任何构造函数是会默认添加这个
	//Entity()
	//{

	//}

	//构造函数
	Entity()
	{
		X = 0.00f;
		Y = 0.00f;
		std::cout << "Created Entity!"  << std::endl;

	}

	//带参数的构造函数
	Entity(float x, float y) {
		X = x;
		Y = y;
	}


	~Entity()
	{
		std::cout << "Destroyed Entity!" << std::endl;
	}

	void  Print() {
		std::cout << X << "," << Y << std::endl;
	}
};

void Function()
{
	//声明了一个函数:	函数名:e1,	返回类型:Entity,参数:无
	Entity e1();
	Entity e;//栈上分配
	Entity e2{};//栈上分配
	e.Print();
}

int main()
{
	Function();
   /*
Created Entity!
Created Entity!
0,0
Destroyed Entity!
Destroyed Entity!

   */

	std::cin.get();
}
#endif

不希望其他人使用构造函数#

class Log
{
private:
	Log()
	{
	}
	//或者
	/*
	Log() = delete;
	*/
public:
	static void Write()
	{
	
	}
}

int main(){
	
	Log l;//这里会报错
}

析构函数#

  • 当你摧毁一个对象时,会调用析构函数,析构函数主要用来清楚任何不再需要的内存
  • 最典型的例子,如果在构造对象时在堆上分配内存,则必须在析构函数中手动清理它
#include <iostream>

class Entity
{
public:
	//C++中不处理的话默认不初始化任何成员变量
	float X, Y;

	//默认的构造函数,什么都不做
	//不写任何构造函数是会默认添加这个
	//Entity()
	//{

	//}

	//构造函数
	Entity()
	{
		X = 0.00f;
		Y = 0.00f;
		std::cout << "Created Entity!" << std::endl;

	}

	//带参数的构造函数
	Entity(float x, float y) {
		X = x;
		Y = y;
	}


	~Entity()
	{
		std::cout << "Destroyed Entity!" << std::endl;
	}

	void  Print() {
		std::cout << X << "," << Y << std::endl;
	}
};

void Function()
{
	Entity e;//栈上分配
	e.Print();
}

int main()
{
	Function();
   /*
Created Entity!
0,0
Destroyed Entity!

   */

	std::cin.get();
}

23enum

  • 枚举,其实就是一组
  • 想用整数 只能是整数,比如char,signed char,unsigned char,short,unsigned short,int,unsigned int,long,unsigned long,long long,unsigned long long,不能是浮点类型 来表示某些状态,并且给他们命名
  • 这里解释一下char为什么分 char 和 signed char:signed char 明确表示 -128127 的小整数;unsigned char 明确表示 0255;char 是专门给字符用的类型,它的符号性由编译器决定。
#ifdef LY_EP23
#include <iostream>

//默认情况下是无符号整型unsigned int
enum Example:unsigned char
{
	//如果不指定的话,第一个
	//为0,然后逐个增加
	//只要没有指定的值,都会根据前一个指定的值逐个增加
	A,B,C
};

enum e_MyLong : long
{
	//如果不指定的话,第一个
	//为0,然后逐个增加
	//只要没有指定的值,都会根据前一个指定的值逐个增加
	A1, B1, C1
};


int a = 0;
int b = 1;
int c = 2;


int main()
{
	Example value = B;//如果e_MyLong里面也有B的话,编译器会报错
	Example value1 = Example::B;

	//if (value == 1) //也可以这么写
	if (value == B) {
		std::cout << "get" << std::endl;
	}
	std::cin.get();
}

#endif

日志系统改进#

#include <iostream>

class Log
{
public:
	enum Level
	{
		//这里不能用Error,因为下面有一个同名函数
		LevelError=0, LevelWarning, LevelInfo
	}; 
private:
	//日志实际级别
	//m开头表示类成员变量
	Level m_LogLevel = LevelInfo;
public:
	void SetLevel(Level level)
	{
		m_LogLevel = level;
	}

	//关于字符串指针,后面会讲解
	void Error(const char* message)
	{
		//因为枚举类型是整型,所以
		//这里可以比较
		if (m_LogLevel >= LevelError)
			std::cout << "[ERROR]:" << message << std::endl;
	}

	void Warn(const char* message)
	{
		if (m_LogLevel >= LevelWarning)
			std::cout << "[WARNING]:" << message << std::endl;
	}

	void Info(const char* message)
	{
		if (m_LogLevel >= LevelInfo)
			std::cout << "[INFO]:" << message << std::endl;
	}
};
 
int main()
{
	Log log;
	//警告或更严重的消息会被打印出
	log.SetLevel(Log::LevelWarning);
	log.Warn("Hello!");
	log.Error("Hello!");
	log.Info("Hello!");
	/*
	[WARNING]:Hello!
	[ERROR]:Hello!
	*/

	std::cin.get();
}

20-22面向对象

编写一个类#

#include <iostream>

class Log
{
public:
	const int LogLevelError = 0;
	const int LogLevelWarning = 1;
	const int LogLevelInfo = 2;
private:
	//日志实际级别
	//m开头表示类成员变量
	int m_LogLevel = LogLevelInfo;
public:
	void SetLevel(int level)
	{
		m_LogLevel = level;
	}

	//关于字符串指针,后面会讲解
	void Error(const char* message)
	{
		if (m_LogLevel >= LogLevelError)
			std::cout << "[ERROR]:" << message << std::endl;
	}

	void Warn(const char* message)
	{
		if (m_LogLevel >= LogLevelWarning)
			std::cout << "[WARNING]:" << message << std::endl;
	}

	void Info(const char* message)
	{
		if (m_LogLevel >= LogLevelInfo)
			std::cout << "[INFO]:" << message << std::endl;
	}
};

int main()
{
	Log log;
	//警告或更严重的消息会被打印出
	log.SetLevel(log.LogLevelWarning);
	log.Warn("Hello!");
	log.Error("Hello!");
	log.Info("Hello!");
	/*
	[WARNING]:Hello!
	[ERROR]:Hello!
	*/

	std::cin.get();
}

静态_static#

使用场景:类/结构体内部;类/结构体外部

18-19类_基础

类_基础知识#

类:将数据和功能整合在一起

类定义及函数#

//类定义
class Player
{
	//默认是私有的
	//int x, y;
//这里的public包括了speed这个成员
public:
	int x, y;
	int speed;
};

//函数
void Move(Player& player, int xa, int ya)
{
	player.x += xa*player.speed;
	player.y += ya*player.speed;
}

int main()
{
	//player:对象
	//创建(实例化)一个对象
	Player player;
	player.x = 1;
	player.y = 2;
	player.speed = 2;
	Move(player, 1, -1);
	LOG(player.x);//3
	LOG(player.y);//0
}

类定义及方法#

//类定义
class Player
{
	//默认是私有的
	//int x, y;
//这里的public包括了speed和Move这个成员
public:
	int x, y;
	int speed;
	//方法
	void Move( int xa, int ya)
	{
		x += xa * speed;
		y += ya * speed;
	}
};



int main()
{
	//player:对象
	//创建(实例化)一个对象
	Player player;
	player.x = 1;
	player.y = 2;
	player.speed = 2;
	player.Move( 1, -1);
	LOG(player.x);//3
	LOG(player.y);//0
}

C++中结构和类的区别#

  • 类的默认成员是私有的

17引用

需要有pointer(指针),即上一个视频的基础

引用其实是指针的扩展,只是指向指针的语法糖,让他更容易阅读。引用本质上是引用现有变量,不同于指针,你可以在其中创建新的指针变量让后将其设置为空指针或其他。引用本身实际上并不占据新变量,他们实际上没有存储空间。

	int a = 5;
	int* b = &a;

	//&符号是类型的一部分
	//使用ref就跟使用a的别名一样
	int& ref = a;
	//这里编译器编译后会直接设置a,而
	//不会再创建一个变量
	ref = 2;

	LOG(a); //5

使用地址增加#

void Increment(int* value) {
	//首先引用指针,然后增加指针处的值
	(*value)++;
}

int main()
{

	int a = 5;
	Increment(&a);
	LOG(a);

	std::cin.get();
}

使用引用实现一样的效果,但是代码更加美观

void Increment(int& value) {
	value++;
}

int main()
{

	int a = 5;
	Increment(a);
	LOG(a);

	std::cin.get();
}

引用的禁忌#

不能指向其他地方


void Increment(int& value) { 
	value++;
}

int main()
{

	int a = 5;
	int b = 8;
	//必须初始化,否则编译错误
	int& ref = a;
	
	//不会出现编译错误,但是其实没有任何效果
	ref = b;

	Increment(ref);
	LOG(b);//打印8,未改变b的值

	std::cin.get();
}

指针,则可以任意变换赋值

16指针

指针

当你写一个程序的时候,你启动他,整个应用程序就会加载到内存,告诉计算机根据代码执行的操作都被加载到内存中,这是cpu实际访问你写的变量的方式。

指针,是一个==存储内存地址 表示内存地址 ==的数字

我们需要从内存中,读取或者写入东西,指针,就是内存的地址

忘记指针的类型

例子1-无类型指针#

void* 表示我们不关心地址指向的实际数据是什么类型的

#include <iostream>
#define LOG(x) std::cout << x << std::endl

int main(){
	void* ptr=0;//0,表示这是一个无效指针
	//void* ptr=NULL;// NULL是一个# define NULL 0,其实也是0
	//void* ptr=nullptr;// c++11引入的
	std::cin.get();
}

~~C/C++ 允许局部函数声明 ~~

void func(int); void func(void*); func(0);//调用func(int),而不是 func(void*),存在歧义 func(nullptr);// 明确调用 func(void*)

例子2-类型指针#

	int var = 8;
	//void* ptr = &var;
	 int* ptr=&var;
	 double* ptr1=(double*)&ptr; //我知道 ptr 是一个 int* 类型变量(一串表示地址的数字),但我骗编译器说:ptr 这块内存里放的是一个 double。 骗编译器说 ptr 这个变量里面存放的那几个字节(也就是一个地址值)可以当成 double 来读取。
	//double* ptr=(double*)&var;
	std::cin.get();

调试查看ptr地址上的值

14-15循环

循环

for#

当需要执行某些操作多次时

	for (int i = 0; i < 5; i = i + 1) {
		Log("Hello World");
	}
  1. 先创建变量i(为零)
  2. 然后检查i<5,是的话执行下面代码块(否则退出)
  3. 然后i=i+1
  4. 然后再检查i<5,是的话执行下面代码块(否则退出)
  5. 3->4->3->4 反复循环直到退出

i的作用域仅限于for代码块

上述代码也可改为

	int i = 0;
	for (; i < 5; ) {
		Log("Hello World");
		i = i + 1;
	}

或者

	int i = 0;
	bool condition = true;
	for (; condition; ) {
		Log("Hello World");
		i = i + 1;
		if (!(i < 5))
			condition = false;
	}

死循环

for(;;)
{
	
}

while#

基本上可以和for语句互转。但是如果有已经存在的条件,优先使用while


	int i = 0;
	while (i < 5) {
		Log("Hello World");
		i++;
	}

do while#

//执行前先判断
bool condition = false;
while (condition)
{

}

//至少执行一次
do
{

} while (condition);

控制流#

continue,break(如果有多重循环,不会影响到外部的循环)

13vs用于c++项目的最佳实践

vs用于c++项目的最佳实践

创建新项目#

解决方案下有项目

MySolusion.sln是解决方案文件

项目下是源代码

Project.vcxproj 是项目相关文件 Project.vcxproj.filters 是过滤器

过滤器决定了vs IDE打开该项目时的视图(实际中不存在文件夹)

显示所有文件#

显示磁盘目录下的所有文件

移动文件位置

#include <iostream>
int main() {
	std::cout << "Hello World!" << std::endl;
	std::cin.get();
}

如图,中间文件(.obj)是放在项目文件夹里

项目编译后生成的可执行文件放在(总的)解决方案文件夹下

由来

$(Platform)\$(Configuration)\,即 x64\Debug 文件夹结构的由来

修改配置#

OutputDirectory: $(SolutionDir)bin\$(Platform)\$(Configuration)\

如果解决方案有多个项目,如果构建DLL文件或者其他需要的东西,我们需要这些在同一个文件夹中(而不用深入每个项目的文件夹)

IntermediateDirectory: $(SolutionDir)bin\intermediates\$(Platform)\$(Configuration)\

这里建议加上项目名,避免中间文件冲突(覆盖) $(SolutionDir)bin\intermediates\$(ProjectName)\$(Platform)\$(Configuration)\

之后右键项目-clean Solution

查看变量值

清理项目#

解决方案下有一个项目

12条件及分支

条件及分支

  • 先检查一个条件,然后跳转到不同的部分并开始执行指令,意味着条件及分支都有会一点开销。如果想编写稍微快点的代码就不该使用条件语句
  • 如果某事是真的,那么跳转到对应的代码块执行

查看汇编#

  • mov dword ptr [x],6意思是在内存中名为 x 的位置,写入4个字节的数值 00000006(十六进制)

    • mov:移动
    • cmp 比较
    • jne:是否为零
  • 不相等则跳转

  • 并且将某个位置(代表变量comparisionResult设置为0)

  • 其中,dword ptr [rbp+0F4h],0 表示 将0存储到内存地址rbp+0xF4处的4字节空间(双字)中 ,rbp通常指基指针寄存器(Base Pointer Register),通常是栈帧的基地址

  • test基本是执行 按位与 运算

    补充:test eax, eax 不会改变 eax 的值,只是临时计算 eax AND eax,然后丢弃结果,唯一目的:设置CPU的标志位 同时,test 指令还会强制清零 溢出标志(OF = 0)和进位标志(CF = 0) 为什么要自己和自己AND?因为 eax AND eax 的结果就是 eax 本身:如果 eax = 0 → 结果是 0 → ZF=1 零标志位(ZF, Zero Flag) ;如果 eax ≠ 0 → 结果非零 → ZF=0 ;如果 eax < 0(最高位为1) → SF=1 符号标志位(SF, Sign Flag)