C++ 错误分类:(编译错误 逻辑错误 运行时错误)

(一)编译错误:

[1]函数隐式删除: C2280

1.拷贝赋值函数(拷贝构造函数)隐式删除:

operator =(const &) 函数,是类对象的默认拷贝赋值函数。它和拷贝构造函数统称为类的复制函数
如果有新的对象生成,则调用的是拷贝构造函数;假如仅仅是变量赋值,则调用的是拷贝赋值函数。

class A
{
};

A a;
A a0=a;//拷贝构造
A a1=(a);//拷贝构造

A a2;
a2=a;//拷贝赋值

假如复制函数在源代码中没有被定义,编译器会在编译过程中创造该函数并且添加到函数符号组中。
然而,在一些特殊情况下,编译器会将其生成的函数隐式删除,从而导致C2280错误。
根据MSDN,这种隐式删除会出现在:

  1. 类变量含有引用(&)或者const类型:(一般来说这样的类不应该复制,因为其成员是不可复制的)
  2. 类声明了移动构造函数,或者声明了移动赋值函数,则编译器会将其生成的与其对应的复制函数隐式删除:(编译器会尽量使用移动构造函数与移动赋值函数来做解析符号以提高代码运行效率)
  3. 一个类的复制函数,会递归地调用其成员类的复制函数。因此假如成员类型的复制函数不存在/不可访问,那么该类型的默认复制函数会被隐式删除:(需要保证该类的成员类型都是可拷贝的)
  4. 假如一个类A显式地定义复制函数,那么任何包含有该类型作为数据成员的类型B假如包含有 volatile A …或者union{A…;… …};(匿名union结构,非匿名union结构会默认执行memcpy()式的内存复制,不会尝试调用A的复制函数,从而不会出现问题)也会导致编译器删除默认复制函数:(A定义了它本身的复制规则,B的默认复制函数可能无法保证A的复制有效执行,导致内存泄漏等风险)
//1.
extern int k;
struct A {
    A();
    int& ri = k; 
};

void f() {
    A a1, a2;
    a2 = a1;    // C2280
}

//2.
class base
{
public:
    base();
    ~base();
    base(base&&);
};

void copy(base *p)
{
    base b{*p};  // C2280
}

//3.
struct A
{


//4.
struct A 
{
    A() = default;
    A(const A&);
    //A一定要显式定义了复制函数
};

struct B 
{
    union {
        A a;
        int i;
    };
    //或者含有volatile标记的A类型成员变量:
    volatile A a0;
    //而非匿名的union不会导致该错误:
    union u0{
        A a;
        int i;
    };
};

int main() {
    B b1;
    B b2(b1);  // C2280
}

[2]函数名与变量名称冲突:C2365 (C2064)

特殊情况:静态类型检查可能得到 C2064:因为解析指针(函数符号)的时候,使用了与之同名的类成员变量名

class Demo
{
	public:
		char var0;
	public:
		bool var0()
		{...}
};

int main()
{
	Dem0 demo;
	std::cout<<demo.var0()<<std::endl;
	//产生C2064,因为var0命名冲突
	//如果Demo类中var0还是private成员变量,还会产生访问权限冲突
}

[3]引用初始化错误:

特殊情况:左值(left value)的静态强制类型转换,是一个右值(right value).不能够使用该右值(right value)来初始化非常量引用.

	// 静态类型转换 static_cast<new_type>()
    int m;
    unsigned int& rm=(unsigned int)m; // 非常量引用的初始值必须为左值
    
    int* p;
    unsigned long& rp=(unsigned long)p; // 非常量引用的初始值必须为左值

这种情况下,应该使用指针进行类型转换,或者使用C++的关键字reinterpret_cast<new_type>(),这种类型转换可以直接得到左值引用.

	// 重解释类型转换 reinterpret_cast<new_type>()
    int m;
    unsigned int& rm=reinterpret_cast<unsigned int&>(m); 
    
    int* p;
    unsigned long& rp=reinterpret_cast<unsigned long&>(p); 

# (二)逻辑错误:
## [1]STL代码误用:
### 1.priority_queue ::top()
&emsp;top()函数返回的实质上是一个地址,虽然该地址本身不能改变,但是它的“内容”却可以改变。
&emsp;因此,虽然可以将top()函数的返回值储存在一个类型为const Type& 的变量referrence_T中,却**一定不能以该变量referrence_T作为对象调用改变priority_queue的mutator函数**。因为,假使这个函数使用到了referrence_T的成员变量值,那么**priority_queue的变化也会导致referrence_T的值被修改**。从而使得算法行为不可确定。

```cpp
class A
{
    private:
        int ig=0;
public:
    A(int ig_value):ig(ig_value){}

public:
    void func(std::priority_queue<A>& q) const
    {
        std::cout<<"初始的ig值"<<this->ig<<"\t\t对象地址:"<<&ig<<std::endl;
        q.emplace(3);
        std::cout<<"emplace(3)后ig的值"<<this->ig<<"\t对象地址:"<<&ig<<std::endl;
        q.push(4);
        std::cout<<"push(4)后ig的值"<<this->ig<<"\t对象地址:"<<&ig<<std::endl;
        q.pop();
        std::cout<<"pop()后ig的值"<<this->ig<<"\t\t对象地址:"<<&ig<<std::endl;
    }

    friend bool operator < (const A& a0,const A& a1)
    {
        return a0.ig<a1.ig;
    }
};


int main()
{
    priority_queue<A> p;

    A a0(0);
    p.emplace(a0);
    p.emplace(-1);
    a0=p.top();
    p.pop();
    a0.func(p);
	
    std::cout<<std::endl<<"错误:"<<std::endl;
    const A& referrence_a=p.top();
    p.pop();
    referrence_a.func(p);
}

// 初始的ig值:0
// emplace(3)后ig的值:0
// push(4)后ig的值:0
// pop()后ig的值:0

// 错误:
// 初始的ig值:-1
// emplace(3)后ig的值:3
// push(4)后ig的值:4
// pop()后ig的值:3

[2]条件语句错误:

1.隐式类型转化:

2.if-else结构问题:

(三)运行时错误:RuntimeError

[1]对数组的越界访问:

1.unsigned与int混合运算导致循环变量越界:

[2]访问nullptr:

1.递归函数内的资源释放顺序错误:

在递归函数的递归生成树中,假如父节点通过指针,将资源留给子节点使用。那么,父节点如果要对该资源进行任何处理,都必须要在(使用该资源的)下一层递归之前进行,否则父节点访问的就会是nullptr。父节点访问nullptr一般来说都会导致RE。

void recursive_func( ... ...,T* resource)  //资源resource不是常量
    {
        //在叶子节点回收资源
        if (... ...)
        {
            delete resource;
            return;
        }
        
        if (... ...)
        {
            /**
             *父节点的资源,可以直接交由子节点使用,
             *但是这么做的话,父节点如果要对该资源进行任何处理,
             *都必须要在(使用该资源的)下一层递归之前进行,否则父节点访问的就会是nullptr
            **/
            UseResource(resource);
            ... ...
            recursive_fun(... ...the_memo);
            return;
        }
        else if (... ...)
        {
            //和上一条一样,父节点对resource的任何处理,必须先于下一层递归(使用该资源的)的开始
            T* copy_of_resource=new T(*resource);
            
            //可以将父节点的资源留给子节点使用
            recursive_func(... ..., resource);
            //创建新的资源给子节点
            recursive_func(... ..., copy_of_resource);
        }
        else
        {   
            //剪枝,提前回收资源
            delete resource;
            return;
        }
    }

[3]死循环:


版权声明:本文为qq_43116414原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。