关于c++:C-STL库
常见库的应用
常见库的应用
构造函数 析构函数
构造函数 析构函数
虚函数机制 虚函数表
虚函数机制 虚函数表
内存调配形式在C++中,内存分成5个区,他们别离是堆、栈、自在存储区、全局/动态存储区和常量存储区。 栈在执行函数时,函数内局部变量的存储单元都能够在栈上创立,函数执行完结时这些存储单元主动被开释。栈内存调配运算内置于处理器的指令集中,效率很高,然而调配的内存容量无限。是由编译器在须要时主动调配,不须要时主动革除的变量存储区。通常寄存局部变量、函数参数等。 堆就是那些由new调配的内存块,他们的开释编译器不去管,由咱们的应用程序(程序员)去管制,个别一个new就要对应一个delete,一个new[]与一个delete[]对应。如果程序员没有开释掉,那么在程序完结后,操作系统会主动回收。 自在存储区就是那些由malloc等调配的内存块,他和堆是十分相似的,不过它是用free来完结本人的生命的。 全局/动态存储区全局变量和动态变量被调配到同一块内存中(在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++外面没有这个辨别了,他们独特占用同一块内存区。) 常量存储区这是一块比拟非凡的存储区,他们外面寄存的是常量,不容许批改。 (留神:堆和自在存储区其实不过是同一块区域,new底层实现代码中调用了malloc,new能够看成是malloc智能化的高级版本)
C++11智能指针介绍智能指针次要用于治理在堆上调配的内存,它将一般的指针封装为一个栈对象。当栈对象的生存周期完结后,会在析构函数中开释掉申请的内存,从而避免内存透露。 为什么要应用智能指针智能指针的作用是治理一个指针,因为存在以下这种状况:申请的空间在函数完结时遗记开释,造成内存透露。应用智能指针能够很大水平上的防止这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会主动调用析构函数,析构函数会主动开释资源。所以智能指针的作用原理就是在函数完结时主动开释内存空间,不须要手动开释内存空间。 auto_ptr(C++98的计划,C++11曾经摈弃)采纳所有权模式。 unique_ptrshared_ptrweak_ptr
练习3.1#include<iostream>#include<fstream>#include<iterator>#include<vector>#include<set>#include<map>int main() { std::ifstream in_file("news.txt"); std::ofstream out_file("news_word_cnt.txt"); if (!in_file || !out_file) { std::cout << "can not read/write file" << std::endl; } // 新建一个vector 而后应用泛型算法copy将元素填充到其中 std::vector<std::string> text; std::string useless_words[3] = {"a", "and", "is"}; std::set<std::string> useless_set(useless_words, useless_words + 3); std::map<std::string, int> words_cnt; // 应用iostream iterator std::istream_iterator<std::string> in_stream(in_file); // 代表in_file的begin std::istream_iterator<std::string> in_eof; // 代表in_file的end std::copy(in_stream, in_eof, std::back_inserter(text)); // 前两个的iterator复制到前面的iterator for (std::string word: text) { if (useless_set.count(word) > 0) { continue; } if (words_cnt.count(word) == 0 ) { words_cnt[word] = 1; } else { words_cnt[word]++; } } // map加工为字符串输入 std::vector<std::string> map_str; auto it = words_cnt.begin(); while (it != words_cnt.end()) { map_str.push_back("" + (it -> first) + " : " + std::to_string(it -> second)); it++; } std::ostream_iterator<std::string> out_stream(out_file, "\n"); // 分隔符为空格 std::copy(map_str.begin(), map_str.end(), out_stream); // 为什么这里能够间接写out_stream 呢? return 0;}练习3.2#include<iostream>#include<fstream>#include<iterator>#include<vector>#include<set>#include<map>#include<algorithm>bool comp_str(std::string str1, std::string str2) { return str1.size() > str2.size();}int main() { std::ifstream in_file("news.txt"); if (!in_file) { std::cout << "can not read/write file" << std::endl; } // 新建一个vector 而后应用泛型算法copy将元素填充到其中 std::vector<std::string> text; std::string useless_words[3] = {"a", "and", "is"}; std::set<std::string> useless_set(useless_words, useless_words + 3); std::map<std::string, int> words_cnt; // 应用iostream iterator std::istream_iterator<std::string> in_stream(in_file); // 代表in_file的begin std::istream_iterator<std::string> in_eof; // 代表in_file的end std::copy(in_stream, in_eof, std::back_inserter(text)); // 前两个的iterator复制到前面的iterator for (std::string word: text) { if (useless_set.count(word) > 0) { continue; } if (words_cnt.count(word) == 0 ) { words_cnt[word] = 1; } else { words_cnt[word]++; } } // 对text依照字符串长度排序 std::sort(text.begin(), text.end(), comp_str); for (std::string word: text) { std::cout << word << std::endl; } return 0;}练习3.3#include<iostream>#include<vector>#include<string> #include<map>// 这里map的> 和 vector的> 两头要有空格void printFamily(const std::string &first_name, const std::map<std::string, std::vector<std::string>* >* family_map) { if ((*family_map).count(first_name) > 0) { // 这里find拿到的是一个pair, 取 pair -> second 才是 value auto names = family_map -> find(first_name); for (std::string name : *(names -> second)) { std::cout << "first name:" << name << std::endl; } }}int main() { // 这里定义value为一个指向vector的指针, 避免复制 std::map<std::string, std::vector<std::string>* > family_map; std::string family_1 = "Last Name"; std::string last_name_arr[6] = {"last name1", "last name2", "last name3", "last name4", "last name5", "last name6"}; std::vector<std::string> last_name_vec(last_name_arr, last_name_arr + 6); family_map[family_1] = &last_name_vec; printFamily(family_1, &family_map); return 0;}练习3.4#include<iostream>#include<iterator>#include<algorithm>#include<fstream>#include<vector>int main() { // 定义输出流 std::istream_iterator<int> is(std::cin); // 出入eof完结 std::istream_iterator<int> eof; std::vector<int> numbers; std::copy(is, eof, std::back_inserter(numbers)); std::vector<int> odd_numers; std::vector<int> even_numbers; for (int number : numbers) { if (number % 2 == 0) { even_numbers.push_back(number); } else { odd_numers.push_back(number); } } // 分两个流写出文件 // 留神这里是ofstream 不是 ostream ... // 共有三层关系 // 1. 定义一个 out-file-stream , 往哪里写 std::ofstream os_odd("odd_numbers.file"); std::ofstream os_even("even_numbers.file"); // 2. 定义一个 ostream_iterator , 怎么写 std::ostream_iterator<int> os_odd_iter(os_odd, " "); std::ostream_iterator<int> os_even_iter(os_even, " "); // 3. 定义一个 copy, 用什么数据写 std::copy(odd_numers.begin(), odd_numers.end(), os_odd_iter); std::copy(even_numbers.begin(), even_numbers.end(), os_even_iter); return 0;}这一章课后题还是比较简单, 然而我感觉有些货色还是须要花肯定的工夫去了解的, 次要就是指"如何设计一个泛型算法"那里, 一步一步的由一个一般的定制化的函数, 通过引入函数指针, find_if()泛型算法, function object, function object adapter 来设计成为一个元素无关, 比拟操作符无关, 容器类型无关的泛型算法函数, 值得重复浏览。 ...
练习3.1#include<iostream>#include<fstream>#include<iterator>#include<vector>#include<set>#include<map>int main() { std::ifstream in_file("news.txt"); std::ofstream out_file("news_word_cnt.txt"); if (!in_file || !out_file) { std::cout << "can not read/write file" << std::endl; } // 新建一个vector 而后应用泛型算法copy将元素填充到其中 std::vector<std::string> text; std::string useless_words[3] = {"a", "and", "is"}; std::set<std::string> useless_set(useless_words, useless_words + 3); std::map<std::string, int> words_cnt; // 应用iostream iterator std::istream_iterator<std::string> in_stream(in_file); // 代表in_file的begin std::istream_iterator<std::string> in_eof; // 代表in_file的end std::copy(in_stream, in_eof, std::back_inserter(text)); // 前两个的iterator复制到前面的iterator for (std::string word: text) { if (useless_set.count(word) > 0) { continue; } if (words_cnt.count(word) == 0 ) { words_cnt[word] = 1; } else { words_cnt[word]++; } } // map加工为字符串输入 std::vector<std::string> map_str; auto it = words_cnt.begin(); while (it != words_cnt.end()) { map_str.push_back("" + (it -> first) + " : " + std::to_string(it -> second)); it++; } std::ostream_iterator<std::string> out_stream(out_file, "\n"); // 分隔符为空格 std::copy(map_str.begin(), map_str.end(), out_stream); // 为什么这里能够间接写out_stream 呢? return 0;}练习3.2#include<iostream>#include<fstream>#include<iterator>#include<vector>#include<set>#include<map>#include<algorithm>bool comp_str(std::string str1, std::string str2) { return str1.size() > str2.size();}int main() { std::ifstream in_file("news.txt"); if (!in_file) { std::cout << "can not read/write file" << std::endl; } // 新建一个vector 而后应用泛型算法copy将元素填充到其中 std::vector<std::string> text; std::string useless_words[3] = {"a", "and", "is"}; std::set<std::string> useless_set(useless_words, useless_words + 3); std::map<std::string, int> words_cnt; // 应用iostream iterator std::istream_iterator<std::string> in_stream(in_file); // 代表in_file的begin std::istream_iterator<std::string> in_eof; // 代表in_file的end std::copy(in_stream, in_eof, std::back_inserter(text)); // 前两个的iterator复制到前面的iterator for (std::string word: text) { if (useless_set.count(word) > 0) { continue; } if (words_cnt.count(word) == 0 ) { words_cnt[word] = 1; } else { words_cnt[word]++; } } // 对text依照字符串长度排序 std::sort(text.begin(), text.end(), comp_str); for (std::string word: text) { std::cout << word << std::endl; } return 0;}练习3.3#include<iostream>#include<vector>#include<string> #include<map>// 这里map的> 和 vector的> 两头要有空格void printFamily(const std::string &first_name, const std::map<std::string, std::vector<std::string>* >* family_map) { if ((*family_map).count(first_name) > 0) { // 这里find拿到的是一个pair, 取 pair -> second 才是 value auto names = family_map -> find(first_name); for (std::string name : *(names -> second)) { std::cout << "first name:" << name << std::endl; } }}int main() { // 这里定义value为一个指向vector的指针, 避免复制 std::map<std::string, std::vector<std::string>* > family_map; std::string family_1 = "Last Name"; std::string last_name_arr[6] = {"last name1", "last name2", "last name3", "last name4", "last name5", "last name6"}; std::vector<std::string> last_name_vec(last_name_arr, last_name_arr + 6); family_map[family_1] = &last_name_vec; printFamily(family_1, &family_map); return 0;}练习3.4#include<iostream>#include<iterator>#include<algorithm>#include<fstream>#include<vector>int main() { // 定义输出流 std::istream_iterator<int> is(std::cin); // 出入eof完结 std::istream_iterator<int> eof; std::vector<int> numbers; std::copy(is, eof, std::back_inserter(numbers)); std::vector<int> odd_numers; std::vector<int> even_numbers; for (int number : numbers) { if (number % 2 == 0) { even_numbers.push_back(number); } else { odd_numers.push_back(number); } } // 分两个流写出文件 // 留神这里是ofstream 不是 ostream ... // 共有三层关系 // 1. 定义一个 out-file-stream , 往哪里写 std::ofstream os_odd("odd_numbers.file"); std::ofstream os_even("even_numbers.file"); // 2. 定义一个 ostream_iterator , 怎么写 std::ostream_iterator<int> os_odd_iter(os_odd, " "); std::ostream_iterator<int> os_even_iter(os_even, " "); // 3. 定义一个 copy, 用什么数据写 std::copy(odd_numers.begin(), odd_numers.end(), os_odd_iter); std::copy(even_numbers.begin(), even_numbers.end(), os_even_iter); return 0;}这一章课后题还是比较简单, 然而我感觉有些货色还是须要花肯定的工夫去了解的, 次要就是指"如何设计一个泛型算法"那里, 一步一步的由一个一般的定制化的函数, 通过引入函数指针, find_if()泛型算法, function object, function object adapter 来设计成为一个元素无关, 比拟操作符无关, 容器类型无关的泛型算法函数, 值得重复浏览。 ...
node 是由 c++ 编写的,外围的 node 模块也都是由 c++ 代码来实现,所以同样 node 也凋谢了让使用者编写 c++ 扩大来实现一些操作的窗口。 如果大家对于 require 函数的形容还有印象的话,就会记得如果不写文件后缀,它是有一个特定的匹配规定的:LOAD_AS_FILE(X)1. If X is a file, load X as its file extension format. STOP2. If X.js is a file, load X.js as JavaScript text. STOP3. If X.json is a file, parse X.json to a JavaScript Object. STOP4. If X.node is a file, load X.node as binary addon. STOP能够看到,最初会匹配一个 .node,而后边的形容也示意该后缀的文件为一个二进制的资源。 而这个 .node 文件个别就会是咱们所编译好的 c++ 扩大了。 ...
前言:最近想把本人写的一个C++我的项目联合所学的Qt,mysql联合起来进行欠缺,后期的一些筹备工作记录如下:如有侵权,请分割删除QT下载链接:我的下载版本为5.9MySql下载链接: 我的下载版本为5.7 MySql的装置配置:1.解压这是我曾经装置好的,红色方框里的文件目前是没有的 2.配置环境变量右击我的电脑-属性-高级零碎设置(进入零碎设置)-点击环境变量先点击Administrator的用户变量-新建,变量名随便,变量值复制门路即可再双击关上Administrator的用户变量中变量一栏的Path,新建一行写上 %MYSQL_HOME%bin,一步步确定就配置好了 3.筹备my.ini文件本人写个.txt文件,再把后缀名改为.ini就行了内容如下: [client]port=3306default-character-set=utf8[mysql] default-character-set=utf8 [mysqld] port=3306 //留神端口号basedir=D:/MYSQL SERVER/mysql-5.7//依照本人的装置门路进行改写datadir=D:/MYSQL SERVER/mysql-5.7/data //依照本人的装置门路进行改写collation-server = utf8_unicode_ciinit-connect='SET NAMES utf8'character-set-server = utf8max_connections=200 default-storage-engine=INNODB sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES 4.快捷键win+R以管理员身份进入命令窗口并将目录切换到MySQL的装置目录的 bin目录 下 5.执行装置语句mysqld -install 留神:呈现Install/Remove of the Service Denied!阐明命令窗口不具备管理员权限 6.进行MySQL的初始化mysqld --initialize-insecure --user=mysql 执行命令后会在MySQL的装置目录下生成data目录并创立root用户。 留神:如果呈现以下状况,阐明配置文件中port=3306的等号左右呈现了空格 7.启动mysql服务net start mysql 8.批改默认账户明码mysqladmin -u root password 123456 9.启动MySQL之后,root用户的明码为空,设置明码mysqladmin -u root -p password 123456 //为了不便,新密码都设置成123456Enter password: //因为因为旧明码为空,所以间接回车即可到此为止,mysql就解压装置结束了,咱们能够命令行输出一些sql语句,以供第三局部测验应用 比方: Qt连贯MySql数据库:1. 要应用Qt SQL的类,须要在我的项目文件(.pro文件)中增加QT += sql2. 查看本人版本Qt中可用的数据库插件:应用QSqlDatabase类中的动态函数drivers()来获取可用的驱动列表,而后遍历输入#include <QApplication>#include <QSqlDatabase>#include <QDebug>#include <QStringList> int main(int argc, char *argv[]){ QApplication a(argc, argv); qDebug()<<"Avaliable drivers;"; QStringList drivers = QSqlDatabase::drivers(); foreach(QString driver,drivers) qDebug()<<driver; return a.exec();}输入: ...
摘要:本文联合作者的工作教训和学习心得,对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,免费送你四个字:自求多福 ...
32W(2020082-20200808)20200806string的compare()办法用来比拟字符串 最好用==0来判断是否相等,之前在网上看了有>0判断不相等,本人在Clion上试了一下,显示后果不错,在cppreference上看了一下,的确有返回-1的状况。where语句中or的状况,必须要加上()。 select count(*) from table t where t.state='I' or t.state='C' AND t.age = 20;select count(*) from table t where (t.state='I' or t.state='C') AND t.age = 20; 加上括号才行。*都是回来再想想,才感觉代码写得不对。
32W(2020082-20200808)20200806string的compare()办法用来比拟字符串 最好用==0来判断是否相等,之前在网上看了有>0判断不相等,本人在Clion上试了一下,显示后果不错,在cppreference上看了一下,的确有返回-1的状况。where语句中or的状况,必须要加上()。 select count(*) from table t where t.state='I' or t.state='C' AND t.age = 20;select count(*) from table t where (t.state='I' or t.state='C') AND t.age = 20; 加上括号才行。*都是回来再想想,才感觉代码写得不对。
32W(2020082-20200808)20200806string的compare()办法用来比拟字符串 最好用==0来判断是否相等,之前在网上看了有>0判断不相等,本人在Clion上试了一下,显示后果不错,在cppreference上看了一下,的确有返回-1的状况。where语句中or的状况,必须要加上()。 select count(*) from table t where t.state='I' or t.state='C' AND t.age = 20;select count(*) from table t where (t.state='I' or t.state='C') AND t.age = 20; 加上括号才行。*都是回来再想想,才感觉代码写得不对。
哈夫曼树https://www.cnblogs.com/smile... 相干概念1、叶子结点的权值(weight)是对叶子结点赋予的一个有意义的数值量。 2、设二叉树有n个带权值的叶子结点,从根节点到各个叶子结点的门路长度与相应叶子结点权值的乘积之和叫做二叉树的带权门路长度。 3、给定一组具备确定权值的叶子结点,能够结构出不同的二叉树,将其中带权门路长度最小的二叉树称为哈夫曼树。 哈夫曼算法根本思维(1) 以权值别离为W1,W2...Wn的n各结点,形成n棵二叉树T1,T2,...Tn并组成森林F={T1,T2,...Tn},其中每棵二叉树 Ti仅有一个权值为 Wi的根结点; (2) 在F中选取两棵根结点权值最小的树作为左右子树结构一棵新二叉树,并且置新二叉树根结点权值为左右子树上根结点的权值之和(根结点的权值=左右孩子权值之和,叶结点的权值= Wi); (3) 从F中删除这两棵二叉树,同时将新二叉树退出到F中; (4) 反复(2)、(3)直到F中只含一棵二叉树为止,这棵二叉树就是Huffman树。 哈夫曼算法的存储构造 思考到对于有n个叶子结点的哈夫曼树有2n-1个结点,并且进行n-1次合并操作,为了便于选取根节点权值最小的二叉树以及合并操作,设置一个数组haftree[2n-1],保留哈夫曼树中的各个结点的信息,数组元素的结点构造如下图所示: weightlchildrchildparent 哈夫曼树的结点构造 其中,weight保留结点权值; lchild保留该节点的左孩子在数组中的下标; rchild保留该节点的右孩子在数组中的下标; parent保留该节点的双亲孩子在数组中的下标。 能够用C++语言中的构造体类型定义上述结点,如下: / 哈夫曼树的结点构造struct element{ int weight; // 权值域 int lchild, rchild, parent; // 该结点的左、右、双亲结点在数组中的下标};哈夫曼编码C++实现为了断定一个结点是否曾经退出哈夫曼树中,可通过parent域的值来确定。初始时parent的值为-1,当某结点退出到树中时,该节点parent域的值为其双亲结点在数组中的下标。 结构哈夫曼树时,首先将n个权值的叶子结点寄存到数组haftree的前n个重量中,而后一直将两棵子树合并为一棵子树,并将新子树的根节点程序寄存到数组haftree的前n个重量的前面。 哈夫曼算法用伪代码形容为: 1、数组haftree初始化,所有数组元素的双亲、左右孩子都置为-1;2、数组haftree的前n个元素的权值置给定权值;3、进行n-1次合并 3.1 在二叉树汇合中选取两个权值最小的根节点,其下标别离为i1,i2; 3.2 将二叉树i1、i2合并为一棵新的二叉树k。Code #include<iostream>#include <iomanip>//这个头文件是申明一些 “流操作符”的//比拟罕用的有:setw(int);//设置显示宽度,left//right//设置左右对齐。 setprecision(int);//设置浮点数的精确度。using namespace std;// 哈夫曼树的结点构造struct element{ int weight; // 权值域 int lchild, rchild, parent; // 该结点的左、右、双亲结点在数组中的下标};// 选取权值最小的两个结点void selectMin(element a[],int n, int &s1, int &s2){ for (int i = 0; i < n; i++) { if (a[i].parent == -1)// 初始化s1,s1的双亲为-1 { s1 = i; break; } } for (int i = 0; i < n; i++)// s1为权值最小的下标 { if (a[i].parent == -1 && a[s1].weight > a[i].weight) s1 = i; } for (int j = 0; j < n; j++) { if (a[j].parent == -1&&j!=s1)// 初始化s2,s2的双亲为-1 { s2 = j; break; } } for (int j = 0; j < n; j++)// s2为另一个权值最小的结点 { if (a[j].parent == -1 && a[s2].weight > a[j].weight&&j != s1) s2 = j; }}// 哈夫曼算法// n个叶子结点的权值保留在数组w中void HuffmanTree(element huftree[], int w[], int n){ for (int i = 0; i < 2*n-1; i++) // 初始化,所有结点均没有双亲和孩子 { huftree[i].parent = -1; huftree[i].lchild = -1; huftree[i].rchild = -1; } for (int i = 0; i < n; i++) // 结构只有根节点的n棵二叉树 { huftree[i].weight = w[i]; } for (int k = n; k < 2 * n - 1; k++) // n-1次合并 { int i1, i2; selectMin(huftree, k, i1, i2); // 查找权值最小的俩个根节点,下标为i1,i2 // 将i1,i2合并,且i1和i2的双亲为k huftree[i1].parent = k; huftree[i2].parent = k; huftree[k].lchild = i1; huftree[k].rchild = i2; huftree[k].weight = huftree[i1].weight + huftree[i2].weight; }} // 打印哈夫曼树void print(element hT[],int n){ cout << "index weight parent lChild rChild" << endl; cout << left; // 左对齐输入 for (int i = 0; i < n; ++i) { cout << setw(5) << i << " "; cout << setw(6) << hT[i].weight << " "; cout << setw(6) << hT[i].parent << " "; cout << setw(6) << hT[i].lchild << " "; cout << setw(6) << hT[i].rchild << endl; }}int main(){ int x[] = { 5,29,7,8,14,23,3,11 }; // 权值汇合 element *hufftree=new element[2*8-1]; // 动态创建数组 HuffmanTree(hufftree, x, 8); print(hufftree,15); system("pause"); return 0;}二叉均衡树(AVL树)AVL简介AVL树种的任意节点的左右子树的高度差的绝对值最大为1,其本质是带了均衡性能的二叉搜寻树。 ...
程序应用动态内存起因: 不晓得本人须要多少对象;不晓得对象精确类型;须要多个对象间共享数据shared_ptrshared_ptr/weak_ptr 的“计数”在支流平台上是原子操作,没有用锁,性能不俗 auto r = make_shared<int>(42);r=q //给r赋值,令他指向另一个地址,递增q指向的对象的援用计数,递加r原来指向对象的援用计数,r原来指向的对象主动开释当一个shared_ptr绑定到一个一般指针时,就将内存治理责任交给了shared_ptr,就不能再用内置指针来拜访shared_ptr指向的内存。shared_ptr能够协调对象的析构,但仅限于其本身的拷贝之间。所以举荐用make_shared而不是new,就能在调配对象的同时与shared_ptr与之绑定,从而防止无心中将同一块内存绑定到了多个独立创立的shared_ptr上 以下调用,将一个长期shared_ptr传递给process。当这个调用表达式完结时,这个长期对象会被销毁,它所指向的内存会被开释 1.int *x(new int(1024);process(x); //不非法,不能int*转换为shared_ptr<int> void process(shared_pt<int> ptr){}process(shared_ptr<int>(x)); //非法,但内存会在Process中被开释int j=*x //未定义,x是个闲暇指针2.shared_ptr<int> p(new int(42));int *q = p.get();{//新程序块shared_ptr<int>(q);} //程序块完结,q被成果,它指向的内存被开释int foo = *p; //未定义reset(p指向的对象援用计数-1)常常与unique一起,管制多个shared_ptr共享的写时复制:if(!p.unique())p.reset(new string(*p));*p += newVal;unique_ptr (领有他指向的对象惟一管理权,ptr销毁对象也销毁)不能拷贝或者赋值unique_ptr,能够release/reset将指针所有权从一个unique_ptr转移给另一个 //release将p1置空,是切断对象的管理权,如果不必的话要本人开释资源,auto to_del = p1.release(); delete to_del;unique_ptr<string> p2(p1.release()); unique_ptr<string> p3(new string("xx"));p2.reset(p3.release()); //reset开释了p2原来指向的内存不能拷贝unique_ptr有个例外:能够拷贝或者赋值一个将要被销毁的Unique_ptr。比方从函数返回一个Unique_ptr weak_ptr。创立时要用shared_ptr来初始化auto p = make_shared<int>(42);weak_ptr<int> wp(p); //wp弱共享p,援用计数未扭转,wp指向的对象可能被开释掉,用wp拜访要用lockdered成员调用check,查看应用vector是否平安以及curr是否在非法范畴内;std::string& StrBlobPtr::deref() const{auto p = check(curr,"dereference past end");return (*P)[curr];}通过援用计数的形式来实现多个shared_ptr对象间的资源共享。 include <iostream>#include <thread>#include <mutex>///using namespace std;templateclass SharedPtr{public:SharedPtr(T* ptr = nullptr): _ptr(ptr), _pCount(new int(1)), _pMutex(new mutex){// 如果是一个空指针对象,援用计数给0if (ptr == nullptr)*pCount = 0;}SharedPtr(SharedPtr<T>& copy) : _ptr(copy._ptr) , _pCount(copy._pCount) , _pMutex(copy._pMutex){ if (_ptr) AddCount();}SharedPtr<T>& operator=(const SharedPtr<T>& copy){ // 避免自赋值 if (_ptr != copy_ptr){ // 开释_ptr旧资源 Release(); // 共享copy对象资源 _ptr = copy._ptr; // 计数减少 _pCount = copy._pCount; _pMutex = copy._pMutex; if (_ptr) AddCount(); } return *this;}T& operator*() { return *_ptr; }T& operator->() { return _ptr; }// 查看以后计数int UseCount() { return *_pCount; }// 获取原始指针T* Get(){ return _ptr; }// 如果有新对象,减少援用计数int AddCount(){ // 为保障多线程下的线程平安,执行锁操作 _pMutex->lock(); ++(*_pCount); _pMutex->unlock(); return *_pCount;}// 如果有对象调用析构,缩小援用计数int SubCount(){ _pMutex->lock(); --(*_pCount); _pMutex->unlock(); return *_pCount;}~SharedPtr(){ Release();}private: // 开释资源 void Release() { // 如果援用计数减为0,则开释资源 if (_ptr && SubCount() == 0){ delete _ptr; delete _pCount; } }private: int* _pCount; // 援用计数 T* _ptr; // 指向治理资源的指针 mutex* _pMutex; // 互斥锁};int main(){ SharedPtr<int> sp1(new int(10)); cout << "Before Add SP2:" << sp1.UseCount() << endl; SharedPtr<int> sp2(sp1); cout << "After Add SP2:" << sp1.UseCount() << endl; return 0;}
锁的意义原子性+可见性同一时间,只有一个线程执行锁中代码 + 锁内读在锁前代码执行完,写在锁开释前可见 原子操作自身内核的原子是通过原子指令实现的https://code.woboq.org/linux/...原子库实现的一下办法能够带内存屏障来增强可见性。 store //原子写load //原子读exchange //原子替换compare_exchange_weak //compare and set 性能更高,然而两个值一样时可能会意外返回false。a.compare_exchange_weak(&expect,val)。if a=expect,则a.store(v), else expect=a,返回false bool compare_exchange_weak (T& expected, T val, memory_order sync = memory_order_seq_cst) volatile noexcept;Compares the contents of the atomic object's contained value with expected:- if true, it replaces the contained value with val (like store).- if false, it replaces expected with the contained value . __asm__ __volatile__("" : : : "memory"); inline void* Acquire_Load() const { void* result = rep_; MemoryBarrier(); return result; } inline void Release_Store(void* v) { MemoryBarrier(); rep_ = v; } compare_exchange_strong ...
数学概念汇合set,是一个无序不反复元素集, 可用于打消反复元素。反对union(并), intersection(交), difference(差)和sysmmetric difference(对称差集)等数学运算。 伊始STL提供了下面这些罕用的数学运算算法,C++程序员应该熟练掌握它们,但它们也只是咱们解决汇合算法的冰山一角,上面咱们对它们开展介绍。 并集 union std::set_union(A.begin(), A.end(), B.begin(), B.end(), std::back_inserter(results));交加 intersection std::set_intersection(A.begin(), A.end(), B.begin(), B.end(), std::back_inserter(results));补集 includes bool UincludesA = std::includes(begin(U), end(U), begin(A), end(A));差集 difference std::set_difference(A.begin(), A.end(), B.begin(), B.end(), std::back_inserter(results));对称差集 sysmmetric difference std::set_symmetric_difference(A.begin(), A.end(), B.begin(), B.end(), std::back_inserter(results));Important须要留神的是,后面所有的算法都要求输出的数据是排序好的。 实际上,这些算法是基于对输出数据曾经排序的事件假如,如果并非如此,则最终的后果都是错的;正是因为这些假如,算法是复杂度是O(n),而不是O(nlogn)
练习2.2和练习2.3头文件#pragma once#include<iostream>#include<vector>void Pentagonal(const std::vector<int> &inputs, int num);inline bool IsValidNum(const int num, const std::vector<int> &v) { if (num <= 0 || num >= 1024 || num <= v.size()) { return false; } return true;}cc文件#include "practice_2_2.h"void Pentagonal(std::vector<int> &inputs, int num) { // 先判断 num 是否非法 if (!IsValidNum(num, inputs)) { return; } for (int i = inputs.size() + 1; i < num + 1;i++) { int value = i * (3 * i - 1) / 2; inputs.push_back(value); }}void PrintElements(const std::vector<int> * inputs) { for (int ele : *inputs) { std::cout << ele << std::endl; }}int main() { std::vector<int> v; Pentagonal(v, 10); Pentagonal(v, 20); PrintElements(&v); // return 0 示意程序胜利运行 return 0;}练习2.4头文件#pragma once#include<iostream>#include<vector>const std::vector<int> * GetPentagonal(int num);int GetValueFromIndex(int index, const std::vector<int> * v);cc文件#include "practice_2_4.h"#include<string>/* 我一开始的上面这种上面是谬误的, 起因是仅仅是申明了一个 指向vector<int>的指针, 然而这个指针并没有指向任何的 对象, 所以间接调用 -> size()函数的话必定是会报错的*/// const std::vector<int> * GetPentagonal(int num) {// // 部分动态对象指针// static std::vector<int> * v;// if (num <= 0 || num > 1024) {// return v;// }// for (int i = v -> size() + 1; i < num + 1; i++) {// std::cout << "point" << i << std::endl;// v -> push_back(i * (3 * i - 1) / 2);// }// return v;// }const std::vector<int> * GetPentagonal(int num) { // 部分动态对象 间接初始化一个对象 static std::vector<int> v; if (num <= 0 || num > 1024) { return &v; } for (int i = v.size() + 1; i < num + 1; i++) { v.push_back(i * (3 * i - 1) / 2); } return &v;}int GetValueFromIndex(int index, const std::vector<int> * v) { // 判断是否为空指针 if (v == 0) { return -1; std::cout << "Error : v is nullptr" << std::endl; } // 判断 index 是否超出 if (index >= v -> size()) { return -1; std::cout << "Error : index too large!" << std::endl; } return (*v)[index];}int main() { const std::vector<int> *v = GetPentagonal(10); // 判断是否为空指针 if (v == 0) { std::cout << "Error: v is nullptr" << std::endl; } std::cout << GetValueFromIndex(5, v) << std::endl; // 确认下空指针是否为0 // 留神 指针能够和0比 一般来说对象不能和0比 要申明为指针 std::string* check = nullptr; if (check == 0) { std::cout << "the value of nullptr is 0" << std::endl; } return 0;}练习2.5/2.6头文件#pragma once#include<iostream>#include<vector>int max(int a, int b);float max(float a, float b);std::string max(const std::string &a, const std::string &b);int max(const std::vector<int> &v);float max(const std::vector<float> &v);std::string max(const std::vector<std::string> &v);int max(const int *arr[]); // 数组只能申明为指针?template <typename T> T TemplateMax(T a, T b);cc文件#include "practice_2_5.h"#include<string.h>int max(int a, int b) { return (a > b) ? a : b;}float max(float a, float b) { return (a > b) ? a : b;}std::string max(const std::string &a, const std::string &b) { std::cout << "a:" << a << "," << "b:" << b << std::endl; bool bigger = a.compare(b); return bigger ? a : b;}template <typename T>T TemplateMax(T a, T b) { return (a > b) ? a : b;}int main() { std::cout << max(1,2) << std::endl; std::cout << max(1.2f, 2.4f) << std::endl; std::cout << max("abc", "abcd") << std::endl; std::cout << max(110, 112) << std::endl; return 0;}补充练习2.5中的字符串比拟, 应用 > 操作符 和 a.compare(b) 都无奈正确返回后果, 留一个问题在这, 心愿好心人能予以解答。https://segmentfault.com/q/10...先送外卖去了... ...
练习2.2和练习2.3头文件#pragma once#include<iostream>#include<vector>void Pentagonal(const std::vector<int> &inputs, int num);inline bool IsValidNum(const int num, const std::vector<int> &v) { if (num <= 0 || num >= 1024 || num <= v.size()) { return false; } return true;}cc文件#include "practice_2_2.h"void Pentagonal(std::vector<int> &inputs, int num) { // 先判断 num 是否非法 if (!IsValidNum(num, inputs)) { return; } for (int i = inputs.size() + 1; i < num + 1;i++) { int value = i * (3 * i - 1) / 2; inputs.push_back(value); }}void PrintElements(const std::vector<int> * inputs) { for (int ele : *inputs) { std::cout << ele << std::endl; }}int main() { std::vector<int> v; Pentagonal(v, 10); Pentagonal(v, 20); PrintElements(&v); // return 0 示意程序胜利运行 return 0;}练习2.4头文件#pragma once#include<iostream>#include<vector>const std::vector<int> * GetPentagonal(int num);int GetValueFromIndex(int index, const std::vector<int> * v);cc文件#include "practice_2_4.h"#include<string>/* 我一开始的上面这种上面是谬误的, 起因是仅仅是申明了一个 指向vector<int>的指针, 然而这个指针并没有指向任何的 对象, 所以间接调用 -> size()函数的话必定是会报错的*/// const std::vector<int> * GetPentagonal(int num) {// // 部分动态对象指针// static std::vector<int> * v;// if (num <= 0 || num > 1024) {// return v;// }// for (int i = v -> size() + 1; i < num + 1; i++) {// std::cout << "point" << i << std::endl;// v -> push_back(i * (3 * i - 1) / 2);// }// return v;// }const std::vector<int> * GetPentagonal(int num) { // 部分动态对象 间接初始化一个对象 static std::vector<int> v; if (num <= 0 || num > 1024) { return &v; } for (int i = v.size() + 1; i < num + 1; i++) { v.push_back(i * (3 * i - 1) / 2); } return &v;}int GetValueFromIndex(int index, const std::vector<int> * v) { // 判断是否为空指针 if (v == 0) { return -1; std::cout << "Error : v is nullptr" << std::endl; } // 判断 index 是否超出 if (index >= v -> size()) { return -1; std::cout << "Error : index too large!" << std::endl; } return (*v)[index];}int main() { const std::vector<int> *v = GetPentagonal(10); // 判断是否为空指针 if (v == 0) { std::cout << "Error: v is nullptr" << std::endl; } std::cout << GetValueFromIndex(5, v) << std::endl; // 确认下空指针是否为0 // 留神 指针能够和0比 一般来说对象不能和0比 要申明为指针 std::string* check = nullptr; if (check == 0) { std::cout << "the value of nullptr is 0" << std::endl; } return 0;}练习2.5/2.6头文件#pragma once#include<iostream>#include<vector>int max(int a, int b);float max(float a, float b);std::string max(const std::string &a, const std::string &b);int max(const std::vector<int> &v);float max(const std::vector<float> &v);std::string max(const std::vector<std::string> &v);int max(const int *arr[]); // 数组只能申明为指针?template <typename T> T TemplateMax(T a, T b);cc文件#include "practice_2_5.h"#include<string.h>int max(int a, int b) { return (a > b) ? a : b;}float max(float a, float b) { return (a > b) ? a : b;}std::string max(const std::string &a, const std::string &b) { std::cout << "a:" << a << "," << "b:" << b << std::endl; bool bigger = a.compare(b); return bigger ? a : b;}template <typename T>T TemplateMax(T a, T b) { return (a > b) ? a : b;}int main() { std::cout << max(1,2) << std::endl; std::cout << max(1.2f, 2.4f) << std::endl; std::cout << max("abc", "abcd") << std::endl; std::cout << max(110, 112) << std::endl; return 0;}补充练习2.5中的字符串比拟, 应用 > 操作符 和 a.compare(b) 都无奈正确返回后果, 留一个问题在这, 心愿好心人能予以解答。https://segmentfault.com/q/10...先送外卖去了... ...
程序喵之前曾经介绍过C++11的新个性和C++14的新个性,链接如下:xxx,明天向敬爱的读者们介绍下C++17的新个性,当初基本上各个编译器对C++17都曾经提供齐备的反对,倡议大家编程中尝试应用下C++17,能够肯定水平上简化代码编写,进步编程效率。 次要新个性如下: 构造函数模板推导结构化绑定if-switch语句初始化内联变量折叠表达式constexpr lambda表达式namespace嵌套__has_include预处理表达式在lambda表达式用*this捕捉对象正本新增Attribute字符串转换std::variantstd::optionalstd::anystd::applystd::make_from_tupleas_conststd::string_viewfile_systemstd::shared_mutex上面程序喵一一介绍: 构造函数模板推导在C++17前结构一个模板类对象须要指明类型: pair<int, double> p(1, 2.2); // before c++17C++17就不须要非凡指定,间接能够推导出类型,代码如下: pair p(1, 2.2); // c++17 主动推导vector v = {1, 2, 3}; // c++17结构化绑定通过结构化绑定,对于tuple、map等类型,获取相应值会不便很多,看代码: std::tuple<int, double> func() { return std::tuple(1, 2.2);}int main() { auto[i, d] = func(); //是C++11的tie吗?更高级 cout << i << endl; cout << d << endl;}//==========================void f() { map<int, string> m = { {0, "a"}, {1, "b"}, }; for (const auto &[i, s] : m) { cout << i << " " << s << endl; }}// ====================int main() { std::pair a(1, 2.3f); auto[i, f] = a; cout << i << endl; // 1 cout << f << endl; // 2.3f return 0;}结构化绑定还能够扭转对象的值,应用援用即可: ...
项目名称:EditUltra 我的项目作者:calvinwilliams 开源许可协定:Apache-2.0 我的项目地址:https://gitee.com/calvinwilliams/EditUltra 我的项目概述EditUltra 是中国人开发开源的对标 UltraEdit 的玲珑疾速又功能丰富的文本/源码编辑器(内嵌数据库客户端、Redis客户端),她基于开源的富文本编辑控件Scintilla提供的根本文本编辑能力,实现了一个残缺的文本/源码编辑器,还间接执行SQL和Redis命令性能,整个软件只有10MB(不蕴含Oracle/MySQL客户端库)。 EditUltra次要性能蕴含但不限于: 多文件选项卡WINDOWS资源管理器右键菜单关上文件/目录中所有文件检测文件变动实时重载文件关上期间主动设置为只读文件换行符和字符编码转换近程文件间接关上和保留高级的切剪、复制和粘贴高级的挪动、累积抉择BASE64编解码、散列摘要计算和加解密书签导航白字符显示编程语言语法高亮、主动实现和办法浮动提醒,语句块折叠开展连贯Oracle/MySQL,获取全表字段列表,执行SQL,显示查问后果到表格连贯Redis,执行命令,显示查问后果到树配置执行对文件、抉择文本的命令EditUltra齐全应用C用纯WIN32API编写而成(v1.0.0.3大概有近2万行代码),又退出了泛滥优化算法和数据结构,提供了极高的运行效率和较低的系统资源占用,秒开文件,键入时毫秒级弹出主动实现列表。 运行程序目前该我的项目仅反对 Windows 平台下的装置和应用。 下载后间接运行外面的editultra.exe即可启动。 留神: 如需内嵌Oracle客户端性能,还要自行装置Oracle,并将WINDOWS环境变量PATH中退出oci.dll的所在门路。如需内嵌MySQL客户端性能,还要自行装置MySQL,并将WINDOWS环境变量PATH中退出libmysql.dll的所在门路。注册WINDOWS右键文件/目录弹出菜单项如果须要在WINDOWS资源管理器中右键菜单减少“关上文件”性能,执行菜单环境->文件右键弹出菜单。 注册胜利后就能在WINDOWS里右键关上文件了。 如果须要在WINDOWS资源管理器中右键菜单减少“关上目录中所有文件”性能,执行菜单环境->目录右键弹出菜单。 WINDOWS10操作系统可能会遇到“没有管理员权限”报错,解决方案是右键“以管理员身份运行”editultra.exe,再执行以上菜单即可。 该项目标更多装置形式与性能介绍,作者曾经写在了我的项目仓库中,如果你想去下载该我的项目或者去看看它的源代码,那么就点击前面的链接去 Gitee 看看吧:https://gitee.com/calvinwilliams/EditUltra
后面程序喵介绍过C++11的新个性,在这里(),这篇文章介绍下C++14的新个性。 函数返回值类型推导C++14对函数返回类型推导规定做了优化,先看一段代码: #include <iostream>using namespace std;auto func(int i) { return i;}int main() { cout << func(4) << endl; return 0;}应用C++11编译: ~/test$ g++ test.cc -std=c++11test.cc:5:16: error: ‘func’ function uses ‘auto’ type specifier without trailing return type auto func(int i) { ^test.cc:5:16: note: deduced return type only available with -std=c++14 or -std=gnu++14下面的代码应用C++11是不能通过编译的,通过编译器输入的信息也能够看见这个个性须要到C++14才被反对。 返回值类型推导也能够用在模板中: #include <iostream>using namespace std;template<typename T> auto func(T t) { return t; }int main() { cout << func(4) << endl; cout << func(3.4) << endl; return 0;}留神: ...
后面程序喵介绍过C++11的新个性,在这里(),这篇文章介绍下C++14的新个性。 函数返回值类型推导C++14对函数返回类型推导规定做了优化,先看一段代码: #include <iostream>using namespace std;auto func(int i) { return i;}int main() { cout << func(4) << endl; return 0;}应用C++11编译: ~/test$ g++ test.cc -std=c++11test.cc:5:16: error: ‘func’ function uses ‘auto’ type specifier without trailing return type auto func(int i) { ^test.cc:5:16: note: deduced return type only available with -std=c++14 or -std=gnu++14下面的代码应用C++11是不能通过编译的,通过编译器输入的信息也能够看见这个个性须要到C++14才被反对。 返回值类型推导也能够用在模板中: #include <iostream>using namespace std;template<typename T> auto func(T t) { return t; }int main() { cout << func(4) << endl; cout << func(3.4) << endl; return 0;}留神: ...
前言 在一次Mysql分享中提到过,会将相干的一些知识点整顿成相应的文章。因为前段时间忙的不可开交,始终没有工夫去整顿这些相干内容。然而必然说进去的话,就要去落实。本章内容次要以实际为主,最好是跟着入手实际。这样能力逐渐把握其中神秘。那么咱们开始吧!!! 1.装置数据库 在做这个实际之前,咱们要先装置一下mysql数据库,这边是通过源码的模式进行装置。不便后续的调试跟踪。 1.1通过git下载mysql源码:#cd /Users/edz/Desktop/src-source/mysql-server/#git clone https://github.com/mysql/mysql-server.git#cd mysql-server#git checkout 5.6.48 以后应用版本为5.6.48,所以咱们切到5.6.48版本。 1.2编译mysql源码:#cd /Users/edz/Desktop/src-source/mysql-server/BUILD#cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local/mysql5.6.48 -DMYSQL_DATADIR=/usr/local/mysql5.6.48/data -DSYSCONFDIR=/usr/local/mysql5.6.48/etc -DWITH_MYISAM_STORAGE_ENGINE=1 -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_MEMORY_STORAGE_ENGINE=1 -DWITH_READLINE=1 -DMYSQL_UNIX_ADDR=/tmp/mysqld.sock -DMYSQL_TCP_PORT=3306 -DENABLED_LOCAL_INFILE=1 -DWITH_PARTITION_STORAGE_ENGINE=1 -DEXTRA_CHARSETS=all -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DWITH_DEBUG=1 -DWITH_UNIT_TESTS=off#make#make install 没有指定mysql装置目录时,在mac零碎下默认会装置在/usr/local/mysql目录中。 implicit instantiation of undefined template 'std::basic_stringstream 谬误解决: #include <sstream> //间接引入头即可1.3编译my.cnf内容[mysqld]datadir=/usr/local/mysql5.6.48/datasocket=/usr/local/mysql5.6.48/data/mysql.sockexplicit_defaults_for_timestamp=truelower_case_table_names=1symbolic-links=0[mysqld_safe]log-error=/data/logs/mariadb.logpid-file=/data/mysql/mariadb.pidmy.conf内容我存在/usr/local/mysql/my.cnf 1.4初始化数据库#cd /usr/local/mysql5.6.48/#./scripts/mysql_install_db --defaults-file=/usr/local/mysql5.6.48/etc/my.cnf --user=root --basedir=/usr/local/mysql初始化数据库过程次要是初始化一些根本数据过程。 1.5 启动数据库 #/usr/local/mysql5.6.48/bin/mysqld --defaults-file=/usr/local/mysql5.6.48/etc/my.cnf --user=root &须要启动数据库服务,能够应用命令“ps aux |grep mysqld”,看一下是否启动胜利。 1.6 连贯数据库#/usr/local/mysql5.6.48/bin/mysql --socket=/usr/local/mysql5.6.48/data/mysql.sock -u root -h 127.0.0.1 --port 3306 -p输出本人数据库的明码即可,连贯到终端。5.6默认状况是没有设置的,mysql5.7会默认设置。 ...
前言 在一次Mysql分享中提到过,会将相干的一些知识点整顿成相应的文章。因为前段时间忙的不可开交,始终没有工夫去整顿这些相干内容。然而必然说进去的话,就要去落实。本章内容次要以实际为主,最好是跟着入手实际。这样能力逐渐把握其中神秘。那么咱们开始吧!!! 1.装置数据库 在做这个实际之前,咱们要先装置一下mysql数据库,这边是通过源码的模式进行装置。不便后续的调试跟踪。 1.1通过git下载mysql源码:#cd /Users/edz/Desktop/src-source/mysql-server/#git clone https://github.com/mysql/mysql-server.git#cd mysql-server#git checkout 5.6.48 以后应用版本为5.6.48,所以咱们切到5.6.48版本。 1.2编译mysql源码:#cd /Users/edz/Desktop/src-source/mysql-server/BUILD#cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local/mysql5.6.48 -DMYSQL_DATADIR=/usr/local/mysql5.6.48/data -DSYSCONFDIR=/usr/local/mysql5.6.48/etc -DWITH_MYISAM_STORAGE_ENGINE=1 -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_MEMORY_STORAGE_ENGINE=1 -DWITH_READLINE=1 -DMYSQL_UNIX_ADDR=/tmp/mysqld.sock -DMYSQL_TCP_PORT=3306 -DENABLED_LOCAL_INFILE=1 -DWITH_PARTITION_STORAGE_ENGINE=1 -DEXTRA_CHARSETS=all -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DWITH_DEBUG=1 -DWITH_UNIT_TESTS=off#make#make install 没有指定mysql装置目录时,在mac零碎下默认会装置在/usr/local/mysql目录中。 implicit instantiation of undefined template 'std::basic_stringstream 谬误解决: #include <sstream> //间接引入头即可1.3编译my.cnf内容[mysqld]datadir=/usr/local/mysql5.6.48/datasocket=/usr/local/mysql5.6.48/data/mysql.sockexplicit_defaults_for_timestamp=truelower_case_table_names=1symbolic-links=0[mysqld_safe]log-error=/data/logs/mariadb.logpid-file=/data/mysql/mariadb.pidmy.conf内容我存在/usr/local/mysql/my.cnf 1.4初始化数据库#cd /usr/local/mysql5.6.48/#./scripts/mysql_install_db --defaults-file=/usr/local/mysql5.6.48/etc/my.cnf --user=root --basedir=/usr/local/mysql初始化数据库过程次要是初始化一些根本数据过程。 1.5 启动数据库 #/usr/local/mysql5.6.48/bin/mysqld --defaults-file=/usr/local/mysql5.6.48/etc/my.cnf --user=root &须要启动数据库服务,能够应用命令“ps aux |grep mysqld”,看一下是否启动胜利。 1.6 连贯数据库#/usr/local/mysql5.6.48/bin/mysql --socket=/usr/local/mysql5.6.48/data/mysql.sock -u root -h 127.0.0.1 --port 3306 -p输出本人数据库的明码即可,连贯到终端。5.6默认状况是没有设置的,mysql5.7会默认设置。 ...
这个版本重点对其余语言的反对做了一些改良,比方新增了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 Examples Zig语言实验性反对注:目前这个语言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 Examples Go依赖包和穿插编译反对新版本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 Examples ...
明天(2020年7月28日)WonderTrader公布了最新版本v0.5.0,同步公布的还有wtpy的最新版本v0.5.0 WonderTrader在v0.5.0中做了如下批改:高频策略引擎正式公布。高频策略引擎之前在WonderTrader中曾经做了实现,然而因为测试不够充沛,临时并没有正式公布,只是以CTA策略组合为主,随着v0.4.0公布的选股引擎,v0.5.0也正式公布了高频引擎,从而实现了WonderTrader对不同策略利用场景的全笼罩。 CTA引擎,实用于多策略组合。CTA引擎采纳同步事件驱动模式,这样就要求策略计算工夫不能太长。对于期货来说,依照500ms一笔快照的速度,个别Python中CTA策略在主K线闭合时触发重算,单次计算工夫为个别在10个毫秒以内,也就是说同一个策略组合中,在两个tick距离的工夫内,除去引擎自身的工夫开销来说,能反对40个以上的策略同时运行。而一般来说全副国内期货的主力合约,也就是这个数目。CTA引擎同步调度的目标是要整合策略组合的指标仓位为净头寸,而后发送给执行模块提交指令,从而从根本上杜绝自成交等问题,而且节俭佣金和保证金,进步资金的利用效率。(回测引擎能够测算出每个策略大略每次计算的均匀耗时,以Demo中的DualThrust为例,个别状况下耗时4-6个毫秒)CTA引擎同时提供tick级响应接口,次要针对策略自身的风控需要。SEL引擎,实用于选股零碎或者简单算法策略。SEL引擎采纳异步工夫驱动模式,只有在两次重算调度之间,策略运算实现即可。SEL引擎因为是异步的起因,所以信号无奈整合到同一时间收回(强行整合到同一时间执行可能会导致交易机会的失落,从而造成策略意料之外的危险)。SEL引擎的劣势是:反对日级别以上的任务调度(次要针对选股的需要,个别选股策略轮换工夫都比拟长),同时也反对分钟线级别的任务调度(次要针对预设股池的盘中筛选),日线级别的任务调度不限定工夫,是否交易工夫都能够执行,分钟线级别的任务调度限定在交易工夫内。比拟典型的用法就是盘后日线级别选股失去根本股池,而后在盘中针对该根本股池设置分钟线级别的选股。HFT引擎,实用于高频策略。HFT引擎也采纳同步事件驱动模式。HFT引擎和CTA引擎不同的点在于,CTA引擎要外接执行模块,而HFT引擎的策略自带执行。简略来说,就是HFT引擎容许策略间接操作交易接口。HFT引擎对策略要求较高,须要策略自行治理订单,解决超时撤销等操作。然而HFT引擎还是向策略提供了一系列不便的接口,例如:HFT引擎对策略只提供买入、卖出和撤单三种下单指令,策略逻辑中,不须要刻意去关怀是买入开仓还是买入平仓,这些都会在交易接口中主动解决掉(交易接口读取开平策略配置文件actionpolicy.json,通过配置文件中,针对不同的种类配置开仓、平仓、平今的优先级和边界参数)。减少股票数据复权因子解决逻辑。在应用WonderTrader进行股票回测或者交易的时候,WonderTrader会主动读取未复权的数据,以及复权因子,而后进行前复权解决。交易模块减少交易数据落地。次要实时落地的数据包含:成交明细、订单明细、持仓数据和资金数据。其余代码细节调整wtpy在v0.5.0中做了如下批改:同步底层为最新版本v0.5.0因为高频策略开发的须要,从新调整了C++底层的目录构造(次要是行情接入接口,之前是数据组件独自应用,当初HFT策略也可能间接应用)新增了高频策略对应的HftContext模块,以及BaseHftStrategy模块。下一阶段的打算到目前为止,WonderTrader根本曾经做到了利用场景全笼罩,所以当前的开发计划,将会从大性能实现为主,变成 wtpy会内置一个基于flask的伺服引擎,次要用于近程监控wtpy会内置一套web-ui,作为flask伺服的资源,供用户近程查看优化底层接口细节,更不便python等应用层调用测试欠缺底层外围模块
对于Queuetemplate <typename T, typename Allocator = AlignedAllocator<Slot<T>>>class Queue {private:static_assert(std::is_nothrow_copy_assignable<T>::value || std::is_nothrow_move_assignable<T>::value, "T must be nothrow copy or move assignable");static_assert(std::is_nothrow_destructible<T>::value, "T must be nothrow destructible");第一个模板参数是队列存储的对象类型,第二个模板参数为内存分配器,默认应用AlignedAllocator,即上文定义的内存分配器。 要求T类型的拷贝赋值,挪动赋值函数和析构函数都要是noexcept的。 public:explicit Queue(const size_t capacity, const Allocator &allocator = Allocator()) : capacity_(capacity), allocator_(allocator), head_(0), tail_(0) { if (capacity_ < 1) { throw std::invalid_argument("capacity < 1"); } // Allocate one extra slot to prevent false sharing on the last slot slots_ = allocator_.allocate(capacity_ + 1); // Allocators are not required to honor alignment for over-aligned types (see http://eel.is/c++draft/allocator.requirements#10) so we verify alignment here if (reinterpret_cast<size_t>(slots_) % alignof(Slot<T>) != 0) { allocator_.deallocate(slots_, capacity_ + 1); throw std::bad_alloc(); } for (size_t i = 0; i < capacity_; ++i) { new (&slots_[i]) Slot<T>(); } static_assert( alignof(Slot<T>) == hardwareInterferenceSize, "Slot must be aligned to cache line boundary to prevent false sharing"); static_assert(sizeof(Slot<T>) % hardwareInterferenceSize == 0, "Slot size must be a multiple of cache line size to prevent false sharing between adjacent slots"); static_assert(sizeof(Queue) % hardwareInterferenceSize == 0, "Queue size must be a multiple of cache line size to prevent false sharing between adjacent queues"); static_assert( offsetof(Queue, tail_) - offsetof(Queue, head_) == static_cast<std::ptrdiff_t>(hardwareInterferenceSize), "head and tail must be a cache line apart to prevent false sharing");}下面代码是Queue的构造函数,为什么要多申请一个Slot防止最初一个Slot的伪共享(不懂)?而后查看了调配的内存起始地址是不是以Slot<T>的对齐数对齐,因为分配器并不被要求对over-aligned(适度对齐)的类型进行对齐,如果不满足对齐要求,则防止伪共享的要求达不到,开释内存抛出异样。接着在capacity_个内存块上结构Slot<T>对象,最初进行一系列的动态断言,确保各个对象的内存散布与大小合乎设计要求。 ...
String Transformation 1(Codeforces 659 Div.2 C)Codeforces的Div.2真是越来越难了QwQ 挂个链接先 - https://codeforces.com/contes/1384/problem/c 题面 - 一共若干组数据,每组数据蕴含A,B两个长度为N的由a~t组成的字符串,你能够抉择A串中任意几个雷同的字母减少它的ACSLL码,问起码多少次操作能够使其变为B串,无奈变成B串则输入-1 Now,开始解题必定先瞧下数据,10*100000级别,那必定贪婪、DP(动静布局)、图论建模三选一 奢侈图论建模首当其冲的在工夫复杂度上挂掉了(官网题解是图论建模+贪婪+并查集(求连通块个数),我也写了相干文章) 再说DP,无显著档次,可能会有后效性,也就在图论之后也挂掉了 那就只有贪婪了 先想到的是串转数后排序,再模拟题中的操作,这样就疏忽了地位,起初在笔者受同学的一种办法启发,想出了一种正确的贪婪并AC了这道题,上面将介绍这种贪婪办法 算法工夫复杂度 - O(Q*N) 核心思想 - 建表以体现状态的递进 首先,如何建表?建什么表?笔者建的表是一个二维数组,体现的是同一下标的A与B的字母对应关系(实际上数量对后果没有影响,想想为什么)a[i][j]寄存的就是有多少个A中的('a'+i-1)对应B中的(a+j-1)比方a[1][2]=3示意有3个A中的'a'对应B中的'b' 举个残缺的栗子 开始贪婪的操作按列扫描,对于每列,将这一整列移到与该该列第一个非零元素的下标雷同的列还没懂的能够联合下相干程序 for(int k=j+1; k<=26; k++){ if(a[k][j]>0){ //如果该元素非零 if(f==0){ //如果之前未有过非零元素 f=1; //标记 temp=k-j; //算出之后的元素要挪动的量 } else{ //否则之前有过 a[k][j+temp]++; //挪动 } 贪婪这就完结了?!没错,真的完结了...... 这个贪婪看似漏洞百出,甚至笔者本人一开始都有所狐疑这实际上无懈可击,没有任何谬误在工夫复杂度上甚至优于官网给出的题解 最初照例奉上残缺程序(C++)和精心设计的注解 //CF #659 Div.2 C - String Transformation 1 //https://codeforces.ml/contest/1384/problem/c//贪婪 #include <bits/stdc++.h>using namespace std;int main(){ ios::sync_with_stdio(false); cin.tie(0); //freopen(".in","r",stdin); //freopen(".out","w",stdout); int T; cin >> T; int a[21][21]; for(int i=1; i<=T; i++){ int N, ans=0; cin >> N; for(int j=1; j<=26; j++) for(int k=1; k<=26; k++) a[k][j]=0; string A, B; cin >> A >> B; int f=0; for(int j=0; j<N; j++){ if(A[j]>B[j]){ //如果A内有元素大于B内所对应的 cout << -1 << endl; //输入-1 f=1; //标记 break; } if(A[j]<B[j]) a[B[j]-'a'+1][A[j]-'a'+1]++; //否则填表 } if(f==1) continue; int temp; for(int j=1; j<=26; j++){ f=0; for(int k=j+1; k<=26; k++){ if(a[k][j]>0){ //如果该元素非零 if(f==0){ //如果之前未有过非零元素 f=1; //标记 temp=k-j; //算出之后的元素要挪动的量 } else{ //否则之前有过 a[k][j+temp]++; //挪动 } } } if(f==1) //如果执行过操作 ans++; //操作总数++ } cout << ans << endl; } return 0;}
示例: char insert_sql[128] = {0};for (int i=0; i<10000; ++i){ sprintf(insert_sql, "insert into bindtest values(%d, %d)", i, i*100); ret = sqlite3_exec(ppdb, insert_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("insert fail\n"); break; } sqlite3_free(errmsg);}SQLite执行在一个事务中的每条语句,反对读事务和写事务。应用程序只能是在读或写事务中能力从数据库中读数据。应用程序只能在写事务中能力向数据库中写数据。应用程序不须要明确通知SQLite去执行事务中的单个SQL语句,SQLite时主动这样做的,这是默认行为,这样的零碎叫做主动提交模式。这些事务被叫做主动事务,或零碎级事务。对于一个select语句,SQLite建设一个读事务,对于一个非select语句,SQLite先建设一个读事务,再把它转换成写事务。每个事务都在语句执行完结时被提交或被终止。应用程序不晓得有零碎事务,应用程序只是把SQL语句提交给SQLite,由SQLite再去解决对于ACID的属性,应用程序接管从SQLite执行SQL返回的后果。一个应用程序能够在雷同的数据库连贯上引发执行select语句(读事务)的并行执行,然而只能在一个闲暇连贯上引起一个非select语句(写事务)的执行。主动提交模式可能对某些应用程序来讲代价过高,尤其是那些写密集的应用程序,因为对于每一个非select语句,SQLite须要反复关上,写入,敞开日志文件。在主动提交模式中,在每条语句执行的最初,SQLite抛弃页缓冲。每条语句执行都会从新结构缓冲区,从新结构缓冲区是破费大,低效的口头,因为会调用磁盘I/O。并且,存在并发管制的开销,因为对每一句SQL语句的执行,应用程序须要从新获取和开释对数据库文件的锁。这种开销会使性能显著降落(尤其是大型应用程序),并只能以关上一个包含了很多SQL语句的用户级别的事务来加重这种状况(如:关上多个数据库)。应用程序能够用begin命令手动的开始一个新的事务,这种事务被称为用户级事务(或用户事务)。当begin执行后,SQLite来到默认的主动提交模式,在语句完结时不会调用commit或abort。也不会抛弃该页的缓冲。间断的SQL语句是整个事物的一部分,当应用程序执行commit/respectively/rollback指令时,SQLite提交或别离或停止事务。如果当事务停止或失败,或应用程序敞开连贯,则整个事务回滚。SQLite在事务实现时复原到主动提交模式上来。begin开始事务commit保留更改rollback回滚所作的更改事务回滚sqlite> select * from stu;0|小黑1|小白sqlite> update stu set name='人类';sqlite> select * from stu;0|人类1|人类sqlite> rollback;sqlite> select * from stu;0|小黑1|小白事务提交sqlite> select * from stu;0|小黑1|小白sqlite> begin;sqlite> select * from stu;0|小黑1|小白sqlite> update stu set name='人类';sqlite> select * from stu;0|人类1|人类sqlite> commit;sqlite> select * from stu;0|人类1|人类sqlite> rollback;Error: cannot rollback - no transaction is active编程试验:事务对插入效率的影响#include <stdio.h>#include <sqlite3.h>#include <time.h>int main(){ // 1. 关上数据库 sqlite3 *ppdb = NULL; int ret = sqlite3_open("test.db", &ppdb); if (ret != SQLITE_OK) { printf("open fail\n"); return -1; } // 2. 执行 sql 语句 // 2.1 创建表格 create table apitest(id int, number int); const char *create_sql = "create table if not exists testTransaction(id int, number int)"; char *errmsg = NULL; ret = sqlite3_exec(ppdb, create_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("%s\n", errmsg); } sqlite3_free(errmsg); // 2.2 数据直接插入 ============================== struct timeval start; struct timeval end; char insert_sql[128] = {0}; mingw_gettimeofday(&start, NULL); for (int i=0; i<1000; ++i) { sprintf(insert_sql, "insert into testTransaction values(%d, %d)", i, i*100); ret = sqlite3_exec(ppdb, insert_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("insert fail\n"); break; } sqlite3_free(errmsg); } mingw_gettimeofday(&end, NULL); printf("insert directly: %ld s\n", end.tv_sec - start.tv_sec); // 2.3 数据直接插入-事务 ============================== mingw_gettimeofday(&start, NULL); sqlite3_exec(ppdb, "begin", NULL, NULL, NULL); for (int i=0; i<1000; ++i) { sprintf(insert_sql, "insert into testTransaction values(%d, %d)", i, i*100); ret = sqlite3_exec(ppdb, insert_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("insert fail\n"); break; } sqlite3_free(errmsg); } sqlite3_exec(ppdb, "commit", NULL, NULL, NULL); mingw_gettimeofday(&end, NULL); printf("insert directly(transaction): %ld us\n", end.tv_usec - start.tv_usec); // 3. 敞开数据库 ret = sqlite3_close(ppdb); if (ret != SQLITE_OK) { printf("close fail\n"); return -1; } return 0;}输入: ...
示例: char insert_sql[128] = {0};for (int i=0; i<10000; ++i){ sprintf(insert_sql, "insert into bindtest values(%d, %d)", i, i*100); ret = sqlite3_exec(ppdb, insert_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("insert fail\n"); break; } sqlite3_free(errmsg);}SQLite执行在一个事务中的每条语句,反对读事务和写事务。应用程序只能是在读或写事务中能力从数据库中读数据。应用程序只能在写事务中能力向数据库中写数据。应用程序不须要明确通知SQLite去执行事务中的单个SQL语句,SQLite时主动这样做的,这是默认行为,这样的零碎叫做主动提交模式。这些事务被叫做主动事务,或零碎级事务。对于一个select语句,SQLite建设一个读事务,对于一个非select语句,SQLite先建设一个读事务,再把它转换成写事务。每个事务都在语句执行完结时被提交或被终止。应用程序不晓得有零碎事务,应用程序只是把SQL语句提交给SQLite,由SQLite再去解决对于ACID的属性,应用程序接管从SQLite执行SQL返回的后果。一个应用程序能够在雷同的数据库连贯上引发执行select语句(读事务)的并行执行,然而只能在一个闲暇连贯上引起一个非select语句(写事务)的执行。主动提交模式可能对某些应用程序来讲代价过高,尤其是那些写密集的应用程序,因为对于每一个非select语句,SQLite须要反复关上,写入,敞开日志文件。在主动提交模式中,在每条语句执行的最初,SQLite抛弃页缓冲。每条语句执行都会从新结构缓冲区,从新结构缓冲区是破费大,低效的口头,因为会调用磁盘I/O。并且,存在并发管制的开销,因为对每一句SQL语句的执行,应用程序须要从新获取和开释对数据库文件的锁。这种开销会使性能显著降落(尤其是大型应用程序),并只能以关上一个包含了很多SQL语句的用户级别的事务来加重这种状况(如:关上多个数据库)。应用程序能够用begin命令手动的开始一个新的事务,这种事务被称为用户级事务(或用户事务)。当begin执行后,SQLite来到默认的主动提交模式,在语句完结时不会调用commit或abort。也不会抛弃该页的缓冲。间断的SQL语句是整个事物的一部分,当应用程序执行commit/respectively/rollback指令时,SQLite提交或别离或停止事务。如果当事务停止或失败,或应用程序敞开连贯,则整个事务回滚。SQLite在事务实现时复原到主动提交模式上来。begin开始事务commit保留更改rollback回滚所作的更改事务回滚sqlite> select * from stu;0|小黑1|小白sqlite> update stu set name='人类';sqlite> select * from stu;0|人类1|人类sqlite> rollback;sqlite> select * from stu;0|小黑1|小白事务提交sqlite> select * from stu;0|小黑1|小白sqlite> begin;sqlite> select * from stu;0|小黑1|小白sqlite> update stu set name='人类';sqlite> select * from stu;0|人类1|人类sqlite> commit;sqlite> select * from stu;0|人类1|人类sqlite> rollback;Error: cannot rollback - no transaction is active编程试验:事务对插入效率的影响#include <stdio.h>#include <sqlite3.h>#include <time.h>int main(){ // 1. 关上数据库 sqlite3 *ppdb = NULL; int ret = sqlite3_open("test.db", &ppdb); if (ret != SQLITE_OK) { printf("open fail\n"); return -1; } // 2. 执行 sql 语句 // 2.1 创建表格 create table apitest(id int, number int); const char *create_sql = "create table if not exists testTransaction(id int, number int)"; char *errmsg = NULL; ret = sqlite3_exec(ppdb, create_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("%s\n", errmsg); } sqlite3_free(errmsg); // 2.2 数据直接插入 ============================== struct timeval start; struct timeval end; char insert_sql[128] = {0}; mingw_gettimeofday(&start, NULL); for (int i=0; i<1000; ++i) { sprintf(insert_sql, "insert into testTransaction values(%d, %d)", i, i*100); ret = sqlite3_exec(ppdb, insert_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("insert fail\n"); break; } sqlite3_free(errmsg); } mingw_gettimeofday(&end, NULL); printf("insert directly: %ld s\n", end.tv_sec - start.tv_sec); // 2.3 数据直接插入-事务 ============================== mingw_gettimeofday(&start, NULL); sqlite3_exec(ppdb, "begin", NULL, NULL, NULL); for (int i=0; i<1000; ++i) { sprintf(insert_sql, "insert into testTransaction values(%d, %d)", i, i*100); ret = sqlite3_exec(ppdb, insert_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("insert fail\n"); break; } sqlite3_free(errmsg); } sqlite3_exec(ppdb, "commit", NULL, NULL, NULL); mingw_gettimeofday(&end, NULL); printf("insert directly(transaction): %ld us\n", end.tv_usec - start.tv_usec); // 3. 敞开数据库 ret = sqlite3_close(ppdb); if (ret != SQLITE_OK) { printf("close fail\n"); return -1; } return 0;}输入: ...
sql 语句执行流程:剖析 - 编译 - 执行问题当执行10000次插入操作时,sql 语句须要被“剖析 - 编译 - 执行”10000次,这是比拟低效的; 示例:char insert_sql[128] = {0};for (int i=0; i<10000; ++i){ sprintf(insert_sql, "insert into bindtest values(%d, %d)", i, i*100); ret = sqlite3_exec(ppdb, insert_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("insert fail\n"); break; } sqlite3_free(errmsg);}尝试解决用绑定机制,能够使 sql 语句只被解析一次,之后的操作应用第一次生成的执行打算,以此带来更高的执行效率。 应用步骤: 配备int sqlite3_prepare( sqlite3 *db, /* Database handle */ const char *zSql, /* SQL statement, UTF-8 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const char **pzTail /* OUT: Pointer to unused portion of zSql */);绑定数据int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));int sqlite3_bind_blob64(sqlite3_stmt*, int, const void*, sqlite3_uint64, void(*)(void*));int sqlite3_bind_double(sqlite3_stmt*, int, double);int sqlite3_bind_int(sqlite3_stmt*, int, int);int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);int sqlite3_bind_null(sqlite3_stmt*, int);int sqlite3_bind_text(sqlite3_stmt*,int,const char*,int,void(*)(void*));int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*));int sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64, void(*)(void*), unsigned char encoding);int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);int sqlite3_bind_pointer(sqlite3_stmt*, int, void*, const char*,void(*)(void*));int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);int sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64);执行int sqlite3_step(sqlite3_stmt* mStmt);如果循环操作(绑定)时int sqlite3_finalize(sqlite3_stmt *pStmt);开释sqlite3_finalize(stmt);示例:sqlite3_stmt *pStmt = NULL;char insert[] = "insert into bindtest values(?, ?)";sqlite3_prepare(ppdb, insert, sizeof(insert), &pStmt, NULL);// 2. 绑定数据for (int i=0; i<10000; ++i){ sqlite3_bind_int(pStmt, 1, i); // 留神这里,绑定从 1 开始 !!! sqlite3_bind_int(pStmt, 2, i * 100); sqlite3_step(pStmt); // 执行插入 sqlite3_reset(pStmt); // 重置}// 3. 开释sqlite3_finalize(pStmt);编程试验#include <stdio.h>#include <sqlite3.h>#include <time.h>int main(){ // 1. 关上数据库 sqlite3 *ppdb = NULL; int ret = sqlite3_open(":memory:", &ppdb); if (ret != SQLITE_OK) { printf("open fail\n"); return -1; } // 2. 执行 sql 语句 // 2.1 创建表格 create table apitest(id int, number int); const char *create_sql = "create table if not exists bindtest(id int, number int)"; char *errmsg = NULL; ret = sqlite3_exec(ppdb, create_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("%s\n", errmsg); } sqlite3_free(errmsg); // 2.2 数据直接插入 ============================== struct timeval start; struct timeval end; char insert_sql[128] = {0}; mingw_gettimeofday(&start, NULL); for (int i=0; i<10000; ++i) { sprintf(insert_sql, "insert into bindtest values(%d, %d)", i, i*100); ret = sqlite3_exec(ppdb, insert_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("insert fail\n"); break; } sqlite3_free(errmsg); } mingw_gettimeofday(&end, NULL); printf("nsert directly: %ld\n", end.tv_usec - start.tv_usec); // 2.3 数据绑定插入 ============================== sqlite3_stmt *pStmt = NULL; char insert[] = "insert into bindtest values(?, ?)"; mingw_gettimeofday(&start, NULL); sqlite3_prepare(ppdb, insert, sizeof(insert), &pStmt, NULL); for (int i=0; i<10000; ++i) { sqlite3_bind_int(pStmt, 1, i); sqlite3_bind_int(pStmt, 2, i * 100); sqlite3_step(pStmt); sqlite3_reset(pStmt); } sqlite3_finalize(pStmt); mingw_gettimeofday(&end, NULL); printf("bind directly: %ld\n", end.tv_usec - start.tv_usec); // 3. 敞开数据库 ret = sqlite3_close(ppdb); if (ret != SQLITE_OK) { printf("close fail\n"); return -1; } return 0;}输入: ...
lower_bound&upper_bound - 二分查找函数他们是C++自带的函数,用于在有序的数列里进行查找。留神,肯定是有序的他们应用的是二分查找的办法,工夫复杂度为O(logn),效率很高应用它们要加上算法头文件,当然,能够应用万能头文件也能够 #include<algorithm> // 算法头文件 #include<bits/stdc++.h> //万能头文件 先理解一下它们的区别lower_bound - 数列递增序时查找大于等于查找数的第一个数,递加则查找小于等于查找数的第一个数 upper_bound - 数列递增序时查找严格大于查找数的第一个数,递加则严格小于等于查找数的第一个数 再看下应用格局能够看出两者应用格局基本相同 lower_bound(begin, end, num);upper_bound(begin, end, num);然而它们的返回值是指针或迭代器,咱们要想获取下标还要再减去首地址,晓得了下标也就能够取得具体指了 举几个例子便于了解 数组内应用: int a[5] = {1, 2, 3, 4, 5}; int p = lower_bound(a, a+5, 3)-a; //p=2int num = a[p]; //num=3vector内应用 vector<int> v;for(int i=1; i<=5; i++) v.push_back(i); int p = lower_bound(v.begin(), v.end(), 3)-v.begin(); //p=2int num = v[p]; //num=3如有疑难欢送在下方评论区提出
根底操作关上数据库文件int sqlite3_open(const char filename, sqlite3 ppDb);该例程关上一个指向 SQLite 数据库文件的连贯,返回一个用于其余 SQLite 程序的数据库连贯对象。如果 filename 参数是 NULL 或 ':memory:',那么 sqlite3_open() 将会在 RAM 中创立一个内存数据库,这只会在 session 的无效工夫内继续。如果文件名 filename 不为 NULL,那么 sqlite3_open() 将应用这个参数值尝试关上数据库文件。如果该名称的文件不存在,sqlite3_open() 将创立一个新的命名为该名称的数据库文件并关上。 操作数据(执行 sql 语句)int sqlite3_exec(sqlite3,const char sql,sqlite_callback,void ,char errmsg);该例程提供了一个执行 SQL 命令的快捷方式,SQL 命令由 sql 参数提供,能够由多个 SQL 命令组成。在这里,第一个参数 sqlite3 是关上的数据库对象,_sqlite_callback_ 是一个回调,_data_ 作为其第一个参数,errmsg 将被返回用来获取程序生成的任何谬误。sqlite3_exec() 程序解析并执行由 sql 参数所给的每个命令,直到字符串完结或者遇到谬误为止。 敞开数据库int sqlite3_close(sqlite3*);该例程敞开之前调用 sqlite3_open() 关上的数据库连贯。所有与连贯相干的语句都应在连贯敞开之前实现。如果还有查问没有实现,sqlite3_close() 将返回 SQLITE_BUSY 禁止敞开的谬误音讯。 编程试验#include <stdio.h>#include <sqlite3.h>int main(){ // 1. 关上数据库 sqlite3 *ppdb = NULL; int ret = sqlite3_open("test.db", &ppdb); if (ret != SQLITE_OK) { printf("open fail\n"); return -1; } // 2. 执行 sql 语句 // 2.1 创建表格 create table apitest(id int, number int); const char *create_sql = "create table apitest(id int, number int)"; char *errmsg = NULL; ret = sqlite3_exec(ppdb, create_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("%s\n", errmsg); } sqlite3_free(errmsg); // 2.2 插入数据 insert into apitest(id int, number int) const char *insert_sql = "insert into apitest values(0, 20200727)"; ret = sqlite3_exec(ppdb, insert_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("%s\n", errmsg); } sqlite3_free(errmsg); // 3. 敞开数据库 ret = sqlite3_close(ppdb); if (ret != SQLITE_OK) { printf("close fail\n"); } return 0;}回调函数查问#include <stdio.h>#include <sqlite3.h>/* *@brief:数据查问回调函数,有多少行数据被调用多少次(返回值始终为0时) *@param:arg: 由 sqlite3_exec 第 4 个参数传入 *@param:cols: 查问到的数据的列数 *@param:values: 一行数据字段值 *@param:names: 一行数据字段明 *@retval: 0 持续查问 *@retval: 1 终止查问 **/int callback(void *arg, int cols, char **values, char **names) // 留神这里!!!{ (void)(arg); static int flag = 0; if (flag == 0) { printf("cols = %d\n", cols); for (int i=0; i<cols; ++i) printf("%s\t", names[i]); flag = 1; } printf("\n"); for (int i=0; i<cols; ++i) printf("%s\t", values[i]); return 0;}int main(){ // 1. 关上数据库 sqlite3 *ppdb = NULL; int ret = sqlite3_open("test.db", &ppdb); if (ret != SQLITE_OK) { printf("open fail\n"); return -1; } // 2. 执行 sql 语句 // 2.1 创建表格 create table apitest(id int, number int); const char *create_sql = "create table if not exists apitest(id int, number int)"; char *errmsg = NULL; ret = sqlite3_exec(ppdb, create_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("%s\n", errmsg); } sqlite3_free(errmsg); // 2.2 插入数据 insert into apitest(id int, number int) const char *insert_sql = "insert into apitest values(0, 20200727)"; ret = sqlite3_exec(ppdb, insert_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("%s\n", errmsg); } sqlite3_free(errmsg); // 2.3 回调函数查问 const char *select_sql = "select * from apitest"; ret = sqlite3_exec(ppdb, select_sql, callback, NULL, &errmsg); // 留神这里!!! if (ret != SQLITE_OK) { printf("select fail\n"); } sqlite3_free(errmsg); // 3. 敞开数据库 ret = sqlite3_close(ppdb); if (ret != SQLITE_OK) { printf("close fail\n"); return -1; } return 0;}输入: ...
根底操作关上数据库文件int sqlite3_open(const char filename, sqlite3 ppDb);该例程关上一个指向 SQLite 数据库文件的连贯,返回一个用于其余 SQLite 程序的数据库连贯对象。如果 filename 参数是 NULL 或 ':memory:',那么 sqlite3_open() 将会在 RAM 中创立一个内存数据库,这只会在 session 的无效工夫内继续。如果文件名 filename 不为 NULL,那么 sqlite3_open() 将应用这个参数值尝试关上数据库文件。如果该名称的文件不存在,sqlite3_open() 将创立一个新的命名为该名称的数据库文件并关上。 操作数据(执行 sql 语句)int sqlite3_exec(sqlite3,const char sql,sqlite_callback,void ,char errmsg);该例程提供了一个执行 SQL 命令的快捷方式,SQL 命令由 sql 参数提供,能够由多个 SQL 命令组成。在这里,第一个参数 sqlite3 是关上的数据库对象,_sqlite_callback_ 是一个回调,_data_ 作为其第一个参数,errmsg 将被返回用来获取程序生成的任何谬误。sqlite3_exec() 程序解析并执行由 sql 参数所给的每个命令,直到字符串完结或者遇到谬误为止。 敞开数据库int sqlite3_close(sqlite3*);该例程敞开之前调用 sqlite3_open() 关上的数据库连贯。所有与连贯相干的语句都应在连贯敞开之前实现。如果还有查问没有实现,sqlite3_close() 将返回 SQLITE_BUSY 禁止敞开的谬误音讯。 编程试验#include <stdio.h>#include <sqlite3.h>int main(){ // 1. 关上数据库 sqlite3 *ppdb = NULL; int ret = sqlite3_open("test.db", &ppdb); if (ret != SQLITE_OK) { printf("open fail\n"); return -1; } // 2. 执行 sql 语句 // 2.1 创建表格 create table apitest(id int, number int); const char *create_sql = "create table apitest(id int, number int)"; char *errmsg = NULL; ret = sqlite3_exec(ppdb, create_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("%s\n", errmsg); } sqlite3_free(errmsg); // 2.2 插入数据 insert into apitest(id int, number int) const char *insert_sql = "insert into apitest values(0, 20200727)"; ret = sqlite3_exec(ppdb, insert_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("%s\n", errmsg); } sqlite3_free(errmsg); // 3. 敞开数据库 ret = sqlite3_close(ppdb); if (ret != SQLITE_OK) { printf("close fail\n"); } return 0;}回调函数查问#include <stdio.h>#include <sqlite3.h>/* *@brief:数据查问回调函数,有多少行数据被调用多少次(返回值始终为0时) *@param:arg: 由 sqlite3_exec 第 4 个参数传入 *@param:cols: 查问到的数据的列数 *@param:values: 一行数据字段值 *@param:names: 一行数据字段明 *@retval: 0 持续查问 *@retval: 1 终止查问 **/int callback(void *arg, int cols, char **values, char **names) // 留神这里!!!{ (void)(arg); static int flag = 0; if (flag == 0) { printf("cols = %d\n", cols); for (int i=0; i<cols; ++i) printf("%s\t", names[i]); flag = 1; } printf("\n"); for (int i=0; i<cols; ++i) printf("%s\t", values[i]); return 0;}int main(){ // 1. 关上数据库 sqlite3 *ppdb = NULL; int ret = sqlite3_open("test.db", &ppdb); if (ret != SQLITE_OK) { printf("open fail\n"); return -1; } // 2. 执行 sql 语句 // 2.1 创建表格 create table apitest(id int, number int); const char *create_sql = "create table if not exists apitest(id int, number int)"; char *errmsg = NULL; ret = sqlite3_exec(ppdb, create_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("%s\n", errmsg); } sqlite3_free(errmsg); // 2.2 插入数据 insert into apitest(id int, number int) const char *insert_sql = "insert into apitest values(0, 20200727)"; ret = sqlite3_exec(ppdb, insert_sql, NULL, NULL, &errmsg); if (ret != SQLITE_OK) { printf("%s\n", errmsg); } sqlite3_free(errmsg); // 2.3 回调函数查问 const char *select_sql = "select * from apitest"; ret = sqlite3_exec(ppdb, select_sql, callback, NULL, &errmsg); // 留神这里!!! if (ret != SQLITE_OK) { printf("select fail\n"); } sqlite3_free(errmsg); // 3. 敞开数据库 ret = sqlite3_close(ppdb); if (ret != SQLITE_OK) { printf("close fail\n"); return -1; } return 0;}输入: ...
数列分块专题 No.2分块利用推广来啦如果还不理解分块请先学习"数列分块1" ->点这里学习 放上题号 - LOJ 6278 数列分块入门 2挂上链接 - https://loj.ac/problem/6278题面 - 给出一个数列,2种操作,区间加法和询问区间内小于某个值的元素个数 Now,进入正题这题较之分块1的题区别就在于单点查值变成了区间询问小于某值的元素个数 这下可犯难了,数列是无序的呀,挨个比拟,O(n^2),必定超时;排序也不行,打乱了原先的程序,就找不到题目给定的区间了...... 这时分块又派上了用场,"大段保护,小段奢侈"的核心思想仍旧 然而如何"大段保护"呢?既然咱们不能排序整个数列,那咱们无妨将每个每个分块排序,瞧下数据,50000级别,O(n*logn)的sort就能够满足需要。 void reset(int x){ ve[x].clear(); for(int i=(x-1)*blo+1;i<=min(x*blo,N);i++) ve[x].push_back(v[i]); sort(ve[x].begin(),ve[x].end()); //对于一个分块的批改并排序(因为vector内元素不可批改,所以清零后从新放入) }排完序后就很明了了,对于每个分块咱们应用O(logn)二分查找(lower_bound函数)来求其小于某值的元素个数。 for(int i=bl[a]+1;i<=bl[b]-1;i++){ int x=c-atag[i]; //先减去区间对立加量,反映了宏观晋升宏观 ans+=lower_bound(ve[i].begin(),ve[i].end(),x)-ve[i].begin(); //二分查找求数量 }"小段奢侈"即头尾有余一个分块的局部应用挨个比拟的办法 for(int i=a;i<=min(bl[a]*blo,b);i++) if(v[i]+atag[bl[a]]<c) ans++; if(bl[a]!=bl[b]) for(int i=(bl[b]-1)*blo+1;i<=b;i++) if(v[i]+atag[bl[b]]<c) ans++;区间加法和之前根本一样,如果还不理解分块请先学习"数列分块1" ->点这里学习但记得"小段保护"后排序 for(int i=a;i<=min(bl[a]*blo,b);i++) v[i]+=c;reset(bl[a]); //"小段" 内单点批改后记得把所在分块批改排序 if(bl[a]!=bl[b]){ for(int i=(bl[b]-1)*blo+1;i<=b;i++) v[i]+=c; reset(bl[b]); //同理 }到这里就根本完结啦~最初附上残缺代码(C++)和精心设计的注解 // LOJ 6278 - 数列分块练习2// https://loj.ac/problem/6278// 分块(vector) #include<bits/stdc++.h>#define ll long longusing namespace std;int N,blo;int v[50005],bl[50005],atag[50005];vector<int> ve[505];void reset(int x){ ve[x].clear(); for(int i=(x-1)*blo+1;i<=min(x*blo,N);i++) ve[x].push_back(v[i]); sort(ve[x].begin(),ve[x].end()); //对于一个分块的批改并排序(因为vector内元素不可批改,所以清零后从新放入) }void add(int a,int b,int c){ for(int i=a;i<=min(bl[a]*blo,b);i++) v[i]+=c; reset(bl[a]); //"小段" 内单点批改后记得把所在分块批改排序 if(bl[a]!=bl[b]){ for(int i=(bl[b]-1)*blo+1;i<=b;i++) v[i]+=c; reset(bl[b]); //同理 } for(int i=bl[a]+1;i<=bl[b]-1;i++) atag[i]+=c;}int query(int a,int b,int c){ int ans=0; for(int i=a;i<=min(bl[a]*blo,b);i++) if(v[i]+atag[bl[a]]<c) ans++; //右边有余一块的中央进行"奢侈" if(bl[a]!=bl[b]) for(int i=(bl[b]-1)*blo+1;i<=b;i++) if(v[i]+atag[bl[b]]<c) ans++; //左边 for(int i=bl[a]+1;i<=bl[b]-1;i++){ int x=c-atag[i]; //先减去区间对立加量,反映了宏观晋升宏观 ans+=lower_bound(ve[i].begin(),ve[i].end(),x)-ve[i].begin(); //二分查找求数量 } return ans;}int main(){ ios::sync_with_stdio(false); cin.tie(0); cin >> N; blo=sqrt(N); for(int i=1;i<=N;i++) cin >> v[i]; for(int i=1;i<=N;i++){ bl[i]=(i-1)/blo+1; ve[bl[i]].push_back(v[i]); } for(int i=1;i<=bl[N];i++) sort(ve[i].begin(),ve[i].end()); for(int i=1;i<=N;i++){ int F, L, R, C; cin >> F >> L >> R >> C; if(F==0) add(L,R,C); // 区间加法 if(F==1) cout << query(L,R,C*C) << endl; //区间查问 } return 0;}如有疑难可在评论区提出 ...
*关灯问题II(Luogu P2622)先挂个链接 - https://www.luogu.com.cn/prob... 题面 - 现有N盏灯,M个按钮。每个按钮能够同时管制这n盏灯——按下某个按钮,对于所有的灯都有一个成果。给出所有开关对所有灯的管制成果,问起码要按几下按钮能将灯从全开变为全关 Now,进入正题 瞧下数据,N<=10,M<=100数据规模不很大,果决BFS(广度优先搜寻)然而......BFS的判重又成了大问题一一比对,看状态是否统一,显然超时不判重,BFS就间接废了 这时候,新Get到的技能——状态压缩闪亮退场状态压缩行将原来状态的数组寄存变为一个01形成的整数寄存这样就这能够设置一个下标代表状态的vis[]数组来判重判重霎时降到O(1)级别状态的扭转则用位运算,也降到O(1)级别 就是这么容易...... 上面挂上程序(C++)和精心设计的注解不便了解 //Luogu P2622 - 关灯问题II//https://www.luogu.com.cn/problem/P2622//BFS+状压 #include <bits/stdc++.h> #define MAXN 2048 using namespace std; int N, M; struct Node{ int dp; //状压值,反馈灯状态 int step; //操作次数}; int vis[MAXN]; //用于BFS的判重 int a[MAXN][MAXN]; //灯的管制成果 void BFS(int n){ queue<Node> Q; Node fir; fir.step = 0, fir.dp = n; Q.push(fir); while(!Q.empty()){ Node u=Q.front(); Q.pop(); int pre=u.dp; //拿出队首 for(int i=1; i<=M; i++){ int now=pre; //now即为状压值,灯的状态 for(int j=1; j<=N; j++){ if(a[i][j]==1){ if((1<<(j-1))&now){ now = now^(1<<(j - 1)); } } else if(a[i][j]==-1){ now = ( (1<<(j-1))|now); } } //应用位运算扭转状压值,反馈了开关对于灯状态的影响 fir.dp=now, fir.step=u.step+ 1; if(vis[now]) continue; //BFS的判重 if(fir.dp==0){ vis[0]=true; cout << fir.step << endl; return ; } //如果达到,输入并退出BFS Q.push(fir); //新状态入队 vis[now] = true; //记录状态,用于判重 } } } int main(){ cin >> N >> M; int temp=(1<<(N))-1; for(int i=1; i<=M; i++){ for(int j=1; j<=N; j++){ cin >> a[i][j]; } } BFS(temp); if(!vis[0]) cout << -1 << endl; //无奈达到,输入-1 return 0; }这题最初的难点就是位运算了其实笔者本人也有点懵......日后笔者会潜心研究并撰写相干专题的文章的还请大家急躁期待 ...
*关灯问题II(Luogu P2622)先挂个链接 - https://www.luogu.com.cn/prob... 题面 - 现有N盏灯,M个按钮。每个按钮能够同时管制这n盏灯——按下某个按钮,对于所有的灯都有一个成果。给出所有开关对所有灯的管制成果,问起码要按几下按钮能将灯从全开变为全关 Now,进入正题 瞧下数据,N<=10,M<=100数据规模不很大,果决BFS(广度优先搜寻)然而......BFS的判重又成了大问题一一比对,看状态是否统一,显然超时不判重,BFS就间接废了 这时候,新Get到的技能——状态压缩闪亮退场状态压缩行将原来状态的数组寄存变为一个01形成的整数寄存这样就这能够设置一个下标代表状态的vis[]数组来判重判重霎时降到O(1)级别状态的扭转则用位运算,也降到O(1)级别 就是这么容易...... 上面挂上程序(C++)和精心设计的注解不便了解 //Luogu P2622 - 关灯问题II//https://www.luogu.com.cn/problem/P2622//BFS+状压 #include <bits/stdc++.h> #define MAXN 2048 using namespace std; int N, M; struct Node{ int dp; //状压值,反馈灯状态 int step; //操作次数}; int vis[MAXN]; //用于BFS的判重 int a[MAXN][MAXN]; //灯的管制成果 void BFS(int n){ queue<Node> Q; Node fir; fir.step = 0, fir.dp = n; Q.push(fir); while(!Q.empty()){ Node u=Q.front(); Q.pop(); int pre=u.dp; //拿出队首 for(int i=1; i<=M; i++){ int now=pre; //now即为状压值,灯的状态 for(int j=1; j<=N; j++){ if(a[i][j]==1){ if((1<<(j-1))&now){ now = now^(1<<(j - 1)); } } else if(a[i][j]==-1){ now = ( (1<<(j-1))|now); } } //应用位运算扭转状压值,反馈了开关对于灯状态的影响 fir.dp=now, fir.step=u.step+ 1; if(vis[now]) continue; //BFS的判重 if(fir.dp==0){ vis[0]=true; cout << fir.step << endl; return ; } //如果达到,输入并退出BFS Q.push(fir); //新状态入队 vis[now] = true; //记录状态,用于判重 } } } int main(){ cin >> N >> M; int temp=(1<<(N))-1; for(int i=1; i<=M; i++){ for(int j=1; j<=N; j++){ cin >> a[i][j]; } } BFS(temp); if(!vis[0]) cout << -1 << endl; //无奈达到,输入-1 return 0; }这题最初的难点就是位运算了其实笔者本人也有点懵......日后笔者会潜心研究并撰写相干专题的文章的还请大家急躁期待 ...
Get 新专题 —— 分块来和大家分享一下 其实这是以前讲过的......我没认真听......当初温习算是明确了...... 注释局部分块板子题 - LOJ 6277 数列分块入门1 挂上链接 - https://loj.ac/problem/6277 题面很简略 - 数列的区间加法,单点查值 然而......数列长度,操作数量最大可达50000,单点查值问题不大,可区间加法就太坑了,如果硬做,工夫复杂度可达O(n^2),必定超时...... 家喻户晓,操作越宏观复杂度越低。不难发现,区间加法一个一个加太过宏观,要设法往宏观拔。分块就是一种办法。 顾名思义,分块就是把数列分成若干块。怎么分呢?设块数为a,一块数量为b,总量为n。咱们心愿a与b绝对均衡(起因一会就会明了)。依据a与b的数量关系,即a*b=n,为使其均衡,无妨令a,b都为sqnrt(n)(根号n)。 分完块后就能够开始操作了 先来了解一下分块。分块的精华,即"大段保护,小段奢侈"。大段即整段,也就是一个分块,小段即操作区间两端不满一块的局部。保护即整段操作,奢侈即原始的一个数一个数的操作。 对于这题,区间加法就能够使用"大段保护,小段奢侈"。将操作区间分为"大段","小段"。 "大段"操作,扫描每一个"大段",加到一个数中,将加一个一个加化为整体加,将宏观化为宏观。 "小段"个别有两段,即左端和右端,别离一个数一个数的加 因为大段数量不会超过sqrt(n),小段中的数的总数也不会超过2 sqrt(n),所以区间加法的总复杂度从O(n^2)级别降到了O(n sqrt(n))级别 单点查问中单点的值=其独自的值+所在区间的对立加的值 那么,分块1到这就根本完结啦,上面会附上残缺代码(C++)和变量解释 残缺代码: 变量解释: a[] - 数列 atag[] - 区间加值 p[] - 数列中某数所在的区间下标
eltielti是什么?一个基于c++11规范的序列化/反序列化协定与实现,与json,xml等文本协定相比,elti能够传递二进制数据;与protobuf协定相比,elti配置简略,即拿即用。 elti是相似于bson和cbor的序列化协定,相较于bson协定空间利用率不现实( https://blog.csdn.net/m0_38110132/article/details/77716792 ) ,cbor的c/c++实现又太少(没有找到宽泛应用的稳固的轮子), elti旨在提供一个在效率,接口易用性,扩展性,空间利用率等方面有一个平衡稳固的体现,elti次要用来做数据传输而不是展现,即elti不思考可读性。 github : https://github.com/chloro-pn/... 格局Element := Key : ValueValue := | Map | Array | DataMap := { Element }ARRAY := { Value }Data := [ byte0, byte1, ... byten ]反对平台linux, macOS 第三方库elti应用 https://github.com/sorribas/varint.c 作为对整形数据进行变长编码的库。 应用 https://github.com/catchorg/Catch2 作为单元测试库。 Buildingjust run make. 配置库文件+头文件门路即可应用。 Test采纳Catch2作为单元测试库,于test文件夹下。 BenchMark耗时(s)eltiprotobufrapidjsonnlohmann/json测试数据10.0600.00560.0370.070测试数据20.0790.0260.3520.770测试数据30.3650.2353.687.225具体信息见benchmark/BENCH_MARK.md TODO减少定位器定位后果到Value(Data, Map, Array)的转化操作,目前定位器对于数据的拜访能力无限,例如不反对拜访数组 长度类型反对复合类型的序列化/反序列化机制,例如:class test {private: classA a; classB b;};seri(const test& t, std::vector<uint8\_t\>& container) { seri(a); seri(b);}目前这种操作不反对,没有对container做进一步形象。 ...
eltielti是什么?一个基于c++11规范的序列化/反序列化协定与实现,与json,xml等文本协定相比,elti能够传递二进制数据;与protobuf协定相比,elti配置简略,即拿即用。 elti是相似于bson和cbor的序列化协定,相较于bson协定空间利用率不现实( https://blog.csdn.net/m0_38110132/article/details/77716792 ) ,cbor的c/c++实现又太少(没有找到宽泛应用的稳固的轮子), elti旨在提供一个在效率,接口易用性,扩展性,空间利用率等方面有一个平衡稳固的体现,elti次要用来做数据传输而不是展现,即elti不思考可读性。 github : https://github.com/chloro-pn/... 格局Element := Key : ValueValue := | Map | Array | DataMap := { Element }ARRAY := { Value }Data := [ byte0, byte1, ... byten ]反对平台linux, macOS 第三方库elti应用 https://github.com/sorribas/varint.c 作为对整形数据进行变长编码的库。 应用 https://github.com/catchorg/Catch2 作为单元测试库。 Buildingjust run make. 配置库文件+头文件门路即可应用。 Test采纳Catch2作为单元测试库,于test文件夹下。 BenchMark耗时(s)eltiprotobufrapidjsonnlohmann/json测试数据10.0600.00560.0370.070测试数据20.0790.0260.3520.770测试数据30.3650.2353.687.225具体信息见benchmark/BENCH_MARK.md TODO减少定位器定位后果到Value(Data, Map, Array)的转化操作,目前定位器对于数据的拜访能力无限,例如不反对拜访数组 长度类型反对复合类型的序列化/反序列化机制,例如:class test {private: classA a; classB b;};seri(const test& t, std::vector<uint8\_t\>& container) { seri(a); seri(b);}目前这种操作不反对,没有对container做进一步形象。 ...
以下材料整顿于集体的学习过程,蕴含较为残缺的两篇文章和样式表编辑预览工具。在文章浏览过程中追随练习,能够对qss根底应用细节有大体理解,后续应用时也可当作工具书参考查阅。参考文章:《Qt Style Sheet 开发总结》《Qt样式表应用阐明》 举荐浏览程序:《Qt Style Sheet 开发总结》、《Qt样式表应用阐明》 编辑工具(非常举荐):QssStylesheetEditor (原作者)QssStylesheetEditor (备份) Qss代码高亮,代码折叠Qss代码主动提醒,主动补全实时预览 Qss 款式成果能够预览简直所有的 qtwidget 控件成果反对在 Qss 中自定义变量自定义变量能够在色彩对话框中拾取变量的色彩反对相对路径援用图片,以及援用资源文件中的图片反对切换不同的零碎 theme,如 xp 主题,vista 主题等(不同 theme 下 qss 成果会略有差别)可能在 windows,linux,unix 上运行反对多国语言(中文,英文)以上资源起源与网络,非常感激作者的开源共享。
根底数据类型类型阐明int(integer)-2^31(-2,147,483,648)到2^31(2,147,483,647)的整型数字float-1.79E+308到1.79E+308可变精度的数字real-3.04E+38到3.04E+38可变精度的数字char定长非Unicode的字符型数据,最大长度8000varchar变长非Unicode的字符型数据,最大长度8000text变长非Unicode的字符型数据,最大长度为2^31-1(2G)例:date(出生日期)name(姓名)gender(性别)hobby(喜好)char(16)varchar(256)char(16)text19940801小白男C++19940802小黑女PHPsql 通用语句注:命令行语句完结要加 ; (分号)创立数据库.open 数据库名注: sqlite3 提供的命令行性能语句,非 sql 通用语句.open person.db创立数据表格create table 表名(字段名 数据类型, 字段名 数据类型, 字段名 数据类型);create table child(date char(16), name varchar(256), gender char(16), hobby text);插入数据insert into 表名 values('字段数据', '字段数据', ' 字段数据', '字段数据');注:如果数据类型是 char,varchar,text 数据必须应用 '' 或 "" 援用insert into child values('19940801', '小白', '男', 'C++');insert into child values('19940802', '小黑', '女', 'PHP');查问数据select 字段名, ..., 字段名 from 表名; 注:字段名如果是多个能够用 " , " 逗号隔开,如果是所有能够用 " * " 星号查问全副: sqlite> select * from child;19940801|小白|男|C++19940802|小黑|女|PHP查问多个: ...
一、性能形容本我的项目为“酒店客房管理系统”,实现了对酒店的客房信息进行治理,次要性能包含: 酒店初始化(1) 初始化管理员账号密码 (2) 初始化操作员账号密码 (3) 初始化酒店客房信息 管理员治理(1) 管理员登录 (2) 查看、减少、删除、批改操作员信息 (3) 操作员信息写入文件 (4) 查看、批改酒店客房信息 (5) 酒店客房信息写入文件 操作员治理(1) 查看客房信息 (2) 客房的预订 (3) 客房的入住 (4) 客房的退房 (5) 预订查问(依据房号和姓名查问预订信息) (6) 客房查问(依据房号查问房间信息) (7) 住客查问(依据姓名查问住客信息) (8) 操作员工作日志写入文件 二、设计与实现1. 模块结构图及各模块的性能模块结构图如下: 本酒店客房管理系统有两个模块:管理员模块和操作员模块。 管理员模块的性能有: 管理员登录:依据输出账号密码登录进入管理员权限操作员治理 查看操作员信息减少操作员信息删除操作员信息批改操作员信息酒店客房治理 查看酒店客房信息批改酒店客房信息操作员模块的性能有: 操作员登录:依据输出账号密码登录进入操作员权限查看客房信息客房的预订客房的入住客房的退房预订查问:依据房号和姓名查问预订信息客房查问:依据房号查问房间信息住客查问:依据姓名查问住客信息2. 数据类型的设计和阐明函数调用关系 Room类Room类的数据类型如下: 在本我的项目的设计中,Room是酒店客房的形象,而Room数组是酒店的形象。 友元函数RoomInit:进行酒店房间初始化。本我的项目初始化了20间客房的类型和价格,并将酒店客房信息保留在“酒店房间信息.xls”文件中。 Operator类 在本我的项目中,Operator是酒店操作员的形象,并用一个Operator数组保留操作员信息。 友元函数OperatorInit():进行操作员信息的初始化。本我的项目初始化了10位操作员的账号和明码,并将酒店客房信息保留在“操作员账户信息.txt”文件中。 友元函数OperatorLogin():登录操作员账号。该函数传入Operator数组并返回登录操作员在数组的第几个元素。 成员函数WriteRoomInformation(Room*):将Room数组中的信息写入到“酒店房间信息.xls”文件中。该函数是一个辅助函数,当Room数组的信息进行批改时,将调用该函数批改文件相应内容。 实现操作员相应性能的成员函数如下: ViewRoomInformation(Room*):查看酒店客房的根本信息。该信息不包含住客的根本信息。ReserveRoom(Room*):预订客房。该函数打印闲暇房间让用户抉择、并输出用户的姓名、身份证号、联系方式等根本信息。CheckIn(Room*):入住房间。该函数先询问客户是否预订房间,若预订需比照信息能力入住,若未预订则打印闲暇房间并输出个人信息实现入住。CheckOut(Room*):住客退房。该函数输出退房的房号并比对住客信息,如统一方可退房、将该房间的信息置空。RoomQuery(Room*):客房查问。输出查问房号可打印房间信息。BookQuery(Room*):预订查问。输出预订房号和姓名可查问预订信息。GuestQuery(Room*):住客查问。输出客户姓名可查问客户信息。OperatorQuit():操作员退出登录。返回主登录菜单或退出程序时将调用该函数。 以上操作员的所有操作均会保留在“操作员工作日志.xls”文件中。工作日志的工夫由以后零碎工夫取得。 Administrator类 在本我的项目中,Administrator是酒店管理员的形象。 构造函数Administrator():初始化管理员账号和明码。在本我的项目中,管理员账号和明码被初始化后不可在程序中进行批改。 ...
课程链接:https://ocw.mit.edu/courses/e... L.02 Flow of Control1. MotivationAlter the order in which a program's statements are excuted, the control flow. 2. Contorl Structure2.1 ConditionalsRelational operator: >, < , ==, !=, <=, etc.Logical operator: &&(and), ||(or), !(not)2.1.1 if, if-else and else ifif conditional:if(condition){ statement 1 statement 1 ...}if(condition) statement only 1if-else conditional:if(condition){ statement 1 statement 2 ...}else{ statement 3 statement 4}if(condition) statement only 1aelse statement only 1bif-else if conditional:if(condition){ statement 1 statement 2 ...}else if(condition){ statement 3 statement 4}2.1.2 switchMay or may not excute certain statements. ...
疾速排序思维:任取待排序元素序列中的某个元素作为基准,依照该元素的排序码大小,把整个元素序列分为左右两个子序列:左侧子序列中的所有元素都比基准元素小,右侧所有元素都比基准元素大,而后别离对这两个子序列反复之前的操作,直到整个序列排好序。例子:初始序列: 基准(pivot) 21 25 49 25* 16 8 211 8 16 21 25 25 49 8(left) 25(right) 2 8 16 21 25* 25 49 25(right)3 8 16 21 25* 25 49 稳定性:不稳固工夫复杂度:O(nlogn)代码:c++//疾速排序void Quick_sort(int arr[],int left,int right) { if (left >= right) return; else { int pivot = arr[left];//基准 int i = left;//左指针 int j = right;//右指针 while (i < j) {//当左<右时,扫描序列 while (i < j && arr[j] >= pivot)//从右往左扫描遇到比基准小的值则停下来 j--; while (i < j && arr[i] <= pivot)//从左往右扫描遇到比基准大的值则停下来 i++; if (i<j) {//而后替换这两个值 swap(arr[i], arr[j]); } } if (i = j) {//当扫描到左右指针相遇时,示意扫描完结,将基准值和以后地位的值进行替换//咱们始终是以最右边的元素为基准元素的 ...
间接插入排序 思维:当插入第i个元素时,后面的i-1个元素曾经排好序,这时,用第i个元素和后面的i-1个元素进行比拟,找到第i个元素的地位将其插入,原来地位上的元素向后顺移。 例子: 初始序列:21 25 49 25* 16 8 1 21 25 49 25* 16 8 2 21 25 49 25* 16 8 3 21 25 25* 49 16 8 4 16 21 25 25* 49 8 5 8 16 21 25 25* 49 i=4时间接插入排序的过程: 初始:21 25 25* 49 16 8 temp 21 25 25* 49 16 8 16 ...
排序算法 1、冒泡排序: 思维:待排序数列有n个元素,首先比拟第0个元素和第1个元素,如果前者比后者大,则替换这两个元素,而后持续比拟第1个和第2个元素,反复此过程直到第n-2和n-1个元素比拟完,这样一趟下来,最大的元素就排好序了,咱们称之为一趟冒泡。而后对残余未排序的元素持续进行上述操作,直到所有元素都排好序。(能够从前往后也能够从后往前比拟) 例子: 原始序列:21 25 49 25* 16 08 1趟: 08 21 25 49 25* 16 2趟: 08 16 21 25 49 25* 3趟: 08 16 21 25 25* 49 4趟: 08 16 21 25 25* 49 第一趟冒泡的过程: 1 21 25 49 25* 16 08 2 21 25 49 25* 08 16 3 21 25 49 08 25* 16 ...
字符串匹配算法——KMP算法一、算法介绍:KMP算法是一种改良的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因而人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的外围是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到疾速匹配的目标。具体实现就是通过一个next()函数实现,函数自身蕴含了模式串的部分匹配信息。二、算法具体分析:1、写出字符串前缀;2、找出每个前缀字符串的最长公共前后缀;3、写出前缀表prefix table;4、依据前缀表进行匹配。三、例子:T:a b a a c a b a b c a cP:a b a b c首先写出字符串P的前缀如下: 最长公共前后缀长度 前缀表a 0 -1a b 0 0a b a 1 0a b a b 2 1a b a b c 0 2而后依据前缀表进行匹配: T:a b a a c a b a b c a c ...
图基本概念图的定义图G由顶点集V和边集E组成,记为G=(V,E),其中V(G)示意图G中顶点的无限非空集;E(G)示意图G中顶点之间的关系(边)的汇合。 留神:线性表能够是空表,树能够是空树,图不能够是空图,图能够没有边,然而至多要有一个顶点。 有向图若E是有向边(简称弧)的无限汇合时,则G为有向图。弧是顶点的有序对,记为<v,w>,其中 v,w 是顶点,v 是弧尾,w 是弧头。称为从顶点v到顶点w的弧。 如上图所示G可示意为: G=(V,E)V={1,2,3}E={<1,2>, <2,1>, <2,3>}无向图若E是无向边(简称边)的无限汇合时,则G为无向图。边是顶点的无序对,记为 (v,w) 或(w,v) ,且有 (v,w) =(w,v) 。其中 v,w 是顶点。 如上图所示,无向图G可示意为: G=(V, E)V={1,2,3,4} E={(1,2), (1,3), (1,4), (2,3), (2,4), (3,4)} 简略图简略图满足以下两条内容: 1)不存在反复边 2)不存在顶点到本身的边 所以下面的有向图和无向图都是简略图。与简略图绝对的是多重图,即:两个结点间接边数多于一条,又容许顶点通过同一条边与本人关联。然而咱们在数据结构中仅探讨简略图,所以多重图不独自解说啦。 4.齐全图无向图中任意两点之间都存在边,称为无向齐全图;如无向图中的示例就是齐全图。 有向图中任意两点之间都存在方向向反的两条弧,称为有向齐全图;如示例中的图就不是齐全图,但如果没有顶点3和指向顶点3 的边,就是一个齐全图。即下图是一个齐全图。 子图若有两个图G=(V,E),G1=(V1,E2),若V1是V的子集且E2是E的子集,称G1是G的子图。 如下面的有向齐全图是有向图的一个子图。 连通、连通图、连通重量在无向图中,两顶点有门路存在,就称为连通的。若图中任意两顶点都连通,同此图为连通图。无向图中的极大连通子图称为连通重量。 以上面两个图为例,上面的图是下面的图的连通重量,并且上面的图是连通图。下面图中I与J也是连通的。 强连通图、强连通重量在有向图中,两顶点两个方向都有门路,两顶点称为强连通。若任一顶点都是强连通的,称为强连通。有向图中极大强连通子图为有向图的强连通重量。 生成树和生成森林连通图的生成树是蕴含图中全副顶点的一个极小连通子图,若图中有n个顶点,则生成树有n-1条边。所以对于生成树而言,若砍去一条边,就会变成非连通图。 在非连通图中,连通重量的生成树形成了非连通图的生成森林。 无向图的一个生成树 顶点的度、入度和出度顶点的度为以该顶点为一个端点的边的数目。 对于无向图,顶点的边数为度,度数之和是顶点边数的两倍。 对于有向图,入度是以顶点为起点,出度相同。有向图的全副顶点入度之和等于出度之和且等于边数。顶点的度等于入度与出度之和。 留神:入度与出度是针对有向图来说的。 边的权和网图中每条边上标有某种含意的数值,该数值称为该边的权值。这种图称为带树图,也称作网。 门路、门路长度和回路两顶点之间的门路指顶点之间通过的顶点序列,通过门路上边的数目称为门路长度。若有n个顶点,且边数大于n-1,此图肯定有环。 简略门路、简略回路顶点不反复呈现的门路称为简略门路。 除第一个顶点和最初一个顶点外,其余顶点不反复呈现的回路称为简略回路。 间隔若两顶点存在门路,其中最短门路长度为间隔。 有向树有一个顶点的入度为0,其余顶点的入度均为1的有向图称作有向树。如下图: 图的存储构造邻接表// queue.h#pragma once#include <iostream>const int queueSize = 100;template<class T>class queue{public: T data[queueSize]; int front, rear;};// graph.h#pragma once#include<iostream>#include"queue.h"// 定义边表结点struct ArcNode{ int adjvex;// 邻接点域 ArcNode* next;};// 定义顶点表结点struct VertexNode{ int vertex; ArcNode* firstedge;}; // 基于邻接表存储构造的图的类实现const int MaxSize = 10;int visited[MaxSize] = { 0 };// 顶点是否被拜访的标记//typedef VertexNode AdjList[MaxSize]; //邻接表 template<class T>class ALGraph{public: ALGraph(T a[], int n, int e);// 构造函数建设具备N个定点e条边的图 ~ALGraph() {}// 析构函数 void DFSTraaverse(int v);// 深度优先遍历图 void BFSTraverse(int v);// 广度优先遍历图private: VertexNode adjlist[MaxSize];// 寄存顶点的数组 int vertexNum, arcNum;// 图中顶点数和边数}; template<class T>ALGraph<T>::ALGraph(T a[], int n, int e){ vertexNum = n; arcNum = e; for (int i = 0; i <vertexNum; i++) { adjlist[i].vertex = a[i]; adjlist[i].firstedge = NULL; } for (int k = 0; k < arcNum; k++) { int i, j; std::cin >> i >> j; ArcNode* s = new ArcNode; s->adjvex = j; s->next = adjlist[i].firstedge; adjlist[i].firstedge = s; }} template<class T>inline void ALGraph<T>::DFSTraaverse(int v){ std::cout << adjlist[v].vertex; visited[v] = 1; ArcNode* p = adjlist[v].firstedge; while (p != NULL) { int j = p->adjvex; if (visited[j] == 0) DFSTraaverse(j); p = p->next; }} template<class T>inline void ALGraph<T>::BFSTraverse(int v){ int visited[MaxSize] = { 0 };// 顶点是否被拜访的标记 queue<T> Q; Q.front = Q.rear = -1; // 初始化队列 std::cout << adjlist[v].vertex; visited[v] = 1; Q.data[++Q.rear] = v;// 被拜访顶点入队 while (Q.front != Q.rear) { v = Q.data[++Q.front]; // 对头元素出队 ArcNode* p = adjlist[v].firstedge; while (p != NULL) { int j = p->adjvex; if (visited[j] == 0) { std::cout << adjlist[j].vertex; visited[j] = 1; Q.data[++Q.rear] = j; } p = p->next; } }}// main.cpp#include"graph.h"using namespace std;int main(){ int arry[] = { 1,2,3,4,5 }; ALGraph<int> graph(arry, 5, 7); graph.BFSTraverse(3); cout << endl; graph.DFSTraaverse(3); system("pause"); return 0;}邻接矩阵#include <stdio.h>#include <stdlib.h>#define maxv 10#define max 10typedef char elem;typedef int elemtype;#include "queue.h"#include "mgraph.h"void main(){ mgraph g; printf("1.初始化函数测试:\n"); initial(g); printf("2.创立函数测试:\n"); create(g); printf("3.输入函数测试:\n"); printg(g); printf("4.输入顶点度函数测试:\n"); degree(g); printf("5.深度优先遍历函数测试:\n"); dfstraverse(g); printf("6.广度优先遍历函数测试:\n"); bfs(g);}//有向图的邻接矩阵,顶点数据为字符型typedef struct MGraph{ elem vexes[maxv];//顶点表 int edges[maxv][maxv];//邻接矩阵 int n,e;//顶点数n和边数e}mgraph;bool visited[maxv];//拜访标记数组void initial(mgraph &g)//初始化函数{ int i,j; g.e=0; g.n=0; for(j=0;j<maxv;j++) g.vexes[j]=0;//建设顶点表 for(i=0;i<maxv;i++) { for(j=0;j<maxv;j++) { g.edges[i][j]=0;//初始化邻接矩阵 } }}int locate(mgraph g,elem u)//查找顶点对应的数组下标值{ for(int i=0;i<g.n;i++) { if(g.vexes[i]==u) return i; } return -1;}void create(mgraph &g)//创立图的邻接矩阵存储{ int i,j,k; elem u,v; printf("请输出有向图的顶点数:"); scanf("%d",&g.n); printf("请输出有向图的弧数:"); scanf("%d",&g.e); fflush(stdin);//清空缓存中的数据 printf("请输出字符型顶点数据,如ABCD:"); for(j=0;j<g.n;j++) scanf("%c",&g.vexes[j]);//建设顶点表 fflush(stdin); printf("请输出弧的信息,格局:弧尾,弧头\n"); for(k=0;k<g.e;k++) { scanf("%c,%c",&u,&v); i=locate(g,u); j=locate(g,v); g.edges[i][j]=1; fflush(stdin); }}void printg(mgraph g)//输入有向图的邻接矩阵{ int i,j; printf("输出图的邻接矩阵存储信息:\n"); printf("顶点数据:\n"); for(i=0;i<g.n;i++) printf("%d:%c\n",i,g.vexes[i]); printf("邻接矩阵数据:\n"); for(i=0;i<g.n;i++) { for(j=0;j<g.n;j++) { printf("%3d",g.edges[i][j]); } printf("\n"); }}void degree(mgraph g)//输入顶点的度{ int i,j,in,out; for(i=0;i<g.n;i++) { in=0; out=0; for(j=0;j<g.n;j++) { if(g.edges[i][j]!=0) out++; if(g.edges[j][i]!=0) in++; } printf("顶点%c的出度为%d---入度为%d---度为%d\n",g.vexes[i],out,in,in+out); }}int firstadjvex(mgraph g,int v)//顶点v的第一个邻接顶点{ for(int i=0;i<g.n;i++) { if(g.edges[v][i]==1) return i; } return -1;}int nextadjvex(mgraph g,int v,int w)//顶点v的绝对于w的下一个邻接顶点{ for(int i=w+1;i<g.n;i++) { if(g.edges[v][i]==1) return i; } return -1;}void dfs(mgraph g,int v)//遍历一个连通重量{ int w; visited[v]=true; printf("%c ",g.vexes[v]); for(w=firstadjvex(g,v);w>=0;w=nextadjvex(g,v,w)) { if(!visited[w]) dfs(g,w); }}void dfstraverse(mgraph g)//深度优先遍历{ int v; for(v=0;v<g.n;v++) visited[v]=false;//标记拜访数组初始化 for(v=0;v<g.n;v++) { if(!visited[v]) dfs(g,v); }}void bfs(mgraph g)//广度优先遍历{ int u=0,v=0,w=0; queue q; for(v=0;v<g.n;v++) visited[v]=false; initqueue(q); for(v=0;v<g.n;v++) { if(!visited[v]) { visited[v]=true; printf("%c ",g.vexes[v]); enqueue(q,v); while(!queueempty(q)) { dequeue(q,u); for(w=firstadjvex(g,u);w>=0;w=nextadjvex(g,u,w)) { if(!visited[w]) { visited[w]=true; printf("%c ",g.vexes[w]); enqueue(q,w); } } } } } destroyqueue(q);}typedef struct{ elemtype *base;//动态分配存储空间 int front;//头指针,若队列不空指向队列头元素 int rear;//尾指针,若队列不空指向队列尾元素的下一个地位}queue;void initqueue(queue &q)//初始化队列{ q.base=new elemtype[max];//调配存储空间 if(!q.base) { printf("队列调配失败\n"); exit(-2); } else q.front=q.rear=0;}int queueempty(queue q)//判断队列是否为空{ if(q.front==q.rear) return 1; else return 0;}void enqueue(queue &q,elemtype e)//入队列操作{ if((q.rear+1)%max==q.front) { printf("队满,无奈插入新元素!\n"); exit(-2); } else { q.base[q.rear]=e; q.rear=(q.rear+1)%max; }}void dequeue(queue &q,elemtype e)//出队列操作{ if(q.front==q.rear)//判断队列是否为空 { printf("空队列,无奈删除头元素!\n"); exit(-2); } else { e=q.base[q.front]; q.front=(q.front+1)%max; }}void destroyqueue(queue &q)//销毁队列{ free(q.base); q.base=NULL; q.front=0; q.rear=0; printf("\n");}十字链表/************************************************************************************ 十字链表构造:次要针对的是有向图进行的存储优化对于有向图来说,邻接表是有缺点的。关怀了出度问题,想理解入度就必须要遍历整个图能力晓得,反之,逆邻接表解决了入度却不了角出度的状况。有没有可能把邻接表与逆邻接表联合起来呢?答案是必定的,就是把它们整合在一起。所以呈现了十字链表构造。顶点构造:[data | firstin | firstout ]其中firstin示意入边表头指针,指向该顶点的入边表中的第一个结点firstout示意出边表头指针,指向该顶点的出边表中的第一个结点。弧构造:[tailvex | headvex | headlink | taillink]其中tailevex是指弧终点在顶点表中的下标,headvex相似。headlink是指入边表指针域,指向起点雷同的下一条边,taillink相似。如果是网,可再减少一个权值域。**********************************************************************************/#include <iostream>#include <string>#include <queue>using namespace std; #define MAXVEXSIZE 10#define SUCCESS 1#define UNSUCCESS 0typedef int Status;int visited[MAXVEXSIZE]; //批示顶点是否被拜访 typedef string VertexType; //顶点类型typedef struct ArcType { int tailvex; //弧头下标 int headvex; //弧尾下标 int weight; //权值 ArcType* headlink; //指向下一个同一弧头的弧 ArcType* taillink; //指向下一个同一弧尾的弧}ArcType;typedef struct { VertexType data; ArcType* firstin; //指向第一条入弧 ArcType* firstout; //指向第一条出弧 }VertexNode;typedef VertexNode CrossList[MAXVEXSIZE]; typedef struct { CrossList crossList; //十字链表 int iVexNum; int iArcNum; }CrossGraph; //由顶点值得到顶点索引int GetIndexByVertexVal( const CrossGraph& G, VertexType val ){ for ( int i = 0; i < G.iVexNum; ++i ) { if ( val == G.crossList[i].data ) return i; } return -1;} //创立有向图Status CreateCrossGraph( CrossGraph& G ){ cout << "输出顶点个数以及边数:"; cin >> G.iVexNum >> G.iArcNum; cout << "请输出" << G.iVexNum << "个顶点:"; for ( int i = 0; i < G.iVexNum; ++i ) { cin >> G.crossList[i].data; G.crossList[i].firstin = NULL; G.crossList[i].firstout = NULL; } cout << "请输出由两点形成的边(" << G.iArcNum << "条):"; for ( int i = 0; i < G.iArcNum; ++i ) { VertexType first; VertexType second; cin >> first >> second; int m = GetIndexByVertexVal( G, first ); int n = GetIndexByVertexVal( G, second ); if ( m == -1 || n == -1 ) return UNSUCCESS; ArcType* pArc = new ArcType; memset( pArc, 0, sizeof(ArcType) ); pArc->headvex = m; pArc->tailvex = n; pArc->weight = 0; //权值临时不必 pArc->taillink = G.crossList[m].firstout;//表头插入法 G.crossList[m].firstout = pArc; pArc->headlink = G.crossList[n].firstin;//表头插入法 G.crossList[n].firstin = pArc; } return SUCCESS;} //求顶点的度,在十字链表中还是挺不便的,因为其是邻接表与逆邻接表的结合体int GetVertexDegree( const CrossGraph& G, VertexType val ){ int m = GetIndexByVertexVal( G, val ); //失去顶点的在顶点表中的索引 if ( -1 == m ) return -1; int TD = 0; //先求出度 ArcType* pArcOut = G.crossList[m].firstout; while ( pArcOut ) { ++TD; pArcOut = pArcOut->taillink; } //再累退出度 ArcType* pArcIn = G.crossList[m].firstin; while( pArcIn ) { ++TD; pArcIn = pArcIn->headlink; } return TD;} //深度优先遍历void DFS( const CrossGraph& G, int i ){ cout << G.crossList[i].data << " "; visited[i] = true; ArcType* pArc = G.crossList[i].firstout; while( pArc ) { int iVex = pArc->tailvex; if ( !visited[iVex] ) { DFS( G, iVex ); } pArc = pArc->taillink; }}void DFSTraverse( const CrossGraph& G ){ for ( int i = 0; i < G.iVexNum; ++i ) { visited[i] = false; } for ( int i = 0; i < G.iVexNum; ++i ) { if ( !visited[i] ) { DFS( G, i ); } }} //广度优先遍历void BFSTraverse( const CrossGraph& G ){ for ( int i = 0; i < G.iVexNum; ++i ) { visited[i] = false; } queue<int> Q; for ( int i = 0; i < G.iVexNum; ++i ) { if ( !visited[i] ) { cout << G.crossList[i].data << " "; visited[i] = true; Q.push( i ); while ( !Q.empty() ) { int iVex = Q.front(); Q.pop(); ArcType* pArc = G.crossList[iVex].firstout; while ( pArc ) { int j = pArc->tailvex; if ( !visited[j] ) { cout << G.crossList[j].data << " "; visited[j] = true; Q.push(j); } pArc = pArc->taillink; } } } }} //销毁图void DestroyGraph( CrossGraph& G ){ for ( int i = 0; i < G.iVexNum; ++i ) { ArcType* pArc = G.crossList[i].firstout; while( pArc ) { ArcType* q = pArc; pArc = pArc->taillink; delete q; } G.crossList[i].firstout = NULL; G.crossList[i].firstin = NULL; } G.iVexNum = 0; G.iArcNum = 0;} int main(){ //创立有向图 CrossGraph G; CreateCrossGraph( G ); //深度优先遍历图 cout << "深度优先遍历:" << endl; DFSTraverse( G ); cout << endl << endl; //广度优先遍历图 cout << "广度优先遍历:" << endl; BFSTraverse( G ); cout << endl << endl; //结点的度 cout << "输出求度的结点:"; VertexType v; cin >> v; cout << "度为:" << GetVertexDegree( G, v ) << endl; //销毁有向图 DestroyGraph( G ); return 0;}邻接多重表/*********************************************************************************************** 邻接多重表构造:次要针对的是无向图进行的存储优化如果咱们在无向图的利用中,关注的重点是顶点,那么邻接表是不错的抉择,但如果咱们更关注边的操作,比方对已拜访过的边做标记,删除某一条边等操作,那就意味着,须要找到这条边的两个边表结点进行操作,这其实还是比拟麻烦的。因而,咱们也按照十字链表的形式,对表结点进行一下革新: [ ivex | ilink | jvex | jlink]其中ivex和jvex是与某条边附丽的两个顶点在顶点表中下标。ilink指向附丽点ivex的下一条边,jlink指向附丽点jvex的下一条边,这就是邻接多重表构造。*************************************************************************************************/#include <iostream>#include <string>#include <queue>using namespace std; #define MAXVEXSIZE 10#define SUCCESS 1#define UNSUCCESS 0typedef int Status;int visited[MAXVEXSIZE]; //批示顶点是否被拜访 typedef string VertexType; //顶点类型typedef struct ArcType { int ivex; //该弧附丽的两点的地位 int jvex; // int weight; //权值 ArcType* ilink; //别离指向附丽该弧的下一条弧 ArcType* jlink; }ArcType;typedef struct { VertexType data; ArcType* firstArc; //指向第一条弧 }VertexNode;typedef VertexNode MulAdjList[MAXVEXSIZE]; typedef struct { MulAdjList mulAdjList; //多重邻接表 int iVexNum; int iArcNum; }MulAdjGraph; //由顶点值得到顶点索引int GetIndexByVertexVal( const MulAdjGraph& G, VertexType val ){ for ( int i = 0; i < G.iVexNum; ++i ) { if ( val == G.mulAdjList[i].data ) return i; } return -1;} //创立无向图Status CreateCrossGraph( MulAdjGraph& G ){ cout << "输出顶点个数以及边数:"; cin >> G.iVexNum >> G.iArcNum; cout << "请输出" << G.iVexNum << "个顶点:"; for ( int i = 0; i < G.iVexNum; ++i ) { cin >> G.mulAdjList[i].data; G.mulAdjList[i].firstArc = NULL; } cout << "请输出由两点形成的边(" << G.iArcNum << "条):"; for ( int i = 0; i < G.iArcNum; ++i ) { VertexType first; VertexType second; cin >> first >> second; int m = GetIndexByVertexVal( G, first ); int n = GetIndexByVertexVal( G, second ); if ( m == -1 || n == -1 ) return UNSUCCESS; ArcType* pArc = new ArcType; memset( pArc, 0, sizeof(ArcType) ); pArc->ivex = m; pArc->jvex = n; pArc->weight = 0; //权值临时不必 pArc->ilink = G.mulAdjList[m].firstArc;//表头插入法 G.mulAdjList[m].firstArc = pArc; pArc->jlink = G.mulAdjList[n].firstArc;//表头插入法 G.mulAdjList[n].firstArc = pArc; } return SUCCESS;} //求顶点的度int GetVertexDegree( const MulAdjGraph& G, VertexType val ){ int m = GetIndexByVertexVal( G, val ); //失去顶点的在顶点表中的索引 if ( -1 == m ) return -1; int TD = 0; ArcType* pArc = G.mulAdjList[m].firstArc; while ( pArc ) { ++TD; if ( m == pArc->ivex ) pArc = pArc->ilink; else pArc = pArc->jlink; } return TD;} //深度优先遍历void DFS( const MulAdjGraph& G, int i ){ cout << G.mulAdjList[i].data << " "; visited[i] = true; ArcType* pArc = G.mulAdjList[i].firstArc; while( pArc ) { int iVex = pArc->jvex; if ( !visited[iVex] ) { DFS( G, iVex ); } pArc = pArc->ilink; }}void DFSTraverse( const MulAdjGraph& G ){ for ( int i = 0; i < G.iVexNum; ++i ) { visited[i] = false; } for ( int i = 0; i < G.iVexNum; ++i ) { if ( !visited[i] ) { DFS( G, i ); } }} //广度优先遍历void BFSTraverse( const MulAdjGraph& G ){ for ( int i = 0; i < G.iVexNum; ++i ) { visited[i] = false; } queue<int> Q; for ( int i = 0; i < G.iVexNum; ++i ) { if ( !visited[i] ) { cout << G.mulAdjList[i].data << " "; visited[i] = true; Q.push( i ); while ( !Q.empty() ) { int iVex = Q.front(); Q.pop(); ArcType* pArc = G.mulAdjList[iVex].firstArc; while ( pArc ) { int j = pArc->jvex; if ( !visited[j] ) { cout << G.mulAdjList[j].data << " "; visited[j] = true; Q.push( j ); } pArc = pArc->ilink; } } } }} //销毁图void DestroyGraph( MulAdjGraph& G ){ for ( int i = 0; i < G.iVexNum; ++i ) { ArcType* pArc = G.mulAdjList[i].firstArc; while( pArc ) { ArcType* q = pArc; pArc = pArc->ilink; int m = q->ivex; //在m号顶点下找以后边的前一条边 ArcType* pmArc = G.mulAdjList[m].firstArc; ArcType* pmPreArc = NULL; while ( pmArc != q ) { pmPreArc = pmArc; if ( m == pmArc->ivex ) { pmArc = pmArc->ilink; } else { pmArc = pmArc->jlink; } } if ( !pmPreArc ) { G.mulAdjList[m].firstArc = q->ilink; } else { if ( m == pmPreArc->ivex ) { pmPreArc->ilink = q->ilink; } else { pmPreArc->jlink = q->ilink; } } int n = q->jvex; //在n号顶点下找以后边的前一条边 ArcType* pnArc = G.mulAdjList[n].firstArc; ArcType* pnPreArc = NULL; while ( pnArc != q ) { pnPreArc = pnArc; if ( n == pnArc->ivex ) { pnArc = pnArc->ilink; } else { pnArc = pnArc->jlink; } } if ( !pnPreArc ) { G.mulAdjList[n].firstArc = q->jlink; } else { if ( n == pnPreArc->ivex ) { pnPreArc->ilink = q->jlink; } else { pnPreArc->jlink = q->jlink; } } delete q; } } G.iVexNum = 0; G.iArcNum = 0;} int main(){ //创立有向图 MulAdjGraph G; CreateCrossGraph( G ); //深度优先遍历图 cout << "深度优先遍历:" << endl; DFSTraverse( G ); cout << endl << endl; //广度优先遍历图 cout << "广度优先遍历:" << endl; BFSTraverse( G ); cout << endl << endl; //结点的度 cout << "输出求度的结点:"; VertexType v; cin >> v; cout << "度为:" << GetVertexDegree( G, v ) << endl; cout << "再输出求度的结点:"; cin >> v; cout << "度为:" << GetVertexDegree( G, v ) << endl; cout << "再输出求度的结点:"; cin >> v; cout << "度为:" << GetVertexDegree( G, v ) << endl; //销毁有向图 DestroyGraph( G ); return 0;}边集数组概念边集数组是由两个一维数组形成。一个是存储顶点的信息;另一个是存储边的信息。这个边数组每个数据元素由一条边的终点下标(begin)、起点下标(end)和权(weight)组成。 ...
串基础顺序存储定长数组存储:将实际的串长存放在数组的第0个位置或是使用“ 0 ”作为终结符。 但是串是的顺序存储存在一个潜在的风险——溢出问题。 所以,串的存储空间可以动态的分配。 链式存储类似链表,但是并不是一个链块只存放一个字符,而是多个。若是没有占满的话可以使用“ # ”来结束。 其实链式存储只是在串的连接与串操作时有一定优势,总体来说不然顺序存储。 串操作Code//头文件//kallen 1 #ifndef _HSTRING_H_ 2 #define _HSTRING_H_ 3 #include <iostream> 4 class mString 5 { 6 public: 7 mString(); 8 void newString(const char *ms); 9 ~mString();10 bool isEmpty()const; 11 int lengthString()const; 12 void copyString(const mString &src); 13 friend int compareString(const mString &lhs,const mString &rhs); 14 void clearStirng(); 15 void concatString(const mString &lhs_src,const mString &rhs_src); 16 void subString(const mString &src,int pos,int len); 17 void insertString(int pos,const mString &ms); 18 void deleteString(int pos,int len);19 int indexkfString(const mString &mt,int pos);20 int indexkmpString(const mString &mt,int pos,int *next); 21 void replaceString(int repos,const mString& mt,const mString &mv,int *next); 22 23 void insaftposString(int pps,const mString& mt,const mString& mv);24 void printString()const;25 private:26 char *base;27 int length;28 void nextSt(const mString& mt,int *next);29 };30 31 #endif //_HSTRING_H_// 实现文件#include "hstring.h"mString::mString(){ base = NULL; length = 0;}mString::~mString(){ if (base!=NULL) { delete base; } base = NULL;}void mString::newString(const char *ms){ int i = 0; while(ms[i]!='\0') { ++i; } length = i; base = new char[length + 1]; for (int j = 0;j<length;++j) { base[j] = ms[j]; } base[length] = '\0';}bool mString::isEmpty()const{ if (0 == length) { return true; } else { return false; }}int mString::lengthString()const{ return length;}void mString::copyString(const mString &src){ int i = 0; for (; i<this->length;++i) { base[i] = src.base[i]; }}int compareString(const mString &lhs,const mString &rhs){ int i = 0; while (lhs.base[i]!='\0'&&lhs.base[i]!='\0') { if (lhs.base[i] > rhs.base[i]) { return 1; } else if (lhs.base[i] < rhs.base[i]) { return -1; } else { ++i; } } if (lhs.base[i] =='\0'&&rhs.base[i]!='\0') { return -1; } else if (lhs.base[i] !='\0'&&rhs.base[i]=='\0') { return 1; } else { return 0; }}void mString::clearStirng(){ length = 0;}void mString::concatString(const mString &lhs_src,const mString &rhs_src){ length = lhs_src.length + rhs_src.length; this->base = new char[length+1]; for (int i = 0; i<lhs_src.length; ++i ) { this->base[i] = lhs_src.base[i]; } for (int i = lhs_src.length,j = 0; j < rhs_src.length; ++j,++i ) { this->base[i] = rhs_src.base[j]; } this->base[length] = '\0';}void mString::printString()const{ int i = 0; for (;i<length;++i) { std::cout<<base[i]; } std::cout<<std::endl;}void mString::subString(const mString &src,int pos,int len){ if (src.length != 0 ) { if (pos>src.length) { std::cout<<"The location of pos is illegal!"<<std::endl; } else { if (pos+len-1 <=src.length) { length = len; this->base = new char[length+1]; for(int i = 0; i < length; ++i,++pos) { this->base[i] = src.base[pos-1]; } } else { length = src.length-pos+1; this->base = new char[length+1]; int j = 0; for (;j<length;++j,++pos) { this->base[j] = src.base[pos-1]; } this->base[j] = '\0'; } } } else { std::cout<<"The string is empty!"<<std::endl; }}void mString::deleteString(int pos,int len){ if (0 == length) { std::cout<<"The string is empty!"<<std::endl; } else { if (pos>length) { std::cout<<"The location of pos is illegal!"<<std::endl; } else { if (pos+len-1>=length) //delete all char after pos, //but it don't move any char { int i = pos - 1; for (;i<length;++i) { base[i] = '\0'; } } else //pos+len-1<length,we have to move the char that //from pos+len to length { int i = pos-1,j=pos+len-1,k=length-(pos+len)+1; for (;k>0;--k,++i,++j) { base[i] = base[j]; } for (int m = len,n = length;m>0;--n,--m) { base[n-1] = '\0'; } } } }}void mString::insertString(int pos,const mString &ms)//insert ms before pos{ if (0!=length&&0!=ms.length) { if (pos>length) { std::cout<<"The location of pos is illegal!"<<std::endl; } else { int len = ms.length,i = length-1,j=length+len-1,k = length-pos+1; int m = pos,n=0; base = (char *)realloc(base,(length+len)*sizeof(char)); length = length+len; if (base==NULL) { std::cout<<"Create memory is failed!"<<std::endl; } for (;k>0;--k,--i,--j) { base[j] = base[i]; } base[length]='\0'; for (;n<len;++m,++n) { base[m-1] = ms.base[n]; } } } else { std::cout<<"The string is empty!"<<std::endl; }}int mString::indexkfString(const mString &mt,int pos){ if (length != 0 &&mt.length!=0) { if (pos>length-mt.length+1) { std::cout<<"The location of pos is illegal!"<<std::endl; return 0; } else { int i = 1,j = pos; while(i<=mt.length&&j<=length) { if (mt.base[i-1]==base[j-1]) { ++i; ++j; } else { j = j-i+2; i = 1; //it's wrong if example : i = 1; j =j-i+2; if (j>length-mt.length +1) { break; } } } if (i > mt.length) { return (j-i+1); } else { return 0; } } } else { std::cout<<"The string is empty!"<<std::endl; return 0; }}int mString::indexkmpString(const mString &mt,int pos,int *next){ int i = pos,j = 0; nextSt(mt,next); while(j<mt.length&&i<=length) { if (j == -1||mt.base[j]==base[i-1]) { ++i; ++j; } else { j = next[j]; } } if (j==mt.length) { std::cout<<i-j<<std::endl; return (i - j); } else { std::cout<<"nothing"<<std::endl; return 0; }}void mString::nextSt(const mString& mt,int *next){ int i = 0,j = -1; next[0] = -1; while (i<mt.length) { if (j==-1||mt.base[i]==mt.base[j]) { ++i; ++j; next[i] = j; } else { j = next[j]; } } //for (int i = 0;i<mt.length;++i) //{ //std::cout<<next[i]; //}}void mString::replaceString(int repos,const mString& mt,const mString &mv,int *next){ if (length!=0) { int pos = repos; if (mt.length!=0&&mv.length!=0) { int pps = pos; while(pos!=0) { pos = indexkmpString(mt,pps,next); if (pos!=0) //when pos == 0,maybe execute copy in the head { //example;a = "abcaabcacbc";mt = "bc";mv = "ff"; insaftposString(pos,mt,mv); //but the result is; "fffaaffacff". } pps = pos+mv.length; //from pos+mv.length } } else { std::cout<<"The string of mt or mv is empty!"<<std::endl; } } else { std::cout<<"The main string is empty!"<<std::endl; }}void mString::insaftposString(int pps,const mString& mt,const mString& mv){ if (mt.length<mv.length) { int n = length - (pps+mt.length-1); //the sum of movement int dis = mv.length - mt.length; int k = length+dis-1; int j = length-1; base = (char *)realloc(base,(length+dis)*sizeof(char)); length = length+dis; if (base==NULL) { std::cout<<"Create memory is failed!"<<std::endl; } for (int i = 1;i<=n;++i,--k,--j) { base[k] = base[j]; } base[length] = '\0'; for (int i = 0,j=pps-1;i<mv.length;++i,++j) { base[j] = mv.base[i]; } } else if (mt.length>mv.length) { int n = length - (pps+mt.length-1); int dis = mt.length-mv.length; int i = pps+mv.length-1; int j = pps+mt.length-1; for (int k = pps-1,l = 0;l<mv.length;++l,++k) { base[k] = mv.base[l]; } for (int k=1;k<=n;++k,++i,++j) { base[i]=base[j]; } for (int k=1,l = length-1;k<=dis;++k) { base[l] = '\0'; } length = length-dis; } else { for (int i = 0;i<mv.length;++i,++pps) { base[pps-1] = mv.base[i]; } printString();//inserts without thinks }}串的算符重载//Sting.h#ifndef _STRING_H_#define _STRING_H_#include <iostream>using namespace std;class String{public: String(const char *str = ""); String(const String &other); String &operator=(const String &other); String &operator=(const char *str); bool operator!() const; char &operator[](unsigned int index); const char &operator[](unsigned int index) const; friend String operator+(const String &s1, const String &s2); String &operator+=(const String &other); friend ostream &operator<<(ostream &os, const String &str); friend istream &operator>>(istream &is, String &str); ~String(void); void Display() const; int Length() const; bool IsEmpty() const;private: String &Assign(const char *str); char *AllocAndCpy(const char *str); char *str_;};#endif // _STRING_H_//String.cpp#pragma warning(disable:4996)#include "String.h"#include <string.h>//#include <iostream>//using namespace std;String::String(const char *str){ str_ = AllocAndCpy(str);}String::String(const String &other){ str_ = AllocAndCpy(other.str_);}String &String::operator=(const String &other){ if (this == &other) return *this; return Assign(other.str_);}String &String::operator=(const char *str){ return Assign(str);}String &String::Assign(const char *str){ delete[] str_; str_ = AllocAndCpy(str); return *this;}bool String::operator!() const{ return strlen(str_) != 0;}char &String::operator[](unsigned int index){ //return str_[index]; //non const 版本调用 const版本 return const_cast<char &>(static_cast<const String &>(*this)[index]);}const char &String::operator[](unsigned int index) const{ return str_[index];}String::~String(){ delete[] str_;}char *String::AllocAndCpy(const char *str){ int len = strlen(str) + 1; char *newstr = new char[len]; memset(newstr, 0, len); strcpy(newstr, str); return newstr;}void String::Display() const{ cout << str_ << endl;}int String::Length() const{ return strlen(str_);}bool String::IsEmpty() const{ return Length() == 0;}String operator+(const String &s1, const String &s2){ //int len = strlen(s1.str_) + strlen(s2.str_) + 1; //char* newstr = new char[len]; //memset(newstr, 0, len); //strcpy(newstr, s1.str_); //strcat(newstr, s2.str_); // //String tmp(newstr); //delete newstr; String str = s1; str += s2; return str;}String &String::operator+=(const String &other){ int len = strlen(str_) + strlen(other.str_) + 1; char *newstr = new char[len]; memset(newstr, 0, len); strcpy(newstr, str_); strcat(newstr, other.str_); delete[] str_; str_ = newstr; return *this;}ostream &operator<<(ostream &os, const String &str){ os << str.str_; return os;}istream &operator>>(istream &is, String &str){ char tmp[1024]; cin >> tmp; str = tmp; return is;}串的匹配朴素串匹配(BF算法)朴素串匹配算法的思路是暴力求解。一个一个的找。一旦不匹配就会全退回去。 ...
为了避免自己编译SFML,选择下载编译好的SFML,但这个时候必须选择和SFML完全一样的toolchain版本。 下载SFML和MingW下载SFML for GCC 7.3.0 MinGW (SEH) - 64-bit,解压到合适目录。下载对应的mingw/gcc版本MinGW Builds 7.3.0 (64-bit),解压到合适目录。设置Clion和CmakeLists.txt打开Clion,新建一个项目,设置Toolchains,选择mingw解压后的目录。在CmakeLists.txt中输入以下内容,SFML_DIR改成SFML解压后的实际目录,project名字也根据实际情况修改。 cmake_minimum_required(VERSION 3.16)project(MyProject)set(CMAKE_CXX_STANDARD 17)set(SFML_DIR "YouSFMLPath/SFML-2.5.1/lib/cmake/SFML")find_package(SFML REQUIRED COMPONENTS audio network graphics window system)if (NOT SFML_FOUND) message(FATAL_ERROR "SFML NOT FOUND!")else() message("SFML FOUND!")endif()include_directories(${SFML_INCLUDE_DIR})add_executable(MyProject main.cpp)target_link_libraries(CppGenerativeArtistry sfml-audio sfml-network sfml-graphics sfml-window sfml-system)复制DLL和测试把SFML\bin目录下的所有dll文件复制到clion生成exe的目录,一般是cmake-build-debug在main.cpp中输入以下代码,如果运行成功即OK。 #include <SFML/Graphics.hpp>int main(){ sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!"); sf::CircleShape shape(100.f); shape.setFillColor(sf::Color::Green); while (window.isOpen()) { sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); } window.clear(); window.draw(shape); window.display(); } return 0;}
一维数组一维数组的CRUD有关数组的CRUD其实是非常简单的。 //顺序表的.h文件#include<iostream>using namespace std;const int MAXSIZE = 20;class SqList{private: int *data; int length;public: SqList(); //构造函数 ~SqList(); // 析构函数 void CreatList(int a[], int n); //创建顺序表 void Display(); //输出顺序表中所有元素 int GetLength(); //获取顺序表的长度 bool GetElem(int, int &elem); // 获取顺序表中的某个元素 bool ListInsert(int i, int elem); // 插入元素 bool ListDelete(int i, int &elem); //删除元素};SqList::SqList(){ cout<<"constract ok"<<endl; data = new int[MAXSIZE]; length = 0;}SqList::~SqList(){ cout<<"No"<<endl; delete [] data;}void SqList::CreatList(int a[], int n){ for(int i = 0; i < n; i++){ data[i] = a[i]; } length = n; cout<<"create SqList success!"<<endl;}//获取顺序表的长度int SqList::GetLength(){ return length;}//获取指定元素bool SqList::GetElem(int i, int &e){ if(length == 0 || i < 1 || i > length){ return false; } e = data[i-1]; return true;}//插入元素bool SqList::ListInsert(int i, int e){ if(length == MAXSIZE) //保证插入位置正确 return false; if(i < 1 || i > length+1) return false; if(i < length){ for(int k = length-1; k >= i-1; k--) //将插入位置之后的元素都向后移动一个位置 data[k+1] = data[k]; } data[i-1] = e; //将待插入的元素赋值给插入位置 length++; //将顺序表的长度增加一个 return true;}//删除元素bool SqList::ListDelete(int i, int &e){ if(length == 0) //保证删除位置正确 return false; if(i < 1 || i > length) return false; e = data[i-1]; //将要删除的元素保存给 e if(i < length){ for(int k = i; k < length; k++){ //将删除位置后面的元素都向前移动一个位置 data[k-1] = data[k]; } } length--; //将顺序表的长度删除一个 return true;}void SqList::Display(){ cout<<"display SqList:"; for(int i = 0; i < length; i++){ cout<<data[i]<<"\t"; } cout<<endl;}数组排序https://www.cnblogs.com/still... ...
今天(2020年7月6日)WonderTrader发布了最新版本,有两个重大更新。 v0.4.0更新如下:新增一个选股调度引擎,用于调度应用层的选股策略,得到目标组合以后,提供自动执行服务,暂时只支持日级别以上的调度周期,执行会放到第二天因为新增了选股调度引擎,所以全面重构WtPorter和WtBtPorter导出的接口函数,以便调用的时候区分新增一个独立的执行器模块WtExecMon,并导出C接口提供服务。主要是剥离了策略引擎逻辑,提供单纯的执行服务,方便作为单纯的执行通道,嫁接到其他框架之下Windows下的开发环境从vs2013升级到vs2017,boost1.72和curl需要同步升级两个重大更新简单说明:选股引擎,也叫做时间驱动引擎,主要用于执行定时的较大计算量的任务,比如多因子选股等,所以WonderTrader内部也命名为选股引擎,相应的策略基类也叫做BaseSelStrategy,而策略的API接口也相应叫做SelContext。 选股引擎主要的适用于需要大量计算的场景,比较典型的就是选股场景。选股引擎底层采用异步回调,策略的计算逻辑可以拥有更多的执行时间,而不用担心将行情通道和交易通道阻塞。相对比的,原来提供的Cta引擎,主要是通过事件驱动(ontick/onbar/onschedule)同步回调,要求策略逻辑尽可能简洁,耗时短。选股引擎回测代码示例如下 from wtpy import WtBtEnginefrom Strategies.DualThrust_Sel import StraDualThrustSelif __name__ == "__main__": #创建一个运行环境,并加入策略 engine = WtBtEngine(isCta=False)#isCta主要用于标记是CTA引擎还是SEL引擎 engine.init('.\\Common\\', "configbt.json") engine.configBacktest(202005150900,202006051500) engine.configBTStorage(mode="bin", path="E:/WTP_Data/") engine.commitBTConfig() # 创建一个选股策略,其实是一个DualThrust的变种,通过遍历目标标进行DualThrust的计算逻辑来实现 straInfo = StraDualThrustSel(name='DT_COMM_SEL', codes=["CFFEX.IF.HOT","SHFE.rb.HOT","DCE.i.HOT"], barCnt=50, period="m5", days=30, k1=0.1, k2=0.1) engine.set_sel_strategy(straInfo, time=5, period="min") engine.run_backtest() kw = input('press any key to exit\n') engine.release_backtest()独立执行器是另一个重大更新。独立执行器的意义在于:无论你用何种策略框架产生的信号,你都能够很轻松的调用独立执行器来执行信号。这就从根本上为迁移平台存有顾虑的用户提供了一条捷径,尤其对于那些想要提升自己的管理效率,但是又没有合适的技术框架的用户来说,这绝对是一个巨大的利好。 对于已经有成熟策略在别的平台上实盘的用户,如果有类似的需求:一套策略需要在多个账号上运行,一般平台上只能运行多个策略实例,每个实例对应一个交易账号,但是这样管理效率很低,想要重构原框架,开发能力和精力都不允许,开发完成以后还需要测试,迁移别的平台又意味着全部策略重写,那么独立执行器就能帮你彻底解决这个问题。对于日级别以上的策略类型,特别是选股策略。就可以通过独立执行器就能简单实现调仓的目的。独立执行器调用代码示例如下 from wtpy import WtExecApiif __name__ == "__main__": execMon = WtExecApi() execMon.initialize("logcfgexec.json") execMon.config("config_exec.json") execMon.run() execMon.set_position("CFFEX.IF.2007", 1) input('press any key to exit\n') execMon.release()独立执行器下一阶段的计划将高频策略引擎WtHftEngine导出到python交易通道数据落地wtpy中会提供一个内置的基于flask的服务引擎,在此基础上实现一套webui,方便初学者调用继续完善文档
单文件例子先以单文件编译为例子 hello.cc: #include<iostream>using namespace std;int main() { cout << "Hello World" << endl; return 0;}编译加运行的命令 g++ 'hello.cc' -o 'hello' -Wall -g -O2 -std=c++17 && hello其含义就是将hello.cc源文件编译输出为hello这个可执行文件并运行, 得到一个"Hello World"的输出, 那么这个简单的 g++ 命令背后发生了什么呢? 编译过程上述gcc命令其实依次执行了四步操作: 1.预处理(Preprocessing) 2.编译(Compilation) 3.汇编(Assemble) 4.链接(Linking) 常见后缀含义.c为后缀的文件:c语言源代码文件 .a为后缀的文件:是由目标文件构成的库文件 .cpp为后缀的文件:是c++源代码文件 .h为后缀的文件:头文件 .o为后缀的文件:是编译后的目标文件 .s为后缀的文件:是汇编语言源代码文件 .m为后缀的文件:Objective-C原始程序 .so为后缀的文件:编译后的动态库文件 1.预处理(Preprocessing)预处理阶段做的事情: 宏的替换,还有注释的消除,还有找到相关的库文件,将#include文件的全部内容插入。若用<>括起文件则在系统的INCLUDE目录中寻找文件,若用""括起文件则在当前目录中寻找文件。预处理之后得到的仍然是文本文件,但文件体积会大很多, 生成的文件是 -i 文件, 对hello.cc文件进行预处理的命令 g++ -E hello.cc -o hello.i或者直接调用cpp命令 cpp hello.cc -o hello.i-E是指让编译器在预处理之后就退出,不进行后续编译过程, -o是指指定输出文件名, 在处理成.i文件以后, 文件的体积会大的多, 变成了4w多行的文件 2.编译(Compilation)这里的编译不是指程序从源文件到二进制程序的全部过程,而是指将经过预处理之后的程序转换成特定汇编代码(assembly code)的过程。将.i预处理文件编译为汇编代码的命令是cc1, 使用-S可以直接从.cc文件到汇编代码 ...
PAT甲级1051题目描述1051 Pop Sequence (25分) Given a stack which can keep M numbers at most. Push N numbers in the order of 1, 2, 3, ..., N and pop randomly. You are supposed to tell if a given sequence of numbers is a possible pop sequence of the stack. For example, if M is 5 and N is 7, we can obtain 1, 2, 3, 4, 5, 6, 7 from the stack, but not 3, 2, 1, 7, 5, 6, 4. ...
我们都知道C++多态是通过虚函数表来实现的,那具体是什么样的大家清楚吗?开篇依旧提出来几个问题: 普通类对象是什么布局?带虚函数的类对象是什么布局?单继承下不含有覆盖函数的类对象是什么布局?单继承下含有覆盖函数的类对象是什么布局?多继承下不含有覆盖函数的类对象是什么布局?多继承下含有覆盖函数的类对象的是什么布局?多继承中不同的继承顺序产生的类对象布局相同吗?虚继承的类对象是什么布局?菱形继承下类对象是什么布局?为什么要引入虚继承?为什么虚函数表中有两个析构函数?为什么构造函数不能是虚函数?为什么基类析构函数需要是虚函数?要回答上述问题我们首先需要了解什么是多态。 什么是多态多态可以分为编译时多态和运行时多态。 编译时多态:基于模板和函数重载方式,在编译时就已经确定对象的行为,也称为静态绑定。运行时多态:面向对象的一大特色,通过继承方式使得程序在运行时才会确定相应调用的方法,也称为动态绑定,它的实现主要是依赖于传说中的虚函数表。如何查看对象的布局在gcc中可以使用如下命令查看对象布局: g++ -fdump-class-hierarchy model.cc后查看生成的文件在clang中可以使用如下命令: clang -Xclang -fdump-record-layouts -stdlib=libc++ -c model.cc// 查看对象布局clang -Xclang -fdump-vtable-layouts -stdlib=libc++ -c model.cc// 查看虚函数表布局上面两种方式其实足够了,也可以使用gdb来查看内存布局,这里可以看文末相关参考资料。本文都是使用clang来查看的对象布局。 接下来让我们一起来探秘下各种继承条件下类对象的布局情况吧~ 1. 普通类对象的布局如下代码: struct Base { Base() = default; ~Base() = default; void Func() {} int a; int b;};int main() { Base a; return 0; }// 使用clang -Xclang -fdump-record-layouts -stdlib=libc++ -c model.cc查看输出如下: *** Dumping AST Record Layout 0 | struct Base 0 | int a 4 | int b | [sizeof=8, dsize=8, align=4, | nvsize=8, nvalign=4]*** Dumping IRgen Record Layout画出图如下: ...
title: 数据结构与算法笔记categories: 数据结构与算法[TOC] 第 1 章 绪论1.1 时间复杂度1.2 空间复杂度第 2 章 线性表2.1 概念2.2 顺序表2.2.1 求解最大子数组求连续子数组的最大和一、暴力法:求出所有连续子数组的和,比较大小时间复杂度为O(n^2)#include<iostream>using namespace std;//暴力法:算出所有子数组的和,比较int* FIND_MAXIMUN_SUBARRAY(int* a, int len) { int i = 0, j = 0, max = 0; int* amax = new int[len];//amax[i]:以i开始的最大子数组的和,则整体的最大子数组的和必在amax数组中产生 for (i = 0; i < len; i++) { amax[i] = a[i]; max = a[i]; for (j = i + 1; j < len; j++) { max = max + a[j]; if (max > amax[i]) { amax[i] = max; } } } return amax;}int main() { int a[16] = { 13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 }; int len = sizeof(a)/sizeof(a[0]); int* amax = FIND_MAXIMUN_SUBARRAY(a, len); for (int i = 0; i < len; i++) { cout << amax[i] << " "; } cout << endl; return 0;}二、使用分治策略求解将数组二分,分别求两个字数组的最大子数组和。求包含了中心点的最大子数组和。取上述三个值的最大值返回。*整个过程是一个深度递归的过程,可以推出: ...
总述static 成员变量属于类,不属于某个具体的对象。static 成员变量必须在类声明的外部初始化。static 成员变量是在初始化时分配内存的,程序结束时释放内存。项目内容关键词static访问控制有初始化类外初始化分配内存时机初始化时释放内存时机程序结束时内存分配位置全局数据区----- 一般概念静态成员变量是一种特殊的成员变量,它被关键字static修饰。 class Student {public: string getName() { return name; } static int total;//静态成员变量private: string name;};这个student类声明了一个public的静态成员变量total,用来表明学生的总数,这个变量为这个类所共有,无论创建多少个学生对象,total变量在内存中都只有一个。static 成员变量属于类,不属于某个具体的对象。 和普通的成员变量一样,static 成员变量也有private、protected、public 的访问控制。 初始化static 成员变量必须在类声明的外部初始化(除了一个例外,之后会说到),以刚刚的类为例,其初始化具体形式为: int Student::total=0;类型 类名::变量名=值;(注意不必再加static)初始化时可以赋初值,也可以不赋值。如果不赋值,那么会被默认初始化为 0。全局数据区的变量都有默认的初始值 0,而动态数据区(堆区、栈区)变量的默认值是不确定的,一般认为是垃圾值。被 private、protected、public 修饰的静态成员变量都可以用这种方式初始化。需要注意的是,static 成员变量是在初始化时分配内存的。 不是在类声明时,也不是在创建对象时。因此,没有初始化的static 成员变量无法使用,因为还没分配内存。 访问方法static 成员变量不占用对象的内存,所以只要经过类外初始化,即使不创建对象也可以访问。可以通过对象名访问,也可以通过类名访问,但要遵循 private、protected 和 public 关键字的访问权限限制。//通过类类访问 static 成员变量Student::total = 10;//通过对象来访问 static 成员变量Student stu;stu.total = 20;//通过对象指针来访问 static 成员变量Student *p_stu = new Student();p_stu -> total = 20;可以类内初始化的一个例外类型static const int可以类内初始化,如下。 class Student {public: string getName() { return name; } static const int total=1;//类内初始化private: string name;};但在类内初始化了就不要在类外再进行一次,否则是重复定义。 ...
作者 谢恩铭,公众号「程序员联盟」(微信号:coderhub)。转载请注明出处。原文:https://www.jianshu.com/p/a8c...《C++探索之旅》全系列内容简介前言什么是 C++什么是程序编程语言C++ 与其他语言的比较C++ 的历史C++ 经典书籍总结第一部分第二课预告1. 前言亲爱的读者,你是否对 C++ 感兴趣,但因为 C++ 看起来很难,或者别人对你说 C++ 挺难的,你不知道如何下口,哦,不,是如何下手? 别担心,这个系列教程就是为 C++ 初学者准备的。当然了,此课程也适合进阶。 不可否认,C++ 是一门非常著名的语言。几乎学计算机的同学都难免会接触一下 C++,中国的计算机编程课一般也是先学 C 语言入门,然后学 Java 或 C++。 当然了,现在有的同学第一门编程语言也有直接学 Python 的。可以参看我的《Python探索之旅》 系列教程。 C++ 这门语言的使用很广泛,特别是在游戏开发领域,C++ 的性能和无限可能性使得其几乎独占鳌头。 我们经常听到说 C++ 是 C 语言的后继。实际上,这两门语言虽然有点像,但还是不一样的。C++ 加入了很多新的特性,比如面向对象编程(Object-Oriented Programmation,缩写为 OOP)。 我一直觉得:学编程,第一门语言最好是 C语言。我之前写过一篇文章:第一门编程语言选什么好? 。有兴趣的朋友也可以学习我的 《C语言探索之旅》 系列教程。 当然有的朋友见解不同,觉得 Python,Java 等相对比较好上手的语言更适合。 C,C++,Java 这三门语言是可以做到相辅相成,融会贯通的。事实上,C++ 的发明受了 C 语言的启发,而 Java 的发明又受了 C++ 的启发。 之后我也会写Java探索之旅系列。 个人认为:有了 C 语言的基础,再去学 C++ 和 Java,会轻松很多。当然,如果你有余力学一下汇编语言,那会更有帮助,请参见我的 学习汇编对编程有什么帮助?如何学习 。在这个系列课程中,我们不仅会一点点带大家领略 C++ 的奇妙之处,后面还将带大家来探索一个 C++ 的库:Qt。 ...
栈概念正如标题所述,栈是一种被约束的线性结构。我们在一个线性结构上给出了一个规定:第一个进去的最后一个出来。这就像有一摞书放在地面上,不允许从中间抽出来,只能从上面一本一本拿一样。这样的线性就是栈。 所谓栈:限定仅在表头进行插入删除的线性结构。因为是后进先出(Last In First Out) 不像普通的线性结构有CRUD(增删改查)那样丰富的操作。栈仅仅只有进和出两个操作。 栈的顺序存储压栈Pushbool Push(T data){ if (IsFull() == true) return false; array[++top] = data; return true;}出栈Popbool Pop() { if (IsEmpty() == true) return false; this->temp = array[top--]; return true;}完整代码#pragma onceusing namespace std;/*栈的基本操作(C语言实现) Stack CreateStack(int MaxSize); //生产堆栈,其最大长度是MaxSize int IsFul(Stack S, int MaxSize); //判断栈S是否以满 void Push(Stack S, ElementType item); // 将元素item压入栈 int IsEmpty(Stack S); //判断栈S是否为空 ElementType Pop(Stack S); // 删除并栈顶返回元素*/template <class T> class Stack {public: T * array;//数组实现的静态栈 int maxSize;//数组最大长度 int top;//栈指针 T temp;//暂存变量 Stack(const int maxSize) { //有参构造函数 this->maxSize = maxSize; array = new T[maxSize]; top = -1; //top指针等于-1.表示为空栈 } ~Stack() { delete[] array; } bool IsFull(){ if (this->top == this->maxSize - 1) return true; return false; } bool IsEmpty(){ if (this->top == -1) return true; return false; } bool Push(T data){ if (IsFull() == true) return false; array[++top] = data; return true; } bool Pop() { if (IsEmpty() == true) return false; this->temp = array[top--]; return true; } void clear() { cout << "top =" << this->top << endl; while(top != -1) { Pop(); cout << temp << " "; } cout << "清理成功" << endl; }};两栈共享空间有关两个栈共享空间的问题,最核心的想法就是:它们是在数组的两端,向中间靠拢,top1,top2是栈1和栈2的栈顶指针。只要top1 ! = top2 这两个栈就可以一直共享空间。 ...
Leetcode-347要求按出现频率进行排序:首先我们应该先统计好每个数字出现的次数,放进一个Hash表中。然后按照频率高低进行排序放进容器,最后输出容器中的前K个数。因为优先队列的大根堆特性,把Hash表存进里面便可以完成自动排序,同理我们也可以把它放进vector中然后进行sort是一样的。第一种方法: vector<int> res;unordered_map<int,int> mp;priority_queue<pair<int,int>> pq;for(auto e:nums){ mp[e]++;}for(auto it:mp){ pq.push(make_pair(it.second,it.first));}while(k--){ res.push_back(pq.top().second); pq.pop();}return res;第二种方法: vector<int> res;unordered_map<int,int> mp;vector<pair<int,int>> temp;for(auto e:nums){ mp[e]++;}for(auto it:mp){ temp.push_back(make_pair(it.second,it.first));}sort(temp.begin(),temp.end(),comp);for(int i = 0;i<k;++i){ res.push_back(temp[i].second);}return res;bool comp(pair<int,int> &a,pair<int,int> &b){ return a.first>b.first;}
Leetcode-165如题,这种题目首先要做的肯定是按'.'分割字符串。下面是我总结的两种分割字符串的方法。首先,使用istringstream和getline进行分割: string version1 = "1.0.0.1";vector<int> v1;istringstream s1(version1);string temp;while(getline(s1,temp,'.')){ v1.push_back(stoi(temp));}其次,还有一个傻瓜办法: string version1 = "1.0.0.1";vector<int> v1;string temp;for(int i = 0;i<version1.size();++i){ if(version1[i] != '.') temp += version[i]; else{ v1.push_back(stoi(temp)); temp.clear(); }}if(temp != "") v1.push_back(stoi(temp));下面附上该题的方法: class Solution {public: int compareVersion(string version1, string version2) { vector<int> v1,v2; istringstream s1(version1); istringstream s2(version2); string temp; while(getline(s1,temp,'.')){ v1.push_back(stoi(temp)); } while(getline(s2,temp,'.')){ v2.push_back(stoi(temp)); } while(v1.size()>v2.size()){ v2.push_back(0); } while(v1.size()<v2.size()){ v1.push_back(0); } for(int i = 0;i<v1.size();++i){ if(v1[i] > v2[i]){ return 1; } if(v1[i] < v2[i]){ return -1; } } return 0; }};
二分查找的方法想必大家都耳熟能详,今天想说一下我们在写代码的时候比较容易忽略的小细节。int mid = (low + high)/2;①int mid = low/2 + high/2;②int mid = low + (high - low)/2;③可以想一下这三种方式有什么不同?首先看第一个:这里面隐藏了一个可能出现的问题就是, 当我们的low和high比较大时,二者相加可能已经超过int的取值范围了,因为这种写法并不是一个好的写法。其次:这种写法更不可取了,举个例子吧:low = 3,high = 5。那么mid应为4,但是用上面这种写法之后mid可就大错特错了。因此,我们平常写的时候应该尽量使用第三种写法,而且这种写法的形式和插值查找的mid比较相似。
反转链表剑指 Offer 24. 反转链表难度简单54 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。 示例: 输入: 1->2->3->4->5->NULL输出: 5->4->3->2->1->NULL限制: 0 <= 节点个数 <= 5000思路一: 虽然是链表的练习,但是算法的思路非常多。依旧我之前所说会用到各种办法,不是链表部分只用链表 本题:需要返回一个链表指针,这是一个新的链表。这个新的链表本质上是从旧的链表尾一个一个拿元素。也就是对于旧的链表而言,实际上是一个先进后出的一个情形。所以我们首先想到符合这个特点的就是栈。 从头开始压栈,压完之后一个一个出栈。放入新的链表中; /** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */class Solution {public: ListNode* reverseList(ListNode* head) { if(head == NULL) return NULL; if(head->next == NULL) return head; stack<ListNode*> stack_listnode; while(head != NULL) { stack_listnode.push(head); head = head->next; } ListNode* q = stack_listnode.top(); ListNode* qHead = q; stack_listnode.pop(); while(stack_listnode.size() != 0) { q->next = stack_listnode.top(); stack_listnode.pop(); q = q->next; } q->next = NULL; return qHead; }}; 思路二: ...
重操C++旧业,习惯通常的数据库操作方式,因此抽时间,把JSON-ORM封装了一个C++版,现支持sqlit3与mysql,postgres已经做好了准备。 设计思路我们通用的ORM,基本模式都是想要脱离数据库的,几乎都在编程语言层面建立模型,由程序去与数据库打交道。虽然脱离了数据库的具体操作,但我们要建立各种模型文档,用代码去写表之间的关系等等操作,让初学者一时如坠云雾。我的想法是,将关系数据库拥有的完善设计工具之优势,来实现数据设计以提供结构信息,让json对象自动映射成为标准的SQL查询语句。只要我们理解了标准的SQL语言,我们就能够完成数据库查询操作。 技术选择json库JSON-ORM 数据传递采用json来实现,使数据标准能从最前端到最后端进行和谐统一。我选择了rapidjson,但rapidjson为了执行效率,直接操作内存,并且大量使用std::move,在使用的时候有很多限制,一不小心还会出现内存访问冲突。 因此我封装了它,提供了一个容易操作的Rjson代理类。 数据库通用接口应用类直接操作这个通用接口,实现与底层实现数据库的分离。该接口提供了CURD标准访问,以及批量插入和事务操作,基本能满足平时百分之九十以上的数据库操作。 class Idb { public: virtual Rjson select(string tablename, Rjson& params, vector<string> fields = vector<string>(), int queryType = 1) = 0; virtual Rjson create(string tablename, Rjson& params) = 0; virtual Rjson update(string tablename, Rjson& params) = 0; virtual Rjson remove(string tablename, Rjson& params) = 0; virtual Rjson querySql(string sql, Rjson& params = Rjson(), vector<string> filelds = vector<string>()) = 0; virtual Rjson execSql(string sql) = 0; virtual Rjson insertBatch(string tablename, vector<Rjson> elements) = 0; virtual Rjson transGo(vector<string> sqls, bool isAsync = false) = 0; };底层数据库访问实现现在已经实现了sqlit3与mysql的所有功能,postgres与oracle也做了技术准备,应该在不久的将来会实现(取决于是否有时间),mssql除非有特别的需求,短期内不会去写。 我选择的技术实现方式,基本上是最底层高效的方式。sqlit3 - sqllit3.h(官方的标准c接口);mysql - c api (MySQL Connector C 6.1);postgres - pqxx;oracle - occi;mssql - ? ...
鄙者在本站发表多篇对于GUI的文章,虽然有些评论,但多是保守派 --- 所谓的保守派,就是以现存的GUI库、各种技术为尊,多数人缺乏创新思想,不敢创新,或者认为其代价太大。 另外,我的文章写的不好,考虑不周全,求学态度不谦虚,也是因素。 对于程序设计语言,相信本站的同学都不陌生,可能你会几门甚至十几门程序设计语言,可能读者也时不时看一下TOIBE排名,了解前100名都是哪些程序设计系统。 对于各种程序设计系统的发明创造,笔者不敢一概而论其发明原因 --- 每种可能都有一个原因,一个目的。比如C语言的发明,可能是因为不同的机器使用不同的汇编太麻烦,统一使用C语言写操作系统更为简单,Unix和C是同时流行的,互相成就;PHP的发明则是为了写网页服务端程序更简单... 当然,有很多通用型程序设计系统。如果单轮关键字、语法,一种程序设计系统的关键词可能只有100个,但是其程序库(函数库、类库)则是五花八门,无所不包的。 如果用一句话概括我的发明 --- 一种以图形界面(UI、GUI)设计为核心的分布式程序设计系统。如果用现存的系统作比喻,那就是和现存的Web有类似的结构,http服务器--GUI服务器,javascript+html -- 笔者自创的GUI描述。 另外,笔者使用C++实现,除了教给用户一套分布式程序设计方法,还提供一个解释执行GUI描述的exe程序 --- 这个程序类似于浏览器解释执行html和javascript,只是我们的GUI描述更为简单。 我们的GUI服务器也很简单,总体上类似与http服务器的get请求、post请求模式,我们提供给用户几个用于编程的API函数,用户简单调用就能实现一个GUI服务器,并在其中添加各种功能函数,便于GUI在描述中调用。 使用者甚至不需要懂tcp/ip以及socket网络编程,只需要简单的添加自己的功能函数在GUI描述中调用即可。 当然,服务器可能做的很复杂 -- 多线程,erlang协程... 各种不同的服务器,有的专注于高并发高连接。初期,我们并不指望能一步做到最好 -- 一个简单的C++、tcp服务器程序,封装起来并不困难。 ; 变量定义,下文中所有部分可以直接使用。@vara=12 b="helo world" c=12.5 server="gui://edu.cn"end; 图形界面描述。; 每一个图形元素可指定处理过程;在子元素对应的屏幕范围内,其处理过程会覆盖父元素的处理过程;; 对于兄弟元素,如果有重叠,重叠区域内,z-index大的会覆盖小的。@gui; z-index属性决定绘制图形元素的次序,数字大的后绘制,可能会覆盖z-index小的图形元素。 ; top left width height 通过百分比定位而不是像素定位1 WINDOW name="first" top=0.1 left=0.2 width=0.5 height=0.5 image="D:\test 1.bmp" z-index=12 REGTANGLE name="f1" top=0.1 left=0.1 width=0.2 height=0.22 A href="file://d/second.gui" caption="你好"2 A href="file://d/third.gui" caption="测试"2 REGTANGLE name="f2" top=0.5 left=0.6 width=0.4 height=0.42 TEXT name="t1" caption="点击以下文字运行示例程序" 1 WINDOW2 BUTTON name="b1" caption="按钮"2 PANEL2 TABVIEW2 TEXT caption=b2 PANEL3 IMAGE2 TEXT caption="Hello world"end; 初始化过程@init; 默认连接本地的gui服务器,调用函数。a=cpp_fun1(12,"hello"); 指定远程服务器a=cpp_fun1(12,"hello")@"gui://163.com"a=cpp_fun1(12,"hello")@server$first.title=a$first.show;为@gui添加一行节点 这里将在第一个参数指向的控件后,添加一个控件节点。该函数不做控件"层次"检查。; 你可以在第一层控件后添加第三层控件。也就是说,即使你push_back成功了,不表示你的窗口描述就是正确的。;系统在创建并绘制控件时,会做检查,如果检查到你的树形结构中,第一层控件节点有个第三层子节点,报错。$first.push_back("t1",4,"TEXT","caption","在函数里添加控件")end; 控件的键盘、鼠标处理过程; 子控件处理函数会覆盖父控件的处理函数; z-index大的图形控件处理函数可能会覆盖z-index小的处理函数@first.click$first.title="Changed Titile"end@f1.clickend@f2.clickend@var为变量定义部分 ...
最近项目中需要用到G729的编解码,查了下FFmpeg,发现只支持G729的解码,没有编码,连用第三方支持都没有。于是开始了漫长的找G729编码器之路。说ITU官网有,大片大片的英文把我吓得不轻;说VoiceAge有封装,去VA官网也没看见;听说是开源的,赶紧去github上搜,也不知道哪个是官方的,反正引用都不多的样子。————我只是想说,这个g729的编码库真的不好找。也不好用。所以,才有了这篇文章。 下载库最终,我使用了在github上找到的https://github.com/DoubangoTe...。用它的原因,一是我们项目用过doubango,这个名字眼熟。二是,这里面有makefile,可以直接./configure, make, make install三部曲生成。 库编译问题下载好上面的库,make的时候发现编译不过,说符号bad_lsf找不到。库的代码里声明了extern Word16 bad_lsf; ,但是没有定义。解决办法是找一个会编译的.c文件,加如下一行代码: Word16 bad_lsf =0;此处应注意,不要把这一行加到coder.c,decoder.c这样包含main函数的文件里,因为那些是Demo代码,编译库的时候不编译的。 注: 有答案是好,但知道找到答案的方法更重要。以上解决办法,是在上面的github库的issue里找到的。当使用库有问题的时候,这是一个很好的找答案的地方。怎么使用编码库g729库没有文档,甚至,都不能一眼看出来哪个是接口的头文件。不过刚才提到了coder.c是示例代码,当然是看示例代码学咯。核心接口如下: //初始化Init_Pre_Process();Init_Coder_ld8a();Init_Cod_cng();`//处理数据Pre_Process(new_speech, L_FRAME);Coder_ld8a(prm, frame, vad_enable);prm2bits_ld8k( prm, serial);简单来说,编码过程分两步。首先从音频数据中提取出一系列参数prm,然后把参数压缩为二进制串serial。 编码后长度增加,啥?写好代码就动手测试,发现写出来的文件比编码前的纯pcm还长,啥情况?居然变长了,那还要你何用!网上一查,发现这还是个普遍现象,需要改代码。原来,prm2bits_ld8k里的处理有问题。加密后需要80个bit,然而prm2bits_ld8k里的处理有问题,每个bit用一个World16来存放,再加上两个World16的头,所以数据不减反增。修复了这个问题,就OK了。压缩比能达到16:1。编码出来的数据有两个Word16的头,第一个是SYNC_WORD,用于确认这是个正常g729帧。第二个是编码后数据的长度。修复代码如下(增加接口函数bits2prm_ld8k_new, prm2bits_ld8k_new,和辅助函数byte2bit, bit2byte。): Word16 byte2bit(int bitlen,unsigned char * bits,int bitpos){ int i; int bit = 0; Word16 newbyte = 0; Word16 value = 0; unsigned char *p = bits + (bitpos / 8); for (i = 0 ;i< bitlen;i++) { bit = (*p >> (7 - bitpos % 8)) &0x01; if (bit == 1) { newbyte = (1<<(bitlen -i-1)); value |= newbyte; } bitpos++; if(bitpos%8 == 0) p++; } return value;}void prm2bits_ld8k_new( Word16 prm[], /* input : encoded parameters (PRM_SIZE parameters) */ Word16 bits[] /* output: serial bits (SERIAL_SIZE ) bits[0] = bfi bits[1] = 80 */){ int count = 0; Word16 i; *bits++ = SYNC_WORD; /* bit[0], at receiver this bits indicates BFI */ count++; int bitpos = 0; switch(prm[0]){ /* not transmitted */ case 0 : { *bits = RATE_0; count++; break; } case 1 : { *bits++ = (RATE_8000 + 15 ) / 16; count++; for (i = 0; i < PRM_SIZE; i++) { bit2byte( prm[i+1], bitsno[i], bits, bitpos ); bitpos += bitsno[i]; // int2bin(prm[i+1], bitsno[i], bits); // bits += bitsno[i]; } count += (bitpos + 7 )/8;//上取整 break; } case 2 : {#ifndef OCTET_TX_MODE *bits++ = RATE_SID; for (i = 0; i < 4; i++) { int2bin(prm[i+1], bitsno2[i], bits); bits += bitsno2[i]; }#else *bits++ = (RATE_SID_OCTET+ 15 ) / 16; for (i = 0; i < 4; i++) { bit2byte( prm[i+1], bitsno2[i], bits, bitpos ); bitpos += bitsno2[i]; // int2bin(prm[i+1], bitsno2[i], bits); // bits += bitsno2[i]; } *bits++ = BIT_0;#endif break; } default : { printf("Unrecognized frame type\n"); exit(-1); } } return;}void bit2byte(Word16 para,int bitlen,unsigned char * bits,int bitpos){ int i; int bit = 0; unsigned char newbyte = 0; unsigned char *p = bits + (bitpos / 8); for (i = 0 ;i<bitlen;i++) { bit = (para >> (bitlen - i -1) ) &0x01; newbyte = (1 << (7-bitpos%8)); if(bit == 1) *p |= newbyte; else *p &= ~newbyte; bitpos++; if (bitpos % 8 == 0) p++; }}void bits2prm_ld8k_new( Word16 bits[], /* input : serial bits (80) */ Word16 prm[] /* output: decoded parameters (11 parameters) */){ Word16 i; Word16 nb_bits; int bitpos = 0; nb_bits = *bits++; /* Number of bits in this frame */ if(nb_bits == RATE_8000 / sizeof( Word16 )) { prm[1] = 1; prm += 2; for (i = 0; i < PRM_SIZE; i++) { // prm[i+2] = bin2int(bitsno[i], bits); // bits += bitsno[i]; *prm++=byte2bit(bitsno[i],bits,bitpos); bitpos += bitsno[i]; } } else#ifndef OCTET_TX_MODE if(nb_bits == RATE_SID) { prm[1] = 2; prm += 2; for (i = 0; i < 4; i++) { *prm++=byte2bit(bitsno[i],bits,bitpos); bitpos += bitsno[i]; } }#else /* the last bit of the SID bit stream under octet mode will be discarded */ if(nb_bits == RATE_SID_OCTET) { prm[1] = 2; prm += 2; for (i = 0; i < 4; i++) { *prm++=byte2bit(bitsno[i],bits,bitpos); bitpos += bitsno[i]; } }#endif else { prm[1] = 0; } return;}FFmpeg解码试试改了g729的源代码,心里惴惴不安,长度是下去了,格式到底是不是这么约定的还不知道呢。用FFmpeg测试解码,发现报错: ...
不管是在使用FFmpeg时,或者学习FFmpeg的源码时,能调试跟踪代码,都是非常方便的。然而,跟无数的开源库一样,ffmpeg是用makefile来管理的,并不能直接生成xcode的项目。 好在Xcode提供了一个External Build的功能。操作流程如下: 新建一个External Build System的空项目ffmpeg。New->Project->Cross-platform->External Build System 在Info栏填写ffmpeg库的地址导入源代码 构建ffmpeg项目去ffmpeg源码目录,按需求配置./configure。为了调试,需要在参数里增加--enable-debug=3 --disable-optimizations --disable-asm --disable-stripping。然后在Xcode中像普通项目一样构建。 注:我测试的时候直接构建报了一个错,然后去ffmpeg目录下make一次,成功之后,再重新构建OK了。不确定这一步是否必须。 新建测试项目FFmpegTest,加入同一个workspace。这样放: 并设置FFmpegTest依赖项目ffmpeg。设置FFmpetTest依赖ffmpeg的库。ffmpeg生成的库在各个模块的libxxx目录下。并配置FFmpegTest的include,lib路径。 由于依赖的静态库,还需要添加ffmpeg依赖的系统库(红框部分)。 效果现在可以调试了,亲测,可以单步跟踪,可以断点。不过无法通过跳转找到代码。 参考文章:xcode5 导入 makefileffmpeg编译时保留调试信息
为什么有时候对于新人,要求最多一点是所谓的:热爱编程呢?因为编程真的是需要一定的门槛,如果达不到的话,用时间来凑也是可以慢慢积累的。但是如果都“不热爱”的话,那就更不会花时间来达到这种门槛了。常见的门槛1.语法,要求一定的逻辑能力,属于最底层的能力。如果对于一门语言的语法都无法理解,也许能够写代码吧,但是写出来不会调试跟没写也没什么区别2.逻辑,主要是要求把问题细分的逻辑,能够把需求从大问题拆分到小问题,最后再具体到实现的能力。其中比较高级的部分就是把这些问题之间的共性发现,具体的共性的话,就是直接代码层面的复用,抽象的共性就是所谓的设计模式,框架等。3.解码能力,写的比较玄乎,其实就是看别人文档的时候,要翻译成自己理解的那一套东西。每个人对于世界理解是不同的,就是所谓的认知模型,把别人的东西理解之后,用自己最熟悉的方式再演绎一遍,既是一个去魅的过程,也是一个理解加深的过程。4.对代码没有抗拒心理,这个的确就更玄幻了,也是跟“热爱代码”相关性最高的一点。因为会涉及到大量文档的阅读,但凡有点抗拒,觉得够用就行的话,对于自己的提升确实会打折扣
FFmpeg支持很多音视频格式的编解码,其中一部分是通过第三方库支持的。这些第三方支持的编解码,默认是不开启的。比如视频的h264,音频的opus(ffmpeg有个原生的opus,但是编码只支持48K). 以opus为例,讲一下如何让ffmpeg支持第三方库。 首先查看当前版本是否支持opus,如果现在就支持,那就啥都不用改啦。ffmpeg -codecs | grep opus查看ffmpeg源码是否支持opus进入ffmpeg源码目录,执行: ./configure --list-encoders | grep opus 可以看到除了原生的opus外,还支持一个libopus。 安装opus库ffmpeg要使用第三方库,需要先安装第三方库,再配置ffmpeg,在生成ffmpeg库的时候依赖该库。下载libopus源码,并执行./configure, make, make install 三步曲,libopus就安装成功了。 配置ffmpeg库,开启libopus在ffmpeg源码目录,执行./configure时,增加参数:--enable-libopus --enable-encoder=libopus其中libopus就是./configure --list-encoders时看到的那个名字。--enable-libopus 表明生成ffmpeg时,使用libopus库。--enable-encoder 表明开启名字为'libopus'的编码器。 然后make,make install就能更新ffmpeg支持libopus了。 重新执行./configure --list-encoders | grep opus,得到下图: 可以看见encoders和decoders后面都增加了libopus。现在就可以用新的libopus进行非48K的编码了。 如果ffmpeg库还要发给别人呢如果确定ffmpeg的功能不需要改,把ffmpeg和第三方库的头文件,库文件从/usr/local/下找出来发过去就OK。 不过,万一哪天别人需要加个h264呢。别人高高兴兴的编了个带h264的版本替换进去(他不知道现在有opus啊,说不定还有别的什么这个那个库)。完蛋!不知道过了多久,终于有人发现音频功能出问题了。 所以./configure的参数也应该放到版本里。别人加h264的时候,就在这个参数上进行添加。 改完一编译,又完蛋。因为opus库安装在我们本地,还记得吗?别人电脑上没有呀。我们把opus库的include和lib放到版本库下,再在ffmpeg的./configure参数里,指定第三方库的目录。指定方法如下: --extra-cflags=" -I 库路径/include " --extra-cxxflags=" -I 库路径/include"--extra-ldflags="-L 库路径/lib"并指定ffmpeg的安装目录,指定方法如下: --prefix=ffmpeg版本库里的安装目录这样就能愉快的使用配置了第三方库的ffmpeg了。 注:mac系统可能还会遇到动态库签名和绝对路径的问题,目前没研究明白,这里就不说了。
久违的双百,QAQLeetcode-342 题目上说了尽量不要使用循环或者递归,于是想到了之前做过的判断一个数是不是2的幂次方,因此,我们看一哈有没有相似性。 4的幂次方一定是2的幂次方,但是反过来却不成立。例如,8是2的3次方,但并不是4的幂次方。 1转成2进制是1;4转成2进制是100;6转成2进制是110;8转成2进制是1000;16转成2进制是10000; 判断一个数是不是2的幂次方,我们要看这个数转成2进制后是不是所有位数只有1个1。那4的幂次方的规律就是看这个数转成2进制后是不是所有位数只有1个1,并且这个1只能在奇数位上出现。 code: class Solution {public: bool isPowerOfFour(int num) { if(num == 1) return true; if(num <= 3) return false; int count = 0; int i = 1; while(num != 0){ if((num&1) != 0 && (i&1) != 0){ count++; } if((i&1) == 0 && (num&1) != 0){ return false; } num>>=1; i++; } if(count == 1) return true; return false; }};这样子的解决方法相较于循环等方法肯定是省力不少的。
关于常成员函数:const double function()和double function()const;很多人可能将他们两个混为一谈了,但是二者并不完全相同。前者是必须保证返回的值是这个常量(注:变量不可),后者是要求你的函数中不涉及改变类的数据成员的值,可以进行输出等,但不可改变数据成员的值。除了上述关于常函数的论述,还有很多人把静态成员函数和常函数混为一谈,考试也经常进行相关的考试,强调的是常函数是有this指针的。 静态函数、静态数据成员首先:静态函数是没有this指针的,因为浅层可以理解为this指针就是一个具体的对象,因为静态函数不属于具体的对象,故没有this指针。同样的静态数据成员也不属于对象。关于静态数据成员,调用其有两种方式:一种是通过类名::,一种是对象名.形式。同时必须在类外进行初始化。 虚函数:背景:对于虚函数,首先需要明白的是为什么要出现虚函数: 假设有一个基类,含有成员函数A,同时他的派生类中也重新定义了新的A,且基类中的首部与派生类的首部均相同,这样会导致派生类的再次定义将基类的A覆盖了,当我们在主函数中想调用基类的A的话就需要:派生类对象.基类::A(),很显然,当程序较复杂时,这样会降低我们的效率。出现: 为了解决上述问题,虚函数应运而生,我们在想调用基类的函数时,就可以重新定义一个基类指针,使之指向派生类对象,再次调用A函数,便调用的是基类的函数。很多人可能会说我也可以不将其定义为虚函数,但是运用同样的方法来调用基类的函数! 是的,但是那样没有运用多态性。综上所述它能很好的消除了二义性。构造函数调用顺序:在这个地方先给出大致的顺序:1.基类 2.子对象 3.派生类自身 通过这个例子我们可以清楚的发现这个大顺序。更细的: 对于子对象之间的构造函数的调用顺序:取决于子对象的声明顺序,跟他的初始化顺序无关。基类: 取决于在派生类的声明顺序,跟基类的定义顺序和在派生类的初始化顺序无关。关于const的两个易混淆点:1. 在这个题中,由于const在name的前边,说明这个是要求地址不变,分析bcd:对于B,重新赋值就相当于改变了地址,对于CD两个运用的动态开辟空间,改变了地址。正确选项A:该选项只是改变了name数组的第三个变量的值,故并没有改变数组的地址。故正确。2. 对比上下两个题的区别,发现const在此题中的位置发生了改变。他放到了数据类型的前边,这就会使得要求变为p指向的变量不能发生改变! 本文保留所有权利,版权归河北工业大学梦云智软件开发团队所有。未经团队及作者事先书面同意,您不得以任何方式将本文内容进行商业性使用或通过信息网络传播本文内容。本文作者:郝泽龙
(一)为什么要用复制构造函数?把参数传递给函数有三种方法:一种是传值,一种是传地址,一种是传引用。传值与其他两种方式不同的地方在于:当使用传值方式的时候,会在函数里面生成传递参数的一个副本,这个副本的内容是按位从原始参数那里复制过来的,两者的内容是相同的。 当原始参数是一个类的对象时,它也会产生一个对象的副本,此时需要注意:一般对象在创建时都会调用构造函数来进行初始化,但是在产生对象的副本时如果再执行对象的构造函数,那么这个对象的属性又再恢复到原始状态,这就不是我们希望的了。所以在产生对象副本的时候,构造函数不会被执行,被执行的是一个默认的拷贝构造函数。 如果类中没有显示的声明一个拷贝构造函数,那么编译器会为你隐式定义一个默认拷贝构造函数。 复制构造函数何时调用: 一个对象以值传递的方式传入函数体一个对象以值传递的方式从函数返回一个对象需要通过另外一个对象进行初始化(二)为什么要自己写复制构造函数当函数执行完毕要返回的时候对象副本会执行析构函数,如果你的析构函数是空的话,也不会发生什么问题,但一般的析构函数都是要完成一些清理工作,如释放指针所指向的内存空间,这时候可能就会出问题。 b->p = a->p; b中的int \*p指针指向了a中int\*p所指向的申请的内存 两块指针指向同一块堆区内存我们在构造函数中为一个指针变量分配了内存,在析构函数中释放给这个指针所指向的内存空间,在把对象传递给函数至函数结束返回的这个过程中:首先有一个对象的副本产生了。这个副本也有一个指针,它和原始对象的指针是指向同块内存空间的,函数返回时,副本对象的析构函数执行了,释放了副本对象中指针指向的内存空间,但是这个内存空间对于原始对象而言还是有效的。 a->p就成为了空指针,这是第一个问题。当原始对象也被销毁的时候,原始对象的析构函数执行,还会对那块已经释放掉的内存空间再次释放,产生严重错误,这是第二个问题。 解决: 既然传值有这样的问题,那是否可以使用传地址或者传引用的方式解决这种问题呢?事实上传地址和传引用确实可以解决这种问题,但是这并不适用所有的情况,有时我们不希望在函数里面的一些操作会影响到函数外部的变量。 为了解决这种问题,此时就需要自己写拷贝构造函数,拷贝构造函数就是在产生副本对象的时候执行的,在拷贝构造函数里面我们申请一个新的内存空间,这样在副本对象执行析构函数时其释放的就是新的内存空间,从而解决这个问题。 如果不准备使用按值传递对象,那么其实是不需要拷贝构造函数,但是我们如果不写拷贝构造函数,编译器又可能为我们创建一个默认的。那么如何保证一个对象将永远不会被通过按值传递方式传递呢?声明一个私有的拷贝构造函数,甚至不必去定义它,除非成员函数或友元函数需要执行按值传递方式的传递。否则,如果用户试图用按值传递方式传递或返回对象,编译器将会报错。
使用C++实现简单(阉割版)的单链表:实现的功能有:插入节点、删除节点、查找、显示列表。 #ifndef FORWARDLIST_H_#define FORWARDLIST_H_#include <iostream>using std::cout;using std::endl;template<class T>struct ListNode{ T m_data; ListNode<T>* m_pNext;};template<class T>class ForwardList{public: ForwardList(const ListNode<T>* node); ~ForwardList(); //在pos位置节点后边插入节点node void Insert(ListNode<T>* pos, const ListNode<T>* node); //删除位于pos位置的节点 ListNode<T> Delete(ListNode<T>* pos); //删除所有值为value的节点 void Delete(const T& value); //按值查找 ListNode<T>* Find(const ListNode<T>* node); //按位置查找 ListNode<T>* Find(int idx); //显示整个列表 void PrintList(); int size() { return count; } //返回头节点 ListNode<T>* Head() { return m_pHead; }private: ListNode<T>* m_pHead; int count;};template<class T>ForwardList<T>::ForwardList(const ListNode<T>* node){ m_pHead = new ListNode<T>; m_pHead->m_data = node->m_data; m_pHead->m_pNext = nullptr; count = 1;}template<class T>ForwardList<T>::~ForwardList(){ ListNode<T>* pos; while (m_pHead != nullptr) { pos = m_pHead; m_pHead = m_pHead->m_pNext; delete pos; }}template<class T>void ForwardList<T>::Insert(ListNode<T>* pos, const ListNode<T>* node) { ListNode<T>* temp = new ListNode<T>; temp->m_data = node->m_data; temp->m_pNext = pos->m_pNext; pos->m_pNext = temp; count++;}template<class T>ListNode<T> ForwardList<T>::Delete(ListNode<T>* pos) { ListNode<T> ret; ListNode<T>* temp; temp = m_pHead; if (temp == pos) { m_pHead = m_pHead->m_pNext; ret.m_data = temp->m_data; ret.m_pNext = nullptr; delete temp; count--; return ret; } while (temp->m_pNext != pos) { temp = temp->m_pNext; } ret.m_data = temp->m_pNext->m_data; ret.m_pNext = temp->m_pNext->m_pNext; delete temp->m_pNext; temp->m_pNext = ret.m_pNext; ret.m_pNext = nullptr; count--; return ret;}template<class T>void ForwardList<T>::Delete(const T& value) { ListNode<T>* temp; ListNode<T> ret; temp = m_pHead; while (m_pHead != nullptr && m_pHead->m_data == value) { m_pHead = m_pHead->m_pNext; delete temp; count--; temp = m_pHead; } while (temp != nullptr) { if (temp->m_pNext != nullptr && temp->m_pNext->m_data == value) { ret.m_pNext = temp->m_pNext; temp->m_pNext = temp->m_pNext->m_pNext; delete ret.m_pNext; count--; } else { temp = temp->m_pNext; } }}template<class T>ListNode<T>* ForwardList<T>::Find(const ListNode<T>* node){ ListNode<T>* pos = m_pHead; while (pos != nullptr && pos->m_data != node->m_data) { pos = pos->m_pNext; } return pos;}template<class T>ListNode<T>* ForwardList<T>::Find(int idx) { if (idx >= count || idx < 0) { cout << "索引溢出!\n"; return nullptr; } ListNode<T>* pos = m_pHead; int i = 0; while (i < idx) { pos = pos->m_pNext; i++; } return pos;}template<class T>void ForwardList<T>::PrintList(){ ListNode<T>* pos = m_pHead; while (pos != nullptr) { cout << pos->m_data << endl; pos = pos->m_pNext; }}#endif测试代码: ...
题目要求编写程序,用先序递归遍历法(或输入先序及中序递归遍历结点访问序列)建立二叉树的二叉链表存储结构,计算并输出二叉树的结点总数以及树的高度;然后输出其先序、中序、后序以及层次遍历结点访问次序。其中层次遍历的实现需使用循环队列。二叉树结点数据类型建议选用字符类型。 数据结构设计采用C++的模板类,创建队列。每个队列对象中,elem指针用来建立长度为n的数组,n表示队列的容量,front表示队头指针,rear表示队尾指针,c表示队列中当前元素的个数。 采用结构体建立二叉树,其中,data表示数据域,lchild表示左指针,rchild表示右指针,BiT表示二叉树结构体指针类型变量,BiTNode表示二叉树结构体类型变量。 算法设计简要描述先序遍历建立二叉树:递归调用函数,不断读取字符,依次建立左子树和右子树,当读取到‘#’字符时,返回NULL指针,最终返回根结点指针。先序和中序遍历结点访问序列建立二叉树: a. 先由先序序列求得根节点; b. 再由根节点在中序序列中的位置,知:它之前的访问的结点为其左子树结点,它之后访问的为其右子树结点; c. 递归应用a,b两条。程序代码#include <conio.h>#include <iostream>using namespace std;typedef char ElemTp;#define MAX 20template <typename T>class Queue //模板类:队列{public: Queue(); //默认构造函数 Queue(int n); //构造函数,调用函数createQueue(int n),创建长度为n的队列 ~Queue(); //虚构函数 int createQueue(int n); //创建长度为n的队列 int empty(); //判断队列是否为空 int full(); //判断队列是否为满 int enQueue(T e); //元素e入队 int dlQueue(T &e); //元素出队,保存在e中private: T *elem; //建立长度为n的数组 int n; //队列容量 int front; //队头指针 int rear; //队尾指针 int c; //队列当前元素个数};typedef struct node{ ElemTp data; //数据域 struct node *lchild, //左指针 *rchild; //右指针}*BiT,BiTNode; void visit(BiT e) //访问函数 { if (e->data != NULL) //输出二叉树的数据域 cout << e->data << " ";}void preorder(BiT bt) //先序遍历二叉树{ if (bt) { visit(bt); //访问根节点 preorder(bt->lchild); //递归调用遍历左子树 preorder(bt->rchild); //递归调用遍历右子树 }}void midorder(BiT bt) //中序遍历二叉树{ if (bt) { midorder(bt->lchild); //递归调用遍历左子树 visit(bt); //访问根节点 midorder(bt->rchild); //递归调用遍历右子树 }}void lasorder(BiT bt) //后序遍历二叉树{ if (bt) { lasorder(bt->lchild); //递归调用遍历左子树 lasorder(bt->rchild); //递归调用遍历右子树 visit(bt); //访问根节点 }}void layertravel(BiT bt) //层次遍历二叉树{ if (bt == NULL) return; Queue<BiT> q(MAX); //建立队列 q.enQueue(bt); //根节点入队 while (!q.empty()) { q.dlQueue(bt); //根节点出队 visit(bt); //访问根节点 if (bt->lchild) q.enQueue(bt->lchild); //左子树不空,访问左子树 if (bt->rchild) q.enQueue(bt->rchild); //右子树不空,访问右子树 }}BiT crtPreBT() //先序递归遍历建立二叉树算法{ BiT bt; char ch; ch = getchar(); if (ch == '#') //读到‘#’返回NULL指针 return NULL; bt = new BiTNode(); //建立新的二叉树结点 bt->data = ch; bt->lchild = crtPreBT(); //递归建立左子树 bt->rchild = crtPreBT(); //递归建立右子树 return bt;}BiT crtPreMidBT(char *pr, int &i, char *mi, int u, int v) //先序及中序递归遍历结点访问序列建立二叉树算法{ BiT bt; int k; if (u > v) return NULL; bt = new BiTNode(); bt->data = pr[i++]; //pr[i]为子树根节点 for (k = u; k <= v; k++) //mi[u...v]为子树中序序列 { if (mi[k] == bt->data) //查找根节点在中序序列中的位置 break; } bt->lchild = crtPreMidBT(pr, i, mi, u, k - 1); //递归建立左子树 bt->rchild = crtPreMidBT(pr, i, mi, k + 1, v); //递归建立右子树 return bt;}int height(BiT bt) //计算二叉树的高度{ int hl, hr; if (!bt) return 0; hl = height(bt->lchild); //递归计算左子树的高度 hr = height(bt->rchild); //递归计算右子树的高度 return (hl > hr) ? (hl + 1) : (hr + 1); //返回整个二叉树的高度(左、右子树高度较大的值加一)}int nodeNum(BiT bt) //计算二叉树的总结点数{ int nl, nr; if (!bt) return 0; nl = nodeNum(bt->lchild); //递归计算左子树的结点数 nr = nodeNum(bt->rchild); //递归计算右子树的结点数 return nl + nr + 1; //返回整个二叉树的结点数(左、右子树结点数之和加一)}void choose(BiT &bt) //选择建立二叉树的方式{ char num, pre[MAX], mid[MAX]; int i = 0, u = 0, v; cout << "请选择建立二叉树的方式: " << endl; cout << "1. 先序遍历建立二叉树" << endl; cout << "2. 先序和中序遍历建立二叉树" << endl; num = _getch(); switch (num) { case '1': //先序遍历建立二叉树 { cout << "您选择了第一种方式." << endl; cout << "请输入先序遍历的字符序列: " << endl; bt = crtPreBT(); } break; case '2': //先序和中序遍历建立二叉树 { cout << "您选择了第二种方式." << endl; cout << "请输入先序遍历的字符序列: " << endl; cin.getline(pre, MAX); cout << "请输入中序遍历的字符序列: " << endl; cin.getline(mid, MAX); v = strlen(mid) - 1; bt = crtPreMidBT(pre, i, mid, u, v); } break; }}template<typename T>Queue<T>::Queue() { front = rear = -1; c = 0;}template<typename T>Queue<T>::Queue(int n) { createQueue(n);}template<typename T>Queue<T>::~Queue(){ c = 0; front = rear = -1; delete[]elem;}template<typename T>int Queue<T>::createQueue(int n){ if (n <= 0) return 0; this->n = n; c = 0; front = rear = -1; elem = new T[n]; if (!elem) return 0; return 1;}template<typename T>int Queue<T>::empty(){ return c == 0;}template<typename T>int Queue<T>::full(){ return c == n;}template<typename T>int Queue<T>::enQueue(T e){ if (c == n) return 0; //队满,入队失败 rear = (rear + 1) % n; elem[rear] = e; c++; return 1; //入队成功返回1}template<typename T>int Queue<T>::dlQueue(T & e){ if (c == 0) return 0; //队空,出队失败 front = (front + 1) % n; e = elem[front]; c--; return 1; //出队成功返回1}int main(){ int h, num; BiT bt; choose(bt); h = height(bt); cout << "二叉树的高度是: " << h << endl; num = nodeNum(bt); cout << "二叉树的总结点数是: " << num << endl; cout << "先序遍历结点访问次序: " << endl; preorder(bt); cout << endl; cout << "中序遍历结点访问次序: " << endl; midorder(bt); cout << endl; cout << "后序遍历结点访问次序: " << endl; lasorder(bt); cout << endl; cout << "层次遍历结点访问次序: " << endl; layertravel(bt); cout << endl; return 0;}示例(1)程序输入: 先序序列:abc##de#g##f### 程序输出: 二叉树的高度是:5 二叉树的总结点数是:7 先序遍历:a b c d e g f 中序遍历:c b e g d f a 后序遍历:c g e f d b a 层次遍历:a b c d e f g(2)程序输入: 先序序列:ABCDEFG 中序序列:CBEDAFG 程序输出: 二叉树的高度是:4 二叉树的总结点数是:7 先序遍历:A B C D E F G 中序遍历:C B E D A F G 后序遍历:C E D B G F A 层次遍历:A B F C D G E(3)程序输入: 先序序列:ABDF####C#E#G#H## 程序输出: 二叉树的高度是:5 二叉树的总结点数是:8 先序遍历:A B D F C E G H 中序遍历:F D B A C E G H 后序遍历:F D B H G E C A 层次遍历:A B C D E F G H(4)程序输入: 先序序列:# 程序输出: 二叉树的高度是:0 二叉树的总结点数是:0 ...
题目要求已知两个n×n阶方阵A和B的上半三角(不含主对角线上元素)元素全为0,计算并输出这两个矩阵的乘积C=A×B。根据矩阵乘法性质可知,C矩阵的上半角三角元素(不含主对角线上元素)必然全为0。 要求A、B、C三个矩阵均采用行序为主序顺序存储其下半三角元素(含主对角线上元素)。程序先从键盘(或字符文件)输入n值,建立三个矩阵的一维数组动态存储结构,然后从键盘(或字符文件)输入两个半三角矩阵,最后输出计算结果到屏幕上(或另一个字符文件中)。 例如:键盘(或字符文件)输入为: 3 1 2 3 4 5 6 -1 -2 -3 -4 -5 -6 则屏幕(或字符文件)输出为: -1 -8 -9 -38 -45 -36 数据结构设计用一维数组,定义三个矩阵:a、b、c。矩阵a和矩阵b通过文件读入,相乘结果保存在矩阵c中,并输出到文件中保存。 算法设计简要描述下三角矩阵的特点是:i≤j,C语言数组按行序为主序,在元素a(i,j)之前需存储i行元素(行下标0~i-1),在第i行上,a(i,j)之前存储有j个元素(行下标0~j-1),故 程序代码#include <iostream>#include <fstream>#include <iomanip>using namespace std;void Read(int *a, char *filename, int n)//读取文件,a为矩阵名,filename为文件名,n为矩阵阶数{ int i = 0, c; ifstream infile(filename); if (!infile) //打开文件失败输出提示信息 { cout << "file open error!" << endl; exit(0); } while (1) { infile >> c; if (infile.eof()) break; a[i] = c; if (i + 1 == n*(n + 1) / 2) //读取元素个数达到最大,跳出循环 break; i++; } infile.close();}void Write(int *a, char *filename,int n)//写入文件,a为矩阵名,filename为文件名,n为矩阵阶数{ int i = 0, j = 0; ofstream outfile(filename); if (!outfile) //打开文件失败输出提示信息 { cout << "file open error!" << endl; exit(0); } else cout << "存储成功!" << endl; for (i = 0, j = 1; i < n*(n + 1) / 2; i++) //将元素按矩阵形式存到文件中 { outfile << setw(5) << a[i]; if ((i + 1) == j*(j + 1) / 2) { j++; outfile << endl; //每行元素达到最大,输出换行 } } outfile.close();}void Mutiply(int *a, int *b, int *c, int n)//两个矩阵相乘,矩阵c=矩阵a*矩阵b,n为矩阵阶数{ int i, j, k, m = 0, sum = 0; //m记录数组c的下标,sum为矩阵c每个位置元素的计算结果 for (i = 0; i < n; i++) //从 0 到 n 按行扫描 { for (j = 0; j <= i; j++) //从 0 到 i 按列扫描 { for (k = j; k <= i; k++)//从 j 到 i 将矩阵 a 与矩阵 b 相乘 { sum += a[i*(i + 1) / 2 + k] * b[k*(k + 1) / 2 + j]; c[m] = sum; } sum = 0; m++; } }}void Print(int *a,int n) //输出函数,a为矩阵名,n为矩阵阶数{ int i, j; for (i = 0, j = 1; i < n*(n + 1) / 2; i++) { cout << setw(5) << a[i]; if ((i + 1) == j*(j + 1) / 2) //将元素按矩阵形式输出 { j++; cout << endl; } }}int main(){ int *a, *b, *c, n, m = 0, j = 0; char filename1[20], filename2[20], filename3[20];/*filename1记录保存矩阵a的文件名,filename2记录保存 矩阵b的文件名,filename3记录保存矩阵c的文件名*/ cout << "请输入n的值: "; //输入矩阵阶数 cin >> n; a = new int[n * (n + 1) / 2]; //分别建立三个数组,n * (n + 1) / 2为数组中的元素个数 b = new int[n * (n + 1) / 2]; c = new int[n * (n + 1) / 2]; a[n * (n + 1) / 2] = b[n * (n + 1) / 2] = c[n * (n + 1) / 2] = { 0 };//初始化所有元素为0 cout << "请输入保存矩阵A的文件名: "; //输入保存矩阵a的文件名 cin >> filename1; Read(a, filename1, n); //读取矩阵a cout << "请输入保存矩阵B的文件名: "; //输入保存矩阵b的文件名 cin >> filename2; Read(b, filename2, n); //读取矩阵b cout << "矩阵A为: " << endl; Print(a, n); //打印矩阵a cout << "矩阵B为: " << endl; Print(b, n); //打印矩阵b Mutiply(a, b, c, n); //将矩阵a与矩阵b相乘,结果保存在矩阵c中 cout << "矩阵C为: " << endl; Print(c, n); //打印矩阵c cout << "请输入要存储矩阵C的文件名: "; cin >> filename3; Write(c, filename3, n); //将矩阵c保存在文件中 return 0;}示例(1)程序输入: n = 3 矩阵A: 1 2 3 4 5 6 矩阵B: -1 -2 -3 -4 -5 -6程序输出: 矩阵C: -1 -8 -9 -38 -45 -36(2)程序输入: n = 5 矩阵A: 10 -6 5 8 -9 4 2 3 9 10 1 -5 -4 2 7 矩阵B: 5 10 12 -6 -3 -2 1 5 -9 8 -4 -6 2 1 3程序输出: 矩阵C: 50 20 60 -74 -120 -8 -4 59 -108 80 -47 -80 4 23 21 ...
最近开发过程中遇到了JNI的Reference相关问题,了解到Local Reference和Global Reference的相关知识点,整理如下: 背景:项目需求,在Native C/C++层调用上层Android Camera Java接口,把所有的操作包括Camera都沉到Native层去实现。但在JNI调试过程中遇到了android JNI ERROR (app bug): accessed stale local reference的报错。 现象:在Native层创建Java的Camera对象,其对象的指针保存到本地,函数返回到Java层,之后再进入Native层,想通过Native层的Camera对象指针调用相应的方法,但是发现每次都是重新调用Java对象方法后报错。 分析:在Native层创建的Java对象,对象创建后会有一个局部引用指向该对象,当从Native环境返回到Java环境,该局部引用失效,此对象就没有引用计数,Java的内存回收机制会自动回收该对象,第二次再进入Native层访问其之前保存的地址时就会报错。 解决:使用全局引用始终持有该对象的引用使其不被自动回收,请看下面的知识点。 Local Reference局部引用,看如下精简代码: env->NewStringUTF("0"); 在JNI中,每次调用NewObject方法创建一个新的对象都会返回一个对该对象的局部引用(Local Reference),该局部引用只在线程当前的Native环境中有效,返回到Java环境后该引用与对象之间的联系就会被断掉,引用失效,所以我们不能在Native方法中把局部引用缓存用于下一次调用时使用。 局部引用可以无限创建吗 如图: 这里引入局部引用表的概念,每当线程从Java环境进入到Native环境后,JVM就会创建该线程Native环境的局部引用表,用来保存本次Native环境所创建的所有局部引用,每当Native中引用或者新创建一个Java对象,JVM就会局部引用表创建一个局部引用,局部引用表是有大小限制的,最大是512,如果超过限制会报OOM内存泄漏。 Q:那如何才能更好的避免由于局部引用过多造成Native环境中的OOM呢? A:控制局部引用的生命周期,如果需要创建过多的局部引用,可以在Java对象的操作结束后,手动调用DeleteLocalRef函数删除局部引用,该局部引用就会在局部引用表中被移除,避免触发局部引用表的大小限制。 注意:局部引用不是我们平时所理解的代码中的局部变量,局部变量在当前生命周期(例如函数退出)结束后就会失效,而局部引用在函数退出后可能不会失效,它的生命周期是和整个Native上下文环境相关联,只有从Native环境返回到Java环境后局部引用才会失效。 Global Reference全局引用,终于到了最上面讨论的问题了,因为局部引用在Native环境返回到Java环境后就会失效,导致下次进入Native环境后再次使用相对应的Java对象就会出错,所以可以使用全局引用来解决这个问题,全局引用可以始终与Java对象保持联系,使得此对象不会被JVM回收掉,见如下代码: JNIEXPORT jint JNICALL JNI_OnLoad (JavaVM * vm, void * reserved) { jclass tmp = env->FindClass("com/example/company/MyClass"); jclass class = env->NewGlobalRef(tmp); return JNI_VERSION_1_6; }这里需要注意,在不需要使用Java对象后尽量手动调用DeleteGlobalRef()函数来使得引用失效,避免对象始终存在,产生潜在的内存泄漏。 Weak Global Reference虚全局引用与全局引用的区别在于该类型的引用可能随时被JVM回收掉,这里涉及到几个函数: NewWeakGlobalRef();DeleteWeakGlobalRef();isSameObject();在使用虚引用前需要通过isSameObject将其和NULL比较,如果返回TRUE返回true表示已经被JVM回收掉就不能使用了,这里有可能前一行代码判断还是可用,后一行代码时就被JVM回收,解决办法时通过NewLocalRef()获取虚全局引用,避免当时被JVM回收。 参考资料https://www.cnblogs.com/zhong...https://www.cnblogs.com/young...https://stackoverflow.com/que...https://www.ibm.com/developer...https://juejin.im/post/5c19bf... 更多文章请关注 更多文章,请关注我的V X 公 主 号:程序喵大人,欢迎交流。
程序一定要从main函数开始运行吗?本文涉及静态链接相关知识。 对于静态链接先提出两个问题: Q:每个目标文件都有好多个段,目标文件在被链接成可执行文件时,输入目标文件中的各个段如何被合并到输出文件? A:合并相似的段,将所有的.text段合并到输出文件的.text段,将所有的.data段合并到输出文件的.data段。 Q:链接器如何为他们分配在输出文件中的空间和地址? A:这里涉及到程序链接的两个步骤: 空间与地址分配:扫描所有的输入目标文件,获得它们每个段的长度属性和位置,收集输入目标文件中的符号表中的所有符号定义和符号引用,统一放到一个全局符号表中,合并所有的段,计算出输出文件中各个段合并后的长度和位置,并建立映射关系。符号解析与重定位:使用第一步收集到的所有信息,读取输入文件中段的数据及重定位信息,进行符号解析和重定位,调整代码中的地址,将每个段中需要重定位的指令和数据进行“修补”,使他们都指向正确的位置。tips:外部符号指的是目标文件需要引用的符号,但是定义在其它目标文件中,链接前外部符号地址都是000000之类,链接后的可执行文件就可以看见这些外部符号都是有地址的。链接就是把相似的段放在一起,先找到段的偏移地址,再找出符号在段中的偏移,这样可以确定符号在整个可执行程序中的地址。 对于那些需要重定位的符号,都会放在重定位表里,也叫重定位段,即.rel.data、.rel.text等,如果.text段有被重定位的地方,就有.rel.text段,如果.data段有被重定位的地方,就有.rel.data段。可以使用objdump查看目标文件的重定位表。 源代码: int main() { printf("程序喵\n"); return 0;}gcc -c testobjdump -r test.otest.o: file format elf64-x86-64RELOCATION RECORDS FOR [.text]:OFFSET TYPE VALUE0000000000000007 R_X86_64_PC32 .rodata-0x0000000000000004000000000000000c R_X86_64_PLT32 puts-0x0000000000000004RELOCATION RECORDS FOR [.eh_frame]:OFFSET TYPE VALUE0000000000000020 R_X86_64_PC32 .text使用nm也可以查看需要重定位的符号: nm -u test.o U _GLOBAL_OFFSET_TABLE_ U puts对于UND类型,这种未定义的符号都是因为该目标文件中有关于他们的重定位项,在链接器扫描完所有的输入目标文件后,所有这种未定义的符号都应该能在全局符号表中找到,否则报符号未定义错误。 注意:我们代码里明明用的是printf,为什么它却引用了puts的符号呢,因为编译器默认情况下会把只用一个字符串参数的printf替换成puts, 可以节省格式解析的时间,使用-fno-builtin会关闭这个内置函数优化选项,如下: ~/test$ gcc -c -fno-builtin testlink.cc -o test.o~/test$ nm test.o U _GLOBAL_OFFSET_TABLE_0000000000000000 T main U printftips:现在的程序和库通常来讲都很大,一个目标文件可能包含成百上千个函数或变量,当需要用到某个目标文件的任意一个函数或变量时,就需要把它整个目标文件都链接进来,也就是说那些没有用到的函数也会被链接进去,这会导致链接输出文件变的很大,造成空间浪费。 有一个编译选项叫函数级别链接,可以使得某个函数或变量单独保存在一个段里面,都链接器需要用到某个函数时,就将它合并到输出文件中,对于没用到的函数则将他们抛弃,减少空间浪费,但这会减慢编译和链接过程,GCC编译器的编译选项是: -ffunction-sections-fdata-sections可能很多人都会以为程序都是由main函数开始执行和结束的,但其实不是,在main函数调用之前,为了保证程序可以顺利进行,要先初始化进程执行环境,如堆分配初始化、线程子系统等,C++的全局对象构造函数也是这一时期被执行的,全局析构函数是main之后执行的。 Linux一般程序的入口是__start函数,有两个段: .init段:进程的初始化代码,一个程序开始运行时,在main函数调用之前,会先运行.init段中的代码。.fini段:进程终止代码,当main函数正常退出后,glibc会安排执行该段代码。如何指定程序入口在ld链接过程中使用-e参数可以指定程序入口,由于一段简短的printf函数其实都依赖了好多个链接库,我们也不太方便使用链接脚本将目标文件与所有这些依赖库进行链接,所以使用下面这段内嵌汇编的程序来打印一段字符串,这段程序不依赖任何链接库就可以打印出字符串内容,读者如果不懂其中的含义也不用担心,只需要了解下面介绍的链接知识就好。 ...
题目要求建立一个字符文件。从键盘输入字符文件名以及子串,程序首先求取子串的nextval数组,然后用改进KMP算法在文件中查找子串,最后在屏幕上显示输出子串在文件中的匹配次数。若文件中无子串,输出匹配次数为0。 涉及知识点串、改进KMP算法、字符串查找、模式匹配。 数据结构设计用字符数组,定义两个字符串:主串s和子串t。主串通过文件输入保存,子串通过控制台输入。 算法设计简要描述串的模式匹配算法,与子串定位的算法概念相同,即在主串s中查找子串t(称为模式)第一次出现的起始位置。 通过改进KMP算法,可以使得文件一次扫描的条件下,不遗漏地找到所有子串与主串成功匹配的位置下标,并记录成功匹配的总次数。 程序代码#include<iostream>#include<fstream>using namespace std;void read(char *filename,char *s) //读取数据,filename代入不同的文件名,s为主串{ char c; int i = 0; ifstream infile(filename); if (!infile) //打开文件失败输出提示信息 { cout << "file open error!" << endl; exit(0); } while (1) //读入字符,保存在主串中 { infile >> c; if (infile.eof()) break; s[i] = c; i++; } s[i] = '\0'; cout << "主串为: "; //输出主串 for (i = 0; i < strlen(s); i++) cout << s[i]; cout << endl; infile.close();}void get_next(char *t, int next[]) //求next数组,t为子串{ int j, k; next[0] = k = -1; j = 1; while (j <= strlen(t) + 1) //为避免文件回溯,求到next[m]值 { if (k == -1 || t[j - 1] == t[k]) next[j++] = ++k; else k = next[k]; }}void get_nextval(char *t, int nextval[], int next[]) //求nextval数组,t为子串{ int j, k; nextval[0] = k = -1; j = 1; while (t[j]) { if (k == -1 || t[j - 1] == t[k]) if (t[++k] == t[j]) nextval[j++] = nextval[k]; else nextval[j++] = k; else k = nextval[k]; } nextval[strlen(t)] = next[strlen(t)]; //nextval[m]即为next[m]的值}int index_kmp(char *s, char *t, int nextval[], int &num) /*KMP算法:子串的模式匹配,s为主 串,t为子串,num记录匹配的次数*/{ int i, j, flag = 1, location; /*flag为标量,第一次匹配时flag为1,否则flag为 0,location记录第一次匹配的位置下标*/ i = j = 0; while (s[i] && (j == -1 || t[j])) { if (j == -1 || s[i] == t[j]) { i++; j++; } else j = nextval[j]; if (!t[j]) { if (flag == 1) //第一次匹配,location记录匹配的位置下标 { location = i - j; flag = 0; cout << "子串出现的位置分别为: "; } cout << i - j << " "; //i - j为每次匹配的位置下标 j = nextval[strlen(t)]; //匹配成功后,j就从nextval[m]开始继续查找 num++; //num记录匹配的次数 } } if(flag == 1) //flag恒为1表示没有一次成功的匹配 { location = -1; //location为-1表示匹配失败 cout << "匹配失败!"; } cout << endl; return location; //返回第一次匹配的位置下标} int main(){ int nextval[20], next[20], i, location, num = 0; /*location记录第一次匹配的位置, num记录匹配的次数*/ char t[20], s[20], filename[20]; //t为子串,s为主串,filename为文件的名称 cout << "请输入文件的名称: "; cin >> filename; read(filename, s); //从文件中读取主串 cout << "请输入子串: "; cin >> t; //输入子串 while (strlen(t) > strlen(s)) //输入错误,重新输入 { cout << "输入错误(子串长度大于主串长度),请重新输入: "; cin >> t; } get_next(t, next); cout << "next数组为: "; for (i = 0; i < strlen(t) + 1; i++) //next数组多一个元素,用来记录子串回溯的位置 cout << next[i] << " "; get_nextval(t, nextval, next); cout << endl << "nextval数组为: "; for (i = 0; i < strlen(t) + 1; i++) //nextval[m]即为next[m]的值,记录子串回溯的位置 cout << nextval[i] << " "; cout << endl; location = index_kmp(s, t, nextval, num); if (location != -1) //location不为1表示匹配成功 { cout << "子串在主串中第一次出现的起始位置为: " << location << endl; cout << "子串在主串中匹配的次数为: " << num << endl; } return 0;}示例(1)程序输入: 主串s: bacbababadababacambabacaddababacasdsd 子串t: ababaca ...
题目要求从键盘输入中缀表达式,建立操作数与运算符堆栈,计算并输出表达式的求值结果。基本要求:实现 +, -, *, /四个二元运算符以及();操作数范围为0至9。提高要求:实现+, -两个一元运算符(即正、负号);操作数可为任意整型值(程序可不考虑计算溢出)。若两个整数相除,结果只保留整数商(余数丢弃);可不处理表达式语法错误。 涉及知识点栈与队列 数据结构设计采用C++的模板类,分别创建元素类型为整型的操作数栈OPND和字符型的运算符栈OPTR,每个栈对象中,elem指针用来建立长度为n的数组,n表示栈中元素的最大个数,top表示栈顶指针。 算法描述①中缀表达式以'#'结束,'#'字符也做运算符处理。②'('、')'作为运算符处理,括号内运算结束后需要消除括号。③需要建立两个不同类型的栈,整型的操作数栈OPND和字符型的运算符栈OPTR。④算法主要步骤: a. 初始时,OPND置空,'#'作为OPTR栈底元素; b. 依次读入表达式中的每个字符,若是操作数,直接压入OPND栈; c. 若是运算符(记为),PTR栈顶运算符(为)优先级; <,压入OPTR栈,继续读下一个字符; =,脱括号,继续读下一个字符; >,执行运算(退栈),等在栈外(不读下一个字符),即继续和OPTR 栈顶运算符比较优先级(重复进行以上操作),直至能入栈。⑤对于一元运算符(即正、负号),用'P'代表'+','N'代表'-'。 程序代码#include<iostream>using namespace std;template <typename T>class Stack //模板类:栈{public: Stack(); //默认构造函数 Stack(int n); //构造函数,调用函数createStack(int n),创建长度为n的栈 ~Stack(); //虚构函数 int createStack(int n); //创建长度为n的栈 int empty(); //判断栈是否为空 int full(); //判断栈是否为满 int push(T e); //将元素e压栈 int pop(T &e); //元素出栈,保存在e中 T get_top(); //得到栈顶元素 friend int isoperator(char &e);//判断字符e是否为运算符 friend int isp(char &e);//返回栈中运算符的优先级 friend int icp(char &e);//返回栈外运算符的优先级 friend int compute(int x, char a, int y);//求值函数private: T *elem; //建立长度为n的数组 int n; //栈中元素的最大个数 int top; //栈顶指针};template<typename T>Stack<T>::Stack(){ top = -1;}template<typename T>Stack<T>::Stack(int n){ createStack(n);}template<typename T>Stack<T>::~Stack(){ n = 0; top = -1; delete[]elem;}template<typename T>int Stack<T>::createStack(int n){ if (n <= 0) return 0; this->n = n; top = -1; elem = new T[n]; if (!elem) return 0; return 1;}template<typename T>int Stack<T>::empty(){ return top == -1;}template<typename T>int Stack<T>::full(){ return top >= n - 1;}template<typename T>int Stack<T>::push(T e){ if (top >= n - 1) return 0; elem[++top] = e; return 1;}template<typename T>int Stack<T>::pop(T & e){ if (top == -1) return 0; e = elem[top--]; return 1;}template<typename T>T Stack<T>::get_top(){ return elem[top];};int isoperator(char &e) //判断是否为运算符{ if (e == '+' || e == '-' || e == '*' || e == '/' || e == '(' || e == ')' || e == '#' || e == 'P' || e == 'N') return 1; //是运算符返回1 else return 0; //不是运算符返回0}int isp(char &e) //返回栈中运算符的优先级{ switch (e) { case '#': return 0; break; case '(': return 1; break; case '+': case '-': return 2; break; case '*': case '/': return 3; break; case 'P': case 'N': return 4; break; case ')': return 5; break; default: return -1; break; }}int icp(char &e) //返回栈外运算符的优先级{ switch (e) { case '#': return 0; break; case ')': return 1; break; case '+': case '-': return 2; break; case '*': case '/': return 3; break; case 'P': case 'N': return 4; break; case '(': return 5; break; default: return -1; break; }}int compute(int x, char a, int y){ switch (a) { case '+': //计算加法 return x + y; break; case '-': //计算减法 return x - y; break; case '*': //计算乘法 return x * y; break; case '/': //计算除法 return x / y; break; default: return -1; break; }}int g1(){ char a, b, c; int i, j, f, value, firstOpnd, secondOpnd, m; Stack<char> OPTR(MAX); //建立运算符栈 Stack<int> OPND(MAX); //建立操作数栈 OPTR.push('#'); //'#'压栈 cout << "请输入中缀表达式: "; a = getchar(); while (a != '#' || OPTR.get_top() != '#') { if (!isoperator(a)) //不是运算符,即为操作数,操作数入栈 OPND.push(a - 48);//将字符型转化为整型数字 else //是运算符,与栈顶运算符比较优先级大小 { b = OPTR.get_top();//得到栈顶元素 i = isp(b); //栈顶运算符的优先级 j = icp(a); //栈外运算符的优先级 if (i < j) //栈外运算符优先级高,运算符入栈 OPTR.push(a); else { OPTR.pop(b); if (b != '('&&i == j || i > j) { c = OPTR.get_top(); if ((c == '(' || c == '#') && (b == 'P' || b == 'N')) /*c为一元运 算符:正负号*/ { OPND.pop(firstOpnd); //得到操作数 switch (b) { case 'P': //正号 f = firstOpnd * 1; break; case 'N': //负号 f = firstOpnd * (-1); break; } } else //c为二元运算符 { OPND.pop(secondOpnd); //得到第二操作数 OPND.pop(firstOpnd); //得到第一操作数 f = compute(firstOpnd, b, secondOpnd); //计算求值 } OPND.push(f); //求值结果压栈 continue; } } } c = a; a = getchar(); //继续读取字符 while(!isoperator(a) && !isoperator(c)) /*若连续读取字符均为数字,则乘以位权 得到多位数*/ { OPND.pop(m); m = m * 10 + a - 48; OPND.push(m); c = a; a = getchar(); } } OPND.pop(value); return value; //返回表达式的结果}int main(){ int a; a = g1(); cout << "运算结果为: " << a << endl; return 0;}示例(1)程序输入:3+5*(9-5)/10#程序输出:5 ...
类成员函数成员函数的声明必须在类的内部,定义既可以在类的内部也可以在类的外部,成员函数也是一个块类外定义的形式为: 返回值类型 类名::成员函数名(参数列表){}引入 this成员函数通过 this 的额外的隐式参数来访问调用它的那个对象,当我们调用一个成员函数时,用请求该函数的对象地址初始化 this,任何定义为 this 名的参数或变量的行为都是非法的,this 是一个常量指针,不允许改变 this 中保存的地址 引入 const 成员函数紧随参数列表之后的 const 关键字,作用是修改隐式 this 指针的类型,表示 this 是一个指向常量的指针,像这样使用 const 的成员函数被称作常量成员函数 常量对象,常量对象的引用以及指针都只能调用常量或常量成员函数 类相关的非成员函数如果函数在概念上属于类但不定义在类中,一般与类声明在同一个头文件中 构造函数初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数,名字和类名相同,没有返回类型,不能被声明为 const 如果没有显式定义构造函数,编译器会为我们隐式定义一个默认构造函数,又被称作合成的默认构造函数,如果存在类内的初始值,用它来初始化成员,否则,默认初始化该成员 如果没有在构造函数的初始值列表中显式地初始化成员,则该成员在构造函数整体之前执行默认初始化,如果成员是const成员或者引用,必须将其初始化,当成员属于某种类类型且该类没有定义默认构造函数时,也必须将这个成员初始化 =default如果定义了其他构造函数,仍想让编译器合成默认的构造函数,可以通过在参数列表后面写上 =default 来要求编译器生成默认构造函数, =default 既可以和声明一起出现在类内部,也可以作为定义出现在类的外部,在类的内部,默认构造函数是内联的,不在内部,默认情况下不是内联的 构造函数初始值列表负责为新创建的对象的一个或几个数据成员赋初值,构造函数初始值是成员名字的一个列表,每个名字后面紧跟括号括起来的(或者在花括号内的)成员初始值,不同成员的初始化通过逗号分隔开来 访问控制与封装public:成员在整个程序内可以被访问private:成员可以被类的成员函数访问,不能被使用该类的代码访问 使用 class 或 struct 关键字struct 默认访问权限为 public, class 默认访问权限是private 友元类允许其他类或函数访问它的非公有成员的方法是:令其他类或函数称为它的友元,增加一条以friend关键字开头的函数声明语句只能出现在类定义的内部,不是类的成员也不受它所在区域访问控制级别的约束 重载成员函数可变数据成员可以通过在变量的声明中加入mutable关键字来实现修改类的某个数据成员,即使是在一个const成员函数内 返回*this的成员函数一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是一个常量引用 基于const的重载类类型可以把类名作为类型的名字使用,从而直接指向类类型,也可以把类名跟在关键字struct或class后 类的声明class 类名;这种声明有时候被称作前向声明 名字查找与类的作用域名字查找过程:1、在名字所在块中寻找声明语句,只考虑在名字使用之前出现的声明2、如果没找到,继续查找外层作用域3、如果最终没有找到匹配的声明,则报错编译器处理完类中的全部声明后才会处理成员函数的定义在类中,如果成员使用了外层作用域中某个名字,而改名字代表一种类型,则类不能在之后重新定义该名字成员定义中普通块作用域的名字查找过程:1、在成员函数内查找该名字的声明,只在函数使用之前出现的声明才被考虑 2、如果在成员函数内没有找到,则在类内继续查找,类的所有成员都可以被考虑3、如果类内也没有找到该名字的声明,在成员函数定义之前的作用域内继续查找如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数 委托构造函数使用它所属类的其他构造函数执行自己的初始化过程,把自己的一些(或全部)职责委托给了其他构造函数有一个成员初始值列表和一个函数体成员函数列表只有一个唯一的入口,就是类名本身,类名后面紧跟圆括号括起来的参数列表,参数列表必须与类中另外一个构造函数匹配 抑制构造函数定义的隐式转换通过将构造函数声明为explicit加以阻止,将只能以直接初始化的形式使用,编译器不会在自动转换过程中使用该构造函数 聚合类使用户可以直接访问其成员,并且具有特殊的初始化语法形式满足以下条件时,说此类是聚合类:1、所有成员都是public的2、没有定义任何构造函数3、没有类内初始值4、没有基类,也没有virtual函数可以提供一个花括号括起来的成员初始值列表来初始化聚合类的数据成员 字面值常量类数据成员都是字面值类型的聚合类是字面值常量类,或者满足以下条件:1、数据成员都是字面值类型2、类必须至少含有一个constexpr构造函数3、如果一个数据成员含有类内初始值,则内置类型成员的初始值必须是一条常量表达式;或者如果成员属于某种类类型,则初始值必须使用成员自己的constexpr构造函数4、类必须使用析构函数的默认定义,该成员负责销毁类的对象constexpr构造函数可以声明称=default的形式或者是删除函数的形式,否则constexpr函数体一般来说应该是空的 类的静态成员声明静态成员通过在成员的声明之前加上关键字static使得其与类关联在一起,类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据,静态成员函数也不与任何对象绑定在一起,不包含this指针,不能声明为const 使用类的静态成员通过作用域运算符直接访问静态成员 定义静态成员可以在类的内部也可以在类的外部定义静态成员函数,在类的外部定义静态成员时,不能重复static关键字,只出现在类的内部声明语句中,一个静态数据成员只能定义一次 静态成员的类内初始化通常情况下,类的静态成员不应该在类的内部初始化,可以为静态成员提供const整数类型的类内初始值,要求静态成员必须时字面值常量类型的constexpr
QObject是Qt对象模型的核心。该模型的主要功能就是信号和槽机制。 Q_OBJECT宏Q_OBJECT宏用于启用元对象特性,例如动态属性、信号和槽。对于实现信号和槽的任何对象,都必须添加Q_OBJECT宏。 信号和槽Qt的信号和槽机制(Signals & Slots) 信号和槽(Signals & Slots)用于对象之间的通信。信号和槽机制是Qt的核心特性,可能也是与其他框架所提供的特性最不同的部分。信号和槽是由Qt的元对象系统(The Meta-Object System)实现的。 objectNameQt对象可以拥有自己的名称,objectName属性保存对象的名称。它的类型是QString,可以通过函数void setObjectName(const QString &name)设置对象名称。 QObjects将自己组织在对象树中。Qt对象树和QObject的构建/销毁顺序当你使用一个对象作为父对象创建一个新的QObject时,该对象将自动将其添加到父对象的children()列表中。父级拥有对象的所有权。 也就是说,它将在其析构函数中自动删除其子级。你可以通过:findChild()函数根据对象名称或类型查找一个对象,findChildren()函数根据对象名称或类型查找一组对象。 T findChild(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) constQList<T> findChildren(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) constQObject既没有复制构造函数,也没有赋值运算符。QObject为什么禁用复制构造函数和赋值运算符?由于没有复制构造函数,QObject的子类就不能作为要存储在容器类中的值,只能存储指针。
原文:官方帮助文档:https://doc.qt.io/qt-5/signalsandslots.html 信号和槽(Signals & Slots)用于对象之间的通信。信号和槽机制是Qt的核心特性,可能也是与其他框架所提供的特性最不同的部分。信号和槽是由Qt的元对象系统(The Meta-Object System)实现的。 产生背景在GUI编程中,当我们更改一个小部件时,我们通常希望通知另一个小部件。更一般地说,我们希望任何类型的对象都能够彼此通信。 例如,如果用户单击关闭按钮,我们可能希望调用窗口的Close()函数。其他工具包使用回调实现这种通信。回调是指向函数的指针,因此,如果希望某个处理函数通知你某个事件,则需要将一个指向另一个函数(回调)的指针传递给处理函数。然后处理函数在适当的时候调用回调。虽然使用这种方法的成功框架确实存在,但是回调可能不够直观,而且在确保回调参数的类型正确性方面可能会遇到问题。 信号与槽在Qt中,我们有一种替代回调技术的方法:使用信号和槽。当特定事件发生时发出信号。Qt的widgets有许多预定义的信号,但是我们总是可以子类化widgets来添加我们自己的信号。槽是响应特定信号而被调用的函数。Qt的窗口小部件有许多预定义的槽,但通常的做法是子类化窗口小部件并添加自己的槽,以便处理需要的信号。 信号和槽机制是类型安全的:信号的签名必须与接收槽的签名匹配。(事实上,槽的签名可能比它接收到的信号短,因为它可以忽略额外的参数。)由于签名是兼容的,编译器可以帮助我们在使用基于函数指针的语法时检测类型不匹配。基于字符串的信号和槽语法将在运行时检测类型不匹配。信号和槽是松散耦合的:发出信号的类既不知道也不关心哪个槽接收信号。Qt的信号和槽机制确保,如果你将信号连接到槽,该槽将在正确的时间与信号的参数一起被调用。信号和槽可以接受任意数量的任意类型的参数。它们是完全类型安全的。 继承自QObject的所有类都可以包含信号和槽。当对象改变它的一些状态时,就会发出信号。它不知道也不关心是否有谁正在接收它发出的信号。这是真正的信息封装,并确保对象可以作为软件组件使用。 槽可以用来接收信号,但它们也是普通的成员函数。就像一个对象不知道是否有任何东西接收到它的信号一样,一个槽也不知道是否有任何信号连接到它。这确保了可以用Qt创建真正独立的组件。你可以将任意多的信号连接到一个槽,一个信号可以连接到任意多的槽。甚至可以将一个信号直接连接到另一个信号。(这将在第一个信号发出时立即发出第二个信号。) 信号和槽一起构成了一个强大的组件编程机制。 信号当对象的内部状态以某种方式发生变化时,对象的客户端或所有者可能会对此感兴趣,就会发出信号。信号是公共访问函数,可以从任何地方发出,但是我们建议只从定义信号及其子类的类发出信号。 当一个信号被发出时,连接到它的槽通常会立即执行,就像一个普通的函数调用一样。当发生这种情况时,信号和槽机制完全独立于任何GUI事件循环。当所有槽返回后,emit语句后面的代码就会执行。在使用排队连接时,情况略有不同;在这种情况下,emit关键字后面的代码将立即继续执行,槽将在稍后执行。 如果几个槽连接到一个信号,当信号发出时,槽将依次执行。(这个顺序无法保证。) 信号是由moc(元对象编译器)自动生成的,不能在.cpp文件中实现。它们永远不能有返回类型(即使用void)。 关于参数的注意事项:我们的经验表明,如果信号和槽不使用特殊类型,那么它们的可重用性更好。如果QScrollBar::valueChanged()使用一种特殊类型,例如假想的QScrollBar::Range,那么它只能连接到专为QScrollBar设计的槽。将不同的输入小部件连接在一起是不可能的。 槽当连接到槽的信号发出时,就会调用槽。槽是普通的c++函数,可以正常调用。它们唯一的特点是可以将信号连接到它们身上。 因为槽是普通的成员函数,所以它们在直接调用时遵循普通的c++规则。但是,作为槽,任何组件都可以通过信号槽连接调用它们,无论其访问级别如何。这意味着从任意类的实例发出的信号可以导致在不相关类的实例中调用私有槽。 你还可以将槽定义为虚拟的,这在实践中非常有用。 与回调相比,信号和槽的速度稍微慢一些,因为它们提供了更大的灵活性,尽管实际应用的差异并不大。一般来说,通过发出信号来调用槽的速度比使用非虚拟函数直接调用的速度大约慢十倍。这是定位连接对象、安全遍历所有连接(即检查后续接收器在发射过程中没有被销毁)和并以通用方式编组任何参数所需的开销。虽然10倍差距听起来可能很多,但是它比任何新建操作或删除操作的开销要小得多。只要执行后台需要新建或删除字符串、向量或列表操作,信号和槽开销就只占整个函数调用开销的很小一部分。 信号和槽机制的简单性和灵活性非常值得这些开销,用户甚至不会注意到这些开销。
QObject禁用复制构造函数和赋值运算符源于它的性质: 可能具有唯一的QObject :: objectName。 Qt对象可以拥有自己的名称,objectName属性保存对象的名称。那么复制一个Qt对象,如何处理新的对象的名字?在对象层次结构中具有位置。 QObjects将自己组织在对象树中。那么复制一个Qt对象,新的对象应位于何处?可以连接到其他Qt对象,以向它们发出信号或接收它们发出的信号。 QObjects可以connect到其他对象。那么复制一个Qt对象,应该如何将这些连接转移到新的对象中?可以在运行时添加未在C ++类中声明的新属性。那么复制一个Qt对象,原始对象中添加的属性是否应该复制到新对象?由于这些原因,应将Qt对象视为身份而不是值。因此,QObject的拷贝构造函数和赋值操作符是禁用的。
想必大家平时都见过volatile关键字,可是你知道什么时候需要使用volatile关键字吗? 直接看下面代码: int a = 100;while (a == 100) { // code}这段程序编译时,如果编译器发现程序始终没有企图改变a的值,那它可能就会优化这段代码,变成while(true)的死循环使得程序执行的更快,然而编译器有时候也会做过度优化,它有时候可能没有意识到程序会改变a的值,却做了这种优化导致程序没有产生预期的行为。 这里为了产生预期的行为,需要阻止编译器做这种优化,可以使用volatile关键字修饰。 volatile int a = 100;volatile关键字和const关键字相对应,const关键字告诉编译器其修饰的变量是只读的,编译器根据只读属性做一些操作,而volatile关键字告诉编译器其修饰的变量是易变的,同理编译器根据易变属性也会做一些操作。它会确保修饰的变量每次都读操作都从内存里读取,每次写操作都将值写到内存里。volatile关键字就是给编译器做个提示,告诉编译器不要对修饰的变量做过度的优化,提示编译器该变量的值可能会以其它形式被改变。 volatile修饰结构体时,结构体的成员也是volatile的吗 struct A { int data;};volatile A a;const A b;答案是结构体内所有的都是volatile,引用c++标准里的一句话: [Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. See 1.9 for detailed semantics. In general, the semantics of volatile are intended to be the same in C + + as they are in C. ]这里大体可以理解为一个对象是volatile,那对象里所有的成员也都是volatile。其实const和volatile可以理解为是硬币的两面,我们经常听到看到传说中的CV修饰词就是const和volatile关键字。 ...
看一段C语言示例源代码: // test.cc#include <stdio.h>int main() { printf("Hello 程序喵\n"); return 0;}编译运行 $ gcc test.cc$ ./a.outHello 程序喵如图一, 我们平时都会使用gcc来编译程序,这一行简单的命令其实经历了很多复杂的过程: 预处理编译汇编链接首先使用file看一下test.cc文件类型: $ file test.cctest.cc: C source, UTF-8 Unicode text, with CRLF line terminators我们接下来看看这每个过程都做了什么? 预处理 命令: $ gcc -E test.cc -o test.i或者$ cpp test.cc -o test.i再看下test.i的文件类型 $ file test.itest.i: C source, UTF-8 Unicode text这里可以看出预处理后的文件和预处理前的文件类型是相同的,都是文本文件,也可以直接查看test.i的内容,里面代码较多,就不贴上来了。 其实预处理主要操作有这几个: 展开所有#define宏定义,进行文本替换删除程序中所有的注释处理所有的条件编译,#if、#ifdef、#elif等处理所有的#include指令,把这些头文件的内容都复制到引用的源文件中添加行号和文件名标识,方便编译器产生警告及调试信息保留所有的#pragma编译器指令,因为编译器会使用他们编译 命令: gcc -S test.cc -o test.s再查看文件类型 $ file test.stest.s: assembler source, ASCII text如图二,编译过程就是把预处理后的文件进行一系列操作生成相应的汇编文件: 词法分析:又称词法扫描,通过扫描器,利用有限状态机的算法将源码中的字符串分割成一系列记号,如加减乘除数字括号等。语法分析:使用语法分析器对词法分析产生的记号运用上下文无关语法的手段进行语法分析,产生语法分析树。这期间如果表达式不合法(括号不匹配等),就会报错。语义分析:语法分析检查表达式是否合法,语义分析检查表达式是否有意义,如浮点型整数赋值给指针,编译器就会报错。中间语言生成:做一些语法树优化,如6+2=8。目标代码生成及优化:将中间代码生成目标汇编代码。汇编 命令: ...
从刚开始接触 cpp 大家都会学习的一个东西就是如何交换两个变量的值,一般要新设置一个变量来储存中间值,然后进行赋值运算。下面要介绍的方法可以省去新建一个变量的空间,使用的位运算还可以提高效率。 #include<iostream>using namespace std;void swap(int &a,int &b){ a ^= b; b ^= a; a ^= b;}int main(){ int m = 1; int n = 2; swap(m, n); cout << m << " " << n << endl;}这里的'^'就是异或运算符⊕。首先让a = a ⊕ b。b = b ⊕ a,那么 b = b ⊕ a = b ⊕ a ⊕ b,所以b = a。最后,a = a ⊕ b,那么 a = a ⊕ b = a ⊕ b ⊕ a = b。至此,完成了 a 和 b 的交换。 ...
Suppose we've written an application library in Unison, We could do some test to make sure that it works as expected. In this article, we'll walk through writing a simple testcase in Unison withgoogletest. What is googletest?Google's C++ test framework. https://github.com/google/googletestYou'll find the document here https://github.com/google/googletest/tree/master/googletest/docs.It will be a good start to read this https://github.com/google/googletest/blob/master/googletest/docs/primer.md.Build googletest as a shared libraryFirst of all, to work in Unison, we need to build googletest into a shared library. ...
一、文件输入输出与打开关闭部分1、文件的输入与输出▪ C++ 把文件看做字符序列,即文件是由一个一个字符数据顺序组成的。▪ 根据数据的组织形式,文件可分为:➢ 文本文件(ASCII 文件):每个字节存放一个ASCII 代码,代表一个字符;➢ 二进制文件:把内存中的数据,按其在内存中的存储形式原样写到磁盘上; 2、文件的打开与关闭 进行文件的打开与关闭需要用到头文件fstream.h ➢ ifstream :输入文件流类,用于文件的输入;➢ ofstream: : 输出文件 流类,用于文件的 输出;➢ fstream: : 输入输出文件 流类,用于文件的 输入/ 输出;◼ 一旦文件打开,即可用<< 和>> 读写文件中的数据,只是必须用于文件相联系的流代替cin 和cout。 [1] 文件的打开:使用成员函数open(); ;➢ 文件流对象.open( 文件名,打开方式);文件打开方式 含 义ios::in 以输入(读)方式打开文件ios::out 以输出(写)方式打开文件ios::app 打开一个文件使新的内容追添加到文件的末尾ios::ate 打开一个文件查找到文件尾ios::trunc 打开一个文件若它存在,则清除文件所有内容ios::binary 以二进制方式打开文件,缺省时为文本方式ios::nocreate 打开一个已有文件,若该文件不存在,则失败ios::noreplace 若打开的文件已经存在,则打开失败注:在打开文件之后,要判断文件是否打开,在文件操作结束时要及时调用成员函数close()来关闭文件。如out.close(); 将关闭与流out相联系的文件。 比如如下代码: 二、获取现在时间的函数在写获取时间的函数时要包含头文件time.h第一种:运行结果为Today is Saturday, day 30 of May in the year 2020.第二种:
本周主要进行了图书馆系统的设计,用c++学到了很多,周末记录一下 关于c++输入输出流关于这个流,一直比较模糊,故今天整理了一下大体的轮廓 流- 你有没有想过,用什么来比喻程序数据的传输呢? 计算机前辈们很早就想过这个概念,这个概念就是流 信息从外部输入设备(入键盘和磁盘)向计算机内部(即内存)输入。或从内存向外部输出设备(如显示器和磁盘)输出的过程,被形象的比喻称为流 缓冲区系统在主存当中专门开辟的用于存放I/O数据的区域,我们称之为缓冲区(buffer),流可以使缓冲形式的,也可以是非缓冲形式的,对于缓冲流,当我们的缓冲区满或者对缓冲区发出刷新的命令的时候,我们就可以将缓冲区的数据与操作真正实现。我们可以类比成我们修改html文档的过程:当我们修改一次的的时候,往往是在html文档里面修改很多的地方然后去浏览器一次性刷新,我们在代码中修改文档就相当于在缓冲区进行操作,只不过这里没有缓冲区满的情况。windows 系统 的 的全缓冲区的大小是4096个字节,也就是输入到4096字节就不能再输入并且自动刷新缓冲区缓冲区,执行I/O操作c++的“流”那一家子 为了实现信息的内外流动,c++系统定义了I/O类库,其中的每一个类都称作相应的流或流类,用以完成某一方面的功能。通常把一个流类性定义的对象也称为流streambuf用于管理一个流的缓冲区 istream提供输入操作的成员函数 ostream提供输出操作的成员函数 iostream也就是我们经常用的,没有提供新的成员函数,只是将istream类和ostream类组合到一起,称为一个流类库中的战斗机 文件流我们对于输入输出流有一定的基础之后,开始进入文件流的世界 概述在c++中,文件被看作是字符序列,即文件是由一个个的字符数据顺序组成的,是一个字符流,要对文件进行I/O,就必须创建一个流(对象),然后将这个流与文件相互关联,就可以在打开文件之后,对此文件进行读写操作,操作完成之后,再关闭这个文件。 有了这个概念,以及一些列操作,必然有对应的类库 c++提供的文件流类:ifstream、ofstream、fstream他们都在头文件 fstream 中有定义:这些文件的关系也和基本的类库相似,我们可以类比记忆ifstream:继承了istream和fstreambase的操作也就是对于文件的操作 和输入继承ofstream:继承了ostream和fstreambase的操作也就是对于文件的操作和输出继承fstream:以上两者的集成版:文件类中的战斗机文件的读写我们读写文件,是要建立一个fstream对象,将此对象与目标文件联系起来,然后对文件利用函数进行操作。这中间有很多的函数具体的用法,在此不再赘述,请参照网上的其他具体教程信息来学习函数的作用 浅谈面向对象把数据及对数据的操作方法放在一起,作为一个相互依存的整体——对象。对同类对象抽象出其共性,形成类。类中的大多数数据,只能用本类的方法进行处理。类通过一个简单的外部接口与外界发生关系,对象与对象之间通过消息进行通信。程序流程由用户在使用中决定。对象即为人对各种具体物体抽象后的一个概念,人们每天都要接触各种各样的对象,如手机就是一个对象。 关于整体概念的理解,如下图点我——一个很详细的面向对象解释 设计一个程序的步骤多花时间放在对于程序的分析上,我们的思路应该是:我需要实现什么功能,我想办法实现。而不应该是:我能实现什么功能,我怎么把我的功能放到我的程序当中 这次的程序设计过程就是犯了后面的错误,导致最后十分混乱 设计流程: 分析需求,需要的功能,需要的对象种类,画出思维导图把功能划分到具体的对象类当中,然后分析出各个类之间的信息联系,设计接口的传输方式把分析出来的类的结构,对象的种类都写出头文件以及声明相应的函数在主函数里面开始流程,在相应的地方写入相应对象的功能框架设计好之后,用语言去补充相应的功能。内存概述内存指的就是运行时的运存,就是我们电脑中的内存条对应的部分,功能是我们运行程序时需要的内存活动空间。但是我们日常生活中,在我们买手机买电脑的时候,商家常常跟我们说这个手机内存64G、256G…(这实际上指的是硬盘的内存)这也就误导了我们内存的概念,所以以后的概念要区分好。 我们在设计程序的时候,这个内存有什么用呢?存在内存中的数据调用快速而且方便,我们可以把我们需要的数据从硬盘等存储空间提取到内存当中,然后对内存中的数据进行操作,操作完之后在从内存中的数据保存到硬盘当中,实现数据的更新,这一过程可以便捷的、快速的实现我们的操作,由此可以理解,内存的作用,以及内存强悍的电脑运行游戏流畅的原因。 版权声明本文保留所有权利,版权归河北工业大学梦云智软件开发团队所有。未经团队及作者事先书面同意,您不得以任何方式将本文内容进行商业性使用或通过信息网络传播本文内容。本文作者:温宇航
函数函数定义包括:返回类型、函数名字、由0个或多个形参组成的列表以及函数体 函数调用完成两项工作:1、用实参初始化函数对应的形参2、被控制权转移给被调用函数 函数的返回类型不能是数组类型或函数类型,但可以使指向数组或函数的指针 形参和函数内部定义的变量统称为局部变量,同时还会隐藏在外层作用域中同名的其他所有声明 局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且知道程序终止才被销毁,在此期间即使对象所在函数结束执行也不会对它有影响 函数声明(函数原型)如果一个函数永远也不会被用到,那么它可以只有声明没有定义 函数声明与函数定义类似,区别是函数声明无须函数体,用一个分号代替即可 尽量把声明放到头文件中,把定义放到源文件中 参数传递传值参数初始化一个非引用类型的变量,初始值被拷贝给变量。对变量的变动不会影响初始值 指针形参和其他非引用类型一样,执行指针拷贝操作时,拷贝的是指针的值,即指针的值不会改变,指针所指对象的值会改变 传引用参数通过使用引用实参,允许函数改变一个或多个实参的值 拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型根本就不支持拷贝操作,当某种类型不支持拷贝操作时,函数只能通过引用型形参访问该类型的对象 引用类型的形参可以作为输出参数,也可作为输入参数,这样就可实现返回多个值的函数 const 形参和实参当用实参初始化形参时会忽略掉顶层const,所以在函数重载时,const int i = int i 指针或引用形参与 const可以使用非常量初始化一个底层 const 对象,反过来不行,一个普通的引用必须用同类型对象初始化 尽量使用常量引用数组形参不允许拷贝数组、使用数组时通常会将其转换成指针 使用标记指定数组长度类似字符串以'0'结束 使用标准库规范传递指向数组首元素和尾后元素的指针 显式传递一个表示数组大小的形参专门定义一个表示数组大小的形参 main:处理命令行选项int main(int argc, char *argv[]) {. . .}int main(int argc, char **argv[]) {. . .}第二个形参argv是一个数组,它的元素是指向C风格字符串的指针,第一个形参argc表示数组中字符串的数量允许main函数没有return语句直接结束,编译器会隐式插入一条返回0的return语句,返回0表示成功,返回其他值表示失败,非0值的具体含义依机器而定 含有可变形参的函数如果所有的实参类型相同,可以传递一个名为initializer_list 的标准库类型;如果实参的类型不同,可以编写一种特殊的函数,也就是所谓的可变参数模板C++还提供了一种特殊的形参类型(即省略符),可以用它传递可变数量的实参,这种功能一般只用于与C函数交互的接口程序 initializer_list 形参提供的操作为:initializer_list<T> lst; 默认初始化;T类型元素的空列表initializer_list<T> lst{a, b, c} lst的元素数量和初始值一样多;lst的元素是对应初试值的副本,列表中的元素是constlst2(lst) 拷贝或复制一个initializer_list对象不会拷贝列表中的元素,拷贝后,原始列表和副本共享元素lst2 = lstlst.size() 列表中的元素数量lst.begin() 返回指向lst中首元素的指针lst.end() 返回指向lst中尾元素下一个位置的指针initializer_list 对象中的元素永远是常量值,无法改变元素的值如果想向initializer_list形参中传递一个值的序列,必须把序列放在一对花括号内 省略符形参只能出现在形参列表的最后一个位置,形式有以下两种: void foo(parm_list, ...);void foo(...);返回类型和return语句return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方return有两种形式: return;return expression;不要返回局部对象的引用或指针调用运算符的优先级与点运算符和箭头运算符相同,符合左结合律 ...
前言和背景《Effective c++》一书中条款01为:视c++为一个语言联邦,该条款中将c++语言分为4个次语言组成的“联邦政府”,其分别为:兼容基础c的部分、c++面向对象的部分、c++模板部分、stl库部分。我非常认同将c++语言看成由几个子语言组成的联邦语言,不过我个人认为stl库应该是建立在前三个子语言的基础上发展出来的,较为成熟和通用的一个典型作品。(尽管有很多人对stl库有各种各样的吐槽,但是话说回来没被吐槽过的c++库真是少之又少...),所以我个人把c++语言分为兼容c部分(随着c++和c语言各自的发展和演化,目前已经不是一个完全包含的关系了)、c++面向对象部分和c++模板部分,这三个部分各自独立成一体系,相互之间又有很深刻的联系。有很多人说c++模板就是c++标准提供给程序员的,用来生成自定义的函数和类的脚本语言,只不过这种脚本语言被c++纳入标准了而已。C++模板部分的基石,我个人认为是模板类型推导和模板的特化与偏特化。从基础的stl库的各种组件的使用到自己进行模板元编程,都是在这两个基本概念之上发展起来的。这篇文章的目的就是记录c++11/14标准下的c++模板类型推导规则。 C++的声明语法:decl-specifier-seq init-declarator-list decl-specifier-seq可以是以下几种(顺序任意): 1.typedef 说明符,如果出现的话说明整个声明是一个typedef声明,该声明会introduce一个新的类型名称,而不是一个函数或者对象。2.inline说明符,(c++17开始允许出现在变量声明中)3.friend说明符,允许出现在类和函数声明中。4.constexpr说明符(c++11),允许出现在变量定义、函数和函数模板声明以及静态数据成员的声明中。5.存储周期说明符(register(until c++11),static,thread_local,extern,mutable)6.type specifiers,其中包括:(1)class declaration(2)enum declaration(3)内置类型说明符(4)auto、decltype specifier(根据表达式来推导类型的两个说明符)(5)之前声明的类和enum名字(6)之前声明的typedef-name或者type alias(7)模板参数填充的模板名字(8)elaborated type 说明符,没用过(9)typename specifier(10)cv说明符(const volatile)(11)attributes这里不介绍,没用过注:上述出现的specifier并不仅仅翻译为说明符,它与前面的关键字作为一个整体,代表了某一段语法规则而不仅仅是其前面的关键字而已,具体请查询:https://en.cppreference.com/w...仅只有一个type specifier允许出现在decl-specifier-seq中,但以下情况除外:1.const和volatile能够和除了自己以外的其他type specifiers一同出现。2.signed和unsigned能够和char、long、short或者int一起出现。3.short和long能够和int一起出现。4.long可以和long一起出现。(c++11) 接下来看init-declarator-list:init-declarator-list的语法规则我们略过,只说明会有哪些情况: 1.unqualified-id2.qualified-id3....4.指针声明5.指向成员的指针声明6.左值引用7.右值引用8.数组声明符9.函数声明符尽管上述列出的标准是如此繁杂和无从下手,但我不得不说这已经是一份简化版本,我略去了c++11之后的标准中提出的内容,以及自己没怎么使用过的部分。我列出这些的目的在于理解,c++中的类型由:decl-specifier-seq 和init-declarator-list两部分共同组成。并且,decl-specifier-seq的部分中,type specifiers参与了类型的真正构成(其余类型的标识符都说明了声明的name其他方面的性质)我们将关注点放在变量的声明上,以此排除掉类、函数和模板声明相关的标识符。一个变量的类型如下: 1.可选的cv标识符2.type specifiers中的标识符3.可选的*、&、&&、[(可选的constexpr)]标识符例如:int、const int、const int&、int*、const int[12]等等,都是符合上述条件的类型。我们之后的模板类型推导也正是建立在这个前提下。 类型推导此节关于模板类型推导、auto类型推导和decltype。大部分借鉴于《Effective modern c++》首先来看模板类型推导。 template<typename T>void f(ParamType param);...f(expr);如上,模板类型T的类型由ParamType和expr联合决定,分如下几种情况: ParamType是引用或者指针,但不是万能引用:如果expr的类型是引用类型,丢掉其引用类型。然后用expr的类型去匹配ParamType以确定T的真实类型。我们在前言中提到过,任何一个变量的类型由三部分组成:可选的cv标识符、裸类型(我自己这么称呼,代表不具有cv属性、不具有引用、指针标识符后缀的类型,数组和函数标识符标识的类型在模板类型推导中是特殊情况,我们在最后讨论)和可选的引用、指针、数组、函数标识符。这里指的是,先用expr中的裸类型确定T的裸类型(直接匹配),然后如果expr或者ParamType中的任何一个具有cv标识符,则给T的裸类型前加cv标识符,推导完成。 template<class T>void f(T& param);int x = 27;const int cx = x;const int& rx = x;f(x); // T是int,ParamType是int&f(cx); //T是const int,ParamType是const int&f(rx); //T是const int,ParamType是const int&将f中的T& 换位const T& ,结果不变。将上述两种情况中的&换为*,结果不变。 ParamType是万能引用类型:万能引用类型的语法格式很固定: template<class T>void f(T&& param);这里面的ParamType是万能引用类型。注:关于c++中的万能引用类型,effective modern c++一书中有章节专门讲这个:Item24 。这种情况对于模板类型T的推导和情况1完全不同,因为这里涉及到了c++中表达式结果的一个性质:value category。说到这个又是一个十分基础和重要的性质,c++中的表达式的结果有两个独立的性质:type和value category。其中type很好理解,例如int i = 2; (i)这个表达式的type就是int&,就是我们理解的变量的类型。而value category有童鞋听过的话就是我们说的左值、纯右值、消亡值等等概念,这个性质代表了变量的生命周期、能否对其进行取地址操作等。理解value category的概念对于c++11非常重要,右值引用、完美转发、std::move、移动构造函数和移动赋值函数等都建立在以下几个基本概念上:value category、模板类型推导、引用折叠规则和右值引用。大家可以打开编译器看看move的一份实现,短短几行代码几乎凝聚了c++11带来的大部分根本性的扩展: ...
前言串,又称作字符串,它是由0个或者多个字符所组成的有限序列,串同样可以采用顺序存储和链式存储两种方式进行存储,在主串中查找定位子串问题(模式匹配)是串中最重要的操作之一,而不同的算法实现有着不同的效率,我们今天就来对比学习串的两种模式匹配方式: 朴素的模式匹配算法(Brute-Force算法,简称BF算法)KMP模式匹配算法朴素的模式匹配算法(BF算法)BF算法是模式匹配中的一种常规算法,它的思想就是: 第一轮:子串中的第一个字符与主串中的第一个字符进行比较 若相等,则继续比较主串与子串的第二个字符若不相等,进行第二轮比较第二轮:子串中的第一个字符与主串中第二个字符进行比较......第N轮:依次比较下去,直到全部匹配图示说明:第一轮: 第二轮: ...... 原理一致,省略中间步骤 第五轮: 第六轮: 代码实现:看完文字与图例讲解,我们来动手实现一个这样的算法 简单归纳上面的步骤就是: 主串的每一个字符与子串的开头进行匹配,匹配成功则比较子串与主串的下一位是否匹配,匹配失败则比较子串与主串的下一位,很显然,我们可以使用两个指针来分别指向主串和子串的某个字符,来实现这样一种算法 匹配成功,返回子串在主串中第一次出现的位置,匹配失败返回 -1,子串是空串返回 0 int String::bfFind(const String &s, int pos) const { //主串和子串的指针,i主串,j子串 int i, j; //主串比子串小,匹配失败,curLenght为串的长度 if (curLength < s.curLenght) return -1; while (i < curLength && j < s.curLength) { //对应字符相等,指针后移 if (data[i] == s.data[j]) i+, j++; else { //对应字符不相等 i = i -j + 1; //主串指针移动 j = 0; //子串从头开始 } //返回子串在主串的位置 if (j >= s.curLength) return (i - s.curLength); else return -1; }}注:代码只为体现算法思路,具体定义未给出 ...
Linux是一个操作系统软件。 与Windows不同的是,Linux是一套开放源代码程序的、并可以自由传播的类Unix操作系统,它是一个支持多用户、多任务、多线程和多CPU的操作系统。它能运行主要的UNIX工具软件、应用程序和网络协议。它支持32位和64位硬件。 Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。 如何快速入门,需要哪些知识点?1:linux环境专栏 linux系统安装 linux开发环境ssh与Samba配置 linux开发环境Gcc配置 linux的命令操作8条目录操作与5条文件操作 2:shell 脚本编程案例 3:统计文件单词数量(文本操作) 状态机实现文件单词统计 4:实现通讯录(结构体) 通讯录实现的架构设计与需求分析 链表的实现与数据结构的定义 架构接口层的实现 业务逻辑的分析与实现 通讯录人员操作代码的调试 通讯录人员操作代码调试与运行 通讯录删除人员操作的调试与BUG解决 文件保存于加载的接口层实现 文件保存业务实现 通讯录调试与运行 5:并发下的技术方案(锁) 多线程并发锁的项目介绍 多线程并发锁的方案一互斥锁 多线程并发锁的方案一自旋锁 多线程并发锁的方案一原子操作 总结如果你以前从未接触过Linux,上面这五个知识点其实是最简单的入门技术知识,你只需多学多操作即可 linux入门教程 希望刚 开始接触linux的 你少走弯路 祝 学习愉快 ~
有时候我们会想jvm和程序在同一进程中,和jvm交互或者做一些定制工作,需要把jvm嵌入到程序中。简单来说过程可以分为三步:初始化jvm/执行java字节码/退出jvm 初始化jvm加载libjvm.so到进程中并且调用JNI_CreateJavaVm JNI_CreateJavaVM(JavaVM **vm, void **penv, JavaVMOption* options)options我们按照launcher中的设置就好,包含了classpath/command/pid pid_t pid = getpid(); char *op_pid = NEW_STRING(256); sprintf(op_pid,"-Dsun.java.launcher.pid=%d",pid); options[op_count++].optionString = "-Djava.class.path=."; if(class_path != nullptr && strcmp(class_path,".") != 0){ char *op_class_path = NEW_STRING(strlen(class_path)+50); sprintf(op_class_path,"-Djava.class.path=%s",class_path); options[op_count++].optionString = op_class_path; } options[op_count++].optionString = "-Dsun.java.command=test_jvm"; options[op_count++].optionString = "-Dsun.java.launcher=SUN_STANDARD"; options[op_count++].optionString = op_pid;退出jvm if(vm->DetachCurrentThread() != JNI_OK || vm->DestroyJavaVM() != JNI_OK){ return -1; }执行java字节码 JavaVM *vm = init_jvm(".:shen.jar"); JNIEnv *env = get_jni_env(); if(vm == nullptr || env == nullptr) return -1; jclass cla = env->FindClass("test"); jmethodID method = env->GetStaticMethodID(cla,"main","([Ljava/lang/String;)V"); jclass cla_string = env->FindClass("java/lang/String"); jobjectArray args = env->NewObjectArray(1,cla_string, nullptr); char *cwd = NEW_STRING(256); getcwd(cwd,256); env->SetObjectArrayElement(args,0,env->NewStringUTF(cwd)); env->CallStaticObjectMethod(cla,method,args); destroy_jvm();本文代码jvm_helper.cpp ...
栈的定义栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。 ——百度百科简单定义:栈就是一种只允许在表尾进行插入和删除操作的线性表 如何理解栈的概念① 举一个生活中的例子:我在一个储物箱中,堆了一堆衣服,我的一件球衣在最下面,而我要拿这件衣服,就意味着我必须将上面的衣服全部拿出来才可以,但是由于箱子只有一个口,我也只能从上面拿东西,心里还默默想着,当初就不该将球衣早早的放进去,导致结果就是先进后出! 你就不能举个计算机中的例子?这就安排! ② 计算机中很多操作都是使用栈的原理来实现的,我们就比如常见的浏览器中的 “前进键” “后退键” 就可以利用栈的原理来实现,我们来用图说明一下 我们想要实现前进后退,可以使用两个栈(暂时称作 M、N)来实现 我们分别浏览了页面A、页面B、页面C,所以我们将这些页面依次压入栈,即图中打开页面部分当用户点击后退时,我们需要退回到页面B中去,但是由于页面C在B上方,我们就必须将页面C从栈M中先弹出,放到栈N中,即图中后退部分但是如果用户突然又想回到页面C去,原理相似的,只需要把栈N中的页面C弹出,重新压入栈M即可而如果用户在浏览B界面的时候,打开了新的界面D,那么C就无法通过前进后退访问了,所以栈M中压入页面D的同时还需要清空栈N 栈的术语说明栈顶:允许进行插入和进行删除操作的一段成为栈顶 栈底:表的另一端称为栈底 (第一个元素进入的位置) 压栈:在栈顶位置插入元素的操作叫做压栈,或入栈、进栈 出栈:删除栈顶元素的操作叫做出栈,也叫作弹栈,或者退栈 空栈:不含元素的空表 栈溢出:当栈满的时候,如果再有元素压栈,则发生上溢,当栈空的时候,再出栈则发生下溢 栈的抽象数据类型#ifndef _STACK_H_#define _STACK_H_#include <exception>using namespace std;template <class T>class Stack { public: virtual bool empty() const = 0; virtual int size() const = 0; virtual void push(const T &x) = 0; virtual T pop() = 0; virtual T getTop() const = 0; virtual void clear() =0; virtual ~Stack() {}};/* 自定义异常类*/// 用于检查范围的有效性class outOfRange:public exception {public: const char* what()const throw() { return "ERROR! OUT OF RANGE.\n"; } }; // 用于检查长度的有效性class badSize:public exception {public: const char* what()const throw() { return "ERROR! BAD SIZE.\n"; } };#endif顺序栈——栈的顺序存储结构开头我们就已经提过了,栈实际上就是一种线性表的特例,所以栈的实现和线性表一样,均使用数组实现,我们使用一个一维数组来存储元素,那么总得有个头阿,我们就需要确定栈底的位置,通常我们选择 0 的一端作为栈底,这样更加方便理解与操作,特别的是,我们设置了一个整型变量top 用来存放栈顶元素的位置(下标),也称作栈顶指针 ...