跳到主要内容

逆波兰法-算术表达式语法分析C++

· 阅读需 3 分钟

逆波兰表达式的C++实现。算术表达式求值,支持加减乘除和幂运算,支持圆括号改变优先级。

//后缀表达式练习
//2018-09-20
#include <stack>
#include <iostream>
#include <cstdlib>
#include <string>
#include <math.h>
using namespace std;
//定义优先级
enum Priority {
PRI_IGNORE,//无效字符,忽略
PRI_NUMBER,//操作数
PRI_PLUS,//加减
PRI_DIVIDE,//乘除
PRI_POWER,//幂
PRI_LEFT_PAR,//左圆括号
PRI_RIGTHT_PAR,//右圆括号
};
//取操作符优先级
Priority getPriority(char symbol) {
if ((symbol >= '0' && symbol <= '9') || symbol == '.')
return PRI_NUMBER;
else if (symbol == '+' || symbol == '-')
return PRI_PLUS;
else if (symbol == '*' || symbol == '/')
return PRI_DIVIDE;
else if (symbol == '^')
return PRI_POWER;
else if (symbol == '(')
return PRI_LEFT_PAR;
else if (symbol == ')')
return PRI_RIGTHT_PAR;
else
return PRI_IGNORE;
}
//中缀表达式转后缀表达式
string toSufixExpression(string express) {
stack<char> operendStack;//操作符暂存栈
string output;//输出后缀表达式
//上一操作符的优先级,如果当前处理的操作符优先级低于它则前操作符出栈
Priority lastPriority = PRI_NUMBER;
string::iterator iter = express.begin();
for (; iter != express.end(); ++iter) {
Priority priority = getPriority(*iter);
switch (priority) {
case PRI_IGNORE:
default:
continue;
case PRI_NUMBER:
output.push_back(*iter);
break;
case PRI_PLUS:
case PRI_DIVIDE:
case PRI_POWER:
case PRI_LEFT_PAR:
//遇到符号向前追加间隔符
output.push_back(' ');
while (operendStack.size()) {
if (getPriority(operendStack.top()) < priority || operendStack.top() == '(')
break;
output.push_back(operendStack.top());
operendStack.pop();
}
lastPriority = priority;
operendStack.push(*iter);
break;
case PRI_RIGTHT_PAR:
while (operendStack.size()) {
if (operendStack.top() != '(') {
output.push_back(operendStack.top());
operendStack.pop();
}
else {
operendStack.pop();
break;
}
}
break;
}
}
//处理完毕,剩余操作符出栈
while (operendStack.size()) {
output.push_back(operendStack.top());
operendStack.pop();
}
return output;
}
//计算后缀表达式
double caculate(string express) {
stack<double> calcStack;//计算存储栈
string::iterator iter = express.begin();
while (iter != express.end()) {
//字符串转整形,并压入栈
if (*iter >= '0' && *iter <= '9') {
string number;
do {
number.push_back(*iter);
++iter;
} while ((*iter >= '0' && *iter <= '9') || *iter == '.');
calcStack.push(atoi(number.c_str()));
continue;
}
//间隔符
if (*iter == ' ') {
++iter;
continue;
}
//操作符,执行运算
if (calcStack.size() < 2)
return 0;
double operandOne = calcStack.top();
calcStack.pop();
double operandTwo = calcStack.top();
calcStack.pop();
switch (*iter++) {
case '+':
calcStack.push(operandTwo + operandOne);
break;
case '-':
calcStack.push(operandTwo - operandOne);
break;
case '*':
calcStack.push(operandTwo * operandOne);
break;
case '/':
calcStack.push(operandTwo / operandOne);
break;
case '^':
calcStack.push(pow(operandTwo, operandOne));
break;
default:
calcStack.push(operandTwo);
calcStack.push(operandOne);
break;
}
}
if (calcStack.size() == 1)
return calcStack.top();
return 0;
}

int main() {
cout << "Please enter an expression:" << endl;
string express;//表达式
getline(cin, express);
if (express.empty()) {
cout << "empty express.\n";
return 0;
}
string output = toSufixExpression(express);
cout << caculate(output) << endl;
system("pause");
return 0;
}

PHP学习-LoadXML与网页格式错乱

· 阅读需 3 分钟

