【C++】一文理清C++中的五种强制类型转换

news2024/11/17 1:38:49

深入理解C++中五种强制类型转换的使用场景

一、C风格的强制类型转换

使用形式:

Type b = (Type)a;

C风格的强制类型转换很容易理解,不管什么类型都可以直接进行转换。C++也支持C风格的强制类型转换,但是C风格的强制类型转换可能会带来一些隐患,出现一些难以察觉的问题,所以C++又推出了四种新的强制类型转换来替代C风格的强制类型转换,降低使用风险。

二、C++风格的强制类型转换

2.1 static_cast

使用格式:

static_cast<type_id>(expression);

2.1.1 基本内置数据类型之间的转换

static_cast 可以用于基本内置数据类型之间的转换,比如把 char 转成 float、double 转换成 long 等,这种内置类型之间的转换往往可以由隐式转换自动执行,而不需要人工特地去执行强制类型转换。由于转换结果可能存在截断性,这种转换的安全性要由开发人员来保证(一般不会出现严重程序运行错误,但是可能会出现逻辑错误),编译器可能会适当打印警告信息。示例如下:

#include <iostream>

int main(int argc, char* argv[])
{
    char type_char = 'A';
    float type_float = type_char; // 隐式转换也可以
    float type_float_cast = static_cast<float>(type_char); // 显式地使用static_cast进行强制类型转换

    double type_double = 1.23;
    long type_long = type_double; // 隐式转换也可以
    long type_long_cast = static_cast<long>(type_double); // 显式地使用static_cast进行强制类型转换
}

2.1.2 指针之间的转换

static_cast可以用于指针之间的转换,这种转换类型检查非常严格,不同类型的指针是直接不给转的,除非使用void*作为中间参数,我们知道隐式转换下void*类型是无法直接转换为其它类型指针的,这时候就需要借助static_cast来转换了。示例如下:

#include <iostream>

int main(int argc, char* argv[])
{
    int type_int = 10;
    float* float_ptr1 = &type_int; // int* -> float* 隐式转换无效
    float* float_ptr2 = static_cast<float*>(&type_int); // int* -> float* 使用static_cast转换无效
    char* char_ptr1 = &type_int; // int* -> char* 隐式转换无效
    char* char_ptr2 = static_cast<char*>(&type_int); // int* -> char* 使用static_cast转换无效

    void* void_ptr = &type_int; // 任何指针都可以隐式转换为void*
    float* float_ptr3 = void_ptr; // void* -> float* 隐式转换无效
    float* float_ptr4 = static_cast<float*>(void_ptr); // void* -> float* 使用static_cast转换成功
    char* char_ptr3 = void_ptr; // void* -> char* 隐式转换无效
    char* char_ptr4 = static_cast<char*>(void_ptr); // void* -> char* 使用static_cast转换成功
}

补充说明static_cast是直接不允许不同类型的引用进行转换的,因为没有void类型引用可以作为中间介质,这点和指针是有相当大区别的

2.1.3 类实例的之间转换

#include <iostream>

class A
{
public:
    int a;
};

class B
{
public:
    int b;
};

class C : public A, public B
{
public:
    int c;
};

int main(int argc, char* argv[])
{
    C c;
    A a = static_cast<A>(c); // 上行转换正常
    B b = static_cast<B>(c); // 上行转换正常

    C c_a = static_cast<C>(a); // 下行转换无效
    C c_b = static_cast<C>(b); // 下行转换无效
}

从测试程序中可以看到如果对类实例使用static_cast进行转换,static_cast是会进行类型判断的,对于上行转换来说这个过程就是正常的,但是下行转换则不行,static_cast认为下行转换等同于两个无关联的类进行转换,会报错。但是这个错误是有解决方法的,我们从报错信息中可以看到当static_cast转换失败时,会使用expression作为传入参数来调用type_id的构造函数,所以我们可以把类C改成以下形式,上面的示例即可编译通过:

class C : public A, public B
{
public:
    C()
    {
    }

    C(const A& v)
    {
        a = v.a;
    }

    C(const B& v)
    {
        b = v.b;
    }

