当前位置: 首页 > news >正文

(自用)仿写程序

前言


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

引入


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

如何仿照着写程序

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

        作者最近在看的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;}
}

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

函数符的特点

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

        函数指针的小结:

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

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

小结

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

相关文章:

  • 使用 Go 语言将 Base64 编码转换为 PDF 文件
  • 深入探索Amazon EC2:解锁云端计算的无限可能
  • 使用 grep 进行文本文件搜索
  • 网页开发——DOM与BOM
  • watchEffect 函数 与 watch 函数的区别
  • HTTP 请求流程
  • LeetCode 234 - 回文链表 C++ 实现
  • 设计模式之结构型模式
  • 深入浅出:理解TCP传输控制协议的核心概念
  • Go 语言错误处理
  • keepalive原理详解及应用
  • Windows采用VS2019实现Open3D的C++应用
  • ~Keepalived高可用集群~
  • CAPL使用结构体的方式组装一条DoIP车辆识别请求报文(payload type 0x0002)
  • [Datawhale AI夏令营 2024 第四期] 从零入门大模型微调之旅的总结
  • (ckeditor+ckfinder用法)Jquery,js获取ckeditor值
  • 【5+】跨webview多页面 触发事件(二)
  • 4月23日世界读书日 网络营销论坛推荐《正在爆发的营销革命》
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • ES6核心特性
  • ESLint简单操作
  • JavaScript设计模式系列一:工厂模式
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • Web设计流程优化:网页效果图设计新思路
  • windows下mongoDB的环境配置
  • 阿里云应用高可用服务公测发布
  • 手机app有了短信验证码还有没必要有图片验证码?
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • ​ssh-keyscan命令--Linux命令应用大词典729个命令解读
  • ​软考-高级-系统架构设计师教程(清华第2版)【第9章 软件可靠性基础知识(P320~344)-思维导图】​
  • ​水经微图Web1.5.0版即将上线
  • ‌‌雅诗兰黛、‌‌兰蔻等美妆大品牌的营销策略是什么?
  • ## 临床数据 两两比较 加显著性boxplot加显著性
  • #stm32整理(一)flash读写
  • (1)STL算法之遍历容器
  • (done) ROC曲线 和 AUC值 分别是什么?
  • (Java企业 / 公司项目)点赞业务系统设计-批量查询点赞状态(二)
  • (Java入门)抽象类,接口,内部类
  • (二) Windows 下 Sublime Text 3 安装离线插件 Anaconda
  • (二)springcloud实战之config配置中心
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (回溯) LeetCode 78. 子集
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (转)淘淘商城系列——使用Spring来管理Redis单机版和集群版
  • ***php进行支付宝开发中return_url和notify_url的区别分析
  • *setTimeout实现text输入在用户停顿时才调用事件!*
  • .NET 中 GetHashCode 的哈希值有多大概率会相同(哈希碰撞)
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)
  • .net经典笔试题
  • .net下的富文本编辑器FCKeditor的配置方法
  • [ vulhub漏洞复现篇 ] AppWeb认证绕过漏洞(CVE-2018-8715)
  • [145] 二叉树的后序遍历 js
  • [AI 大模型] Meta LLaMA-2
  • [autojs]逍遥模拟器和vscode对接