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的方法查看是否过期
}