    int c;
};

综上,我们可以得出使用static_cast对类实例进行强制类型转换时有以下特点:

  • 进行上行转换是完全安全合法的,当然这个过程由隐式转换来完成也是合法的;
  • 进行下行转换时,static_cast会认为两个类无关联,这种转换不合法。如果此时硬要转换的话,比如类A->类B(这两个类可以无任何关系,因为实例下行转换static_cast就是认为他们没关联),可以在B中添加一个使用类A进行构造的构造函数,比如B(const A&),这样就可以正常使用static_cast来进行类A->类B的操作了

2.1.4 没有多态的类实例指针或引用之间的转换

#include <iostream>

class A
{
public:
    int a;
};

class B
{
public:
    int b;
};

class C : public A, public B
{
public:
    int c;
};

int main(int argc, char* argv[])
{
    C c;

    A* a_ptr = static_cast<A*>(&c); // 上行指针转换正常
    B* b_ptr = static_cast<B*>(&c); // 上行指针转换正常
    A& a_ref = static_cast<A&>(c);  // 上行引用转换正常
    B& b_ref = static_cast<B&>(c);  // 上行引用转换正常
}

可以看到上行转换都是正常的,转换过程中不会出现任何显性和隐性错误,下面来看一下下行转换的示例:

int main(int argc, char* argv[])
{
    C c;
    A* a_ptr = static_cast<A*>(&c);
    B* b_ptr = static_cast<B*>(&c);
    A& a_ref = static_cast<A&>(c);
    B& b_ref = static_cast<B&>(c);

    C* c_ptra = static_cast<C*>(a_ptr); // 下行指针转换正常
    C* c_ptrb = static_cast<C*>(b_ptr); // 下行指针转换正常
    C& c_refa = static_cast<C&>(a_ref); // 下行引用转换正常
    C& c_refb = static_cast<C&>(b_ref); // 下行引用转换正常

    A* a_ptr_fail = static_cast<A*>(b_ptr); // B* -> A*,无关联的两个类型,无效
}

从上面的例子可以看到,下行转换也是正常的,并且static_cast也会拒绝掉两个无关联类之间的转换???这和书中说的不一样啊,不是说static_cast下行转换不安全吗?别急,上面的例子是片面的,各位看一下下面的代码就知道了:

int main(int argc, char* argv[])
{
    A a;
    B b;

    // 以下都能转换成功,说明static_cast根本就没有安全检查,只看到有继承关系就给转换了
    C* c_ptra = static_cast<C*>(&a);
    C* c_ptrb = static_cast<C*>(&b);
    C& c_refa = static_cast<C&>(a);
    C& c_refb = static_cast<C&>(b);
}

综上,我们可以得出使用static_cast对没有多态的类实例指针或引用进行强制类型转换时有以下特点:

  • 进行上行转换(派生类指针->基类指针、派生类引用->基类引用)是完全安全的,没有任何问题,当然这个过程由隐式转换来完成也是合法的
  • 进行下行转换(基类指针->派生类指针、基类引用->派生类引用)由于缺乏安全检查,所以是有问题的,要尽量避免这种用法(可以转,但不推荐)
  • 如果两个类无继承关系,则使用static_cast进行转换时会失败,但是这种情况下static_cast会显性地展示出错误信息,是安全的

2.1.5 具有多态的类实例指针或引用之间的转换

#include <iostream>

class A
{
public:
    virtual void print()
    {
        std::cout << "A" << std::endl;
    }
};

class B
{
public:
    virtual void print()
    {
        std::cout << "B" << std::endl;
    }
};

class C : public A, public B
{
public:
    virtual void print() override
    {
        std::cout << "C" << std::endl;
    }
};

int main(int argc, char* argv[])
{
    C c;

    A* a_ptr = static_cast<A*>(&c); // 上行指针转换正常
    B* b_ptr = static_cast<B*>(&c); // 上行指针转换正常
    a_ptr->print();                 // 输出C,符合多态的要求
    b_ptr->print();                 // 输出C,符合多态的要求

    A& a_ref = static_cast<A&>(c); // 上行引用转换正常
    B& b_ref = static_cast<B&>(c); // 上行引用转换正常
    a_ref.print();                 // 输出C,符合多态的要求
    b_ref.print();                 // 输出C,符合多态的要求
}

