(自用)仿写程序

前言


        学习的基本过程有理解→总结→应用这几个步骤.总结的目的大概是概括出大体的一种思路,一些必然和必不然,整理出"概念",并以概念指导应用

引入


        尝试做一些和编程有关的概念总结.为了满足那个很朴素的想法:总结出概念,编程的思路就水到渠成地来了.---就好像学了单词就想写出好作文一样,当然谁都知道除非天赋惊人,否则这个过程是很困难的.不管怎么说做一点努力.当中肯定有不严谨的地方,看官不必较真

如何仿照着写程序

        起因:学习有时候会遇到这种情况:没有源码理解不清楚,或者有源码又吃不透,但有几个现成的例子给参考.能不能仿照例子把代码写出来. 一半靠猜,一半依葫芦画瓢把问题解决了.

        作者最近在看的STL函数符和算法,就差不多是这种情况.尝试从中整理一种思路

回顾STL算法和函数符

        简单的说STL算法是非成员函数处理容器数据.函数符是在算法中做形参的函数指针和函数符.函数符分为生成器,一元函数,二元函数,谓词,二元谓词这几类.

一元函数的编写

        <C++ Prime Plus> 6th Edition(以下称"本书")P710有个例子:4个参数的transform()函数调用:

//原书代码
transform(gr8.begin(),gr8.end(),out,sqrt);

        函数逻辑:把gr8对象所有元素(gr8.begin()和gr8.end()标记值区间),开方(sqrt处理)后,交给迭代器out(类型ostream_iterator<double,char>),输出到屏幕.

        问题就是前面提到的:没有transform()的原型,只有个例子,应该怎么看?

        sqrt是代入的参数,是一个函数名,因此原型是函数指针,并且是一元函数(书上有写:最后一个参数是一个函数符).

//sqrt原型double sqrt(double a);   //a可以接收int类型参数

        对应函数指针是:

//伪代码
double (*unary_function)(double val);    //只有一个参数的一元函数指针

        最关键的一点,也是transform()函数的内在联系:val值已确定,类型为double,从*gr8.begin()开始到*gr8.end()前面的一个值,也就是整个容器内的数据依次传入val.同时返回值类型也已确定,是double型.

        如果要仿写代码,现在需求改为gr8的每个值加1,那么先定义一个addOne函数,如下:

//定义被函数指针指向的函数
double addOne(double a){return a+1;
}
//transform函数调用
transform(gr8.begin(),gr8.end(),out,addOne);

        但函数指针是在val类型已确定的情况下编写的,适应不了泛型的要求,所以应该开发一个模板类实现的版本,以适应泛型,下面的二元函数有泛型版本.

二元函数的编写 

        接下来是5个参数的transform()函数

//本书代码
double add(double x,double y){return x+y;}
...
transform(gr8.begin(),gr8.end(),m8.begin(),out,add);

          函数逻辑:把gr8对象所有元素(gr8.begin()和gr8.end()标记值区间)和m8.begin()标记开始位置的容器元素(*m8.begin()取值)依次相加(*gr8.begin()和*m8.begin()相加,然后各自移动一个位置,add处理)后,交给迭代器out(类型ostream_iterator<double,char>),输出到屏幕.

        对应函数指针是:

//伪代码
double (*binary_function)(double val1,double val2);    //两个参数的二元函数指针

       已定义好的add代入tranform()函数的形参中,完成函数调用.

==========下面是泛型版本.

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;template<class T>
class myplus {											//模板里只一个方法
public:T operator()(const T& t1, const T& t2);
};template<class T>										//满足二元函数指针要求的函数原型
T myplus<T>::operator()(const T& t1, const T& t2) {		//函数形参传入值来自transofrom()的其他两个参数return t1 + t2;
}

        测试代码:

int main(void) {myplus<double> addObject;												//生成对象double y = addObject(2.2, 3.4);											//函数调用cout << "加法结果是:" << y << endl;double z=myplus<double>() (3.0, 5.0);									//匿名对象的函数调用cout << "加法结果是:" << z << endl;vector<double> ar5{ 1.0,2.0,3.0,4.0,5.0 };								//vector对象声明vector<double> br5{ 6.0,7.0,8.0,9.0,10.0 };ostream_iterator<double, char> out(cout, " ");							//输出流迭代器声明transform(ar5.begin(), ar5.end(), br5.begin(),out, myplus<double>());	//调用transform()cout <<  endl;transform(ar5.begin(), ar5.end(), br5.begin(),out, addObject);			//调用transform()
}

        输出结果:

加法结果是:5.6
加法结果是:8
7 9 11 13 15
7 9 11 13 15

注意:这种写法是否有问题笔者不确定,但是结果没错

再梳理一下应用步骤:

        1>transform()函数需要一个二元函数,并且把两个值传给函数形参.----固定的由transform()定义

        2>可以根据需要开发一个满足二元函数指针的函数,非泛型版本,形参类型和迭代器对象一致.调用transform()时,把函数名作为实参传入,在transform()最后一个参数,如add.

        3>为了更好满足需要,开发一个泛型版本.

                1)先声明一个模板类,属性可有可无,根据需要而定.

                2)重载operator()().因为是二元函数,所以定义为

T operator()(const T& t1, const T& t2);    //二元函数的原型

                这也是固定的,不这样写不满足二元函数的要求,不能把他传给transform().

                3)传给transform()的时候,写法是"模板类名<容器数据类型>()",这里传进去的是myplus<double>().根据匿名表达式调用模板类方法

double z=myplus<double>() (3.0, 5.0);	//二元函数的调用

对比一下其他传函数名的写法

//伪代码
typedef void (*pf)(int a);    //函数指针pf定义
void fun(int a);              //符合pf的函数fun的原型//另一个函数定义,参数有函数指针pf
void otherfun(pf pff);         
otherfun(fun);                //传入fun   

        fun的定义后面有个(int a),去掉括号里面部分,传给otherfun里的函数指针类型时用fun;

        myplus模板函数把(3.0,5.0)去掉,传入transoform()函数时,用myplus<double>()

//二元函数的传入
transform(ar5.begin(), ar5.end(), br5.begin(),out, myplus<double>());
transform(ar5.begin(), ar5.end(), br5.begin(),out, addObject);

        所以为什么叫函数对象?对象名就是函数名,可以作为函数符的实参传入 

        myplus函数符对应预定义函数符plus. 其他函数符与之类似

        现在可以编写用于transform()的二元函数,分析的目的就是编写自定义函数符

==========以上是二元函数泛型版本编写全过程,一元函数与之类似 

谓词的编写

         本书P708有个例子,谓词表示的函数指针如下:        

//伪代码,谓词表示的函数指针
bool (*pf)(paraType pt);

        和前面一样,形参pt的值已确定,整个容器内的数据依次传入pt.list模板有一个将谓词作为参数的remove_if( )成员,该函数将谓词应用于区间中的每个元素,如果谓词返回true,则删除这些元素

bool tooBig(int n){return n>100;}
list<int> scores;
scores.remove_if(tooBig);

        注意:这里的谓词是容器方法remove_if的参数,只能由list容器对象调用.他不是STL函数(算法)的参数.

        tooBig作为谓词使用,有一个问题:逻辑表达不完整,只能固定与数字100做比较.表达完整逻辑还需要一个参数,泛型版本的tooBig可以解决这个问题.

==========以下泛型版本的谓词

/*已测试,谓词的表达*/
#include<iostream>
#include<list>
#include<algorithm>
using namespace std;template<class T>
class TooBig {																T val;
public:TooBig(const T& v);														bool operator()(const T& t1);
};template<class T>
TooBig<T>::TooBig(const T& v):val(v){}					//构造函数,传入比较数据template<class T>										//满足谓词要求的函数原型,当list元素值大于传入值时返回true
bool TooBig<T>::operator()(const T& t1) {				//函数形参传入值来自list的元素的值return t1>val;
}

测试代码:

int main(void) {TooBig<double> tg(35.0);							//先生成对象bool is_yes = tg(20.0);								//函数调用cout << "成立吗?" << is_yes << endl;is_yes = TooBig<double>(25.0)(10.0);				//匿名对象调用cout << "成立吗?" << is_yes << endl;									list<double> demo1;									//生成list<double>对象demo1.push_back(10.0);demo1.push_back(20.0);demo1.push_back(30.0);demo1.push_back(40.0);demo1.push_back(50.0);list<double> demo2(demo1);							//生成数据副本demo1.remove_if(tg);								//使用函数符tg,删除35.0以上的数据for (auto pr = demo1.begin(); pr != demo1.end(); pr++) {	//显示删除后的数据	cout << *pr << endl;}cout << "====================================" << endl;demo2.remove_if(TooBig<double>(25.0));				//使用匿名函数符,删除25.0以上的数据for (auto pr = demo2.begin(); pr != demo2.end(); pr++) {	//显示删除后的数据	cout << *pr << endl;}
}

