C++感受15-Hello STL 泛型启蒙

ops/2025/1/12 6:31:57/
  • 生鱼片和STL的关系,你听过吗?
  • 泛型编程和面向对象编程,它们打架吗?
  • 行为泛型和数据泛型,各自的目的是?

泛型启蒙

0 楔

俄罗斯生鱼片,号称俄罗斯版的中国烤鸭,闻名于世。其鱼肉,源于北极海域,传统上用白鱼如omul、nelma 或 muksun 制作,传统上与伏特加搭配,加冰食用,味道柔软、新鲜、冰爽。

虽然我隐约知道前苏联曾经有过一段人民生活物资贫溃的时期,但我还是不太清楚,26岁的Alexander Stepanov 是怎么吃生鱼片,吃到严重中毒,吃到住院,吃到精神恍惚,吃到在心中产生 STL 库的种子……
在这里插入图片描述

1 五段话

五段来自 STL之父 (Alexander Stepanov / 亚历山大・斯特潘诺夫)的话(摘自其多个采访会谈内容)。

  • 谈专业——

“我出生在前苏联的莫斯科。我曾求学于莫斯科国立大学,学习数学。但‘遗憾’的是,我从来都没能成为数学家”

  • 谈程序设计——

“程序设计就像同未理顺的复杂问题打的一场战斗,要打好这场战斗,数学首当其冲,几个世纪以来,数学的作用正在于此。如果将现在生动的数学体系作为实验证据,对于解决人类遇到的复杂性问题,数学还是最有效的”

  • 谈中国——

“中国是一个伟大的国家。曾有过许多伟大的数学家:秦九韶的《数书九章》就是古代数学中的经典……”

  • 谈为什么没成为数学家——

“我实在不能对Tamagawa算术、Coxeter群等一些纯数学的东西感到兴趣” “我想用数学干点实事……”

  • 谈数学专业对编程思想的影响——

“…… 1976年,又要说回到苏联了,我因为吃生鱼片得了严重的食物中毒而住院,在精神恍惚中,我忽然意识到并发的加法计算能力是基于加法是结合性的。同时,我意识到并发的减法运算是和半群结构类型有关联的,这就是最基本的重点:算法是定义于代数结构基础之上的。我又花了一些年头,意识到必须在正规公理上加入复杂性必要条件以扩展结构的概念,接着又花了15年之久才完成全面的架构。我相信迭代器理论是计算科学的中心就象环或Banach区间理论是数学的中心一样。每次当我找到一个算法时,我都要努力去寻求它所定义的结构基础。我想做的就是泛化地描述算法,并乐此不疲。我可以花一个月时间去精确地描述一个众所周知的算法的泛化表示……”

2 课堂视频

我们对 STL,对泛型编程的认识,就从 STL 之父的介绍说起……

C++感受16-Hello STL 泛型启蒙

3 泛型函数

3.1 语法

template <typename T /* 类型参数列表 */>
函数定义

其中函数定义中,可将 T 视为类型名字,可用在函数返回值类型、函数参数、函数体内,比如:

template <typename T>
T add(T a, T b)
{return a + b;  // 相加结果的类型需为 T ,或能隐式转换为 T
}

类型参数列表和函数参数类似,可以有多个参数:

// 一个双类型参数的函数模板,可用于输出带标题的值
tempate <typename S, typename V>
void OutputWithTitle( S const& title, V const& value)
{std::cout << title << " : " << value;
}

3.2 使用

调用者通常通过精准地指定参数类型,为编译器提供准确的类型参数,由编译器在代码需要处,使用明确的类型,替换模板中对应的类型参数(比如上述示例中的 T、S 、V),在编译器自动生成实际函数。由“函数模板”生成的函数,需要时,我们会称它为 “模板函数”。

  • 例一、add 使用