可以看到上行转换都是正常的,转换过程中不会出现任何显性和隐性错误,下面来看一下正常的下行转换的示例:

int main(int argc, char* argv[])
{
    C c;
    A* a_ptr = static_cast<A*>(&c);
    B* b_ptr = static_cast<B*>(&c);
    A& a_ref = static_cast<A&>(c);
    B& b_ref = static_cast<B&>(c);

    C* c_ptra = static_cast<C*>(a_ptr); // 下行指针转换正常
    C* c_ptrb = static_cast<C*>(b_ptr); // 下行指针转换正常
    c_ptra->print(); // 输出C,符合多态的要求
    c_ptrb->print(); // 输出C,符合多态的要求

    C& c_refa = static_cast<C&>(a_ref); // 下行引用转换正常
    C& c_refb = static_cast<C&>(b_ref); // 下行引用转换正常
    c_refa.print(); // 输出C,符合多态的要求
    c_refb.print(); // 输出C,符合多态的要求
}

可以看到这个也是正常的,和前面那个没有多态的差不多,接下来看一下不正常的下行转换例子:

int main(int argc, char* argv[])
{
    A a;
    B b;

    C* c_ptra = static_cast<C*>(&a);
    C* c_ptrb = static_cast<C*>(&b);
    c_ptra->print(); // 正常输出A
    c_ptrb->print(); // 段错误

    C& c_refa = static_cast<C&>(a);
    C& c_refb = static_cast<C&>(b);
    c_refa.print(); // 正常输出A
    c_refb.print(); // 段错误
}

上面这个例子中的下行转换是错误的,但是通过c_ptra可以正常调用类A的print()方法打印出字母A来,使用c_ptrb就直接段错误了,原因是类A是第一个被继承的,类B是第二个被继承的,也就是在类C中,第一个虚表指针指向的就是类A的虚表,第二个虚表指针指向的就是类B的虚表。在上面的例子那样进行错误地转换时,由于类A被继承之后它位置的特殊性导致可以使用c_ptra正确地调用类A的print()方法,而类B则不行,可能这有点难理解,下面给大家看张图就明白了,如图2-1所示:

img

从图2-1中可以看出,对于类C来说,它始终调用着_vptr.A指向的print()方法,当我们使用纯类B类型进行下行转换时,根本就没有这一块的数据(这个转换是不完整的、不安全的),所以就会出现段错误了。当然,使用纯类A类型进行下行转换也是不完整、不安全的,只不过位置刚好才不会出现段错误而已。综合分析上面的代码是完全错误的,一定要杜绝写出这种垃圾代码。

综上,我们可以得出使用static_cast对具有多态的类实例指针或引用进行强制类型转换时有以下特点:

  • 进行上行转换(派生类指针->基类指针、派生类引用->基类引用)是完全安全的,没有任何问题,当然这个过程由隐式转换来完成也是合法的

  • 进行下行转换(基类指针->派生类指针、基类引用->派生类引用)由于缺乏安全检查,所以是有问题的,并且因为具有多态的类往往具有特殊的用法,所以在这种情况下产生的后果比前面没有多态情况下的要更严重,要尽量避免这种用法

    2.1.6 不能转换掉expression的const或volitale属性

static_cast不能转换掉expressionconstvolitale属性。示例如下:

#include <iostream>

int main(int argc, char* argv[])
{
    int temp = 10;

    const int* a_const_ptr = &temp;
    int* b_const_ptr = static_cast<int*>(a_const_ptr); // const int* -> int* 无效

    const int a_const_ref = 10;
    int& b_const_ref = static_cast<int&>(a_const_ref); // const int& -> int& 无效

    volatile int* a_vol_ptr = &temp;
    int* b_vol_ptr = static_cast<int*>(a_vol_ptr); // volatile int* -> int* 无效

    volatile int a_vol_ref = 10;
    int& b_vol_ref = static_cast<int&>(a_vol_ref); // volatile int& -> int& 无效
}