用php写后端动态生成网页内容的时候,用到了DOMDocument类的操作。为了减少创建元素和文本节点的代码(与效率无关),使用了loadXML()方法载入静态的HTML文本(通过heredoc)。

$xml = new DOMDocument(); $xml->loadXML(<<<_HTML <div class="-article"> <div class="-article-title" onselectstart="return false;"></div> <hr/> <div class="-article-body"></div> <hr/> <div class="-article-extra"></div> <div class="-aborted"></div> <div class="-article-picture"></div> </div> _HTML ); ...... echo xml->saveXML();

没错,就是用loadXML()载入HTML,这样做是因为saveHTML()的时候会输出完整的HTML文档(包含html和body元素)而不是我想要的文章部分,而saveXML()则只需要去除首行的文档声明即可。

这里不谈这样的做法好与不好,只说说我遇到的问题。

遇到的问题是,网页版面乱了。出现了块级元素的堆叠,就是生成的元素后面的同级元素变成了它的子元素。感觉是标签没有闭合导致的,用浏览器看了生成的网页代码,终于找到了问题所在。

耗子屎在这一行:

<div class="-aborted"></div>

由于一些原因,生成元素中这块被废弃了,后面的代码又很多地方使用了getElementsByTagName方法获取指定元素,需要靠子元素的位置定位,所以不好直接删除(可见装载静态的html文档结构并不是个好点子),于是把这个<div>的类名设置为-aborted,然后统一处理。因为是废弃元素,所以自然也不会为它生成内容了,最后saveXML输出的文本中,将这个空元素<div class="-aborted"></div>转化成了<div class="-aborted"/>。在html5中自闭合标签是有严格控制的,只有特定的标签才允许,因此<div/>并没有被浏览器认为是一个闭合的标签,然后和后面的div块混乱了,才导致的这个问题。

解决办法是,给heredoc中的废弃元素的内容加个空格,xml封装器就不会将它当作空元素了。不过最好还是全都动态生成元素,少生幺蛾子。或者使用其他更好的办法。

PHP-防止静态资源被直接访问

· 阅读需 7 分钟

用PHP写后端,想要达到用户登录后才可以访问一些图片和视频资源的效果,因此要阻止用户直接输入资源地址访问资源。

找了一些资料,自己总结了几种方法。

1.根据Referer头——防盗链

浏览器在发起HTTP请求时一般都会一同发送Referer头。Referer头是用户跳转前的页面,也就是通过哪个页面发起的请求。通过禁止非法Referer头的资源请求可以一定程度防止资源被非法访问。一般这个都是在web服务器软件上设置而不是后端处理。由于这个方法并不是很靠谱所以没试过(毕竟请求头是由请求方控制的),不过应付一般用户足够了,特别适合用来防止其他网站挂自己服务器的资源链接以转移服务器负载,不过一般小网站用不到就是了。

2.通过复杂文件名

给资源文件赋以随机的文件名,用数据库记录,然后定期或不定期更新文件名,用户访问页面时后端php动态的查询资源文件名。

这个方法还算可以,缺点是频繁的数据库连接将会增大服务器负载。如果服务器支持可以试试数据库持久连接,不过需要注意持久连接的一些坑,不然可能造成连接锁死之类的问题。

3.隐藏资源——将资源文件放在用户无法访问的目录。

这样做有两种方案可以选择,一是在用户需要访问时将资源文件复制到相应位置(可通过创建硬链接避免时间和空间浪费),二是将所有对资源的访问重定位到一个文件,后端统一验证身份后输出文件内容。

第一种其实意义不大,因为总是要让用户访问的,那只要有授权用户在需要资源文件,你就得把资源放在那里,然后就谁都可以访问了,再然后发现问题回到方法2了——还是得改文件名。

第二种方法是比较靠谱的方法。比如我将资源访问重定向到resource.php这个文件,然后验证身份后根据GET请求参数去找用户请求的文件,然后用readfile函数读取并输出文件就OK。用户的看到的网页源代码将类似这样:

<img src="http://127.0.0.1/resource.php?path=filename"/>

filename可以是真正的资源相对路径,因为用户反正是无法直接访问的,暴露文件路径反而可以省去查数据库的消耗。需要注意的是,如果需要传递的文件路径中包含特殊字符如“/”等需要转义。可在后端统一由某个接口封装,生成安全的资源链接,类似这样:

<?php function getURL($path) { return 'http://127.0.0.1/resource.php?path=' . urlencode($path); } ?> <body> <img src="<?php echo getURL('picture/dog.jpg')?>"> </body>

然后resource.php中验证用户是否已授权,如果是且资源访问合法,readfile('/resource_path/' . $_GET['path']),结束。

这应该是目前最靠谱的办法,不过它也有缺陷。如果请求的资源是视频这种比较大的文件,浏览器会一直等待资源接收完毕才显示后面的内容,而不是页面加载完再以流媒体的方式加载视频资源,因此这个方法无法用于大文件资源。同时,测试发现,css中的url()资源不支持这样的方式,因此背景图片之类的资源也无法使用这种方法。

4.大杂侩——结合两种方法

非常不幸,我要做的东西正好需要请求大量视频资源,因此采用方法3中的第二类发现浏览器一直转加载视频,后面的评论等板块得等视频下载完成才加载,完全不能忍。于是想了半天,采取了折中的办法:对于小图片、少量图片、文本资源采用方法3第二方案,也就是资源访问重定向到resource.php统一处理;对于大图片、视频资源、大量图片和css中的资源,则通过临时硬链接的方式。

上面3中已经说了readfile()输出的方法,下面说说硬链接的具体做法。

我们知道,php中有个session的概念,它为一个会话生成一个id,并在客户端以cookie的形式保存,在服务器端创建一个唯一的session文件,用于保存与相关会话相关的数据,每次用户请求中包含的cookie便告诉服务器当前会话的相关数据,比如是否已经登录等。

这里正利用了php的session机制。

我在网站目录中创建了一个temp目录,它包含一个空文件index.html以防止用户直接访问目录看到目录下的文件列表(也可在服务器端配置禁止对目录的访问),因此用户可以访问该目录下的文件而无法得知它包含哪些子目录或文件,这是前提。

当用户页面需要请求一个需要授权访问的资源时,如果用户是授权的(通过session机制判断),后端生成相应资源一个非随机的硬链接。它是这样的一个硬链接:

  1. 它的父目录是temp/当前会话session ID/
  2. 它的文件名由它的相对路径通过哈希算法生成,再加上文件后缀

例如,如果我的资源目录(禁止用户访问)是/resource_path,存在资源/resource_path/video/dog.mp4。网站目录是/(对于用户),包含temp子目录。那么我的页面应该这样写:

<video src="<?php echo '/temp/' . session_id() . '/' . md5('video/dog.mp4') . '.mp4'?>"></video>

之前的getURL函数变成这样:

function getURL($path, $flow = false) {
//非法资源
if(!file_exists('/resource_path/' . $path)) {
return '';
}
//小文件资源,采用资源重定向方案
if(!$flow) {
return '/resource.php?path=' . urlencode($path);
}
//获取后缀
preg_match('/\.[^\.\/]*$/', $path, $sufix);
$filename = '/temp/'. session_id() . '/' . md5($path) . $sufix[0];
//判断资源链接是否已创建,WWW_ROOT为网站根目录实际路径
if(file_exists(WWW_ROOT . $filename)) {
return $filename;
}
//创建temp/sessionID目录
else if(!file_exists(dirname(WWW_ROOT . $filename))) {
mkdir(dirname(WWW_ROOT . $filename));
}
//创建硬链接。注意Windows不支持PHP的link函数,看你服务器平台
if(stripos(PHP_OS, 'WIN') === false) {
link(HEVER_ROOT . $path, HEVER_ROOT . $filename);
}
else {
system('mklink /H "' . HEVER_ROOT . $filename . '" "' . HEVER_ROOT . $path . '"');
}
return HEVER_HOME . $filename;
}

当然,md5()的参数也可以另外的算,不一定是相对路径。

这样做的好处是,当用户再次请求相同的资源时,只需确保相应的硬链接存在即可直接不管了,因此需要这个硬链接的文件名是非随机的避免查询数据库。而且这样多了一层session id阻隔,即使非法用户知道了生成资源链接文件名的规则也无法访问到资源,因为资源链接是在当前会话的session id目录下的,除非他能猜到某个会话的session id再模拟发送cookie——还不如让他猜用户名和密码呢。

