记录 Java 转 C++ 开发时遇到的那些“坑”(一)

1,126 阅读3分钟

教训一、map[key] 会向map插入一条空记录

  unordered_map<string, string> map; 
  map.emplace("key1", "value1"); 
  map.emplace("key2", "value2"); 

  cout<<"value="<<map["key3"]<<endl; 

  auto iter = map.find("key3"); 
  if (iter != map.end()){ 
    cout<<"key3 found!"<<endl; 
  }else{ 
    cout<<"key3 not found!"<<endl; 
  } 

  cout<<"value="<<map["key3"]<<endl; 

运行结果:

value=
key3 found!
value=

key3居然能在map中通过find函数查到。

正确的用法是:

C++里,map[key] 只能用于存值,不能用于取值。

文档如下:如果key不存在,则插入一条记录,值为默认值。 image.png

举一个经典的场景, 统计一个字符串数组里每个字符串出现的次数,正确做法是:

    unordered_map<string, int> map;
    string array[] = {"a", "a", "b", "c", "c", "c"};
    for (string item : array) {
        if (map.find(item) != map.end()) {
            map[item]++;
        } else {
            map[item] = 1;
        }
    }
    cout << "{";
    for (auto it = map.begin(); it != map.end(); it++) {
        cout << "\"" << it->first << "\": " << it->second;
        if (next(it) != map.end()) {
            cout << ", ";
        }
    }
    cout << "}" << endl;

运行结果:

{"c": 3, "a": 2, "b": 1}

教训二、 C++里的 getter 方法可能会发生值拷贝:

猜猜下面的代码会输出什么?:


class Test{
public:
    Test() {
        cout<<"list real address = "<<&list<<endl;
    }

private:
    vector<string> list;

public:
    void addToList(const string & item){
        list.emplace_back(item);
    }
    vector<string> getList(){
        return list;
    }
    vector<string> & getListPointer(){
        return list;
    }
};
inline string listToString(const vector<string> & list){
    string result = "{";
    for(const auto & str : list){
        result += str + ", ";
    }
    if (!list.empty()) {
        result.pop_back();
        result.pop_back();
    }
    result = result + "}";
    return result;
}
inline void test1() {
    Test t;
    t.addToList("1");
    const auto list1 = t.getList();
    const auto & list2 = t.getList();

    const auto list3 = t.getListPointer();
    const auto & list4 = t.getListPointer();


    cout << "list1 address: " << (&list1) <<", value=" <<listToString(list1) << endl;
    cout << "list2 address: " << (&list2) <<", value=" <<listToString(list2) << endl;
    cout << "list3 address: " << (&list3) <<", value=" <<listToString(list3) << endl;
    cout << "list4 address: " << (&list4) <<", value=" <<listToString(list4) << endl;

}

输出:

list real address = 0x7ffeee78a8e0
list1 address: 0x7ffeee78a900, value={1}
list2 address: 0x7ffeee78a920, value={1}
list3 address: 0x7ffeee78a940, value={1}
list4 address: 0x7ffeee78a8e0, value={1}

也就是说,只有list4才是原地址,另外3种都发生了值的拷贝,都创建了新的list。如果list里的内容较多,可能就会严重影响性能。

教训三、bool 类型的默认值可能不是 false,int 的默认值可能不是0

class Test{
public:
    Test() {}

public:
    bool a{};
    int b;
    bool c;
};

inline void test1() {
    Test t;
    cout << "a: " << (t.a ? "true" : "false") << endl;
    cout << "b: " << (t.b ) << endl;
    cout << "c: " << (t.c ? "true" : "false") << endl;
}
int main() {
    test1();
    return 0;
}

执行结果:

a: false
b: 1907956496
c: true

a和c同为bool,初始化方式不一样,导致其值也不一样。b的类型是int,其默认值并不是0。

建议:做好初始化!

教训四、不要忽略 IDE 的警告:

string foo(){
    return "xxxx";
}

const char *c1 = foo().c_str();  //这种用法看上去没问题吧

但IDE给出了警告: image.png

参考:C++备忘录014:函数返回临时变量的生命周期

教训五、函数没写返回值,IDE也不会报错(起码Clion没有报错)

int test(int a){
    cout<<"a="<<a<<endl;
}

int main() {
    test(1);
}

这样的代码在 Ubuntu 上的确可以执行成功, 但是在别的 Linux 平台, 可能会出现难以排查的内存错误!

建议经常使用IDE的代码检查工具“Code -> Inspect Code”检查下代码:

image.png

待续。