==========以上泛型版本的谓词

函数符的特点

        不管是哪一种函数符,都是把容器元素做实参传入.

        函数指针的小结:

        前面说到过函数指针是"逻辑占位",代表"多段逻辑",从函数符可以看出,他还有"数据和逻辑分离"的作用.

        对于外层函数来说,他把数据交给函数指针,规定了返回值类型,以及把函数指针返回的值用于下一个逻辑中.而具体的数据处理,交给函数指针来完成.

小结

        在没有函数原型的情况下仿照例子写程序 .仿写程序还是有一点难度的,因为要考虑的因素更多,还要做一些假设,不能保证想得东西完全正确.所以个人认为尽量还是把知识点理解透彻,少走弯路.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/402970.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

视频汇聚/安防综合管理系统EasyCVR非管理员账户能调用分配给其他用户的通道是什么原因?

视频汇聚/安防综合管理系统EasyCVR视频监控平台&#xff0c;作为一款智能视频监控综合管理平台&#xff0c;凭借其强大的视频融合汇聚能力和灵活的视频能力&#xff0c;在各行各业的应用中发挥着越来越重要的作用。平台不仅具备视频资源管理、设备管理、用户管理、网络管理和安…

超精细CG杰作:8K壁纸级官方艺术插画,展现极致美丽与细节的汉服女孩

极致精美的数字艺术杰作&#xff1a;8K壁纸级别的官方插画&#xff0c;展现超高清细节与和谐统一的美感&#xff0c;女孩的精致面容与眼神在光影下熠熠生辉&#xff0c;汉服主题下的超高分辨率作品&#xff0c;文件巨大&#xff0c;细节丰富&#xff0c;令人惊叹。 正向提示词…

Android gradle 构建

Understanding Tasks - Gradle task kapt 是 Kotlin 语言的注解处理器&#xff0c;它是 Android Studio 中用于处理 Kotlin 注解的工具。它通过在编译期间生成代码来增强 Kotlin 代码的功能。需要 Kotlin 编译器来解析和处理注解&#xff1b;使用 APT 来生成代码&#xff0c…

【初阶数据结构】链表(附题)

目录 一、顺序表的问题及思考 二、单链表 2.1链表的概念及结构 2.2.单链表的实现 2.2.1.节点的定义 2.2.2.链表的打印 2.2.3.头部插入删除/尾部插入删除 a.创建节点 b.尾插 c.头插 d.尾删 e.头删 2.2.4.查找数据 2.2.5.在指定位置之前插入数据 2.2.6删除pos节点 …

每日OJ_牛客_DP3跳台阶扩展问题

目录 DP3跳台阶扩展问题 题解代码1&#xff08;dp&#xff09; 题解代码2&#xff08;找规律&#xff09; DP3跳台阶扩展问题 跳台阶扩展问题_牛客题霸_牛客网 题解代码1&#xff08;dp&#xff09; 假定第一次跳的是一阶&#xff0c;那么剩下的是n-1个台阶&#xff0c;跳法…

矩阵中的最大得分(Lc3148)——动态规划

给你一个由 正整数 组成、大小为 m x n 的矩阵 grid。你可以从矩阵中的任一单元格移动到另一个位于正下方或正右侧的任意单元格&#xff08;不必相邻&#xff09;。从值为 c1 的单元格移动到值为 c2 的单元格的得分为 c2 - c1 。 你可以从 任一 单元格开始&#xff0c;并且必须…

Spring由哪些模块组成?

Spring由哪些模块组成&#xff1f; 简单描述则是主要由以下几个模块组成&#xff1a; Spring框架采用的是分层架构&#xff0c;它一系列的功能要素被分成20个模块&#xff0c;这些模块大体分为Core Container、Data Access/Integration、Web、AOP(Aspect Oriented Programmi…

Spring:IOC的详解☞Bean的实例化、Bean的生命周期

1、Bean基础配置 bean的基础配置&#xff1a; <bean id"" class""/> Bean的别名&#xff1a;name属性 Bean的作用范围&#xff1a;scope配置 使用bean的scope属性可以控制bean的创建是否为单例&#xff1a; singleton 默认为单例prototype 为非单…

ES6 (一)——ES6 简介及环境搭建

