文章

我踩过的c++的坑

c++坑还挺多的,以后每个坑我都记录下来
持续更新

在类的实例化时不能使用其他的类的构造函数

例如

TCHAR current_directiom[100];
GetCurrentDirectoryW(200, current_directiom);
Installer in(wstring(curren));

理论上没毛病。

但是在笔者的环境中in被视为一个函数而非一个类。

必须要写成

TCHAR current_directiom[100];
GetCurrentDirectoryW(200, current_directiom);
wstring path(current_directiom);
Installer in(path);

才行

char current_directiom[100];
GetCurrentDirectoryA(200, current_directiom);
string path = string(current_directiom) + string("\\data\\setting.ini");

而这样的却又是可以的,迷

两个指针作为参数不能重载运算符

QListWidget * operator<<(QListWidget *output, char  *your_output);

这样的语句是错误的,为避免出现内置类型的重载,必须有一个及以上的参数为类或枚举类型

单例模式请务必加锁

今天偷懒,写了一个简化版的单例模式,没加异步锁,被别人看出来了。。。

别再using namespace std

这会极大的污染命名空间。很容易产生c226不明确问题

替代方法 using xxx::yyy

xxx: 命名空间
yyy:标识符

关于switch

switch其实是一个流控制器。如果你用了比较传统的写法

...
case A :
...
break

那么你在A中定义的变量会一路传下来,这是不安全的。所以会报错。

解决方案:

...
case A :
{
    ...
}
break

让变量超出范围就消失就行了。

如果你下文的逻辑还要用到这个变量,就把它定义到swutch块前面去。

输入输出重定向的问题

不要在powershell中进行输入输出重定向,会报很多神奇的错误,改成cmd即可。

别在迭代器中使用erase

例如这段代码

for (auto num_ptr = digits.begin(); num_ptr != digits.end(); num_ptr++)
{
    if (*num_ptr == 0)
    {
        digits.erase(num_ptr);
        digits.push_back(0);
    }
}

会出现错误

warning.png

这段错误在STL中的源码我也放下

if (this->_Getcont() == 0
    || this->_Ptr == 0
    || ((_Myvec *)this->_Getcont())->_Mylast <= this->_Ptr)
{    // report error
    _DEBUG_ERROR("vector iterator not incrementable");
    _SCL_SECURE_OUT_OF_RANGE;
}

this->_Getcont()的源码是

return (_Myproxy == 0 ? 0 : _Myproxy->_Mycont);

原来的迭代器在erase后已经失效了

解决方案

把迭代器自增分出来

for (auto num_ptr = digits.begin(); num_ptr != digits.end();)
{
    if (*num_ptr == 0)
    {
        num_ptr = digits.erase(num_ptr);
        digits.push_back(0);
    }
    else
    {
        num_ptr++;
    }
}

stl算法的前后界陷阱

笔者在今天写代码的时候碰到了一个问题,max_element总是出错,找出来的不包括最后一个,一番研究后啼笑皆非。

我们使用这个函数对整个容器进行操作时,一定会用max_element(vec.begion(),vec.end()),其中的end()是最后一个迭代器的下一个。
但是我们在对容器的一部分进行操作时,却容易忘记掉,第二个参数的意义,最后导致少操作了一个。。。

关于system

严格来说,这个不能算是c的坑,而应该算是c语言和c一起搞出来的的坑。

system函数,总所周知,是不需要库的(c++),而我又好久没有写过pure c了,于是某日我在用pure c写代码时,突然发现system函数怎么命名空间里没有,而我那时恰好在给学妹演示代码,于是就变成大型翻车现场了...后来我发现,这个函数在c中其实放在stdlib.h里面。

我觉得c++标准库不再需要标明.h是一个重大的创举2333

关于相对路径

需要注意的是当你使用相对地址,或者使用某些库求地址的库时,其基准地址会存在陷阱。

对于代码a.txt按照道理你想要达成的是通过相对地址访问与程序同一个层次的a.txt

  • 对于直接运行程序来说,你得到的就是正确的结果。
  • 对于命令行运行的程序例如.\xxx\a.exe时,你的基准地址为命令行的工作目录,这就会导致你访问不到,或者访问了错误的a.exe。

关于类的定义先后

在vs中偶遇

error C2143: 语法错误: 缺少“;”(在“<”的前面)
note: 参见对正在编译的类 模板 实例化“LList”的引用
error C4430: 缺少类型说明符 - 假定为 int。注意: C++ 不支持默认 int
error C2238: 意外的标记位于“;”之前
error C2143: 语法错误: 缺少“;”(在“<”的前面)
error C4430: 缺少类型说明符 - 假定为 int。注意: C++ 不支持默认 int
error C2238: 意外的标记位于“;”之前
error C2143: 语法错误: 缺少“;”(在“<”的前面)
error C4430: 缺少类型说明符 - 假定为 int。注意: C++ 不支持默认 int
error C2238: 意外的标记位于“;”之前

这样的一大堆错误。

我当时看了很长时间都没发现错误,而且静态检测中也没报错。就是实例化后会出现类型推倒失败。我开始也以为是类的前后问题。但是因为内含类未报错。所以也就没有接着看了。

再把组合的成员声明放在总成员上后,问题就解决了。

