• C++11新特性:基于范围的for循环

    在C++11中,介绍了一种新的for循环写法,基于范围的for循环。
    与旧的for循环类似,也是用来对遍历容器中的所有元素,新的写法更加简洁方便。可以使用auto自动适配容器中的内容。为了遍历容器时提高效率,在访问容器内部时可以声明为元素的引用,进而避免不必要的对象拷贝。示例代码如下(代码节选自 cppreferance

    std::vector<int> v = {0, 1, 2, 3, 4, 5};
    for (const int& i : v) // access by const reference
    {
        std::cout << i << ' ';
    }
    std::cout << '\n';
    
    for (auto i : v) // access by value, the type of i is int
    {
        std::cout << i << ' ';
    }
    std::cout << '\n';
    
    for (int n : {0, 1, 2, 3, 4, 5}) // the initializer may be a braced-init-list
    {
        std::cout << n << ' ';
    }
    std::cout << '\n';
    
    int a[] = {0, 1, 2, 3, 4, 5};
    for (int n : a) // the initializer may be an array
    {
        std::cout << n << ' ';
    }
    std::cout << '\n';
    
    for (int n : a)
    {
        std::cout << 1 << ' '; // the loop variable need not be used
    }
    std::cout << '\n';
    

    注意 std::map 的范围for循环遍历的写法
    std::map 中存放的是 key-value pair 键值对,所以在遍历的时候每次拿到的是一个 pair ,在访问其中的元素时,需要注意书写方法。示例代码如下。

    std::map<int, std::string> mapDemo;
    for (auto& kvp : mapDemo)
    {
        std::cout << kvp.first;
        std::cout << " - ";
        std::cout << kvp.second;
        std::cout << std::endl;
    }
    
  • Linux常用命令行指令 - scp

    scp 命令代表的是 secure copy,与 cp 命令的本地拷贝十分相似,主要区别就在于 scp 的路径可以是一个远程机器的路径。使用 scp 进行文件拷贝时,文件的传输时加密的。

    cp source_path dest_path
    

    从远程机器向本地拷贝

    以下命令会把 host 主机上的 src_path 文件拷贝到本地 dest_path 目录中。

    scp [email protected]:src_path dest_path
    

    从本地向远程机器拷贝

    以下命令会把本地 src_path 的文件拷贝打 host 主机上的 dest_path 路径上。

    scp src_path [email protected]:dest_path
    

    拷贝目录下的所有文件

    可以使用 -r 参数完成目录的递归拷贝, r 代表 recursive 递归。

    scp -r [email protected]:/src_path dest_path
    

    在拷贝时限制带宽

    可以使用 -l 参数限制传输的带宽, l 代表 limit 限制,限制带宽的单位是 kbps
    命令会在将拷贝传输的速度限制为 100kbps。

    scp -l 100 src_path dest_path
    
  • C++11新特性:自动类型推导

    关键字 auto 用于自动类型推导

    auto x=0;     // int
    auto c='a';   // char
    auto d=0.5;   // double
    

    自动类型推导主要用于声明比较复杂的变量,或者变量是在泛型编程(模板)中自动生成出来的。

    vector<int> vecNumbers;
    vector<int>::const_iterator it = vecNumbers.begin();
    auto it = vecNumbers.begin();
    

    auto 类型推导并不会指定 constvolatile 属性

    如果需要限定声明的变量为 constvolatile 属性,需要额外提供相应的关键字限定。因为编译器并不能推断出声明的变量是否具有常值属性和易失属性。

    volatile auto num=5;           // volatile int
    auto volatile const var=true;  //const valatile bool
    auto const ch='a';             // const char
    

    关键字 decltype 用于获取一个对量的类型

    使用 decltype 存储一个对象或表达式的类型, decltype 可以看做是 auto 的补充。

    vector<int> vecNumbers;
    typedef decltype(vecNumbers.begin()) Iterator;
    Iterator it2;
    

    函数返回值类型推导

    在C++11中可以使用 autodecltype 完成函数返回类型的推导。
    旧的函数声明格式 return_type func_name(param_type param, ...);
    新的函数声明格式示例如下,以下函数接收 bool 作为函数的参数,并返回 bool 类型的结果。

    auto func_name(bool bParam)->decltype(bParam)
    {
      return bParam;
    }
    

    函数返回值类型推导在模板泛型编程中非常有用,模板函数能够定义一个通用的返回类型,返回值类型会根据 decltype 自动推导。
    新的函数声明格式更易于维护,因为返回值并没有作为硬编码写入源文件中,但这样做可能会牺牲代码的可读性。

    template <class T>
    auto get_end(vector<T>& v) -> decltype(v.end())
    {
      return v.end();
    }
    
    vector<int> vecNumbers;
    auto it = get_end(vecNumbers);      // returns iterator
    
    const vector<int> vecNumbers2;
    auto it2 = get_enm(vecNumbers2);    // returns const_iterator
    
  • Linux下的FIFO、pipe、unix domain socket漫谈

    在做Linux开发时,经常会接触 管道AF_UNIX 等相关词汇,为了弄清他们之间的关系,查阅了一些资料,将结果整理并记录下来。

  • Linux下使用gdb的调试技巧(2)

    本文重点介绍在gdb模式下的常用命令。

  • 使用命令行修改Linux系统的时区

    系统时区目录 系统支持时区的配置文件存放在 /usr/share/zoneinfo/ 目录下,可以进入到该目录下查看和搜索目标时区的相关配置文件。

    时区配置文件 Linux系统的当前时区配置文件存放在 /etc/localtime , 可以将时区配置文件拷贝到该位置下,也可以建立一个软连接指向系统时区目录中的配置文件。

    操作示例 以将系统的时区改为中国时间为例,以下操作先将旧的时区配置文件删掉,然后建立新的时区配置文件的符号链接。

    rm /etc/localtime
    ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
    

    完成以上操作后,可以执行 date 命令查看修改效果。

  • Linux常用命令行指令 - netstat

    常见的选项有:
    -a (all)显示所有选项,netstat默认不显示LISTEN相关
    -t (tcp)仅显示tcp相关选项
    -u (udp)仅显示udp相关选项
    -n 拒绝显示别名,能显示数字的全部转化成数字。(重要)
    -l 仅列出有在 Listen (监听) 的服务状态
    -p 显示建立相关链接的程序名(macOS中表示协议 -p protocol)
    -r 显示路由信息,路由表
    -e 显示扩展信息,例如uid等
    -s 按各个协议进行统计 (重要)
    -c 每隔一个固定时间,执行该netstat命令。

  • Linux常用命令行指令 - tcpdump

    tcpdump常用于网络抓包,能够将抓取的包保存下来,这样就能进一步深入分析。抓包保存的文件可以使用tcpdump进行查看,也可以借助第三方软件(如wireshark)进行辅助分析。

    1. 抓取指定网络设备上的包

    抓取网卡eth0上的包

    tcpdump -i eth0
    

    2. 限制抓包的数量

    只抓取5个包

    tcpdump -c 5 -i eth0
    

    3. 将抓取的包以ASCII的方式打印出来

    tcpdump -A -i eth0
    

    4. 将抓取的包以十六进制的方式打印出来

    tcpdump -XX -i eth0
    

    5. 对抓取到的包上打出更方便阅读的时间戳

    增加 -t 参数能够将抓取到的包中的时间以方便阅读的形式打印出来,使用 -tttt 参数能够将包中的时间戳以更详细的方式打印出来。

    tcpdump -t -i eth0
    tcpdump -tttt -i eth0
    

    6. 将抓取的包写入到文件中

    tcpdump -w netdump.pcap -i eth0
    

    7. 读取抓到的包

    tcpdump -r netdump.pcap
    

    8. 在抓到的包中显示ip地址

    增加 -n 参数不会解析主机名,增加 -nn 参数不会解析主机名和端口名

    tcpdump -n -i eth0
    tcpdump -nn -i eth0
    

    9. 以绝对值显示包的ISN号

    tcpdump -S -i eth0
    

    10. 对抓取到的包显示更详细的信息

    参数 -v 是verbose的缩写,以下命令表示对抓取到的包显示更详细的信息。

    tcpdump -v -i eth0
    tcpdump -vv -i eth0
    tcpdump -vvv -i eth0
    

    11. 对抓到的包进行协议类型过滤

    支持的协议类型有:fddi, tr, wlan, ip, ip6, arp, rarp, decnet, tcp, udp
    以下命令只会抓取网卡eth0上的arp包

    tcpdump -i eth0 arp
    

    12. 在抓包时对包进行大小过滤

    下面的命令会分别抓取大于1024字节和小于1024字节的包

    tcpdump -i eth0 greater 1024
    tcpdump -i eth0 less 1024
    

    13. 抓取指定端口上的包

    tcpdump -i eth0 port 22
    

    14. 抓取发往指定目的和端口上包

    可以使用 andor 对过滤条件进行组合以实现精确的抓包。

    tcpdump -w net_dump.pcap -i eth0 dst 10.181.140.216 and port 22
    

    常用tcpdump命令示例

    tcpdump –i any 'port 8888'
    tcpdump –i any 'tcp port 8888'
    tcpdump –i any 'tcp src port 8888'
    tcpdump –i any 'tcp src port 8888 and udp dst port 9999'
    tcpdump -i any 'src host 127.0.0.1 and tcp src port 12345' -XX -nn -vv
    
  • Linux常用命令行指令 - nc

    nc 命令是 netcat 的缩写,能够使用TCP或UDP协议读写网络连接中的数据,是一个方便可靠的调试工具。

  • Linux常用命令行指令 - lsof

    lsof是list open files的简称,用于列出系统中所有打开的文件。由于在linux系统下,所有 对象都是文件,所以这个指令十分有用。

    下面就给出了几种常见的用法,需要注意的是有些指令需要root权限执行。

    1. 查看打开指定文件的进程

    lsof /var/syslog.log
    

    2. 查看指定目录下打开的文件

    lsof +D /home/
    

    3. 查看以指定进程名所有打开的文件

    使用 -c 命令能够查看指定进程名使用的文件,允许在一个命令行指令中使用多个 -c 参数。

    lsof -c procname
    

    4. 查看使用指定挂载点的进程

    在停止挂载某个目录时,系统会告诉我们“设备资源繁忙”,所以我们需要找出使用挂载点的程序,终止这些进程,解除目录的占用状态。

    lsof /home
    


    lsof +D /home/
    

    5. 查看指定用户打开的文件

    lsof -u username
    

    另外还可以进行反向过滤,在输出中不显示指定用户的文件,语法如下。

    lsof -u ^username
    

    6. 查看指定进程打开的文件

    lsof -p pid
    

    7. 终止属于某个用户的所有进程

    kill `lsof -t -u username`
    

    相似的,可以使用 -t 参数打印使用某个文件的进程id。

    lsof -t /var/log
    

    8. 让结果同时满足多个选项

    默认情况下,多个参数是以或(or)进行操作的,可以使用 -a 选项让结果同时满足多个参数。

    lsof -a -u username -c procname
    

    9. 让命令重复执行

    可以使用 -r 参数让命令重复执行。以下命令会每隔5s执行一次,每次的执行结果会用 “====” 分割开。

    lsof -u username -c procname -a -r5
    

    10. 列出所有的网络连接

    lsof -i
    

    让结果中的ip地址和端口号显示数字而不是英文别名

    lsof -i -Pn
    

    显示所有的 AF_UNIX socket

    lsof -U
    

    11. 列出指定进程使用的网络连接

    lsof -i -a -p pid
    

    12. 查看指定端口上的监听

    lsof -i :portid
    

    13. 列出所有tcp和udp连接

    lsof -i tcp; lsof -i udp
    

    14. 列出所有的网络文件系统

    网络文件系统(network file system)

    lsof -N -u username
    
  • 重载、覆盖和隐藏的区别

    重载(overload) :就是函数或者方法有相同的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。
    多个重载函数在调用的时候根据函数的参数来区别不同的函数。
    关键点:函数名相同,参数表不同

    覆盖(override) :是指在派生类中重新对基类中的虚函数重新实现,即函数名和参数都一样,只是函数的实现体不一样。
    关键词:派生类中,虚函数,函数名和参数表完全相同

    隐藏(hide) :派生类中的函数把基类中相同名字的函数屏蔽掉了。
    隐藏一词可以这么理解:在调用一个类的成员函数的时候,编译器会沿着类的继承链逐级的向上查找函数的定义,如果找到了那么就停止查找了,所以如果一个派生类和一个基类都有同一个同名(暂且不论参数是否相同)的函数,而编译器最终选择了在派生类中的函数,那么我们就说这个派生类的成员函数"隐藏"了基类的成员函数,也就是说它阻止了编译器继续向上查找函数的定义.

    关于这三种情况的示例代码如下

    #include <iostream.h>
    class Base
    {
    public:
      virtual void f(float x)
      {
        cout << "Base::f(float) " << x << endl;
      }
    
      void g(float x)
      {
        cout << "Base::g(float) " << x << endl;
      }
    
      void h(float x)
      {
        cout << "Base::h(float) " << x << endl;
      }
    };
    
    class Derived : public Base
    {
    public:
      virtual void f(float x)
      {
        cout << "Derived::f(float) " << x << endl;
      }
    
      void g(int x)
      {
        cout << "Derived::g(int) " << x << endl;
      }
    
      void h(float x)
      {
        cout << "Derived::h(float) " << x << endl;
      }
    };
    

    从以上代码能够看出:
    (1)函数Derived::f(float)覆盖了Base::f(float)。
    (2)函数Derived::g(int)隐藏了Base::g(float),而不是重载。
    (3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。

    考虑代码在运行时的结果:

    void main(void)
    {
      Derived  d;
      Base *pb = &d;
      Derived *pd = &d;
    
      // Good : behavior depends solely on type of the object
      pb->f(3.14f); // Derived::f(float) 3.14
      pd->f(3.14f); // Derived::f(float) 3.14
    
      // Bad : behavior depends on type of the pointer
      pb->g(3.14f); // Base::g(float) 3.14
      pd->g(3.14f); // Derived::g(int) 3        (surprise!)
    
      // Bad : behavior depends on type of the pointer
      pb->h(3.14f); // Base::h(float) 3.14      (surprise!)
      pd->h(3.14f); // Derived::h(float) 3.14
    }
    

    在第一种调用中,函数的行为取决于指针所指向的对象。在第二第三种调用中,函数的行为取决于指针的类型。所以说,隐藏破坏了面向对象编程中多态这一特性,会使得开发人员产生混乱。

    函数接口设计注意要点
    在设计基类的函数接口时,不要出现既是虚函数,又对该虚函数进行了重载的情况。这样会对派生类的多态函数实现造成不便。
    示例代码如下

    class Base
    {
      virtual foo();
      virtual foo(int n);
    }
    
    class Derived : public Base
    {
      virtual foo();
    }
    

    从以上代码能够看出:

    1. Derived::foo()Base::foo() 构成了多态关系
    2. Derived::foo() 隐藏了 Base::foo(int)

    所以,如果派生类希望实现 foo() 函数的多态,也就必须实现 foo(int) 的多态,否则就会出现隐藏。
    如果在现有代码中已经发生了上述 Base 的情况,在不改变原有代码的情况下,可以使用在派生类中使用这样的语法 using Base::foo; ,这会将基类中所有的名为 foo 的函数引入到派生类中来。

  • 关于Qt中使用中文编码的一些问题

    Qt库作为一个开源库,并且支持多语言。在开发时需要考虑字符编码问题。
    我现在的使用Qt开发环境为VS2012+Qt5.1.1,所以一下所讨论的也是基于这个版本而言的。目前Qt5已经将 tr() 删掉了。

    在windows下使用Qt库进行开发
    有两种主流开发环境:

    1. 使用Qt Creator作为开发环境
    2. 使用Visual Studio + Visual Assist + Qt Visual Studio Addon作为开发环境

    目前我使用的就是第二种开发环境,鉴于在Windows平台下没有哪个开发环境能与Visual Studio相媲美。

    使用VS开发Qt程序时需要注意源文件的编码格式
    Qt5官方推荐的源文件编码格式为UTF-8,QString内部的编码格式就是UTF-8,使用QtCreator创建的源文件的编码格式也是UTF-8 without BOM。
    但是VS在中文操作系统上,如果不进行特殊的设置,默认创建的含有中文的源文件编码格式为gb3212。这样的原始字符串就是gb2312格式的,在使用时需要进行特殊转换,这样显示在界面上才不会乱码。
    推荐使用一下两种方法解决gb2312编码问题:

    • QString::fromLocale8Bit() 会将gb2312编码的字符串转换为UTF-8格式以便存放在QString中。
    • QStringLiteral() 是一个宏定义,会在编译期将字符串实例化,对静态字符串使用这个字符串也是一个提高效率的方法(因为减少了运行时的内存申请开销)。

    在VS中使用utf-8 with BOM的源文件格式
    如果使用了utf-8 with BOM的源文件格式,VS会将其中的字符串转换为gb2312编码的中文,这样是为了兼容旧版本的编译器。
    可以使用编译选项让VS编译器不进行这项转换,这样就可以直接使用字符串 char* 初始化QString了。

    • 微软在VS2010中提供了 #pragma execution_character_set("utf-8") 这样的编译选项,能够防止编译器进行文件编码转换,保证字符串保留utf-8格式。
    • VS2012 并不支持以上编译选项,VS2012将这个特性取消了
    • VS2015 可以使用 /utf-8 让编译器能够识别 utf-8 without BOM格式的文件,并保留utf-8的字符串编码。

    C++11的编码格式支持
    对于支持C++11标准的编译器,可以采用如下的写法产生utf-8格式的字符串。

    const char* szMsg = u8"字符串";