2.1.7 小结

通过上面的介绍,我们可以很直观地看到static_cast相比C风格的强制类型转换要安全很多,有很大程度上的类型安全检查。本节我们所有的例子都可以使用C风格的强制类型转换去做,但是转出来的结果有可能会错到天际去,并且编译器不会给你任何报错信息。同时我们也要认识到static_cast也是有明显缺点的,那就是无法消除const和volatile属性、无法直接对两个不同类型的指针或引用进行转换和下行转换无类型安全检查等,不过没关系,其它三个强制类型转换的关键字刚好能弥补static_cast的这些缺点。

2.2 const_cast

const_cast的作用是去除掉constvolitale属性,前面介绍static_cast的时候我们知道static_cast是不具备这种功能的。使用格式如下:

const_cast<type_id>(expression);

**注意事项:**const_cast不是用于去除变量的常量性,而是去除指向常量对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用,并且const_cast不支持不同类型指针或引用之间的转换,比如说float*转换成int*是不允许的,直白一点说就是type_id和expression要基本类型保持一致,相差的话只能差const或volatile属性。

先来看一个错误的使用示例:

#include <iostream>

int main(int argc, char* argv[])
{
    int type_int = 100;
    float type_float = const_cast<float>(type_int);        // 错误,const_cast只能转换引用或者指针
    float* type_float_ptr = const_cast<float*>(&type_int); // 错误,从int* -> float* 无效
    float& type_float_ref = const_cast<float&>(type_int);  // 错误,从int& -> float& 无效
}

再来看一个不太正确的使用示例:

#include <iostream>

int main(int argc, char* argv[])
{
    const int type_const_int = 100;
    int* type_const_int_ptr = const_cast<int*>(&type_const_int); // 转换正确
    int& type_const_int_ref = const_cast<int&>(type_const_int);  // 转换正确
    
    *type_const_int_ptr = 10;
    std::cout << *type_const_int_ptr << std::endl; // 输出10
    std::cout << type_const_int << std::endl;      // 输出100,没有改变

    type_const_int_ref = 20;
    std::cout << type_const_int_ref << std::endl; // 输出20
    std::cout << type_const_int << std::endl;     // 输出100,没有改变

    // 以下三个输出结果一致,说明const_cast确实只是去除了一些属性,并没有重新搞快内存把需要转换的变量给复制过去
    std::cout << "&type_const_int:\t" << &type_const_int << std::endl;
    std::cout << "type_const_int_ptr:\t" << type_const_int_ptr << std::endl;
    std::cout << "&type_const_int_ref:\t" << &type_const_int_ref << std::endl;
}

在上面这个例子中,转换是成功了,但是type_const_int的常量性并没有被改变,这是因为const_cast并没有办法把变量的常量性去除,而且比较有意思的是我们可以看到type_const_int对应地址的内容确实被改变了,但是type_const_int的值却并没有被改变,这是好事,因为从一开始我们把它定义为常量类型时这个值就不应该再被改变了。至于后面使用type_const_int_ptr和type_const_int_ref试图去改变type_const_int的值,这是很危险的做法,不同编译器可能会有不同的处理,是有可能出现严重错误的,要杜绝这种用法。
从这里看起来const_cast好像有点鸡肋。。。但是事实上不是这样的,在某些场景下const_cast还是挺好用的,比如下面这个例子:

#include <iostream>

void fun(const int& v)
{
    int& type_int_ref = const_cast<int&>(v);
    type_int_ref = 10;
}

int main(int argc, char* argv[])
{
    int type_int = 100;

    fun(type_int);
    std::cout << type_int << std::endl; // 输出10,改变了
}

上面的例子比较粗糙,但是大致上也就是这么个用法了。如果一个变量本来就不具备const属性,但是在传递过程中被附加了const属性,这时候使用const_cast就能完美清除掉后面附加的那个const属性了。

2.3 reinterpret_cast

reinterpret_cast意为“重新解释”,它是C++中最接近于C风格强制类型转换的一个关键字。它让程序员能够将一种对象类型转换为另一种,不管它们是否相关。使用格式如下:

reinterpret_cast<type_id>(expression);

注意事项如下:

  1. type-id和expression中必须有一个是指针或引用类型(可以两个都是指针或引用,指针引用在一定场景下可以混用,但是建议不要这样做,编译器也会给出相应的警告)。
  2. reinterpret_cast的第一种用途是改变指针或引用的类型
  3. reinterpret_cast的第二种用途是将指针或引用转换为一个整型,这个整型必须与当前系统指针占的字节数一致
  4. reinterpret_cast的第三种用途是将一个整型转换为指针或引用类型
  5. 可以先使用reinterpret_cast把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值(由于这个过程中type-id和expression始终有一个参数是整形,所以另一个必须是指针或引用,并且整型所占字节数必须与当前系统环境下指针占的字节数一致)
  6. 使用reinterpret_cast强制转换过程仅仅只是比特位的拷贝,和C风格极其相似(但是reinterpret_cast不是全能转换,详见第1点),实际上reinterpret_cast的出现就是为了让编译器强制接受static_cast不允许的类型转换,因此使用的时候要谨而慎之
  7. reinterpret_cast同样也不能转换掉expression的const或volitale属性。

先来看一个错误的使用示例:

#include <iostream>

class A
{
public:
    int a;
};

class B
{
public:
    int b;
};

int main(int argc, char* argv[])
{
    float type_float = 10.1;
    int type_int = reinterpret_cast<int>(type_float); // 出错,type-id和expression中必须有一个是指针或者引用(注意事项第1点)
    char type_char = reinterpret_cast<char>(&type_float); // 出错,我的是64位系统,这里type-id只能是long类型(注意事项第3点)
    double* type_double_ptr = reinterpret_cast<double*>(type_float); // 出错,这里expression只能是整型(注意事项第4点)

    A a;
    B b;
    long type_long = reinterpret_cast<long>(a); // 出错,type-id和expression中必须有一个是指针或者引用(注意事项第1点)
    B b1 = reinterpret_cast<B>(a); // 出错,type-id和expression中必须有一个是指针或者引用(注意事项第1点)
    A a1 = reinterpret_cast<A>(&b); // 出错,B* -> A不允许,我的是64位系统,type-id只能是long(注意事项第3点)
    A* a_ptr = reinterpret_cast<A*>(b); // 出错,这里expression只能是整型(注意事项第4点)
}

下面再来看正确的使用示例:

#include <iostream>

class A
{
public:
    int a;
};

class B
{
public:
    int b;
};

int main(int argc, char* argv[])
{
    float type_float = 10.1;

    long type_long = reinterpret_cast<long>(&type_float); // 正确,float* -> long(注意事项第3点)

    float* type_float_ptr = reinterpret_cast<float*>(type_long); // 正确,long -> float*(注意事项第4点)
    std::cout << *type_float_ptr << std::endl; // 正确,仍然输出10.1(注意事项第5点)

    long* type_long_ptr = reinterpret_cast<long*>(&type_float); // 正确,float* -> long*(注意事项第1点)
    
    char type_char = 'A';
    double& type_double_ptr = reinterpret_cast<double&>(type_char); // 正确,char -> double&(注意事项第4点)

    A a;
    B b;
    long a_long = reinterpret_cast<long>(&a); // 正确,A* -> long(注意事项第3点)
    A* a_ptr1 = reinterpret_cast<A*>(type_long); // 正确,long -> A*(注意事项第4点)
    A* a_ptr2 = reinterpret_cast<A*>(&b); // 正确,B* -> A*(注意事项第1点)
}

2.3 dynamic_cast

前面三种都是编译时完成的,而dynamic_cast是运行时处理的,使用格式如下:

dynamic_cast<type_id>(expression);

