Cherno CPP学习笔记-03-高级特性

1.12、再打一天渔吧

P27、C++继承

讲了一些思想,还是挺好的,就不记了。

P28、C++虚函数

虚函数引入了一种叫做Dynamic Dispatch (动态联编)的机制,通常通过虚函数表(v表)来实现编译。

  • 虚函数表就是一个表,包含所有基类中所有虚函数的映射,这样我们在运行时可以将他们映射到正确的覆写(override)函数

  • 虚函数有额外开销。一方面,需要额外的内存来存储v表,这样我们就可以分配到正确的函数,包括基类中要有一个成员指针,指向v表;另一方面,每次调用虚函数时,我们需要遍历这个表,来确定要映射到哪个函数。

为什么需要虚函数的一个例子:

c++ 复制代码
#include<iostream>
#include<string>
//这里不使用虚函数
class Entity 
{
public:
	std::string GetName() { return "Entity"; }
};

class Player : public Entity
{
private:
	std::string m_name;
public:
	Player(const std::string& name)
		: m_name(name) {}

	std::string GetName() { return m_name; }
};
int main() {
	
	Entity* e = new Entity();
	std::cout << e->GetName() << std::endl;
	Player* p = new Player("Cherno");
	std::cout << p->GetName() << std::endl;
	//输出:
	//Entity
	//Cherno

	Entity* entity = p;
	std::cout << entity->GetName() << std::endl;
	//输出:
	//Entity  
	//无法实现多态原因:我们在声明函数时,我们的方法通常在类内部起作用。
	//当要调用方法的时候,会调用属于该类型的方法

	std::cin.get();
}

用虚函数改进仅需要将Entity类的方法返回值前加上virtual关键字

c++ 复制代码
class Entity 
{
public:
	virtual std::string GetName() { return "Entity"; }
};

同时最好将Player类中对应的方法加上override (C++11特性),防止拼写错误

c++ 复制代码
std::string GetName() override { return m_name; }

P29、C++接口(纯虚函数)

纯虚函数允许我们在基类中定义一个没有实现的函数,然后强制子类去实现该函数。

实现语法:

c++ 复制代码
class Entity 
{
public:
	virtual std::string GetName() = 0;
};

一个例子:(GetClassName接口)

c++ 复制代码
#include<iostream>
#include<string>

class Printable
{
public:
	virtual std::string GetClassName() = 0;
};

class Entity : public Printable
{
public:
	std::string GetClassName() override { return "Entity"; }
};

class Player : public Entity
{
public:
	std::string GetClassName() override { return "Player"; }
};

void Print(Printable* obj) 
{
	std::cout << obj->GetClassName() << std::endl;
}

int main() {
	
	Entity* e = new Entity();
	Player* p = new Player();
	Entity* entity = p;
	
	Print(e);
	Print(p);
	Print(entity);

	//输出:
	//Entity
	//Player
	//Player
	std::cin.get();
}

P30、C++可见性

可见性是属于面向对象编程的概念,它指的是类的某些成员或方法实际上有多可见:谁能看见他们、谁能调用他们、谁能使用它们;

