Shell中的条件判断语句if的用法

一、基本语法

if [ command ]; then
     符合该条件执行的语句
fi
if [ command ];then
     符合该条件执行的语句
elif [ command ];then
     符合该条件执行的语句
else
     符合该条件执行的语句
fi

语法说明

bash shell会按顺序执行if语句,如果command执行后且它的返回状态是0,则会执行符合该条件执行的语句,否则后面的命令不执行,跳到下一条命令。
当有多个嵌套时,只有第一个返回0退出状态的命令会导致符合该条件执行的语句部分被执行,如果所有的语句的执行状态都不为0,则执行else中语句。
返回状态:最后一个命令的退出状态,或者当没有条件是真的话为0。

(更多…)

你可能还喜欢下面这些文章

bash教程:一、变量,函数,控制流程

c语言的位操作

std::endl为什么导致程序变慢

linux shell 入门

shell 变量的定义

程序启动停止脚本

每次启动程序都要敲一堆命令,终止程序都要ps+grep找到程序pid然后kill,太麻烦了!

花了点时间写了个程序启动停止脚本,如下:

#! /bin/bash

readonly CMD=$1
readonly PARAM=$2

readonly BIN="./your_bin"

start() {
    local bin=$1
    local param=$2

    if [ -f .pid ];then
        echo "${bin} is running"
        return 0;
    fi

    if [ ${param}"" == "-d" ];then
        nohup ${bin} &
        echo $! > .pid
        echo "${bin} started"
        return 0
    fi

    ${bin}
}

stop() {
    pid=`cat .pid`
    if [ $pid"" = "" ]; then
        echo "already stopped"
        exit
    fi
    kill -2 $pid
    rm .pid
    echo "stopped"
}

help() {
    echo "sh start.sh [start|stop|restart] [-d]"
}

case "$CMD" in
    start)
        start $BIN $PARAM
        ;;
    stop)
        stop
        ;;
    restart)
        stop
        sleep 1
        start
        ;;
    *)
        help
        ;;
esac

只需要配置一个BIN变量即可实现程序的启动和停止,十分简单。

你可能还喜欢下面这些文章

Shell中的条件判断语句if的用法

如何选择特征

特征工程是数据分析中最耗时间和精力的一部分工作,它不像算法和模型那样是确定的步骤,更多是工程上的经验和权衡。因此没有统一的方法。这里只是对一些常用的方法做一个总结。本文关注于特征选择部分。后面还有两篇会关注于特征表达和特征预处理。

1. 特征的来源

在做数据分析的时候,特征的来源一般有两块,一块是业务已经整理好各种特征数据,我们需要去找出适合我们问题需要的特征;另一块是我们从业务特征中自己去寻找高级数据特征。我们就针对这两部分来分别讨论。

2.  选择合适的特征

我们首先看当业务已经整理好各种特征数据时,我们如何去找出适合我们问题需要的特征,此时特征数可能成百上千,哪些才是我们需要的呢?

第一步是找到该领域懂业务的专家,让他们给一些建议。比如我们需要解决一个药品疗效的分类问题,那么先找到领域专家,向他们咨询哪些因素(特征)会对该药品的疗效产生影响,较大影响的和较小影响的都要。这些特征就是我们的特征的第一候选集。

这个特征集合有时候也可能很大,在尝试降维之前,我们有必要用特征工程的方法去选择出较重要的特征结合,这些方法不会用到领域知识,而仅仅是统计学的方法。

最简单的方法就是方差筛选。方差越大的特征,那么我们可以认为它是比较有用的。如果方差较小,比如小于1,那么这个特征可能对我们的算法作用没有那么大。最极端的,如果某个特征方差为0,即所有的样本该特征的取值都是一样的,那么它对我们的模型训练没有任何作用,可以直接舍弃。在实际应用中,我们会指定一个方差的阈值,当方差小于这个阈值的特征会被我们筛掉。sklearn中的VarianceThreshold类可以很方便的完成这个工作。

特征选择方法有很多,一般分为三类:第一类过滤法比较简单,它按照特征的发散性或者相关性指标对各个特征进行评分,设定评分阈值或者待选择阈值的个数,选择合适特征。上面我们提到的方差筛选就是过滤法的一种。第二类是包装法,根据目标函数,通常是预测效果评分,每次选择部分特征,或者排除部分特征。第三类嵌入法则稍微复杂一点,它先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据权值系数从大到小来选择特征。类似于过滤法,但是它是通过机器学习训练来确定特征的优劣,而不是直接从特征的一些统计学指标来确定特征的优劣。下面我们分别来看看3类方法。

2.1 过滤法选择特征