// 调用1, 生成 int add (int a, int b) ...
auto r1 = add (1, 2); // auto 是 C++11 的新语法,此处编译器可推出其为 int // 调用2, 生成 double add (double a, double b) ...
double r2 = add (1.1, 2.0); // 此处的 2.0 不能写成 2// 调用3, 生成 std::string add(std::string a, std::string b) ...
std::string r3 = add(std::string("Hello "), // 明确的std::string 类型std::string("STL") // 同上); // 将返回 "Hello STL"

上述代码将生成三个版本的 add 函数。
其中,r2 例中,如果 2.0 写成 2, 将造成编译器无法判定原模板中唯一的类型参数 T 的实际类型。r3 例中,如果直接传递纯C风格字符串(即字符串祼指针)"Hello " 和 “STL”,将造成编译失败,因为祼的字符串指针,不支持 “相加” 操作。

  • 例二、OutputWithTitle 使用
OutputWithTitle("姓名", "丁小明"); // S和V都是 char const* 类型std::string title = "积分";
double value = 2999.052;
OutputWithTitle(title, value); // S→std::string,V→double;

3.3 auto: C++20 的骚操作

到了 C++20 新标准,一些简单的函数模板定义,可以使用 auto 来简化写法。

比如,经典写法:

template <typename T>
void foo(T v)
{// ...
}

在 20 新标下,可写作:

void foo(auto v)
{// ...
}

注意,如果需要多个参数模板,而每个参数模板都使用 auto 限定类型的话,此时,auto 并不执行 “同一类型” 的限定。比如:

// a , b 都是 auto,但并不存在类型必须一致的限定
auto add(auto a, auto b)
{return a + b;
}// 以下调用成立
auto r = add(900, 99.99); // 返回 999.99

看起来很自由,但如此 “挣脱类型的束缚”,反倒容易滋生程序逻辑错误的恶果。

4. 泛型数据

有些时候——哪怕不是数学家——我们也会更加关心一个类型内部的组成结构,而不太关心(或者可以相对放心地忽略)数据的内部组成的类型。

比如,一个数学软件,可能希望有个类型,可以表示二维笛卡尔坐标(Cartesian coordinates)中的二维直角坐标点,不关心其中的x, y 坐标采用哪种类型。

像“不关心”,或 “随便”这样的用语,记得要反过来理解,它们的真实意思不是真的不在乎,面是:“干嘛分这分那?我都要!”。

template <typename T>
struct Point
{T x, y; // 用 T 表示 x, y 将来的真实类型,二者一致
};

和函数模板自动推理出函数略有不同,实际使用中,类的模板转换成类时,通常需要在类(class或struct)之后带上 ,其中 T 用于明确指定所需模板参数的真实类型。

// 使用 int 实例化类模板
Point<int> p1; 
p1.x = 10// 可将 float 赋 .y,只是转换过程中小数位会丢失
p1.y = 9.8f; // 使用 float 实例
Point<float> p2; 
p2.x = 99.f;
p2.y = 100; // 同样,此时不存在语法错误

以上示例中,之所以能跨类型赋值,是因为此时无论 p1 还是 p2 ,内部的 x, y 的类型都已经明确指定了。

定义类模板时,各类型参数既可用于指定成员数据的类型,也可用在成员方法(包括构造、析构等)身上(返回值、入参、函数内临时变量定义等等):

template <typename T>
struct Point
{T x, y;Point() = default; // 默认构造 C++11语法Point(T x, T y): x(x), y(y) // 成员数据初始化列表{}// 让 x, y 各自增加指定的长度void IncBy (T dx, T dy){x += dx;y += dy;}
};

构造、成员数据初始化列表知识点,讲 见《Hello Object 封装版.下》 等课堂。

注意,Point的第二个构造函数,和 IncBy() 成员函数都用到了 T,它就是定义类模板时的那个T,无需在函数自身加 template <typename > 作定义。

继续本体不包含 ,当构造函数使用到所在类模板的类型参数,并且,构造对象时的入参,能明确的,完整地推后出类模板的所有模板参数,则从 17 新标开始,定义对象时,不显示指定类模板的类型入参(全部或部分),也是可行的,比如:

Point p3 (99, 100); // 相当于 Point<int>
Point p4 (99.0, 100.0); // 相当于 Point<double>
Point p5 (std::string("90"), std::string("12.345")); // 虽然奇怪,但也合法

我们当然举双手双脚反对 p5 的类型 Point<std::string> ,试想对它调用 IncBy(“哦”, “哈”)之后……

5 初窥 vector

vector 翻译为 “矢量”,它是 STL 中最经典的的一个数据容器类(模板)。请阅读以下示例代码,在下一节课一上课,我们就来完善它:

#include <vector>using namespace std;struct 鸡精 { void 忍耐() { cout << "欢迎品尝!\n"; } };
struct 象妖 { void 愤怒() { cout << "大胆!可笑!!\n"; } };int main()
{   vector<鸡精> v1; // 鸡精专用瓶vector<象妖> v2; // 象妖专用瓶std::cout << "妖怪!我叫你一声,你可敢答应?\n";...鸡精 sj1;v1.push_back(sj1);象妖 dx1;v2.push_back(dx1);...
}

只是看可学不会编程哦,像 STL 这样的库更是需要动手实践。谁出题?有专业的,一线编程多年的同行为你出题,为你批改,欢迎到本课原发站 www.d2school.com 做作业,提问,扎实扎实踏出编程学习的每一步,看似“劳累”,但这么做的,你会获得更快的进步。

作业示例


http://www.ppmy.cn/ops/149368.html

相关文章

ASP.NET Core 中,Cookie 认证在集群环境下的应用

在 ASP.NET Core 中&#xff0c;Cookie 认证在集群环境下的应用通常会遇到一些挑战。主要的问题是 Cookie 存储在客户端的浏览器中&#xff0c;而认证信息&#xff08;比如 Session 或身份令牌&#xff09;通常是保存在 Cookie 中&#xff0c;多个应用实例需要共享这些 Cookie …

单片机实现模式转换

[任务] 要求通过单片机实现以下功能&#xff1a; 1.单片机有三种工作模式(定义全局变量MM表示模式&#xff0c;MM1&#xff0c;2&#xff0c;3表示三种不同的模式) LED控制模式 风扇控制模式 蜂鸣器控制模式 2.可以在某一个模式下通过拓展板KEY1按键控制设备 (按…

CDA数据分析师一级经典错题知识点总结(1)

1、运算符的优先级&#xff1a; 、>、and、or 2、销售漏斗模型主要观测粗细&#xff0c;斜率&#xff0c;流速&#xff0c;体形几个方面&#xff1b;最需要关注流速。 3、波士顿矩阵 4、订单详情表应该连接在人货场中的“货”。 5、堆叠条形图属于构成类图表。 6、在Exce…

【数据库系统概论】数据库恢复技术

目录 11.1 事务的基本概念 事务的定义 事务的开始与结束 事务的ACID特性 破坏ACID特性的因素 11.2 数据库恢复概述 11.3 故障的种类 1. 事务内部的故障 2. 系统故障 3. 介质故障 4. 计算机病毒 11.4 恢复的实现技术 如何建立冗余数据 数据转储 登记日志文件 11…

『SQLite』解释执行(Explain)

摘要&#xff1a;本节主要讲解SQL的解释执行&#xff1a;Explain。 在 sqlite 语句之前&#xff0c;可以使用 “EXPLAIN” 关键字或 “EXPLAIN QUERY PLAN” 短语&#xff0c;用于描述表查询的细节。 基本语法 EXPLAIN 语法&#xff1a; EXPLAIN [SQLite Query]EXPLAIN QUER…

Android车机DIY开发之软件篇(三)编译Automotive OS错误(1)

Android车机DIY开发之软件篇(三)编译Automotive OS错误(1) 问题 FAILED: out/soong/build.ninja cd “KaTeX parse error: Expected EOF, got & at position 49: …soong_build")" &̲& BUILDER"PWD/KaTeX parse error: Expected EOF, got & …

mysql -> 达梦数据迁移(mbp大小写问题兼容)

安装 注意后面初始化需要忽略大小写 初始化程序启动路径 F:\dmdbms\tool dbca.exe 创建表空间&#xff0c;用户&#xff0c;模式 管理工具启动路径 F:\dmdbms\tool manager.exe 创建表空间 创建用户 创建同名模式&#xff0c;指定模式拥有者TEST dts 工具数据迁移 mysql -&g…

ETL的工作原理

ETL的工作原理 什么是ETL_云计算主题库-阿里云 ETL的工作原理可以分为三个主要的步骤&#xff1a;Extract&#xff08;提取&#xff09;、Transform&#xff08;转换&#xff09;、Load&#xff08;加载&#xff09;。 工作步骤 描述 Extract &#xff08;提取&#xff09;…