C++中有三个基础的可见性修饰符:(Java中还有default、C#中有internal)

  • private:只有这个类本身能访问所修饰的符号,友元除外。

    • friend是C++关键字,它可以让类或者函数成为类的朋友(友元),可以从类中访问私有成员。
  • protect:只有这个类本身和层次结构中的所有子类可以访问所修饰的符号,友元除外?

  • public:公开访问。

P31、C++数组

C++11库中有标准数组std::array,优点有边界检查、记录数组大小等。

堆区new出来的数组,对指针使用sizeof()没有意义。所以很多情况下需要我们自己维护数组的大小。

如果在类中申请栈上的数组:

c++ 复制代码
static const int exampleSize = 5;
int example[exampleSize];

使用std::array:

c++ 复制代码
#include <array>

std::array<int, 5> another;
int size = another.size();

P32、字符串

没什么东西,不如学STL。

1.13、三天打渔

P33、C++字符串字面量

字符串字面量,是在双引号之间的一串字符。

字符串字面量永远保存在内存中的只读区域内

c++ 复制代码
char name[] = "Cherno";
//在汇编模式中,'Cherno'被保存到只读区,name指针保存的是一个只读区的地址。
name[2] = 'a';
//如果要修改,寄存器会复制只读区的'Cherno'到name变量中,产生了额外开销。(不能直接修改只读区的内存)

VS的编译器是MSVC。

c++ 复制代码
//一个字节的字符 utf8
const char* name = u8"Cherno";

//宽字符wchar_t windows上是2字节,linux上是4字节,mac也是4字节?
const wchar_t* name1 = L"Cherno";

//两个字节的字符 utf16
const char16_t* name2 = u"Cherno";

//四个字节的字符 utf32
const char32_t* name3 = U"Cherno";
c++ 复制代码
//原版
std::string name0 = (std::string)"hello" + ", Alice";

//C++14新特性
using namespace std::string_literals;
std::string name1 = "hello"s + ", Alice";

std::wstring name2 = L"hello"s + L", Alice";

std::u16string name3 = u"hello"s + u", Alice";

std::u32string name4 = U"hello"s + U", Alice";

	//原版
	const char* example = "line1\n"
		"line2\n"
		"line3\n";

	//C++14新特性  R()忽略转义字符
	const char* ex = R"(line1
		line2
		line3)";

P34、C++中的CONST

c++ 复制代码
const int* a = new int;
//星号前,不能修改指针指向的内容 如: *a = 2;

int* const a = new int;
//星号后,不能修改指针的指向 如 a = &b;
c++ 复制代码
class Entity
{
private:
	int m_x, m_y;
    mutable int var;
public:
    //类中方法括号后的const意为这个方法不会修改任何实际的类,只能读,不能写;mutable修饰的变量除外
	int GetX() const
	{
		//m_x = 2; 不被允许
        var = 2; //可以
		return m_x;
	}
    int GetX()
	{
		return m_x;
	}
    
};

//参数const约定不修改e的内容, &防止拷贝,优化性能
void PrintEntity(const Entity& e)
{
    //此处必须调用带const修饰的GetX函数
	std::cout << e.GetX() << std::endl;
}

P35、C++的mutable关键字

mutable标记一个变量,意味着类中的const方法可以修改这个成员。

mutable的用途:

  • 与const一起使用,如P34中,想要统计GetX()的调用次数,最好设置一个成员 mutable int cnt;
  • 在lambda表达式中使用
c++ 复制代码
//使用mutable修饰lambda表达式允许传值的条件下修改内部x的值,但此时外部x的值没有改变
int x = 8;
auto f = [=]() mutable
{
	x++;
    std::cout << x << std::endl;
}

f();
//x == 8; 

P36、C++的成员初始化列表

语法:

c++ 复制代码
class Entity
{
private:
	std::string m_Name;
	int m_x, m_y;
public:
	//初始化列表中最好按照声明的顺序写,防止警告
	Entity()
		:m_Name("Unkonwn"),m_x(0),m_y(5)
	{
	}
	Entity(const std::string& name)
		:m_Name(name)
	{
	}

};
  • 便于维护易读性:函数体内部可能会有Init之类的其他操作;

  • 提升性能:

c++ 复制代码
#include<iostream>
#include<string>
class Example
{
public:
	Example()
	{
		std::cout << "Create Example !" << std::endl;
	}
	Example(int x)
	{
		std::cout << "Create Example with" << x << std::endl;
	}
};

class Entity
{
private:
	std::string m_Name;
	Example m_Example;
public:
	//初始化列表中最好按照声明的顺序写,防止警告
	Entity()
	{
		m_Name = "Unknown";
		m_Example = Example(8);
	}
	Entity(const std::string& name)
		:m_Name(name), m_Example(Example(8))
	{
	}

};

int main() {
	Entity e1;
	//输出:
	//Create Example !
	//Create Example with8
	
	Entity e2("Cherno");
	//输出:
	//Create Example with8

	std::cin.get();
}
c++ 复制代码
//不使用初始化列表的情况,在Entity构造函数中相当于:
Entity()
	{
    	Example m_Example;
		m_Name = "Unknown";
		m_Example = Example(8);
	}