上面我们已经讲到了使用特征方差来过滤选择特征的过程。除了特征的方差这第一种方法,还有其他一些统计学指标可以使用。

第二个可以使用的是相关系数。这个主要用于输出连续值的监督学习算法中。我们分别计算所有训练集中各个特征与输出值之间的相关系数,设定一个阈值,选择相关系数较大的部分特征。

第三个可以使用的是假设检验,比如卡方检验。卡方检验可以检验某个特征分布和输出值分布之间的相关性。个人觉得它比比粗暴的方差法好用。如果大家对卡方检验不熟悉,可以参看这篇卡方检验原理及应用,这里就不展开了。在sklearn中,可以使用chi2这个类来做卡方检验得到所有特征的卡方值与显著性水平P临界值,我们可以给定卡方值阈值, 选择卡方值较大的部分特征。

除了卡方检验,我们还可以使用F检验和t检验,它们都是使用假设检验的方法,只是使用的统计分布不是卡方分布,而是F分布和t分布而已。在sklearn中,有F检验的函数f_classif和f_regression,分别在分类和回归特征选择时使用。

第四个是互信息,即从信息熵的角度分析各个特征和输出值之间的关系评分。在决策树算法中我们讲到过互信息(信息增益)。互信息值越大,说明该特征和输出值之间的相关性越大,越需要保留。在sklearn中,可以使用mutual_info_classif(分类)和mutual_info_regression(回归)来计算各个输入特征和输出值之间的互信息。

以上就是过滤法的主要方法,个人经验是,在没有什么思路的 时候,可以优先使用卡方检验和互信息来做特征选择

2.2 包装法选择特征

包装法的解决思路没有过滤法这么直接,它会选择一个目标函数来一步步的筛选特征。

最常用的包装法是递归消除特征法(recursive feature elimination,以下简称RFE)。递归消除特征法使用一个机器学习模型来进行多轮训练,每轮训练后,消除若干权值系数的对应的特征,再基于新的特征集进行下一轮训练。在sklearn中,可以使用RFE函数来选择特征。

我们下面以经典的SVM-RFE算法来讨论这个特征选择的思路。这个算法以支持向量机来做RFE的机器学习模型选择特征。它在第一轮训练的时候,会选择所有的特征来训练,得到了分类的超平面$w \dot x+b=0$后,如果有n个特征,那么RFE-SVM会选择出$w$中分量的平方值$w_i^2$最小的那个序号i对应的特征,将其排除,在第二类的时候,特征数就剩下n-1个了,我们继续用这n-1个特征和输出值来训练SVM,同样的,去掉$w_i^2$最小的那个序号i对应的特征。以此类推,直到剩下的特征数满足我们的需求为止。

2.3 嵌入法选择特征

嵌入法也是用机器学习的方法来选择特征,但是它和RFE的区别是它不是通过不停的筛掉特征来进行训练,而是使用的都是特征全集。在sklearn中,使用SelectFromModel函数来选择特征。

最常用的是使用L1正则化和L2正则化来选择特征。在之前讲到的用scikit-learn和pandas学习Ridge回归第6节中,我们讲到正则化惩罚项越大,那么模型的系数就会越小。当正则化惩罚项大到一定的程度的时候,部分特征系数会变成0,当正则化惩罚项继续增大到一定程度时,所有的特征系数都会趋于0. 但是我们会发现一部分特征系数会更容易先变成0,这部分系数就是可以筛掉的。也就是说,我们选择特征系数较大的特征。常用的L1正则化和L2正则化来选择特征的基学习器是逻辑回归。

此外也可以使用决策树或者GBDT。那么是不是所有的机器学习方法都可以作为嵌入法的基学习器呢?也不是,一般来说,可以得到特征系数coef或者可以得到特征重要度(feature importances)的算法才可以做为嵌入法的基学习器。

3.  寻找高级特征

在我们拿到已有的特征后,我们还可以根据需要寻找到更多的高级特征。比如有车的路程特征和时间间隔特征,我们就可以得到车的平均速度这个二级特征。根据车的速度特征,我们就可以得到车的加速度这个三级特征,根据车的加速度特征,我们就可以得到车的加加速度这个四级特征。。。也就是说,高级特征可以一直寻找下去。

在Kaggle之类的算法竞赛中,高分团队主要使用的方法除了集成学习算法,剩下的主要就是在高级特征上面做文章。所以寻找高级特征是模型优化的必要步骤之一。当然,在第一次建立模型的时候,我们可以先不寻找高级特征,得到以后基准模型后,再寻找高级特征进行优化。

寻找高级特征最常用的方法有:

若干项特征加和: 我们假设你希望根据每日销售额得到一周销售额的特征。你可以将最近的7天的销售额相加得到。
若干项特征之差: 假设你已经拥有每周销售额以及每月销售额两项特征,可以求一周前一月内的销售额。
若干项特征乘积: 假设你有商品价格和商品销量的特征,那么就可以得到销售额的特征。
若干项特征除商: 假设你有每个用户的销售额和购买的商品件数,那么就是得到该用户平均每件商品的销售额。

当然,寻找高级特征的方法远不止于此,它需要你根据你的业务和模型需要而得,而不是随便的两两组合形成高级特征,这样容易导致特征爆炸,反而没有办法得到较好的模型。个人经验是,聚类的时候高级特征尽量少一点,分类回归的时候高级特征适度的多一点。

4. 特征选择小结

特征选择是特征工程的第一步,它关系到我们机器学习算法的上限。因此原则是尽量不错过一个可能有用的特征,但是也不滥用太多的特征。

不错的文章,原文地址:https://www.cnblogs.com/pinard/p/9032759.html

signal函数详解

signal作用是为信号注册一个处理器。这里的“信号”是软中断信号,这种信号来源主要有三种:

  1. 程序错误:比如除0,非法内存访问。
  2. 外部信号:终端Ctrl-C产生的SIGINT信号,定时器产生的SIGALERM。
  3. 显示请求:kill函数发送的任意信号。

当kill一个进程的时候,默认会发送SIGTERM信号,此时这个信号只有默认处理操作(SIG_DFL),直接中断进程执行。如果此时该进程正在执行一个任务,直接终止该进程会导致任务没有完成。这个时候为SIGTERM信号注册一个信号处理函数就十分有必要。

介绍

typedef void (*sighandler_t) (int);
sighandler_t signal (int sig, sighandler_t handler)

参数

sig要设置信号处理函数的信号。它可以是实现定义值或下例值之一:
SIGABRT
SIGFPE
SIGILL
SIGINT
SIGSEGV
SIGTERM
定义信号类型
(宏常量)
handler信号处理函数。这必须是下列之一:SIG_DFL 宏。信号处理函数被设为默认信号处理函数。SIG_IGN 宏。忽略信号。指向函数指针。函数签名必须等价于如下:extern “C” void fun(int sig);

返回值

成功时为先前的信号处理函数,失败时为 SIG_ERR (某些实现上能禁用设置信号处理函数)。

使用

使用方法请直接看下面的例子。注册一个信号用于在按下Ctrl-C时不立刻终止进程,而是输出一段文字并sleep一段时间。

#include <signal.h>
#include <unistd.h>
#include <iostream>

bool is_stop = false;
void quit_handler(int signo) {
    is_stop = true;
    std::cout << "stopping\n";
    sleep(2);
}

int main() {
    auto prev = signal(SIGINT, quit_handler);
    if (prev == SIG_ERR) {
        abort();
    }
    while (!is_stop) {
        std::cout << "running\n";
        sleep(1);
    }
    std::cout << "stopped\n";
}

如何安装多个处理函数

signal只能为一个信号添加一个处理函数,添加多个处理函数时后面的函数会覆盖前面的函数。看如下代码

#include <signal.h>
#include <unistd.h>
#include <iostream>

bool is_stop = false;
void quit_handler1(int signo) {
    is_stop = true;
    std::cout << "handler1\n";
    sleep(2);
}

void quit_handler2(int signo) {
    is_stop = true;
    std::cout << "handler2\n";
    sleep(2);
}

int main() {
    auto prev1 = signal(SIGINT, quit_handler1);
    auto prev2 = signal(SIGINT, quit_handler2);

    if (prev1 == SIG_ERR || prev2 == SIG_ERR) {
        abort();
    }
    while (!is_stop) {
        std::cout << "running\n";
        sleep(1);
    }
    std::cout << "stopped\n";
}

运行之后按下Ctrl-C,结果发现只有handler2有效,handler1失效了。

running
running
^Chandler2
stopped

有两个quit_handler,上面的代码最终执行的是quit_handler2。后面注册的函数覆盖了前面注册的。那么如何注册两个处理器呢?

由于signal会返回上一个处理操作,可能是SIG_DFL、SIG_IGN宏,也可能是上一个处理函数。这给我们注册多个处理器带来了可能。只要我们在第二次注册的处理函数中调用前一次的处理函数就可以了!看下面代码:

#include <unistd.h>
#include <signal.h>
#include <iostream>

bool is_stop = false;
void (*prev_handler)(int) = nullptr;

void quit_handler1(int signo) {
    is_stop = true;
    std::cout << "handler1\n";
    sleep(2);
}

void quit_handler2(int signo) {
    if (prev_handler != nullptr) {
        prev_handler(signo);
    }
    is_stop = true;
    std::cout << "handler2\n";
    sleep(2);
}