当然这个方法也不是完美的,由于可能会生成大量的硬链接和session文件并且废弃后不会消失,需要定期执行清理脚本来清除过期的链接和文件。我的解决方案是,当用户登录或登出时触发一个脚本,它会清除超过1天未访问过的session文件和对应的temp目录中的同名名录(其实不是同名,session文件还有sess_前缀)。这个灵感来自于wordpress的伪cron机制。

需要注意的是,Windows系统不支持php的link函数,因此得用windows的shell。还有一点,清理php session文件时,如果php.ini中未配置session.save_path,在php中用session_save_path()可能获取不到php默认的session文件存放目录,因此建议在文件首主动配置session目录,使用session_save_path(string $path)函数。

注:以上方案均未经过严密的测试,请谨慎参考。

求小于一个整数的质数的个数的n个版本

· 阅读需 6 分钟

输入一个整数n,输出不大于它的质数的个数。

这是一个经典的问题。不管用什么算法,思路都是嵌套循环,对小于n的自然数判断是否质数。

代码不好贴,就截图了。文末面有源码链接。

基础版

最笨的办法就是按顺序分别判断:
//基础版
int primeNum_0(int n)
{
int div;//试除变量
int count = 0;//质数个数
for (int i = 2; i <= n; ++i)
{
for (div = 2; div < i; ++div)
{
if (i % div == 0)//不是质数
break;
}
if (div == i)//质数
++count;
}
return count;
}

测试输出:

There are 9592 prime numbers in 99999.
Completed with 1429 ms.

升级版

//升级版
int primeNum_1(int n)
{
int div, count = 0;
for (int i = 2; i <= n; ++i)
{
//试除上限为i的平方根取较大整数
int top = floor(sqrt(i)) + 1;
for (div = 2; div < top; ++div)
{
if (i % div == 0)
break;
}
if (div >= top)
++count;
}
return count;
}

这里对比上面的基础版,尽管代码有些变化,但可以看出它们仅有的区别就是当试除i的数div大于√i时,就不再继续试除,而判定i为质数。

这里利用了数本身的性质:如果一个数i可以被不小于√i的整数整除,那么得到的商一定是不大于√i的整数。因此,在试除到√i时便可以判定i是否为质数了。

这样一步操作,使运算的次数大大减少。

测试输出:

There are 9676 prime numbers in 99999.
Completed with 24 ms.
There are 665107 prime numbers in 9999999.
Completed with 5680 ms.

对于输入99999,对比基础版的超过1秒的运算时间,升级版只用了不到0.1秒。

升级改良版

在升级版的基础上,还可以改进。

//升级改良版
int primeNum_2(int n)
{
int div, count = 1;//2直接算作质数
for (int i = 3; i <= n; i = i + 2)
{
//因为试除从3开始,2的试除单独提出来
if(i % 2 == 0)
continue;
int top = floor(sqrt(i)) + 1;
for (div = 3; div < top; div = div + 2)
{
if (i % div == 0)
break;
}
if (div >= top)
++count;
}
return count;
}

这个版本相对于升级版又有了一些改动:试除从3开始,每次步进2。因为从循环里是3开始的,所以对于2的试除单独放出来(减少避免在循环中增加条件判断),不影响性能。同时这样3也无法正常计算了,索性把3也提前算上,count初始化为2。

这里的原理也很简单:任何大于2的偶数不可能是质数。为了应用这个原理,有了上面的改动,虽然代码变得有些畸形,不过速度却相对提升了接近一倍。

其实这里还有个bug,当输入1或者2的时候也会输出有两个质数。可以在循环外加上条件判断,单独处理,只是这样代码会不那么美观。事实上这个算法在设计上本身也很不完美,博主还没有学过算法,很多不规范的地方请谅解。

测试输出:

There are 9674 prime numbers in 99999.
Completed with 0 ms.
There are 665105 prime numbers in 9999999.
Completed with 2826 ms.

这里99999的输入已经可以在1毫秒之内解决了,9999999用了接近3秒,和升级版的5秒多相比有了不错的提升。

