标签 c/c++ 下的文章

在写代码的过程中,CLion提醒我把push_back方法替换成emplace_back方法:

代码中我的想法是使用vector创建一个二维数组,并提前分配好空间,避免后序频繁扩容增加时间复杂度。

emplace_back函数的作用是减少对象拷贝和构造次数,是C++11中的新特性,主要适用于对临时对象的赋值。

在使用push_back函数往容器中增加新元素时,必须要有一个该对象的实例才行,而emplace_back可以不用,它可以直接传入对象的构造函数参数直接进行构造,减少一次拷贝和赋值操作。

例如以下学生类:

class stu_info {
private:
    string name;
public:
    stu_info(const string &name) {
        this->name = name;
        cout << "stu_info(): " << this->name << endl;
    }

    ~stu_info() {
        cout << "~stu_info(): " << this->name << endl;
    }

    stu_info(const stu_info &s) {
        this->name = s.name;
        cout << "copy constructor: " << this->name << endl;
    }
};

使用push_back插入元素的办法:

vector<stu_info> v;
v.push_back(stu_info("nginx"));

在push_back之前,必须使用stu_info实例一个临时对象传入才行,实例对象就必须要执行构造函数,然后拷贝到容器中再执行一次拷贝构造函数。

而emplace_back可以不用执行多余的拷贝构造函数了,它是直接在容器内执行对象的构造:

vector<stu_info> v;
v.emplace_back("redis");

两个函数的执行结果:

我以前确实是还不知道这个函数,要不是编译器提醒我,以后我可能也不会知道。不得不说IDE在某些方面也能帮助我们提高眼界

问题背景:在刷题的过程中,要使用min函数,但是线上OJ并没有这个函数。因为一时也想不起它到底属于哪个头文件,所以为了偷懒,顺手就写下了以下宏定义:

#define min(x, y) (x) < (y) ? (x) : (y)

正常情况下这个宏定义是没有问题的,代码提交错误我也从没怀疑过它有问题。因为我认为自己对宏定义已经十分了解了,它的坑我基本都遇到过,该写的括号都写了,只是没有加do...while(0)而已,应该不会有问题。

直到我提交失败了n次后,当我抱着试一试的态度把这个宏定义替换成了内联函数后,提交就过了:

static inline int min(int x, int y) {
    return x < y ? x : y;
}

此时我的心里就只有两个字:卧槽!为什么我不早点开启调试呢?因为错误案例的数据量特别大,调试到触发问题的点太耗时了,所以一直没有调试。触发问题出现的场景是我对宏定义进行了嵌套调用:

x = min(min(1, 3), 2) + 1

使用-E选项预处理发现他们被展开成了如下形式,预期的结果应该返回2,但是这个表达式返回的结果是1,所以就出现了问题:

x = ((1) < (3) ? (1) : (3)) < (2) ? ((1) < (3) ? (1) : (3)) : (2) + 1;

《Effective C++》中明确提出了一点就是:少使用宏定义!宏定义只是简单的文本替换,它不会在编译时候检查,在复杂的表达式逻辑中很容易就会产生问题。

c语言可变长参数传递问题

一、问题描述

C语言中的函数提供了一种可变长参数机制,这个机制使得我们在操作的时候充分自定义自己的功能,例如使用最多的printf函数:

printf("%s: %d", "HelloWorld", 10);

它的函数声明为:

printf(const char *fmt, ...);

其中的...就代表不固定的参数,使用起来十分方便。但是在函数嵌套的时候,不能直接使用...来占位,例如:

#define logerr(s, ...) do { fprintf(stderr, s, ...); } while (0)

编译时就会报错:

va_args.c:4:50: error: expected expression before ‘...’ token
 #define logerr(s, ...) do { fprintf(stderr, "s", ...); } while (0)

如果要嵌套使用,需要通过宏__VA_ARGS__完成:

#define logerr(s, ...) do { fprintf(stderr, s, __VA_ARGS__); } while (0)

二、参数个数为0的问题

使用上面的方法,参数个数为0的时候编译也会报错:

#define logdbg(s, ...) do { fprintf(stderr, s, __VA_ARGS__); } while (0)

int main() {
    logdbg("HelloWorld\n");
    return 0;
}

编译报错:

> gcc va_args.c  -o debug/va_args
va_args.c: In function ‘main’:
va_args.c:5:59: error: expected expression before ‘)’ token
 #define logdbg(s, ...) do { fprintf(stderr, s, __VA_ARGS__); } while (0)

