文章

c++(9·强制转换,回调)

强制转换

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

隐式 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
39
40
41
int num = 97;
char ch = static_cast<char>(num);

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


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 方法

展开

  • C++中内置类型之间的相互转换
    • 浮点 -> 整形, 整形 -> 浮点
    • 整形/浮点不同精度
    • 字符 -> 整形 , 整形 -> 字符
    • 有类型的指针,无类型指针,相互转换
  • 对有继承关系的类的指针或引用进行强制转换

dynamic_cast 动态转换

运行时转换,会进行运行时类型检查,专门用于处理继承体系间的转换,它是更安全的

多态类之间的向下转换,要求父类中至少有一个虚函数,并且父类指针实际指向子类对象,才会转换成功,如果转换失败返回nullptr指针

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

展开

const_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;
    }
    

    展开

    • ReturnType (*PointerName)(ParameterTypes…);
    • PointerName = FunctionName,将函数赋值给函数指针
    • PointerName() == (*PointerName)()调用
    • 作用:
      • 实现回调机制
  • 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重载了调用运算符() 的类
    • 作用:
      • 可以存储状态
        • 例子:

          • 学校要求你实现以下一些需求:
            1. 统计学生人数
            2. 把所有学生的年龄+1
            3. 删除学生中成绩不及格的人
          • 可以一个共有的遍历函数,和实现4个函数,它们都依赖于共有函数,这符合回调机制,但是发现用函数指针实现时无法保存数据,比如统计学生个数,当调用遍历时,无法增加count,状态的保存可以用函数对象实现
          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
          
          class Iterator{
            public:
              Iterator(const vector<className>& v) : vec(v){}
              void ForAll(){
                for(auto& i : vec){
                  Func(i);
                }
              }
              virtual void Func(className obj) = 0;
            private:
              vector<className> vec; 
          };
          class Count : public Iterator{
            public:
              Count(const vector<ClassName>& v) : Iterator(v) {}
              void Func(className obj) override{
                ……
              }
            private:
              int count;
          }
          class AddAge : public Iterator{
              ……
          }
          //……其他需求
          

          展开

          • 如上所示,4个类存储状态,在调用各自的ForAll内部,调用各自的Func实现,但是这样会造成类爆炸
  • 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; });
      }
      

      展开

      • 捕获局部变量, 代替函数对象的状态保存功能,简化代码,防止类爆炸
      • 在调用时直接定义,作为STL的谓词,简化函数定义和调用语法,匿名防止名称冲突
  • 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 进行授权
本页总访问量