Lesson2

接口

回顾一下 Human 和 Student:

class Human {	// 虚基类
public:
	virtual ~Human() {};	// 不能是纯虚函数
	virtual void talk() = 0;	// 纯虚函数,没有函数体
};

class Student final : public Human {
public:
	Student(const std::string& name) : name_(name) {}

	void talk() override {	// 重写父类的纯虚函数,有函数体
		std::cout << fmt::format("Student {} talk.\n", name_);
	}

private:
	std::string name_;
};

定义了纯虚函数的类是虚基类,虚基类的作用是当成接口给其他用户使用,其他用户只关心该类提供了什么功能,而不用关心实现的细节,甚至不用关心实际类型。Human 定义了纯虚函数 talk(),它的函数体是由子类 Student 去实现的。

注意,析构函数不能是纯虚函数。这是因为子类(Student)在析构的时候会调用父类(Human)的析构函数,来销毁父类子对象。如果父类的析构函数是纯虚函数(换句话说,没有实现;再换句话说,没有函数体),那么父类就没有办法销毁了。

Dynamic_cast

虽然 Student 和 Teacher 都继承自 Human,但是它们可能各自有独特的能力。比如,Student 可以学,Teacher 可以教:

class Student final : public Human {
public:
	// ...
	void learn() {	// Student 独特的函数
		std::cout << fmt::format("Student {} learn.\n", name_);
	}
	// ...
};

class Teacher final : public Human {
public:
	// ...
	void teach() {	// Teacher 独特的函数
		std::cout << fmt::format("Teacher {} teach.\n", name_);
	}
	// ...
};

现在我们有一个函数,根据输入对象的实际类型,来调用它独特的函数:

// 根据输入的实际类型,调用不同的函数
void do_something(Human* ph) {
	// 尝试将 Human 转换成 Student
	if (auto ps = dynamic_cast<Student*>(ph); ps) {
		ps->learn();	// 调用 Student 独特的函数
	}
	// 尝试将 Human 转换成 Teacher
	else if (auto pt = dynamic_cast<Teacher*>(ph); pt) {
		pt->teach();	// 调用 Teacher 独特的函数
	}
}

Dynamic_cast() 函数用来做类型转换,如果输入对象的实际类型不是我们希望的类型,则转换失败,得到空指针;如果输入对象的实际类型是我们希望的类型,则转换成功。

统一初始化

C++11 之前对象初始化都是小括号,会带一些问题:

class Human {};
Human h();	// error

我们定义了 Human 类型,接着希望定义一个 Human 类型的对象 h。但是编译器会报错,因为编译器把对象定义当成了函数声明,函数名是h,参数是空,返回值是 Human 类型。

C++11 引入了大括号初始化,帮助编译器做出正确的选择:

class Human {};
Human h{};	// correct

如果我们想使用字符串 “abc” 来初始化一个 std::string 对象,下面 3 种写法是等价的:

std::string s1("abc");		// 小括号初始化
std::string s2 = "abc";		// 赋值初始化(隐式构造)
std::string s3{ "abc" };	// 大括号初始化

注意,我们一般使用大括号初始化,可以方便地区分变量和函数。

C++11引入了初始化列表 std::initializer_list,帮助我们更方便地完成初始化操作,它的底层实现是数组。我们可以使用初始化列表来替换上面的 “abc”:

std::initializer_list<char> il = { 'a', 'b', 'c' };
std::string s4{ il };	// 把元素 'a','b','c' 添加到容器 s4 中

我们也可以把临时的初始化列表对象当成参数:

std::string s4{ { 'a', 'b', 'c' } };

甚至可以省略外面的大括号:

std::string s5{ 'a', 'b', 'c' };

大括号即可给普通对象初始化,也可以给容器初始化,所以大括号初始化也叫做统一初始化。

注意,下面两个初始化是不一样的:

std::string s6(3, 'a');		// 添加 3 个 'a' 到容器 s6 中
std::string s7{ 3, 'a' };	// 添加 3 和 'a' 到容器 s7 中

右值语义

请添加图片描述

左值可以放在等号左边,右值可以放在等号右边:

  • 左值:可以获取地址。
  • 将亡值:右值引用。
  • 纯右值:字面量,临时变量。

引用分为左值引用和右值引用。左值引用只能绑定到左值,右值引用只能绑定到右值。但是,常量左值引用可以绑定到右值。C++提供了 std::move() 函数,来将左值转换成右值引用。左值被转成后就不应该再使用了,因为它的资源可能已经移动走了。

右值的用处:

  • 提高效率。大多数情况下,移动的效率比复制高。复制是将对象管理的资源做了一份拷贝(深拷贝),而移动则是将资源转交给了其他对象(浅拷贝)。
  • 移动语义。有些类型无法将管理的资源进行拷贝,比如套接字、数据库连接。传递这种类型只能使用移动,不能使用复制。

自动类型推导

Auto 可以根据初始化的输入来推导变量的类型,先来看输入是左值的情况:

int n = 1024;	// 左值

auto x1 = n;	// int
auto& x2 = n;	// int&
auto&& x3 = n;	// int&

有 3 种形式:

  • x1 的类型就是 n 的类型,与 n 的 CV 限定符无关,与 n 是左值还是右值无关。
  • x2 的类型是左值引用,只能绑定到左值,并且与 n 的 CV 限定符有关。
  • x3 是万能引用,类型取决于 n 是左值还是右值。如果 n 是左值,则 x3 是左值引用;如果 n 是右值,则 x3 是右值引用。由于 n 是左值,x3 是左值引用。

再来看输入是右值的情况:

auto x1 = 1024;			// int
const auto& x2 = 1024;	// const int&
auto&& x3 = 1024;		// int&&

同样有 3 种形式:

  • x1 的类型就是 1024 的类型。
  • x2 的类型是常量左值引用,可以绑定到右值。注意,不能去掉 const,因为非常量左值引用只能绑定到左值。
  • x3 是万能引用,由于 1024 是右值,x3 是右值引用。

字面量

Auto 可以搭配字面量使用:

auto x1 = 0;		// int
auto x2 = 0u;		// unsigned int
auto x3 = 0l;		// long
auto x4 = 0ul;		// unsigned long
auto x5 = 0.0f;		// float
auto x6 = 0.0;		// double

还可以表示字符串:

#include <string>
using namespace std::literals;

auto s1 = "abc";	// const char*
auto s2 = "abc"s;	// std::string
auto s3 = "abc"sv;	// std::string_view

还可以表示时间:

#include <chrono>
using namespace std::literals;

auto t1 = 1s;	// std::chrono::seconds
auto t2 = 1ms;	// std::chrono::miniseconds
auto t3 = 1us;	// std::chrono::microseconds
auto t4 = 1ns;	// std::chrono::nanoseconds
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值