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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;

运行结果:

1
2
3
value=
key3 found!
value=

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

正确的用法是:

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;

运行结果:

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

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;

}

输出:

1
2
3
4
5
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}

执行结果:

1
2
3
a: false
b: 1907956496
c: true

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

建议:做好初始化!

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

1
2
3
4
5
6
string foo(){
return "xxxx";
}

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

但IDE给出了警告:
image.png

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

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

1
2
3
4
5
6
7
int test(int a){
cout<<"a="<<a<<endl;
}

int main() {
test(1);
}

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

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

image.png

待续。