int main() {
    auto prev1 = signal(SIGINT, quit_handler1);
    auto prev2 = signal(SIGINT, quit_handler2);
    if (prev1 == SIG_ERR || prev2 == SIG_ERR) {
        abort();
    }
    if (prev2 != SIG_DFL && prev2 != SIG_IGN) {
        prev_handler = prev2;
    }
    while (!is_stop) {
        std::cout << "running\n";
        sleep(1);
    }
    std::cout << "stopped\n";
}

使用一个变量prev_handler保存前一次注册的handler,在quit_handler2中,调用prev_handler。运行之后会发现两个handler都会被调用,结果如下:

running
running
running
^Chandler1
handler2
stopped

信号列表

SignalDescription
SIGABRT由调用abort函数产生,进程非正常退出
SIGALRM用alarm函数设置的timer超时或setitimer函数设置的interval timer超时
SIGBUS某种特定的硬件异常,通常由内存访问引起
SIGCANCEL由Solaris Thread Library内部使用,通常不会使用
SIGCHLD进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略
SIGCONT当被stop的进程恢复运行的时候,自动发送
SIGEMT和实现相关的硬件异常
SIGFPE数学相关的异常,如被0除,浮点溢出,等等
SIGFREEZESolaris专用,Hiberate或者Suspended时候发送
SIGHUP发送给具有Terminal的Controlling Process,当terminal 被disconnect时候发送
SIGILL非法指令异常
SIGINFOBSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程
SIGINT由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程
SIGIO异步IO事件
SIGIOT实现相关的硬件异常,一般对应SIGABRT
SIGKILL无法处理和忽略。中止某个进程
SIGLWP由Solaris Thread Libray内部使用
SIGPIPE在reader中止之后写Pipe的时候发送
SIGPOLL当某个事件发送给Pollable Device的时候发送
SIGPROFSetitimer指定的Profiling Interval Timer所产生
SIGPWR和系统相关。和UPS相关。
SIGQUIT输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程
SIGSEGV非法内存访问
SIGSTKFLTLinux专用,数学协处理器的栈异常
SIGSTOP中止进程。无法处理和忽略。
SIGSYS非法系统调用
SIGTERM请求中止进程,kill命令缺省发送
SIGTHAWSolaris专用,从Suspend恢复时候发送
SIGTRAP实现相关的硬件异常。一般是调试异常
SIGTSTPSuspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程
SIGTTIN当Background Group的进程尝试读取Terminal的时候发送
SIGTTOU当Background Group的进程尝试写Terminal的时候发送
SIGURG当out-of-band data接收的时候可能发送
SIGUSR1用户自定义signal 1
SIGUSR2用户自定义signal 2
SIGVTALRMsetitimer函数设置的Virtual Interval Timer超时的时候
SIGWAITINGSolaris Thread Library内部实现专用
SIGWINCH当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程
SIGXCPU当CPU时间限制超时的时候
SIGXFSZ进程超过文件大小限制
SIGXRESSolaris专用,进程超过资源限制的时候发

关于typedef void (*sighandler_t) (int);

这里有个typdef比较奇怪,没见过这种写法。其实抛开typedef的自定义类型,只看函数,其实就和 int fun(int a,cha b)类似,这就很通俗易懂。

要想看懂上面的函数,就必须理解typedef的用法。

首先,我们看这个定义:typedef char *p ,这里,我们首先把typedef盖上,那么就是char *p。这句话想必大家都能看懂,也就是声明了一个p指针。p指针指向char类型的数据,加上typedef之后就解释成声明了一种指向char变量指针的类型p,也就是说,p=char *,p a=char *a。

然后我们来看 typedef void (*sighandler_t)(int),首先不看typedef,也就是void (*sighandler_t)(int),这就话的意思是声明了一个指向一个函数并且这个函数能接受一个整型参数并返回一个无类型指针变量sighandler_t,加上typedef之后,sighandler_t就变成了一个类型,sighandler_t p,p就是一个指向一个函数并且这个函数能接受一个整型参数并返回一个无类型指针的变量。

所以一句话就是,typedef在语句中所起的作用就是把语句原先定义变量的功能变成了定义类型的功能,仅此而已。

你可能还喜欢下面这些文章

websocket协议详解

gcc/g++编译参数详解

ftp命令大全详解

vsftpd配置文件详解

chkconfig给linux添加开机自启动服务,chkconfig命令详解

shell中 $? $# $* $$ $* $@ $0 特殊变量含义

shell中有一些常用的难记的特殊变量,如下:

$0当前脚本的文件名
$n传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。
$#传递给脚本或函数的参数个数。
$*传递给脚本或函数的所有参数。
$@传递给脚本或函数的所有参数。被双引号(” “)包含时,与 $* 稍有不同
$?上个命令的退出状态,或函数的返回值。
$$当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。
(更多…)

你可能还喜欢下面这些文章

linux shell 入门

Shell中的条件判断语句if的用法

并发任务分配问题

这是在工作中遇到的实际问题和解决过程。问题已经被抽象成并发任务的分配问题。

问题

如果有 n 组数据均分给 m 个处理器处理,那么每个处理器分到的数据是 \(\lceil \frac{n}{m} \rceil\) 。如果n组数据的类型有差异,其中有a组是一类数据,剩余 n-a 组是另一类数据。只有同类数据才能被一次性处理,那么该如何分配?

这个问题在现实中是存在的。比如HTTP并发请求处理一些数据。数据被批量送来,但类型不一样。为了节省耗时,我们希望并发处理这些不同的数据。并发数是确定好的。现在需要计算每个请求处理的数量,以便我们能给每一个请求打包数据。

求解

n 组数据交给 m 个处理器处理,每个处理器最多分到 \(\lceil \frac{n}{m} \rceil\) 组数据,这是毫无疑问的。如果 n 组数据中有a组是一类数据,n-a组是另一类数据。同类数据必须分配到同一个处理器。那么a类数据得到的处理器的数量是 \(\lceil \frac {a} {\lceil \frac{n}{m} \rceil} \rceil \),b类得到的处理器的数量是 \(\lceil \frac {n-a} {\lceil \frac{n}{m} \rceil} \rceil \)。我们现在其实需要考虑它们总共需要的处理器数量和m的关系。原有的m个处理器是否满足这种需求?如果不满足,需要多少个处理器才能满足?

即,求 \(( \lceil \frac {a}{\lceil \frac{n}{m} \rceil} \rceil +\lceil \frac {n-a} {\lceil \frac{n}{m} \rceil} \rceil ) \) 和 m的关系。

对于上面的问题,我们的存在一些已知的前提条件:

  1. m, m 为正整数
  2. \(n \leq m\)

根据上面已知的条件,我们可以得出一些引理:

  1. \(\lceil \frac {n}{m} \rceil \geq \frac {n}{m} \)
  2. \( \lceil \frac {n}{m} \rceil \leq \frac {n}{m} + \frac {m-1}{m} \)

因此,容易得出\(( \lceil \frac {a}{\lceil \frac{n}{m} \rceil} \rceil +\lceil \frac {n-a} {\lceil \frac{n}{m} \rceil} \rceil ) \geq \lceil \frac {n}{\lceil \frac {n}{m} \rceil} \rceil \geq m \)

即数据类型分成两种的时候所需要的处理器数量是大于等于m的,原先的处理器个数可能不够用了。那么多少才够用?这是现在需要考虑的问题。

容易得出,\( ( \lceil \frac {a}{\lceil \frac{n}{m} \rceil} \rceil +\lceil \frac {n-a} {\lceil \frac{n}{m} \rceil} \rceil ) \leq \lceil \frac {am}{n}\rceil + \lceil \frac {(n-a)m} {n} \rceil \)

根据上面的引理可以得出 \( \lceil \frac {am}{n}\rceil + \lceil \frac {(n-a)m} {n} \rceil \leq \frac {am}{n} + \frac {n-1}{n} + \frac {(n-a)m} {n} + \frac {n-1}{n} = n+2-\frac{2}{n} \)

由已知条件可以知道,\( ( \lceil \frac {a}{\lceil \frac{n}{m} \rceil} \rceil +\lceil \frac {n-a} {\lceil \frac{n}{m} \rceil} \rceil )\) 是正整数,因此可以将 \( n+2-\frac{2}{n} \)向下取整为\(n+1\)。

即需要n+1个处理器才能满足要求。

因此遇到这种问题的时候,要么增加一个处理器,要么计算每个处理器能处理的数量的时候在原先处理器数量减一的基础上计算。

为什么C++模板不支持分离式编译

前言

最近编译C++代码时出现链接失败信息,类似下图:

图一

初见这个错误有些令人费解,不过经过一番分析,发现原因还是清晰的,和大家一起分享一下。

图一中使用的tpl.h代码如下:

#pragma once

template <typename T>
int compare(const T& a, const T& b);

tpl.cpp代码如下:

#include "tpl.h"
#include <iostream>

template <typename T>
int compare(const T& a, const T& b) {
    if (a == b) {
        return 0;
    }
    return (a > b) ? 1 : -1;
}

main.cpp代码如下:

#include <iostream>
#include "tpl.h"

int main() {
    int res = compare<int> (1, 2);
    std::cout << res << std::endl;
}