目录 简介 环境搭建 可以在 Node.js 环境中运行 ES6 webpack 入口 (entry) loader 插件 (plugins) 利用 webpack 搭建应用 gulp 如何使用&#xff1f; 简介 ES6&#xff0c; 全称 ECMAScript 6.0 &#xff0c;是 JavaScript 的下一个版本标准&#xff0c;2015.06 发版…

Python 如何创建和解析 XML 文件

XML&#xff08;可扩展标记语言&#xff09;是一种广泛使用的标记语言&#xff0c;主要用于存储和传输数据。它具有结构化、层次化的特点&#xff0c;常被用作数据交换格式。Python 提供了多种工具和库来处理 XML 文件&#xff0c;包括创建、解析和操作 XML 文档。 一、XML 简…

Ubuntu24.04使用SRS 搭建 RTMP流媒体服务器

一、简介 SRS(Simple Realtime Server)是一个简单高效的实时视频服务器&#xff0c; 是国人写的一款非常优秀的开源流媒体服务器软件&#xff0c;可用于直播/录播/视频客服等多种场景&#xff0c;其定位是运营级的互联网直播服务器集群。支持RTMP/WebRTC/HLS/HTTP-FLV/SRT/GB28…

手撕C++入门基础

1.C介绍 C课程包括&#xff1a;C语法、STL、高阶数据结构 C参考文档&#xff1a;Reference - C Reference C 参考手册 - cppreference.com cppreference.com C兼容之前学习的C语言 2.C的第一个程序 打印hello world #define _CRT_SECURE_NO_WARNINGS 1 // test.cpp // …

甄选系列“论软件开发过程RUP及其应用”,软考高级论文,系统架构设计师论文

论文真题 RUP(Rational Unified Process)是IBM公司的一款软件开发过程产品,它提出了一整套以UML为基础的开发准则,用以指导软件开发人员以UML为基础进行软件开发。RUP汲取了各种面向对象分析与设计方法的精华,提供了一个普遍的软件过程框架,可以适应不同的软件系统、应用…

http request-02-Ajax XHR 的替代方案-fetch 标准

http 请求系列 http request-01-XMLHttpRequest XHR 简单介绍 http request-01-XMLHttpRequest XHR 标准 Ajax 详解-01-AJAX&#xff08;Asynchronous JavaScript and XML&#xff09;入门介绍 Ajax XHR 的替代方案-fetch Ajax XHR 的替代方案-fetch 标准 Ajax 的替代方案…

06、stm32 引脚输入

一、配置 二、代码 /* USER CODE BEGIN WHILE */while (1){/** 引脚默认上拉 高电平 按键按下 读取到低电平* */if(HAL_GPIO_ReadPin(GPIOB,KEY_Pin) GPIO_PIN_RESET ){HAL_Delay(20);if(HAL_GPIO_ReadPin(GPIOB,KEY_Pin) GPIO_PIN_RESET ){HAL_GPIO_TogglePin(GPIOC,LED_P…

【C++】智能指针详解

一、从new和delete谈起 在C中&#xff0c;可以使用new和delete关键字进行对象的创建和销毁&#xff0c;new一个对象实际上是在堆上分配内存&#xff0c;而new出来的对象也要自己用delete释放&#xff0c;从而回收内存&#xff0c;否则会造成内存的泄露。由程序员自己new来分配…

修改mf后缀的文件为zip(仅修改文件后缀,并非通过压缩解压的方式修改实际的文件)

仅修改文件后缀的python实现 1、以下代码仅简单的修改mf后缀的文件为zip&#xff0c;并非通过压缩解压的方式修改实际的文件。 2、执行后原有mf后缀的文件直接转换为zip的后缀&#xff0c;请注意备份。 &#xff08;注意&#xff1a;如果rar或者tar转成zip&#xff0c;请使用解…

机器学习/自主系统与亚当·斯密

人工智能中的机器学习和自主系统是当前科技领域的热门话题&#xff0c;它们与亚当斯密的经济学理论之间可能存在一些潜在的联系和启示。亚当斯密的经济学理论主要关注市场经济的运行和资源分配。他的核心观点是&#xff0c;通过市场机制的作用&#xff0c;个体追求自身利益的行…

前端框架(三件套)

学习网站 HTML 系列教程&#xff08;有广告&#xff09; HTML&#xff08;超文本标记语言&#xff09; | MDN (mozilla.org)&#xff08;英文不太友好&#xff09; 1.HTML5 & CSS3 1.1HTML5表格 <!DOCTYPE html> <html lang"en"> <head>…