文章

c++(9·类型转换,回调)

类型转换

类型转换:将一种数据类型转换为另一种数据类型

隐式转换

  • 时机:在表达式、条件语句、初始化、赋值、函数形参实参……情况会自动隐式转换,使其类型匹配
  • 支持的转换操作:
    • 算数类型转换(整形(char,bool,int),浮点,低精度高精度,有符号无符号)
    • 数组/函数和指针
    • 指针作为条件时和bool转换
    • const和非const
    • C风格字符串和string类

隐式 vs. 显示/强制

  • 显示可以避免意外转换,从而产生符合预期的转换方式
  • 显示让代码更明确清晰,提高可维护性

C风格 vs. C++风格

  • C++ 显示/强制 类型转换运算符,相比于C风格强制转换,它更加规范、明确、安全

C风格转换

1
2
3
4
5
double pi = 3.14159;
int truncatedPi = pi;//double 转换为 int

uintptr_t address = (uintptr_t)ptr;//将指针转换为整数类型
int *newPtr = (int*)address;//将整数转换回指针

展开

static_cast 静态转换

编译时转换,不会进行 类型检查(编译运行时检查合法性),可能造成编译/运行时崩溃

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
int num = 97;
char ch = static_cast<char>(num);

int value = 42;
int* ptr = nullptr;
void* voidPtr = static_cast<void*>(intPtr);
int* newIntPtr = static_cast<int*>(voidPtr);

class Animal {
public:
    virtual void sound() const {
        std::cout << "Animal makes a sound" << std::endl;
    }
};
 
class Dog : public Animal {
public:
    void sound() const override {
        std::cout << "Dog barks" << std::endl;
    }
    void fetch() const {
        std::cout << "Dog fetches the ball" << std::endl;
    }
};

Dog myDog;
Animal* animalPtr = static_cast<Animal*>(&myDog);  // 安全的上行转换
animalPtr->sound();  // 调用的是 Dog 的 sound

Animal* animalPtr = new Animal(); 
Dog* dogPtr = static_cast<Dog*>(animalPtr);//不安全
dogPtr->sound();  // 调用 Animal 的 sound 方法
dogPtr->fetch();  //未定义行为

Animal* animalPtr = new Dog();  // Animal指针指向Dog对象
Dog* dogPtr = static_cast<Dog*>(animalPtr);  // 下行转换
dogPtr->sound();  // 调用 Dog 的 sound 方法
dogPtr->fetch();  // 调用 Dog 的 fetch 方法

展开

  • 基本数据类型之间的相互转换
    • 浮点-》整数,丢弃小数
    • 整数-》浮点,数值不变
    • 高精度-》低精度,仅存低精度范围
    • 低精度-》高精度,数值不变
    • bool,可以被整数赋值,false -> 0, true -> 1
    • char,可以被整数赋值, ASCII <-> 字符
    • 无符号有符号
  • 任何类型指针都可以赋值给void指针,void指针转换成目标类型的指针需要显示转换
  • 类层次结构中基类和派生类之间指针或引用的转换,派生到基类上行安全,下行不安全,要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,才能转换成功

dynamic_cast 动态转换

1
2
3
4
5
6
7
8
9
10
11
12
Dog myDog;
Animal* animalPtr = dynamic_cast<Animal*>(&myDog);  // 上行转换(子类到父类)
animalPtr->sound();//调用子类方法

Animal* animalPtr = new Dog();
Dog* dogPtr = dynamic_cast<Dog*>(animalPtr);//父类指针转换为子类指针 
if (dogPtr) {  // 如果转换成功
    dogPtr->sound();  // 输出: Dog barks
    dogPtr->fetch();      // 输出: Dog fetches the ball
} else {
    std::cout << "Conversion failed!" << std::endl;
}

展开

运行时转换,会类型检查,因此可以针对多种情景做出应对方案,比static_cast对类层次转换更安全

类层次结构中基类和派生类之间指针或引用的转换,要求父类中至少有一个虚函数,要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,才能转换成功,转换失败返回空

const_cast 常量转换

  • const指针/引用 转为 非const指针/引用,不能用于在不同类型之间进行转换
  • 不要通过去掉const性的指针去修改值,虽然被指向的常量本身不会改变,但是这样的做法是未定义的(C++规范中并没有明确规定,由编译器来自行决定)
  • 那么它到底用来做什么?函数参数接收非常量对象,但内部并没有修改,仅仅读取,可以利用cast去除常量性传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void modify(int* p) 
{
    *p = 100;  // 修改指针指向的值
}