我一直习惯把模板实现写在头文件中,因此从未遇见这种错误。这次偶然将模板声明和实现分离,出现了链接错误。这引发了我一些思考,为什么模板不支持分离式编译?

模板的编译

为了搞清楚模板是怎么编译的,这里以上述tpl.cpp中的compare模板函数为例。tpl.h中声明了一个模板函数并且在tpl.cpp中实现这个模板函数。现在我们编译tpl.cpp,生成的汇编代码如下:

图二

可以看出,一个模板如果没有被调用的时候,编译器不会对这部分代码做任何处理,一行指令都没有。实际上编译器也不知道要怎么处理这个模板。在没有调用之前,模板的参数类型是不确定的。

假如我们在tpl.cpp中增加调用compare的函数的test函数,如下:

int test() {
    int res = compare<int> (1, 2);
    std::cout << res << std::endl;
    return res;
}

再编译tpl.cpp就会发现,生成的汇编文件里面已经有了以int为参数的compare函数了,如下图:

图三

链接错误问题分析

那么图一中的链接错误是怎么发生的?我们执行了g++ tpl.cpp main.cpp这个命令,大致会经过下面这三个步骤:

  1. 编译并且汇编tpl.cpp,生成tpl.o目标文件。
  2. 编译并且汇编main.cpp,生成main.o目标文件。
  3. 链接tpl.o和main.o生成可执行文件。

编译tpl.cpp已经分析过了,由于没有调用模板函数,因此编译器不会对模板做任何处理。实际上编译器根本不知道要怎么处理,因为模板里面的类型是不确定的。只有在调用的时候才会确定下来,这个时候编译器才知道这个函数长什么样,才会执行编译操作。

编译main.cpp的时候,由于模板compare已经声明但未实现,因此这里也不会生成具体代码,只会生成一个call指令。很显然这个call指令中的函数地址现在肯定是错误的,头文件tpl.h并没有去实现这个函数,需要依赖链接器将这个地址修改成正确的地址。

问题就出在编译tpl.cpp的时候模板没有实例化,编译器并没有编译这个函数,因此找在符号表中找不到这个函数的地址,链接器不知道要怎么处理,因此会出现链接错误。

如何改正

比较好的方式是在头文件中定义模板而不仅仅只是声明。

比如上面的tpl.h代码改成这样

#pragma once

template <typename T>
int compare(const T& a, const T& b) {
    if (a == b) {
        return 0;
    }
    return (a > b) ? 1 : -1;
}

就不会有链接问题了。

std::endl为什么导致程序变慢

最近在写hadoop的streaming任务,在输出的时候用了std::endl,就像下面这样:

os << "content" << std::endl

运行后发现程序跑的比python还慢,令人费解。我入门C++的时候,输出hello world也是这样写的,有什么问题?

于是查了一下std::endl,发现问题挺大。std::endl解释如下:

Inserts a new-line character and flushes the stream.
Its behavior is equivalent to calling os.put('\n') (or os.put(os.widen('\n')) for character types other than char), and then os.flush().

也就是说每次执行到std::endl的时候都会将缓冲区的内容写入到输出的对象中,这样一来速度慢也就不足为奇。

性能测试

#include "timer.h"
#include <fstream>
#include <iostream>

int main() {
    {
        Timer timer;
        fstream fs("./with_endl.txt", std::fstream::out);
        for (int i = 0; i<100000; i++) {
            fs << "test" << std::endl;
        }
        std::cout << "with endl:" << timer.elapsed() << "ms \n";
    }

    {
        Timer timer;
        fstream fs("./without_endl.txt", std::fstream::out);
        for (int i = 0; i<100000; i++) {
            fs << "test" << "\n";
        }
        std::cout << "without endl:" <<timer.elapsed() << "ms \n";
    }

}
with endl:397ms 
without endl:18ms 

不加std::endl性能高出20倍。如果程序的逻辑十分简单,那么输出字符串的时候最好用”\n”代替加std::endl

你可能还喜欢下面这些文章

Shell中的条件判断语句if的用法

C++入门:三、函数

c语言的位操作

gcc/g++编译参数详解

编译步骤

gcc 与 g++ 分别是 gnu 的 c & c++ 编译器。gcc/g++ 在执行编译工作的时候,总共需要4步:

  1. 预处理,生成 .i 的文件[预处理器cpp]
  2. 将预处理后的文件转换成汇编语言, 生成文件 .s [编译器egcs]
  3. 有汇编变为目标代码(机器代码)生成 .o 的文件[汇编器as]
  4. 连接目标代码, 生成可执行程序 [链接器ld]

参数详解

-x language filename