注意事项如下:

  1. dynamic_cast是运行时处理的,运行时会进行类型检查(这点和static_cast差异较大)

  2. dynamic_cast不能用于内置基本数据类型的强制转换,并且dynamic_cast只能对指针或引用进行强制转换

  3. dynamic_cast如果转换成功的话返回的是指向类的指针或引用,转换失败的话则会返回nullptr

  4. 使用dynamic_cast进行上行转换时,与static_cast的效果是完全一样的

  5. 使用dynamic_cast进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。并且这种情况下dynamic_cast会要求进行转换的类必须具有多态性(即具有虚表,直白来说就是有虚函数或虚继承的类),否则编译不通过

  6. 需要有虚表的原因:类中存在虚表,就说明它有想要让基类指针或引用指向派生类对象的情况,dynamic_cast认为此时转换才有意义(事实也确实如此)。而且dynamic_cast运行时的类型检查需要有运行时类型信息,这个信息是存储在类的虚表中的

  7. 在C++中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操作时,更容易产生错误。dynamic_cast则可以在运行期对可能产生问题的类型转换进行测试

先来看一个错误的使用示例:

#include <iostream>

class A
{
public:
    void print()
    {
        std::cout << "A" << std::endl;
    }
};

class B
{
public:
    void print()
    {
        std::cout << "B" << std::endl;
    }
};

class C : public A, public B
{
public:
    void print()
    {
        std::cout << "C" << std::endl;
    }
};

int main(int argc, char* argv[])
{
    C c;
    A* a_ptr = dynamic_cast<A*>(&c); // 成功,上行转换没有任何要求(注意事项第4点)
    B* b_ptr = dynamic_cast<B*>(&c); // 成功,上行转换没有任何要求(注意事项第4点)

    A a = dynamic_cast<A>(c); // 错误,dynamic_cast无法用于转换类实例(注意事项第2点)

    C* c_ptra = dynamic_cast<C*>(a_ptr); // 错误,类C不具备多态,无法进行下行转换(注意事项第5点)
    C* c_ptrb = dynamic_cast<C*>(b_ptr); // 错误,类C不具备多态,无法进行下行转换(注意事项第5点)
}

从上面的例子中可以看到,dynamic_cast是无法对实例进行转换的,并且dynamic_cast进行下行转换时是需要对应的类拥有多态性的,这两点和static_cast是有区别,当然这些不是最重要的区别,下面我们再来看一个例子:

#include <iostream>

class A
{
public:
    virtual void print()
    {
        std::cout << "A" << std::endl;
    }
};

class B
{
public:
    virtual void print()
    {
        std::cout << "B" << std::endl;
    }
};

class C : public A, public B
{
public:
    virtual void print()
    {
        std::cout << "C" << std::endl;
    }
};

int main(int argc, char* argv[])
{
    C c;

    // 第一组
    A* a_ptr = dynamic_cast<A*>(&c);
    B* b_ptr = dynamic_cast<B*>(&c);
    C* c_ptra = dynamic_cast<C*>(a_ptr); // 成功,类C具备多态性,可以使用dynamic_cast进行下行转换
    C* c_ptrb = dynamic_cast<C*>(b_ptr); // 成功,类C具备多态性,可以使用dynamic_cast进行下行转换
    // 以下输出内容一致
    std::cout << &c << std::endl;
    std::cout << c_ptra << std::endl;
    std::cout << c_ptra << std::endl;

    // 第二组
    A a;
    B b;
    C* c_ptra1 = dynamic_cast<C*>(&a); // 编译正常(好的编译器会给你个警告),转换结果为nullptr,说明转换失败
    C* c_ptrb1 = dynamic_cast<C*>(&b); // 编译正常(好的编译器会给你个警告),转换结果为nullptr,说明转换失败
    // 以下输出内容一致,都是0,说明c_ptra1和c_ptrb1都是nullptr
    std::cout << c_ptra1 << std::endl;
    std::cout << c_ptrb1 << std::endl;
}

从上面的例子中可以看出以下几点信息:

  • 对于第一组来说,因为这一组的下行转换是安全的(因为a_ptr和b_ptr本来就是类C的指针类型转换过去的),所以对于这种情况static_cast和dynamic_cast是一样的,都可以正常使用。

  • 对于第二组来说,这个是一个错误使用的示例,我们之前也有用static_cast做过同样的实验(忘了的话可以翻回去2.1.3小节看一下),当时使用static_cast进行这种错误的下行转换时是一点问题没有的,可以转换成功,甚至可以使用一下(当然这是极其糟糕的行为),而现在使用dynamic_cast来转换则不同了,虽然编译也可以通过,但是它返回的结果是一个空指针,这就可以在运行时提示我们转换不合法了。