总的来说vs,这方面的问题检验还有点问题。所以以后看到这样的错误要自己注意下LOL。

有关于类多重引用的顺序

设有A,B两类

class A
{
private:
    int a,b;
public:
    A(B b):
    a(b.a),b(b.b)
    {}
}
class B
{
private:
    int a,b;
public:
    B(A a):
    a(a.a),b(a.b)
    {}
}

这样子的代码是错误的,因为编译器是自顶而下编译的,在编译A类时,不知道B类的情况,甚至不知道B类。

那我们改一下,提前告诉编译器A类的存在

class B;
class A
{
private:
    int a,b;
public:
    A(B b):
    a(b.a),b(b.b)
    {}
}
class B
{
private:
    int a,b;
public:
    B(A a):
    a(a.a),b(a.b)
    {}
}

这样子可以了吗?答案是这样子也是不行的,因为在A类中用到了b.a这样的东西,而编译器虽然知道B的存在,但是也就仅仅知道它的存在,换言之,这是个不完整类型。而编译器不能访问不完整类型的成员。

可以改成指针形式

class B;
class A
{
private:
    int a,b;
public:
    A(B *b):
    a(b->a),b(b->b)
    {}
}
class B
{
private:
    int a,b;
public:
    B(A a):
    a(a.a),b(a.b)
    {}
}

这样子就可以解决问题了

如果你一定不要指针的话,也可以,那你可以把类内方法的实现放到两个类下面

class B;
class A
{
private:
    int a,b;
public:
    A(B *b);
}
class B
{
private:
    int a,b;
public:
    B(A a);
}
A::A(B *b):
    a(b->a),b(b->b)
    {}
B::B(A a):
    a(a.a),b(a.b)
    {}

stl迭代器陷阱

今天在用stl迭代经典算法时偶遇问题

果然像是这种问题,还是没有成体系学习stl甚至c++的锅。

iterator not incrementable
vector iterators incompatible

这些问题碰到了很多次,原因这里简单记一下

  1. 迭代器在容器增加,删除时很容易失效,特别是erase函数,此时就会出现不兼容错误,因为此时迭代器的行为是不可预测的。
  2. 在使用memset时很容易弄坏几个指针,就会报错

解决方法

  1. 不使用memset简单粗暴操作
  2. 在敏感操作后的迭代器应该重新获得,可以存储偏移,也可以使用敏感操作的返回值重新获得

反思

不再土法学习c++,继续系统化

类的静态变量需要定义

符号未定义.......static.....

如果出现这种问题,说明你没有定义类的静态变量。你至少需要在一个.cpp中定义一次

与普通便量不同,此处不会触发重定义

命令行程序的缓冲区

有些人(),为了程序看上去比较骚气

会使用

_wsystem(L"title 停车 -xxxx");  // 宽字节版
system("color F0");
system("mode con cols=36 lines=25");

这样的代码。

然后我们就会发现少了一个很重要的东西,滚条

如果一屏幕放不下就看不见上面的数据了

解决方法

#include
......
HANDLE con = GetStdHandle(STD_OUTPUT_HANDLE);
COORD buf = { 36,200 };
SetConsoleScreenBufferSize(con, buf);

使缓冲区变大

缓冲区长度高于窗口时会有竖直的滚动条,反之反之,缓冲区应大于窗体

关于类的函数友元

类的友元函数被类声明后。事实上就已经将此函数声明了。也就是这个函数是一个特殊的类方法。期望将独立于类的方法作为友元(即将别人的友元同时作为自己的友元声明)会在编译期报错。事实上

class A
{
private:
    int a;
    friend void test();
}
class B
{
private:
    int a;
    friend void test();
}
void test();

这里的三个test是不同的test。位于不同的作用域 :: A:: B::,那么显然::A不能调用B::a

这都是简单的东西,因为搞错了我们的ide都会报错。

但是如果test是A B中某类的操作符重载的话就会混淆ide,从而不报错

class A
{
private:
    int a;
    friend A&& operator +(const A& a, const A& b);  // 无用 没有A::A&& operator (const A& a, const A& b) +的定义
}
class B
{
private:
    int a;
    friend A&& operator +(const A& a, const A& b);  // 无用 没有B::A&& operator (const A& a, const A& b) +的定义
}

A&& operator +(const A& a, const A& b);  // 有用 有::A&& operator (const A& a, const A& b) +的定义

A&& operator +(const A& a, const A& b)  // ::A&& operator (const A& a, const A& b) +的定义
{
    
}

没错,这里的operator +会被我们的vs识别为一个东西,甚至ctrl 点进去都是同一个,也就是下面的实现。然后你就会发现你可以使用A::a却不可以使用B::a,如果你水平和我一样菜的话,就会觉得很晕。

实际上,编译器只认最后面那个。上面的两个其实是被无视掉的,也就是被认为是只有声明没有定义的玩意。而函数可以使用A的私有变量其实是因为它是A的重载函数,c++给的语法糖罢了。

这里的原因是vs的bug,把三个A&& operator (const A& a, const A& b) +的声明都当成了::A&& operator (const A& a, const A& b) +。。。

其实和我菜也有一定关系。

License:  CC BY 4.0