参数含义为指定文件所使用的语言。根据约定,C语言的后缀名称为”.c”,而 C++ 的后缀名为”.cpp”或”.cc”,但如果你的源代码后缀不约定的那几种,那么需要使用-x参数来指定文件所使用的语言。这个参数对他后面的文件名都起作用。 可以使用的参数吗有下面的这些:c、objective-c、c-header、c++、cpp-output、assembler、assembler-with-cpp。

例子:

gcc -x c hello.pig 

-x none filename

关掉上一个选项,也就是让gcc根据文件名后缀,自动识别文件类型 。

例子

gcc -x c hello.pig -x none hello2.c 

-c

只对源代码做预处理、编译、汇编工作,生成的文件为obj文件。

例子

gcc -c hello.c 

他将生成 .o 的 obj 文件

-S

只对源代码做预处理和编译,生成的文件为汇编代码。

例子

gcc -S hello.c 

它将生成 .s 的汇编代码。

-E

只对源代码做预处理,不生成文件,你需要把它重定向到一个输出文件里面。

例子

gcc -E hello.c > out.txt 
gcc -E hello.c | more

-o

设置目标文件的名称,默认情况下,gcc编译之后的文件名为a.out,通过-o参数可以指定编译后的输出文件名。

例子

gcc -o hello hello.c
gcc -o hello.asm -S hello.c

-pipe

使用管道代替编译中临时文件, 在使用非 gnu 汇编工具的时候, 可能有些问题。

gcc -pipe -o hello.exe hello.c 

-ansi

关闭 gnu c中与 ansi c 不兼容的特性, 激活 ansi c 的专有特性(包括禁止一些 asm inline typeof 关键字, 以及 UNIX、vax 等预处理宏)。

-fno-asm

此选项实现 ansi 选项的功能的一部分,它禁止将 asm, inline 和 typeof 用作关键字。

-fno-strict-prototype

只对 g++ 起作用。使用这个选项,g++ 会认为不带参数的函数为没有显式的对参数的个数和类型说明,而不是没有参数。

而 gcc 无论是否使用这个参数, 都将对没有带参数的函数认为没有显式说明的类型。

-fthis-is-varialble

就是向传统 c++ 看齐, 可以使用 this 当一般变量使用。

-fcond-mismatch

允许条件表达式的第二和第三参数类型不匹配, 表达式的值将为 void 类型。

-funsigned-char 、-fno-signed-char、-fsigned-char 、-fno-unsigned-char

这四个参数是对 char 类型进行设置, 决定将 char 类型设置成 unsigned char(前两个参数)或者 signed char(后两个参数)。

-include file

包含某个代码,简单来说,就是便以某个文件,需要另一个文件的时候,就可以用它设定,功能就相当于在代码中使用 #include<filename>

例子用法:

gcc hello.c -include /root/pianopan.h 

-imacros file

将 file 文件的宏, 扩展到 gcc/g++ 的输入文件, 宏定义本身并不出现在输入文件中。

-Dmacro

相当于 C 语言中的 #define macro

-Dmacro=defn

相当于 C 语言中的 #define macro=defn

-Umacro

相当于 C 语言中的 #undef macro

-undef

取消对任何非标准宏的定义

-Idir

在你是用 #include “file” 的时候, gcc/g++ 会先在当前目录查找你所制定的头文件, 如果没有找到, 他回到默认的头文件目录找, 如果使用 -I 制定了目录,他会先在你所制定的目录查找, 然后再按常规的顺序去找。

对于 #include<file>, gcc/g++ 会到 -I 制定的目录查找, 查找不到, 然后将到系统的默认的头文件目录查找 。

-I-

就是取消前一个参数的功能, 所以一般在 -Idir 之后使用。

-idirafter dir

在 -I 的目录里面查找失败, 讲到这个目录里面查找。

-iprefix prefix 、-iwithprefix dir

一般一起使用, 当 -I 的目录查找失败, 会到 prefix+dir 下查找

-nostdinc

使编译器不再系统默认的头文件目录里面找头文件, 一般和 -I 联合使用,明确限定头文件的位置。

-nostdin C++

规定不在 g++ 指定的标准路经中搜索, 但仍在其他路径中搜索, 此选项在创 libg++ 库使用 。

-C

在预处理的时候, 不删除注释信息, 一般和-E使用, 有时候分析程序,用这个很方便的。

-M

生成文件关联的信息。包含目标文件所依赖的所有源代码你可以用 gcc -M hello.c 来测试一下,很简单。

-MM

和上面的那个一样,但是它将忽略由 #include<file> 造成的依赖关系。   

-MD

和-M相同,但是输出将导入到.d的文件里面   

-MMD

和 -MM 相同,但是输出将导入到 .d 的文件里面。