//会有两次Example的构造过程

//而使用初始化列表相当于声明的时候就赋初值:
Example m_Example = Example(8);
//故只调用一次

P37、C++的三元操作符

?: 没什么东西

P38、 创建并初始化C++对象

栈上创建还是堆上创建。

  • 如果对象很大,或者想要显式地控制对象地生存期,在堆上创建。

    • 需要手动delete,忘了就内存泄漏
    • 智能指针可以自动delete。(在没有引用时)
  • 反之在栈上创建。(栈大小一般只有几M)

P39、C++ new关键字

C++ 复制代码
//malloc做的仅仅是分配内存,然后给我们一个指向那个内存的指针(C++中不推荐使用)
Entity* e = (Entity*)malloc(sizeof(Entity)) ;
//new相比malloc多做的是调用了构造函数
Entity* e = new Entity();

P40、C++隐式转换与explicit关键字

隐式的意思是,不会明确地告诉他要做什么,有点像自动,通过上下文知道意思。

C++允许编译器对代码进行一次隐式转换。

如果我们开始有一个数据类型,然后有另一种类型,在两者之间,C++允许隐式进行转换,而不需要用cast做强制转换。

c++ 复制代码
class Entity
{
private:
	std::string m_Name;
	int m_Age;
public:
	Entity(int age)
	:m_Name("Cherno"),m_Age(age) {}

	Entity(const std::string& name)
		:m_Name(name),m_Age(-1) {}

};

void PrintEntity(const Entity& entity)
{
	//Printing
}

int main() {
	Entity e1(22);
	Entity e2("Cherno");
	//隐式转换 或者称为 隐式构造函数
	Entity e3 = 22;
	Entity e4 = (std::string)"cherno";

	//另一种隐式转换
	PrintEntity(22);
    
	std::cin.get();
}

explicit关键字:禁用隐式转换的功能

  • explicit关键字放在构造函数前面,表示不能隐式转换,必须显式调用此构造函数
  • 例子略

P41、C++运算符及其重载

运算符:

  • 加减乘除、逆向引用、箭头、+=、&、<<、new、delete、逗号、括号等
c++ 复制代码
class Vector2
{
public:
	float x, y;
	Vector2(float x, float y) : x(x), y(y) {}
	Vector2 operator+(const Vector2& other) const
	{
		return Vector2(x + other.x, y + other.y);
	}
	Vector2 operator*(const Vector2& other) const
	{
		return Vector2(x * other.x, y * other.y);
	}
	bool operator==(const Vector2& other)
	{
		return x == other.x && y == other.y;
	}
};

std::ostream& operator<<(std::ostream& stream, Vector2& vector)
{
	stream << vector.x << " ," << vector.y << std::endl;
	return stream;
}
int main() {
	Vector2 position(4.0f, 4.0f);
	Vector2 speed(0.5f, 1.5f);
	Vector2 powerUp(1.1f,1.1f);

	Vector2 result = position + speed * powerUp;
	std::cout << result << std::endl;

	std::cin.get();
}

P42、C++的this关键字

没什么东西。

P43、C++的对象生存期(栈、作用域、生存期)

作用域Scope:

  • if作用域、for、while、空作用域、类作用域

智能栈指针?一个例子:(离开作用域后自动被销毁)其他用途:计时器timer、互斥锁mutex locking 等。

c++ 复制代码
class Entity
{
public:
	Entity() { std::cout << "Created Entity!" << std::endl; }
	~Entity() { std::cout << "Delete Entity!" << std::endl; }
};

class ScopePtr
{
private:
	Entity* m_Ptr;
public:
	ScopePtr(Entity* entity) :m_Ptr(entity) {}
	~ScopePtr()
	{
		delete m_Ptr;
	}
};

int main() {
	{
		//ScopePtr对象e是在栈上分配的,e如果被删除,析构函数删除Entity对象
		ScopePtr e = new Entity();
	}

	std::cin.get();
}

