• [调优工具] Launch Application模式下分析C/C++应用失败
    使用最版本的调优工具,对沙箱实验室里面的鲲鹏软件性能调优实践任务进行测试,失败,配置截图如下,麻烦专家分析下原因。
  • [技术干货] 【转载】C 与 C++ 40 年的爱恨情仇
    以下为译文:70年代初,贝尔实验室创建了C语言,它是开发UNIX的副产品。很快C就成为了最受欢迎的编程语言之一。但是对于Bjarne Stroustrup来说,C的表达能力还不够。于是,他在1983年的博士论文中扩展了C语言。于是,支持类的C语言诞生了。当时,Bjarne Stroustrup明白编程语言有许多组成部分,除了语言本身,还有编译器、链接器和各种库。提供熟悉的工具有助于语言被广泛接受。在这种历史背景下,在C语言的基础上开发C++也是有道理的。40年后,C和C++都在行业中得到了广泛使用。但是,互联网上的C开发人员认为C++是有史以来最糟糕的人类发明,而许多C++开发人员则希望有朝一日C语言灰飞烟灭。究竟发生了什么事?从表面上看,C和C++都可以满足相同的用例:高性能、确定性、原生但可移植的代码,可用于最广泛的硬件和应用程序。但是,更让C自豪的是它是一门低级语言,更接近汇编。而C++,从诞生第一天开始就充斥了各种奇怪的东西。例如析构函数这个黑魔法。自作主张的编译器。尽管很早C++就有了类型推断功能,但是80年代中期的开发人员还无法接受这个概念,因此Bjarne Stroustrup不得不删除了auto,直到C++ 11又重新添加回来。从那以后,C++就不断加入各种工具来实现抽象。很难说C++是一种低级语言还是高级语言。从设计目的上来说,C++两者都是。但是在不牺牲性能的情况下,建立高级抽象是很困难的。于是C++引入了各种工具来实现constexpr、move语义、模板和不断增长的标准库。从根本上讲,我认为C信任开发人员,而C++信任编译器。这是一个巨大的差异,单凭“两者的原生类型相同”、“while循环的语法相同”等简单一致是无法掩盖的。C++开发人员将有这些问题归咎于C,而C开发人员则认为C++过于疯狂。我觉得站在C的角度看C++,这种说法也很正确。作为C的超集,C++确实很疯狂。一个经验丰富的C开发人员面对C++可能没有熟悉的感觉。C++不是C,这就足以引发互联网上的激烈争论。然而,虽然我不喜欢C,但也没有权利取笑C。尽管我有一定的C++经验,但用C编写过的代码少之又少,而且肯定是很糟糕的代码。好的编程语言包括良好的实践、模式、惯用写法,这些都需要多年的学习。如果你尝试用编写C++的方式写C的代码,或者用C的方式编写C++的代码,那感觉一定很糟糕。即便你懂C,也不一定会C++,反之亦然,懂C++也不一定会用C编程。那么,我们是否应该停止说C/C++,为这两个不幸的命名而感到悲哀吗?也不至于。尽管C++的设计理念与C不一样,但是C++仍然是C的超集。也就是说,你可以在C++转换单元中包含C的头文件,这样依然可以通过编译。而这正是造成混乱的地方。C++不是C的扩展,它是由不同的委员会、不同的人独立设计的标准。从逻辑上讲,喜欢C++理念的人会参与C++社区以及C++标准化的过程,而其他人可能会尝试参与C。无论是C的委员会还是C++委员会,他们表达意图和方向的方式只能通过各自的最终产品:标准;而标准是众多投票的成果。然而,编译器很难知道它正在处理的是C头文件还是C++头文件。extern “C” 标记并没有得到广泛一致的使用,而且它只能影响修饰,而不会影响语法或语义。头文件仅对预处理器有影响,对于C++编译器而言,所有内容都是C++转换单元,因此也就是C++。然而,人们依然会在C++中包含C头文件,并期望它“正常工作”,而大多数时候也确实可以正常工作。那么,我们不禁想问:由不同地方的、不同的人开发的C++代码如何保持C的兼容性?恐怕很难。最近,一位同事让我想起了康威定律:"设计系统的架构受制于产生这些设计的组织的沟通结构。"根据这个逻辑,如果两个委员不互相合作,则他们创造的语言也不会互通。C++维护了一个与C及其标准库的不兼容列表。然而该列表似乎并未反映出许多C11和C18中添加、但在C++中不合法的功能。更清晰的介绍请参见这个维基本科页面(https://en.wikipedia.org/wiki/Compatibility_of_C_and_C%2B%2B)。然而,仅仅列出两种语言之间的不兼容性,并不足以衡量二者的不兼容性。那些存在于C++标准库中但主要声明来自C的函数,很难声明成constexpr,更难声明成noexcept。C的兼容性会导致性能成本,而C函数是优化的障碍。许多C的结构在C++中都是有效的,但无法通过代码审查(如NULL、longjmp、malloc、构造/析构函数、free、C风格的类型强制转换等)。在C看来,这些惯用写法可能问题不大,但在C++中可不行。C++具有更强大的类型系统,不幸的是,C的惯用写法在这个类型系统中凿了一个洞,因此实现C的兼容性需要在安全性方面付出代价。别误会,C++仍然关心C的兼容性,某种程度上。然而,有趣的是C也很关心C++,某种程度上。实话实说,C对C++的关心程度可能高于C++对C的关心。看来,每个委员会还是在乎另一个委员会的工作。但我们很不情愿。C++知道,许多基础库都是用C编写的,不仅包括libc,而且还有zip、png、curl、openssl(!)以及许多其他库,无数的C++项目都在使用这些库。C++不能破坏这些兼容性。但是最近,尤其是在过去的十年中,C++的规模已远远超过C。C++拥有更多的用户,并且社区更加活跃。也许这就是为什么如今C++委员会的规模是C委员会的10倍以上。C++是不可忽视的力量,因此C委员会必须考虑不破坏C++兼容性。如果非要说一个标准追随另一个标准对话,那么如今C++是领头者,而C是追随者。现在,C++处于稳定的三年周期中,无论是风雨还是烈日,抑或是致命的新疫情。而C每十年左右才发布一次主版本。不过这也很合理,因为作为一种较低级的语言,C不需要发展得那么快。C语言的环境也与C++完全不同。C多用于平台,更多地用于编译器。每个人(甚至他们的狗狗)都会编写C编译器,因为该语言的特性集很小,所以任何人都可以编写C编译器。而C++委员会真正考虑的实现只有四种,而且在每次会议上这四种实现都会出现。所以,C语言中的许多功能都是与实现有关的,或者是可选支持的,这样各种编译器不需要做太多努力就可以声称自己遵从了标准,据说这样委员会的人会比较高兴。如今,C++更加侧重于可移植性,而不是实现的自由。这又是一个理念的不同。因此,你的提议破坏了C的兼容性我提议的P2178的一部分理论上会影响与C的兼容性。这样的话所有方案都不会令人满意。有人可能会说,你可以先向C委员会提议你的新特性。这意味着需要召开更多会议。C会议的严格出席规则可能导致你无法参加会议,这就将那些不愿意花上数千美元成为ISO会员的个人拒之门外。这是因为C委员会必须遵守ISO的规则。而且,如果新的标准刚刚发布,那么可能还需要等待十年时间,你的提案才会被考虑。最重要的是,如果C委员不理解或不在乎你正在努力解决的问题,那么你的提案就石沉大海了。或者他们可能没有精力来处理这个问题。而且,可能你也没有精力来处理C。毕竟,你的本意是要改进C++。实际上,哪怕会议上无人反对你的提议(尽管不太可能发生),如果有人让你先去跟C委员会的人讨论,就等于给你的提议判了死刑。另一种可能的情况是,C委员会接受与C++中存在的版本略有不同的版本。true只能做一个宏来实现。char16_t需要通过typedef。char32_t不一定是UTF-32。static_assert对应的是 _Static_assert。这类的情况还有很多,我们应该责备C吗?可能不应该。他们的委员会只是在尽力将C语言做好。反之亦然。在C++20中,指定的初始化器就受到了C的启发,但采取了略微不同的规则,因为如果完全一样的话就不符合C++的初始化规则。对于这个问题,我也有责任。C有VLA。如果当时我在,我一定会反对在标准C++中采用它,因为它导致了太多安全性问题。我也会坚决反对将_Generic添加到C++中的提议。也许_Generic的目的是减少由于缺乏模板或缺乏重载而导致的问题,但是C++有这两个功能,从我的角度来看,_Generic并不适合我想象中的C++。这两个委员会似乎对于对方语言的关心程度也不一样。有时我们会遇到兼容性非常好的情况(std::complex),有时完全不在乎兼容性(静态数组参数)。这没有办法。别忘了每个委员会都是一群人,他们在不同的时间、不同的地点投票,而试图控制结果会导致投票毫无意义。将这些人放在同一个房间也不现实。ISO可能会反对,参与者的不平衡会导致C的人处于极大的劣势。C的兼容性不重要如果你是C开发人员,那么肯定会把C视为一种简洁的编程语言。但对于我们其他人而言,C的印象完全不同。C是通用的、跨语言的胶水,可以将一切紧密地结合在一起。对于C++用户而言,C就是他们的API。从这一点来看,C的价值在于其简单性。请记住,C++关心的那一部分C是出现在接口(头文件)中的C。我们关心的是声明,而不是定义。C++需要调用C库中的函数(Python、Fortran、Rust、D、Java等语言也一样,在所有情况下都可以在接口边界使用C)。因此,C是一种接口定义语言。向C添加的内容越多,定义接口就越困难。这些接口随着时间的推移保持稳定的可能性较小。那么,C++中缺少<threads.h>是否重要?可能并不重要,因为这不太可能出现在公共接口中。如今大家都在谈论C过去,C的兼容性是C++的一大卖点。但如今,每个人(甚至他们的金鱼)都懂C。Rust可以调用C函数,Python、Java、一切语言都可以!甚至怪异的Javascript都可以在WebAssemby中调用C函数。但是在这些语言中,接口是显式的。该语言提供的工具可以公开特定的C声明。当然,这比较麻烦。但这可以让接口非常非常清晰。而且还是有界的。例如,在rust中,调用C函数并不会迫使Rust牺牲某些设计来容纳C子集。实际上C是被包含进去的。mod confinment {use std::os::raw::{c_char};extern "C" {pub fn puts(txt: *const c_char);}}pub fn main() {unsafe {confinment::puts(std::ffi::CString::new("Hello, world!").expect("failed!").as_ptr());}}
  • [技术干货] 2020-09-26:请问rust中的&amp;和c++中的&amp;有哪些区别?
    2020-09-26:请问rust中的&和c++中的&有哪些区别?#福大大架构师每日一题#
  • [技术干货] 【转载】这些都是当年在C/C++上折的地儿…
    一、导语C++是一门被广泛使用的系统级编程语言,更是高性能后端标准开发语言;C++虽功能强大,灵活巧妙,但却属于易学难精的专家型语言,不仅新手难以驾驭,就是老司机也容易掉进各种陷阱。本文结合作者的工作经验和学习心得,对C++语言的一些高级特性,做了简单介绍;对一些常见的误解,做了解释澄清;对比较容易犯错的地方,做了归纳总结;希望借此能增进大家对C++语言了解,减少编程出错,提升工作效率。二、陷阱我的程序里用了全局变量,为何进程退出会莫名其妙的core掉?Rule:C++在不同模块(源文件)里定义的全局变量,不保证构造顺序;但保证在同一模块(源文件)里定义的全局变量,按定义的先后顺序构造,按定义的相反次序析构。我们程序在a.cpp里定义了依次全局变量X和Y;按照规则:X先构造,Y后构造;进程停止执行的时候,Y先析构,X后析构;但如果X的析构依赖于Y,那么core的事情就有可能发生。结论:如果全局变量有依赖关系,那么就把它们放在同一个源文件定义,且按正确的顺序定义,确保依赖关系正确,而不是定义在不同源文件;对于系统中的单件,单件依赖也要注意这个问题。std::sort()的比较函数有很强的约束,不能乱来相信工作5年以上至少50%的C/C++程序员都被它坑过,我已经听到过了无数个悲伤的故事,《圣斗士星矢》,《仙剑》,还有别人家的项目《天天爱消除》,都有人掉坑,程序运行几天莫名奇妙的Crash掉,一脸懵逼。如果要用,要自己提供比较函数或者函数对象,一定搞清楚什么叫“严格弱排序”,一定要满足以下3个特性:非自反性非对称性传递性尽量对索引或者指针sort,而不是针对对象本身,因为如果对象比较大,交换(复制)对象比交换指针或索引更耗费。注意操作符短路考虑游戏玩家回血回蓝(魔法)刷新给客户端的逻辑。玩家每3秒回一点血,玩家每5秒回一点蓝,回蓝回血共用一个协议通知客户端,也就是说只要有回血或者回蓝就要把新的血量和魔法值通知客户端。玩家的心跳函数heartbeat()在主逻辑线程被循环调用void  GamePlayer::Heartbeat(){    if (GenHP() || GenMP())    {        NotifyClientHPMP();    }}如果GenHP回血了,就返回true,否则false;不一定每次调用GenHP都会回血,取决于是否达到3秒间隔。如果GenMP回蓝了,就返回true,否则false;不一定每次调用GenMP都会回血,取决于是否达到5秒间隔。实际运行发现回血回蓝逻辑不对,Word麻,原来是操作符短路了,如果GenHP()返回true了,那GenMP()就不会被调用,就有可能失去回蓝的机会。你需要修改程序如下:void GamePlayer::Heartbeat(){    bool hp = GenHP();    bool mp = GenMP();    if (hp || mp)    {        NotifyClientHPMP();    }}逻辑与(&&)跟逻辑或(||)有同样的问题, if (a && b) 如果a的表达式求值为false,b表达式也不会被计算。有时候,我们会写出 if (ptr != nullptr && ptr->Do())这样的代码,这正是利用了操作符短路的语法特征。别让循环停不下来for (unsigned int i = 5; i >=0; --i){    //...}程序跑到这,WTF?根本停不下来啊?问题很简单,unsigned永远>=0,是不是心中一万只马奔腾?解决这个问题很简单,但是有时候这一类的错误却没这么明显,你需要罩子放亮点。内存拷贝小心内存越界memcpy,memset有很强的限制,仅能用于POD结构,不能作用于stl容器或者带有虚函数的类。带虚函数的类对象会有一个虚函数表的指针,memcpy将破坏该指针指向。对非POD执行memset/memcpy,免费送你四个字:自求多福注意内存重叠内存拷贝的时候,如果src和dst有重叠,需要用memmov替代memcpy。理解user stack空间很有限不能在栈上定义过大的临时对象。一般而言,用户栈只有几兆(典型大小是4M,8M),所以栈上创建的对象不能太大。用sprintf格式化字符串的时候,类型和符号要严格匹配因为sprintf的函数实现里是按格式化串从栈上取参数,任何不一致,都有可能引起不可预知的错误; /usr/include/inttypes.h里定义了跨平台的格式化符号,比如PRId64用于格式化int64_t用c标准库的安全版本(带n标识)替换非安全版本比如用strncpy替代strcpy,用snprintf替代sprintf,用strncat代替strcat,用strncmp代替strcmp,memcpy(dst, src, n)要确保[dst,dst+n]和[src, src+n]都有有效的虚拟内存地址空间。多线程环境下,要用系统调用或者库函数的安全版本代替非安全版本(_r版本),谨记strtok,gmtime等标准c函数都不是线程安全的。STL容器的遍历删除要小心迭代器失效vector,list,map,set等各有不同的写法:int main(int argc, char *argv[]){    //vector遍历删除    std::vector v(8);    std::generate(v.begin(), v.end(), std::rand);    std::cout << "after vector generate...\n";    std::copy(v.begin(), v.end(), std::ostream_iterator(std::cout, "\n"));    for (auto x = v.begin(); x != v.end(); )    {        if (*x % 2)            x = v.erase(x);        else            ++x;    }     std::cout << "after vector erase...\n";    std::copy(v.begin(), v.end(), std::ostream_iterator(std::cout, "\n"));     //map遍历删除    std::map m = {{1,2}, {8,4}, {5,6}, {6,7}};    for (auto x = m.begin(); x != m.end(); )    {        if (x->first % 2)            m.erase(x++);        else            ++x;    }    return 0;}有时候遍历删除的逻辑不是这么明显,可能循环里调了另一个函数,而该函数在某种特定的情况下才会删除当前元素,这样的话,就是很长一段时间,程序都运行得好好的,而当你正跟别人谈笑风生的时候,忽然crash,这就尴尬了。圣斗士星矢项目曾经遭遇过这个问题,基本规律是一个礼拜game server crash一次,折磨团队将近一个月。比较low的处理方式可以把待删元素放到另一个容器WaitEraseContainer里保存下来,再走一趟单独的循环,删除待删元素。当然,我们推荐在遍历的同时删除,因为这样效率更高,也显得行家里手。三、性能空间换取时间通过空间换取时间是提高性能的惯用法,bitmap,int map[]这些惯用法要了然于胸。减少拷贝 & COW了解Copy On Write。只要可能就应该减少拷贝,比如通过共享,比如通过引用指针的形式传递参数和返回值。延迟计算和预计算比如游戏服务器端玩家的战力,由属性a,b决定,也就是说属性a,b任何一个变化,都需要重算战力;但如果ModifyPropertyA(),ModifyPropertyB()之后,都重算战力却并非真正必要,因为修改属性A之后有可能马上修改B,两次重算战力,显然第一次重算的结果会很快被第二次的重算覆盖。而且很多情况下,我们可能需要在心跳里,把最新的战力值推送给客户端,这样的话,ModifyPropertyA(),ModifyPropertyB()里,我们其实只需要把战力置脏,延迟计算,这样就能避免不必要的计算。在GetFightValue()里判断FightValueDirtyFlag,如果脏,则重算,清脏标记;如果不脏,直接返回之前计算的结果。预计算的思想类似。分散计算分散计算是把任务分散,打碎,避免一次大计算量,卡住程序。哈希减少字符串比较,构建hash,可能会多费一点存储空间,但收益可观,信我。日志节制日志的开销不容忽视,要分级,可以把日志作为debug手段,但要release干净。编译器为什么不给局部变量和成员变量做默认初始化因为效率,C++被设计为系统级的编程语言,效率是优先考虑的方向,c++秉持的一个设计哲学是“不为不必要的操作付出任何额外的代价”。所以它有别于java,不给成员变量和局部变量做默认初始化,如果需要赋初值,那就由程序员自己去保证。结论:从安全的角度出发,不应使用未初始化的变量,定义变量的时候赋初值是一个好的习惯,很多错误皆因未正确初始化而起,C++11支持成员变量定义的时候直接初始化,成员变量尽量在成员初始化列表里初始化,且要按定义的顺序初始化。理解函数调用的性能开销(栈帧建立和销毁,参数传递,控制转移),性能敏感函数考虑inlineX86_64体系结构因为通用寄存器数目增加到16个,所以64位系统下参数数目不多的函数调用,将会由寄存器传递代替压栈方式传递参数,但栈帧建立、撤销和控制转移依然会对性能有所影响。递归的优点、缺点虽然递归函数能简化程序编写,但也常常带来运行速度变慢的问题,所以需要预估好递归深度,优先考虑非递归实现版本。递归函数要有退出条件且不能递归过深,不然有爆栈危险。四、数据结构和容器了解std::vector的方方面面和底层实现vector是动态扩容的,2的次方往上翻,为了确保数据保存在连续空间,每次扩充,会将原member悉数拷贝到新的内存块; 不要保存vector内对象的指针,扩容会导致其失效 ;可以通过保存其下标index替代。运行过程中需要动态增删的vector,不宜存放大的对象本身 ,因为扩容会导致所有成员拷贝构造,消耗较大,可以通过保存对象指针替代。resize()是重置大小;reserve()是预留空间,并未改变size(),可避免多次扩容; clear()并不会导致空间收缩 ,如果需要释放空间,可以跟空的vector交换,std::vector .swap(v),c++11里shrink_to_fit()也能收缩内存。理解at()和operator[]的区别 :at()会做下标越界检查,operator[]提供数组索引级的访问,在release版本下不会检查下标,VC会在Debug版本会检查;c++标准规定:operator[]不提供下标安全性检查。C++标准规定了std::vector的底层用数组实现,认清这一点并利用这一点。常用数据结构数组:内存连续,随机访问,性能高,局部性好,不支持动态扩展,最常用。链表:动态伸缩,脱离插入极快,特别是带前后驱指针,内存通常不连续(当然可以通过从固定内存池分配规避),不支持随机访问。查找:3种:bst,hashtable,基于有序数组的bsearch。二叉搜索树(RBTree),这个从begin到end有序,最坏查找速度logN,坏处内存不连续,节点有额外空间浪费;hashtable,好的hash函数不好选,搜索最坏退化成链表,难以估计捅数量,开大了浪费内存,扩容会卡一下,无序;基于有序数组的bsearch,局部性好,insert/delete慢。五、最佳实践对于在启动时加载好,运行中不变化的查询结构,可以考虑用sorted array替代map,hash表等因为有序数组支持二分查找,效率跟map差不多。对于只需要在程序启动的时候构建(排序)一次的查询结构,有序数组相比map和hash可能有更好的内存命中性(局部命中性)。运行过程中,稳定的查询结构(比如配置表,需要根据id查找配置表项,运行过程中不增删),有序数组是个不错的选择;如果不稳定,则有序数组的插入删除效率比map,hashtable差,所以选用有序数组需要注意适用场合。std::map or std::unorder_map?想清楚他们的利弊,map是用红黑树做的,unorder_map底层是hash表做的,hash表相对于红黑树有更高的查找性能。hash表的效率取决于hash算法和冲突解决方法(一般是拉链法,hash桶),以及数据分布,如果负载因子高,就会降低命中率,为了提高命中率,就需要扩容,重新hash,而重新hash是很慢的,相当于卡一下。而红黑树有更好的平均复杂度,所以如果数据量不是特别大,map是胜任的。积极的使用const理解const不仅仅是一种语法层面的保护机制,也会影响程序的编译和运行。const常量会被编码到机器指令。理解四种转型的含义和区别避免用错,尽量少用向下转型(可以通过设计加以改进)static_cast, dynamic_cast,const_cast,reinterpret_cast,傻傻分不清?C++砖家说:一句话,尽量少用转型,强制类型转换是C Style,如果你的C++代码需要类型强转,你需要去考虑是否设计有问题。理解字节对齐字节对齐能让存储器访问速度更快。字节对齐跟cpu架构相关,有些cpu访问特定类型的数据必须在一定地址对齐的储存器位置,否则会触发异常。字节对齐的另一个影响是调整结构体成员变量的定义顺序,有可能减少结构体大小,这在某些情况下,能节省内存。牢记3 rules和5 rules,当然C++11又多了&&的copy ctor和op=版本只在需要接管的时候才自定义operator=和copy constructor,如果编译器提供的默认版本工作的很好,不要去自找麻烦,自定义的版本勿忘拷贝每一个成分,如果要接管就要处理好。组合优先于继承,继承是一种最强的类间关系典型的适配器模式有类适配器和对象适配器,一般而言,建议用对象适配的方式,而非用基于继承的类适配方式。减少依赖,注意隔离最大限度的减少文件间的依赖关系,用前向声明拆解相互依赖。了解pimpl技术。头文件要自给自足,不要图省事all.h,不要包含不必要的头文件,也不要把该包含的头文件推给user去包含,一句话,头文件包含要不多不少刚刚好。严格配对打开的句柄要关闭,加锁/解锁,new/delete,new[]/delete[],malloc/free要配对,可以使用RAII技术防止资源泄露,编写符合规范的代码Valgrind对程序的内存使用方式有期望,需要干净的释放,所以规范编程才能写出valgrind干净的代码,不然再好的工具碰到不按规划写的代码也是武功尽废啊。理解多继承潜在的问题,慎用多继承多继承会存在菱形继承的问题,多个基类有相同成员变量会有问题,需要谨慎对待。有多态用法抽象基类的析构函数要加virtual关键字主要是为了基类的析构函数能得到正确的调用。virtual dtor跟普通虚函数一样,基类指针指向子类对象的时候,delete ptr,根据虚函数特征,如果析构函数是普通函数,那么就调用ptr显式(基类)类型的析构函数;如果析构函数是virtual,则会调用子类的析构函数,然后再调用基类析构函数。避免在构造函数和析构函数里调用虚函数构造函数里,对象并没有完全构建好,此时调用虚函数不一定能正确绑定,析构亦如此。从输入流获取数据,要做好数据不够的处理,要加try catch;没有被吞咽的exception,会被传播从网络数据流读取数据,从数据库恢复数据都需要注意这个问题。协议尽量不要传float,如果传float要了解NaN的概念,要做好检查,避免恶意传播可以考虑用整数替代浮点,比如万分之五(5%%),就保存5。定义宏要遵循常规要对每个变量加括弧,有时候需要加do {} while(0)或者{},以便能将一条宏当成一个语句。要理解宏在预处理阶段被替换,不用的时候要#undef,要防止污染别人的代码。了解智能指针和指针的误用理解基于引用计数法的智能指针实现方式,了解所有权转移的概念,理解shared_ptr和unique_ptr的区别和适用场景考虑用std::shared_ptr管理动态分配的对象。指针能带来弹性,但不要误用,它的弹性指一方面它能在运行时改变指向,可以用来做多态,另一方面对于不能固定大小的数组可以动态伸缩,但很多时候,我们对固定大小的array,也在init里new/malloc出来,其实没必要,而且会多占用sizeof(void*)字节,而且增加一层间接访问。size_t到底是个什么?我该用有符号还是无符号整数?size_t类型是被设计来保存系统存储器上能保存的对象的最大个数。32位系统,一个对象最小的单位是一个字节,那2的32次方内存,最多能保存的对象数目就是4G/1字节,正好一个unsigned int能保存下来(typedef unsigned int size_t)。同样,64位系统,unsigned long是8字节,所以size_t就是unsigned long的类型别名。对于像索引,位置这样的变量,是用有符号还是无符号呢?像money这样的属性呢?一句话:要讲道理,用最自然,最顺理成章的类型。比如索引不可能为负用size_t,账户可能欠钱,则money用int。比如:template <class T> class vector{    T& operator(size_t index) {}};标准库给出了最好的示范,因为如果是有符号的话,你需要这样判断if (index < 0 || index >= max_num) throw out_of_bound();而如果是无符号整数,你只需要判断 if (index >= max_num),你认可吗?整型一般用int,long就很好,用short,char需要很谨慎,要防止溢出整型包括int,short,long,long long和char,没错,char也是整型,float是实型。绝大多数情况下,用int,long就很好,long一般等于机器字长,能直接放到寄存器,硬件处理起来速度也通常更快。很多时候,我们希望用short,char达到减少结构体大小的目的。但是由于字节对齐的原因,可能并不能真正减少大小,而且1,2个字节的整型位数太少,一不小心就溢出了,需要特别注意。所以,除非在db、网络这些对存储大小非常敏感的场合,我们才需要考虑是否以short,char替代int,long。其他情况下,就相当于为省电而不开楼道的灯,省不了多少钱却冒着摔断腿的危险。局部变量更没有必要用(unsigned) short,char等,栈是自动伸缩的,它既不节省空间,还危险,还慢。六、扩展了解c++高阶特性模板和泛型编程,union,bitfield,指向成员的指针,placement new,显式析构,异常机制,nested class,local class,namespace,多继承、虚继承,volatile,extern "C"等有些高级特性只有在特定情况下才会被用到,但技多不压身,平时还是需要积累和了解,这样在需求出现时,才能从自己的知识库里拿出工具来对付它。了解C++新标准关注新技术,c++11/14/17、lambda,右值引用,move语义,多线程库等c++98/03标准到c++11标准的推出历经13年,13年来程序设计语言的思想得到了很大的发展,c++11新标准吸收了很多其他语言的新特性,虽然c++11新标准主要是靠引入新的库来支持新特征,核心语言的变化较少,但新标准还是引入了move语义等核心语法层面的修改,每个CPPer都应该了解新标准。OOD设计原则并不是胡扯设计模式六大原则(1):单一职责原则设计模式六大原则(2):里氏替换原则设计模式六大原则(3):依赖倒置原则设计模式六大原则(4):接口隔离原则设计模式六大原则(5):迪米特法则设计模式六大原则(6):开闭原则熟悉常用设计模式,活学活用,不生搬硬套神化设计模式和反设计模式,都不是科学的态度,设计模式是软件设计的经验总结,有一定的价值;GOF书上对每一个设计模式,都用专门的段落讲它的应用场景和适用性,限制和缺陷,在正确评估得失的情况下,是鼓励使用的,但显然,你首先需要准确get到她。
  • [产品体验官] 使用昇腾弹性云服务器实现黑白图像上色应用(C++)体验2
    一开始打开Xfce 终端启动“Mind Studio”会报个错,不用管它,不影响实验进行。
  • [C/C++] C和C++
    C语言是面向过程的,而C++是面向对象的C和C++的区别:C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制)。C++,首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。 所以C与C++的最大区别在于它们的用于解决问题的思想方法不一样。之所以说C++比C更先进,是因为“ 设计这个概念已经被融入到C++之中 ”。C与C++的最大区别:在于它们的用于解决问题的思想方法不一样。之所以说C++比C更先进,是因为“ 设计这个概念已经被融入到C++之中 ”,而就语言本身而言,在C中更多的是算法的概念。那么是不是C就不重要了,错!算法是程序设计的基础,好的设计如果没有好的算法,一样不行。而且,“C加上好的设计”也能写出非常好的东西。
  • [产品体验官] 使用昇腾弹性云服务器实现黑白图像上色应用(C++)
    老照片上色也是用的这个吧~这个实验很好理解,注意在这里ctrl+s不大好打出来,曲线救国的方法是:
  • [分享交流] xmake v2.3.6 发布, 新增 fortran 编译支持
    文章转载自 OSCHINA 社区 [http://www.oschina.net]这个版本重点对其他语言的支持做了一些改进,比如新增了fortran的编译支持,zig语言的实验性支持,另外对golang/dlang增加了第三方依赖包支持以及交叉编译支持。虽然,xmake重点关注c/c++的构建支持,但是其他语言的支持xmake也会不定期做一些改进,其主要目的并不是替代它们官方自身的构建系统,仅仅只是为了支持与c/c++的混合编译,更好的为c/c++项目服务, 毕竟有些c/c++项目中,还是会偶尔调用其他语言的代码接口,比如与cuda, dlang, objc,swift, asm等语言的混合调用,所以xmake还是会对他们做一些基础性的编译支持。另外,关于c/c++方面,我们也对vs预览版中新的/sourceDependencies xxx.json输出的头文件依赖格式也做了支持(这对于多语言下,头文件依赖检测会更加的可靠稳定)。项目源码官方文档新特性介绍Fortran语言编译支持这个版本开始,我们已经完全支持使用gfortran编译器来编译fortran项目,我们可以通过下面的命令,快速创建一个基于fortran的空工程:$ xmake create -l fortran -t console test它的xmake.lua内容如下:add_rules("mode.debug", "mode.release")target("test")     set_kind("binary")     add_files("src/*.f90")更多代码例子可以到这里查看:Fortran ExamplesZig语言实验性支持注:目前这个语言xmake还在试验性支持阶段,还很不完善,比如:windows上不支持,linux/macOS下动态库编译还不支持,请自行评估使用。我们可以通过下面的配置方式,尝试性体验下,至少linux/macOS下console和static library程序还是可以跑的。add_rules("mode.debug", "mode.release")target("test")     set_kind("binary")     add_files("src/*.zig")至于为啥windows不支持呢,详情见我之前提给zig的issues,#5825而动态库不支持,也是因为我躺了一些坑(zig生成的动态库会自动追加.0.0.0),详情见:issue 5827另外还躺了下其他坑,个人感觉坑有点多,所以我暂时还是试验阶段,等过段时间再看看。更多例子见:Zig ExamplesGo依赖包和交叉编译支持新版本xmake对go构建支持继续做了一些改进,比如对go的交叉编译也进行了支持,例如我们可以在macOS和linux上编译windows程序:$ xmake f -p windows -a x86另外,新版本对go的第三方依赖包管理也进行了初步支持:add_rules("mode.debug", "mode.release")add_requires("go::github.com/sirupsen/logrus", {alias = "logrus"})add_requires("go::golang.org/x/sys/internal/unsafeheader", {alias = "unsafeheader"})if is_plat("windows") then     add_requires("go::golang.org/x/sys/windows", {alias = "syshost"})else     add_requires("go::golang.org/x/sys/unix", {alias = "syshost"})endtarget("test")     set_kind("binary")     add_files("src/*.go")     add_packages("logrus", "syshost", "unsafeheader")不过还有一些不完善的地方,比如目前必须手动配置所有级联依赖包,会稍微繁琐些,后续有待改进。更多例子见:Go ExamplesDlang/Dub依赖包支持xmake对dlang的dub包管理也进行了支持,可以快速集成dlang的第三方依赖包:add_rules("mode.debug", "mode.release")add_requires("dub::log 0.4.3", {alias = "log"})add_requires("dub::dateparser", {alias = "dateparser"})add_requires("dub::emsi_containers", {alias = "emsi_containers"})add_requires("dub::stdx-allocator", {alias = "stdx-allocator"})add_requires("dub::mir-core", {alias = "mir-core"})target("test")     set_kind("binary")     add_files("src/*.d")     add_packages("log", "dateparser", "emsi_containers", "stdx-allocator", "mir-core")cl.exe新的头文件依赖文件支持msvc的头文件依赖通常需要解析/showIncludes的输出内容,提取里面的includes文件列表来处理依赖编译问题,但是呢,cl.exe对这个的输出做的很不好,includes信息和编译输出是混在一起的。对构建工具处理依赖解析非常不友好,尤其是多语言环境下,如何判断是includes,需要通过前置的Note: including file: 字符串来判断提取,但中文下,又是注意: 包含文件: , 如果换成日语环境,又是日文的前缀字符串,编码格式问题、硬编码问题导致解析处理上,总归不是很完美。关于这一点,最新的vs2019预览版中,微软终于对齐做了改进,通过新的/sourceDependencies xxx.json编译选项,可以更好的输出includes依赖信息,方便多语言环境下的解析提取。另外,这个新选项的输出是独立到单独的json文件中去的,终于不是跟编译输出混一起了,也终于不用痛苦地解析分离编译错误、警告信息、includes列表信息了。输出内容大概长这样:{     "Version": "1.0",     "Data": {         "Source": "z:\\personal\\tbox\\src\\tbox\\tbox.c",         "Includes": [             "z:\\personal\\tbox\\src\\tbox\\tbox.h",             "z:\\personal\\tbox\\src\\tbox\\prefix.h",             "z:\\personal\\tbox\\src\\tbox\\prefix\\prefix.h",             "z:\\personal\\tbox\\src\\tbox\\prefix\\config.h",             "z:\\personal\\tbox\\src\\tbox\\config.h",             ...而新版本中,xmake通过新增内置的core.base.json模块处理json解析,很方便地对新的头文件依赖数据进行解析和支持,优先使用此模式(如果cl是新版本支持的话,老版本cl还是使用/showIncludes)。Xcode插件生成支持目前,我们还没有时间去自己实现xcode工程的生成,但不代表不支持,因为xmake支持生成cmakelists.txt文件,而cmake是支持xcode工程文件生成的,在官方还没有实现之前, 我们也可以通过cmake变相支持它,xmake会自动内部调用cmake中转下生成结果,对用户而言使用上没啥区别,只需要确保cmake已经安装即可:$ xmake project -k xcode!> 等之后有时间,我们会重新自己实现各更加完善的xcode输出插件,也欢迎大家帮忙贡献。xmake-vscode插件intellisense支持近期,我们也更新了下xmake-vscode插件,通过自动生成compile_commands.json到当前项目的.vscode目录下,然后我们只需要配置.vscode/c_cpp_properties.json在里面关联上这个.vscode/compile_commands.json路径 就能实现intellisense自动提示,同步xmake.lua里面的includedirs等配置信息。至于,具体怎么生成c_cpp_properties,官方文档里面有详细说明:https://code.visualstudio.com/docs/cpp/configure-intellisense-crosscompilation里面的主要配置项:  "configurations": [     {      "compileCommands": ".vscode/compile_commands.json",     }   ],更新内容新特性添加xcode工程生成器插件,xmake project -k cmake (当前采用cmake生成)#870: 支持gfortran编译器#887: 支持zig编译器#893: 添加json模块#898: 改进golang项目构建,支持交叉编译#275: 支持go包管理器去集成第三方go依赖包#581: 支持dub包管理器去集成第三方dlang依赖包改进#868: 支持新的cl.exe的头文件依赖输出文件格式,/sourceDependencies xxx.json#902: 改进交叉编译工具链
  • [技术干货] 【转载】Python, C++和Java代码互翻,Facebook开发首个自监督神经编译器
    译者 | 刘畅来源 | AI科技大本营(ID:rgznai100) 将早期的编程语言(例如COBOL)的代码库迁移到现在的编程语言(例如Java或C++)是一项艰巨的任务,它需要源语言和目标语言方面的专业知识。COBOL如今仍在全球大型的系统中广泛使用,因此公司,政府和其他组织通常必须选择是手动翻译其代码库还是尽力维护使用这个可追溯到1950年代的程序代码。 FaceBook公司开发了一个工具TransCoder,这是一个完全自我监督的神经编译器系统,它可以使代码迁移变得更加轻松和高效。本文的方法是第一个能够将代码从一种编程语言转换为另一种编程语言而无需并行数据进行训练的AI系统。本文已经证明TransCoder可以成功地在C++,Java和Python 3之间进行翻译功能。TransCoder的性能优于开源的代码和基于商业规则的翻译程序。在本文的评估中,该模型正确地将90%以上的Java函数转换为C++,将74.8%的C++函数转换为Java,并将68.7%的函数从Java转换为Python。相比之下,市售工具只能正确地将61.0%的功能从C++转换为Java,而开源的翻译器仅能准确地将38.3%的Java函数转换为C++。 自我监督训练对于在编程语言之间进行翻译特别重要。传统的有监督学习方法依赖于大规模的并行数据集进行训练,但是对于COBOL到C++或C++到Python来说,这些数据根本不存在。TransCoder只依赖于仅用一种编程语言编写的源代码,而不需要源代码和目标语言中的相同代码示例。它不需要编程语言方面的专业知识,并且可以很容易地将TransCoder的方法推广到其他编程语言中。本文还创建了专门为此领域设计的新的评估指标。 TransCoder对于将遗留代码库更新为现代编程语言可能很有用,现代编程语言通常更高效且易于维护。它还展示了一个神经机器翻译技术应用的新领域。与Facebook AI以前使用神经网络解决高级数学方程式的工作一样,本文认为NMT可以帮助完成通常与翻译或模式识别任务无关的其他任务。特意为编程语言建立序列到序列模型 在自然语言中,即使在越来越依赖自动化机器翻译系统的专业翻译人员群体中,神经机器翻译的最新结果也被广泛认可。但是,由于代码翻译领域中缺少并行数据,因此它们在该方面的应用受到了限制。程序员仍然依赖于基于规则的代码转换工具,这需要专家复查和调试输出,或者手动翻译代码。TransCoder通过利用无监督机器翻译到编程语言翻译取得的成功来克服这些挑战。 本文构建了一个带有注意力机制的序列到序列(seq2seq)模型,该模型由具有转换结构的编码器和解码器组成。TransCoder使用单个共享模型,部分基于Facebook AI以前在XLM上针对所有编程语言所做的工作。本文按照Facebook AI先前的研究中详细介绍的无监督机器翻译的三个原则进行了训练:初始化,语言建模和反向翻译。本文首先利用开源GitHub项目中的源代码使用蒙版语言模型(MLM)对本文的模型进行了预训练。就像自然语言处理中的上下文一样,这种预训练会创建跨语言的嵌入:在相似上下文中使用来自不同编程语言的关键字在嵌入空间(例如catch和except)中非常接近。这些嵌入的跨语言性质来自存在于多种语言中的大量通用的标记(锚点)。锚点的示例包括C++,Java和Python通用的关键字(例如,for,while,if,try),以及源代码中出现的数学运算符,数字和英语字符串。 使用MLM进行预训练使TransCoder可以生成输入序列的高质量表示。然而,由于从未训练过解码器基于源表示对序列进行解码,因此解码器缺乏翻译能力。为了解决此问题,本文训练了该模型使用降噪自动编码(DAE)对序列进行编码和解码。DAE的工作方式类似于有监督的机器翻译算法,在该算法中,模型被训练为在给定序列存在损坏的情况下预测该序列。作为输入给解码器的第一个符号是指示输出编程语言的特殊标记。在测试时,该模型可以对Python序列进行编码,并使用C++起始符号对其进行解码以生成C++翻译器。C++翻译的质量将取决于模型的“跨语言”:如果编码器将Python函数和有效的C++翻译映射到相同的潜在表示,则解码器将成功翻译成C++。 仅预训练的跨语言模型和自动降噪就足以进行翻译。但是,这些翻译的质量往往很低,因为从未训练过该模型在测试时应该完成什么样的工作,即从一种语言翻译为另一种语言的功能。为了解决此问题,本文使用反向翻译,这是在弱监督的情况下利用单语数据的最有效方法之一。对于每种目标语言,本文使用一个模型和一个不同的开始标记。它经过训练可以从源到目标以及从目标到源并行转换。目标到源版本用于将目标序列翻译成源语言,从而产生与标记目标(GT)序列相对应的嘈杂源序列。然后以弱监督的方式训练模型,让模型可以从嘈杂的源序列中重建目标序列,并学习从源到目标的转换。直到收敛为止。 为了评估该模型,以前对源代码翻译的大多数研究都依赖于自然语言中使用的度量标准,例如BLEU分数或其他基于标记之间相对重叠的方法。但是,这些类型的指标不太适合编程语言。语法差异小的两个程序在执行代码时可能会获得很高的BLEU分数,但会产生非常不同的结果。相反,具有不同实现方式的语义等效程序将具有较低的BLEU分数。另一种度量标准是参考匹配,或与GT完全匹配的翻译百分比,但这通常会低估翻译的质量,因为它无法识别语义上等效的代码。 为了更好地衡量TransCoder和其他代码翻译技术的性能,本文创建了一个称为计算精度的新指标,该指标可评估假设函数在给定相同输入时是否生成与参考相同的输出。我们还将发布测试集以及用于计算该指标的脚本和单元测试。下面的示例显示了TransCoder如何将示例代码从Python转换为C++。我们使用以上的代码作为模型输入:TransCoder成功将Python输入函数SumOfKsubArray转换为C++。它还可以推断参数的类型,返回类型和函数的参数。该模型将Python dequeue()容器附加到C++实现dequeue <>。下面是该模型在C++中的输出:最新研究并在实际应用中提供的帮助 自动代码翻译有可能使在公司或开源项目中工作的程序员更加高效,因为他们可以更轻松地集成公司内其他团队或其他开源项目的各种代码,还可以大大减少更新用早期语言编写的代码库的工作量和成本。 反编译的进步可能会促使公司和其他机构更新到最新的语言并促进未来的创新,这可能让使用服务的人们以及机构本身受益。编程语言机器翻译的进步也可以帮助那些没有时间学习多种语言编程的人。 更广泛地说,人工智能有潜力帮助其他编程任务。例如,Facebook AI以前共享了神经代码搜索,这是一种在查询代码中使用自然语言的方法。以及Getafix,该工具可学习自动为编码错误提供建议修复。虽然TransCoder并非旨在帮助调试或提高代码质量,但它可以帮助工程师迁移旧代码库或使用以其他语言编写的外部代码。 为了促进使用深度学习进行代码翻译的研究,本文还发布了一个测试集,该测试集使其他研究人员可以使用计算精度而不是语义盲模型来评估代码翻译模型。我们期待看到其他人如何在我们与TransCoder的合作基础上继续前进,并为新的翻译任务推进自我监督学习。转载自:CSDN论坛
  • [问题求助] C++如何判断一个浮点数为0?
    c++如何判断一个浮点数为0?
  • [技术干货] 【转载】从C、C++、Java到Python,编程入门到底学什么语言好?
    【转载华为云社区】最近,TIOBE更新了7月的编程语言榜单,常年霸榜的C、Java和Python依然蝉联前三位。万万没想到的是,R语言居然冲到了第八位,创下了史上最佳记录。而且后续随着业内对数据统计和挖掘需求的上涨,R语言热度颇有些势不可挡的架势然而作为程序员吃饭的工具,编程语言之间也形成了某种鄙视链,各大论坛里弥漫着剑拔弩张的气氛,众口难调。也难怪有很多初学者会有疑惑,为什么会有这么多编程语言,我到底应该学什么语言?回顾编程语言几十年来的兴衰起伏,似乎也折射了整个信息产业的变迁消亡,想要在技术的洪流里激流勇进,找准并学精一两门编程语言更加显得至关重要。编程语言的黄金时代“有人不喜欢花括号,开发了Python;有人在一个周末设计了出了JavaScript;有人因为上班太无聊,于是发明了C语言”。关于编程语言的八卦轶事很多,但归根结底,一个编程语言的诞生一定是需求的推动。从面向机器的语言、面向过程的语言到面向对象的语言,编程语言的历史也经历了由繁到简。而互联网崛起的90年代,无疑也是编程语言的黄金时代。套用维基百科的总结,“1990年代未见到有什么重大的创新,大多都是以前构想的重组或变化。”但就是这样的一个时代,却是各种编程语言大放异彩的开始。彼时最主流的编程语言是贝尔实验室两位大佬发明的C/C++语言,互联网的载体计算机的基础设施,都是用这套语言来编写的,包括Linux操作系统、数据库、编译器等等。这也奠定了C语言在IT领域的地位,只要有计算机的存在,C/C++肯定会有它们的一席之地。但后来者居上的故事也随时在发生,这里的主角是已经落幕的昔日巨头网景和Sun。1994年,网景发布了一款真正的网络浏览器Navigator,但受限于当时的技术限制,Navigator更像是一个本地展示页面,不具备任何交互功能。巧的是,Sun公司在1995年正式向市场推出他们开发的Java(即Oak语言),特点是“一次编写,到处运行”,网景看中了这个潜力股,选择与Sun结盟。当时的Java可以作为脚本嵌入到网页中实现交互功能,即我们所熟知的Java Applet。然而,网景认为Java作为网页脚本还是过于复杂,程序员Brendan Eich就在10天内,集百家之长,开发了Javascript,如今所有浏览器前端库都需要基于JavaScript实现。计算机的普及、互联网的崛起,Web应用的出现,Ruby、PHP等语言也悉数登场。如知乎作者“强哥学堂”述,“这段时间,虽然没有创造编程语言,但是由于大量企业的参与,编程语言得到了空前的发展,形成了今天的编程语言的基本形态。也使得软件开发越来越标准化,规模化。”与此同时,还有一些“小众”的编程语言,正蓄势待发中,等待着“爆红”的机会。苹果的搅局,生态很关键2010年iPhone 4问世,乔布斯用智能手机改写了之后的互联网发展轨迹。iOS和Android支撑起的智能手机市场,让移动互联网成为绝对的舞台主角。苹果开创了一个全新的生态,作为iOS的应用开发语言,始于1984年的Objective-C从籍籍无名走向声名大噪,依托各种iPhone、iPad应用程序的开发,Objective-C曾一度挺进TIOBE排行榜的Top 3。不过,苹果在2014年的WWDC上推出了新的编程语言Swift,支持编写macOS/OS X、iOS、iPadOS、watchOS和tvOS的软件应用,并在第二年宣布开源、支持Linux。自此之后,Objective-C的市场份额逐年递减,从2014年的12%下降到2016年的1%。从下图也可以看出,新语言Swift从0开始,这几年一直处于稳定增长中,今年1月也冲进了TIOBE榜单的第九位。虽然一门计算机语言从编译器、语法、基础库到在程序员的圈子中普及,并不是短时间可以一蹴而成,但苹果仍然以一己之力将Objective-C和Swift带入了编程语言的主流市场,由此可见,编程语言和一个产品的生态规模息息相关。微软和Visual Basic的关系也是如此,移动互联网时代之前,VB曾经依靠可视化的开发环境,成为最炙手可热的桌面开发工具,并一度拥有300万开发者。而且借助Windows操作系统市场优势,VB如今也一直保持着居高不下的热度(遗憾的是,微软表示不打算继续演化 Visual Basic了)。回到移动互联网时代,另一边的Android选择的是Java,随着Android阵营的越来越庞大,Java一直是TIOBE榜单上的第一,成绩最差也能拿个第二。一个编程语言的长盛不衰,除了它本身的优势之外,其生态的建设也至关重要,毕竟用的人多了,也就能自成一派,这一点在Python的流行中体现的淋漓尽致。大数据+AI时代,Python一骑绝尘当学Python要从娃娃抓起的口号开始流行时,编程语言从IT圈子进入到大众圈。大数据、AI的时代,Python可以说是一骑绝尘,从数据分析、机器学习、Web开发到爬虫开发都可以在Python的帮助下非常轻松的解决。统计分析人员可以用Python解决数据爬取和分析的问题,大学生可以用Python解决租房的问题……最关键的是,Python的学习门槛比C++,Java都要低,简洁的语法、成熟的第三方库,即使是非程序员群体,也能在短时间内速成。用于统计分析、绘图、数据挖掘的R语言也是如此,赶上了数据应用分析的潮流,自然能从众多编程语言中脱颖而出。针对今年7月榜单R语言创纪录的成绩,TIOBE的 CEO Paul Jansen 明确表示有两大趋势起着关键作用:一是商业统计语言和软件包(如 SAS、Stata 和 SPSS)的时代已经结束,大学和研究机构采用Python和R进行统计分析;二是与COVID-19相关的研究需要进行大量统计和数据挖掘,所以易于学习和使用的统计编程语言热度上升。再比如诞生于2010年,由Mozilla主导开发的Rust语言,其更加强调安全性、存储器配置、以及并发处理等特性。去年Facebook推出的区块链项目Libra,就选择了Rust语言来构建,因为它足够安全。Rust的排名也从2012年的200名开外,在最新一期TIOBE榜单上冲进了Top20。我们以互联网的崛起为分界线,可以看到各个编程语言的此消彼长,必然伴随着新的产品和技术。顺应新技术发展的编程语言,即便时隔几十年,也可以重新焕发光彩。而且随着开源社区的壮大,一门优秀的编程语言可以博采众家之长,成长为后起之秀。结语如果你想以编程语言立生,建议你先学习Java,然后是Python和C++,因为这三种编程语言,基本上可以解决我们工作中遇到的大部分问题。所以,不妨先试试华为云推出的Java开发全栈成长计划,让你0基础入门,构建Java技能体系,向全栈工程师迈进!最后,如C#之父Anders Hejlsberg曾总结:如果要我概括在未来十年编程语言会变成什么样,首先,我认为编程语言应该变得更加“声明式”,我们需要设法为语言引入一些如元编程,函数式编程的能力,同时可能也要寻找让用户有办法扩展语法,使他们可以构造领域特定语言等等。不过总体来说我想强调的是,对于编程语言,新的范式则是“多范式”编程语言。本文部分内容有参考以下文章:1、《IT行业激荡30年->编程语言变迁史》https://zhuanlan.zhihu.com/p/1068080452、《计算机和编程语言的发展历史》https://blog.csdn.net/abc6368765/article/details/83990756
  • [C/C++] c语言和C++的区别
    他们的应用场合是不一样的C语言:C语言是结构化和模块化的语言,是面向过程的。当程序的规模较小时,C语言运用起来得心应手。但是当问题比较复杂、程序的规模比较大的时候,C语言就会展现出它的局限性。C++:正是因为有大规模的程序需要去处理,C++就应运而生了。C++是由C发展而来的,与C语言兼容。C++既可用于面向过程的结构化程序设计,也可用于面向对象的程序设计,是一种功强大的混合型的程序设计语言。语法的不同1、输入/输出函数 C语言:用一个例子说明一下:int a=1;double d=3.1415926;printf("%d\n",a);printf("a=%d\n",a);printf("b=%6.3f, b=%6.2f, b=%.3f\n",b,b,b);scanf("%d",&a);//取地址,输入a的值%d和%f称为格式说明符,表示以此格式输出对应表达式的值,\n表示换行。%6.3f 中的6表示占六列,.3 表示输出对应浮点表达式值时只输出三位小数。C++:也是用一个例子说明:int a=5;float b;cout << "a="<cin >>b;cout必须要和”<<“一起使用,cin必须要和”>>“一起使用。我觉得C++中的输入输出流是很强大的,它不像C里面还要指定格式。endl表示换行。2、字符串另外一个我觉得C++里面另外一个特别好用的用来存放字符串的就是string,它是在C++标准库中声明的一个字符串类。C语言:char a[20]="hangzhou";char *p="hangzhou";C++: string s1="abcd";string s2="hello world";string s3=s1+s2;s1=s2;c++比c更方便许多。3、头文件C语言中所有的头文件都带后缀 .h(比如stdio.h)C++中有系统提供的头文件不带后缀 .h,用户自己编制的头文件可以有后缀 .h。4、结构体和类C语言:结构体struct Student //声明了一个名为Student的结构体类型{ int num;char name[20];char sex;} ;Student stud1,stud2; //定义了两个结构体变量 ,默认情况下为public类型。C++:类class Student{ private:int num;char name[20];char sex;public:void display(){ cout<<"num="<cout<<"name="<cout<<"sex="<};Student stud1,stud2;//定义了两个Student类的对象转自知乎@cdfarsight
  • [大赛专区] 口罩大作战(赛道一)陪跑经历和算法思路代码(C++)分享
      因为一直在电脑旁上网课并且一直拿柜子当椅子坐导致腰肌劳损,所以直到现在才来发分享帖,下面开始正题。  本来早就在学校官方网站上就看到了该比赛,因为课程大作业的原因所以一直推到4月28日才正式开始报名编写代码~  一开始看到题目我首先想到的是TSP(旅行商)问题,就是多次全局搜索取次优解,想到之前编写过用蚁群算法编写过TSP代码(想着这下可以直接搬过来用了),但看到测试用例说要1000幅地图在5分钟内跑完,我开始犹豫了,想起之前跑蚁群的时候一幅地图就要搜索几千次才能得出一个次优解,想着这1000幅地图有点够呛,但自始至终都没注意一幅地图的大小限定(12*12),我脑内一直想的是有在任意大小的地图内N个口罩需求区,1个随机仓库,N个随机口罩捐赠区的配送问题(这也是我一直的毛病看到一个问题就想实现一个通用性比较强的解法,结果最后是试用在用例上效果并不好~~~~)于是我放弃了蚁群搜索算法和Dijkstra这些耗时的搜索算法,使用构造函数的方法(然后陷入了大坑无法自拔~~)第一天(4月28日)——完成了算法思路构建和数据结构编写(代码第一版)1、首先是既然是构造函数法肯定是要有权重值,题目中说基于距离最短,所以将距离因素权重设为0.8,而口罩数量的因素权重设为0.2,然后是每个建筑的位置和口罩数,而且在配送的过程中要时刻删除相关的坐标,所以使用链表数据结构来存储位置信息和口罩数信息,然后是建筑物信息,题目的建筑物可以分为两类,一类是仓库类(捐献小区也可看作是仓库)这类结构体链表中的口罩数是正数;一类是待配送类,这类结构体链表中的口罩数是负数,定义最大口罩数为10000;#define MAX 10000 struct coordinate { int x;         //坐标位置       int y; int cost; struct coordinate *next = NULL; }; class building { public: int sum;                    //目前建筑数量 struct coordinate *last; struct coordinate *first; building() { sum = 0; first =  NULL; last =  NULL; } };2、接着是开始main函数编写while循环监控是否有命令输入,若命令是S则是仓库命令,调用add函数向warehouse建筑类内添加仓库;若命令是R则是小区命令,若num>0则是捐赠小区,向warehouse建筑类内添加,若num<0则是需求小区,向resident建筑类内添加,并且记录总共的需求数need_sum;如果命令为G,则是移动命令,开始执行action函数,若需求小区数sum为0,结束;int main() { char op; int x, y; int num; //int count1 = 0; int Delivery_x, Delivery_y, Delivery = 100; int need_sum = 0; //int total = 0; struct coordinate *pre =  NULL; building warehouse = building(); building resident = building(); while (cin >> op) { if (op == 'S') { cin >> x >> y; add(&warehouse, x, y); if (warehouse.first->next == warehouse.last) { Delivery_x = warehouse.last->x; Delivery_y = warehouse.last->y; } } else if (op == 'R') { cin >> x >> y >> num; if (num > 0) { pre = add(&warehouse, x, y, num); if (x == Delivery_x && y == Delivery_y) { change(&warehouse, &resident, &Delivery_x, &Delivery_y, &Delivery, &need_sum, 2, warehouse.last, pre); } } else { need_sum += abs(num); add(&resident, x, y, num); } } if (op == 'G') { action(&warehouse, &resident, &Delivery_x, &Delivery_y, &Delivery, &need_sum); if (resident.sum == 0) { break; } } } }3、main函数涉及相关函数(1)添加建筑函数add这个就没什么多说的了,建立新的位置信息类结构体,在连到链表上面;(不要在意其中的cout,这是运行测试模块能不能正常运行用的)struct coordinate * establish(int x, int y, int cost = MAX) { struct coordinate *p = new struct coordinate; p->x = x; p->y = y; p->cost = cost; return p; } void action(class building *wa, class building *re, int *d_x, int *d_y, int *sum, int *need, int flat, struct coordinate *n, struct coordinate *n_pre); struct coordinate* add(class building *build, int x, int y, int num = MAX) { struct coordinate* pre =  NULL; if (build->first ==  NULL) { build->first = new struct coordinate; build->last = establish(x, y, num); (*build).first->next = build->last; //cout<<(*build).first->next->x<<(*build).first->next->y<<(*build).first->next->cost<<endl; //cout<<(*build).last->x<<(*build).last->y<<(*build).last->cost<<endl; } else { pre = build->last; (*build).last->next = establish(x, y, num); build->last = (*build).last->next; //cout<<(*build).first->next->x<<(*build).first->next->y<<(*build).first->next->cost<<endl; //cout<<(*build).last->x<<(*build).last->y<<(*build).last->cost<<endl; } build->sum++; return pre; }(2)移动函数action1、如果当前配送员口罩数大于等于需要口罩数的平均数的一半(为什么是大于等于需要口罩数的平均数的一半,因为假设极端情况所有的需求小区都是200,那么派送员最大是100,要满足配送需求就是大于等于此时需要口罩数的平均数的一半即100),为什么还有(*sum)!=0,这是后期发现的bug之后再说,我们现在按时间顺序来说,满足条件后说明可以配送      当此时发现配送员发现持有口罩数大于等于需求区口罩数时(*sum >= abs(p->cost)),计算该需求区的权重意愿值权重函数构造思路------前半部分:float(abs(*d_x - p->x) + abs(*d_y - p->y))*beta距离越短越好后半部分:float(abs(*d_x - p->x) + abs(*d_y - p->y)) / abs(p->cost)*alpha  单位距离配送越多越好     当此时发现配送员发现持有口罩数小于需求区口罩数时(*sum < abs(p->cost))时,使用另一个构造函数计算该需求区的权重意愿值权重函数构造思路------前半部分:同上  后半部分:a = float(abs(*d_x - p->x) + abs(*d_y - p->y))*beta + float(abs(*d_x - p->x) + abs(*d_y - p->y)) /b*alpha  因为此时所持口罩数大于均值,优先配送需求大的小区,减小需求大的小区从仓库到该区域的来回步数;2、如果当前配送员口罩数大于所有的需求,若有则判断权重意愿值3、否则如果有当前配送员口罩数大于需求小区的,则使用权重函数判断此时权重float(abs(*d_x - p->x) + abs(*d_y - p->y)) / abs(p->cost);  单位距离配送越多越好4、若有配送目标则a!=-1,则移动到该点,移动玩之后判断是否移动到了其他需求小区的位置或捐献小区的位置,若有进入change函数改变对应的口罩数;5、否则跳出前往获取口罩Supplementary(wa, re, d_x, d_y, sum, need);void action(class building *wa, class building *re, int *d_x, int *d_y, int *sum, int *need) { float alpha = 0.2, beta = 0.8, a = -1; int best_dis = MAX; int b; struct coordinate *p = re->first->next; struct coordinate *p1 = re->first->next; struct coordinate *pre = re->first; struct coordinate *best= NULL; struct coordinate *best_pre = NULL; while (p !=  NULL) { if (*sum >= (*need) / (re->sum * 2)&&(*sum)!=0) {                 if (*sum >= abs(p->cost)) { a = float(abs(*d_x - p->x) + abs(*d_y - p->y))*beta + float(abs(*d_x - p->x) + abs(*d_y - p->y)) / abs(p->cost)*alpha; //cout<<"a="<<a<<"SUM="<<*sum<<"AVG"<<*need<<"RE"<<(re->sum*2)<<endl; if (a < best_dis) { best_dis = a; best_pre = pre; best = p; } } else { b=abs(p->cost) - (*sum);                 b=(b==0)?1:b; a = float(abs(*d_x - p->x) + abs(*d_y - p->y))*beta + float(abs(*d_x - p->x) + abs(*d_y - p->y)) /b*alpha; //cout<<"a="<<a<<"SUM="<<*sum<<"AVG"<<*need<<"RE"<<(re->sum*2)<<endl; if (a < best_dis) { best_dis = a; best_pre = pre; best = p; } } } else if(*sum >= (*need)){ a = float(abs(*d_x - p->x) + abs(*d_y - p->y))*beta + float(abs(*d_x - p->x) + abs(*d_y - p->y)) / abs(p->cost)*alpha; //cout<<"a="<<a<<"SUM="<<*sum<<"AVG"<<*need<<"RE"<<(re->sum*2)<<endl; if (a < best_dis) { best_dis = a; best_pre = pre; best = p; } } else if (*sum >= abs(p->cost)) { a = float(abs(*d_x - p->x) + abs(*d_y - p->y)) / abs(p->cost); //cout<<"a="<<a<<"SUM="<<*sum<<"AVG"<<*need<<"RE"<<(re->sum*2)<<endl; if (a < best_dis) { best_dis = a; best_pre = pre; best = p; } } else{             break; } pre = p; p = p->next; } //cout<<"目前位置"<<*d_x<<','<<*d_y<<"要去的位置:"<<best->x<<','<<best->y<<endl; if (a != -1) { if (*d_x > best->x) { cout << 'N' << endl; (*d_x)--; } else if (*d_x < best->x) { cout << 'S' << endl; (*d_x)++; } else if (*d_y < best->y) { cout << 'E' << endl; (*d_y)++; } else if (*d_y > best->y) { cout << 'W' << endl; (*d_y)--; } if (*d_x == best->x&&*d_y == best->y) change(wa, re, d_x, d_y, sum, need, 1, best, best_pre); else { int flat = 0; struct coordinate *w = (*wa).first->next; struct coordinate *w_pre = (*wa).first; while (w !=  NULL) { if (abs(*d_x - w->x) == 0 && abs(*d_y - w->y) == 0) { flat = 2; break; } w_pre = w; w = w->next; } struct coordinate *r = (*re).first->next; struct coordinate *r_pre = (*re).first; while (r !=  NULL) { if (abs(*d_x - r->x) == 0 && abs(*d_y - r->y) == 0) { flat = 1; break; } r_pre = r; r = r->next; } if (flat == 2) change(wa, re, d_x, d_y, sum, need, flat, w, w_pre); else change(wa, re, d_x, d_y, sum, need, flat, r, r_pre); } } else { Supplementary(wa, re, d_x, d_y, sum, need); } }(3)获取口罩Supplementary函数1、判断若此时口罩数+捐献小区数大于均值则判断该小区权重意愿值构造函数思路:float(abs(*d_x - p->x) + abs(*d_y - p->y))*beta + float(abs(*d_x - p->x) + abs(*d_y - p->y)) /b*alpha     距离至上,后半部分表示获取口罩的损失率越小越好2、得到位置之后移动,同移动函数action后半部分,先移动后判断是否移动到其他区域;void Supplementary(class building *wa, class building *re, int *d_x, int *d_y, int *sum, int *need) { float a = -1; int best_dis = MAX; int b; struct coordinate *p = (*wa).first->next; struct coordinate *pre = wa->first; struct coordinate *best= (*wa).first->next; struct coordinate *best_pre =wa->first; while (p !=  NULL) { if ((*sum + p->cost) > (*need) / (re->sum * 2)) { b = ((100 - (*sum)) > p->cost) ? (p->cost-(*sum)) : (100-(*sum)); b =(b==0)?1:b; a = float(abs(*d_x - p->x) + abs(*d_y - p->y))*beta + float(abs(*d_x - p->x) + abs(*d_y - p->y)) /b*alpha; //cout<<"获取口罩a="<<a<<"SUM="<<*sum<<"AVG"<<*need<<"RE"<<(wa->sum*2)<<endl; if (a < best_dis) { best_dis = a; best = p; best_pre = pre; } } pre = p; p = p->next; } }(4)口罩数变动函数change这个没什么多说的,flat是区分送口罩还是取口罩,送口罩:足够就送完删除该小区,不够就持有清0  取口罩:足够就取满,不够就取完删除该捐献小区;void change(class building *re, int *d_x, int *d_y, int *sum, int *need, int flat, struct coordinate *best, struct coordinate *best_pre) { if (flat == 1) { if (*d_x == best->x&&*d_y == best->y) { if ((-best->cost) > *sum) { best->cost = best->cost + *sum; (*need) -= *sum; *sum = 0; } else { (*sum) = best->cost + (*sum); (*need) += best->cost; best->cost = 0; //cout<<best->cost<<"剩余"<<(*sum)<<endl; best_pre->next = best->next; re->sum--; if (re->last == best) re->last = best_pre; delete best; if (re->sum == 0) return; } } } if (flat == 2) { if (*d_x == best->x&&*d_y == best->y) { if ((best->cost + (*sum) > 100)) { best->cost = best->cost - (100 - *sum); *sum = 100; } else { (*sum) = best->cost + (*sum); best->cost = 0; best_pre->next = best->next; //cout<<"这里"<<best->next->x<<best->next->y<<best->next->cost<<endl; warehouse.sum--; if (warehouse.last == best) warehouse.last = best_pre; delete best; } } } }然后后面几天就是debug的时间了第二天(4月29日)——debug半天才知道测试不支持C++11 用null替换nullptr,删除了万能开头头文件;第三天(4月30日)1、通过测试一堆数据发现有的时候G命令没有及时移动命令,发现action函数中*sum >= (*need) / (re->sum * 2)这个判断条件若need==1,re->sum==1,sum==0时会卡住,所以加上sum!=0的条件2、添加了捐献小区落脸上的条件判断,第一次提交成功也是最成功的一次,之后不管如何修改都平均步数会更低;第四天(5月1日)——通过分析几天的每日PK例子发现代码在一个区域配送时,有时会跑到其他区域配送增加重复的步数,猜想分区域配送可能会好一点,分为右上配送,左上配送,左下配送,右下配送,但捐赠小区不分区照常获取最优口罩,让最远的待配送区域留到最后配送,进行逆时针配送,防止出现重复步数;例如:分区后的main函数,代码中涉及到的移动判断位置都要加入分区域判断(篇幅情况不过多赘述原代码)building resident_onright = building(); building resident_belowright=building(); building resident_onleft=building(); building resident_belowleft=building(); building warehouse = building(); int need_sum_onright = 0,need_sum_onleft=0,need_sum_belowleft=0,need_sum_belowright=0;if (op == 'R') { cin >> x >> y >> num; if (num > 0) { pre = add(&warehouse, x, y, num); if (x == Delivery_x && y == Delivery_y) {                     if(x<=wa_x&&y>wa_y) change(&resident_onright, &Delivery_x, &Delivery_y, &Delivery, &need_sum_onright, 2, warehouse.last, pre);                 else if(x<wa_x&&y<=wa_y)                     change(&resident_onleft, &Delivery_x, &Delivery_y, &Delivery, &need_sum_onleft, 2, warehouse.last, pre);                 else if(x>=wa_x&&y<wa_y)                     change(&resident_belowleft, &Delivery_x, &Delivery_y, &Delivery, &need_sum_belowleft, 2, warehouse.last, pre);                 else if(x>wa_x&&y>=wa_y)                     change(&resident_belowright, &Delivery_x, &Delivery_y, &Delivery, &need_sum_belowright, 2, warehouse.last, pre); } } else { if(x<=wa_x&&y>wa_y){                 need_sum_onright += abs(num);                 if(abs(x-wa_x)+abs(y-wa_y)>further_dis){                 further_dis=abs(x-wa_x)+abs(y-wa_y);                 position=1;                 } add(&resident_onright, x, y, num); }else if(x<wa_x&&y<=wa_y){     need_sum_onleft += abs(num);     if(abs(x-wa_x)+abs(y-wa_y)>further_dis){                 further_dis=abs(x-wa_x)+abs(y-wa_y);                 position=2;                 } add(&resident_onleft, x, y, num); }else if(x>=wa_x&&y<wa_y){     need_sum_belowleft += abs(num);     if(abs(x-wa_x)+abs(y-wa_y)>further_dis){                 further_dis=abs(x-wa_x)+abs(y-wa_y);                 position=3;                 } add(&resident_belowleft, x, y, num); }else if(x>wa_x&&y>=wa_y){     need_sum_belowright += abs(num);     if(abs(x-wa_x)+abs(y-wa_y)>further_dis){                 further_dis=abs(x-wa_x)+abs(y-wa_y);                 position=4;                 } add(&resident_belowright, x, y, num); } } }最后debug提交发现并没有什么用~~~直接崩溃第五天(5月2日)没办法生病了,划水度过~~~~~~~~~眼睁睁的看着排名一直下跌从20到37~~~~~~~~~~~~~~~最后附上垃圾代码,以上!
  • [大赛专区] 关于口罩大作战赛道一(C++)的记录和总结——White-Loub
        2020.5.6    20:38    四月末看到学校的通知,加上女朋友的大力“怂恿”,作为一个刚刚入门的小白,我毅然决然的和女朋友一起报名参加了比赛,后面的过程真是一把鼻涕一把泪。我想把我的思路和参赛经历记录下来,也就当做纪念啦。当然啦,由于学识疏浅,我的代码里并没有什么特别厉害的算法,如果能给各位提供一点点一点点参考,那真是。。。很很很很有幸了(注:本人排名30+,和我排名相近的同学和排名在前的大佬们怕是没有什么可以参考的了),献丑啦!        我使用的是C++语言,代码整体思路非常简单。    首先我思考了一下配送口罩的整个过程,其实就是1.快递员取口罩;2.快递员思考目的地;3.快递员送口罩。在代码实现的过程中,思路可以稍微修改一些,比如快递员取口罩和送口罩其实都包含了快递员的移动和口罩数量的变化,为了更好实现,在代码中我们就可以将这两部分分开,所以上面的几点其实可以转换为下面几个模块:1.快递员朝目标地移动;2.快递员携带的口罩数量和建筑物口罩数量变化;3.判断快递员的目标地。其实我认为这道题最难的地方就在于快递员目标地的判断,这部分的算法就导致了快递员的送货效率(完成一幅地图的步数),很显然,作为小白,我不知道该如何更好的处理这一部分,算法真是又难又厉害呀。    明确了刚才的思路,我们就可以开始写具体的代码啦,以下记录的代码主要是样例,具体代码由于比赛还未结束,会在结束后更新!    当然,我们首先先完成地图的录入工作,也就是记录仓库和需求小区的位置(题目开始的6个输入)!/*例如,这是仓库的录入代码*/ char S;//定义仓库名称  int S_x,S_y;//定义仓库变量;我没有使用数组来记录各个建筑物的值,而是使用了整数变量,便于理解 cin>>S; if(S =='S') { cin>>S_x>>S_y; //输入仓库坐标信息 }    然后,就开始代码的主体,就是按照刚才的思路来,在这里,为了获取一个G的输入我们就进行一次移动或者判断,我使用了While循环!第一步的移动可以靠预设的值进行,以后的移动靠上一轮循环判断的目标进行移动,同时,用一个变量(我用的是send_or)判断移动是配送移动还是取货移动可能更容易梳理逻辑,另外不要忘记捐赠小区的输入!char G;//获取系统输入指令 int send_or=1;//设定送货还是取货状态,0为取货,1为送货  int P_x,P_y,P_send;//设定快递员位置和快递员口罩数量  int O_send=0;//设定快递员到的补给点,初始补给点设为仓库(假设仓库编号为0) .............;//部分变量设置未展示 cin>>G; //首先是先获取第一个G的输入,然后开始循环 while(G) {     /*如果新输入为R开头的捐赠小区,则记录离目标点近的补给小区*/       if(G=='R')     {         int i;         cin>>R_donation_x[donation_j]>>R_donation_y[donation_j]>>R_donation_num[donation_j];//我使用数组记录捐赠的小区,比较复杂,不要忘记捐赠小区的录入就好         for(i=1;i<donation_j;i++)         {             if(R_donation_x[donation_j]==R_donation_x&&R_donation_y[donation_j]==R_donation_y)             {                 R_donation_num=R_donation_num+R_donation_num[donation_j];                 R_donation_x[donation_j]=0;                 R_donation_y[donation_j]=0;                 R_donation_num[donation_j]=0;                 donation_j=donation_j-1;                 break;              }         }         donation_j++;     }      /*进入移动阶段*/     /*例如判断是否向仓库移动*/     if(G=='G' && send_or==0 && O_send==0)     { if((S_x-P_x)<0)//判断是否需要向北走  { cout<<'N'<<endl; P_x--; } else if((S_x-P_x)>0)//判断是否需要向南走  { cout<<'S'<<endl; P_x++; } else if((S_y-P_y)>0)//判断是否需要向东走  { cout<<'E'<<endl; P_y++; } else if((S_y-P_y)<0)//判断是否需要向西走  { cout<<'W'<<endl; P_y--;      }     }          /*判断是否向捐赠小区移动*/      .........................//部分代码未展示          /*判断是否向目标小区移动*/     .........................//部分代码未展示 }    然后,是对快递员到达各个位置数量变化的判断,依旧在While循环内,这里要注意两个问题:1.捐赠小区生成在快递员的当前位置;2.移动途中经过了需求小区!while(G) {     ....................//刚才的代码          /*以下进入判断阶段*/     /*如果经过或者踩到仓库的处理方法,只判定数量改变*/     if(P_x==S_x&&P_y==S_y)     {         P_send=100;     }          /*如果经过或者到达捐赠小区,包括捐赠小区生成在快递员位置,只判定数量改变*/     i_pro=1;     for(i_pro=1;i_pro<donation_j;i_pro++)     {          if(P_x==R_donation_x[i_pro]&&P_y==R_donation_y[i_pro])         {             if(P_send+R_donation_num[i_pro]<=100)              {                 P_send=P_send+R_donation_num[i_pro];                 R_donation_num[i_pro]=0;             }             else             {                 R_donation_num[i_pro]=R_donation_num[i_pro]+P_send-100;                 P_send=100;             }                  break;//如果已经确认踩到,就跳出循环          }     }          /*如果经过或者到达需求小区的处理方法,只判定数量改变*/     .....................................//部分代码未展示 }    最后,就是对目标地的判断,主要是判断快递员的目标,再次循环时,快递员再根据目标进行移动、判断数量、判断目标,不断循环!最后部分因为我代码的变量太复杂,展示后不太容易看懂,我就详细的记录和总结一下我的思路,和刚才的例子形成一个整体。刚才我们已经说了,对于目标地点的判断是核心,再此之前,我们要完成需求小区、捐赠小区信息录入,要有数组或者专门的变量进行记录,这样我们可以确定每个建筑;然后是移动,第一步的移动可以靠预设的值进行,以后的移动靠上一轮循环判断的目标进行移动。    最后的判断部分,我的想法很简单,就是每一步结束以后进行判定:1.如果处在取货状态,快递员到达目标点(仓库或者捐赠小区)后,就改成送货状态;或者未到达目标点,但是身上的货物满足当前需求(当前配送的目标需求小区的需求)也改为送货状态;2.如果处于送货状态,快递员身上的货物由于经过了非目标需求小区导致货物数量不达标,则改为取货状态;3.正处于取货状态,先判断除仓库外口罩数量满足当前需求(当前配送的目标需求小区的需求)的捐赠小区,再选择这些小区中距离最近的一个作为取货的目标,如果一个都没有,就设定仓库为取货的目标。    依次循环,便可跑完地图。    注意,这道题中需要注意的几个点,我记录下来:1.捐赠小区可能生成在快递员位置上,此时快递员会取走该捐赠小区的货物(快递员的携带的货物不会超过100,只会补齐缺少的数量);2.捐赠小区的口罩如果没被取完会一直保留;3.经过需求小区和捐赠小区,快递员的数量一定要进行判定是否发生变化。    总结。最后做个总结!我写这一篇记录真是在各位大佬面前献丑啦,感谢华为和各位参赛同学,真是让我见识到算法的神奇!我更加可以理解好的算法在一段代码中起到的核心作用,而且编程真是逻辑性非常强的事情,有时候逻辑上错一点,代码便会运行出错,而为了让自己的思路通畅,就需要我有良好的编程习惯(比如规范的格式、便于理解的变量命名等),就像那句话说的一样:“I'm not a great programmer; I'm just a good programmer with great habits.”OK!感谢各位!我会继续努力,争取早日搞懂诸如启发式搜索之类的算法,然后在最后的几个小时,我希望再改进一下代码,优化一下快递员取货目标的计算!哈哈哈~    冲鸭!--------------------------------------------------------------------我是分割线!---------------------------------------------------------------------    2020.5.7    21:18    更新一波,分享我和女朋友的代码!班门弄斧,献丑啦!    今天晚上看了各位大佬的解题思路,受益匪浅!我们会继续努力的!    继续冲鸭!
  • [大赛专区] 赛道一小结(C++)_本次竞赛算法思路与代码分享以及学习c++的资源链接和计算机速成课视频链接
             从学校官网得知比赛消息,到现在转眼已经快两周了,比赛也接近了尾声,写一个小结也算记录下这次的比赛经历,毫不夸张的再说,这次比赛我投入了几乎自己全部的精力和热情去做这件事,中断了坚持了一个月的健身,刚刚恢复的作息再一次变得乱七八糟,但是看到提交成功也有一种小小的成就感,第一眼看到竞赛标题,“口罩配送”,就觉得新奇和接地气,自己参加比赛之余也觉得自己是在为改善社会生活做了一丁点贡献。       这是我最终成绩的截图,以及有幸登入一次每日PK榜,和前面的大神有好远的距离,看了大神们的分享,发现还是思路有了很大的局限性。       下面说说我的编程之旅吧,看完题目和要求,我就先衡量自己的选择和需要准备的东西,编程语言我选了c++,并没有什么高大上原因的考虑,因为我只会它,然后就是编程软件和开发环境,我用的是VS2019而没选择devc++,是因为用过前者,但是这个选择也让我后来遇到了一个问题。       搭建好编译环境,我就着手于整个程序框架的搭建,我喜欢整体确定后,慢慢完善的感觉,写完了输入输出函数,就开始着眼于方法的实现,一开始太过于贪心,总想着用些高大上的有技巧的方法,结果一整天也没什么进度,主要还是知识储备量匮乏,高深的算法也理解不了就更谈不上灵活使用。后来我就完全自己去想解决方法,首先是数据的整理以及特征化。我建立了下图中这些结构体数组,其中包含地图每一点的信息如同一幅图像数组,配送员的状态,捐赠小区的状态以及配送小区的状态。       结合之前和导师做过图像滤波方面的东西,我就选择了类似于均值滤波,中值滤波这样的小窗口来收集配送员周边的捐赠和配送小区的信息,从而选择移动的方向,后来由于窗口选择太小对突然出现的捐赠小区不敏感,从而路径优化的程度太低放弃了,窗口再大就不如处理整个12*12矩阵了。不过得益于此,从这之后,我从局部最优联想到当前路径最优。即将在最短步数下完成一个配送次数,配送次数就是指满足一个小区所需要到该小区的次数,需求量大于100的为2,小于100的为1。        具体操作就是:        1,当配送员在仓库时,将配送小区信息按照距离仓库的由近到远排序,选择最近的配送点作为配送目标。        2,当配送员到达配送小区后,根据配送员身上的剩余口罩数目判断接下来的目标点,按照配送小区距离配送员的近到远查询配送小区的需求数进行查询,当目前地图未出现捐赠点时,如果配送员自身剩余口罩不满足一个配送次数,则将目标点为仓库。        3,当目前地图出现捐赠点时,配送员自身剩余口罩加上全部捐赠数目满足一个配送次数,则启动最短路径查询函数,以目前所在点到仓库加上距离仓库最近点的配送小区所需要的步数为衡量标准,计算满足一个配送次数所走的最短路径,并将该路径沿途的捐赠点坐标信息以及目标点坐标信息进行保存,每经过一个捐赠点就会将下一步的坐标作为行进目标。       4,当所有配送小区需求数目为0,退出程序。        其中,最短路径查询函数的原理是分别以剩余四个配送点作为路径终点,采用递归方式,进行将捐赠点任意组合形成路径,比如1,2,3三个捐赠点,就会有1,2,3,12,13,123等六条路径,同时对需要经过多捐献点的路径将捐赠点按照走完所需要最短步数的原则进行排序,结合配送员和捐赠点的口罩数将不符合配送次数的路径剔除。       具体代码我分享到附件当中,有兴趣的可以看看。       下图是代码函数中的子函数以及相关注释:       在这个最终版想法之前,我是用路径最优法,选择比原路径降低步数最多的路径进行存储,再将去往每个配送点的路径进行同样比较,选择降低步数最多的路径作为当前的路径。后来经过测试发现当前路径最优法会造成多余步数,就是比如配送员还差一步到达目标,此时出现一个捐赠点,程序判定配送员经过该捐赠点到达另一个目标比当前的路径优化的步数多,它就会毅然去往捐赠点,导致未完成目标,之前走的路就有点多余。为解决这个问题,我就将追求当前路径最优改成了当前路径最短,与路径最优选择方法一致,只不过是选择一条步数总和最少的路,完成程序编写之后就开始了我漫长又难熬的调试过程,用一句话简单却又痛苦的话就是,我走了一些弯路。       线下测试和程序debug的成功和一上传就说我编译错误,让我一度抓狂,后来被群里的一个小伙伴建议用devc++试试,我就用它编译了一下,出了很多警告和一个错误,我才知道vs2019可能会默认一些新编译规则,我改正了警告,修复了错误。然而故事并没有结束,我遇到了群里很多小朋友遇到的问题“配送已结束,仍有小区未获得口罩”,我本着一定是数据或我程序判定部分出问题的观念,不停地debug,验证数据的传递和每一个判断,却什么也没发现,我想是我设想的地图情况太少的原因,就边百度边修改了线下测试程序,改成随机生成地图,测试100幅,报了错误,我将报错的地图一幅幅的分析,都没找到程序哪儿的命令出错了,我仔细看了看测试代码,发现错误原因是两者的信息不一致,后来又是群里一个小伙伴点醒了我,测试程序只能接受方向指令,无法进行多余的操作,即我程序原本设定的是到了配送小区有选择的留下口罩数目,但是测试程序直接扣除了配送员身上的全部,导致两者信息的不一致。Bug找到进行了修复,害怕再失败我就在线下测试了1000幅随机地图,都成功了,我就提交了作品,事实证明我还是太年轻了,网页信息提醒我配送员超出了地图范围,这个原因有两种,一种是接受你的移动指令后,位置超出了地图数组边界,另一种就是配送员原地打转。。。。由于没有地图信息无法调试,我就又跑了随机测试程序,事实证明同志们,测试次数再多也不为过!我跑了3000幅地图!!才有一幅爆出了我超出范围的错误!有了地图后面的单步修改程序就简单了很多,在提交成功后,评分第一次出现的时候,真是超级激动有木有!接下来没有程序细节上的问题,我又对程序算法做了一些调整,将路径最优算法和路径最短算法进行结合使用,路径最优算法用于当配送员位于配送点时触发,能保证当前路径最优,在新捐献点出现时触发路径最短算法,就解决了路径最优算法造成的步数浪费。写了这么多,絮絮叨叨,应该也没人会看完,本来打算5.4时候写一下发一下,庆祝下节日,可惜程序的bug没让我如愿。      以上就是我这次竞赛的全部经历和想法历程了,也算是一种自我梳理。最后这几天状态的总结可以用一个成语来形容,废寝忘食,有时候亲身体会了才明白古人诚不欺我,也许对编程是一种热爱吧,只有热爱才会让我专心致志和想要去完成一件事,一直觉得虚拟行业很酷,你编的每一条代码,每一行指令都是在让那个世界越来越美,希望有一天自己参与完成的一些东西在这个世界的角落,稳定而又高效的运行着,它的每一秒都是在让这个世界更美好。,希望大家多多批评指正!多多留言给我!                                                                                                                                                                                             2020/05/05           注:下面的链接是我在学习c+和数据结构时看的一些视频链接,网上反响比较好的,希望大家一起学习!大家一起进步!          【C语言】《带你学C带你飞》(小甲鱼):https://www.bilibili.com/video/BV17s411N78s?p=1          计算机速成课(很有意思,对于梳理计算机体系很有用):https://www.bilibili.com/video/BV1EW411u7th?p=1          C++从0到1入门编程(附C++编程环境搭建教程):https://www.bilibili.com/video/BV1et411b73Z?p=1          数据结构(郝斌):https://www.bilibili.com/video/BV11s41167h6?p=1
总条数:417 到第 页
上滑加载中