从速度上来看,99999从20多毫秒提升到1毫秒,二十多倍,而9999999却只是加快了两倍左右,这或许和CPU的多任务机制有关,相关内容不怎么熟悉,就不解释了。因此用执行时间来反应算法速度并不很科学,或许用变量记录最内层循环执行的次数会更好。

豪华版


//豪华版
int primeNum_3(int n)
{
int count = 1;
int maxSize;//最大存储质数的个数
//申请内存
if(MAX_SIZE < ceil(sqrt(n)))
maxSize = MAX_SIZE;
else
maxSize = (int)(sqrt(n));
int* primeNums = (int*)malloc((maxSize * sizeof(int)));

primeNums[0] = 2;//2先放进去
int size = 1;//当前存储的质数个数
int div, cur, top;
for (int i = 3; i <= n; ++i)
{
top = ceil(sqrt(i));//试除上限
cur = 0;//当前试除数在存储空间的位置
div = primeNums[cur];
while (i % div != 0)
{
if (div >= top)
{
if (size < maxSize)//判断存储空间是否已满
primeNums[size++] = i;//将质数加入存储数组
++count;
break;
}//找到质数

//若已试除到存储空间最后一个数,步进2
if (cur < size - 1)
div = primeNums[++cur];
else
div += 2;
}
}
free(primeNums);
return count;
}

还有一个可以利用的性质,对于正整数i,如果i 可以被div整除(div为小于i且大于2的非质数),那么i一定存在小于div的质数可以整除i,因为非质数div可以被分为若干质数的乘积。

而我们判断一个数是否是质数是从2开始去试除这个数(即使这个判断被单独列出),而2是最小的质数,因此要判断一个大于2的正整数i是否是质数,只需要判断是否存在一个数k可以整除i,其中k是[2,√i]区间内的质数

因此,我们可以建立一个质数存储表。每当判断出一个数是质数时,就把这个数加入表中。因为我们是从小到大开始判断,所以这个表也是从小到大的。然后对于一个数i,只需用这个表中不大于√i的质数去试除i,如果最后一不大于√i的质数都无法整除i,那么i是一个质数。

这个方法比上一种快一些,但消耗的空间也大得多。

测试程序

int main()
{
int n = 0;
scanf("%d", &n);
if(n < 2)
return 0;

int result, timeCount;//计时

if(n <= 100000)
{
timeCount = clock();
result = primeNum_0(n);
timeCount = clock() - timeCount;
printf("%d 个质数,基础版,%d 毫秒\n", result, timeCount);
}//数值太大基础版耗时太久

timeCount = clock();
result = primeNum_1(n);
timeCount = clock() - timeCount;
printf("%d 个质数,升级版,%d 毫秒\n", result, timeCount);

timeCount = clock();
result = primeNum_2(n);
timeCount = clock() - timeCount;
printf("%d 个质数,升级改良版,%d 毫秒\n", result, timeCount);

timeCount = clock();
result = primeNum_3(n);
timeCount = clock() - timeCount;
printf("%d 个质数,豪华版,%d 毫秒\n", result, timeCount);

//system("pause");
}

总结

程序中还有一些考虑不周的地方甚至小bug,例如div的步进放在了跳出判断的前面,这样导致了奇质数的平方也被判断为质数了。在代码文件中修改了一些,可能仍然存在bug。

[转载]gdb调试利器

· 阅读需 7 分钟

转自:http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/gdb.html

GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。 对于一名Linux下工作的c++程序员,gdb是必不可少的工具;

1. 启动gdb

对C/C++程序的调试,需要在编译前就加上-g选项:
$g++ -g hello.cpp -o hello
调试可执行文件:
$gdb <program>
program也就是你的执行文件,一般在当前目录下。

调试core文件(core是程序非法执行后core dump后产生的文件):

$gdb <program> <core dump file> $gdb program core.11127
调试服务程序:
$gdb <program> <PID> $gdb hello 11127
如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试他。program应该在PATH环境变量中搜索得到。

2. gdb交互命令

启动gdb后,进入到交互模式,通过以下命令完成对程序的调试;注意高频使用的命令一般都会有缩写,熟练使用这些缩写命令能提高调试的效率;