以上论述了C++中新增的四种强制类型转换,文中也有对它们各自的优点和缺点进行解析。整体来说C++增加的这几种强制类型转换相比C风格的更加安全,所以在编写C++代码时还是尽量使用它们替换C风格的强制类型转换吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/700582.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023.6.29-限定次数的循环操作

首先试着实现一个功能&#xff1a;键入一个整数&#xff0c;然后显示出这个整数个“*”&#xff1a; 代码&#xff1a; int a;printf("请输入一个整数&#xff1a;");scanf("%d",&a);while (a-- > 0)printf("*");printf("\n")…

模拟信号、数字信号

1、模拟信号、模拟电路 1&#xff09;模拟信号 是指连续变化的电信号&#xff0c;比如说话时的声音信号。 模拟信号可以用连续的电压或电流来表示&#xff08; 模拟信号就是电信号&#xff09;数字信号则是用离散的电压或电流来表示&#xff08;0 或 1&#xff09; 2&#x…

ROS:常用命令汇总

目录 一、前言二、rosnode三、rostopic3.1rostopic list3.2rostopic pub3.3rostpic echo3.4rostopic info 四、rosservice4.1rosservice args4.2rosservice call 五、rosmsg5.1rosmsg package5.2rosmsg show 六、rossrv6.1rossrv package6.2rossrv show 七、rosparam7.1rospara…

CSDN | CDC 城市开发者联盟开始招募啦!

一、CDC&#xff08;城市开发者联盟&#xff09;介绍 CDC 是由 CSDN 发起的公益性同城高端开发者社区&#xff0c;全称 City Developers Community&#xff0c;通过在每个城市招募和培养“CDC 城市合伙人”&#xff0c;一起在当地构建“千城千面”的线下开发者社交圈&#xff…

arcserver图层数据导出插件

arcserver数据导出插件使用 下载地址&#xff1a;https://download.csdn.net/download/u011405698/87966050?spm1001.2014.3001.5503 如果需要代码或商业合作请联系公众号&#xff1a;世奇 插件不依赖其他第三方类库&#xff0c;只依赖arcpy&#xff0c;使用简单方便。可以…

C++标准模板库STL (未完)

概述 STLstandard template library是 C 标准库的一部分&#xff0c;无需单独安装&#xff0c;只需#include 头文件 什么是模板&#xff1f;见c面向对象程序设计中模板一节 顺序性容器 vector 向量 变长数组 声明 vector<类型名> 变量名[数组长度];类型名可以是已有…

社交商务时代已经到来,ss客服帮您抓住风口

社交商务时代已经到来&#xff0c;ss客服帮您抓住风口 近年来&#xff0c;社交媒体商务的概念已成为一股强大的力量。通过将社交媒体平台的影响力和影响力与在线购物的便利性相结合&#xff0c;它正在彻底改变企业与客户互动和销售产品或服务的方式。 什么是社交商务&#xf…

MyBatisPlus代码生成器插件

1、下载IDEA插件&#xff1a;MybatisPlus。 2、使用插件生成代码 首先点击IDEA导航菜单上的Other选项&#xff0c;然后进行数据库和代码生成器的配置&#xff1a; 数据库配置&#xff1a; 代码生成器配置&#xff0c;然后点击code generator&#xff1a; 最会插件会按照配置…

Timeout while checking target RAM, RAMCode did not respond in time

问题 使用jlink烧入gd32f450时&#xff0c;jlink提示错误Timeout while checking target RAM, RAMCode did not respond in time; 解决办法 原因是RAM的地址范围错误&#xff0c;默认选择gd32f450vi的ram大小是512k&#xff0c;但是该芯片中的ram并不连续&#xff0c;其中64…

C++ 动态内存