const int a = 10;
const int * p = &a;
modify(const_cast<int*>(p));//试图修改常量对象,不安全,可能导致未定义行为

int b = 20;  
const int* p = &b;
modify(const_cast<int*>(p));//试图修改非常量对象,安全修改 b = 100

void print(const int* p) {
    std::cout << "Value: " << *p << std::endl;
}
int c = 50;
int* p = &c;
print(const_cast<const int*>(p));//非常量转换为常量是安全的

展开

reinterpret_cast 重新解释转换

不同类型之间进行低级别(二进制)的类型转换, 它有更强大的转换功能,但最不安全

1
2
3
4
5
6
7
8
9
10
A* objA;
B* bPtr = reinterpret_cast<B*>(objA);
bPtr->display();//强行转换不相关类型,可能导致内存错误和未定义行为

int x = 42;
uintptr_t* address = reinterpret_cast<uintptr_t*>(&x);//如果修改了x的值足够大,转换为int,可能会导致非法的内存访问
int newV = reinterpret_cast<int>(address);

float f = 3.14f;
int* intPtr = reinterpret_cast<int*>(&f);// 输出 f 的位模式所代表的整数,结果可能非常难以理解

展开

  • 非继承体系下指针或引用的类型转换
  • 指针和整数之间的转换:允许将指针/引用转换为整数类型,或将整数转换为指针/引用类型

回调(callback)

可调用对象:

可以直接通过调用运算符()调用

  • 普通函数
  • 函数指针

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    int test(int a)
    {
        return a;
    }
    int main(int argc, const char * argv[])
    {
        int (*fp)(int a);
        fp = test;
        cout<<fp(2)<<endl;
        return 0;
    }
    

    展开

    • 作用:实现回调机制
    • 可以绑定:全局函数/静态成员函数/无捕获的lambda
  • std::function 函数包装器

    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
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    
    int fun1(int a){
        return a;
    }
    template<typename T>
    T fun2(T a){
        return a + 2;
    }
    struct add{
      int operator()(int x){
          return x + 9;
      }
    };
    auto fun3 = [](int a) {return a * 2;};
    template <typename T>
    struct sub{
        T operator()(T a){
            return a - 8;
        }
    };
    template <typename T>
    struct foo2{
        static T foo(T a){
            return a * 4;
        }
    };
    struct foo1{
      static int foo(int a){
          return a * 3;
      }
    };
    struct foo3{
      int foo(int a){
          return a * a;
      }
    };
    template <typename T>
    struct foo4{
        T foo(T a){
            return a * 6;
        }
    };
    int main(int argc, char *argv[]){
        std::function<int(int)> callback;
    
        callback = fun1; 
        std::cout << callback(42) << std::endl;
        callback = fun2<int>;
        std::cout << callback(42) << std::endl;
        callback = add();
        std::cout << callback(42) << std::endl;
        callback = fun3;
        std::cout << callback(42) << std::endl;
        callback = sub<int>();
        std::cout << callback(42) << std::endl;
        callback = foo2<int>::foo; 
        std::cout << callback(42) << std::endl;
        callback = foo1::foo;
        std::cout << callback(42) << std::endl;
        foo3 test_foo1;
        callback = std::bind(&foo3::foo, test_foo1, std::placeholders::_1); 
        std::cout << callback(42) << std::endl;
        foo4<int> test_foo2;
        callback = std::bind(&foo4<int>::foo, test_foo2, std::placeholders::_1);
        std::cout << callback(42) << std::endl;
        return 0;
    }
    

    展开

    • std::function< return type(ParameterTypes…) > name;
    • 作用:实现回调机制
    • 优势:比函数指针更灵活,它可以包装所有可调用对象
    • 缺点:性能开销大于函数指针
  • 函数对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    class FuncObjType
    {
    public:
        void operator() ()
        {
            cout<<"Hello C++!"<<endl;
        }
    };
    FuncObjType funcObj;
    funcObj();
    

    展开

    • operator重载了调用运算符() 的类
    • 作用:相比于函数指针/function,可以存储状态

      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
      
      class Counter {
      private:
          int count;  // 存储状态
      public:
          Counter() : count(0) {}
              
          // 重载函数调用运算符
          int operator()() {
              return ++count;
          }
              
          // 可以重载不同版本的调用运算符
          int operator()(int increment) {
              count += increment;
              return count;
          }
      };
      
      int main() {
          Counter counter;
              
          std::cout << counter() << std::endl;  // 输出: 1
          std::cout << counter() << std::endl;  // 输出: 2
          std::cout << counter(5) << std::endl; // 输出: 7
          std::cout << counter() << std::endl;  // 输出: 8
              
          return 0;
      }
      

      展开

  • lambda/匿名表达式:

    • [capture list] (parameter list) -> return type { function body }
    • [capture list]:

      • lambda默认情况不能像普通函数一样使用外部变量,可以通过capture list / 参数形式传递进来
      • []:默认不捕获任何变量;
      • [ x ]:仅以值捕获x,其它变量不捕获;并非像传参那样调用时才拷贝,而是lambda被创建时拷贝,因此x之后的修改都不会影响它在lambda中的值
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      auto createLambda() {
        int x = 10;
        return [&x]() { return x; };
      }
      int main() {
        auto func = createLambda();
        func();
        return 0;
      }
      

      展开

      • [&x]:仅以引用捕获x,其它变量不捕获;会一直跟踪x的变化,但如果在引用捕获的变量生命周期已结束时调用lambda表达式, 就会出现垂悬引用问题
      • [=]:以值方式捕获所有外部变量;
      • [&]:以引用方式捕获所有外部变量;
      • [=, &x]:默认以值捕获所有变量,但是x是例外,通过引用捕获;
      • [&, x]:默认以引用捕获所有变量,但是x是例外,通过值捕获;
      • [this] 捕获当前类的 this 指针,允许访问当前类的成员
    • (parameter list)可以省略
    • -> return_type 可以省略,编译器会自动推断
    • auto lambda = ……
    • 函数指针 ptr = ……(仅对于空捕获)
    • 对于全局/文件/命名空间作用域/static修饰的可以直接访问,无需通过捕获列表捕获,但对于局部非静态/类非静态成员,需要捕获才能使用(注意需要在同一作用域下才能捕获到)
    • 在编译阶段,lambda表达式会自动转换为重载了()的匿名类

      • 捕获列表 -> 作为成员并列表初始化
      • 参数列表 -> ()重载中的参数列表
      • 函数体 -> ()重载中的函数体
    • 作用:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      
      class Iterator{
        public:
          Iterator(const vector<className>& v) : vec(v){}
          template<typename T>
          void ForAll(T&& callback){
            for(auto& i : vec){
              callback(i);
            }
          }
        private:
          vector<className> vec; 
      };
      int main(){
        Iterator manager(v);
        int count = 0;
        manager.forEach([&count](Student&) { ++count; });
      }
      

      展开

      • 定义在类内、函数内、参数列表中,非常灵活
      • 匿名防止名称冲突
  • std::bind 函数适配器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    double callableFunc (double x, double y) {return x/y;}
    auto NewCallable = std::bind (callableFunc, std::placeholders::_1,2);  
    std::cout << NewCallable (10) << '\n'; 
    class Base
    {
    public:
        void display_sum(int a1, int a2)
        {
            std::cout << a1 + a2 << '\n';
        }
    
        int m_data = 30;
    };
    int main() 
    {
        Base base;
        auto newiFunc = std::bind(&Base::display_sum, &base, 100, std::placeholders::_1);
        newiFunc(20); // should out put 120. 
    }
    

    展开

    • 接受一个可调用对象,生成一个新的可调用对象
    • 绑定普通函数:std::bind (callableFunc, ParameterList… / placeholders),普通函数做实参时,会隐式转换成函数指针
    • 绑定成员函数:std::bind (&className::callableFunc, &callableFunc,ParameterList… / placeholders)
    • 绑定带有引用的函数:要使用ref / cref ,防止拷贝
    • 结果可以用std::function保存
    • 作用:
      • 减少参数数量,调整参数顺序,以便适配所需的参数数量

回调

回调函数:

它有别于直接调用a,而通过调用b 间接调用a

实现方式

  • 函数指针,作为形参,仅可以传入普通函数,空捕获的lambda 表达式
  • std::function,作为形参,可以传入任何可调用对象
  • 函数对象类型作为形参类型,将函数对象传入

作用:

  • 解耦,增加灵活性,扩展性

使用场景

  • 一个行为调用,触发其他行为(例如事件)
本文由作者按照 CC BY 4.0 进行授权
本页总访问量