原因是因为参数个数零,预编译后main函数里面的代码变成了:

> gcc -E va_args.c | tail 
int main() {
    const char *msg = "HelloWorld";

    do { fprintf(
# 10 "va_args.c" 3 4
   stderr
# 10 "va_args.c"
   , "HelloWorld\n", ); } while (0);
    return 0;
}

可以看到:fprintf函数的最后是"HelloWorld", );,最后一个逗号和括号之间没有数据,语法不通过。

解决方案

__VA_ARGS__前面加上##,例如:

#include <stdio.h>
#include <stdarg.h>

#define logerr(s, ...) do { fprintf(stderr, s, ##__VA_ARGS__); } while (0)
#define logdbg(s, ...) do { fprintf(stderr, s, ##__VA_ARGS__); } while (0)

int main() {
    const char *msg = "HelloWorld";
    logerr("%s\n", msg);
    logdbg("HelloWorld\n");
    return 0;
}

编译运行:

> make va_args
gcc va_args.c -o debug/va_args
> ./debug/va_args 
HelloWorld
HelloWorld

STL中的string类有两个方法size()length()用来返回字符串的长度。 两者在实现上没有区别:

> sed -n 907,918p /usr/include/c++/7/bits/basic_string.h 
      // Capacity:
      ///  Returns the number of characters in the string, not including any
      ///  null-termination.
      size_type
      size() const _GLIBCXX_NOEXCEPT
      { return _M_string_length; }

      ///  Returns the number of characters in the string, not including any
      ///  null-termination.
      size_type
      length() const _GLIBCXX_NOEXCEPT
      { return _M_string_length; }

一、静态库和动态库

静态库是指程序在编译阶段就把库文件嵌入到程序中的三方库,这种行为在程序运行前就已经决定了,程序在编译完成后不再依赖库文件。

动态库和静态库不一样,它是在程序运行期间才发生的调用行为,不会嵌入到程序,相对来说,链接动态库的二进制文件体积更小。

windows和linux平台下的静态/动态库后缀分别为lib dll.a .so,其中linux中动态库的命名规则为:libxxx.so.x.y.z,xxx表示库名,x是主版本号,y是此版本号,z是发行版本号。

- 阅读剩余部分 -

fgets函数的声明如下:

char *fgets(char *s, int size, FILE *stream);

s表示待接收字符串的缓冲区,size为最大大小,stream为读取的数据流。

对于数据的读取来说,函数实际最多读到size - 1个字节,如果读取的数据比这个长,会自动截断,保证在最后以\0结尾,要注意的是读取字符时会把\n也读进来

- 阅读剩余部分 -

reserve方法用来给vector预留空间,预留的空间只会改变capacity的大小,不会改变size大小。resize方法表示重新调整数组大小,capacity和size都会改变。

使用reserve后,不能直接使用下标来增加元素,虽然内存是已经分配了直接使用不会报错,但是直接通过下标来复制会导致其他参数得不到更新(如size),会导致意想不到的错误。如以下代码:

int i;
vector<int> v;
v.reserve(10);
cout << "cap: " << v.capacity() << ", size: " << v.size() << endl;
for (i = 0; i < 10; i++) {
    v[i] = i;
}
cout << "cap: " << v.capacity() << ", size: " << v.size() << endl;

输出:

cap: 10, size: 0
cap: 10, size: 0

通过下标给数组中的每个元素复制,实际上本身数组的长度并没有得到增长,一旦再执行push_back就会导致前面的数据被覆盖。正确的方式是使用push_back或者insert方法插入元素。

一、YouCompleteMe介绍

YouCompleteMe(简称YCM)是一款vim的智能补全插件,支持C/C++, Go, Python...等多种代码类型补全。

它强大的功能吸引了不少人的使用,但有无数人因为安装它“折腰”,因为它的安装过程确实很麻烦。

花了一个下午的时间,来回装了两次,终于算是勉强搞定。

首先假定你已经安装好了vim和对应的插件管理器:升级安装vim 8.0并添加vundle插件管理

要注意的是,vim编辑器要求编译的时候添加了python支持。

根据测试,选择python2支持会比python3省事一些,因为后面安装cmake的时候貌似只能使用python2(具体是不是这样没有去深入研究,目前暂且按python2的来)。

先上一张效果图:

- 阅读剩余部分 -