P44、C++的智能指针

智能指针是自动实现new和delete操作的一种方式。当你new之后不需要delete,甚至不用new。

本质上是一个原始指针的一个包装。

第一个智能指针:unique_ptr:作用域指针

c++ 复制代码
//unique_ptr不能被copy,防止二次释放
class Entity
{
public:
	Entity() { std::cout << "Created Entity!" << std::endl; }
	~Entity() { std::cout << "Delete Entity!" << std::endl; }
	void Print() { std::cout << "Print" << std::endl; }
};

class ScopePtr
{
private:
	Entity* m_Ptr;
public:
	ScopePtr(Entity* entity) :m_Ptr(entity) {}
	~ScopePtr()
	{
		delete m_Ptr;
	}
};

int main() {
	{
		//智能作用域指针unique_ptr
		std::unique_ptr<Entity>entity(new Entity());
		//std::unique_ptr<Entity>entity = new Entity();  不能这样因为explicit修饰
		/*
			template <class _Dx2 = _Dx, _Unique_ptr_enable_default_t<_Dx2> = 0>
			_CONSTEXPR23 explicit unique_ptr(pointer _Ptr) noexcept 
			: _Mypair(_Zero_then_variadic_args_t{}, _Ptr) {}
		*/
		//更推荐的做法:(如果构造函数抛出异常,这种方法更安全)
		std::unique_ptr<Entity>entity2 = std::make_unique<Entity>();

		//entity2 = entity; 不能copy,因为unique_ptr的拷贝构造函数被删除了

		entity->Print();
	}

	std::cin.get();
}

引用计数指针:shared_ptr:引用计数可以跟踪指针有多少个引用,一旦引用计数达到0,他就被删除了。

c++ 复制代码
std::shared_ptr<Entity> entity = std::make_shared<Entity>(); 	//更推荐使用
std::shared_ptr<Entity> entity(new Entity());
  • shared_ptr需要额外分配一块内存,叫做控制块,用来存储引用计数。
  • 如果首先创建一个new Entity,然后将其传递给shared_ptr构造函数,他必须做两次内存分配,
    • 先做一次new Entity,然后是shared_ptr的控制内存块的分配。
    • 如果把他们组合起来会更有效率。
c++ 复制代码
	{
		std::shared_ptr<Entity> e0;
		{
			std::shared_ptr<Entity> entity = std::make_shared<Entity>();
			e0 = entity;
		}//此处entity不释放,因为还有e0在引用它
	}//e0回收,entity将在此处被释放

弱指针:weak_ptr:与shared_ptr一起使用,但不会增加shared_ptr的引用计数

c++ 复制代码
	{
		std::weak_ptr<Entity> w0;
		{
			std::shared_ptr<Entity> entity = std::make_shared<Entity>();
			w0 = entity;//不会增加引用计数
		}//此处entity释放
        //可以随时使用weak_ptr的方法查看是否过期
	}
相关推荐
新晓·故知20 分钟前
<基于递归实现线索二叉树的构造及遍历算法探讨>
数据结构·经验分享·笔记·算法·链表
魔理沙偷走了BUG31 分钟前
【数学分析笔记】第4章第4节 复合函数求导法则及其应用(3)
笔记·数学分析
z樾1 小时前
Github界面学习
学习
道爷我悟了2 小时前
Vue入门-指令学习-v-html
vue.js·学习·html
NuyoahC2 小时前
算法笔记(十一)——优先级队列(堆)
c++·笔记·算法·优先级队列
计算机学姐3 小时前
基于SpringBoot+Vue的在线投票系统
java·vue.js·spring boot·后端·学习·intellij-idea·mybatis
彤银浦3 小时前
python学习记录7
python·学习
这可就有点麻烦了3 小时前
强化学习笔记之【TD3算法】
linux·笔记·算法·机器学习
少女忧3 小时前
51单片机学习第六课---B站UP主江协科技
科技·学习·51单片机
邓校长的编程课堂4 小时前
助力信息学奥赛-VisuAlgo:提升编程与算法学习的可视化工具
学习·算法