关于c++:C42-类型转换函数-下
类类型是否可能转为一般类型?
类类型是否可能转为一般类型?
规范数据类型之间会进行因式类型平安转换转换规则如下:char-short-int-unsigned int-long -unsigned long-float-double
最近写 CPP 我的项目遇到了一个问题,用了几个工具来解决,这里记录一下,和大家一起探讨。 1. 起因我的一个 CPP 我的项目的 UI 框架应用的是 CefSharp,UI 层是 C#,而一些模块代码应用的是 CPP,运行报错如下 报错信息是 System.IO.FileLoadException:"未能加由"CefSharp.Core.Runtime.dl"导入的过程第一感觉是过程加载某个配置文件或 dll 未胜利,或者动静库版本问题,但错报不进去,而且 VS 的 Debugger 无奈显示更多内容,用 Dependencies 看过程,dll 都是找到了的,那会是什么起因呢? 2. 剖析通过共事的提醒,应用微软提供的 Windows Debugging Tools 调试套件 gflags.exe 工具,让过程加载过程中的日志能在 VS 的<输入>报进去,配置如下。在 Image File 中输出残缺过程名,而后选中 Show loader snaps 显示加载器快照,而后重启过程。 而后从新运行,在 VS 的输入中就能够看到过程加载过程中的日志,如下。 在输入中搜 ERROR,能够看到加载哪些 dll 失败、正告等信息,有一些不重要,持续找发现加载的一个 dbghelp.dll 文件后,应用它的一个函数 SymGetSearchPathW 未找到失败,到库目录发现这个文件的工夫比拟老了,这个办法须要在 6.3 以上版本的 dbghelp.dll 文件才提供。 能够应用 dumpbin 命令的 dumpbin dbghelp.dll /EXPORTS 命令看到这个 dll 里并没有这个SymGetSearchPathW 办法,如下 ...
逗号操作符逗号操作符(,)能够形成逗号表达式逗号表达式用于将多个子表达式连贯为一个表达式逗号表达式的值为最初一个表达式的值逗号表达式中的前N-1个子表达式能够没有发返回值逗号表达式依照从左向右的程序计算每个子表达式的值#include <iostream>#include <string>using namespace std;void func(int i){ cout << "func() : i = " << i << endl;}int main(){ int a[3][3] = { (0, 1, 2), (3, 4, 5), (6, 7, 8) }; int i = 0; int j = 0; while( i < 5 ) func(i), i++; for(i=0; i<3; i++) { for(j=0; j<3; j++) { cout << a[i][j] << endl; } } (i, j) = 6; cout << "i = " << i << endl; cout << "j = " << j << endl; return 0;}
1.缘起大家好,我是《Linux后端开发工程实际》的作者万木春。我发现市面上的Linux后端开发相干书籍大多集中在API手册或运维手册,却鲜有针对后端开发岗位和工程实际的高效学习指南。在我本身学习Linux后端开发的过程中,也曾经验过许多弯路和挫折,因而我萌发了编写一本贴合Linux后端开发岗位和工程实际的书籍的想法。 2.撰稿创作这本书,我冒了很大的危险。我抉择在实现全书的创作之后,再寻找出版社投稿,这无疑减少了被出版社退稿的危险,可能会让我之前的致力化为泡影。 创作过程是最具挑战性的局部,它须要强烈的自律精力和长期的保持。我也曾多次中断创作,然而通过不懈的致力和保持,我最终实现了全书。我的工作经验也是我可能实现这本书的关键因素之一。 作为一个完满主义者,我在创作过程中会常常纠结于代码是否优雅、格调是否对立、是否存在bug、知识点是否齐备等问题,因而我对内容和代码进行了重复的调整和欠缺。 在IO模型与并发那章中,压测工具和17种不同的并发模型的设计并非欲速不达,而是通过屡次思考、推倒重来和迭代才得出的后果。为了获取更精确的压测数据和更稳固的示例程序,我破费了几周的工夫在压测和优化程序上。 在MyRPC框架设计与实现那章中,为了实现一个高性能、易读、构造清晰且代码量适中的RPC框架,我重复实现了四个版本的RPC框架。为了确保RPC框架代码的正确性,我编写了102个单元测试用例,并对RPC框架所有的代码进行了充沛的测试,最初对RPC框架还进行了齐备的性能压测和内存透露的检测。为了更好的治理测试用例,我专门创立了一个excel文档对测试用例进行治理,这个excel文档局部内容下图所示。 为了让读者更好的了解书中的内容,我购买了业余的绘图软件,精心绘制了60多张的图片。秉着实际出真知的准则,在书中有很多常用工具的实现,例如,ping、make、shell、traceroute和arp等命令行工具。通过编码实现常用工具,能够深刻的了解并把握相干的核心技术点。 3.投稿因为稿件品质过硬,且选题独特,在投稿的时候并没有遇到什么大的问题。通过人民邮电出版社的张涛编辑的帮忙,我顺利地与人民邮电出版社签订了出版合同。 4.三审三校三审三校的过程历时8个月,稿件通过了7、8次的重复批改。为了给读者呈现出更好的内容并晋升浏览体验,我十分重视细节,比方代码中是否少了一个空格、正文是否标准、代码缩进是否统一,甚至图片的箭头是否有间隙。我也非常感谢人民邮电出版社的排版和编辑老师,他们不厌其烦地调整书稿,没有他们的辛勤付出,《Linux 后端开发工程实际》就无奈顺利出版。在这期间,张涛编辑还会和我一起探讨书的中英文的命名等细节,他们的工作做得十分粗疏。 5.写在最初《Linux 后端开发工程实际》是一本实用性极强的书,它是市面上少有的能残缺形容如何从 0 到 1 构建 Linux C/C++后端微服务集群的书。通过实际案例和具体的代码实现,读者能够一步步构建出本人的后端微服务集群,从而深刻了解和把握 Linux 后端开发的精华。它将率领您走进 Linux 后端开发的世界,开启您的 Linux 后端开发之旅。让咱们一起摸索 Linux 后端开发的有限可能,独特迎接挑战和时机。 京东购买链接:https://item.jd.com/10096373960488.html 天猫购买链接:https://detail.tmall.com/item.htm?abbucket=7&id=765959625705&... 当当购买链接:https://product.dangdang.com/11702142394.html 拼多多购买链接:https://mobile.yangkeduo.com/goods.html?goods_id=586950483757...
#include <iostream>#include <algorithm>using namespace std;int main() { int n, a[100], b[100], i, j; cin >> n; // 输出n个数a和n个数b for (int i = 0; i < n; i++) cin >> a[i]; for (int i = 0; i < n; i++) cin >> b[i]; // 找到b中最大的递增元素的地位 for (i = 0; i < n - 1 && b[i] <= b[i + 1]; i++); // 找到a中与b不同的元素的地位 for (j = i + 1; a[j] == b[j] && j < n; j++); if (j == n) { cout << "Insertion Sort" << endl; // 如果a和b雷同,则是应用插入排序对a进行排序 sort(a, a + i + 2);//插排的性质:[0,i+1]排序 } else { //不是插排则模仿并排 cout << "Merge Sort" << endl; int k = 1, flag = 1; // 如果a和b不同,则是应用归并排序对a进行排序 while(flag) { flag = 0; for (i = 0; i < n; i++) { if (a[i] != b[i]) flag = 1; } k = k * 2; // 对a的每个子数组进行排序 for (i = 0; i < n / k; i++) sort(a + i * k, a + (i + 1) * k); sort(a + n / k * k, a + n); } } for (j = 0; j < n; j++) { if (j != 0) printf(" "); printf("%d", a[j]); } return 0;}依照插入排序两头序列的性质判断是否是插入排序(后面为按序,后边与原数组一样)如果是插入排序,则第i轮完结后前i个数字为已排序,第i+1到开端为原数组 ...
这道题能够说是我目前刷到的patb组里最麻烦的一道题 次要是带分数这玩意曾经记不清多少年都没碰过了,而且各种状况非常容易思考不全 最初靠本人也没写出个能全过的解,起初一看是数太大了而我全程都在用int的问题,能够说是百密一疏…… 这里附上柳神的赏心悦目的c++解法,正文我写的,而后前面的解析由chatglm4生成,不便了解。 #include <iostream>#include <cmath>using namespace std;long long a, b, c, d;// 计算两个数的最大公约数long long gcd(long long t1, long long t2){ return t2 == 0 ? t1 : gcd(t2, t1 % t2);}void func(long long m, long long n){ if (m * n == 0) { printf("%s", n == 0 ? "Inf" : "0"); return; } // 符号判断 bool flag = ((m < 0 && n > 0) || (m > 0 && n < 0)); // 分数化简运算时不思考正负,先取绝对值!! m = abs(m); n = abs(n); long long x = m / n; // 整数位 printf("%s", flag ? "(-" : ""); if (x != 0) printf("%lld", x); if (m % n == 0) { // 判断是否只有整数 if (flag) printf(")"); // 如果是负整数,则补全括号 (正整数没括号) return; } if (x != 0) printf(" "); // x不为0,则输入空格 (且整数的状况已在后面提前返回,不会运行到这) m = m - x * n; // 真分子 long long t = gcd(m, n); m = m / t; n = n / t; // 约分成最简分式 printf("%lld/%lld%s", m, n, flag ? ")" : "");}int main(){ scanf("%lld/%lld %lld/%lld", &a, &b, &c, &d); func(a, b); printf(" + "); func(c, d); printf(" = "); func(a * d + b * c, b * d); printf("\n"); // a/b+c/d通分公式,输入有理化后的后果 func(a, b); printf(" - "); func(c, d); printf(" = "); func(a * d - b * c, b * d); printf("\n"); func(a, b); printf(" * "); func(c, d); printf(" = "); func(a * c, b * d); printf("\n"); func(a, b); printf(" / "); func(c, d); printf(" = "); func(a * d, b * c); return 0;}这段代码是一个C++程序,用于解决分数的加减乘除运算,并且输入后果。程序首先定义了一个计算最大公约数的函数gcd,而后定义了一个解决分数并格式化输入的函数func。主函数main中读取用户输出的两个分数,而后别离计算它们的和、差、积、商,并输入后果。程序应用scanf函数从规范输出读取两个分数,格局为%lld/%lld %lld/%lld,别离对应四个长整型变量a, b, c, d。这里的%lld是读取长整型的格局指定符。func函数是程序的外围,它承受两个长整型参数m和n,别离代表分数的分子和分母。函数首先查看分子或分母是否为0,如果是,则依据状况输入Inf(无穷大)或0。接着,函数解决分数的正负号,并计算分数的整数局部x。而后,函数通过计算最大公约数来简化分数,并依照格局输入分数。在main函数中,程序别离计算了两个分数的和、差、积、商,并调用func函数输入后果。加减运算通过通分后相加或相减来实现,乘法通过分子相乘、分母相乘来实现,除法令是通过分子乘以另一个分数的分母、分母乘以另一个分数的分子来实现。程序的输入格式化得很好,可能解决正负整数和分数,以及它们的运算后果。 ...
LLVM 框架提供多种下载方式: LLVM Download Page (官方网站): https://releases.llvm.org/ LLVM GitHub Release (GitHub 发行版): https://github.com/llvm/llvm-project/releases LLVM For Debian (Debian 下载链接): https://apt.llvm.org/ 本文将应用 LLVM Debian 主动下载安装脚本, 如下图所示 倡议: 下载安装时应该指定具体的 LLVM 版本号, 不要应用最新版本. 示例: 对于 LLVM-18, 应该应用如下 Bash 命令进行下载安装. wget https://apt.llvm.org/llvm.shchmod +x llvm.shsudo ./llvm.sh 18 all装置实现后, 应该在 /usr/bin 目录下见到相似的文件名: ls /usr/bin 这些带有 -18 后缀的可执行文件都是 LLVM 框架的一部分, 兴许某个时候你会用到它们, 但兴许永远都不会用到. 为了使用方便, 咱们能够给这些文件增加硬连贯: ln /usr/bin/FileCheck-18 /usr/bin/FileCheckln /usr/bin/clang-18 /usr/bin/clangln /usr/bin/clang++-18 /usr/bin/clang++ln /usr/bin/llvm-as-18 /usr/bin/llvm-asln /usr/bin/llvm-dis-18 /usr/bin/llvm-disln /usr/bin/llvm-link-18 /usr/bin/llvm-linkln /usr/bin/llc-18 /usr/bin/llcln /usr/bin/lli-18 /usr/bin/lli这样就能够通过 clang example.c 的形式运行名为 example.c 的 C 语言文件了. ...
潜规则操作符只有两种值(true和false)逻辑表达式不能齐全计算就能确定最终值最终后果只能是true或者false逻辑表达式:
内存泄露动静申请堆空间,用完后不偿还C++语言中没有垃圾回收机制指针无法控制所指堆空间的生命周期例:内存泄露 #include <iostream>#include <string>using namespace std;class Test{ int i; public: Test(int i) { this->i = i; } int value() { return i; } ~Test() { }};int main(){ for(int i = 0;i<5;i++) { Test* p = new Test(i); cout << p->value() <<endl; } return 0;}输入: 01234深度的思考:须要一个非凡的指针指针的生命周期完结时被动开释堆空间一片堆空间最多只能由一个指针标识杜绝指针运算和指针比拟例:智能指针:
字符串类的兼容性string类最大限度的思考了C字符串的兼容性能够依照应用C字符串的形式应用string对象#include <string>using namespace std;int main(){ string s = "a1b2c3d4"; int n = 0; for(int i = 0;i<s.length();i++) { if(isdigit(s[i])) { n++; } } cout << n <<endl; return 0;}输入: 4重载数组拜访操作符数组拜访符是C/C++中的内置操作符数组拜访符的原生意义是数组拜访和指针运算例: #include <iostream>#include <string>using namespace std;int main(){ int a[5]; for(int i=0;i<5;i++) { a[i] = i; } for(int i=0;i<5;i++) { cout << *(a + i) << endl; //cout << a[i] << endl; } cout << endl; for(int i=0;i<5;i++) { i[a] = i+10; //a[i] = i+10; } for(int i=0;i<5;i++) { cout<<*(i+a)<<endl; //cout<< a[i]<<endl; } return 0;}输入: ...
解决方案C到C++的进化过程引入了自定义类型在C++中能够通过类实现字符串类型的定义规范库中的字符串类C++语言反对C语言的所有概念C++语言中没有原生的字符串类型C++规范库提供了string类型string间接反对字符串的连贯string间接反对字符串的大小比拟string间接反对子串查找和提取string间接反对字符串的插入和替换 例: #include <iostream>#include <string>using namespace std;void string_sort(string a[], int len){ for(int i=0; i<len; i++) { for(int j=i; j<len; j++) { if( a[i] > a[j] ) { swap(a[i], a[j]); } } }}string string_add(string a[], int len){ string ret = ""; for(int i = 0;i<len;i++) { ret += a[i]+";"; } cout <<endl; return ret;}int main(){ string sa[7] = { "Hello World", "D.T.Software", "C#", "Java", "C++", "Python", "TypeScript" }; string_sort(sa, 7); for(int i=0; i<7; i++) { cout << sa[i] << endl; } cout << endl; cout << string_add(sa, 7) << endl; return 0;}输入: ...
Pika 社区很快乐发表,咱们明天公布曾经过咱们生产环境验证 v3.5.1 版本 https://github.com/OpenAtomFoundation/pika/releases/tag/v3.5.1 。 该版本不仅做了很多优化工作,还引入了多项新性能。这些新性能包含 动静敞开WAL、ReplicationID 检测是否增量复制、在 K8s 环境上 Pika 服务的主动注册从而实现集群的自组织、以及 exporter 检测集群指标等等,无疑将会让用户享受到更为稳固和高效的 NoSQL 应用体验。 1 新个性1 Slow log 减少队列等待时间统计,在队列阻塞的时候不便咱们进行问题定位。PR 1997, 作者 wangshao1。2 主从复制应用 ReplicationID 判断是否进行增量同步,解决原主从同步形式切主后整个数据集会进行全量复制的问题,能够晋升 Pika 性能。PR 1951, 作者 Mixficsol。3 WAL 以 'disablewal' 命令形式反对动静敞开,在写性能遇到瓶颈的时候,能够通过命令敞开 WAL 缓解写性能降落的问题,敞开 WAL 有机器宕机后失落数据的危险,用户须要依据本人的应用习惯衡量。PR 2015,作者 Mixficsol。4 flush 线程数和 compaction 线程数合二为一,在 Compaction 性能瓶颈时,能够动静调整线程数,缓解 Comapction 损耗 Pika 性能的问题。PR 2014, 作者 Tianpingan。5 降级了 RocksDB 版本到 v8.3.3。PR 2000, 作者 dingxiaoshuai123。6 新增周期性打印工作队列的长度性能,在队列阻塞的时候能够疾速定位问题。PR 1978, 作者 Tianpingan。7 新增利用一个 pika_exporter 监测整个集群的指标,实现一个 Pika Exporter 实例监控整个集群,解决了 3.5.0 版本一个 Pika Exporter 监测一个 Pika 实例耗费资源的问题。PR 1953, 作者 chenbt-hz。8 实现在 K8s 环境上 Pika 服务的主动注册,在启动时主动注册,从而实现集群的自组织 ,实现了通过命令拉起整个 Pika Cluster 集群。PR 1931, 作者 machinly。2 bug 修复1 调整了 Rate_limit 参数,修复了压测时呈现 RPS 为 0 的状况 。PR 2009, 作者 Mixficsol。2 修复了 INFODATA 命令中对于遍历数据文件时呈现空门路的逻辑判断。PR 1996, 作者 Mixficsol。3 修复了 Codis 在线上呈现大毛刺的问题。PR 2016, 作者 chejinge。4 修复了 macOS 环境下编译应用 tools 导致编译不过的问题 。PR 2011, 作者 A2ureStone。5 缩小了 exporter 非必要日志的打印,升高 了资源利用率。PR 1945, 作者 Mixficsol。3 应用倡议本次新增了几个配置参数,大家在应用过程中,须要依据应用状况按需调整: ...
前言古代C++中像auto、智能指针、挪动语义等都是一些重大的优化个性,但也有一些像constexpr、nullptr等等这样一个小的个性。这章的内容就是这些小个性的汇合。 条款7:在创建对象时留神辨别()和{}在古代C++中有3种形式来以指定的值初始化对象,别离时小括号、等号和大括号: int x(0); //初始化值在小括号中int y = 0; //初始化值在等号后int z{0}; //初始化值在大括号中其中,大括号模式的初始化时C++11引入的对立初始化形式。大括号初始化能够利用的语境最为宽泛,能够阻止隐式窄化的类型转换,还对最令人苦恼的解析语法免疫。 先说阻止隐式窄化的类型转换,比方上面代码能够通过编译: double x,y,z;int sum1(x+y+z); //能够通过编译,表达式的值被截断为intint sum2 = x+y+z; //同上而以下代码不能够通过编译,因为大括号初始化禁止内建类型间接进行隐式窄化类型的转换。 int sum3{x+y+z}; //编译不通过再说最令人苦恼的解析语法免疫。C++规定:任何可能解析为申明的都要解析为申明,而这会带来副作用。所谓最令人库娜的解析语法就是——程序员原本想要以默认形式结构一个对象,后果却不小心申明了一个函数。举个例子,我想调用一个没有形参的Widget构造函数,如果写成Widget w();,那后果就变成了申明了一个函数(名为w,返回一个Widget类型对象)而非对象。而用大括号初始化Widget w{};就不存在这个问题了。 然而,不能自觉的都应用大括号初始化。在构造函数被调用时,只有形参中没有任何一个具备std::initializer_list类型,那么大括号和小括号没有区别 ;如果又一个或多个构造函数申明了任何一个具备std::initializer_list类型的形参,那么采纳了大括号初始化语法的调用语句会强烈地优先选用带有std::initializer_list类型形参的重载版本。也就是说,因为std::initializer_list的存在,大括号初始化和小括号初始化会产生天壤之别的后果。 这点最突出的例子是:应用两个实参来创立一个std::vector<数值类型>对象。std::vector有一个两个参数的构造函数,容许指定容器的初始大小(第一个参数),以及所有元素的初始值(第二个参数);但它还有一个std::initializer_list类型形参的构造函数。如果要创立一个元素为数值类型的std::vector(比方std::vector<int>),并且传递两个实参给构造函数,那么应用大括号和小括号初始化的差异就比拟大了: std::vector<int> v1(10, 20); //创立一个含有10个元素的vector,所有元素的初始值都是20std::vector<int> v1{10, 20}; //创立一个含有2个元素的vector,元素的值别离时1,20所以,如果是作为一个类的作者,最好把构造函数设计成客户无论应用小括号还是大括号都不会影响调用得重载版本才好。 条款8:优先选用nullptr,而非0或NULL因为0和NULL都不是指针类型,而nullptr才是真正的指针类型。比方在重载指针类型和整型的函数时,如果应用0或者NULL调用这样的重载函数,则永远不会调用到指针类型的重载版本,只有应用nullptr能力调用到。当然为了兼容咱们依然须要遵循C++98的领导准则:防止在整型和指针类型之间重载。 条款9:优先选用别名申明,而非typedefC++11提供了别名申明来替换typedef,两者作用在大部分状况下是一样的。比方上面的typedef: typedef std::unique_ptr<<std::unordered_map<std::string, std::string>>> UPtrMapSS;typedef void (*FP)(int, const std::string&);能够用上面的别名申明来替换: using UPtrMapSS = std::unique_ptr<<std::unordered_map<std::string, std::string>>>;using FP = void (*)(int, const std::string&);但还有有一种场景是只能应用别名申明的,那就是在定义模板的时候,typedef不反对模板化,但别名申明反对。在C++98中须要用嵌套在模板化的struct里的typedef能力达到雷同成果。比方上面这段: template<typename T>struct MyAllocList { typedef std::list<T, MyAlloc<T>> type; //MyAllocList<T>::type是std::list<T, MyAlloc<T>>的同义词};MyAllocList<Widget>::type lw; //客户代码在C++11中用别名申明就很简略了: template<typename T>using MyAllocList = std::list<T, MyAlloc<T>>; //MyAllocList<T>是std::list<T, MyAlloc<T>>的同义词MyAllocList<Widget> lw; //客户代码这里还能够看到,别名模板能够让人免写“::type”后缀。并且在模板内,对于内嵌typedef的援用常常要求加上typename的前缀,而别名模板没有这个要求。 ...
c++的对象到底在栈上还是调配在堆上?首先,毋庸置疑,应用new和malloc系列函数调配的对象,肯定是在堆上的。 Object *obj = new Object();有争议的是 Object obj;它是在栈上还是在堆上呢? 要答复这个问题,首先咱们须要了解这句话的意思,这句话并不代表在栈上分配内存,它代表的是让obj具备“主动存储(automatic storage)”的性质。所谓的“主动存储”,意思是这个对象的存储地位取决于其申明所在的上下文。如果这个语句呈现在函数外部,那么它就在栈上创建对象,此时obj变量和obj指代的对象(此时obj实质上其实是obj指代对象的首地址)都在栈上。如果这个语句不是在函数外部,而是作为一个类的成员变量,则取决于这个类的对象是如何调配的。思考上面的代码: class Test{ Object obj;}Test *test = new Test;test指针是在栈上,它所指代的对象Test是存在堆上,那么obj变量和obj对象就在堆上。 class Test{ Object obj;}Test test;test变量在栈上,test对象在栈上,那么obj变量和obj对象就在栈上。 遵循这么一个准则:指针变量和一般变量由上下文定义,指针所指向的内存在堆上,一般变量所指代的对象由上下文定义。 栈大小栈大小是有默认值的,如果申请的长期变量太大就会超过栈大小,造成栈溢出。 它的默认值是能够批改的,个别,在unix-like平台,栈的大小是由环境变量管制的,所以不能通过设置编译器(像gcc)的任何编译标记来设置;在windows平台,栈的大小是蕴含在可执行文件里的,它能够在visual c++的编译过程中设置,但在gcc里是不可行的。 办法为:我的项目->属性->链接器->零碎->堆栈保留大小 (字节数) 在个别状况下,不同平台默认栈大小如下所示(仅供参考) SunOS/Solaris 8172K bytes (Shared Version)Linux 10240K bytesWindows 1024K bytes (Release Version)AIX 65536K bytes 演示栈空间代码#include <iostream>class Test {public: Test() { std::cout << "Test" << std::endl; } ~Test() { std::cout << "~Test" << std::endl; }private: char a[1024 * 1024];};class TestContainer {public: TestContainer() { std::cout << "TestContainer" << std::endl; } ~TestContainer() { std::cout << "~TestContainer" << std::endl; }private: Test test;};int main(int argc, char* argv[]) { TestContainer t; while (1) {} return 0;}栈大小1MB(默认值),申请栈空间1MB后果:程序解体,stack overflow ...
古代CPU一次至多能装载4字节(32位机),即一个整数,这4个字节在内存中的排列程序将影响它的值。这就是字节序问题。字节序分为大端字节序(big endian)和小端字节序(little endian)。 如上图,大端字节序是指数据的高位存在内存的低地址处,小端字节序是指数据的高位存在内存的高地址处。 古代PC大多采纳小端字节序。当格式化的数据须要在两台应用不同字节序的主机进行传递时,接收端会谬误的解决。解决的办法是:发送端将发送的数据转换成大端字节序而后发送,接收端依据本人的字节序决定需不需要解决(小端转换,大端不解决)。 在做网络编程的时候,应用提供的库即可进行转换(linux库,windows下是一样的,头文件有所区别) #include <netinet/in.h>unsigned long int _htonl(unsigned long int hostlong);unsigned short int _htons(unsigned short int hostshort);unsigned long int _ntohl(unsigned long int netlong);unsigned short int _ntohs(unsigned short int netshort);如果平台没有提供这套,那么能够本人实现一下,原理很简略,就是调换下数据 #include <stdint.h>#define BigLittleSwap16(v) ((0xFF00 & (uint16_t)(v)) >> 8 | (0x00FF & (uint16_t)(v)) << 8)#define BigLittleSwap32(v) ((0xFF000000 & (uint32_t)(v)) >> 24 | (0x00FF0000 & (uint32_t)(v)) >> 8 | (0x0000FF00 & (uint32_t)(v)) << 8 | (0x000000FF & (uint32_t)(v)) << 24) #define BigLittleSwap64(v) ((0xFF00000000000000 & (uint64_t)(v)) >> 56 |(0x00FF000000000000 & (uint64_t)(v)) >> 40 |(0x0000FF0000000000 & (uint64_t)(v) )>> 24 |(0x000000FF00000000 & (uint64_t)(v)) >> 8 |(0x00000000FF000000 & (uint64_t)(v)) << 8| (0x0000000000FF0000 & (uint64_t)(v)) << 24 | (0x000000000000FF00 & (uint64_t)(v) )<< 40 | (0x00000000000000FF & (uint64_t)(v) )<< 56 )int isCPUBigEndian(){ union Data{ short val; char byte; } data; data.val = 0x0102; return (data.byte == 0x01);}unsigned long int _htonl(unsigned long int hostlong);unsigned short int _htons(unsigned short int hostshort);unsigned long int _ntohl(unsigned long int netlong);unsigned short int _ntohs(unsigned short int netshort);unsigned long int _htonl(unsigned long int hostlong){ return isCPUBigEndian() ? hostlong : BigLittleSwap32(hostlong);}unsigned short int _htons(unsigned short int hostshort){ return isCPUBigEndian() ? hostshort : BigLittleSwap16(hostshort);}unsigned long int _ntohl(unsigned long int netlong){ return isCPUBigEndian() ? netlong : BigLittleSwap32(netlong);}unsigned short int _ntohs(unsigned short int netshort){ return isCPUBigEndian() ? netshort : BigLittleSwap16(netshort);}测试: ...
参考了这篇博客:C++实现vector 练习模板类,命名空间,重载运算符的根底应用。加了个命名空间和对方括号的重载,使得能够通过索引拜访数据。 #include <iostream>namespace Morpheus { template<typename T> class vector { private: T* _first; T* _last; T* _end; public: vector(int size = 5) { _first = new T[size]; _last = _first; _end = _first + size; } ~vector() { delete _first; _first = nullptr; _last = nullptr; _end = nullptr; } bool isEmpty() { return _first == _last; } bool isFull() { return _last == _end; } int size() { return _last - _first; } void push_back(const T& x) { if (isFull()) { expand(); } *_last++ = x; } void pop_back() { if (!isEmpty()) { _last--; } } T operator[](const int& i) { return _first[i]; } private: void expand() { int len = _end - _first; T* temp = new T[2 * len]; for (int i = 0; i < len; ++i) { temp[i] = _first[i]; } delete[] _first; _first = temp; _last = _first + len; _end = _first + 2 * len; } };}int main() { Morpheus::vector<double> v; v.push_back(1.2); v.push_back(1.3); v.push_back(1.4); for (int i = 0; i < v.size(); ++i) { std::cout << v[i] << ","; } v.pop_back(); v.pop_back(); for (int i = 0; i < v.size(); ++i) { std::cout << v[i] << ","; }}
作者:喜马拉雅 董道光 宣言:缓存不是万金油,更不是垃圾桶!!! 缓存作为喜马拉雅至关重要的根底组件之一,每天承载着微小的业务申请量。一旦缓存呈现故障,对业务的影响将十分重大。因而,确保缓存服务的稳固和高效运行始终是咱们的重要指标。 上面是咱们对喜马缓存历史故障复盘后总结的一套缓存应用标准,在此分享给大家,心愿小伙伴们能在缓存选型和应用的过程中少踩坑。 1. 缓存选型1.1 缓存类型介绍喜马线上缓存类型次要有 4 种: 1. redis 主从模式:官网原版 2.codis-redis:豌豆荚开源,redis 集群解决方案 3. 云数据库 redis:redis-cluster 容器化部署 4.xcache:基于 codis、pika、redis 自研的一套海量 KV 存储解决方案 1.2 缓存应用模式介绍应用模式次要分为 2 种: 1.cache 模式:数据不须要长久化,实例复原不须要加载数据,扩缩容不须要迁徙数据 2.store 模式:数据须要长久化,实例复原须要加载数据,扩缩容须要迁徙数据 上面是对各种类型缓存做了简略比照: 2. 缓存应用军规2.1 缓存类型应用标准1)redis 集群模式首选云数据库 redis,海量 KV 存储首选 xcache 云数据库 redis 采纳官网 redis cluster 模式,容器化部署,反对故障主动复原和弹性伸缩,是以后 redis 集群的主推计划,但不反对数据长久化,如果必须要做数据长久化,并且对延时要求十分高,能够应用 codis redis。数据量十分大,并且对延时要求不是特地高,能够抉择 xcache。 2) redis 不要当 db 应用 ,如果数据肯定要做长久化,能够抉择 xcache redis 当 db 应用,故障复原数据很慢,重大影响 SLA。并且如果主从全副挂掉,slave 机器无奈复原时,数据就会齐全失落。xcache 人造反对数据长久化 3) 不要应用客户端分片模式 客户端分片模式不具备高可用和弹性伸缩能力,倡议应用真正的集群模式,如 codis-redis、云数据库 redis、xcache ...
前言最近看到个问题, 就是在命名空间中申明一个变量 ( int rand = 0 ), 用using namespace将这个命名空间引入 ( 净化 ) 进全局空间, 当函数调用此变量时产生谬误. 这是命名空间全局净化典型案例, 咱们进行一些分析 一、命名空间是什么?C++ 命名空间(namespace)是一种将全局作用域宰割为若干个小的作用域的机制。 它能够解决命名抵触(name clash)的问题,使不同作用域下的同名标识符互不烦扰。 命名空间的应用办法如下: 1.定义命名空间:能够在全局空间下定义命名空间,也能够在已有的命名空间内定义子命名空间,例如: namespace math{ int add(int a, int b) { return a + b; } namespace geometry { double circle_area(double r) { return 3.14159 * r * r; } } // namespace geometry} // namespace math2.应用命名空间:能够用 using namespace 或 using 关键字引入一个或多个命名空间,也能够应用作用域限定符拜访命名空间中的标识符。 using namespace math; // 引入 math 命名空间using math::geometry::circle_area; // 仅引入 math::geometry::circle_area 标识符int main(){ int sum = math::add(1, 2); // 应用作用域限定符拜访 math 命名空间中的函数 double area = circle_area(10.0); // 间接应用 circle_area 标识符(已引入) return 0;}二、命名空间全局净化命名空间是一种避免变量和函数因名称反复, 导致程序失败的机制。 ...
C++ shared_ptr智能指针 reset() 详解最近看了个问题:智能指针援用计数为什么不是0?, 问将智能指针reset后为何不是援用减一. 看代码比拟好阐明 #include <cstdio>#include <memory>auto main() -> int{ std::shared_ptr<int> ptr = std::make_shared<int>(100); auto second = ptr; auto third = ptr; printf("num = %d, count = %ld\n", *ptr, ptr.use_count()); ptr.reset(); printf("ptr count = %ld\n", ptr.use_count()); printf("second and third count = %ld\n", second.use_count()); return 0;}ptr调用reset后, 援用数为0, 而不是2. 这是没有明确reset()函数的语义, 通过源码可能比拟好了解: void reset() _NOEXCEPT { shared_ptr().swap(*this); }这是reset()的实现, 用shared_ptr()调用结构一个长期指针, 并将其与调用reset()的指针对象(上例中的ptr)进行替换, 原对象指向的资源变为nullptr, 援用计数变为0. 长期对象指向原指针指向的资源(上例中的100), 援用计数变为3(长期对象, second, third), 语句完结, 长期对象析构, 援用计数减一. ...
c++ 获取数字字符串的子串数值,比方给定字符串"123456",要获取第三位和第四位的数值,这里是34。 应用substr应用substr截取字串,再应用c_str()获取字符数组,再应用atoi()转换为数字 结构字符数组间接应用索引获取字符,构建字符数组,再应用atoi()转换为数字 代码#include <string>#include <iostream>#include <chrono>using namespace std;int main(int argc, char* argv[]) { string val = "123"; int total = 1000000; std::chrono::time_point<std::chrono::system_clock> start = std::chrono::system_clock::now(); for (int i = 0; i < total; i++) { int tmp = atoi(val.substr(1, 2).c_str()); } std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::now(); std::chrono::microseconds diff = std::chrono::duration_cast<std::chrono::microseconds>(end - start); cout << "using substr:" << diff.count() << "ms" << endl; start = std::chrono::system_clock::now(); for (int i = 0; i < total; i++) { char vals[2] = { val[1],val[2] }; int tmp = atoi(vals); } end = std::chrono::system_clock::now(); diff = std::chrono::duration_cast<std::chrono::microseconds>(end - start); cout << "using char[]:" << diff.count() << "ms" << endl; return 0;}执行后果 ...
在 c++ 面向对象应用中,咱们经常会定义一个基类类型的指针,在运行过程中,这个指针可能指向一个基类类型的对象,也可能指向的是其子类类型的对象,那当初问题来了,咱们如何去判断这个指针到底执行了一个什么类型的对象呢?明天咱们就聊一下这个问题,首先咱们要辨别是否容许 RTTI,据此有不同方法。 1 容许应用 RTTI在关上 rtti 的场景下,能够应用 dynamic_cast 和 typeid 这两个运算符来判断对象的实在类型。 1.1 应用 dynamic_castdynamic_cast 用于在运行时进行多态类型检查和转换,它能够将指向基类的指针转换为指向派生类的指针或援用。如果转换胜利,则阐明对象属于指标类或其派生类。如果转换失败,则返回空指针。咱们看如下例子,咱们想判断指针 basePtr 是否指向了 Child2 类型的对象。总共进行了两次测试,第一次让该指针指向了 Child1 类型的对象,第二次则是指向了 Child2 类型的对象。 #include <iostream>class Basic {public: virtual void say() { std::cout << "我是基类" << std::endl; }};class Child1 : public Basic {public: void say() { std::cout << "我是 child 1" << std::endl; }};class Child2 : public Basic {public: void say() { std::cout << "我是 child 2" << std::endl; }};int main(){ Basic* basePtr; basePtr = new Child1(); if (dynamic_cast<Child2*>(basePtr)) { std::cout << "[test 1]指针指向了 Child2 类型对象" << std::endl; } else { std::cout << "[test 1]指针没有指向 Child2 类型对象" << std::endl; } delete basePtr; basePtr = new Child2(); if (dynamic_cast<Child2*>(basePtr)) { std::cout << "[test 2]指针指向了 Child2 类型对象" << std::endl; } else { std::cout << "[test 2]指针没有指向 Child2 类型对象" << std::endl; } delete basePtr;}让咱们一起看看两次的打印,这是合乎咱们的预期的,应用 dynamic_cast 能够判断一个基类类型的指针是否指向了某个具体类类型。 ...
介绍C++ 是一种高级语言,它是由 Bjarne Stroustrup 于 1979 年在贝尔实验室开始设计开发的。C++ 进一步裁减和欠缺了 C 语言,是一种面向对象的程序设计语言。C++ 可运行于多种平台上,如 Windows、MAC 操作系统以及 UNIX 的各种版本。 本教程通过通俗易懂的语言来解说 C++ 编程语言。 环境设置我采纳的是 gcc + vscode的搭配形式进行开发 正文为什么须要正文呢?正文是为了更好的解释,同时也将进步源代码的可读性。C++ 正文个别有两种: // - 个别用于单行正文。/ ... / - 个别用于多行正文。例子如下: #include <iostream>using namespace std; int main() { // 这是一个正文 cout << "Hello World!"; return 0;}C++ 正文以 / 开始,以 / 终止。例如: #include <iostream>using namespace std; int main() { /* 这是正文 */ /* C++ 正文也能够 * 跨行 */ cout << "Hello World!"; return 0;}也能够在多行正文中嵌套单行正文 ...
最近在写代码的时候,碰到了 crosses initialization of ... 的问题,只因我在 switch 的某个 case 分支下定义了一个变量,于是乎便将这个问题整顿一下。 1 switch case 的某个分支下定义了变量switch case 是咱们在工作中常见的分支语句,如果在某个分支下不失当的应用了局部变量,就有可能呈现本文提到的问题。 1.1 问题代码示例#include <iostream>void switchTest(int code);int main() { switchTest(1);}void switchTest(int code) { switch (code) { case 1: int myNum = 0; break; case 2: break; default: break; }}上述代码,我在第一个 case 分支下定义了 myNum 变量,尝试编译,呈现如下谬误。次要看 C2360 这一行,myNum 的初始化操作由 case标签跳过,也就是说当 code !=1 的时候,不会进行 myNum 的初始化。第一反馈是“我这个 myNum 只在 case 1 下用啊,不初始化就不初始化嘛,反正其余中央又不必”。 呃,这就牵扯到变量的作用范畴了。myNum 这个局部变量的作用范畴是从定义处开始直到 switch 语句完结。艰深说,就算咱们跳过了 case 1 处的初始化,myNum 在前面的分支中,也是能够被拜访到的,被拜访到就有可能被应用(尽管咱们本人没打算在前面应用,但编译器认为这是有危险的),然而咱们又没有对其进行初始化,那这就会出问题。那这个问题怎么解决呢? ...
书籍:Modern C++ for Absolute Beginners作者:Slobodan Dmitrović出版:Apress入群邀请:7个业余方向交换群+1个材料需要群原文:书籍下载-《零根底的Modern C++入门》-第二版 01 书籍介绍以结构化、扼要且敌对的形式学习C++编程语言。本书传授古代C++编程语言的基础知识、C++规范库以及包含C++23在内的古代C++规范,无需任何编程教训。 C++是一门独特的语言,它的复杂性令人诧异,但在许多方面却十分优雅和晦涩。然而,它也是一门不能仅凭猜想就能学会的语言,容易犯错且难以正确使用。为了解决这个问题,每个章节都充斥了逐步减少复杂度的实在示例。《零根底的Modern C++入门》,第二版不仅传授C++23的编程技巧,更提供了打牢C++根底的内容。 作者率领你学习C++编程语言、规范库以及从C++11到C++23的根底。每章配有适量的理论知识和大量的源代码示例。你将应用C++23的性能和规范,并对之前的C++版本进行比拟和钻研。 浏览本书后,你将可能开始应用古代C++规范进行编程。为此,书中还附有大量相干的源代码示例,能够通过专门的GitHub存储库收费获取。 02 通过这本书,能够学习到什么· 以敌对而无效的形式介绍古代C++编程· 学习C++的基础知识:类型、运算符、变量、常量、表达式、援用、函数、类、输出/输入、智能指针、多态等· 在Windows上设置Visual Studio环境,在Linux上应用GCC编写本人的代码· 申明和定义函数、类和对象,并将代码组织到命名空间中· 摸索面向对象编程:类和对象、封装、继承、多态等,应用最先进的C++个性· 利用最佳实际来组织源代码和管制程序流程· 相熟C++语言的要点和注意事项等内容· 把握lambda表达式、继承、多态、智能指针、模板、模块、契约、概念等基础知识 03 作者简介Slobodan Dmitrović是一位软件参谋、培训师,也是几本编程书籍的作者。他是一名资深的研发软件开发人员,在该行业积攒了二十年的教训。Slobodan为企业客户和学术机构提供C++培训课程。 04 书籍纲要 1. SCI写作书籍举荐:《Science Research Writing》-second Edition 书籍举荐-《基于C++的数字图像处理》-2023书籍举荐-《ROS2机器人编程扼要教程》
动态内存调配是在堆上调配的,且本人调配的,必须要本人开释,否则就会内存透露,具体C++怎么实现的呢? 老规矩,先上C++验证代码: #include<iostream>// #include <cstdlib>/** * 内存治理:堆区内存调配*/int main(){ int *arr = new int[11]; arr[0] = 0; arr[9] = 9; arr[88] = 88;// 越界 // std::cout<<arr[88] << std::endl; delete[] arr; int* arr_malloc = (int*)malloc(11 * sizeof(int)); arr_malloc[0] = 0; arr_malloc[9] = 9; arr_malloc[88] = 88;// 越界 free(arr_malloc); return 0;}下面C++代码,别离用new/delete调配开释堆内存,又用malloc/free调配开释内存,且还进行了越界赋值,看看什么成果。,上面是对应的汇编: main:.LFB1522: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp# new/delete movl $44, %edi # 申请调配44个字节 call _Znam@PLT movq %rax, -16(%rbp) # rax里是函数返回值,rbp-8存储这块堆内存的地址 movq -16(%rbp), %rax movl $0, (%rax) # 堆内存+0的地址赋值为0,即第0个元素 movq -16(%rbp), %rax addq $36, %rax movl $9, (%rax) # # 9 * 4 == 36,即第9个元素 movq -16(%rbp), %rax addq $352, %rax # 88 * 4 = 352 movl $88, (%rax) # 越界赋值88 cmpq $0, -16(%rbp) # 空指针查看, 如果相等,那么零标记(ZF)将被设置为1,否则将被设置为0。 je .L2 # je .L2指令查看零标记,如果它为1,则跳转到.L2标签处 movq -16(%rbp), %rax movq %rax, %rdi call _ZdaPv@PLT# malloc/free.L2: movl $44, %edi call malloc@PLT # malloc调配44字节 movq %rax, -8(%rbp) movq -8(%rbp), %rax movl $0, (%rax) movq -8(%rbp), %rax addq $36, %rax movl $9, (%rax) movq -8(%rbp), %rax addq $352, %rax # 越界赋值 movl $88, (%rax) movq -8(%rbp), %rax movq %rax, %rdi call free@PLT # 不做空指针查看,间接调用free去开释 movl $0, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc根据上述汇编可知,汇编上间接调用的是零碎调用的动态链接库,具体new/delete、malloc/free的实现代码,须要本人看内核源码,回头钻研钻研。然而基本上咱们能够宏观晓得,C++编译器只管调用零碎调用,去给你调配开释内存,而且必须是你本人执行delete/free了之后,它才会取调用对应的零碎调用去开释对应的堆内存,如果你C++代码里不执行delete/free,那么C++编译器也不会帮你查看。 ...
文章首发【重学C++】04 | 说透C++右值援用(上) 引言大家好,我是只讲技术干货的会玩code,明天是【重学C++】的第四讲,在后面《03 | 手撸C++智能指针实战教程》中,咱们或多或少接触了右值援用和挪动的一些用法。 右值援用是 C++11 规范中一个很重要的个性。第一次接触时,可能会很乱,不分明它们的目标是什么或者它们解决了什么问题。接下来两节课,咱们具体讲讲右值援用及其相干利用。内容很干,留神珍藏! 左值 vs 右值简略来说,左值是指能够应用&符号获取到内存地址的表达式,个别呈现在赋值语句的右边,比方变量、数组元素和指针等。 int i = 42;i = 43; // ok, i是一个左值int* p = &i; // ok, i是一个左值,能够通过&符号获取内存地址int& lfoo() { // 返回了一个援用,所以lfoo()返回值是一个左值 int a = 1; return a; };lfoo() = 42; // ok, lfoo() 是一个左值int* p1 = &lfoo(); // ok, lfoo()是一个左值相同,右值是指无奈获取到内存地址的表白是,个别呈现在赋值语句的左边。常见的有字面值常量、表达式后果、长期对象等。 int rfoo() { // 返回了一个int类型的长期对象,所以rfoo()返回值是一个右值 return 5;};int j = 0;j = 42; // ok, 42是一个右值j = rfoo(); // ok, rfoo()是右值int* p2 = &rfoo(); // error, rfoo()是右值,无奈获取内存地址左值援用 vs 右值援用C++中的援用是一种别名,能够通过一个变量名拜访另一个变量的值。 ...
简略重述下重载与重写的区别:重载。类的成员函数的函数名雷同,但参数列表不同(类型、个数、程序不同),就是函数的重载。C++编译器依据你调用函数时传入的不同的参数列表,去执行对应的那个函数。这点在c语言中是不能实现的,因为C语言没有这个语法个性,所以C编译器不反对,如果你这么写编译器必定会给你报错无奈编译通过。重写。即笼罩,子类的某个成员函数A的返回值、函数名和参数列表和父类某个成员函数A截然不同,那么子类就把父类的A函数给笼罩掉了,当前调用子类的A函数,执行的是子类A函数的逻辑,如果在子类中不重写,则将会调用父类的A函数。重载简略剖析下,我猜想,重载在汇编中也没什么不同,必定和一般函数一个样,有不同的函数标号,执行哪个重载函数,编译器就会Call 哪个函数。C++编译器会将每个成员函数都别离翻译成汇编并给它起个标号名,依据你要调用哪个具体的重载函数,去Call哪个汇编标号的函数。上面来剖析下: #include<iostream>/** * 继承:重载*/class Son{public: int a; void add(){ a = 0; } void add(int i){ a = i; } void add(int i, int j){ a = i+j; }};int main(){ Son son; son.add(); son.add(33); son.add(10, 78); return 0;}类中有3个重载函数add,函数名一样,参数不一样,别离调用,看看编译成汇编是怎么实现重载个性的: main:.LFB1525: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp # 调配16字节栈帧给main函数 movq %fs:40, %rax movq %rax, -8(%rbp) xorl %eax, %eax leaq -12(%rbp), %rax # 算出rbp-12这个值 movq %rax, %rdi call _ZN3Son3addEv # 调用add() leaq -12(%rbp), %rax movl $33, %esi movq %rax, %rdi call _ZN3Son3addEi # 调用add(int i) leaq -12(%rbp), %rax movl $78, %edx movl $10, %esi movq %rax, %rdi call _ZN3Son3addEii # 调用add(int i, int j) movl $0, %eax movq -8(%rbp), %rcx xorq %fs:40, %rcx je .L6 call __stack_chk_fail@PLT.L6: leave .cfi_def_cfa 7, 8 ret .cfi_endproc果然不出咱们所料,即便简单的重载概念,也只是编译器层面的抽象概念,真正体现到硬件、cpu指令、内存调配与读写时,其实仍旧是C语言汇编那点事件,无非就是内存在哪调配,它的地址怎么计算,而后传参,Call函数标号。通过这里,能够更加粗浅领会到,所谓C++的语法个性在汇编层面的实现,其实就是C语言汇编的各种组合,包含内存调配、地址计算、变量的字节长度计算、函数调用、参数传递等等。 ...
简略的继承C++编译器是怎么把逻辑上是继承关系的C++代码,父类和子类,怎么用汇编体现这种关系呢?老规矩,先上C++代码: #include<iostream>/** * 简略继承*/class Father{public: int a; Father(){ a = 11; } ~Father(){ a = 22; }protected: int b;private: int c;};class Son : public Father{public: int s; Son(){ s = 66; }};int main(){ Son son; son.a = 888; return 0;}汇编是如何体现的呢? .file "3-object-class-inherit-0.cpp" .text .section .rodata .type _ZStL19piecewise_construct, @object .size _ZStL19piecewise_construct, 1_ZStL19piecewise_construct: .zero 1 .local _ZStL8__ioinit .comm _ZStL8__ioinit,1,1 .section .text._ZN6FatherC2Ev,"axG",@progbits,_ZN6FatherC5Ev,comdat .align 2 .weak _ZN6FatherC2Ev .type _ZN6FatherC2Ev, @function_ZN6FatherC2Ev:.LFB1523: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movq %rdi, -8(%rbp) # 把main函数的rbp-48这个值,放到 rbp-8 movq -8(%rbp), %rax # 把main函数的rbp-48这个值,放到rax寄存器 movl $11, (%rax) # 把11放到main函数的rbp-48这个值所指向的内存处(弄了半天,绕这么大弯子,就是想让父类构造函数的赋值语句,赋值到main函数栈帧所指向的内存,因为编译器无奈间接算出父类成员变量a的内存地址,所以只能用传值的形式一路传过来) nop popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc.LFE1523: .size _ZN6FatherC2Ev, .-_ZN6FatherC2Ev .weak _ZN6FatherC1Ev .set _ZN6FatherC1Ev,_ZN6FatherC2Ev .section .text._ZN6FatherD2Ev,"axG",@progbits,_ZN6FatherD5Ev,comdat .align 2 .weak _ZN6FatherD2Ev .type _ZN6FatherD2Ev, @function_ZN6FatherD2Ev:.LFB1526: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movq %rdi, -8(%rbp) movq -8(%rbp), %rax movl $22, (%rax) # 把22放到main函数栈帧rbp-48这个值,这是变量a的内存地址,放到a所在的内存地址处。留神(%rax)是指rax寄存器外面的数值所指向的内存,而%rax是指寄存器 nop popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc.LFE1526: .size _ZN6FatherD2Ev, .-_ZN6FatherD2Ev .weak _ZN6FatherD1Ev .set _ZN6FatherD1Ev,_ZN6FatherD2Ev .section .text._ZN3SonC2Ev,"axG",@progbits,_ZN3SonC5Ev,comdat .align 2 .weak _ZN3SonC2Ev .type _ZN3SonC2Ev, @function_ZN3SonC2Ev:.LFB1529: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp # 给Son子类的构造函数分配内存 movq %rdi, -8(%rbp) # 把main函数的rbp-48这个值放到Son构造函数的rbp-8内存里 movq -8(%rbp), %rax # 把main函数的rbp-48这个值放到rax movq %rax, %rdi # 把main函数的rbp-48这个值放到rdi寄存器用作传参数,在子构造函数里调用父构造函数,能够看出,是先调用的父构造函数的逻辑,再执行子构造函数的代码逻辑 call _ZN6FatherC2Ev # 调用父构造函数 movq -8(%rbp), %rax movl $66, 12(%rax) # 执行子构造函数的代码逻辑 rax+12 == rbp-48+12 == rbp-36 nop leave .cfi_def_cfa 7, 8 ret .cfi_endproc.LFE1529: .size _ZN3SonC2Ev, .-_ZN3SonC2Ev .weak _ZN3SonC1Ev .set _ZN3SonC1Ev,_ZN3SonC2Ev .section .text._ZN3SonD2Ev,"axG",@progbits,_ZN3SonD5Ev,comdat .align 2 .weak _ZN3SonD2Ev .type _ZN3SonD2Ev, @function_ZN3SonD2Ev:.LFB1533: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp # 调配栈桢 movq %rdi, -8(%rbp) # # 把main函数栈帧rbp-48这个值,这是变量a的内存地址,放到rbp-8内存处 movq -8(%rbp), %rax movq %rax, %rdi # 计算main函数栈帧rbp-48这个值,这是变量a的内存地址,父类析构函数用到它 call _ZN6FatherD2Ev nop leave .cfi_def_cfa 7, 8 ret .cfi_endproc.LFE1533: .size _ZN3SonD2Ev, .-_ZN3SonD2Ev .weak _ZN3SonD1Ev .set _ZN3SonD1Ev,_ZN3SonD2Ev .text .globl main .type main, @functionmain:.LFB1531: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 pushq %rbx subq $40, %rsp # 为main调配40字节栈帧 .cfi_offset 3, -24 movq %fs:40, %rax movq %rax, -24(%rbp) xorl %eax, %eax leaq -48(%rbp), %rax # 这不是超出栈了吗? movq %rax, %rdi # 把rbp-48这个值传到结构函数参数?(这里构造函数是无参结构啊)留神:无参结构只是C++语法个性上的说法,汇编里,用到什么数据就传什么数据,具体用到什么,编译器本人计算 call _ZN3SonC1Ev movl $888, -48(%rbp) # 父类的成员变量是在main函数的栈帧里调配的,该对象是main函数的局部变量,所以在栈上调配,如果是全局变量,就不在栈上调配了 movl $0, %ebx leaq -48(%rbp), %rax # 计算main函数栈帧rbp-48这个值,这是变量a的内存地址,是为了一路传给父类析构函数用的 movq %rax, %rdi # call _ZN3SonD1Ev # 析构函数 movl %ebx, %eax movq -24(%rbp), %rdx xorq %fs:40, %rdx je .L7 call __stack_chk_fail@PLT.L7: addq $40, %rsp popq %rbx popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc.LFE1531: .size main, .-main .type _Z41__static_initialization_and_destruction_0ii, @function_Z41__static_initialization_and_destruction_0ii:.LFB2015: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movl %edi, -4(%rbp) movl %esi, -8(%rbp) cmpl $1, -4(%rbp) jne .L10 cmpl $65535, -8(%rbp) jne .L10 leaq _ZStL8__ioinit(%rip), %rdi call _ZNSt8ios_base4InitC1Ev@PLT leaq __dso_handle(%rip), %rdx leaq _ZStL8__ioinit(%rip), %rsi movq _ZNSt8ios_base4InitD1Ev@GOTPCREL(%rip), %rax movq %rax, %rdi call __cxa_atexit@PLT.L10: nop leave .cfi_def_cfa 7, 8 ret .cfi_endproc.LFE2015: .size _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii .type _GLOBAL__sub_I_main, @function上述汇编,我删掉了一点点开端的非核心汇编,避免篇幅太长。 ...
绪论之前,我花了几章工夫,摸索了C语言编译器,如何用简略的汇编指令,实现C语言面向过程的概念,包含实现根本数据类型、构造体、逻辑管制分支构造、函数调用、参数传递和指针等,根本对用汇编实现面向过程有了大略的把握和宏观上的理解,而我做这件事的最终目标,就是想摸索C++的面向对象,是如何用汇编实现的。 「这也是我最喜爱的学习形式,一开始不陷入细枝末节,先从宏观上把握整体构造、整体框架,再去宏观把握、宏观细扣具体的知识点,与大家共勉。」 在写C++面向对象的汇编实现原理之前,我想先思考思考我想要问的问题是什么?想要摸索的是哪些点?因而在这篇文章里记录、思考一下! 次要有以下相干知识点须要摸索钻研: # 纯C++语法的使用,编译器提供了语法个性1. 创立1个类,类中蕴含根本数据类型、根本成员函数、结构与析构函数,main函数里创立个对象进行对其进行操作;2. 父子类继承时,父类不同修饰符下的数据、成员函数是如何解决的?3. 重载与重写是如何实现的?4. 多态相干?5. 虚函数与纯虚函数?6. 类的动态成员,友元函数等7. 抽象类、运算符重载等# 库的调用:8. 多线程,开启一个新的线程,会被编译成什么汇编?9.volatile/原子变量/锁...会被编译成什么汇编?临时想到这些想理解的,其它的边写边想吧!能把C++面向对象的实现原理齐全搞清楚,也须要花点功夫。正好上半年也刚过去,下半年的打算也要逐步开始,那么就先心愿8月31日前能利用业余时间把C++大部分面向对象的问题都摸索分明!
1 .让本人习惯C++条款01 视C++为一个语言联邦CObject-Oriented C++Template C++STLC++高效编程守则视状况而变动,取决于你应用C++的哪一部分。条款02 尽量与const,enum,inline替换#define对于单纯常量,最好以const对象或enums替换#defines。对于形似函数的宏(macros),最好改用inline函数替换#defines。条款03 尽可能应用const将某些货色申明为const能够帮忙编译器侦测出谬误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。编译器强制施行bitwise constness,但你编写程序时应该应用“概念上的常量性”(conceptual constness)。当const和non-const成员函数有着本质等价的实现时,令con-const版本调用const版本可防止代码反复。条款04 确定对象应用前已被初始化为内置类型对象进行手工初始化,因为C++不保障初始化他们。构造函数最好应用成员初始值列(member initialization list),而不要在构造函数本体内应用赋值操作(assignment)。初始值列列出的成员变量,其排列秩序应该和它们在class中申明的秩序雷同。为罢黜“跨编译单元之初始化秩序”问题,请以local static对象替换non-local static对象。2. 结构/析构/赋值运算条款05 理解C++默认编写并调用哪些函数编译器能够暗自为class创立default构造函数、copy构造函数和copy assignment操作符,以及析构函数。(C++11开始还有move constructor 和move assignment)。条款06 若不想应用编译器主动生成的函数,就应该明确回绝为驳回编译器主动(暗自)提供的机能,可将相应的成员函数申明为private并且不予实现。应用像uncopyable这样的base calss也是一种做法。(C++11当前能够应用=delete通知编译器删除不须要的成员函数。)条款07 为多态积攒申明virtual析构函数多态性质的base calsses应该申明一个virtual析构函数。如果class带有任何virtual函数,它就应该领有一个virtual析构函数。Classes的设计目标如果不是作为base classes应用,或不是为了具备多态(polymorphically),就不应该申明virtual析构函数。条款08 别让异样逃离析构函数析构函数相对不要吐出异样。如果一个被析构哈你数调用的函数可能抛出异样,析构函数应该捕获人分和异样,并吞下它们或完结程序。如果客户须要对某个操作函数运行期间抛出的异样做出反馈,那么class应该提供一个一般函数(而非析构函数中)执行该操作。条款09 绝不在结构和析构过程中调用virtual函数在结构和析构期间不要调用virtual函数,因为这类调用从不降落至derived class。条款10 令operator=返回一个reference to *this为了实现“连锁赋值”,应该令operator=返回一个reference to *this。条款11 在operator=中解决“自我赋值”确保当对象自我赋值时operator=有良好的行为。其中技术包含比拟“起源对象”和“指标对象”的地址、精心周到的语句程序、以及copy-and-swap。确定任何函数如果操作一个以上的对象,而其中多个对象时同一个对象时,其行为依然正确。条款12 复制对象时勿忘其每一个成分Copying函数应该确保赋值“对象内的所有成员变量”及“所有base class”成分。不要尝试以某个copying函数实现另一个copying函数。应该将独特机能放在第三个函数中,并有两个copying函数独特调用。3. 资源管理条款13 以对象治理资源为了避免资源泄露,请应用RAII对象,它们在构造函数中取得资源并在析构函数中开释资源。两个常被应用的RAII Class别离时tr1::shared_ptr和auto_ptr。前者通常是较好的抉择,因为其copy行为比拟直观。若抉择auto_ptr,赋值动作会使它(被复制物)指向null。(C++11中应用std::shared_ptr、std::unique_ptr和std::weak_ptr代替了两者。)条款14 在资源管理类中小心copying行为复制RAII对象必须一并复制它所治理的资源,所以资源的copying行为决定RAII对象的copying行为。一般常见的RAII class copying行为是:克制copying、履行援用计数法(reference counting)。不过其行为也都能够被实现。条款15 在资源管理类中提供对原始资源的拜访APIs往往要求拜访原始资源(raw resources),所以每一个RAII Class应该提供一个“获得其所治理之资源”的方法。对原始资源的拜访可能经由显式转换或隐式转换。一般而言显式转换比拟平安,隐式转换对客户比拟不便。条款16 成对应用new和delete时要采取雷同模式如果在new表达式中应用[],必须在相应的delete表达式中也应用[]。如如果在new表达式中不应用[],肯定不要在相应的delete表达式中也应用[]。条款17 以独立语句蒋newed对象置入智能指针以独立语句将newed对象存储于(置入)智能指针内。如果不这样做,一旦异样被抛出,有可能导致难以觉察的资源泄露。4. 设计与申明条款18 让接口容易被正确应用,不易被吴用好的接口很容易被正确应用,不容易被误用。应该在所有的接口中致力达成这些性质。“促成正确应用”的方法包含接口的一致性,以及与内置类型的行为兼容。“阻止误用”的方法包含建设新类型、限度类型上的操作,解放对象值,以及打消客户的资源管理责任。条款19 设计class犹如设计typeClass的设计就是type的设计。应该带着和“语言设计者当初设计语言内置类型”时一样的审慎来研究class的设计。条款20 宁以pass-by-reference-to-const 替换 pass-by-value尽量以pass-by-reference-to-const替换pass-by-value。前者通常比拟高效,并可防止切割问题。(slicing problem)以上规定并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言,pass-by-value往往比拟适当。条款21 必须返回对象时,别妄想返回其reference绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象而有可能共事须要多个这样的对象。条款22 将成员变量申明为private切记将成员变量申明为private。这可赋予客户拜访数据的一致性、可轻微划分访问控制、允诺约束条件取得保障,并提供class作者以充沛的实现弹性。protected并不比public更具封装性。条款23 宁以non-member、non-friend替换number函数宁肯拿non-member non-friend函数替换member函数。这样做能够减少封装性、包裹弹性(packing flexibility)和机能扩充性。条款24 若所有参数皆需类型转换,请为此采纳non-number函数如果须要为某个函数的所有参数(包含被this指针所指向的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member。条款25 思考写出一个不摈弃异样的swap函数当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异样。如果你提供一个member swap,也该提供一个non-member swap用来调用前者。对于class(而非template),也请特化std;;swap。调用swap时应针对std::swap应用using申明式,而后调用swap并且不带任何“命名空间资格润饰”。为“用户定义类型”进行std templates全特化时好的,但千万不要尝试在std内退出某些对std而言全新的货色。5. 实现条款26 尽可能延后变量定义式的呈现工夫尽可能延后变量定义式的呈现。这样做可减少程序的清晰度并改善程序效率。条款27 尽量少做转型动作如果能够,尽量避免转型,特地时在重视效率的代码中防止dynamic_casts。如果有个设计须要转型动作,试着倒退无需转型的代替设计。如果转型时必须的,试着将它暗藏于某个函数背地。客户随后能够调用该函数,而不需将转型放进本人的代码内。宁肯应用C++-style(旧式)转型,而不是用新式转型。前者很容易辨识进去,而且也比拟有着分门别类的执掌。条款28 防止返回handles指向对象外部成员防止返回handles(包含references、指针、迭代器)指向外部对象。恪守这个条款可减少封装性,帮忙const成员函数的行为像个const,并将产生dangling handlers的可能性降至最低。条款29 为“异样平安”而致力是值得的异样平安函数(Exception-salf functions)即便产生异样也不会泄露资源或容许任何数据结构毁坏。这样的函数辨别为三种可能的保障:基本型、强烈型、不抛异样型。“强烈保障”往往可能以copy-and-swap实现进去,但“强烈保障”并非对所有函数都能够实现或具备实现意义。函数提供的“异样平安保障”通常最高只等于其所调用之各个函数的“异样平安保障”中的最弱者。条款30 透彻理解inlining的里里外外将大多数inlining限度在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制降级(binary upgradability)更容易,也可使潜在的代码收缩问题最小化,使程序的速度晋升机会最大化。不要只因为function templates呈现在头文件,就将它们申明为inline。条款31 将文件间的编译依存关系降至最低反对“编译依赖最小化”的个别构想是:依赖于申明式,不要依赖于定义式。基于此构想的两个伎俩时Handle classes和Interface classes。程序库头文件应该以“齐全且仅有申明式”(full and declaration-only forms)的模式存在。这种做法不管是否设计templates都实用。6. 继承与面向对象条款32 确定你的public继承塑模出Is-a关系“public继承”象征Is-a。实用于base class身上的每一件事件肯定也实用于derived classes身上,因为每一个“derived class”对象也是一个base class对象。条款33 防止遮掩继承而来的名称derived class内的名称会遮掩base class内的名称。在public继承下素来没有人心愿如此。为了让被遮掩的名称再见天日,可应用using申明式或转交函数(forwarding functions)。条款34 辨别接口继承和实现继承接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口。纯虚(pure virtual)函数只具体指定接口继承。非纯虚(impure virtual)函数具体指定接口继承及缺省实现继承。non-virtual函数具体指定接口继承预计强制性实现继承。条款35 思考virtual函数以外的其余抉择virtual函数的代替计划包含NVI手法及Strategy设计模式的多种形式。NVI手法本身时一个非凡模式的Template Method设计模式。将机能从成员函数移到class内部函数,带来的一个毛病时,非成员函数无法访问class的non-public成员。tr1::function(C++11曾经移到std::function)对象的行为就像个别函数指针。这样的对象可接收“与给定之指标签名式(target signature)兼容”的所有可调用物(callable entities)。条款36 绝不从新定义继承而来的non-virtual函数相对不要从新定义继承而来的non-virtual函数。条款37 绝不从新定义继承而来的缺省参数相对不要从新定义一个继承而来的缺省参数值,因为缺省参数值是动态绑定,而virtual函数——你惟一应该覆写的定西——确是动静绑定。条款38 通过复合塑模出has-a或“依据某物实现出”复合(composition)的意义和public继承齐全不同。在利用域(application domain),复合意味着has-a(有一个),在实现域(implementation domain),复合意味着is-implemented-in-terms-of(依据某物实现出)。条款39 理智而审慎的应用private继承Private继承意味着is-implemented-in-terms-of(依据某物实现出)。它通常比复合(composition)的级别低。然而当derived class 须要拜访protected base class的成员,或须要从新定义继承而来的virtual函数时,这么设计时正当的。和复合(composition)不同,private继承能够造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。条款40 理智而审慎的应用多重继承多重继承比繁多继承简单。它可能导致新的歧义性,以及对virtual继承的须要。virtual继承会减少大小、速度、初始化(及赋值)复杂度等老本。如果virtual base classes不带任何数据,将时最具备实用价值的状况。多重继承确实有正当用处。其中一个情节波及“public继承某个interface class”和“private继承某个帮助实现的class”的两相组合。7. 模板与泛型编程条款41 理解隐式接口和编译器多态classes和templates都反对接口(interface)接多态(polymorphism)。对classes而言接口时显式的(explicit),以函数签名为核心,多态则是通过virtual函数产生于运行期。对于templates参数而言,接口是隐式的(implicit),奠基于无效表达式。多态则时通过template具现化和函数重载 解析(function overloading resolution)产生于编译期。条款42 理解typename的双重意义申明template参数时,前缀关键字class和typename可调换。请应用关键字typename标识嵌套隶属类型名称;但不得在base class lists(基类列)或member initialization list(成员初始值列)内以它最为base class 修饰符。条款43 学习解决模板化基类内的名称可在derived class templates内通过this->指涉base class templates内的成员名称,或由一个明确写出的“base class资格修饰符”实现。条款44 将与参数无关的代码抽离templatesTemplates生成多个classes和多个函数,所有任何template代码都不该与某个造成收缩的template参数产生相依关系。因非类型模板参数(non-type template parameters)而造成的代码收缩往往能够打消,做法是以函数参数或class成员变量替换template参数。条款45 使用成员函数末班承受所有兼容类型请应用member functions templates(成员函数模板)生成“可承受所有兼容类型”的函数。如果你申明member templates用于“泛化copy结构”或“泛化assignment操作”,你还是须要申明失常的copy构造函数和copy assignment操作符。条款46 须要类型转换时请为模板定义非成员函数当编写一个class template,而它所提供的“与此template相干的”函数反对“所有参数之隐式类型转换”时,请将那些函数定义为“class template外部的friend函数”。条款47 请应用traits classes体现类型信息Traits classes使得“类型相干信息”在编译期可用。它们以template和"template特化"实现实现。整合重载技术(overloading)后,traits calsses有可能在编译器对类型执行if...else测试。条款48 意识template元编程Template metaprogramming(TPM,模板元编程)可将工作由运行期移到编译期,因此得以实现晚期谬误侦测和更高的执行效率。TMP可被用来生成“基于政策抉择组合”(based on combinations of policy choices)的客户定制代码,也可用来防止生成对某些非凡类型并不适宜的代码。8. 定制new和delete条款49 理解new-handler的行为set_new_handler容许客户指定一个函数,在内存调配无奈取得满足时被调用。Nothrow new是一个颇为局限的工具,因为它只实用于内存调配,后继的结构函数调用还是可能抛出异样。条款50 理解new和delete的正当替换机会有许多理由须要写个自定义的new和delete,包含改善效力、对heap使用谬误进行调试、收集heap应用信息。条款51 编写new和delete时需猛攻惯例operator new应该内含一个无穷循环,并在其中尝试分配内存,如果它无奈满足内存需要,就该调用new-handler。它也应该有能力解决0 byte申请。class专属版本则还应该解决“比正确大小更大的(谬误)申请”。operator delete应该在收到null指针时不做任何事。class专属版本则还应该解决“比正确大小更大的(谬误)申请”。条款52 写了placement new也要写placement delete当你写一个placement operator new,请确定也写出对应的placement operator delete。如果没有这样做,你的程序可能会产生隐微而连续不断的内存泄露。当你申明placement new和placement delete时,确定不要有意识(非故意)的覆盖了它们的失常版本。9. 杂项探讨条款53 不要轻忽编译器的正告请庄重看待编译器收回的正告信息。致力在你的编译器的最高(最严苛)正告级别下争取“无任何正告”的荣誉。不要适度依赖编译器的报警能力,因为不同的编译器看待事件的态度并不相同。一旦移植到另一个编译器上,你本来依赖的正告信息有可能隐没。条款54 让本人相熟包含TR1在内的规范程序库C++规范库的次要机能由STL、iostreams、locales组成。并蕴含C99规范程序库。TR1增加了智能指针(例如tr1::shared_ptr)、一般化函数指针(tr1::function)、hash-based容器(unorderd_map,unordered_set)、正则表达式(regular expressions)以及另外10个组件的反对。TR1本身只是一份标准。为了取得TR1提供的益处,你须要一份实物。一个好的实物起源时Boost。条款55 让本人相熟BoostBoost是一个社群,也是一个网站。致力于收费、源码凋谢、同僚复审的C++程序库开发。Boost在C++标准化过程中表演深具影响力的角色。Boost提供许多TR1组件的实现品,以及其余许多程序库 ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第31面: 面试官:什么是锁?有什么作用? 二师兄:在C++中,锁(Lock)是一种同步工具,用于爱护共享资源,避免多个线程同时拜访,从而防止数据竞争和不统一。 面试官:有哪些锁? 二师兄:从品种上分,能够分为一般锁、读写锁、递归锁等品种。 二师兄:从实现上分,能够分为互斥锁、自旋锁、信号量、条件变量等。 面试官:互斥锁如何应用? 二师兄:在C++11之前,C++便准层面并没有定义锁,锁的利用要依赖于平台。Linux下应用pthread库中的mutex; #include <pthread.h>pthread_mutex_t mutex_ = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_lock(&mutex_);//被爱护的区域pthread_mutex_unlock(&mutex_);二师兄:C++11引入了std::mutex,对立了各个平台上互斥锁的应用:#include <mutex>std::mutex mutex_;mutex_.lock();//被爱护的区域mutex_.unlock();面试官:pthread_mutex和std::mutex有没有非阻塞的api? 二师兄:有的,别离是pthread_mutex_trylock()和try_lock(),当获取不到锁时这两者并不阻塞以后线程,而是立刻返回。须要留神的是,当pthread_mutex_trylock()获取到锁时返回0,而std::mutex::try_lock()办法获取不到锁时返回false。 面试官:std::lock_guard和std::unique_lock用过吗? 二师兄:用过。 面试官:两者有什么相同点和不同点? 二师兄:相同点是两者都应用RAII(资源获取即初始化)技术实现的锁,反对主动上锁,主动解锁。 二师兄:不同点次要包含三个方面: 1.灵活性:std::unqiue_lock的灵活性要高于std::lock_gurad,std::unique_lock能够在任何工夫解锁和锁定,而std::lock_guard在结构时锁定,在析构时解锁,不能手动管制。 2.所有权:std::unique_lock反对所有权转移,而std::lock_gurad不反对。 3.性能:因为std::unique_lock的灵活性更高,它的性能可能会略微低一些。 面试官:能实现一个lock_gurad吗? 二师兄:我尝试一下: class lock_guard{ explicit lock_guard(std::mutex& m):mutex_(m) { mutex_.lock(); } ~lock_guard() { mutex_unlock(); }private: std::mutex& mutex_;};面试官:为什么会产生死锁? 二师兄:当过程A持有锁1申请锁2,过程B持有锁2申请锁1时,两者都不会开释本人的锁,两者都须要对方的锁,就会造成死锁。当然事实中可能比这要简单,但原理是雷同的。 面试官:如何防止死锁? 二师兄:次要从以下几个方面动手: 1.防止循环期待,如果须要在业务中获取不同的锁,保障所有业务依照雷同的程序获取锁。 2.应用超时锁,当锁超时时,主动开释锁。 3.应用try_lock,当锁被占用时,返回false并继续执行。 4.锁的粒度尽量要小,只爱护竟态数据而不是整个流程。 面试官:晓得adopt_lock_t/defer_lock_t/try_to_lock_t这三种类型的用法吗? 二师兄:额。。不晓得。。 面试官:好的,回去等告诉吧。 让咱们来看看最初一个问题: 晓得adopt_lock_t/defer_lock_t/try_to_lock_t这三种类型的用法吗?adopt_lock_t/defer_lock_t/try_to_lock_t都是空类,次要示意std::lock_gurad和std::unqiue_lock的默认结构中的操作: adopt_lock_t:默认互斥量已被以后线程锁定,不应用lock()办法对互斥量加锁: std::mutex mtx_;mtx_.lock(); //lock{ std::lock_guard<std::mutex> lock_(mtx_,std::adopt_lock); //这里默认以后线程曾经对mtx_加过锁 ...}//unlockdefer_lock_t:尽管我领有了std::mutex的援用,然而在构造函数中并不调用lock()办法对互斥量加锁: std::mutex mtx_;{ std::unique_lock<std::mutex> ulock_(mtx_,std::defer_lock); //这里并没有加锁 ulock_.lock(); if(ulock_.owns_lock()) { //locked }else { //unlocked }}//if locked,unlocktry_to_lock_t:在构造函数执行是并不是应用lock()办法加锁,而是应用try_lock()办法加锁: ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第31面: 面试官:strcpy函数应用过吧? 二师兄:用过。 面试官:这个函数有什么作用? 二师兄:次要用做字符串复制,将于字符从一个地位复制到另一个地位。 面试官:strncpy函数也应用过吧,和strcpy有何不同? 二师兄:strncpy多了一个size_t的参数,用于防止缓冲区溢出。 面试官:是否实现一个strncpy函数? 二师兄:好的。 void strncpy(char *dest, char *src, size_t n){ for (size_t i = 0; i < n; i++) { *(dest + i) = *(src + i); }}面试官:额。。如果strlen(src) < n会产生什么? 二师兄:嗯。。那要做个判断。。 void strncpy(char *dest, char *src, size_t n){ size_t len = strlen(src) > n ? n : strlen(src); for (size_t i = 0; i < len; i++) { *(dest + i) = *(src + i); }}面试官:如果strlen(dest) < n呢? ...
四叉树是一种空间索引(划分)技术,通过递归地将一整块区域平均划分为4个子区域,每个区域托管肯定数量的二维点,于是任意一个二维点都能够依据它的坐标疾速找到所述的子区域,失去它的邻点。 具体介绍略过,只有会实现二叉树的二分查找,就不难领会四叉树划分的思维。因为查找邻点的速度十分快,所以罕用于游戏中的碰撞检测,GIS的地理位置索引等。 上面是我实现的一种传统四叉树。 节点定义: #include <vector>#include <cstdlib>#include <ctime>#include <iostream>//二维数据点定义typedef struct Point_2D { int x; int y;}Point;//节点定义typedef struct Quad_Tree_Node { Point start_xy; //左上角起始点 int width; //宽度 int height; //高度 int capacity; //节点容量 bool is_leaf; //叶节点标记 std::vector<Point> points; //存储的二维点 Quad_Tree_Node* lu_child; //子节点,左上 Quad_Tree_Node* ru_child; //子节点,右上 Quad_Tree_Node* lb_child; //子节点,左下 Quad_Tree_Node* rb_child; //子节点,右下 int depth; //以后节点深度}Quad_Node;二维数据点构造只有横纵坐标,也能够携带别的信息。四叉树节点中须要保留所属子区域的信息,我应用左上角起始点+宽度+高度来定义一块区域。设置了节点容量来限度一块区域能保留的数据量,如果超过容量,节点就要持续决裂。 创立节点办法: Quad_Node* Quad_Tree::create_node_(const Point& start, const int& w, const int& h) { Quad_Node* new_node = new Quad_Node; new_node->start_xy = start; new_node->width = w; new_node->height = h; new_node->capacity = _set_capacity; new_node->is_leaf = true; new_node->lu_child = nullptr; new_node->lb_child = nullptr; new_node->ru_child = nullptr; new_node->rb_child = nullptr; return new_node;}创立节点办法须要左上起始点、宽度和高度三个入口参数。 ...
人不知;鬼不觉,《C++面试八股文》曾经更新30篇了,这是我第一次写技术博客,因为集体能力无限,呈现了不少纰漏,在此向各位读者小伙伴们致歉。 为了不误导更多的小伙伴,当前会不定期的出勘误文章,请各位小伙伴注意。 在《C++面试八股文:C++中,设计一个类要留神哪些货色?》一文中, #include <iostream>struct Foo{};struct Goo{ void f1(Foo& f){std::cout <<"non const function" << std::endl;} void f1(const Foo& f){std::cout <<"const function" << std::endl;}};int main(int argc, char const *argv[]){ Foo foo; Goo goo; goo.f1(foo); //无奈通过编译,error: ‘void Goo::f1(Foo)’ cannot be overloaded with ‘void Goo::f1(Foo)’ return 0;}这里的例子f1和f2办法的参数应该是Foo f和const Foo f,这才是顶层const。在此感激知乎用户 退乎 的揭示。 在《C++面试八股文:std::string是如何实现的?》一文中, 有std::string重载的相干问题,我曾经在 技术勘误:《C++面试八股文:std::string是如何实现的?》 一文中做了具体阐明,在此再次感激知乎用户 肃穆 的斧正。 在《C++面试八股文:override和finial关键字有什么作用?》一文中,final 误拼为 finial,感激知乎用户 DiaoYan 的斧正。 同时,这张内存布局图也有谬误, 应该是这样的: 感激知乎用户 清越 的斧正。 在《C++面试八股文:std::vector理解吗?》一文中, 面试官:push_back和emplace_back有什么区别?除了文中所说的不同点,还要一点:emplace_back能够传入构造函数结构对象,而push_back只能拷贝或挪动对象。 ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第30面: 面试官:什么是空指针? 二师兄:个别咱们将等于0/NULL/nullptr的指针称为空指针。空指针不能被解援用,然而能够对空指针取地址。 int* p = nullptr; //空指针*p = 42; //空指针不能够解援用int** pp = &p //空指针能够取地址面试官:你晓得0/NULL/nullptr三者之间的区别吗? 二师兄:尽管三者都能定义空指针,但三者类型不同。 二师兄:0是int类型,NULL在g++下是一个宏定义,而nullptr是有类型的; #define NULL ((void *)0)typedef decltype(nullptr) nullptr_t;sizeof(0);// 4sizeof(NULL);//8sizeof(nullptr);//8二师兄:在函数重载时,会依据实参的类型抉择重载函数:#include <iostream>void fun(int) {std::cout << "int" << std::endl;}void fun(int*) {std::cout << "int*" << std::endl;}void fun(nullptr_t) {std::cout << "nullptr_t" << std::endl;}int main(int argc, char const *argv[]){ fun(0); //编译通过,匹配fun(int) fun(NULL); //编译失败,能够匹配 fun(int) fun(int*) fun(nullptr_t) fun(nullptr); //编译胜利,匹配fun(nullptr_t) return 0;}二师兄:在C++11之后,倡议应用nullptr定义空指针,因为它时有类型的,编译器可能对它进行类型查看。 面试官:什么是野指针? 二师兄:野指针突出一个野字,这个野就是状态未知的。它可能指向一块未知的区域: int* p; //野指针,指针未初始化*p = 42; //对野指针解援用,未定义的操作面试官:什么是垂悬指针? 二师兄:垂悬指针是指指针指向的内容已被开释,指针指向的对象的生命周期已完结。 int* p = new int(42);delete p;*p = 1024; //垂悬指针,指针指向的对象已被开释int* p = nullptr;{ int i = 42; p = &i;}*p = 1024; //垂悬指针,指向的对象的生命周期已完结面试官:如何解决空指针、野指针、垂悬指针带来的问题? ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第29面: 面试官:什么是构造函数? 二师兄:构造函数是一种非凡的成员函数,用于创立和初始化类的对象。构造函数的名称与类的名称雷同,并且没有返回类型。构造函数在对象被创立时主动调用。 struct Foo{ Foo(int v):val(i){} //构造函数private: int val;};面试官:什么是默认构造函数?什么状况下默认构造函数会被创立? 二师兄:没有任何参数的构造函数(所有参数都要默认参数的构造函数也是)。个别定义类时没有显式的申明任何构造函数,默认构造函数会被编译器主动创立。 struct Foo{private: int val;}; //此时默认构造函数会被创立二师兄:当然就算为类自定义了构造函数,咱们也能够通过Foo()=default为类显式定义一个默认构造函数。 面试官:什么是构造函数初始值列表? 二师兄:是为了初始化成员变量所传入的参数列表: class Foo{public: Foo(int i, long l):ival_(i),lval_(l){} //初始值列表private: int ival_; long lval_;};面试官:下面的构造函数和以下的构造函数有什么区别?Foo(int i, long l){ ival_ = i; lval_ = l;}二师兄:这是初始化与赋值的区别。这段代码中的ival_和lval_先被默认初始化,而后被赋值。而初始化列表是间接初始化,少了一步赋值。 面试官:如果把构造函数写成Foo(int i, long l):lval(l),ival_(i){}会有什么问题吗? 二师兄:成员初始化的程序尽量要和定义的程序保持一致。如上面的代码,就是未定义的: class Foo{public: Foo(int i):jval_(i),ival_(jval_){} //未定义的行为,因为ival先被初始化,这时候jval是未定义的private: int ival_; int jval_;};面试官:什么是委托构造函数? 二师兄:构造函数在结构对象的时候把一部分工作委托给其余构造函数进行结构,这是C++11引入的新个性: class Foo{public: Foo(int i, long l):ival_(i),lval_(l){} Foo(int i):Foo(i,0){} //委托给Foo(int i, long l)private: int ival_; long lval_;};面试官:如果构造函数没有初始化任何成员变量,应用这个构造函数会产生什么? ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第27面: 面试官:晓得std::unordered_set/std::unordered_map吗? 二师兄:晓得。两者都是C++11引入的新容器,和std::set和std::map性能相似,key惟一,unordered_map的value可变。 二师兄:不同于set/map,unordered_set/unordered_map都是无序容器。 面试官:那你晓得它们底层怎么实现的吗? 二师兄:两者底层应用哈希表实现,因而插入、删除和查找操作的均匀工夫复杂度为常数工夫O(1)。 面试官:既然均匀复杂度是O(1),那么是不是能够取代set和map了? 二师兄:这里是均匀的工夫复杂度。哈希表插入、删除和查找操作的最差工夫复杂度是O(n),要比set/map的O(log n)大。 面试官:那你感觉哪些场景适宜set/map,哪些场景适宜unordered_set/unordered_map? 二师兄:set/map实用于须要有序存储和疾速查找的场景,而unordered_set/unordered_map实用于须要疾速插入和查找的场景。 面试官:unordered_set/unordered_map对于key的类型有什么要求吗? 二师兄:因为unordered_set/unordered_map底层采纳哈希表,所以在应用自定义类型作为key的时候,须要通知编译器如何计算此类型的hash值,同时还要通知编译器如何判断两个自定义类型的对象是否相等。以下代码无奈通过编译: #include <iostream>#include <unordered_set>struct Foo{ std::string str; int val;};int main(int argc, char const *argv[]){ std::unordered_set<Foo> uset; uset.insert({"42",42}); uset.insert({"1024",1024}); return 0;}二师兄:此时须要为Foo类型实现bool operator==(const Foo& o) const函数和size_t operator()(const Foo& f) const仿函数,能力通过编译:#include <iostream>#include <unordered_set>struct Foo{ std::string str; int val; bool operator==(const Foo& o) const { return str == o.str && val == o.val; }};struct Hash{ size_t operator()(const Foo& f) const { return std::hash<std::string>()(f.str) ^ std::hash<int>()(f.val); }};int main(int argc, char const *argv[]){ std::unordered_set<Foo,Hash> uset; uset.insert({"42",42}); uset.insert({"1024",1024}); return 0;}二师兄:当然咱们也能够应用std::function或者lambda来代替仿函数,目标都是为了使得编译器晓得如何计算自定义类型的哈希值。 ...
c++有一个代码看起来挺奇怪 QString qstr = "aaa";char* cstr = qstr.toStdString().c_str(); //errstd::string std_str = qstr.toStdString();char *cstr = std_str.c_str(); //ok应用链式调用时候就出错了而离开调用就是好的问问chatgpt才晓得c_str()返回的的是std string的底层数据,如果std string曾经销毁了这个字符串就是野指针了,而离开调用的时候std_str变量到函数结尾才开释就是好的
某日二师兄加入XXX科技公司的C++工程师开发岗位第27面: 面试官:用过std::set/std::map吗? 二师兄:用过。 面试官:能介绍一下二者吗? 二师兄:std::set是一个有序的汇合,其中的元素是惟一的,即每个元素只能呈现一次。个别用于去重和主动排序。 二师兄:std::map同样是有序组合,只不过它不止有key,每个key还对用一个value。其中key是惟一,不可反复,然而value却没有限度。key/value也被称为键值对。 面试官:晓得他们底层应用什么数据结构存储数据的吗? 二师兄:两者都是应用红黑树作为底层的数据结构。红黑树是一种主动均衡的二叉树,它确保插入、删除和查找操作的工夫复杂度都是O(log n)。 面试官:set/map对于key的类型有什么要求吗? 二师兄:因为set/map被称为有序容器,所以对插入进去的key有排序的要求。个别须要为类型实现<比拟办法,以下代码无奈通过编译: #include <iostream>#include <set>struct Foo{ Foo(int v):val(v){} int val;};int main(int argc, char const *argv[]){ std::set<Foo> iset; iset.insert(Foo(1024)); iset.insert(Foo(42)); for(auto it = iset.begin(); it != iset.end(); ++it) { std::cout << it->val << std::endl; } return 0;}二师兄:此时须要为Foo类型实现bool operator<(const T&, const T&)函数,能力通过编译:bool operator<(const Foo& lhs,const Foo& rhs) {return lhs.val < rhs.val;}面试官:依照你的办法,能够实现从小到大的排序。如何实现从大到小的排序? 二师兄:set/map类模板的第二个模板参数能够传入比拟类型,默认比拟类型是std::less<_Key>,咱们能够传入std::greater<T>,此时须要实现bool operator>(const T&, const T&)函数。 二师兄:还有一种办法是手写一个仿函数,重载bool operator()(const T, const T) const函数用于比拟两者的大小: ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第26面: 面试官:deque用过吗? 二师兄:说实话,很少用,根本没用过。 面试官:为什么? 二师兄:因为应用它的场景很少,大部分须要性能、且须要主动扩容的时候应用vector,须要随机插入和删除的时候能够应用list。 面试官:那你晓得STL中的stack是如何实现的吗? 二师兄:默认状况下,stack应用deque作为其底层容器,但也能够应用vector或list作为底层容器。 面试官:你感觉为什么STL中默认应用deque作为stack的底层容器吗? 二师兄:额。。(stack也不须要双端插入啊,不应该vector更好吗。。)不是很分明。。 面试官:没关系。那你晓得deque是如何实现的吗? 二师兄:与vector内存空间间断不同,deque是局部间断的。deque通常保护了一个map(不是std::map),map的每个元素指向一个固定大小的chunk。同时保护了两个指针,指向头chunk和尾chunk。在deque的头部或尾部插入元素时,deque会找到头部或尾部的指针,并通过指针找到对应的chunk。如果chunk中还有未被元素填充的地位,则将元素填充到数组中,如果此指针指向的chunk曾经被元素填满,则须要从新开拓一块固定大小的chunk,并将chunk记录在map中。 面试官:deque的查找、插入、删除的工夫复杂度是什么? 二师兄:dqueue查找的工夫复杂度是O(N),插入要分状况,如果是头插和尾插,工夫复杂度为O(1),如果是两头插入,则是O(N)。删除元素和插入元素的工夫复杂度雷同。 面试官:好的。面试完结,回去等告诉吧。 让咱们来看一下二师兄的体现: 为什么STL中默认应用deque作为stack的底层容器吗?STL默认抉择deque最为stack的底层容器必定是有起因的。vector和list同样能够作为deque的底层容器,让咱们比拟一下三个容器的差别:(只思考头插和尾插,因为stack不须要随机插入) dequevectorlist插入O(1)O(1)O(1)删除O(1)O(1)O(1)从上表中看到,三种容器的插入和是删除的工夫复杂度雷同。 然而如果间断插入时,状况发生变化。vector的capacity被耗尽,元素产生搬移。 list倒没有这个顾虑,然而当元素尺寸很小时,list的空间利用率太低。 deque尽管遍历效率不如vector、随机插入效率不然list,但stack并不需要这两种操作,所以deque是stack底层容器的最佳抉择。 明天的面试分享到这里就完结了,让咱们持续期待二师兄的体现吧。 关注我,带你21天“精通”C++!(狗头)
某日二师兄加入XXX科技公司的C++工程师开发岗位第25面: 面试官:array相熟吗? 二师兄:你说的是原生数组还是std::array? 面试官:你感觉两者有什么区别? 二师兄:区别不是很大,原生数组(非动静数组)和std::array都在栈上开拓空间,初始化的时候须要提供数组长度,且长度不可扭转。有一点区别的是,std::array提供了平安的下标拜访办法at,当下标越界时会抛出异样。 面试官:还有其余区别吗? 二师兄:让我想想。。。在当作参数传递时,原生数组会进化为指针,而std::array会保留类型和长度信息。 面试官:好的。晓得空数组的长度和长度为0的std::array对象有什么区别吗? 二师兄:(这也太刁钻了吧。。)空数组的长度应该是0,然而长度为0的std::array对象的长度是1,因为它是空类。 面试官:如果一个类型的拷贝构造函数和拷贝赋值运算符是被删除的,能够应用std::array存储它吗? 二师兄:当然能够。只是不能传递这个std::array对象,而只能传递这个对象的援用或指针。 面试官:你感觉array和vector的性能哪个好? 二师兄:array的性能更好,array的内存调配在栈上,编译时候确定须要在栈上开拓的空间。vector的元素存在堆上,须要开拓和开释堆内存。但vector更灵便,如果能提前确定数据量,应用reserve函数一次性开拓空间,性能和array没有太大的差距。 面试官:好的。你方才说array能在编译时候确定须要在栈上开拓的空间,请问array在编译时还可能做些什么? 二师兄:比方给定一个array,咱们能够在编译时求它所以元素的和。 #include <iostream>#include <array>constexpr int sum(auto arr){ int res = 0; for (size_t i = 0; i < arr.size(); i++) res += arr[i]; return res;}int main(int argc, char const *argv[]){ constexpr std::array arr = {1,2,3,4,5,6,7,8,9}; constexpr int res = sum(arr); std::cout << res << std::endl; return 0;}//g++ test.cpp -std=c++20 面试官:好的,应用array实现编译期排序,没问题吧? 二师兄:(终于,该来的还是来了!)我尝试一下: #include <iostream>#include <array>constexpr auto sort(auto arr){ for ( int i = 0; i < arr.size() -1; i++) { for ( int j = 0; j < arr.size() - i -1; j++) { if (arr[j] < arr[j + 1]) { auto tmp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = tmp; } } } return arr;}int main(int argc, char const *argv[]){ constexpr std::array arr {10.5, 28.6, 4.4, 23.8, 12.4, 3.8, 19.7, 17.5, 19.1, 0.6, 1.9, 1.5, 25.4, 5.4}; constexpr auto arr2 = sort(arr); for (size_t i = 0; i < arr2.size(); i++) { std::cout << arr2[i] << std::endl; } return 0;}//g++ test2.cpp -std=c++20二师兄:应用了C++20的auto参数类型主动推断个性,实现了相似于泛型的成果。arr能够是任何实现了constexpr opearator<函数的类型。 ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第24面: 面试官:list用过吗? 二师兄:嗯,用过。 面试官:请讲一下list的实现原理。 二师兄:std::list被称为双向链表,和C中手写双向链表实质上没有大的区别。list对象中有两个指针,一个指向上一个节点(node),一个指向下一个节点(node)。 二师兄:与手写双向链表不同的是,list中有一个base node,此node并不存储数据,从C++11开始,此node中蕴含一个size_t类型的成员变量,用来记录list的长度。 二师兄:所以说从C++11开始,size()的工夫复杂度是O(1),在此之前是O(N)。 面试官:是每个node都蕴含一个记录长度的成员变量吗? 二师兄:不是,GCC中的实现只有在header node上记录了长度信息,其余node并没有记录。 struct _List_node_base{ _List_node_base* _M_next; _List_node_base* _M_prev; ...};struct _List_node_header : public _List_node_base{#if _GLIBCXX_USE_CXX11_ABI std::size_t _M_size;#endif...}; 面试官:增加和删除元素会导致迭代器生效吗? 二师兄:并不会,因为在任意地位增加和删除元素只须要扭转prev/next指针指向的对象,而不须要挪动元素的地位,所以不会导致迭代器生效。 面试官:list和vector相比,有哪些劣势?什么状况下应用list,什么状况下应用vector? 二师兄:次要有2点劣势:1.list在随机插入数据不会导致数据的搬移。2.list随机删除也不会导致数据搬移。所以在频繁的随机插入/删除的场景应用list,其余场景应用vector。 面试官:你晓得std::sort和list成员函数sort有什么区别吗? 二师兄:std::sort是STL算法的一部分。它排序的容器须要有随机拜访迭代器,所以只能反对vector和deque。list成员函数sort用于list排序,工夫复杂度是O(N*logN)。 面试官:forward_list理解吗?晓得如何实现的吗? 二师兄:std::forward_list是C++11引入的新容器之一。它的底层是单向链表,引入它的次要目标是为了达到手写链表的性能。同时节俭了局部内存空间。(只有一根指针) 面试官:list在pop_front/pop_back的时候须要留神哪些问题? 二师兄:须要判断list的size()不能为0,如果list为空,pop_front/pop_back会导致coredump。 面试官:你晓得list的成员函数insert和forward_list的成员函数的insert_after有什么区别? 二师兄:两者都能够向特定地位增加元素。不同的是insert把元素插入到以后迭代器前,而insert_after把元素插入到以后迭代器后。 面试官:以下代码的输入是什么? #include <iostream>#include <list>int main(int argc, char const *argv[]){ std::list<int> li = {1,2,3,4,5,6}; for(auto it = li.begin(); it!= li.end(); ++it) { if(0 == *it % 2) li.erase(it); } for(auto& i : li) std::cout << i << " "; std::cout << std::endl;}二师兄:应该是1 3 5。 ...
Qt中的私有类中个别都会蕴含d_ptr这样一个公有类型的指针,指针指向该类对应的公有类,引入这个指针次要是为了解决二进制兼容的问题。 什么是二进制兼容Qt作为一个第三方库,公布后会有很多私有类提供给第三方应用,例如QWidget这类控件类。如果Lib1.0版本中蕴含以下实现。 class Widget { …private: Rect m_geometry;};class Label : public Widget {… String text() const { return m_text; }private: String m_text;};当降级为Lib2.0后,新增了m_stylesheet这样一个成员。 class Widget { …private: Rect m_geometry; String m_stylesheet; // new stylesheet member};class Label : public Widget {public: … String text() const { return m_text; }private: String m_text;};如果第三方利用进行了这样的降级,须要从新编译App,否则会导致解体。究其原因,通过增加了一个新的数据成员,咱们最终扭转了 Widget 和 Label 对象的大小。为什么会这样?因为当你的C++编译器生成代码的时候,他会用偏移量来拜访对象的数据。上面是一个对象在内存外面布局的一个简化版本。| Label对象在 Lib1.0的布局| Label 对象在 Lib2.0的布局 | | m_geometry <偏移量 0> | m_geometry <偏移量 0>| | —————- | m_text <偏移量 1>| | m_stylesheet <偏移量 1> | —————- | | —————- | m_text <偏移量 2>|,在Lib 1.0中,Label的 text 成员在(逻辑)偏移量为1的地位。在编译器生成的代码里,应用程序的办法 Label::text() 被翻译成拜访 Label 对象外面偏移量为1的地位。 在Lib 2.0中,Label 的 text 成员的(逻辑)偏移量被转移到了2的地位!因为应用程序没有从新编译,它依然认为 text 在偏移量1的地位,后果却拜访了stylesheet变量!为什么Label::text()的偏移量的计算的代码会在App二进制文件执行,而不是Lib的二进制文件。 答案是因为Label::text() 的代码定义在头文件外面,最终被内联。 ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第23面: 面试官:vector理解吗? 二师兄:嗯,用过。 面试官:那你晓得vector底层是如何实现的吗? 二师兄:vector底层应用动静数组来存储元素对象,同时应用size和capacity记录以后元素的数量和以后动静数组的容量。如果继续的push_back(emplace_back)元素,当size大于capacity时,须要开拓一块更大的动静数组,并把旧动静数组上的元素搬移到以后动静数组,而后销毁旧的动静数组。 面试官:你晓得新开拓的动静数组的容量是就数组的多少倍比拟适合? 二师兄:这个值在不同的编译器上不是固定的。MSVC 是1.5,而GCC是2。 面试官:有没有什么好的方法晋升vector间断插入效率? 二师兄:有的,如果晓得数据的大略量,咱们能够应用reserve办法间接为vector扩容这个量级。这样在后续的数据插入时就不会因为频繁的capacity被用尽而导致的屡次的数据搬移,从而晋升vector插入效率。 面试官:push_back和emplace_back有什么区别? 二师兄:两者都能够在容器尾部插入元素。在GCC中,如果插入的元素是右值,两者都会move元素到容器。如果是左值,两者都会copy元素到容器。惟一不同的一点是,当C++版本高于C++17时,emplace_back返回以后插入的值的援用,而push_back返回void。 面试官:erase和remove有什么区别? 二师兄:erase属于成员函数,erase删除了元素,remove属于算法库函数,而remove只会把元素挪动到尾部。 面试官:哪些状况下迭代器会生效? 二师兄:迭代器生效次要有两种状况引起:1.插入数据。因为插入数据可能导致数据搬移(size > capacity),所以迭代器生效。2.删除数据。当应用erase删除数据时,被删除数据前面的数据顺次向前移一位。这会导致被删除数据之后的迭代器生效。 面试官:如何疾速的清空vector容器并开释vector容器所占用的内存? 二师兄:有两种办法:第一种,应用clear办法清空所有元素。而后应用shrink_to_fit办法把capacity和size(0)对齐,达到开释内存的作用: #include <iostream>#include <vector>int main(int argc, char const *argv[]){ std::vector<int> vi; vi.reserve(1024); for (int i = 0; i < 1024; i++) vi.push_back(i); std::cout << vi.size() << " " << vi.capacity() << std::endl; //1024 1024 vi.clear(); std::cout << vi.size() << " " << vi.capacity() << std::endl; //0 1024 vi.shrink_to_fit(); std::cout << vi.size() << " " << vi.capacity() << std::endl; //0 0}二师兄:第二种,应用swap办法;#include <iostream>#include <vector>int main(int argc, char const *argv[]){ std::vector<int> vi; vi.reserve(1024); for (int i = 0; i < 1024; i++) vi.push_back(i); std::cout << vi.size() << " " << vi.capacity() << std::endl; //1024 1024 std::vector<int>().swap(vi); //应用长期量(size =0, capacity=0)和vi替换,长期量会立刻析构 std::cout << vi.size() << " " << vi.capacity() << std::endl; //0 0}面试官:你晓得vector<bool>是如何实现的吗? ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第22面: (二师兄好苦逼,节假日还在面试。。。) 面试官:C++的继承理解吗? 二师兄:(不好意思,你面到我的强项了。。)理解一些。 面试官:什么是虚函数,为什么须要虚函数? 二师兄:虚函数容许在基类中定义一个函数,而后在派生类中进行重写(override)。 二师兄:次要是为了实现面向对象中的三大个性之一多态。多态容许在子类中重写父类的虚函数,同样的函数在子类和父类实现不同的状态,简称为多态。 面试官:你晓得override和finial关键字的作用吗? 二师兄:override关键字通知编译器,这个函数肯定会重写父类的虚函数,如果父类没有这个虚函数,则无奈通过编译。此关键字可省略,但不倡议省略。 二师兄:finial关键字通知编译器,这个函数到此为止,如果后续有类继承以后类,也不能再重写此函数。 二师兄:这两个关键字都是C++11引入的,为了晋升C++面向对象编码的安全性。 面试官:你晓得多态是怎么实现的吗? 二师兄:(起开,我要开始装逼了!)C++次要应用了虚指针和虚表来实现多态。在领有虚函数的对象中,蕴含一个虚指针(virtual pointer)(个别位于对象所在内存的起始地位),这个虚指针指向一个虚表(virtual table),虚表中记录了虚函数的实在地址。 #include <iostream>struct Foo{ size_t a = 42; virtual void fun1() {std::cout <<"Foo::fun1" << std::endl;} virtual void fun2() {std::cout <<"Foo::fun2" << std::endl;} virtual void fun3() {std::cout <<"Foo::fun3" << std::endl;}};struct Goo: Foo{ size_t b = 1024; virtual void fun1() override {std::cout <<"Goo::fun1" << std::endl;} virtual void fun3() override {std::cout <<"Goo::fun3" << std::endl;}};using PF = void(*)();void test(Foo* pf){ size_t* virtual_point = (size_t*)pf; PF* pf1 = (PF*)*virtual_point; PF* pf2 = pf1 + 1; //偏移8字节 到下一个指针 fun2 PF* pf3 = pf1 + 2; //偏移16字节 到下下一个指针 fun3 (*pf1)(); //Foo::fun1 or Goo::fun1 取决于pf的实在类型 (*pf2)(); //Foo::fun2 (*pf3)(); //Foo::fun3 or Goo::fun3 取决于pf的实在类型}int main(int argc, char const *argv[]){ Foo* fp = new Foo; test(fp); fp = new Goo; test(fp); size_t* virtual_point = (size_t*)fp; size_t* ap = virtual_point + 1; size_t* bp = virtual_point + 2; std::cout << *ap << std::endl; //42 std::cout << *bp << std::endl; //1024} ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第21面: 面试官:用过STL吗? 二师兄:(每天都用好吗。。)用过一些。 面试官:你晓得STL是什么? 二师兄:STL是指规范模板库(Standard Template Library),是C++区别于C语言的特色之一。 面试官:那你晓得STL的六大部件是什么? 二师兄:别离是容器(container)、迭代器(iterator)、适配器(adaptor)、分配器(allocator)、仿函数(functor)和算法(algorithm)。 面试官:那你晓得有哪些容器吗? 二师兄:STL中容器的数量比拟多,依照类型能够分为程序容器和关联容器。 二师兄:程序容器次要有vector、deque、list、forward_list和array。其中forward_list和array是C++11引入的。 二师兄:关联容器次要有set、map、multiset、multimap、unordered_set、unordered_map、unordered_multiset、unordered_multiamp。其中后四种是C++11新引入的。 面试官:好的。那你晓得迭代器分为哪些品种吗? 二师兄:别离是输出迭代器(Input Iterator)、输入迭代器(Output Iterator)、前向迭代器(Forward Iterator)、双向迭代器(Bidirectional Iterator)和随机拜访迭代器(Random Access Iterator)。 二师兄:其中输出和输入迭代器别离用于读取和写入数据,前向迭代器只能向前拜访而不能向后拜访(forward_list),双向迭代器既可向前也可向后(list),随机拜访迭代器能够通过下标拜访任何非法的地位(vector)。 面试官:你晓得适配器是做什么的吗? 二师兄:适配器是一种设计模式。次要起到将不同的接口对立起来的作用。STL中的容器适配器如stack和queue,通过调用容器的接口,实现适配器所需的性能。 面试官:有理解过分配器吗? 二师兄:分配器次要用于内存的调配与开释。个别容器都会自带默认分配器,很少会本人实现分配器。 面试官:有应用分配器做一些内存调配的工作吗? 二师兄:没有。。。 面试官:晓得仿函数是做什么用的吗? 二师兄:是一个可执行的对象,类型重载了operator()()运算符。 struct Add{ int operator()(int a, int b) {return a +b;}}int a = 42, b = 1024;auto sum = Add()(a,b);//ORAdd add;auto sum = add(1,2);面试官:STL中常见的算法有哪些? 二师兄:个别分为三类,查找、排序和数值操作。 二师兄:查找罕用的有std::find、std::find_if、std::find_first_of等。 二师兄:排序次要用std::sort及其家族的一系列算法。 二师兄:数值操作次要用std::accumulate求和。 面试官:那你晓得STL六大部件之间的分割吗? 二师兄:(想了想)不是特地分明。。。 面试官:好的,回去等告诉吧。 让咱们回顾一下二师兄的体现: 有应用分配器做一些内存调配的工作吗?这里次要是问有没有手写过分配器: template <typename T>class MyAllocator {public: typedef T value_type; MyAllocator() noexcept {} template <typename U> MyAllocator(const MyAllocator<U>&) noexcept {} T* allocate(std::size_t n) { if (n > std::size_t(-1) / sizeof(T)) throw std::bad_alloc(); if (auto p = static_cast<T*>(std::malloc(n * sizeof(T)))) return p; throw std::bad_alloc(); } void deallocate(T* p, std::size_t) noexcept { std::free(p); }};实例中定义了一个名为MyAllocator的模板类,它重载了allocate和deallocate运算符用于分配内存和开释内存。示例中malloc和free函数来调配和开释内存,也能够用new和delete。 ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第20面: 面试官:C++中反对哪些类型转换? 二师兄:C++反对C格调的类型转换,并在C++11引入新的关键字标准了类型转换。 二师兄:C++11引入四种新的类型转换,别离是static_cast、dynamic_cast、const_cast、和reinterpret_cast。 二师兄:static_cast用处最宽泛,除了前面三种类型转换外,其余的类型转换都能应用static_cast实现。 二师兄:dynamic_cast次要用于运行时的从父类指针向子类指针转换,如果转换不胜利则返回nullptr。 #include <iostream>struct Base{ virtual void fun() {}};struct Derived : public Base{ virtual void fun() override {}};int main(int argc, char const *argv[]){ Base* b1 = new Base; Base* b2 = new Derived; Derived* d1 = dynamic_cast<Derived*>(b1); //d1 == nullptr Derived* d2 = dynamic_cast<Derived*>(b2); //d2 != nullptr}二师兄:const_cast次要用于去除指针或援用类型的const属性。此操作可能会导致未定义的行为,所以须要慎用。#include <iostream>void function(const int& val){ int& v = const_cast<int&>(val); v = 42;}int main(int argc, char const *argv[]){ int val = 1024; function(val); std::cout << val << std::endl; //val == 42}//-----------------------------------------------#include <iostream>static constexpr int val_static = 1024;void function(const int& val){ int& v = const_cast<int&>(val); v = 42;}int main(int argc, char const *argv[]){ function(val_static); std::cout << val_static << std::endl;}// Segmentation fault二师兄:reinterpret_cast能够将指针或援用转换为任何类型的指针或援用。reinterpret_cast实现依赖于编译器和硬件,可能导致未定义的行为。#include <iostream>int main(int argc, char const *argv[]){ int i = 42; double d = 42.0; long* l1 = reinterpret_cast<long*>(&i); long* l2 = reinterpret_cast<long*>(&d); std::cout << *l1 << std::endl; //*i1 == 42 std::cout << *l2 << std::endl; //*i2 == 4631107791820423168 X86_64 GCC 11.3 }面试官:好的。既然曾经有C格调的类型转换,C++11为什么还要引入新的类型转换关键字? ...
用于在 Visual Studio 上预编译的 C++ 蕴含(stdc++.h for VS v1.0.0)此文件是 GCC 13.1.0 中 <bits/stdc++.h> 文件的批改版本,与 Visual Studio 兼容。批改由 Xi Xu 实现,他的集体官方主页是 https://xi-xu-zg.github.io/。批改版本是依据 GNU General Public License v3.0 or later 散发的。原始文件是依据 GNU General Public License v3.0 or later 散发的。您能够在 https://gcc.gnu.org/onlinedocs/gcc-13.1.0/libstdc++/api/a00839_source.html 找到原始文件。// C++ includes used for precompiling on Visual Studio// Copyright (c) 2023 Xi Xu// SPDX-License-Identifier: GPL-3.0-or-later// This file is a modified version of the <bits/stdc++.h> file from GCC 13.1.0 that is compatible with Visual Studio.// The modifications are made by Xi Xu whose personal official home page is https://xi-xu-zg.github.io/.// The original file is distributed under the GNU General Public License v3.0 or later.// You can find the original file at https://gcc.gnu.org/onlinedocs/gcc-13.1.0/libstdc++/api/a00839_source.html.// 17.4.1.2 Headers#pragma once// C#include <cassert>#include <cctype>#include <cfloat>#include <ciso646>#include <climits>#include <csetjmp>#include <cstdarg>#include <cstddef>#include <cstdlib>#include <cstdint>// C++// #include <bitset>// #include <complex>#include <algorithm>#include <bitset>#include <functional>#include <iterator>#include <limits>#include <memory>#include <new>#include <numeric>#include <typeinfo>#include <utility>#include <array>#include <atomic>#include <initializer_list>#include <ratio>#include <scoped_allocator>#include <tuple>#include <typeindex>#include <type_traits>#if _HAS_CXX17#include <any>// #include <execution>#include <optional>#include <variant>#include <string_view>#endif#if _HAS_CXX20#include <bit>#include <compare>#include <concepts>#include <numbers>#include <ranges>#include <span>#include <source_location>#include <version>#endif#if _HAS_CXX23#include <expected>#include <stdatomic.h>#if __cpp_impl_coroutine#include <coroutine>#endif#endif// C#include <cassert>#include <cctype>#include <cerrno>#include <cfloat>#include <ciso646>#include <climits>#include <clocale>#include <cmath>#include <csetjmp>#include <csignal>#include <cstdarg>#include <cstddef>#include <cstdio>#include <cstdlib>#include <cstring>#include <ctime>#include <cwchar>#include <cwctype>#include <ccomplex>#include <cfenv>#include <cinttypes>#include <cstdalign>#include <cstdbool>#include <cstdint>#include <ctgmath>#include <cuchar>// C++#include <complex>#include <deque>#include <exception>#include <fstream>#include <functional>#include <iomanip>#include <ios>#include <iosfwd>#include <iostream>#include <istream>#include <iterator>#include <limits>#include <list>#include <locale>#include <map>#include <memory>#include <new>#include <numeric>#include <ostream>#include <queue>#include <set>#include <sstream>#include <stack>#include <stdexcept>#include <streambuf>#include <string>#include <typeinfo>#include <utility>#include <valarray>#include <vector>#include <array>#include <atomic>#include <chrono>#include <codecvt>#include <condition_variable>#include <forward_list>#include <future>#include <initializer_list>#include <mutex>#include <random>#include <ratio>#include <regex>#include <scoped_allocator>#include <system_error>#include <thread>#include <tuple>#include <typeindex>#include <type_traits>#include <unordered_map>#include <unordered_set>#include <shared_mutex>#if _HAS_CXX17#include <any>#include <charconv>// #include <execution>#include <filesystem>#include <optional>#include <memory_resource>#include <variant>#endif#if _HAS_CXX20#include <barrier>#include <bit>#include <compare>#include <concepts>#include <format>#include <latch>#include <numbers>#include <ranges>#include <span>#include <stop_token>#include <semaphore>#include <source_location>#include <syncstream>#include <version>#endif#if _HAS_CXX23#include <expected>#include <spanstream>#if __has_include(<stacktrace>)#include <stacktrace>#endif#include <stdatomic.h>#include <stdfloat>#endifGit 仓库 GitHub Gitee ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第19面: 面试官:什么是智能指针? 二师兄:智能指针是C++11引入的类模板,用于治理资源,行为相似于指针,但不须要手动申请、开释资源,所以称为智能指针。 面试官:C++11引入了哪些智能指针? 二师兄:三种,别离是shared_ptr、unique_ptr、和weak_ptr。 面试官:说一说三种指针的特色及用处。 二师兄:好的。shared_ptr应用了援用计数(use count)技术,当复制个shared_ptr对象时,被治理的资源并没有被复制,而是减少了援用计数。当析构一个shared_ptr对象时,也不会间接开释被治理的的资源,而是将援用计数减一。当援用计数为0时,才会真正的开释资源。shared_ptr能够不便的共享资源而不用创立多个资源。 二师兄:unique_ptr则不同。unique_ptr独占资源,不能拷贝,只能挪动。挪动过后的unique_ptr实例不再占有资源。当unique_ptr被析构时,会开释所持有的资源。 二师兄:weak_ptr能够解决shared_ptr所持有的资源循环援用问题。weak_ptr在指向shared_ptr时,并不会减少shared_ptr的援用计数。所以weak_ptr并不知道shared_ptr所持有的资源是否曾经被开释。这就要求在应用weak_ptr获取shared_ptr时须要判断shared_ptr是否无效。struct Boo;struct Foo{ std::shared_ptr<Boo> boo;};struct Boo{ std::shared_ptr<Foo> foo;};二师兄:Foo中有一个智能指针指向Goo,而Goo中也有一根智能指针指向Foo,这就是循环援用,咱们能够应用weak_ptr来解决这个文通。Boo boo;auto foo = boo.foo.lock();if(foo){ //这里通过获取到了foo,能够应用}else{ //这里没有获取到,不能应用}面试官:好的。智能指针是线程平安的吗? 二师兄:是的。抛开类型T,智能指针是类型平安的。 面试官:为什么? 二师兄:因为智能指针底层应用的援用计数是atomic的原子变量,原子变量在自增自减时是线程平安的,这保障了多线程读写智能指针时是平安的。 面试官:好的。为什么尽量不要应用裸指针初始化智能指针? 二师兄:因为可能存在同一个裸指针初始了多个智能指针,在智能指针析构时会造成资源的屡次开释。 面试官:为什么不要从智能指针中返回裸指针呢? 二师兄:是因为如果返回的裸指针被开释了,智能指针持有的资源也生效了,对智能指针的操作是未定义的行为。 面试官:智能指针可能持有数组吗? 二师兄:shread_ptr和unique_ptr都能够持有数组。 面试官:那你晓得在开释资源的时候两者有什么不同吗? 二师兄:这个临时还不分明。。 面试官:能够应用动态对象初始化智能指针吗? 二师兄:让我想想。。不能够,因为动态对象的生命周期和过程一样长,而智能指针的析构的时候会导致动态资源被开释。这会导致未定义的行为。 面试官:如果须要在一个类中实现一个办法,这个办法返回这个类的shread_ptr实例,须要留神哪些货色? 二师兄:须要继承std::enable_shared_from_this类,办法返回shared_from_this()。 struct Foo : public std::enable_shared_from_this<Foo>{ std::shared_ptr<Foo> get_foo() { return shared_from_this(); }};面试官:为什么不间接返回this指针? 二师兄:额。。。不太分明,然而这应该是个范式。 面试官:好的,明天的面试完结了,请回去等告诉吧。 明天二师兄的体现不错,让咱们看看一些答复的不太现实的中央吧。 智能指针是线程平安的吗?很遗憾,使用不当的时候并不是。 #include <iostream>#include <memory>#include <thread>#include <chrono>struct Foo{ Foo(int i):i_(i){} void print() {std::cout << i_ << std::endl;} int i_;};int main(int argc, char const *argv[]){ { auto shptr = std::make_shared<Foo>(42); std::thread([&shptr](){ std::this_thread::sleep_for(std::chrono::seconds(1)); shptr->print(); }).detach(); } std::this_thread::sleep_for(std::chrono::seconds(2)); return 0;}// g++ test.cpp -o test -lpthread// ./test // Segmentation fault当咱们向另一个线程传递智能指针的援用时,因为use count并没有加1,在shptr析构时间接销毁了治理的Foo实例,所以在线程中执行shptr->print()会引发coredump。 ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第18面: 面试官:std::string用过吧? 二师兄:当然用过(废话,C++程序员就没有没用过std::string的)。 面试官:std::string("hello")+"world"、"hello"+std::string("world")和std::string("hello")+std::string("world")的后果是什么?为什么? 二师兄:前者和后者的后果都是std::string的对象,内容是“helloworld\0”,而两头的这个表达式无奈通过编译。起因是std::string重载了operator+(const char*)和operator+(const std::string&),然而const char* 却没有重载operator+运算符。 面试官:std::string 有两个API,resize和reserve,你晓得它们之间的区别吗? 二师兄:resize对应的是size,resize能够扭转字符串的大小。reserve对应的是capacity,reserve只能扭转capacity的大小。 二师兄:当resize传入的参数小于字符串的szie时,多余的字符串会被截取。当reserve传入的参数小于capacity时,reserve什么也不会做。 二师兄:当resize传入的参数大于字符串的szie时,减少的字符串会被默认初始化。当reserve传入的参数大于capacity时,capacity会被扩容。 面试官:好的。能够通过下标拜访std::string实例的内容吗? 二师兄:能够的,std::string重载了下标运算符,能够像数组一样通过下标运算取出某个字符。 面试官:你晓得std::string的at成员办法吗? 二师兄: 嗯,和下标运算性能类似,不过不必放心越界问题。能够平安的拜访字符串中的字符。 面试官:既然有at办法了,为什么还要重载下标运算符呢? 二师兄:次要是因为性能上的考量。at尽管保障了不会超出字符串范畴(超出范围抛出异样),然而性能低于下标操作。这就是有舍有得。为了平安应用at,为了性能应用下标操作。C++给了你多个抉择,如何抉择看你的需要。 面试官:那你晓得std::string是如何实现的吗? 二师兄:在string外部保护一个指针,这个指针指向真正的字符串的地位。 面试官:能简略的写一下实现代码吗? 二师兄:好的。 class string{public: string():size_(0),data_(nullptr){} explicit string(const char* c) { size_ = strlen(c); data_ = (char*)malloc(size_+1); memset(data_,0,size_+1); memcpy(data_,c,size_); } size_t size() const {return size_;} const char* c_str() const {return data_;}private: size_t size_; char* data_;};二师兄:在实现append或者+=的时候,须要把以后字符的长度加上append的内容的长度,以此长度申请一块新内存,而后把以后字符串的内存和append 的内容考入新申请的内存中。free掉之前data_指向的内存,而后把data_指针指向新申请的内存。 面试官:好的。这样的实现有一些弊病。如果频繁的对一个std::string对象append内容,会产生什么? 二师兄:是的,因为频繁的malloc和free,会有性能问题。因所以编译器在实现std::string的时候个别会事后申请一块大的内存,这块内存的长度是capacity,当增加的字符串的长度加上以后的字符串长度小于capacity时,间接增加到以后的块上即可。 面试官:好的。针对字符串比拟少的状况,个别编译器会做一些优化,你晓得如何优化的吗? 二师兄:这个如同在哪看过,不记得额。。。 面试官:好的,明天的面试完结了,请回去等告诉吧。 明天二师兄的体现不错,除了最初一个问题,基本上都答上来了。让咱们来看下这个问题: 针对字符串比拟少的状况,个别编译器会做一些优化,你晓得如何优化的吗?咱们能够看看GCC中std::string的实现: typedef basic_string<char> string; _Alloc_hider _M_dataplus;size_type _M_string_length;enum { _S_local_capacity = 15 / sizeof(_CharT) };union{ _CharT _M_local_buf[_S_local_capacity + 1]; size_type _M_allocated_capacity;};这里的_CharT就是char,所以_S_local_capacity等于15。当字符串的长度小于等于15时,间接存在_M_local_buf中,而不须要在堆中申请内存。当字符串长度大于15时,在内存中申请一块内存,这块内存的起始地址保留在_M_dataplus中,这块内存的容量保留在_M_allocated_capacity 中,而字符串的实在长度保留在_M_string_length中。当向字符串中增加字符时,如果增加字符的长度大于 _M_allocated_capacity - _M_string_length,则须要resize,否则间接追加到_M_dataplus保留的内存块中即可。 ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第17面: 面试官:聊一聊指针? 二师兄:好的。 面试官:你感觉指针实质上是什么? 二师兄:这要从内存地址开始说起了。如果有一块容量是1G的内存,假如它的地址是从0x00000000 到0x3fffffff,每一个字节都对应一个地址。当咱们申明一个变量并初始化它时: int a = 42;二师兄:操作系统会调配一块容量为4(sizeof(int))的地址,这块内存的首地址是0x00001000(假如),完结地址是0x00001003,在申请的这4个字节上放入42。当咱们对这个变量取地址时,int a = 42;int* p = &a;二师兄:操作系统会再调配一块内存,这块内存的大小是sizeof(int*),这块内存的起始地址是0x00002000(假如),完结地址是0x00002003(32位操作系统),而后将&a取到的起始地址0x00001000放入0x00002000-0x00002003中。并将刚调配的这块内存的起始地址赋给p。 二师兄:当咱们对p操作时,是对0x00002000-0x00002003这块内存操作,当咱们对*p(解援用)操作时,是对0x00001000-0x00001003这块内存操作。 二师兄:回到问题,我感觉指针的实质就是内存地址。尽管指针指向一块内存地址,但它同时也是一个变量,也能够对指针取地址。如果对指针取地址,同样能够失去一个内存地址(如0x00002000),如果把这个内存地址存起来,那么指向这个内存地址的变量的类型就是二级指针(int**)。 面试官:好的。在0x00002000-0x00002003这块内存中,咱们放入了一个地址是0x00001000,然而并没有这个地址的长度。只晓得一个地址而不晓得长度,怎么能把数据取出来? 二师兄:这次要是因为p的类型是int*,当对p解援用时,编译器晓得理解援用的后果是int类型,所以从0x00001000往后读取4个字节(sizeof(int)),并依照以后CPU的模式(思考大小端)把这四个字节组成一个int类型的变量。 面试官:malloc函数你晓得吧,返回的类型是void*,在free的时候怎么晓得这块内存的大小的呢? 二师兄:额。。这个还不太分明。。 面试官:没关系,明天就到这里,回去等告诉吧。 让咱们来看看让二师兄折戟的这个问题: malloc函数返回的类型是void*,在free的时候怎么晓得这块内存的大小的呢?这里牵扯到malloc和free的实现形式,不同的厂商实现的形式不尽相同。以ptmalloc为例,当应用malloc申请size = 16的内存时,malloc会从内存池中调配一块sizeof(chunk)+16长度的内存。chunk段保留了一些前后chunk的信息,也保留了这块内存的大小(16)。malloc函数返回的地址是0x00001000,而在free(0x00001000)时,free函数会用0x00001000减去特定值(sizeof(chunk)),失去chunk的起始地址,从chunk中获取这块内存真正的尺寸,从而实现free的工作。抛开内存的利用率不说,这是一个十分美好的实现! ![]() 好了,今日份面试到这里就完结了。关注我,每天带你学习一个C++小常识! 关注我,带你21天“精通”C++!(狗头)
某日二师兄加入XXX科技公司的C++工程师开发岗位第16面: 面试官:什么是左值,什么是右值? 二师兄:简略来说,左值就是能够应用&符号取地址的值,而右值个别不能够应用&符号取地址。 int a = 42; //a是左值,能够&aint* p = &a;int* p = &42; //42是右值,无奈取地址二师兄:个别左值存在内存中,而右值存在寄存器中。int a = 42, b = 1024;decltype(a+b); //类型为右值,a+b返回的值存在寄存器中decltype(a+=b); //类型为左值,a+=b返回的值存储在内存中二师兄:严格意义上分,右值分为纯右值(pvalue)和将亡值(xvalue)。C++中,除了右值残余的就是左值。42; //纯右值int a = 1024;std::move(a); //将亡值面试官:C++98/03中曾经有了左值,为什么还要减少右值的概念? 二师兄:次要是为了效率。特地是STL中的容器,当须要把容器当作参数传入函数时: void function(std::vector<int> vi2){ vi2.push_back(6); for(auto& i: vi2) { std:: cout < i << " " ;} std::cout << std::endl;}int main(int argc, char* argv[]){ std::vector<int> vi1{1,2,3,4,5}; function(vi1); return 0;}二师兄:当咱们要把vi1传入函数时,在C++98/03时只能通过拷贝构造函数,把vi1中所有的元素全副拷贝一份给vi2,拷贝实现之后,当function函数返回时,vi2被析构,而后vi1被析构。 二师兄:在C++11及之后,咱们能够通过std::move()把vi1强制转为右值,此时在初始化vi2时执行的不是拷贝结构而是挪动结构: void function(std::vector<int>&& vi2){ vi2.push_back(6); for(auto& i: vi2) { std:: cout < i << " " ;} std::cout << std::endl;}int main(int argc, char* argv[]){ std::vector<int> vi1{1,2,3,4,5}; function(std::move(vi1)); return 0;}二师兄:这里只进行了一次结构。一次挪动(当元素特地多时,挪动的老本绝对于拷贝根本能够疏忽不记),一次析构。效率失去很大的晋升。 ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第15面: 面试官:理解auto关键字吗? 二师兄:嗯,理解一些(我很相熟)。 面试官:说一说auto的用法吧? 二师兄:auto次要是为了编译器进行类型推导。比方: auto i = 42; //i 被推导位int型std::vector<int> vi;for(auto it = vi.cbegin(); it != vi.cend(); ++it){ std::cout << *it << std::endl;} //迭代器的类型又臭又长auto l_fun = [](int a, int b){return a+b;} //lambda的类型基本上不可能手写进去二师兄:也能够作为函数返回类型的占位符:auto add(int a, int b)->(decltype(a+b)){ return a + b;} //C++11auto add(int a, int b){ return a + b;} //C++14及当前二师兄:在C++20中还能够推导参数的类型,从而实现相似模板的成果:auto add(auto a,auto b){ return a+b;} //C++20 此时能够这样应用 auto res = add(std::string("hello"),"world");//相似以下成果,不过下面的写法更简洁template<typename T,typename U>auto add(T&& t, U&& u)->decltype(t+u){ return t + u;}面试官:嗯,不错。你感觉auto有哪些益处? ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第14面: 面试官:在C++中,有哪些可执行体? 二师兄:可执行体? 面试官:也就是可调用对象。 二师兄:让我想一想。函数、函数指针、类的静态方法、类的成员办法、仿函数、lambda表达式。 面试官:能说一说他们之间有什么区别吗? 二师兄:好的。函数是一段代码,执行特定工作,承受参数并返回值。 int add(int a, int b){ return a + b;}二师兄:函数指针是指向函数的指针。int add(int a, int b){ return a + b;}using addptr = int(*)(int,int);addptr ptr = &add; //函数指针int sum = addptr(1,2);二师兄:类的静态方法和函数基本一致,只不过此静态方法属于整个类所有。而成员办法则只能通过类的实例来调用。class Foo{public: static int add(int a, int b) { return a + b;} //静态方法 int add2(int a, int b) { return a + b; } //成员办法};int sum1 = Foo::add(1,2);Foo f;int sum2 = f.add2(2,1);二师兄:仿函数是一个类或构造体,重载了()运算符。struct Add{ int operator()(int a, int b) { return a+ b;} };int sum = Add()(1,2);二师兄:lambda表达式在C++11时被引入,实质上是是一个匿名函数。auto add = [](int a, int b) {return a + b;};int sum = add(1,2);//orint a =1 , b = 2;auto ladd = [a,b](){return a + b;};int sum = ladd();面试官:你晓得std::function? ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第13面: 面试官:什么是RAII? 二师兄:RAII是Resource Acquisition Is Initialization的缩写。翻译成中文是资源获取即初始化。 面试官:RAII有什么特点和劣势? 二师兄:次要的特点是,在对象初始化时获取资源,在对象析构时开释资源。这种技术能够防止资源正路或内存透露,进步程序的健壮性和可维护性。 面试官:应用RAII能够做哪些事件? 二师兄:次要能够治理动态分配的内存而不须要手动申请和开释,治理锁不须要手动加锁和解锁,治理句柄不须要手动关上和敞开。 面试官:你晓得有哪些C++规范库中曾经存在的类型应用了RAII技术? 二师兄:有std::shared_ptr、std::unqiue_ptr和std::lock_guard及std::unqiue_lock。 面试官:晓得std::lock_guard如何实现的吗? 二师兄:应该是在结构的时候锁定,在析构的时候解锁。 class lock_gurad{public: lock_gurad(std::mutex& mtx):mtx_(mtx){mtx_.lock(); } ~lock_gurad(){mtx_.unlock();}private: std::mutex mtx_;};面试官:好的。明天的面试到此结束,回去等告诉吧。让咱们认真看一下二师兄的这段代码,不难发现存在以下问题: std::mutex mtx_不能间接保留值,因为mutex没有拷贝构造函数,所以这里须要应用援用。须要在构造函数前加上explicit关键字,避免编译器隐式转换lock_gurad不能拷贝(因为会有多个实例治理一个互斥锁,导致不可预测的行为),所以要删除拷贝构造函数和拷贝赋值运算符。修复后的代码如下: class lock_guard{public: explicit lock_guard(std::mutex& mtx) : mtx_(mtx){ mtx_.lock(); } ~lock_guard(){ mtx_.unlock(); } lock_guard(const lock_guard&) = delete; lock_guard& operator=(const lock_guard&) = delete;private: std::mutex& mtx_;};好了,今日份面试到这里就完结了。 关注我,带你走进二师兄的C++面试生涯。 关注我,带你21天“精通”C++!(狗头)
某日二师兄加入XXX科技公司的C++工程师开发岗位第12面: 面试官:理解位运算吗? 二师兄:理解一些。(我很相熟) 面试官:请列举以下有哪些位运算? 二师兄:按位与(&)、按位或(|)、按位异或(^),按位取反(~)、左移(<<)和右移(>>)。 面试官:好的。那你晓得位运算有什么劣势吗? 二师兄:劣势次要有两点:1.速度快。2.节俭寄存器/内存空间。 面试官:在C++中,如何解决int型正数最高位(是1)的左移或者右移? 二师兄:不同编译器解决的办法不同。此操作在C++中属于未定义的行为。所以不要应用带符号的整数加入位运算。 面试官:如何判断一个数是不是2的整数次方? 二师兄:应用这个数与这个数-1按位与,如果后果是0,则这个数是2的整数次方,否则不是。 bool is_power_of_two(unsigned int n){ return n & (n-1) == 0;}面试官:如何应用位运算替换两个数,而不能申请额定的空间? 二师兄:能够应用异或操作,原理是一个数异或两次同一个数,后果等于原值。 void swap(unsigned int& a, unsigned int& b){ a = a ^ b; // a = a ^ b; b = a ^ b; // b = a ^ b ^ b = a; a = a ^ b; // a = a ^ b ^ a = b;}面试官:如何获取一个数字中的某一位是0 还是1 ? ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第11面: 面试官:在C++中,你都晓得都哪些运算符? 二师兄:啥?运算符?+-*/=这些算吗? 面试官:嗯,还有其余的吗? 二师兄:当然还有,+=,-=,*=,/=,==,还有逻辑运算,位运算等。 面试官:好的。那你晓得这些运算的优先级吗? 二师兄:(面试官傻逼吧,这谁记得住)记不住了,个别我都会加括号来表白我的意思。 面试官:好的。那你晓得上面这段程序会输入什么吗? #include <iostream>int main(int argc, char const *argv[]){ int i = 0; std::cout << i++ + ++i << std::endl;}二师兄:应该是2吧。 面试官:那你晓得运算符的求值程序吗? 二师兄:应该是从左向右? 面试官:%这个符号是求余的你应该晓得吧。如果应用一个int型正数对另一个int型正数求余数,后果是负数还是正数? 二师兄:应该是负数吧。 面试官:对一个整数判断是否位true或者false,能够用if(val == true)或if(val == false)吗? 二师兄:不能,因为在应用int型与bool型比拟时,会把true和false转换成int型,下面两个表达式等同于if(val == 1)和if(val == 0),与咱们的本意不符。 面试官:你晓得a=a+1和a+=1这两者的区别吗? 二师兄:两者应该没啥区别吧? 面试官:你晓得++i和i++的区别吗? 二师兄:前者返回i+1,后者返回i。之后i会被+1。 面试官:C++11中的左值右值你相熟吗? 二师兄:理解过一些(我很相熟)。 面试官:那你晓得算数运算符、逻辑运算符、位运算符返回的后果是左值还是右值吗? 二师兄:额,让我想想。。。应该都是右值。 面试官:好的。那你晓得点(.)和箭头(->)运算符返回的后果是左值还是右值吗? 二师兄:额。。。应该都是左值。(应该是的吧。。。) 面试官:咱们都晓得,一个int型的正数,在最高(31)位上的值是1。那么如果对这个正数进行右移(>>)操作,最高位上的1会被挪动吗? 二师兄:应该不会。可能跟编译器实现无关。 面试官:你晓得重载运算符要留神哪些事项吗? 二师兄:其实工作中很少重载运算符,我感觉最重要的事项就是尽量不要重载运算符。(我好机智。。) 面试官:好的,明天的面试完结了,回去等告诉吧。 对于今日二师兄的体现,让咱们来回过头看一下: 在C++中,你都晓得哪些运算符?除了算术运、逻辑、位、关系、等于运算符,如sizeof,decltype、new/delete也属于运算符。 晓得上面这段程序会输入什么吗? 应该是2吧。 这里程序的输入的后果是不确定的。起因会在上面讲。 那你晓得运算符的求值程序吗?在C++的规范中,大部分的运算符两侧的表达式的求值程序是不确定的。在上个例子中,可能会先计算i++,而后再计算++i,也可能反过来。所以这里的后果不缺行,属于未定义的行为(undefined behavior)。 那么C++中有没有确定求值程序的二元运算符呢?答案是有的,而且仅有这四个:&&,||,:?,,。C++规范保障这四个运算符的求值程序是从左到右确定的。 你晓得a=a+1和a+=1这两者的区别吗?此两者有一点区别,那就是前者的a会被求值2次,而后者只会被求值1次。如果开启编译器优化,有可能会被优化雷同的成果。 你晓得++i和i++的区别吗?除了二师兄的答复外,前置++的效率要高于后者,因为前者不须要缓存值,以用来返回。 晓得点(.)和箭头(->)运算符返回的后果是左值还是右值吗?这里箭头运算符的调用者必定是左值(想想看为什么),然而号运算符的调用者可不肯定是左值。所以箭头运算符的后果肯定是左值,当点运算符的调用者是左值时,返回值时左值,当点运算符的调用者是右值时,返回值时右值。 正数进行右移(>>)操作,最高位上的1会被挪动吗很遗憾,这里答复会和不会都不对,因为这也属于未定义的行为(undefined behavior)。 所以不要对有符号的类型进行按位操作,最好的状况是正好以后编译器的实现和你的预期吻合,以后平台运行无异样。一旦移植代码,可能会遇到意想不到的问题。 ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第10面: 面试官:理解sizeof操作符吗? 二师兄:稍微理解(不就是求大小的嘛。。) 面试官:请讲以下如何应用sizeof? 二师兄:sizeof次要是求变量或者类型的大小。间接应用sizeof(type)或sizeof(var)即可。 面试官:嗯。sizeof(int*)、sizeof(int**) 和sizeof(int[4])各返回什么? 二师兄:前两者的返回值相等。在32位操作系统中返回4,64位操作系统中返回8。sizeof(int[4])返回16,是因为sizeof运算时数组不会进化为指针。 面试官:如果一个int* p = nullptr,那么对其进行sizeof(*p)会产生什么? 二师兄:返回4。起因是sizeof在编译时求值,sizeof只须要获取*p的类型,并不对*p求值。所以不会产生段谬误。 面试官:上面三个szieof运算符,各返回什么? #include <iostream>#include <string>int main(int argc, char const *argv[]){ const char* str1 = "hello"; char str2[] = "hello"; std::string str3 = "hello"; std::cout << sizeof(str1) << std::endl; std::cout << sizeof(str2) << std::endl; std::cout << sizeof(str3) << std::endl;}二师兄:第一个返回4或8,因为它是个指针,第二个是个数组,不过开端有个\0结束符,所以它的值是6,第三个不分明,然而等于sizeof(std::string)。 面试官:好的。应用sizeof对以下两个构造体求大小, #include <iostream>struct Foo{ char c; int i; double d;};struct Goo{ char c; double d; int i;};int main(int argc, char const *argv[]){ std::cout << sizeof(Foo) << std::endl; std::cout << sizeof(Goo) << std::endl;}二师兄:sizeof(Foo)应该等于16,而sizeof(Goo)应该等于24。因为须要内存对齐。 ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第9面: 面试官:C++中,设计一个类要留神哪些货色? 二师兄:设计一个类次要思考以下几个方面:1.面向对象的封装、继承及多态。2.big three或者big five。3.运算符和函数重载、动态成员、友元、异样解决等相干问题。 面试官:请介绍一下面向对象的三个个性。 二师兄:封装是将类的函数和数据封装起来,内部不能间接拜访类的数据,而是须要通过办法拜访数据。继承是指一个类能够继承另一个类的属性和办法。多态是指一个对象能够体现出多种状态。 面试官:请问多态是如何实现的? 二师兄:多态的是通过父类的指针或援用指向子类的对象实现的。在对象中保护一个虚指针(vtptr),这个指针指向一个虚表(vtable),当用户通过父类对象拜访子类的办法时,通过查问虚表中对应的办法的地址,并跳转到此地址执行间接拜访对象的办法。所以多态是有一点点运行时开销的。 面试官:你方才所说的big three和big five是什么? 二师兄:(嘿嘿,被装到了)类的big three别离是拷贝构造函数(copy constructor)、拷贝赋值运算符(copy assignment)和析构函数。而类的big five则多了两个,别离是挪动构造函数(move constructor)和挪动赋值运算符(move assignment)。前面两个是C++11之后引入的。 面试官:好的。那你晓得为什么要引入挪动结构和挪动赋值吗? 二师兄:次要是为了效率。挪动结构和挪动赋值不须要把所有的数据从新拷贝一遍,而是霸占了被挪动对象的数据的所有权。代价是被挪动对象在被挪动后不能应用。 面试官:嗯。那你晓得为什么挪动结构和挪动赋值都要加上noexcept关键字吗? 二师兄:额。。。如同不让抛异样? 面试官:你晓得类的动态成员变量须要留神哪些问题吗? 二师兄:要留神哪些问题?额。。。 面试官:在成员办法后加const是为什么? 二师兄:次要是为了束缚这个成员办法不更改对象的任何数据。 面试官:还有其余的起因吗? 二师兄:如同没有了吧。。。 面试官:类的成员办法能够通过const符号重载吗? 二师兄:这个,,应该能够吧。。 面试官:你晓得什么是类的成员办法的援用限定符吗? 二师兄:没有据说过耶。。。 面试官:好的,回去等告诉吧。 让咱们来看一看今日二师兄的体现吧, 为什么挪动结构和挪动赋值都要加上noexcept关键字?因为在应用挪动语义时,通常会将资源的所有权从一个对象转移到另一个对象,而不是复制资源。如果抛出异样,那么在转移资源的过程中可能会呈现问题,导致资源透露或其余不可预测的行为。 另外,加上 noexcept 关键字还能够进步代码的性能,因为编译器能够在不必要的状况下进行优化。 类的动态成员变量须要留神哪些问题?动态成员变量的初始化程序是不确定的。如果一个动态成员变量依赖于另一个动态成员变量的值,要确保第二个动态化成员先被初始化,否则程序可能会呈现未定义的行为。 动态成员变量的值能够被多个实例同时批改,因而在多线程拜访动态成员时要留神数据竞争问题。动态变量的生命周期与程序的生命周期雷同,因而它们可能会占用大量的内存。 在成员办法后加const是为什么?一是能够束缚此办法不会更改对象的任何数据。二是cosnt对象也能够拜访此成员办法。 #include <iostream>struct Foo{ void f1(){std::cout <<"f1" << std::endl;} void f2() const{std::cout <<"f2" << std::endl;}};int main(int argc, char const *argv[]){ Foo foo; foo.f1(); foo.f2(); const Foo& foo2 = foo; foo2.f1(); //这里无奈通过编译,因为const对象无法访问非const 办法 foo2.f2(); //这里能够通过编译}类的成员办法能够通过const符号重载吗?这是一个很好的问题,预计很多人没有思考过。先说答案,底层const能够,而顶层const不能够。 ...
某日二师兄加入XXX科技公司的C++工程师开发岗位第8面: 面试官:C++中,函数的参数应该传值还是传援用? 二师兄:要看参数的用处。如果是出参,必须传援用。如果是入参,次要思考参数类型的大小,来决定传值还是传援用。 面试官:为什么不应用指针? 二师兄:传指针也称之为传援用(pass by refereence)。然而在C++中不举荐应用指针,因为指针须要判断是否位空,而援用则无需放心。 面试官:入参传值还是传援用有什么规范吗? 二师兄:没有规范吧。个别参数类型大小大于16字节(64位操作系统)时,传援用成果要好于传值。 面试官:为什么? 二师兄:因为传值相当于拷贝结构,当变量的类型尺寸大于16字节时,拷贝结构所耗费的工夫大于解援用。此时传援用收益更高。 面试官:你晓得函数参数传递的过程中,寄存器起什么作用吗? 二师兄:额。。理解过一些。个别简略的数据类型,能够通过寄存器传递。而对于结构图和类则应用是通过堆栈传递参数。当然不同的编译器对此实现不尽相同。 面试官:那么你晓得什么是cdel、stdcall、fastcall、thiscall吗? 二师兄:据说过,不是很理解。。 面试官:好的,回去等告诉吧。 今日二师兄的体现比较稳定,只有最初一问没有答复上来: 你晓得什么是cdel、stdcall、fastcall、thiscall吗?这些都是C++中的函数调用约定。它们决定了函数参数的传递形式和堆栈的清理形式。 cdecl是默认的调用约定,它将参数依照从右到左的程序顺次压入堆栈中,由调用者负责清理堆栈。stdcall将参数依照从右到左的程序顺次压入堆栈中,由被调用函数负责清理堆栈。fastcall将前两个整型或指针参数存储在寄存器中,其余参数依照cdecl约定压入堆栈中。thiscall是用于成员函数的调用约定,它将this指针作为隐式参数传递,并将其存储在寄存器或堆栈中,其余参数依照cdecl约定压入堆栈中。须要留神的是,这些约定可能因编译器和平台的不同而不同。 好了,今日份面试到这里就完结了,小伙伴们,对于明天二师兄的面试,能打几分呢? 关注我,带你21天“精通”C++!(狗头)
某日二师兄加入XXX科技公司的C++工程师开发岗位第7面: 面试官:C++中,static和const的关键字有哪些用法? 二师兄:satic关键字次要用在以下三个方面:1.用在全局作用域,润饰的变量或者函数为动态的,限度在本文件内应用。2.办法内润饰润饰动态局部变量,在第一次拜访的时候初始化。3.内类润饰成员函数和成员变量,此函数或变量由类持有,而非类的对象持有。 二师兄:const关键字次要也有三个用处:1.润饰函数参数,则在函数外部不能够批改此参数。2.润饰类的成员办法,外表此办法不会更改类对象的任何数据。3.润饰变量,表明变量不能够被修该。 面试官:当初两个指针,一个是 const int*,另一个是int * const,两者有什么区别? 二师兄:这要要看const和* 的地位,const在前称为常量指针,const在后称为指针常量。常量指针的指针局部可变,指针指向的内容不可变。指针常量则相同,指针指向的内容可变,指针不可变。 面试官:嗯,那么你晓得顶层const和底层const吗? 二师兄:额。。。不晓得。。。 面试官:晓得constexpr关键字的作用吗? 二师兄:理解一些。constexpr关键字润饰的变量具备编译器求值的性能。润饰的函数是否在编译器求值要取决于传入的参数是不是编译器确定的,这属于元编程的领域。 面试官:嗯,那你晓得consteval和constinit这两个关键字的作用吗? 二师兄:额。。。理解过一些,遗记了。 面试官:好的,回去等告诉吧。 让咱们来复盘一下今日二师兄的体现: const在前称为常量指针,const在后称为指针常量。这里的表述仁者见仁智者见智。然而在赫赫有名的《C++ Primer Edition 5》中文版中,const int* 被称为指向常量的指针,而int* const则被称为常量指针。这种表述更容易了解两种指针的差别。 你晓得顶层const和底层const吗?这里的顶层const和底层const概念次要是为了辨别const润饰的是变量自身还是变量指向的内容。 int i = 42;const int ci = 42;//顶层constconst int* pi = &i; //磁层const,因为const润饰的是i所在的地址,不能通过pi去批改i,然而能够批改piint* const pi2 = &i;//顶层const,因为pi2无奈批改const int& ri = i; //底层const,不能通过ri批改i在传递参数时,顶层const能够被疏忽,而底层const不能被疏忽。 你晓得consteval和constinit这两个关键字的作用吗?还记得constexpr是否在编译时求值要取决于传入的参数吗?如果传入的参数是编译时确定的,constexpr函数就会在编译时求值,反之则会在运行时求值。 constexpr int add(int a, int b){ return a+b;}constexpr int sum1 = add(1,2); //编译时求值int a = 1, b = 2;int sum2 = add(a,b); //运行时求值当应用consteval润饰函数和变量时,如果不能在编译时求值,则编译谬误。 ...
某日二师兄加入XXX科技公司的C++工程师开发岗位6面: 面试官: 如何在堆上申请一块内存? 二师兄:罕用的办法有malloc,new等。 面试官:两者有什么区别? 二师兄:malloc是向操作系统申请一块内存,这块内存没有通过初始化,通常须要应用memset手动初始化。而new个别随同三个动作,向操作系统申请一块内存,并执行类型的默认构造函数,而后返回类的指针。 面试官:嗯,那你晓得calloc和realloc吗? 二师兄:calloc比malloc多做了一步,就是把申请的内存初始化成0。而realloc则能够扭转以后指针所指向的内存块的大小。 面试官:好的。那么你晓得这些api/操作符失败会产生什么吗? 二师兄:malloc/calloc/realloc失败会返回NULL,而new失败则会抛出异样。 面试官:有没有让new失败不抛出异样的办法? 二师兄:如同有,然而我不记得了。。。 面试官:没关系。。。咱们都晓得new和delete成对呈现,new[]和delete[]也是成对呈现,那么我想问,如果应用new[]创立的对象用delete开释了会产生什么?为什么? 二师兄:额。。。内存透露?对,会产生内存透露。因为内存没有被开释。 面试官:好的。咱们都晓得C++中的内存治理是一个比拟麻烦的事件,当初有个需要,须要在程序中记录被动申请的内存和被动开释的内存,以确保没有产生内存透露。有什么好的办法吗? 二师兄:能够重载new和delete运算符。 面试官:如何重载new和delete运算符? 二师兄:我得查一下材料,这个重载用的很少。。。 面试官:(笑)好吧,最初一个问题,咱们下面始终在探讨堆中的内存的调配和开释,请问一下,如果在栈上调配一块固定的内存?栈中的内存如何开释? 二师兄:额。。。(思考)应用 char[size] ? 应该不须要手动开释。 面试官:好的,回去等告诉吧。 对于二师兄的体现,小伙伴们能给打几分呢?咱们先看看二师兄在面试中体现不太好的中央: 面试官:有没有让new失败不抛出异样的办法?在C++中咱们能够应用以下办法使得new运算符不抛出异样, int* p = new (std::nothrow) int(42);if(p == nullptr){ //调配失败}这个个性须要C++11反对。 再看下一个问题: 如果应用new[]创立的对象用delete开释了会产生什么?肯定会产生内存透露吗?答案是,不肯定。这取决于类型T。咱们先看第一种状况: class Foo{public: Foo():num_(42){}private: int num_;};Foo* pf = new Foo[1024];delete pf;当类型T没有治理资源时,delete pf会把整个申请的1024个Foo所占用的内存全副归还给操作系统,此时并没有内存透露。再看下一种状况: class Foo{public: Foo():num_(new int(42)){} ~Foo(){delete num_;}private: int* num_;};Foo* pf = new Foo[1024];delete pf; 此时会造成内存透露,起因很简略。在执行delete[]时,首先逆序执行每个元素的析构函数,而后再把整块内存归还给操作系统。而delete只会把内存还给操作系统,没有执行析构函数。当类没有资源须要治理时,执行与不执行析构函数都无关紧要,然而当类中须要治理资源时,析构函数的执行就至关重要了。 如何重载new和delete运算符?#include <iostream>#include <cstdlib>#include <map>struct MemoryInfo { size_t size; const char* file; int line;};std::map<void*, MemoryInfo> memoryMap;void* operator new(size_t size, const char* file, int line) { void* ptr = std::malloc(size); memoryMap[ptr] = {size, file, line}; return ptr;}void operator delete(void* ptr) noexcept { auto it = memoryMap.find(ptr); if (it != memoryMap.end()) { std::free(ptr); memoryMap.erase(it); }}#define new new(__FILE__, __LINE__)int main() { int* p = new int(42); for (const auto& [ptr, info] : memoryMap) { std::cout << "Memory allocated at " << ptr << " with size " << info.size << " in file " << info.file << " at line " << info.line << std::endl; } delete p; for (const auto& [ptr, info] : memoryMap) { std::cout << "Memory allocated at " << ptr << " with size " << info.size << " in file " << info.file << " at line " << info.line << std::endl; } return 0;}最初一个问题: ...
某日小二加入XXX科技公司的C++工程师开发岗位5面: 面试官:struct和class有什么区别? 小二:在C++中,struct和class的惟一区别是默认的访问控制。struct默认的成员是public的,而class的默认成员是private的。 面试官:struct、class和union有哪些区别? 小二:union和struct、class在内存布局上不同。union实例的所有成员共享一块内存,这块内存的大小等同于union中成员尺寸最大的一个。 面试官:你能列举一下union适宜应用的场合吗? 小二:额。。(我哪晓得啊,素来没用过。。) 面试官:好的,回去等告诉吧。 对于这三个关键字,置信小伙伴们对前两个关键字并不生疏,然而对于union可能用的会少一些。 union最开始的呈现是为了解决内存占用的问题。例如你须要存储一个IP地址,然而你并不知道IP的类型,你须要: struct IP{ char ipv4[4]; char ipv6[16];}; //sizeof(IP) = 20对于一个IP地址,要么是IPV4要么是IPV6,IPV4和IPV6是互斥的,那么必定有一块空间是被节约了。此时咱们的union就派上用场了: union IP{ char ipv4[4]; char ipv6[16];}; //sizeof(IP) = 16第二种用法是能够通过union判断大小端。(当然也能够通过其余办法) union End{ unsigned char uc; unsigned short us;};bool is_little_end(){ End end{0}; end.us = 1; return end.uc;}小端模式的意思是,低位放在低内存,高位放在高内存。而大端正好相同。当咱们给us赋值1时,如果是大端,内存中的数据应该是这样的:0x00,0x01,这时候如果取uc的值,只能取到0x00。如果是小端,内存中的数据应该是0x01,0x00,此时取到的uc的值是0x01,返回1(true)。 小伙伴们,你还晓得union的其余用法吗? 关注我,带你21天“精通”C++!(狗头)
某日小二加入XXX科技公司的C++工程师开发岗位4面: 面试官:memset、memcpy和strcpy的区别是什么? 小二:memset用于将一块内存设置为特定的值, memcpy用于将一块内存从一个地位复制到另一个地位, strcpy用于将一个字符串从一个地位复制到另一个地位。 面试官:嗯,不错。那么你晓得这三个函数在应用时要留神哪些问题吗? 小二:呃。。。 面试官:好的,回去等告诉吧。 这三个函数在C/C++开发中用到的频率比拟高。 memset函数罕用于POD类型对象的初始化,个别第二个参数都是0,第三个参数是这段内存的长度。 memcoy函数须要留神的点是,函数传入的源地位和指标地位不能有重叠,否则这种操作引发的后果无奈预知。如果你不可能确定传入的源地位和指标地位是否存在重叠,那么能够应用memmove函数代替memcoy。 strcpy存在另一个问题,那就是源字符串的长度可能大于指标区域的长度,导致指标区域内存被超写,造成不可预知的谬误。当然也能够通过应用strncpy函数传入指标区域的大小-1,如果源字符串的长度小于指标区域的大小-1,则全副拷贝,否则最多只拷贝指标长度-1的内容,保障指标字符串以0结尾。 小伙伴们,如果你是小二,你能比小二答复的更好吗? 关注我,带你21天“精通”C++!(狗头)
;;; package --- Summary(require 'package);;(setq package-archives '(("melpa" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/")));;(add-to-list 'package-archives;; '("melpa" . "http://melpa.org/packages/") t)(add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/"))(package-initialize) (when (not package-archive-contents) (package-refresh-contents));;(unless (package-installed-p 'use-package) (package-install 'use-package)) ;(require 'use-package);(setq use-package-always-ensure t) ;;(use-package irony;; :defer t;; :diminish irony-mode;; :init;; (add-hook 'c++-mode-hook 'irony-mode-when-supported);; (add-hook 'c-mode-hook 'irony-mode-when-supported);; :config;; (irony-cdb-autosetup-compile-options)) (add-to-list 'load-path "~/_emacs/custom") ;;(require 'setup-ggtags);;(require 'setup-cedet) (setq emacs_list_dir "~/_emacs/")(add-to-list 'load-path emacs_list_dir);;(add-to-list 'load-path "~/_emacs/ecb")(add-to-list 'load-path "~/_emacs/emacs-eclim")(add-to-list 'load-path "~/_emacs/icicles")(add-to-list 'load-path "~/_emacs/CodePilot");;(add-to-list 'load-path "~/_emacs/cedet-1.1");;(add-to-list 'load-path "~/_emacs/cedet-1.1/common");;(add-to-list 'load-path "~/_emacs/cedet-1.1/speedbar");;(add-to-list 'load-path "~/_emacs/cedet-1.1/eieio");;(add-to-list 'load-path "~/_emacs/cedet-1.1/semantic");;(add-to-list 'load-path "~/_emacs/cedet-1.1/semantic/bovine");;(add-to-list 'load-path "~/_emacs/cedet-1.1/ede");;(add-to-list 'load-path "~/_emacs/cedet-1.1/srecode")(add-to-list 'load-path "~/_emacs/yasnippet")(add-to-list 'load-path "~/_emacs/fuzzy-el")(add-to-list 'load-path "~/_emacs/popup-el")(add-to-list 'load-path "~/_emacs/auto-complete")(add-to-list 'load-path "~/_emacs/company-mode")(add-to-list 'load-path "~/_emacs/auto-complete-clang")(add-to-list 'load-path "~/_emacs/pos-tip")(add-to-list 'load-path "~/_emacs/window-numbering")(add-to-list 'load-path "~/_emacs/highlight-parentheses");;(add-to-list 'load-path "~/_emacs/python");;(add-to-list 'load-path "~/_emacs/git-emacs")(add-to-list 'load-path "~/_emacs/emacs-htmlize")(add-to-list 'load-path "~/_emacs/imenu-list") ...
暴力枚举法(Brute Force)是许多刚接触编程或算法的选手最容易上手,也最显著的算法。尽管暴力枚举往往效率极低,然而能够很快地解决一些问题。 本文将介绍暴力枚举法的办法和优化技巧。留神本文中许多名字并非业余学名,而是我本人定义的,请不要过于纠结。 作者:Eriktse 简介:19岁,211计算机在读,CCPC全国赛金牌,ICPC区域赛银牌服役选手力争以通俗易懂的形式解说编程和算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……)欢送加群一起游玩~QQ群:6002401501.确定解的模式(枚举变量)在进行暴力之前,咱们须要剖析出解的模式,比方要求满足条件的三元组的个数,咱们就枚举所有三元组,查看哪些满足条件。比方咱们要求满足条件的区间的个数,就能够枚举所有的二元组(示意左右端点)。 有些题目解的模式可能不太惟一,须要抉择适合的模式,对于不同的模式抉择不同的枚举办法。比方枚举子集,能够用循环,也能够用dfs,有时候在可能剪枝的状况下,dfs会比循环间接枚举子集快很多。 2.抉择枚举办法常见的枚举办法有间接枚举法和递归枚举法,依据题目不同,有时候也可能有用一些构造方法来进行枚举。 常见的间接枚举(循环)不会超过4层循环,且循环层数固定。 如果你发现循环层数是可变的,往往就要用递归枚举,比方你要枚举所有长度小于等于$n$的一个货色,就须要用到递归。 3.判断函数在枚举出一个解后,咱们须要判断其是否是可行解,于是咱们要写一个判断函数。 这个判断函数能够依据你枚举出的一个解,来判断这个解是否可能。 举个例子咱们要求范畴$[1, n]$的所有质数。 那么咱们解的模式就是一个整数,于是咱们遍历解空间$x \in [1, n], x \in Z^+$的所有解,说人话就是$[1, n]$的所有整数,而后编写判断函数,用于判断一个解$x$是否是可行解,即判断一个数字$x$是否是质数,并执行操作:[YES->将解退出到解集中,NO->舍弃]。 例题ETOJ 1014: straax'aks Array链接:http://oj.eriktse.com/problem.php?id=1014 这道题看数据范畴,显著反对$O(n^3)$,所以能够大胆地暴力,枚举所有$i < j < k$的三元组,并$O(1)$判断是否满足条件即可。 代码: #include <bits/stdc++.h>using namespace std; using ll = long long;const ll N = 1e6 + 9, inf = 8e18;ll a[N]; bool check(ll a, ll b, ll c, ll m){ return (a + b + c) * (a ^ b ^ c) >= m;} void solve(){ int n, m;cin >> n >> m; for(int i = 1;i <= n; ++ i)cin >> a[i]; ll ans = 0; for(int i = 1;i <= n; ++ i) for(int j = i + 1;j <= n; ++ j) for(int k = j + 1;k <= n; ++ k) if(check(a[i], a[j], a[k], m)) { ans ++; //cout << a[i] << ' ' << a[j] << ' ' << a[k] << '\n'; } cout << ans << '\n';} signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int _ = 1; while(_ --)solve(); return 0;}ETOJ 1016: 全排列链接:http://cdn.oj.eriktse.com/problem.php?id=1016 ...
在算法比赛中,有两种排序较为常见,第一种是$O(nlogn)$的排序,个别是基于比拟的排序,第二种是桶排序。两种办法各有优劣,选取适合的排序办法对于解题十分重要。 本文次要解说这两种排序办法,不要只会写冒泡排序了! 作者:Eriktse 简介:19岁,211计算机在读,CCPC全国赛金牌,ICPC区域赛银牌服役选手力争以通俗易懂的形式解说编程和算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……)欢送加群一起游玩~QQ群:600240150基于比拟的排序这里不解说疾速排序的外部原理,咱们只须要晓得在什么场合应用即可。 在C++中,个别不须要本人写疾速排序,用STL中的sort()函数即可(具体的实现办法不肯定是疾速排序),即工夫复杂度为$O(nlogn)$的排序办法。 应用sort()函数前须要引入头文件#include <algorithm>。在vector中,应用sort(v.begin(), v.end())进行排序,在C数组中用sort(a, a + n)进行排序。 个别在数据范畴$1 times 10 ^ 6$以内能够用疾速排序,且元素的大小个别很大。 桶排序当元素的大小比拟小的时候,能够采纳桶排序,其工夫复杂度为$O(n)$,是一个线性复杂度。 它利用的是值域小的个性,开一个数组记录数字呈现的次数,而后下标主动就排序了。 在某些状况下,用桶的思维能够优化复杂度。 例题luogu P1177 【模板】排序链接:https://www.luogu.com.cn/problem/P1177 本题数据范畴反对应用基于比拟的排序,所以间接是应用即可。 <bits/stdc++.h>头文件蕴含了<algorithm>。代码: #include <bits/stdc++.h>using namespace std;signed main(){ ios::sync_with_stdio(0); int n;cin >> n; vector<int> a(n); for(int i = 0;i < n; ++ i)cin >> a[i]; sort(a.begin(), a.end()); //留神上面这句,前面这部分意思是 //当i的迭代器和a.back()迭代器雷同时就换行 for(auto &i : a)cout << i << " n"[&i == &a.back()]; return 0;}luogu P1271 【深基9.例1】选举学生会链接:https://www.luogu.com.cn/problem/P1271 本题须要采纳桶排序,因为基于比拟的排序办法最小复杂度为$O(nlogn)$,可能无奈满足本题需要。 代码: #include <bits/stdc++.h>using namespace std;const int N = 2e6 + 9;int a[N];//a[i]示意数字i呈现的次数signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, m;cin >> n >> m; for(int i = 1;i <= m; ++ i) { int x;cin >> x; a[x] ++; } for(int i = 0;i <= n; ++ i) { for(int j = 1;j <= a[i]; ++ j) { cout << i << ' '; } } return 0;}
某日小二加入XXX科技公司的C++工程师开发岗位2面: 面试官:指针、援用、解援用和取地址是C++中的重要概念,请问它们在语法和语义上有所不同? 小二:指针是一个变量,它存储了一个内存地址。 小二:援用是一个别名,它容许应用一个变量的多个名称来拜访同一个内存地位。 小二:解援用是指应用*运算符来拜访指针所指向的内存地位处的值。 小二:取地址是指应用&运算符来获取变量的地址。 指针是一个变量,它存储了一个内存地址。能够应用指针来拜访和批改存储在该地址处的值。指针能够通过&运算符获取变量的地址,也能够通过*运算符解援用指针来拜访该地址处的值。 int x = 10; int* ptr = &x; *// 获取x的地址* *ptr = 20; *// 批改x的值* 援用是一个别名,它容许应用一个变量的多个名称来拜访同一个内存地位。援用能够看作是指针的一种简化模式,它不须要应用*运算符来解援用,也不须要应用&运算符来获取地址。 int x = 10; int& ref = x; *// ref是x的援用* ref = 20; *// 批改x的值* 解援用是指应用*运算符来拜访指针所指向的内存地位处的值。 int x = 10;int* ptr = &x;int y = *ptr; *// 解援用ptr,获取x的值* 取地址是指应用&运算符来获取变量的地址。 int x = 10; int* ptr = &x; *// 获取x的地址* 总的来说,指针和援用都能够用来拜访和批改变量的值,但它们的语法和语义有所不同。指针须要应用*运算符来解援用,而援用不须要。指针能够被从新赋值指向另一个内存地位,而援用一旦绑定到一个变量上就不能再绑定到另一个变量上。解援用和取地址是指针和援用的基本操作,它们能够用来拜访和批改变量的值以及传递变量的地址给函数。 面试官:十分好,那么你晓得援用是怎么实现的吗? 小二:援用的底层实现的具体细节在不同编译器上可能不同,然而有一种办法就是常量指针。同样,常量指针须要在初始化的时候绑定指向的对象,而且在绑定之后不能批改绑定的对象。这和援用的行为合乎。 面试官:能够对援用取地址吗? 小二:能够,取到的地址和对援用绑定的对象取到的地址雷同。 面试官:那么能够对援用解援用吗? (挖坑) ...
某日小二加入XXX科技公司的C++高级工程师开发岗位1面: 面试官:请问C和C++的区别有哪些? 小二:C++是C的超集。 面试官:还有吗? 小二:... 面试官:面试完结,回去等音讯吧。 小二:淦。 小二的答案对吗? 实际上这句话是有问题的,严格的说,C语言和C++有很多交加,这部分交加占了C语言的大部分,占了C++的小局部。 滞销C++书籍作者侯杰老师曾说过,当咱们议论C++的时候,其实在议论四个方面的货色; 1.面向过程的编程语言2.面向对象的编程语言3.STL(Standard Template Library,规范模板库)4.元编程其中,只有第一条面向过程的编程语言和C语言的定位重合。 那么咱们很容易得出结论,C++独有的性能和个性包含面向对象的编程范式,STL和元编程。那么C中有的,而C++中没有的这部分是什么呢? 次要蕴含一些语法细节的变动,C++在思考到安全性、易用性、性能完整性等一系列问题后的实现与C语言现有的实现不同的中央,以下列出一些,并不是残缺列表: 1.编译器类型大小不同 如C语言的单个字符编译时占用4个字节,而C++只占用一个字节。 如空构造体在C语言中的大小是0,而在C++中的大小是1。 2.类型查看尺度不同 如C语言中的main函数的返回值能够是任何类型,但在C++中main函数的返回值只能是int类型。 如C语言中从void*转换成任意类型的指针,而C++中必须应用强制转换符。 3.关键字雷同但应用不同如const,auto。 那么回到结尾,让咱们从新模仿一编结尾的面试环节: 面试官:请问C和C++的区别有哪些? 小二:C是面向过程的编程语言,而C++不仅是面向过程的编程语言,还是面向对象的编程语言。同时C++蕴含了C中所没有的STL,且C++反对函数是编程/泛型编程/元编程。 面试官:还有吗? 小二:C++号称是C的超集,其实大部分工夫这句话是对的,但还有一些轻微的点C++是不能兼容C语言的,如balabalabala... 面试官:面试完结,请留神放弃手机畅通。 小二:好。 关注我,带你21天“精通”C++!(狗头)
作者 | CQT&星云团队 一、背景代码级品质技术:顾名思义为了服务质量更好,波及到代码层面的相干技术,特地要指出的是,代码级品质技术不单纯指代码召回技术,如动态代码扫描、单元测试等。 钻研代码级品质技术次要有以下几个方面的起因: 1、随着精准测试等概念的衰亡,对代码覆盖率的依赖逐步减轻,代码插桩的性能、准确性、时效性等都成为业界要解决的难题; 2、随着智能化,尤其是基于危险驱动的测试倒退,对代码的了解须要失去冲破,能力更好的从代码实现中开掘危险、判断危险; 3、在黑盒级别的测试,品质工作者往往是通过对象的返回或外在表象观测对下的异样体现进而发现问题,然而其实可能很多潜在的异样并没有到“肉眼”可观测到的级别而导致问题漏出,这时候就须要有更多的对象更细的运行数据来供品质工作者剖析去发现问题的蛛丝马迹,诸如:内存泄露、性能好转等,所以业界有很多prof、火焰图、asan等偏白盒动静检测问题的技术呈现; 4、代码作为产生理论问题最前沿的阵地,大部分问题都能够归因到某段代码不合理,如果能够在代码级别间接召回问题,无论从仿真复杂度、修复老本、定位老本等均会失去极大改善; 5、代码是工程师交换的舞台,通过对代码级品质技术的钻研和标准,能够促成代码更加的具备鲁棒性和更优质的设计如单测晋升可测性等,也能够促成品质保障人员对代码加深掌控与了解,进而在品质保障各类场合施展关键作用。 从领导品质行为、极大晋升召回问题能力、加强代码鲁棒性、晋升人员对代码的掌控力等多个方面,都能够看出代码级品质技术的要害且不可代替作用,百度品质效力平台于2019年开始关注和投入该方向,在代码了解、代码探针、代码品质技术利用等多个层级多方面进行摸索和落地,接下来的文章中会顺次为大家介绍。 二、代码级品质技术架构要了解代码级品质技术的原理和后续的次要利用场景,首先要了解代码从语言到可执行态的根本过程,上面以C++为例说下根本过程: C++从代码到可执行bin文件,次要分为四个阶段:预处理、编译、汇编和链接。 预处理:解决一些#号定义的命令或语句(如#define、#include、#ifdef等),生成.i文件; 编译:进行词法剖析、语法分析和语义剖析等,生成.s的汇编文件,大家相熟的AST形象语法树就在该过程产生; 汇编:将对应的汇编指令翻译成机器指令,生成二进制.o指标文件; 链接:链接分为两种,动态链接:将动态链接库中的内容间接装填到可执行程序中;动静链接:只在可执行程序中记录与动态链接库中共享对象的映射信息。 代码级品质技术的技术原理,次要是获取到该过程的代码片段数据或植入对应的指标代码,来达到对应的品质指标,如获取片段数据能够用来了解代码判断危险,能够用来指结构化代码构造,供主动生成单元测试和代码检测提供根底数据;如植入对应指标代码,能够用来做插桩(即覆盖率采集)或动态数据采集等。 基于上述介绍与了解,咱们把代码级品质技术划分大范畴为两个档次,两个档次内蕴含多个档次,如下图所示: 大档次一:代码了解,CodeC(Code Comprehend):偏底层技术,基于底层AST等能力、剖析出代码的个性(AST、调用链、依赖等)和危险度,通过API、SDK等形式对外提供根底服务 存储层:次要用来代码编译过程的根底数据和对应的数据存储选型调优等,在这个过程次要难点在于根底工具的选型和过程性能的调优,以达到能够在业务利用的指标;剖析层:剖析层是依靠根底的数据,依据特定的要求,对数据进行结构化的建模,如函数调用链、依赖关系等,做好根底的剖析供下层利用;模型层:用于通过剖析层和根底数据,去训练代码存在的潜在危险或危险偏差(性能问题突出等);API层:通过API、SDK等形式对外提供根底服务。该层会遇到泛滥技术挑战,如要适配不同语言的解析器、编译过程;根底框架进行代码调优;剖析过程数据缺失修复等,是一项十分粗疏且有技术挑战的工作,当然咱们在该过程也会摸索出一些技术教训供大家参考。 大档次二:代码级品质技术利用,Code:次要是依靠代码了解的过程或产出,植入对应的信息,以达到对应的品质指标,这个档次利用场景是要害,因而咱们是以解决问题的指标为导向,对该档次进行细分,所以指标或利用场景的不同会使得该档次的分类会一直减少,目前分为以下四类: CodeQ(Code Quality): 与召回问题相干(智能UT、基于规定的代码缺点检测、基于AI的代码缺点检测、火焰图、ASAN等在个分类);CodeP(Code Probe):与动静插桩相干(ccover、covtool、jacoco、gocov等在这层),次要是往代码外面植入探针获取运行行为数据;CodeH(Code Health):评估代码衰弱度(相似sonarcube等)、代码危险度评估用于决策后续的品质行为;CodeDL(Code defect location):代码缺点定位。上面的章节咱们会散布从第二级的档次,为大家做基本原理和过程介绍,后续还会有系列发文再深刻的介绍对应实现内容。 三、代码了解层介绍代码了解是一个以软件程序为对象,对其外部的运作流程进行剖析,获取相干的常识信息,这些信息能够用于软件开发、软件测试、软件维护等各个阶段,旨在对程序进行性能优化和正确性验证。代码了解罕用的剖析方向有动态剖析、动态分析、非源码剖析3类,然而随着LLM大模型的倒退,咱们也正在钻研模型在代码了解畛域的冲破与利用。 动态剖析:是指在不运行代码的形式下,通过词法剖析、语法分析、控制流、数据流剖析等技术对程序代码进行扫描,验证代码是否满足规范性、安全性、可靠性、可维护性等指标的一种代码剖析技术。 动态分析:软件系统在模仿的或实在的环境中执行之前、之中和之后,对软件系统行为的剖析。 非代码剖析:次要是对数据文件、配置文件等非源码文件和源码间进行关联剖析,当代码仓变更时,能感知变更内容对源码、性能的影响。 动态分析多为对程序进行的一些功能测试或性能测试等对程序的运行后果,资源应用状况的相干程序剖析工作。故本大节次要介绍动态程序剖析相干的代码了解技术,不对动静程序剖析做开展。 动态程序剖析在不执行程序程序的状况下对程序进行剖析,剖析的对象能够是针对特定版本的源代码或者二进制文件,通过词法剖析、语法分析、控制流、数据流剖析等技术对程序代码进行扫描,依据不同的剖析指标,失去对应的剖析后果。在学术界和工业界次要利用在软件平安畛域,验证代码是否满足规范性、安全性、可靠性、可维护性;在百度外部,除破绽检测外,动态程序剖析还包含多维度的代码剖析和度量伎俩,在交付零碎和监测零碎中被宽泛应用。 业界动态剖析个别基于以下4种形式开展: 关键字匹配,基于正则表达式剖析基于AST的代码剖析,联合正则表达式和关键字能力长处:联合语法和语义,能够引入作用域等更多概念,更精确。毛病:无奈应答所有场景,另外,基于AST来剖析失去的数据流,疏忽了分支、跳转、循环等影响执行过程程序的条件,短少控制流信息。基于IR/CFG的代码剖析等自制的两头语言数据结构剖析属于以后比拟支流的代码剖析计划,例如被源伞,实现了多种语言生成对立的IR,这样一来对于新语言的扫描反对难度就变得大大减少。IR:是一种相似于汇编语言的线性代码,其中各个指令依照程序执行。其中当初支流的IR是三地址码(四元组),例如llvm的IR。CFG:(Control flow graph)控制流图,在程序中最简略的控制流单位是一个基本块,在CFG中,每一个节点代表一个基本块,每一个边代表一个可控的管制转移,整个CFG代表了整个代码的的管制流程图。基于IR来生成失去CFG。基于QL(Query Language)剖析例如codeQL,把源代码转化成一个可查问的数据库,通过 对源代码工程进行要害信息剖析提取,形成一个关系型数据库。安全漏洞、Bug 和其余谬误被建模为可针对从代码中提取的数据库执行的查问。罕用的动态程序剖析技术: 数据流剖析数据流剖析收集程序运行到不同地位时各个值的信息和它们随工夫变动的信息。污点测验是一个典型的通过数据流剖析进行程序危险检测的例子,它会找到所有的可能被使用者批改的变量(也就是有“污点”、不平安的变量),并阻止这些变量在被修复安全漏洞前被应用。控制流剖析用于剖析程序控制流构造的动态剖析技术,目标在于生成程序的控制流图,在污点剖析、编译器设计、程序剖析、程序了解等畛域都有重要利用。指针剖析指针剖析次要用于剖析指针所有可能指向的对象,它会剖析出一个指针所指向的内存地位和所对应的对象。能够用于调用剖析、代码优化、Bug追究、安全性剖析与验证测试。四、代码级利用之探针代码探针,也是插桩技术,它是在保障被测程序原有逻辑完整性的根底上在程序中插入一些探针(又称为“探测仪”,实质上就是进行信息采集的代码段,能够是赋值语句或采集笼罩信息的函数调用),通过探针的执行并抛出程序运行的特色数据,通过对这些数据的剖析,能够取得程序的控制流和数据流信息,进而失去逻辑笼罩等动静信息,从而实现测试目标的办法。 不同语言的插桩技术有所不同,常见的技术有:ccover、covtool、jacoco、gocov。 CodeP代码探针能够利用在: 代码监控(办法执行耗时等),代码剖析(函数、数据流的跟踪等),业务埋点。 除此之外,代码探针最常见的场景是代码覆盖率采集。 4.1 覆盖率代码覆盖率,是软件测试中的一种度量,形容程序中源代码被测试的比例和水平,所得比例称为代码覆盖率 ,剖析未笼罩局部的代码,从而反推在后期测试设计是否充沛,没有笼罩到的代码是否是测试设计的盲点。覆盖率统计的分类蕴含: 行覆盖率:行覆盖率是最根本的指标,示意是否代码中的每个可执行语句都被执行过; 分支覆盖率:分支笼罩应用一组测试参数来测试是否代码中所有的分支都能被测试到了; 门路覆盖率: 对包含所有分支在内的所有的门路都能测试一遍,这就是门路笼罩; 变更行覆盖率:上一次公布代码后更新的代码的行覆盖率,这个数据能够不便的看出新的代码是否做了测试。 覆盖率的业务应用场景宽泛比方:RD自测、RD准入、QA准出、外包评估、精准测试、集成测试、基线降级评估、灰度测试评估,自动化测试能力评估、众测等。 代码探针实现覆盖率统计的步骤如下: 1、辨认待插桩的函数 2、用codep技术对函数进行插桩,插桩技术分为: 源码插桩: 侵入式的在源代码的根底上替换或者插入另外一些代码编译过程插桩: 在字节码文件中写入桩函数 (如asm、javassit等技术)3、探针采集笼罩信息整合, 统计覆盖率数据 五、代码级利用之召回从召回异样问题的角度介绍两类代码级技术利用:智能UT、SA。与现有异样测试方法进行比照剖析,压力测试、功能测试依赖编译运行且需人工结构异样场景,存在高老本、低召回的问题。而智能UT和SA基于白盒剖析产出的数据能够提前、疾速、低成本、轻量级地召回异样问题。 5.1 智能UT单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证,这里的最小测试单元是指函数或者类。在问题召回层面,UT针对最小单元进行测试,结构数据简略、易于验证正确性,便于后续性能的回归,可能更早地发现问题,定位和解决问题成本低。 ...
阐明最近在学中频信号处理的一些货色,顺便用 QT 写了一个小工具,能够显示信号的时域波形图、幅度谱、功率谱、二次方谱、四次方谱、八次方谱、刹时包络、刹时频率、刹时相位、非线性刹时相位、刹时幅度直方图、刹时频率直方图、刹时相位直方图、眼图、星座图、语谱图、瀑布图。 1. 实现思路工具为纯 C++ 开发,包含频谱计算以及图像可视化。这里简述下实现思路,应用到了以下第三方库: QCustomPlot 绘图库。FFTW 傅氏变换库。FreeGlut 工具包。频谱计算局部,通过调用 FFTW 的 C++ 接口,同时开启 OpenMP 多核反对,能够取得很快的计算速度,比 MATLAB 还快,工具中的所有频谱都是在点击按钮时现算的。在我的电脑上(8 核 Intel i7-6700HQ CPU @ 2.60GHz),实测对 33554432 长度(2 的 25 次方)的双精度浮点数组进行傅里叶变换,两种形式的耗时如下: MATLAB 的 fft 函数耗时 629 毫秒。C++ 调用 FFTW 接口并开启 OpenMP 多核反对,耗时 390 毫秒。图像可视化局部,应用 QCustomPlot 实现图像绘制,并联合 FreeGlut 工具开启 OpenGL 反对,绘图品质及响应速度也失去了较好的晋升,大数据显示并进行交互时不会呈现卡顿。为了进一步晋升速度,还批改了 QCustomPlot 的局部源码,将数据更新接口裸露进去,应用 memcpy() 间接对原始绘图数据所在的内存块进行操作,晋升数据更新的速度。主界面如下: 2. 时域波形图测试信号:2FSK 调制,调制速率 8888 Baud/s,采样率 284416 Hz,信号频率 62216 Hz 与 79992Hz,文件大小 64 MB。波形显示时做了贝塞尔平滑解决。 3. 幅度谱测试信号:2FSK 调制,调制速率 8888 Baud/s,采样率 284416 Hz,信号频率 62216 Hz 与 79992Hz,文件大小 64 MB。 ...
Coke(一):你好,世界C++ Workflow是一款高性能的异步编程范式,自Github开源以来,曾经收货了一万多枚Star,失去了越来越多的认可。 Coke我的项目是一个高性能的协程库,基于C++ 20提供的协程组件开发,提供一组简洁的异步接口,而其后盾则是由C++ Workflow强力驱动。应用Coke能够轻松地创立协程工作,并通过C++ Workflow的调度器高效地调度和执行,Coke心愿为大家带来一种新鲜的体验C++ Workflow的形式。 作为开篇,本文介绍大家进入编程世界常遇到的第一个示例:Hello World。 #include <iostream>#include <chrono>#include "coke/coke.h"coke::Task<> helloworld(size_t n, std::chrono::milliseconds ms) { for (size_t i = 0; i < n; i++) { // 每两次打印之间期待500毫秒,与同步期待不同,异步期待不会阻塞任何线程 if (i != 0) co_await coke::sleep(ms); // 向屏幕打印 Hello World std::cout << "Hello World" << std::endl; }}int main() { // 启动协程工作,并期待其实现 coke::sync_wait(helloworld(3, std::chrono::milliseconds(500))); return 0;}Coke我的项目正在开发中,有想法或倡议的同学欢送提Issue或Pull request! 本系列文章同步公布于集体网站和知乎专栏,转载请注明起源,以便读者及时获取最新内容及勘误。
联合实际重学C++在计算机科学畛域,C++是一种高级编程语言。它反对面向对象编程和泛型编程,并且具备弱小的性能和灵活性。如果你想要成为一名优良的程序员,学习C++是必不可少的。 然而,C++的学习过程并不容易。如果没有联合实际去学习,很容易呈现了解上的艰难。因而,在这篇文章中,咱们将介绍一些联合实际的办法,以帮忙你更好地把握C++。 抉择一个我的项目首先,抉择一个C++我的项目作为学习的指标。这个我的项目能够是一个简略的控制台应用程序,也能够是一个简单的图形界面应用程序。无论你抉择什么样的我的项目,都须要确保它与你的趣味相干,并且不会让你失去急躁。 学习基础知识在开始编写代码之前,你须要先理解C++的基础知识。这包含变量、运算符、数组、指针、函数等等。能够通过浏览书籍、查找在线教程或加入课程来学习这些内容。 编写代码当你把握了根本的C++常识后,就能够开始编写代码了。在这个过程中,你须要留神以下几点: 应用正文:为了更好地了解你的代码,你须要增加正文来解释每一行代码的作用。逐渐构建程序:不要一口气写出整个应用程序。相同,从简略的代码块开始,并逐渐减少性能。测试代码:在编写代码时,你须要测试它是否失常工作。通过输出不同的数据并查看后果,能够确定代码是否正确。学习高级主题当你可能编写根本的C++代码时,就能够开始学习更高级的主题,例如模板、异样解决、多线程编程等等。这些主题能够让你编写更简单和高效的应用程序。 加入开源我的项目最初,加入开源我的项目是一个十分好的学习C++的办法。通过加入开源我的项目,你能够与其余程序员单干,并且有机会参加到大型项目中。此外,你还能够从其余程序员的教训中学习,并且将本人的代码提交到GitHub等开源代码库中。 总之联合实际的形式是学习C++的最佳办法之一。通过抉择一个我的项目、把握基础知识、编写代码、学习高级主题和加入开源我的项目,你能够更快地把握C++编程语言,并成为一名优良的程序员。
在软件开发中,Makefile是一种十分罕用的自动化工具。Makefile文件蕴含了一系列规定,用于编译、打包、测试等操作,能够帮忙咱们自动化这些操作,进步我的项目的治理和编译效率。本文将介绍Makefile中罕用的命令,以及如何应用它们来编译和治理我的项目makemake命令是最根本的命令,用于执行Makefile文件中的规定。Makefile是由一系列规定组成的,每个规定由指标、依赖和命令三局部组成。make会依据这些规定,主动确定哪些文件须要从新编译,哪些文件不须要编译。通常,咱们能够应用如下命令运行make: make [options] [target]其中,options是可选的参数,用于管制make的行为。例如,咱们能够应用“-j”参数指定并行编译的数量。target是要编译的指标文件名,能够省略。如果省略,则默认执行Makefile文件中的第一个指标。 cleanclean命令用于删除生成的指标文件。在编译过程中,咱们会生成一些两头文件和指标文件,这些文件可能会占用较多的磁盘空间。应用clean命令能够清理这些文件,开释磁盘空间。例如: clean: rm -rf *.o这条规定的作用是删除所有以“.o”结尾的文件。 allall命令用于生成所有的指标文件。通常,咱们在Makefile中会定义多个指标,应用all命令能够一次性编译所有指标。例如: all: target1 target2 target3这条规定的作用是编译target1、target2、target3三个指标。 installinstall命令用于将生成的文件装置到指定的目录中。在编译实现后,咱们通常须要将生成的可执行文件或库文件装置到零碎中,以便其余程序应用。例如: install: all cp target /usr/local/bin/这条规定的作用是将编译生成的target文件拷贝到“/usr/local/bin/”目录下。 uninstalluninstall命令用于卸载已装置的文件。在装置实现后,咱们可能须要卸载已装置的文件,例如进行版本回滚。例如: uninstall: rm /usr/local/bin/target这条规定的作用是删除“/usr/local/bin/”目录下的target文件。 dependdepend命令用于自动更新源文件的依赖关系。在大型项目中,源文件之间可能存在简单的依赖关系,当一个源文件发生变化时,须要从新编译依赖于它的所有文件。应用depend命令能够自动更新依赖关系,防止手动保护依赖关系。例如: depend: gcc -M *.c > .depend这条规定的作用是生成一个.depend文件,保留所有源文件的依赖关系。 自定义命令除了上述常用命令外,Makefile还反对自定义命令。例如,咱们能够定义一个命令来运行我的项目的测试: test: ./run_tests这条规定的作用是运行我的项目的测试脚本。 高级个性Makefile还反对循环、条件语句等高级个性。例如,咱们能够应用循环来编译多个源文件: objects = main.o foo.o bar.o all: $(objects) $(objects): %.o: %.c gcc -c $< -o $@这条规定的作用是编译main.c、foo.c和bar.c三个源文件,生成对应的指标文件。其中,“%”示意通配符,示意匹配任意字符。 总结以上是罕用的Makefile命令,应用这些命令能够大大提高我的项目的治理和编译效率。另外,Makefile还反对循环、条件语句等高级个性,能够依据须要灵便应用。把握这些命令和个性,能够让你更加高效地治理和编译软件我的项目。
C++day12笔记无代码1 设计类、求圆的周长1.1 关键字 class1.2 class + 类名 { 成员 }1.3 类的形成 : 成员函数 + 成员属性1.4 作用域 public : 公共作用域1.5 属性 : 半径1.6 行为 : 求圆周长、设置圆半径 、获取圆半径1.7 测试 : 实例化对象 通过类创建对象 Circle c1;1.8 给对象赋值 c1.setR(10) 获取半径 c1.getR(); 获取周长 c1.calculateZC();1.9 设计一个学生类,有姓名和学号,给姓名学号赋值,并且打印学生的信息2 内联函数2.1 内联函数的引出 --- 宏函数缺点2.1.1 必须保障运算完整性,加括号2.1.2 即便加了括号,有些状况仍然和预期后果不符2.1.3 宏函数 也不器重作用域2.2 内联函数 inline2.3 函数申明和实现必须同时加inline 才算内联函数2.4 类外部成员函数 默认后面加了inline关键字2.5 内联函数和编译器关系2.5.1 退出内联后只是对编译器的一个倡议,有些状况下编译器仍然不会依照内联形式处理函数2.5.2 循环、 判断、 函数体过于宏大、对函数取地址 ,不会依照内联解决2.5.3 一个好的编译器,会将短小的函数 暗藏的退出inline 关键字3 函数的默认参数以及占位参数3.1 默认参数3.1.1 在形参前面 = 默认值3.1.2 如果参数列表中 有一地位有了默认参数,那么从这个地位起从左到右都必须有默认参数3.1.3 函数的申明和实现 只能有一个有默认参数3.2 占位参数3.2.1 函数参数列表中 ( int )只写类型,调用必须要传入参数调用3.2.2 占位参数也能够有默认参数 (int = 0)3.2.3 目前用不到占位参数,前面学习符号重载才会用到4 函数重载4.1 满足条件4.1.1 同一个作用域,函数名称雷同4.1.2 函数参数 类型 、 个数、 程序不同4.2 函数的返回值 不能够作为函数重载条件4.3 援用 退出const 和不加const也能够作为重载条件4.4 函数重载碰到函数默认参数 也须要留神防止二义性5 extern C 浅析5.1 函数重载原理 编译器在底层会将函数名字做两次润饰,不便外部拜访函数体5.2 用处: 在C++下能够运行C语言的文件5.3 #ifdef __cplusplus 两个下划线 + c plus plus5.3.1 extern "C" {5.4 #endif5.5 #ifdef __cplusplus5.5.1 }5.6 #endif6 类的封装6.1 C语言的封装6.1.1 毛病:C语言下没有做类型转换的检测6.1.2 毛病2: 将属性和行为拆散6.2 C++封装6.2.1 将属性和行为作为一个整体,来体现生存中的事物6.2.2 将这些成员 加以权限管制6.3 权限6.3.1 public 私有权限6.3.2 protected 爱护权限6.3.3 private 公有权限6.4 C++中struct 和 class 区别6.4.1 struct 默认权限 public6.4.2 class 默认权限 private7 尽量将成员属性设置为公有7.1 设置为公有权限的属性,能够本人管制属性的读写操作7.2 能够将传入数据,进行有效性的检测8 ...
download:看动画,轻松学习23种C++设计模式如听仙乐耳暂明音乐,心灵,美好生活人们常说“音乐是一种语言”,因为它不分国界、不分种族,可能用独特的形式传播情感和思维。在快节奏的古代生存中,很多人都深受各种压力的困扰,而寻找一个能够让本人放松、愉悦情绪的办法显得尤为重要。而听音乐就是其中一种很好的形式。 有一句流传甚广的名言:“如听仙乐耳暂明”。这句话出自唐代文学家白居易的《琵琶行》,他借助音乐的美好来表白对历史上妇女的同情和关爱。这句话意思是说,听到柔美的音乐时,心灵会因而失去短暂的安定和舒服,好像置身于仙境之中。 实际上,这句话的含意并不仅限于“霎时的安定”这一层面。当咱们凝听音乐时,不仅能够抚平焦虑和疲乏的情绪,还能够使咱们取得更深的思考和灵感。音乐能够激发出咱们心田最深处的情感和渴望,让咱们感触到生命的美妙和丰盛。同时,音乐还可能传播出一种超过语言、逾越文化的共鸣和情感,帮忙咱们与别人建设更严密的分割。 当然,不同的人喜爱的音乐类型也不尽相同。有些人喜爱古典音乐,因为它优雅、高贵,具备无奈言喻的韵味;有些人则喜爱流行音乐,因为它轻松、自在,可能很好地表白现代人的情感和思维。无论是什么类型的音乐,只有它可能触动你的心弦,传递给你真正的打动和力量,就是值得去凝听并收藏的。 总之,“如听仙乐耳暂明”这句话,通知咱们要用心去凝听音乐,尝试从中寻找生存的美妙和意义,让音乐成为咱们生存中不可或缺的一部分。
download:C/C++ 从0到1零碎精讲 我的项目开发综合基础课莫辞更坐弹一曲音乐,古琴,传统文化莫辞更是中国现代文人雅士中的代表人物之一,他是一位喜好古琴的文艺青年。据史料记录,莫辞更经常在宴会上弹奏古琴,给人们带来美好的音乐享受。其中最有名的就要数他“更坐弹一曲”的故事了。 相传有一次,莫辞更加入了一个宴会,过后的氛围非常热烈,人们喝着酒、谈着天。突然间,有人向莫辞更提出了一个挑战:“你能一边弹古琴,一边吟诗吗?”莫辞更并不慌乱,他微笑着承受了这个挑战,而后开始演奏起古琴来。他的指尖轻捷地拨动着琴弦,音乐随之流淌而出,像一朵绽开的花,漂亮而动人。同时,他还口吟着本人创作的诗歌,将音乐与诗意完满联合在一起。 这一幕场景,让在场的众人为之倾倒。他们惊叹于莫辞更的琴艺、诗才和气派,也由衷地感触到了中国传统文化的博大精深。 莫辞更“更坐弹一曲”的故事,至今仍被前人传颂不衰。这个故事通知咱们,艺术能够逾越时空的界线,它是人类智慧和理性的结晶。古琴作为中国传统音乐的代表,有着博大精深的历史渊源和独特的韵味,而莫辞更则将其演奏得酣畅淋漓,彰显出了中国文化的魅力与底蕴。 在当今社会,咱们应该更加关注和弘扬传统文化,让这些古老而神秘的艺术模式在古代焕发新的光辉。同时,咱们也要向莫辞更这样的前辈学习,用本人的才华和致力去发明属于本人的经典,为传统文化的倒退奉献本人的力量。
中断表达式转后缀表达式规定:1.数字本人排列2.操作符: a栈为空 || 优先级比栈顶高:入栈b小于等于栈顶,就让栈顶pop,之后会有新的栈顶,接着比拟,直到满足条件a 题解class Solution {public: int evalRPN(vector<string>& tokens) { stack<int>st; for(auto x:tokens) { if(x == "+") { int x = st.top(); st.pop(); int y = st.top(); st.pop(); st.push(y+x); } else if(x == "-") { int x = st.top(); st.pop(); int y = st.top(); st.pop(); st.push(y-x); } else if(x == "*") { int x = st.top(); st.pop(); int y = st.top(); st.pop(); st.push(y*x); } else if(x == "/") { int x =st.top(); st.pop(); int y = st.top(); st.pop(); st.push(y/x); } else { st.push(stoi(x)); } } return st.top(); }留神点1.其余类型->string:to_string() ...
在C++编程中,异样解决和错误处理机制是十分重要的。它们能够帮忙程序员无效地解决运行时谬误和异常情况。本文将介绍C++中的异样解决和错误处理机制。什么是异样解决?异样解决是指在程序执行过程中产生异样或谬误时,程序可能捕捉并解决这些异样或谬误的机制。例如,当程序试图拜访一个未初始化的指针或除以零时,就会产生异样。异样解决的目标是确保程序可能在呈现问题的状况下继续执行,并且可能提供有用的错误信息,以便于程序员进行调试和修复。 C++中的异样解决机制C++中的异样解决机制基于三个关键字:try、catch、和throw。try块用于蕴含可能抛出异样的代码。如果在try块中产生异样,程序将跳转到与之关联的catch块。catch块用于解决异常情况。throw关键字用于抛出异样。当throw关键字被执行时,程序将跳转到try块之外的第一个catch块。 以下是一个简略的C++异样解决示例: #include <iostream>using namespace std;int main() { try { int age = 15; if (age >= 18) { cout << "能够进入网吧" << endl; } else { throw(age); } } catch (int myAge) { cout << "未满18岁,禁止进入网吧" << endl; } return 0;}在这个例子中,如果年龄小于18岁,程序会抛出一个异样,并跳转到catch块。catch块将输入一个谬误音讯,通知用户未满18岁禁止进入网吧。 什么是错误处理?错误处理是指在程序执行过程中呈现谬误时,程序可能捕捉并解决这些谬误的机制。错误处理的目标是确保程序可能正确地响应谬误,并且可能提供有用的错误信息,以便于程序员进行调试和修复。 C++中的错误处理机制在C++中,错误处理机制通常通过返回错误代码来实现。当程序执行到某个函数时,如果该函数执行呈现谬误,则会返回一个特定的错误代码,程序员能够依据该错误代码进行相应的解决。通过错误处理机制,程序可能更加强壮和牢靠地运行。通常,错误代码为正数,而胜利的返回值为零或负数。 以下是一个简略的C++错误处理示例: #include <iostream>using namespace std;int main() { int result = 0; result = 10 / 0; if (result < 0) { cout << "产生谬误" << endl; } else { cout << "后果为:" << result << endl; } return 0;}在这个例子中,程序试图除以零。因为除以零是一个谬误状况,程序将返回一个正数错误代码。在主函数中,咱们查看错误代码并输入一个谬误音讯。 ...
拜访【WRITE-BUG数字空间】_[内附残缺源码和文档] 零碎依据危险评估,为旅客设计一条合乎旅行策略的旅行线路并输入,零碎能查问以后时刻旅客所处的地点和状态(停留城市/所在交通工具)。 试验内容和试验环境形容1.1 试验内容城市之间有各种交通工具(汽车、火车和飞机)相连,有些城市之间无奈中转,须要路径直达城市。某旅客于某一时刻向零碎提出旅行要求。思考在以后 COVID-19 疫情环境下,各个城市的危险水平不一样,分为低危险、中危险和高风险三种。零碎依据危险评估,为该旅客设计一条合乎旅行策略的旅行线路并输入;零碎能查问以后时刻旅客所处的地点和状态(停留城市/所在交通工具)。 1.2 环境形容Windows 10 环境 PC 机;Microsoft Visual Studio 2019 集成化开发环境;Qt Creator 4.11.1(Based on Qt 5.14.1) 集成化开发环境;Qt 运行库(Qt 5.14.2 MinGW 32-bit) 软件设计2.1 数据结构City 类(city.h):class City : public QObject{public: City(string name = "", int number = 0, double risk = 0);string name; //城市名int number; //城市序号QPoint pos; //城市在地图上的地位double risk; //城市危险值void add_route(); //为该城市增加时刻表(未应用)vector<class Transport*> everyday_table; //每日时刻表signals: };Tourist 类(tourist.h): class Tourist{public: Tourist(City* A = nullptr, City* B = nullptr, int limit = 0, int number = 0);bool enabled; //是否启用int number; //旅客序号int status; //旅客状态int stlimit; //最早登程工夫int limit; //最晚达到工夫void plan_route(int time); //为旅客布局路线void update_status(int time); //更新旅客信息City* start; //出发点City* destination; //目的地City* nowat; //以后地位Route plan; //以后布局门路double risk; //以后旅客所接受的危险值void dfs(City* at, Route rut, int time); //以后所在位置 路线 以后工夫 工夫限度signals: ...
在面向对象的编程中,多态性是一个十分重要的概念。多态性意味着在不同的上下文中应用同一对象时,能够产生不同的行为。C++是一种面向对象的编程语言,在C++中,虚函数是实现多态性的要害什么是虚函数虚函数是一个在基类中申明的函数,它能够被子类重写并提供不同的实现。在C++中,应用关键字virtual来申明一个虚函数。虚函数的原理是将函数调用的控制权交给运行时环境,而不是编译时环境。因而,虚函数的实现须要在运行时能力确定。虚函数的申明模式如下: virtual 返回类型 函数名(参数列表) { // 实现代码 }例如: class Shape { public: virtual void draw() { // 实现代码 } }; class Circle : public Shape { public: void draw() override { // 实现代码 } };在下面的例子中,Shape类定义了一个虚函数draw(),并在Circle类中重写了它。留神,在Circle类中的重写函数中应用了override关键字,这是C++11中引入的新个性,示意该函数是对基类中同名函数的重写。 多态性的实现当应用基类指针或援用来拜访派生类对象时,如果虚函数已被重写,将调用派生类中的实现。这种行为称为运行时多态性,因为理论调用的函数是在运行时确定的。例如: Shape* s = new Circle(); s->draw(); // 调用Circle类中的draw()函数在下面的例子中,咱们应用基类指针s来拜访Circle类的对象,因为Circle类重写了draw()函数,所以调用的是Circle类中的实现。这种行为能够使代码更加灵便、可扩大和易于保护。多态性的实现有两种形式:动态多态和动静多态。动态多态是通过函数重载实现的,而动静多态是通过虚函数实现的。 动态多态是在编译时确定函数的调用,函数重载是动态多态的一种模式。例如: void print(int a) { // 实现代码 } void print(float b) { // 实现代码 }在下面的例子中,咱们定义了两个函数print(),一个承受一个整数参数,另一个承受一个浮点数参数。在调用print()函数时,编译器会依据传递的参数类型确定调用哪个函数。 动静多态是在运行时确定函数的调用。虚函数是动静多态的一种模式。在应用虚函数时,能够将基类指针或援用指向派生类对象,这样就能够实现多态性调用。例如: Shape* s = new Circle(); s->draw(); // 调用Circle类中的draw()函数在下面的例子中,咱们应用基类指针s来拜访Circle类的对象,因为Circle类重写了draw()函数,所以调用的是Circle类中的实现。这种行为称为运行时多态性,因为理论调用的函数是在运行时确定的。 多态的底层原理在C++中,多态是通过虚函数表和虚指针来实现的。虚函数表是一个非凡的表格,其中蕴含了虚函数的地址。每个类都有一个虚函数表,其中蕴含了该类及其基类的虚函数地址。当一个对象被创立时,它将蕴含一个指向其类的虚函数表的指针,称为虚指针。当调用一个虚函数时,程序将首先查找该对象的虚指针,而后应用虚指针中的虚函数表来查找正确的函数地址。这种办法使得程序在运行时可能动静地抉择正确的函数。 多态性的益处多态性能够使代码更加灵便、可扩大和易于保护。多态性能够使代码更加通用,能够使同样的代码实用于不同的对象。多态性能够进步代码的复用性,能够缩小代码的反复编写。多态性能够使代码更加易于保护,因为代码能够更加清晰、简洁和易于了解。 ...
C++中的多线程编程和同步机制使得程序员能够利用计算机的多外围来进步程序的运行效率和性能。本文将介绍多线程编程和同步机制的基本概念和应用办法。多线程编程根底在C++中,应用<thread>库来创立和治理线程。线程能够通过函数、成员函数或者Lambda表达式来实现。以下是一个应用Lambda表达式来创立线程的例子: #include <thread>#include <iostream>int main() { std::thread t([](){ std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl; }); t.join(); return 0;}上述代码创立了一个线程并输入了该线程的ID。在创立线程时,须要将线程函数作为参数传递给std::thread。在上述例子中,咱们应用了Lambda表达式来定义线程函数,该表达式会输入一行文本。 同步机制多线程编程中最常见的问题是数据竞争和死锁。为了防止这些问题,咱们须要应用同步机制来控制线程的拜访。 互斥量互斥量是C++中最罕用的同步机制之一。互斥量能够保障同一时间只有一个线程能够访问共享资源。以下是一个应用互斥量来爱护共享资源的例子: #include <thread>#include <mutex>#include <iostream>std::mutex mtx;void thread_func() { mtx.lock(); std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl; mtx.unlock();}int main() { std::thread t1(thread_func); std::thread t2(thread_func); t1.join(); t2.join(); return 0;}上述代码创立了两个线程,并应用互斥量来爱护共享资源。在线程函数中,咱们先调用mtx.lock()函数来锁定互斥量,而后访问共享资源,最初再调用mtx.unlock()函数来开释互斥量。在上述例子中,咱们应用了两个线程来访问共享资源,然而只有一个线程能够拜访该资源。这是因为在一个线程访问共享资源时,该资源会被锁定,其余线程无法访问该资源,直到该线程开释互斥量为止。 条件变量条件变量是C++中另一个罕用的同步机制。条件变量能够让线程在某些条件满足时才继续执行,否则就期待。以下是一个应用条件变量来同步线程的例子: #include <thread>#include <mutex>#include <condition_variable>#include <iostream>std::mutex mtx;std::condition_variable cv;bool ready = false;void consumer() { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [](){ return ready; }); std::cout << "Hello from consumer thread " << std::this_thread::get_id() << std::endl;}void producer() { std::this_thread::sleep_for(std::chrono::seconds(1)); ready = true; cv.notify_one();}int main() { std::thread t1(consumer); std::thread t2(producer); t1.join(); t2.join(); return 0;}上述代码创立了两个线程,一个生产者线程和一个消费者线程。生产者线程在1秒后将ready变量设置为true,而后告诉消费者线程继续执行。消费者线程期待条件变量cv,直到ready变量的值为true为止。在该例子中,咱们应用了条件变量来同步生产者和消费者线程。 ...
C++是一门弱小的编程语言,然而在内存治理方面却存在着一些问题。手动治理内存不仅费时费力,而且容易出错。因而,C++中引入了智能指针这一概念,以更好地治理内存。什么是智能指针?在C++中,内存的调配和开释都是由开发者手动实现的。这种形式尽管很灵便,但也非常容易出错,比方遗记开释内存或开释了曾经开释的内存等。为了防止这些问题,C++引入了智能指针这一概念。智能指针是一品种,它在析构时主动开释所治理的对象所占用的内存。这样,程序员就不须要手动治理内存,缩小了出错的可能性。智能指针是一种RAII(Resource Acquisition Is Initialization)技术的利用。 RAII的根本思维是:在对象的构造函数中进行资源的调配,在析构函数中进行资源的开释。智能指针也是这种思维的一种扩大,它在析构时主动开释资源。 C++中的几种智能指针C++中有三种智能指针:unique_ptr、shared_ptr和weak_ptr。每种智能指针都有其独特的性能和特点,上面将逐个介绍。 unique_ptrunique_ptr是一个独享所有权的智能指针,不能共享所有权。当unique_ptr被销毁时,它所治理的对象的内存也会被主动开释。unique_ptr也能够通过std::move()转移所有权。unique_ptr应用的办法很简略,只须要将所需治理的对象传递给unique_ptr即可。 #include <iostream> #include <memory> int main() { // 应用unique_ptr治理int类型的对象 std::unique_ptr<int> up1(new int(10)); std::cout << "up1: " << *up1 << std::endl; // 应用make_unique函数创立unique_ptr对象 auto up2 = std::make_unique<int>(20); std::cout << "up2: " << *up2 << std::endl; // unique_ptr能够通过std::move()转移所有权 std::unique_ptr<int> up3 = std::move(up1); std::cout << "up3: " << *up3 << std::endl; return 0; }shared_ptrshared_ptr是一个共享所有权的智能指针,能够有多个shared_ptr指向同一个对象。每当一个shared_ptr被销毁时,它所治理的对象的援用计数会减1。当援用计数为0时,对象的内存也会被主动开释。shared_ptr的应用办法和unique_ptr相似,只须要将所需治理的对象传递给shared_ptr即可。须要留神的是,shared_ptr不能治理动态分配的数组,因为它无奈确定数组的长度。 #include <iostream> #include <memory> int main() { // 应用shared_ptr治理int类型的对象 std::shared_ptr<int> sp1(new int(10)); std::cout << "sp1: " << *sp1 << std::endl; // 应用make_shared函数创立shared_ptr对象 auto sp2 = std::make_shared<int>(20); std::cout << "sp2: " << *sp2 << std::endl; // 能够有多个shared_ptr指向同一个对象 std::shared_ptr<int> sp3 = sp1; std::cout << "sp1 count: " << sp1.use_count() << std::endl; std::cout << "sp3 count: " << sp3.use_count() << std::endl; return 0; }weak_ptrweak_ptr是一个弱援用的智能指针,它能够与shared_ptr一起应用。weak_ptr不会减少所治理的对象的援用计数,因而它不会影响对象的生命周期。能够通过weak_ptr的lock()成员函数来获取一个指向所治理的对象的shared_ptr。须要留神的是,在应用lock()函数之前,须要判断weak_ptr是否曾经过期,即判断其指向的对象是否曾经被销毁。 ...
这几周与公司的软件开发专家(职称)探讨产品的软件新架构与计划,次要波及两点 是否复用现有的外围机制基于领域建模设计对于第一点,单方达成统一。 对于第二点,畛域能够了解为业务,业务专家(产品经理,需要工程师,临床工程师等)与研发人员一起,通过头脑风暴、事件风暴、会议、合作等形式,使得研发人员对产品业务进行正当、高效的软件建模。 但在大多数公司中,因为分工(考核)不同,不存在研发人员能高效的对业务进行软件建模,这也就要求研发人员本人去相熟、了解业务,比方你曾经在一个业务上做了5年研发了,经验了各种需要的迭代,探讨,那么你有可能就是你们公司的畛域(业务)专家。 软件开发专家是在一个行业深耕,对业务精通后,进行正当、高效的软件建模,从而为架构、设计、开发提供指南。 这章节,我给大家分享软件建模常识。 什么是软件建模模型是对客观存在的形象,例如驰名的物理学公式 E=mc2,就是品质能量转换的物理法则的数学模型。 除了物理学公式以外,还有一些货色也是模型,比方地图是对天文空间的建模;机械安装、电子电路、建筑设计的各种图纸是对物理实体的建模。 如下图所示,建模和修建的例子 具体解说请浏览开发简单软件的零碎办法(二)之软件建模 本文由IT文艺男(微信公众号itwenyinan) 原创!
惟一不变的就是变动自身。 咱们常常讲的零碎、子系统、模块、组件、类、函数就是从逻辑上将软件一步步合成为更轻微的局部,即逻辑单元, 分而治之, 简单问题拆解为若干简略问题, 一一解决。 逻辑单元外部、内部的交互会产生依赖,从而产生了内聚、耦合概念。内聚次要形容逻辑单元外部,耦合次要形容逻辑单元之间的关系。 咱们常常讲的高内聚,低耦合,如何做到,做到的规范是什么?这就是开发、设计、架构的五大准则所体现出的价值,最终达到高内聚,低耦合的软件指标。 耦合逻辑单元之间存在依赖, 导致改变可能会相互影响, 关系越严密, 耦合越强,逻辑单元独立性越差。 比方模块A间接操作了模块B中数据, 则视为强耦合, 若A只是通过数据与模块B交互, 则视为弱耦合。 内聚逻辑单元外部的元素, 关联性越强, 则内聚越高, 逻辑单元单一性更强。 一个逻辑单元该当尽可能独立实现某个性能。 比方有各种场景须要被引入到以后模块, 代码品质将变得十分软弱, 这种状况倡议拆分为多个模块。 高内聚、低耦合内聚性,又称块内分割。指逻辑单元的性能强度的度量,即一个逻辑单元外部各个元素彼此联合的严密水平的度量。 耦合性,又称块间分割。指逻辑单元互相分割严密水平的一种度量。 比方电脑USB口。能够插入USB口工作的设施和电脑都是低耦合性,电脑不依赖于内部任何内部USB设施,只有你合乎USB标准,插上就能够用。USB标准就像一个抽象类,所有外部设备必须实现抽象类,确保能被正确调用。 设计模式、SOLID准则等都是为了更好的做到高内聚、低耦合。 SOLID准则S:繁多职责准则 (<font color="#00b050">S</font>ingle Responsibility Principle)O:开闭准则 (<font color="#00b050">O</font>pen/Closed Principle)L:里氏替换准则 (<font color="#00b050">L</font>iskov Substitution Principle)I:接口隔离准则 (<font color="#00b050">I</font>nterface Segregation Principle)D:依赖倒置准则 (<font color="#00b050">D</font>ependency Inversion Principle)上述是面向对象编程的要害准则。诸如此类的设计准则可能帮忙开发人员构建更易于保护、扩大的零碎。 具体解说请浏览软件开发、设计、架构的五大准则 本文由IT文艺男(微信公众号itwenyinan) 公布!
补题链接:https://codeforces.com/gym/104337 原文链接:https://www.eriktse.com/algorithm/1136.htmlM. Different Billing签到题,写几个柿子而后枚举B或C即可。 #include <bits/stdc++.h>#define int long longusing namespace std;signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int x, y;cin >> x >> y; for(int c = 0;c <= x; ++ c) { int b = (y - 2500 * c) / 1000; if(b < 0 || 1000 * b + 2500 * c != y)continue; int a = x - b - c; if(a >= 0) { cout << a << ' ' << b << ' ' << c << '\n'; return 0; } } cout << -1 << '\n'; return 0;}C. Darkness I这题是对Minecraft中有限水的拓展过程为背景的一道思维题。 ...
C++ STL(规范模板库)提供了一组丰盛的容器和算法,使得开发者可能更加高效地编写程序。本文将介绍STL中的一些罕用容器和算法。容器vectorvector是一个动静数组,能够在运行时调整大小。它的长处在于能够疾速地拜访元素,毛病是在插入和删除元素时须要挪动前面的元素。 #include <vector>#include <iostream>using namespace std;int main() { vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); for (int i = 0; i < v.size(); i++) { cout << v[i] << " "; } cout << endl; v.pop_back(); for (int i = 0; i < v.size(); i++) { cout << v[i] << " "; } cout << endl; return 0;}除了push_back和pop_back,vector还提供了很多其余的成员函数和迭代器,能够不便地拜访和批改元素。比方,能够应用v.front()和v.back()别离拜访首元素和尾元素,应用v.insert()和v.erase()在任意地位插入和删除元素。此外,vector还提供了v.empty()和v.size()别离判断容器是否为空和获取容器大小。 listlist是一个双向链表,能够在任意地位插入和删除元素,但拜访元素比较慢。 #include <list>#include <iostream>using namespace std;int main() { list<int> l; l.push_back(1); l.push_back(2); l.push_back(3); for (list<int>::iterator it = l.begin(); it != l.end(); it++) { cout << *it << " "; } cout << endl; l.pop_back(); for (list<int>::iterator it = l.begin(); it != l.end(); it++) { cout << *it << " "; } cout << endl; return 0;}和vector一样,list也提供了很多其余的成员函数和迭代器,能够不便地拜访和批改元素。比方,能够应用l.front()和l.back()别离拜访首元素和尾元素,应用l.insert()和l.erase()在任意地位插入和删除元素。此外,list还提供了l.empty()和l.size()别离判断容器是否为空和获取容器大小。 ...
以前写的C++根本都是C with STL,大多是面向过程的算法题,或者比拟小的我的项目,而后常常报各种编译谬误(对编译原理不熟),常常把人搞到解体,搞不懂构建、链接之类的货色。 当初开始记录一下XMake的学习笔记,记录一些学习过程中踩的坑,在这篇文章,你将学习到Windows下利用MSYS2进行Mingw-w64环境搭建和XMake装置,并用Xmake构建一个我的项目,编译一个HelloWorld程序。 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 浏览原文取得更好浏览体验:https://www.eriktse.com/algorithm/1121.htmlXMake简介一个由国人开发的C++构建工具,真的很好用!yyds。 xmake 是一个基于 Lua 的轻量级跨平台构建工具,应用 xmake.lua 保护我的项目构建,相比 makefile/CMakeLists.txt,配置语法更加简洁直观,对老手十分敌对,短时间内就能疾速入门,可能让用户把更多的精力集中在理论的我的项目开发上。尽管,简略易用是 xmake 的一大特色,但 xmake 的性能也是十分弱小的,既可能像 Make/Ninja 那样能够间接编译我的项目,也能够像 CMake/Meson 那样生成工程文件,还有内置的包管理系统来帮忙用户解决 C/C++依赖库的集成应用问题。目前,xmake次要用于C/C++我的项目的构建,然而同时也反对其余native语言的构建,能够实现跟C/C++进行混合编译,同时编译速度也是十分的快,能够跟Ninja持平。官网:https://xmake.io/#/zh-cn/ Step1:装置MSYS2MSYS2是一个Windows平台上的GNU/Linux-like环境,能够用于开发和调试Unix/Linux软件。MSYS2应用pacman软件包管理器,能够不便地装置和治理软件包。它也提供了一个相似于Bash的shell,反对大多数Unix/Linux命令和工具。MSYS2还提供了穿插编译工具链,能够不便地在Windows上编译和构建Linux软件。 间接到官网https://www.msys2.org/ 找到 installer下载,而后装置到一个容易找到的门路,比方我装置到D:\msys2目录下,接下来你就能够假想这个目录是一个linux的根目录。 装置实现后的目录差不多是这样的:MSYS2中又有多个子系统,咱们用Mingw64就好。 Step2: 装置XMake接下来应用以下命令用pacman装置XMake: pacman -Sy mingw-w64-x86_64-xmake 装置实现后,用xmake命令就能够应用xmake了。 如果提醒xmake命令没有找到,那就是环境变量的问题,用上面的办法来批改:举荐在etc/profile.d里新建一个env.sh,外面写 export $PATH=$PATH:D:/msys2/mingw64/bin完预先用指令source /etc/profile来刷新配置,再用echo $PATH来查看环境变量是否增加胜利。 为了在VS Code的终端中应用xmake命令,还须要将D:\msys2\mingw64\bin退出到Windows的环境变量中。 留神,这里如果你发现改了环境变量,在vscode中还是不能用xmake命令,你须要重启一下vscode......这是一个小坑,过后困惑了良久。 Step3: 运行Hello World新建一个HelloWorld文件夹,在该文件夹下新建一个文件xmake.lua,外面编写以下内容: set_config("plat", "mingw")set_config("sdk", "D:/msys2/mingw64")target("HelloWorld")-- 设定为二进制文件,也就是windows下的exe文件 set_kind("binary") -- 将src目录下的所有cpp一起编译 add_files("src/*.cpp") 在src/HelloWorld.cpp中写一个简略的程序: #include <iostream>signed main(){ std::cout << "Hello, World!" << '\n'; return 0;}而后在终端中执行命令(留神在我的项目根目录执行): xmake构建实现后能够用xmake run间接运行,或本人用其余办法运行。二进制文件会保留在build外面,而后依据平台进行分类。 ...
拜访【WRITE-BUG数字空间】_[内附残缺源码和文档] 用C++做了个有AI性能的贪吃蛇小游戏,心愿大家enjoy it. 总体详情开发环境:VIsual Studio 2017 开发语言:C++ 和 少许Windows API 运行环境:Windows 10 01 初始化工作-游戏设置游戏设置和相干初始化放在了一个类外面,并进行了动态申明。次要设置了游戏窗口的长和款。并在GameInit()函数外面设置了窗口大小,暗藏光标,初始化随机数种子等。代码如下: //游戏设置相干模块,把函数都放到一个类外面了。函数定义为static动态成员,不生成实体也能够间接调用class GameSetting{public: //游戏窗口的长宽 static const int window_height = 40; static const int window_width = 80;public: static void GameInit() { //设置游戏窗口大小 char buffer[32]; sprintf_s(buffer, "mode con cols=%d lines=%d",window_width, window_height); system(buffer); //暗藏光标 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO CursorInfo; GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息 CursorInfo.bVisible = false; //暗藏控制台光标 SetConsoleCursorInfo(handle, &CursorInfo);//设置控制台光标状态 //初始化随机数种子 srand((unsigned int)time(0)); }}; 用到了几个相干的Windows API,本文不做过多介绍,大家百度即可。
SpringBoot 是一个基于 Spring 框架的疾速开发框架,旨在简化 Spring 应用程序的开发和部署。在本文中,咱们将深入分析 SpringBoot 启动过程的源代码,并提供必要的解释和阐明。SpringBoot启动过程简介SpringBoot应用程序的启动过程能够分为以下几个步骤: 加载应用程序上下文扫描应用程序中的所有组件主动配置应用程序环境启动嵌入式Web服务器 在上面的章节中,咱们将逐个剖析这些步骤的源代码。加载应用程序上下文SpringBoot 应用程序的上下文是一个蕴含所有应用程序组件的容器。在启动过程中,SpringBoot 会加载并初始化这个容器。这个步骤的源代码在SpringApplication类中。具体来说,SpringApplication类的run办法是这个过程的入口点。在这个办法中,Spring Boot会通过调用createApplicationContext办法来创立应用程序上下文。上面是createApplicationContext办法的源代码:protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass;if (contextClass == null) { try { switch (this.webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); break; case REACTIVE: contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break; default: contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); } } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable to create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex); }}return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);}复制代码在这个办法中,SpringBoot 会依据应用程序类型(Servlet或Reactive)抉择适合的上下文类。而后,它会应用 Java 反射机制来实例化这个类并返回一个可配置的应用程序上下文对象。扫描应用程序中的所有组件在上一步中,SpringBoot创立了应用程序上下文。在这一步中,SpringBoot会扫描应用程序中的所有组件并将它们注册到应用程序上下文中。这个步骤的源代码在SpringApplication类中的scan办法中。具体来说,在这个办法中,SpringBoot 会创立一个SpringBootBeanDefinitionScanner对象,并应用它来扫描应用程序中的所有组件。上面是scan办法的源代码:private void scan(String... basePackages) { ...
拜访【WRITE-BUG数字空间】_[内附残缺源码和文档]内存池:程序事后向零碎申请一大块足够的内存,尔后,当零碎须要申请内存的时候,不是间接向操作习题申请,而是向内存池中申请,当开释的时候,不返回给操作系统,而是返回给内存池,当程序退出时,内存池才将申请的内存真正开释高并发内存池借鉴tcmalloc(ThreadCaching Malloc),即线程缓存的malloc,实现了高效的多线程内存治理,用于代替零碎的内存调配相干的函数(malloc,free)池化技术:向零碎先申请适量的资源,而后本人治理,以备不时之需,之所以申请适量资源,是因为每次申请资源都有较大的开销,那边不如提前申请好,进步程序运行效率在计算机中除了内存池,还有连接池,线程池,对象池等,以线程池为例,它的次要思维是,先启动若干数量的线程,让他们处于睡眠状态,当接管到客户端申请时,唤醒某个沉睡的线程,让它解决客户端申请,当解决完申请之后,线程又进入了休眠状态内存池:程序事后向零碎申请一大块足够的内存,尔后,当零碎须要申请内存的时候,不是间接向操作习题申请,而是向内存池中申请,当开释的时候,不返回给操作系统,而是返回给内存池,当程序退出时,内存池才将申请的内存真正开释内存池解决问题:1.次要解决效率问题2.内存碎片问题malloc理论就是一个内存池定长内存池固定大小的内存申请治理特定:性能达到极致不思考内存碎片等问题设计形式:向内存申请一块足够大的内存块,而后每次申请内存时咱们就切出去一小部分拿来应用,
阐明应用 VLD 内存透露检测工具辅助开发时整顿的学习笔记。本篇对 VLD 源码包中的各文件用处做个概述。同系列文章目录可见 《内存透露检测工具》目录 [TOC] <br/> 1. 整体概览以 vld2.5.1 版本为例,下载源码 后,根目录下一共 5 个文件夹:.teamcity、lib、mfc_detect、setup、src。还有 12 个文件:.editorconfig、.gitignore、.mailmap、appveyor.yml、AUTHORS.txt、change_toolset.ps1、CHANGES.txt、COPYING.txt、README.md、vld.ini、vld_vs14.sln、vld_vs14_wo_mfc.sln。 2. 文件夹 .teamcity该文件夹的目录构造如下: vld-master\.teamcity└─Vld │ project-config.xml │ ├─buildTypes │ Vld_CompileAll.xml │ Vld_DebugCrtDllWin32.xml │ ├─pluginData │ plugin-settings.xml │ └─vcsRoots Vld_HttpsGithubComKindDragonVldGitRefsHeadsMaster.xml外面全是 xml 文件,是 teamcity 继续集成工具的配置文件,用于 VLD 我的项目源码的集成治理,对于该工具的介绍,可拜访其官网:Jetbrains-teamcity。 3 文件夹 lib该文件夹下有三个子文件夹:cppformat、dbghelp、gtest。 3.1 文件夹 cppformat(生成 libformat)该文件夹的目录构造如下: vld-master\lib\cppformat ChangeLog.rst format.cc format.h format.vcxproj format.vcxproj.filters LICENSE.rst posix.cc posix.hcppformat 是一个开源的 C++ 格式化库,仓库地址为 Github-fmtlib,在 VLD 中被用来格式化输入堆栈信息,详见 源码 callstack.cpp 第 224~275 行。 ...
The IPQ4019 and IPQ4029 chipsets from Qualcomm are designed for high-performance Wi-Fi networks, providing advanced features such as MU-MIMO, beamforming, and enhanced spectral efficiency. These chipsets are suitable for a wide range of applications, including enterprise, industrial, and residential wireless networks. When combined with the Wallys WiFi5 Industrial Wireless AP DR-AP40X9-A, these chipsets can provide exceptional long-range connectivity. The DR-AP40X9-A is designed for outdoor use and is equipped with high-power radios and antennas, allowing it to cover a large area with a strong Wi-Fi signal. With the IPQ4019 or IPQ4029 chipset, the DR-AP40X9-A can deliver reliable and high-speed wireless connectivity over long distances, making it an ideal solution for industrial applications, remote locations, and outdoor environments. ...
拜访【WRITE-BUG数字空间】_[内附残缺源码和文档] 玩家指标是吃掉所有豆豆,途中,若玩家撞到怪兽则游戏完结。游戏中存在超级豆子,吃到后,所有怪兽静止,且玩家进入无敌状态(可撞怪兽使之回到初始地位)。玩家可应用键盘方向键进行抉择、管制挪动,回车键确定,Esc 键返回,空格键暂停。 控制台小游戏——吃豆豆一、概述1.1 次要内容:以吃豆豆大作战根底参考,实现了运行在 Windows 管制台下的一个吃豆豆小游戏。玩家指标是吃掉所有豆豆,途中,若玩家撞到怪兽则游戏完结。游戏中存在超级豆子,吃到后,所有怪兽静止,且玩家进入无敌状态(可撞怪兽使之回到初始地位)。玩家可应用键盘方向键进行抉择、管制挪动,回车键确定,Esc 键返回,空格键暂停。 1.2 已实现的指标:地图反对自定义编辑(可编辑地图中的:空地、墙、豆子、超级豆子、小怪初始地位及数量、吃豆人初始地位)可抉择游戏难度(即设定不同的小怪挪动速度)怪兽可追踪吃豆人地位,往吃豆人的所在位置凑近可查看历史记录 TOP 10,并且可对游戏记录进行回放在回放的过程中,反对按下方向键后,退出回放模式,在以后情景下,操纵吃豆人,进入失常的游戏模式,持续游戏有用户敌对的 UI 界面(比方:吃到超级豆子后吃豆人变黑白闪动,且下方显示进度条倒计时;抉择界面高亮显示选项)二、次要类的设计2.1 顺次介绍各个类的设计:Position 类因为是在管制台下实现本游戏,所以显示界面的所有元素都是字符,而一些较为好看,且合乎游戏中的元素的特殊字符个别都是 2 个英文字符的宽度,所以这里界面设计的次要思维使将整个控制台运行窗口看成一个二维的画布,横坐标以每两个英文字符的宽度为单位,纵坐标则以每行为单位。 而有了画布之后,画布上的每一个点其实能够看作一个数据结构,这就是 Position 类的意义。
@[toc] 问题背景在较长一段时间里,C/C++开发者所应用的集成开发环境(IDE)要么是比拟重量级的VS(Visual Studio),要么是Codeblocks·、·Visual C++ 6.0等轻量级IDE,又或者是诸如notepad++、sublime text等文本编辑器+GCC编译器+命令行工具的组合式开发环境。这几种开发方式要么太过宏大比拟吃硬件资源,比方VS动辄就要占用几十个G的硬盘空间,要么尽管轻量然而没有敌对的代码提醒和自动化的编译链接运行过程,开发体验并不是很敌对。 近几年来,随着VSCode(Visual Studio Code)的风行,越来越多的开发者抉择以此来作为集体的集成开发环境。应用VSCode开发C/C++程序能够下载安装VSCode官网的C/C++ for Visual Studio Code插件实现开发过程中的代码提醒、调试、运行等性能,然而须要自行装置C/C++编译器,笔者是用的是GCC编译器MinGW。具体开发环境的搭建过程在此不作解说,本文关注点在于开发过程中遇到的中文字符相干的乱码问题。 问题形容测试代码英文字符串文本文件 es_src.txt Chinese中文字符串文本文件 ch_src.txt 中文main.c #include <stdio.h>#include <stdlib.h>#include <string.h>int main(){ char str_es[] = "Chinese"; char str_ch[] = "中文"; char str[30]; // 读英文字符串的文本文件测试 printf("读文件测试:\n"); FILE *fpr_es = fopen("es_src.txt", "r"); while (fgets(str, 30, fpr_es) != NULL) { printf("strcmp(%s, %s)=%d\n", str_es, str, strcmp(str_es, str)); } fclose(fpr_es); // 间接打印含中文字符串测试 printf("请输出英文字符串“Chinese”:\n"); // 读入英文字符串 scanf("%s", str); printf("间接打印测试:\n"); // 间接打印英文字符串 printf("str=%s\n", str); // 与英文字符串常量进行比拟 printf("strcmp(%s, %s)=%d\n", str_es, str, strcmp(str_es, str)); // 英文字符串写入文件测试 printf("写文件测试:\n"); FILE *fpw_es = fopen("es_tar.txt", "w"); fprintf(fpw_es, "%s\n%s\n", str_es, str); fclose(fpw_es); printf("写入实现!\n"); // 读中文字符串的文本文件测试 printf("读文件测试:\n"); FILE *fpr_ch = fopen("ch_src.txt", "r"); while (fgets(str, 30, fpr_ch) != NULL) { printf("strcmp(%s, %s)=%d\n", str_ch, str, strcmp(str_ch, str)); } fclose(fpr_ch); // 间接打印中文字符串测试 printf("请输出中文字符串“中文”:\n"); // 读入中文字符串 scanf("%s", str); printf("间接打印测试:\n"); // 间接打印中文字符串 printf("str=%s\n", str); // 与中文字符串常量进行比拟 printf("strcmp(%s, %s)=%d\n", str_ch, str, strcmp(str_ch, str)); // 中文字符串写入文件测试 printf("写文件测试:\n"); FILE *fpw_ch = fopen("ch_tar.txt", "w"); fprintf(fpw_ch, "%s\n%s\n", str_ch, str); fclose(fpw_ch); printf("写入实现!\n"); system("pause"); return 0;}测试后果控制台输入后果能够看到(a) 从文件中读取英文字符串并打印能够失常显示;间接打印英文字符串常量和间接打印从键盘读入的英文字符串均失常显示,应用strcmp进行比拟后果为0。(b) 从文件中读取中文字符串并打印能够失常显示;间接打印中文字符串常量能够失常显示,然而没有显示出从键盘读入的中文字符串,应用strcmp进行比拟后果也不为0。读文件测试:strcmp(Chinese, Chinese)=0 请输出英文字符串“Chinese”:Chinese间接打印测试:str=Chinesestrcmp(Chinese, Chinese)=0写文件测试:写入实现!读文件测试:strcmp(中文, 中文)=0 请输出中文字符串“中文”: 中文间接打印测试:str=strcmp(中文, )=1写文件测试:写入实现!Press any key to continue . . .英文字符串文本文件 es_tar.txt英文字符串常量和从键盘读入的英文字符串均失常写入到文件中。 ...
DAY16共3题: 奇♂妙拆分(简略数学)区区区间间间(枯燥栈)小AA的数列(位运算dp) 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 浏览原文取得更好浏览体验:https://www.eriktse.com/algorithm/1119.html奇♂妙拆分(简略数学)依据贪婪的想法,若要使得因子尽可能多,那么因子该当尽可能小,大于根号n的因子至少一个,从小到大枚举[1, sqrt(n)]的所有整数,如果i可能整除n就作为一个因子。 Code: #include <bits/stdc++.h>#define int long longusing namespace std;void solve(){ int n;cin >> n; set<int> st; for(int i = 1;i <= n / i; ++ i) if(n % i == 0)st.insert(i), n /= i; st.insert(n); cout << st.size() << '\n';}signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int _;cin >> _; while(_ --)solve(); return 0;}区区区间间间(枯燥栈)题意:给定一个数组,求所有子区间的最大值与最小值的差的和。 如果暴力枚举必定不行,单单是子区间个数就有n ^ 2个,所以咱们应该思考每一个元素对答案的奉献,也就是说咱们须要晓得某个元素a[i]它会在多少个区间里作为最大值,在多少个区间里作为最小值。 咱们预处理出四个数组,别离是lmax[], rmax[], lmin[], rmin[]示意点i作为最大值的左右最远地位,和作为最小值的左右最远地位(lmax[i] = j,示意在区间[j, i]中的最大值都是a[i],其余的三个数组相似定义)。 用枯燥栈能够解决出这四个数组,一下以求lmax[]举例,保护一个枯燥不增栈,栈内寄存的是下标。 初始时栈内仅有一个a[0] = inf。 ...
题目链接:https://ac.nowcoder.com/acm/contest/54484/B 题意很简略,然而数据范畴偏大。 错排公式首先来推导一下错排公式: $$D(n) = n!\sum_{k=0}^{n}\frac{(-1)^k}{k!}$$ 设一个函数: $$S_i示意一个排列中p_i = i的计划数$$ 那么咱们能够晓得: $$D(n) = n! - |\cup_{i=1}^{n}S_i|$$ 这个示意所有计划数减去至多有一个地位放对的计划数。 当初来考虑一下如何解决前面这个并集,并集往往是不好求的,而交加会好求很多,所以在求并集的时候咱们往往采取容斥原理将一个并集转换成诸多交加的加减运算。 咱们用一个图能够来示意当n = 3的状况: 其中有: $$|S_1 \cup S_2 \cup S_3| = |S_1| + |S_2| + |S_3| - |S_1 \cap S_2| - |S_1 \cap S_3| - |S_2 \cap S_3| + |S_1 \cap S2 \cap S_3|$$ 扩大一下就能够失去上面的柿子: $$|\cup_{i=1}^{n}S_i| = \sum_{k=1}^{n}(-1)^k\sum_{1\leq i_1 \leq i_2 \leq ... \leq i_k \leq n}|S_{i1}\cap S_{i2} ... \cap S_{ik}|$$ ...
上一篇文章咱们讲了两种经典的博弈模型:《【ACM博弈论】SG函数入门(1):从巴什博奕到尼姆游戏》,这一节咱们开始解说SG函数。 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 浏览原文取得更好浏览体验:https://www.eriktse.com/algorithm/1111.html在理解SG函数之前,咱们须要晓得博弈图。 博弈图就比方Bash博弈,当n=7,m=3时,咱们能够画出如下的博弈图。 咱们能够发现,每一个点都有至少2个后继状态(即出点),这个是能够通过Bash推出来的。 其余博弈题大多也能够相似的推出一个这样的图。 SG函数SG函数能够了解为一个用于示意博弈图中节点状态的一个函数。同时sg(x) = n还示意节点x的出点形成一个汇合{y | 0 <= sg(y) <= n - 1},也就是说x能够达到所有sg小于它本人的sg的点。 就比方上图,咱们规定必败态的sg = 0,必胜态的sg != 0。于是咱们能够晓得sg(0) = 0,而后往回推。 sg函数转移方程 $$sg(x) = mex({y | y \in out[x]})$$ 说人话就是x的sg是其所有出点的sg形成的汇合做mex运算,mex示意汇合中最小的没呈现过的自然数。 代码个别为: int mex(set<int>& st){ for(int i = 0;; ++ i) if(st.find(i) == st.end())//如果找不到i return i;}于是咱们能够推出下面这个博弈图的所有点的sg函数。 留神是依据所有出点推出以后点,只有所有出点都确定了,以后点的sg能力确定,有点像建反图而后topo,然而个别咱们会间接写一个记忆化搜寻而后打表找法则。在解决带环的图时须要具体情况具体分析。 下面这张图咱们很容易找出法则,就是0 1 2 0 1 2....子游戏的合并Nim定理:全局后果等于子游戏SG的异或和。 咱们昨天学过Nim博弈,他是有n堆石子,每次能够选一堆拿走若干个。那么咱们能够将子游戏看做是一堆石子,每堆石子的个数是 (sg) 个,而后取走若干个石子类比为将sg转移到更小的sg。 当初咱们就能够解决一些形象的博弈问题了。 做题个别思路个别是三步:找出SG转移方程,打表找法则,子游戏合并。 为什么须要打表找法则呢,因为个别题目给的数据会很大,且个别会有较强的规律性,打表找到法则就行无需证实,证实对于比赛来说太侈靡了,而且没太大意义。 例题:AtCoder Beginner Contest 297 - Constrained Nim 2 ...
前言之前分享过Scott Meyers的两本书《Effective C++》和《More Effective C++》。这两本书对我深刻学习C++有着很大的帮忙,倡议所有想进阶C++技术的同学都能够看看。然而,这两本书是大神Scott在C++11之前出的,而C++11对于C++社区来说是一次重大的改革,被称为古代C++,用以辨别C++11之前的传统C++。 好在Scott在之后也带来了全新的《Effective Modern C++》,其中也是连续了作者一贯的格调和品质。带来了42个独家技巧,助你改善C++11和C++14的高效用法(封面语)。 本文首先就带同学们一起看看这本书的前两章——类型推导和auto。 首先申明本文只是做知识点的总结,书中有更具体的推导和解说过程,感兴趣的同学还是强烈建议大家去读原书。 类型推导条款1:了解模板类型推导模板类型推导是C++11利用最宽泛的个性之一——auto的根底。所以,了解auto的推导规定和正确应用形式的根底就是了解模板类型推导的规定。 先来看看模板和其调用的个别模式。 template<typename T>void f(ParamType param);f(expr);这里须要T的类型推导后果,依赖于expr的类型和ParamType的模式。其中,ParamType的模式须要分三种状况探讨。 状况1:ParamType是个指针或者援用,但不是万能援用(模式如T&&)。在这种状况下,模板类型推导具备以下规定: expr的援用属性会被疏忽。疏忽expr的援用性后,expr的类型和ParamType的类型进行模式匹配,由此决定T的类型。举个例子: // 申明模板template<typename T>void f(T& param);// 申明变量int a = 1;const int ca = a;const int& cra = a;//调用模板f(a); //a的类型是int,T的类型是int,param的类型是int&。f(ca); //ca的类型是const int,T的类型是const int,param的类型是const int&。f(cra); //cra的类型是const int&,T的类型是const int,param的类型是const int&。要点1:在模板类型推导过程中,具备援用类型的实参会被当成非援用类型来解决。状况2:ParamType是个万能援用。在这种状况下,模板类型推导规定如下: 如果expr是个左值,则T和ParamType都会被推到为左值援用。如果expr是个右值,则和状况1中的推导规定雷同。举个同状况1相似的例子: // 申明模板template<typename T>void f(T&& param);// 申明变量int a = 1;const int ca = a;const int& cra = a;//调用模板f(a); //a是左值,类型是int,T的类型是int&,param的类型是int&。f(ca); //ca是左值,类型是const int,T的类型是const int&,param的类型是const int&。f(cra); //cra是左值,类型是const int&,T的类型是const int&,param的类型是const int&。f(1); //1是右值,类型是int,T的类型是int,param的类型是int&&。要点2:对万能援用形参进行推导时,左值实参会进行非凡解决。状况3:ParamType即不是指针也不是援用。这种状况就是按值传递,其指标推导规定如下: ...