运行

  • run:简记为 r ,其作用是运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步的命令。
  • continue (简写c ):继续执行,到下一个断点处(或运行结束)
  • next:(简写 n),单步跟踪程序,当遇到函数调用时,也不进入此函数体;此命令同 step 的主要区别是,step 遇到用户自定义的函数,将步进到函数中去运行,而 next 则直接调用函数,不会进入到函数体内。
  • step (简写s):单步调试如果有函数调用,则进入函数;与命令n不同,n是不进入调用的函数的
  • until:当你厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体。
  • until+行号: 运行至某行,不仅仅用来跳出循环
  • finish: 运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息。
  • call 函数(参数):调用程序中可见的函数,并传递“参数”,如:call gdb_test(55)
  • quit:简记为 q ,退出gdb

设置断点

  • break n (简写b n):在第n行处设置断点
    (可以带上代码路径和代码名称: b OAGUPDATE.cpp:578)
  • b fn1 if a>b:条件断点设置
  • break func(break缩写为b):在函数func()的入口处设置断点,如:break cb_button
  • delete 断点号n:删除第n个断点
  • disable 断点号n:暂停第n个断点
  • enable 断点号n:开启第n个断点
  • clear 行号n:清除第n行的断点
  • info b (info breakpoints) :显示当前程序的断点设置情况
  • delete breakpoints:清除所有断点:

查看源代码

  • list :简记为 l ,其作用就是列出程序的源代码,默认每次显示10行。
  • list 行号:将显示当前文件以“行号”为中心的前后10行代码,如:list 12
  • list 函数名:将显示“函数名”所在函数的源代码,如:list main
  • list :不带参数,将接着上一次 list 命令的,输出下边的内容。

打印表达式

  • print 表达式:简记为 p ,其中“表达式”可以是任何当前正在被测试程序的有效表达式,比如当前正在调试C语言的程序,那么“表达式”可以是任何C语言的有效表达式,包括数字,变量甚至是函数调用。
  • print a:将显示整数 a 的值
  • print ++a:将把 a 中的值加1,并显示出来
  • print name:将显示字符串 name 的值
  • print gdb_test(22):将以整数22作为参数调用 gdb_test() 函数
  • print gdb_test(a):将以变量 a 作为参数调用 gdb_test() 函数
  • display 表达式:在单步运行时将非常有用,使用display命令设置一个表达式后,它将在每次单步进行指令后,紧接着输出被设置的表达式及值。如: display a
  • watch 表达式:设置一个监视点,一旦被监视的“表达式”的值改变,gdb将强行终止正在被调试的程序。如: watch a
  • whatis :查询变量或函数
  • info function: 查询函数
  • 扩展info locals: 显示当前堆栈页的所有变量

查询运行信息

  • where/bt :当前运行的堆栈列表;
  • bt backtrace 显示当前调用堆栈
  • up/down 改变堆栈显示的深度
  • set args 参数:指定运行时的参数
  • show args:查看设置好的参数
  • info program: 来查看程序的是否在运行,进程号,被暂停的原因。

分割窗口

  • layout:用于分割窗口,可以一边查看代码,一边测试:
  • layout src:显示源代码窗口
  • layout asm:显示反汇编窗口
  • layout regs:显示源代码/反汇编和CPU寄存器窗口
  • layout split:显示源代码和反汇编窗口
  • Ctrl + L:刷新窗口

注解

交互模式下直接回车的作用是重复上一指令,对于单步调试非常方便;

3. 更强大的工具

cgdb

cgdb可以看作gdb的界面增强版,用来替代gdb的 gdb -tui。cgdb主要功能是在调试时进行代码的同步显示,这无疑增加了调试的方便性,提高了调试效率。界面类似vi,符合unix/linux下开发人员习惯;如果熟悉gdb和vi,几乎可以立即使用cgdb。

关于宏定义的问题

· 阅读需 2 分钟

C语言中的宏定义有时候很方便,有时候也有些不便。宏最重要的性质之一就是,它是在编译的时候直接替换相应的关键字,只是简单的替换。所以用宏定义表达式时需要额外注意。

今天打算写一个函数库,然后里面的函数都是驼峰命名风格(例如funcMyFunction)的,想同时实现Pascal风格(FuncMyFunction)调用。显然把代码复制一遍定义新函数是很不明智的,体积增加,代码大量重复,维护不便等等。