了解动态内存在 C 中是如何工作的是成为一名合格的 C 程序员必不可少的。C 程序中的内存分为两个部分&#xff1a; 栈&#xff1a;在函数内部声明的所有变量都将占用栈内存。堆&#xff1a;这是程序中未使用的内存&#xff0c;在程序运行时可用于动态分配内存。 很多时候&…

【C/C++】类成员函数指针 定义 使用方法

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

使用conda虚拟环境,Jupyter Notebook 链接不上 kernel

1&#xff0c;检查 ipykernel 和 ipython 是否一致 输入pip list 或者conda list检查一下相应库的版本是不一致 不一致的话&#xff0c;可以更新这两个库的版本&#xff1a;pip install --upgrade 库名 2&#xff0c;看控制台的报错&#xff0c;如果是报404&#xff0c;内核找不…

leetcode 145. 二叉树的后序遍历

2023.6.24 继上一题前序遍历&#xff0c;这道后序遍历就很容易了&#xff0c;把递归的顺序稍微改一下就行。 递归法&#xff1a; class Solution { public:void travelsal(TreeNode* cur , vector<int>& ans){if(cur nullptr) return;travelsal(cur->left , an…

如何设计真正高性能高并发分布式系统?

1 、引子 软件复杂性来源于几个方面&#xff1a;高并发高性能、高可用、可扩展、低成本、低规模、可维护、安全等。架构演化、发展都是为了试图降低复杂性&#xff1a; 高并发、高性能&#xff1a;互联网系统特点&#xff0c;用户量大&#xff0c;请求量大&#xff0c;高并发高…

NLP学习笔记(二)

文章目录 &#xff08;一&#xff09;负采样&#xff08;二&#xff09;GloVe1.带全局语料库的跳元模型2.GloVe模型3.问题4.跳元模型与GloVe模型的比较 &#xff08;三&#xff09;问题1.参数初始化2.梯度下降3.下游任务4.句法信息5.似然估计6.词向量表示 &#xff08;一&#…

MySQL基础篇(day04,复习自用)

MySQL第四天 单行函数内容练习 聚合函数内容练习 单行函数 内容 #第七章 单行函数 SELECT ABS(-123),ABS(32),SIGN(-23),SIGN(43),PI(),CEIL(32.32),CEILING(-43.23),FLOOR(32.32), FLOOR(-43.23),MOD(12,5) FROM DUAL;#取随机数 SELECT RAND(),RAND(),RAND(10),RAND(10),RAND…

亚马逊开店卖什么好呢?有哪些热销产品推荐?

现如今提起跨境电商&#xff0c;人们首先会想到亚马逊平台&#xff0c;作为利润的核心&#xff0c;其发展与优势都是有目共睹的&#xff0c;所以引来了大批的创业者前往亚马逊开店&#xff0c;然而在亚马逊开店后&#xff0c;首先想到的问题就是卖什么产品好&#xff1f;什么产…

推荐一款轻量级全栈式开源测试平台!

1、RunnerGo介绍 今天给大家介绍一个好用的测试平台&#xff1a;RunnerGo&#xff08;开源&#xff09;。RunnerGo是一款轻量级、全栈式的测试平台&#xff0c;支持接口管理、场景管理、性能测试、自动化测试等功能。与市面上的性能测试工具不同的是RunnerGo基于go语言研发&am…

三元表达式空指针问题

三元表达式空指针问题 问题描述&#xff1a; 如下代码标红处出现空指针异常&#xff1a; 测试及定位问题&#xff1a; 情景一&#xff1a;将num赋值为20&#xff0c;将flag赋值为true 正常输出10 情景二&#xff1a;将num赋值为null&#xff0c;将flag赋值为true 此时可以看到…

域渗透——vCenter安装及使用

简介 VMware vCenterServer 提供了一个可伸缩、可扩展的平台&#xff0c;为 虚拟化管理奠定了基础。VMware vCenter Server&#xff08;以前称为 VMware VirtualCenter&#xff09;&#xff0c;可集中管理 VMware vSphere 环境&#xff0c;与其他管理平台相比&#xff0c;极大…