-Wa,option

此选项传递 option 给汇编程序; 如果 option 中间有逗号, 就将 option 分成多个选项, 然 后传递给会汇编程序。

-Wl.option

此选项传递 option 给连接程序; 如果 option 中间有逗号, 就将 option 分成多个选项, 然 后传递给会连接程序。

-llibrary

制定编译的时候使用的库

例子

gcc -lcurses hello.c

使用 ncurses 库编译程序

-Ldir

制定编译的时候,搜索库的路径。比如你自己的库,可以用它制定目录,不然编译器将只在标准库的目录找。这个dir就是目录的名称。

-O0 、-O1 、-O2 、-O3

编译器的优化选项的 4 个级别,-O0 表示没有优化, -O1 为默认值,-O3 优化级别最高。

-g

只是编译器,在编译的时候,产生调试信息。

-gstabs

此选项以 stabs 格式声称调试信息, 但是不包括 gdb 调试信息。

-gstabs+

此选项以 stabs 格式声称调试信息, 并且包含仅供 gdb 使用的额外调试信息。

-ggdb

此选项将尽可能的生成 gdb 的可以使用的调试信息。

-static

此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么动态连接库,就可以运行。

-share

此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库。

-traditional

试图让编译器支持传统的C语言特性。

GCC 是 GNU 的 C 和 C++ 编译器。实际上,GCC 能够编译三种语言:C、C++ 和 Object C(C 语言的一种面向对象扩展)。利用 gcc 命令可同时编译并连接 C 和 C++ 源程序。

如果你有两个或少数几个 C 源文件,也可以方便地利用 GCC 编译、连接并生成可执行文件。例如,假设你有两个源文件 main.c 和 factorial.c 两个源文件,现在要编 译生成一个计算阶乘的程序。

factorial.c 文件代码

int factorial (int n) {
    if (n <= 1)
        return 1;
    else
        return factorial (n - 1) * n;
}

main.c 文件代码

#include <stdio.h> 
#include <unistd.h> 
int factorial (int n); 
int main (int argc, char **argv) 
{ 
  int n; 
  if (argc < 2) 
  { 
    printf ("Usage: %s n\n", argv [0]); 
    return -1; 
  } 
  else 
  { 
   n = atoi (argv[1]); 
   printf ("Factorial of %d is %d.\n", n, factorial (n)); 
   } 
  return 0; 
}

利用如下的命令可编译生成可执行文件,并执行程序:

$ gcc -o factorial main.c factorial.c 
$ ./factorial 5 
Factorial of 5 is 120. 

GCC 可同时用来编译 C 程序和 C++ 程序。一般来说,C 编译器通过源文件的后缀名来判断是 C 程序还是 C++ 程序。在 Linux 中,C 源文件的后缀名为 .c,而 C++ 源文件的后缀名为 .C 或 .cpp。但是,gcc 命令只能编译 C++ 源文件,而不能自动和 C++ 程序使用的库连接。因此,通常使用 g++ 命令来完成 C++ 程序的编译和连接,该程序会自动调用 gcc 实现编译。假设我们有一个如下的 C++ 源文件(hello.c):

hello.c 文件代码

#include <iostream> 
void main (void) 
{ 
  cout << "Hello, world!" << endl; 
}

则可以如下调用 g++ 命令编译、连接并生成可执行文件:

$ g++ -o hello hello.c 
$ ./hello 
Hello, world! 

gcc 命令的常用选项

选项解释
-ansi只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色, 例如 asm 或 typeof 关键词。
-c只编译并生成目标文件。
-DMACRO以字符串”1″定义 MACRO 宏。
-DMACRO=DEFN以字符串”DEFN”定义 MACRO 宏。
-E只运行 C 预编译器。
-g生成调试信息。GNU 调试器可利用该信息。
-IDIRECTORY指定额外的头文件搜索路径DIRECTORY。
-LDIRECTORY指定额外的函数库搜索路径DIRECTORY。
-lLIBRARY连接时搜索指定的函数库LIBRARY。
-m486针对 486 进行代码优化。
-o FILE生成指定的输出文件。用在生成可执行文件时。
-O0不进行优化处理。
-O 或 -O1优化生成代码。
-O2进一步优化。
-O3比 -O2 更进一步优化,包括 inline 函数。
-shared生成共享目标文件。通常用在建立共享库时。
-static禁止使用共享连接。
-UMACRO取消对 MACRO 宏的定义。
-w不生成任何警告信息。
-Wall生成所有警告信息。

你可能还喜欢下面这些文章

websocket协议详解

signal函数详解

ftp命令大全详解

vsftpd配置文件详解

chkconfig给linux添加开机自启动服务,chkconfig命令详解