于是我就打算通过定义宏来实现:

namespace A
{
#define TestFunc testFunc
// ......
}

但问题就来了,宏定义是无视命名空间的。

也就是说,虽然是在名空间A中定义的,但它是全局有效的。如果实际项目中包含了这个头文件,而这个项目又包含了其他库的头文件,万一有相同名称的函数就会出错,因为所有文件中的"TestFunc"都会被替换为testFunc,然后出错。虽然这个概率不大,但其实也不算太小。

想了半天也没想到完美的解决方案,不过想到两个规避的方法。

  1. 单独用一个头文件定义这些宏,然后在编译时灵活决定是否包含它(或者在头文件中设置条件编译)。
  2. 给库中的函数加上前缀,比如mylib_funcMyFunc,这样再定义宏也能很大程度上规避重复,这条可以与第一条同时进行,应该比较保险了。

这个问题意义不是很大,不过想到了也就一说。

sublime——解决从命令提示符输入数据的问题

· 阅读需 1 分钟

Sublime Text默认的编译配置会掠过scanf等输入函数,直接在输出窗口显示运行结果,当需要输入数据时很不方便。其实可以自己修改sublime的编译配置来达到目的。

假设%DIR%是sublime安装目录;

找到%DIR%\Packages\C++.sublime-package文件,复制一份到其它目录,把文件后缀改为zip,解压后找到C++ Single File.sublime-build文件,可用记事本打开并复制内容;

选择sublime菜单->工具(Tools)->编译系统(Build System)->新编译系统(New Build System),新建一个编译系统,把刚刚复制的内容粘贴到新建的文件中;

修改其中的"variants"项下面,"name": "Run"后面的"shell_cmd"项;

假设原来是:

"shell_cmd": "g++ -m32 "${file}" -o "${file_path}/${file_base_name}" && "${file_path}/${file_base_name} ""

则修改为:

"shell_cmd": "g++ -m32 "${file}" -o "${file_path}/${file_base_name}" && start cmd /c "${file_path}/${file_base_name}""

CTRL+S或点击菜单文件(File)->保存(Save)保存文件,命名为User-C++.sublime-build(或其它)。

大功告成。

Sublime Text——便携版编译环境配置

· 阅读需 3 分钟

实验室电脑没有GCC,于是在U盘里复制了一份MinGW的C++编译环境。由于懒得每次配置环境变量,就想把MinGW放到Sublime目录里,直接使用。

但sublime的编译系统默认的工作目录是目标文件所在的目录,所以编译时提示找不到g++命令。由于U盘的盘符经常会改变,所以也无法固定设置。找了很久,终于找到了办法。

sublime定义了一组特殊变量:

$file_path
The directory of the current file, e.g. C:\Files.
$file
The full path to the current file, e.g.C:\Files\Chapter1.txt.
$file_name
The name portion of the current file, e.g.Chapter1.txt.
$file_extension
The extension portion of the current file, e.g.txt.
$file_base_name
The name-only portion of the current file, e.g.Document.
$folder
The path to the first folder opened in the current project.
$project
The full path to the current project file.
$project_path
The directory of the current project file.
$project_name
The name portion of the current project file.
$project_extension
The extension portion of the current project file.
$project_base_name
The name-only portion of the current project file.
$packages
The full path to thePackagesfolder.
其中,$packages是可以定位到sublime安装目录中的 "Data\Packages" 目录的。

配置方法

假设%DIR%是sublime安装目录;

复制MinGW目录到%DIR%\Data\Packages目录;

找到%DIR%\Packages\C++.sublime-package文件(注意和上面的Packages目录不是同一个),复制一份到其它目录,把文件后缀改为zip,解压后找到C++ Single File.sublime-build文件,用记事本打开它,复制内容;

选择sublime菜单->工具(Tools)->编译系统(Build System)->新编译系统(New Build System),新建一个编译系统,把刚刚复制的内容粘贴到新建的文件中;

修改"working_dir" 后面的"${file_path}" 为"${packages}\MinGW\bin"(g++.exe等所在目录),其他的也可以根据需要自己修改,注意反斜杠要转义;

CTRL+S或点击菜单文件(File)->保存(Save)保存文件,命名为User-C++.sublime-build(或其它)。

大功告成。