本书的笔记是在看 《Essential C++》产生的,主要讲述了tsl的标准使用、继承、标准类的样式,我也学习了一下智能指针,明白了智能指针下C++也可以享受ARC内存管理的丝滑。

思考记录

我有点明白为啥说学好C++对编程是实打实的帮助了,学iOS OC的时候,很多时候被封装好的接口其实是抹掉了背后所蕴含的知识点。

比如iOS中取对象,无论是静态存储区、堆、栈,都能用 空格 来取出来,C++针对这3种情况取值的符号都不一样。

我的问题

待解决问题

(一)aff 的C++接口如何回调原生的?

(二)aff 是怎么被链接进工程的?支持patch吗?开发的流程是怎样的?

已解决问题

(一)C++开发中建议使用函数指针吗? 使用函数指针会不会带来什么问题?

在C++开发中,函数指针可以用于实现灵活的函数调用、回调函数和插件系统等。然而,函数指针并非总是最佳选择,特别是在C++中,有更现代、安全和易于使用的替代方案,如函数对象(functors)和C++11引入的lambda表达式。

以下是使用函数指针可能带来的一些问题:

可读性:函数指针的语法可能令人困惑,特别是对于那些不熟悉C和C++的开发人员。这可能导致代码难以理解和维护。

类型安全:函数指针不提供编译时的类型检查。如果函数指针类型与实际函数类型不匹配,可能导致未定义行为和运行时错误。

封装和扩展性:函数指针仅限于普通函数,而不能直接访问类的成员函数。虽然可以使用成员函数指针,但它们的语法更加复杂,并且需要使用std::bind或其他技术来捕获类实例。

在C++中,有更好的替代方案,如函数对象(functors)和lambda表达式。这些替代方案提供了更好的类型安全、封装和可读性。例如,C++11引入的std::function和std::bind可以用于存储和调用可调用对象(如普通函数、成员函数、函数对象和lambda表达式)。这使得C++中的回调和事件处理更加灵活和安全。

总之,虽然函数指针在C++开发中仍然有一定的用途,但在许多情况下,使用函数对象和lambda表达式可能是更好的选择。这些替代方案提供了更好的类型安全、封装和可读性,使得代码更容易理解和维护。

(二)给我介绍一下 lambda表达式 怎么用的

lambda表达式 作用: 创建匿名函数。

C++11引入了lambda表达式,它是一种创建匿名函数对象的简洁语法。lambda表达式允许您在需要函数对象的地方快速定义一个简单的函数,而无需显式地声明一个函数或类。lambda表达式在很多场景下非常有用,如算法、回调函数和事件处理等。

以下是lambda表达式的基本语法:

[capture_list](parameter_list) -> return_type {
    // 函数体
}   

(三)给我介绍一下 函数对象 ,以及什么时候适合用 函数对象,什么时候适合用 lambda表达式? 什么时候适合用 函数指针?

函数对象(functors),也称为仿函数,是C++中一种实现泛型编程的技术。函数对象是一个重载了函数调用运算符operator()的类或结构的对象。这意味着您可以像调用普通函数一样调用函数对象。函数对象通常用于作为参数传递给其他函数或算法,或者用于实现回调和事件处理等。

以下是一个简单的函数对象示例:

#include <iostream>
#include <vector>
#include <algorithm>

// 函数对象:计算平方
struct Square {
    void operator()(int n) const {
        std::cout << n * n << std::endl;
    }
};

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // 创建函数对象实例
    Square square;

    // 使用函数对象
    square(4); // 输出:16

    // 将函数对象作为参数传递给算法
    std::for_each(numbers.begin(), numbers.end(), square);

    return 0;
}   

与函数对象相比,lambda表达式是一种更简洁的创建匿名函数对象的方式。lambda表达式允许您在需要函数对象的地方快速定义一个简单的函数,而无需显式地声明一个函数或类。

以下是一个使用lambda表达式的示例:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // Lambda表达式:计算每个数字的平方
    std::for_each(numbers.begin(), numbers.end(), [](int n) {
        std::cout << n * n << std::endl;
    });

    return 0;
}   

在选择函数对象、lambda表达式还是函数指针时,以下是一些建议:

  1. 使用函数对象:当您需要创建具有状态的函数对象或需要重用的可配置函数时,可以使用函数对象。函数对象可以携带状态,并可以在不同的函数调用之间共享。

  2. 使用lambda表达式:当您需要快速定义一个简单的、无状态的函数时,可以使用lambda表达式。lambda表达式提供了一种简洁的方式来创建匿名函数对象,特别是在算法、回调函数和事件处理等场景中。

  3. 使用函数指针:当您需要与C语言代码或库互操作时,可以使用函数指针。函数指针允许您直接访问函数的地址,但它们不具备类型安全性,并且不能携带状态。

总之,在C++中,函数对象、lambda表达式和函数指针都可以用于实现泛型编程、回调函数和事件处理等。在实际开发过程中,您可以根据需要、可读性和性能要求来选择合适的方法。

(四)上面解释函数对象时,说到了 需要创建具有状态的函数对象或需要重用的可配置函数时,可以使用函数对象。这是什么意思? 什么叫具有状态的函数对象?

具有状态的函数对象是指函数对象可以携带状态信息(即数据成员),这些状态信息可以在函数对象的多次调用之间保持和共享。这与普通函数和lambda表达式(无状态)不同,它们通常无法在多次调用之间保留状态。

函数对象可以在类或结构中定义数据成员,这些数据成员可以在函数调用运算符operator()的实现中使用。这样,您可以根据需要在多次调用之间共享和修改这些数据成员。

以下是一个具有状态的函数对象示例:

#include <iostream>
#include <vector>
#include <algorithm>

// 函数对象:计算累积和
struct Accumulator {
    int sum;

    Accumulator() : sum(0) {}

    void operator()(int n) {
        sum += n;
        std::cout << "Current sum: " << sum << std::endl;
    }
};

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // 创建具有状态的函数对象实例
    Accumulator accumulator;

    // 使用具有状态的函数对象
    std::for_each(numbers.begin(), numbers.end(), accumulator);

    return 0;
}   

在这个示例中,我们定义了一个名为Accumulator的结构,它具有一个名为sum的数据成员。Accumulator的函数调用运算符operator()实现中,我们使用sum数据成员来计算累积和。这使得Accumulator在多次调用之间保留和共享其状态(累积和)。

具有状态的函数对象在很多场景下非常有用,如需要在多次调用之间共享数据、实现可配置的函数行为等。函数对象提供了一种灵活、可重用的方式来实现具有状态的函数。

最简单的一个应用是:函数对象可以统计一个函数被各个业务调用了多少次。

(五)iOS 中如何想实现函数对象,可以用OC怎么实现?

在Objective-C中,要实现具有状态的函数对象,您可以创建一个自定义类,并在其中定义一个带有状态的方法。这个方法可以访问类的实例变量(即状态),从而在多次调用之间保持和共享状态。

以下是一个Objective-C中具有状态的函数对象示例:

#import <Foundation/Foundation.h>

// 自定义类:Accumulator
@interface Accumulator : NSObject
@property (nonatomic) int sum;

- (void)accumulate:(int)n;
@end

@implementation Accumulator

- (instancetype)init {
    self = [super init];
    if (self) {
        _sum = 0;
    }
    return self;
}

- (void)accumulate:(int)n {
    self.sum += n;
    NSLog(@"Current sum: %d", self.sum);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray *numbers = @[@1, @2, @3, @4, @5];

        // 创建具有状态的函数对象实例
        Accumulator *accumulator = [[Accumulator alloc] init];

        // 使用具有状态的函数对象
        for (NSNumber *number in numbers) {
            [accumulator accumulate:[number intValue]];
        }
    }
    return 0;
}   

(六)C++中如何对 vector 进行深拷贝?

在C++中,对std::vector进行深拷贝的方法取决于向量中存储的元素类型。对于简单的值类型(如int、double、std::string等),std::vector的默认拷贝构造函数将自动执行深拷贝。但是,如果std::vector包含指针或其他需要手动管理的资源,您需要确保正确地执行深拷贝。

以下是针对不同元素类型的深拷贝示例:

  1. 对于值类型(如int、double、std::string等),直接使用拷贝构造函数:

    std::vector original_numbers = {1, 2, 3, 4, 5};
    std::vector copied_numbers(original_numbers); // 使用拷贝构造函数进行深拷贝

  2. 对于指针类型或其他需要手动管理的资源,您需要遍历原始向量并手动创建新的对象:

    int main() {

     // 创建原始向量并添加MyClass对象的shared_ptr
     std::vector<std::shared_ptr<MyClass>> original_objects;
     original_objects.push_back(std::make_shared<MyClass>(1));
     original_objects.push_back(std::make_shared<MyClass>(2));
     original_objects.push_back(std::make_shared<MyClass>(3));
    
     // 手动执行深拷贝
     std::vector<std::shared_ptr<MyClass>> copied_objects;
     for (const auto& obj : original_objects) {
         copied_objects.push_back(std::make_shared<MyClass>(obj->value()));
     }
    
     // 使用原始向量
     for (const auto& obj : original_objects) {
         std::cout << "Original: " << obj->value() << std::endl;
     }
    
     // 使用拷贝向量
     for (const auto& obj : copied_objects) {
         std::cout << "Copied: " << obj->value() << std::endl;
     }
    
     return 0;
    

    }

    在这个示例中,我们创建了一个包含MyClass对象指针的std::vector。然后,我们遍历原始向量并手动创建新的MyClass对象,以确保正确地执行深拷贝。

请注意,手动管理指针和内存可能导致错误和内存泄漏。为了简化内存管理,您可以考虑使用智能指针(如std::shared_ptr或std::unique_ptr),它们可以自动管理对象的生命周期。在这种情况下,您可以直接使用std::vector的拷贝构造函数来执行深拷贝。

for (const auto& obj : original_objects) {
    copied_objects.push_back(std::make_shared<MyClass>(obj->value()));
}

这句代码是什么意思? 为什么要加 const 和 auto& ?

这段代码是一个基于范围的for循环(C++11引入),用于遍历original_objects向量中的所有元素。original_objects向量包含std::shared_ptr类型的元素。对于original_objects中的每个元素,我们创建一个新的MyClass对象,使用std::make_shared将其包装在std::shared_ptr中,并将其添加到copied_objects向量中。这样,我们就实现了original_objects向量的深拷贝。

关于const auto& obj:

auto:auto关键字用于自动推导变量的类型。在这个示例中,编译器根据original_objects向量中的元素类型自动推导出obj的类型(即std::shared_ptr)。

const:const关键字表示obj是一个常量引用,不能用于修改original_objects向量中的元素。这有助于确保我们不会意外地修改原始向量。

&:&符号表示我们使用引用来遍历向量中的元素,而不是复制它们。这可以提高遍历性能,特别是对于大型对象。因为我们不需要修改original_objects向量中的元素,所以我们使用const关键字将引用声明为常量引用。

总之,const auto& obj表示我们以常量引用的形式遍历original_objects向量中的元素,以实现深拷贝。这种方法提供了类型安全、性能优势和代码简洁性。

(七)* & 的关系

  1. C++中的*和&的作用分别是什么
  • : 作为指针解引用运算符:如果p是一个指针,那么p就是取指针p所指向的值。例如,如果int p = &a;,那么*p就是变量a的值。

& : 作为取地址运算符:&a 就是取变量a的地址。例如,int a = 10;,那么&a就是变量a的内存地址。

此外,* 和 & 在C++中还有其他的用途。例如,* 可以作为创建指针变量的标志,如int* p;表示创建了一个指向整型的指针p。而 & 可以作为创建引用变量的标志,如int& r = a;表示创建了一个引用变量r,它引用了变量a。

int a = 32;
int* p = &a;
  1. 指针变量和引用变量区别是什么

指针变量:指针是一个变量,它存储的是另一个变量的内存地址。指针可以被重新赋值,指向另一个变量的地址。

引用变量:引用是一个别名,它为某个变量提供了另一个名字,实际上引用和它所引用的变量共享相同的内存地址。

  1. 使用引用变量会导致引用计数+1吗,指针变量会吗?

在C++中,引用变量和指针变量本身并不会导致引用计数的增加。引用计数通常是与智能指针(如std::shared_ptr)相关的概念,而不是与普通的引用或指针相关。

(六)C++中的vector可以装不同类型的对象吗

C++中,std::vector是一个模板容器,它只能存储相同类型的元素。换句话说,您不能在一个std::vector中存储不同类型的对象。

但,你可以使用一些方法来实现类似功能:

  1. 使用 std::variant(C++ 17及更高版本支持)std:variant允许在一个类型中存储多种不同类型的值。你可以创建一个std::vector,其元素类型为std::varinat,从而实现在一个std::vector中存储不同类型的对象。

    #include
    #include
    #include

    int main() {

     using IntOrFloat = std::varint<int,float>;
     std::vector<IntOrFloat> mixedVector = {42,3.14f,7};
    
     for (const audo &value : mixedVector) {
         std::visit([](const auto &v){std::cout << v << std::endl;}, value);
     }
    

    }

  2. 使用基类指针:如果您的不同类型的对象都是某个基类的派生类,您可以创建一个std::vector,其元素类型为基类指针。这样,您可以在std::vector中存储指向不同类型派生类对象的指针。

    #include
    #include
    #include

    class Base {
    public:

     virtual void print() const = 0;
    

    };

    class DerivedA : public Base {
    public:

     void print() const override {
         std::cout << "DerivedA" << std::endl;
     }
    

    };

    class DerivedB : public Base {
    public:

     void print() const override {
         std::cout << "DerivedB" << std::endl;
     }
    

    };

    int main() {

     std::vector<std::unique_ptr<Base>> mixedVector;
     mixedVector.push_back(std::make_unique<DerivedA>());
     mixedVector.push_back(std::make_unique<DerivedB>());
    
     for (const auto &value : mixedVector) {
         value->print();
     }
    
     return 0;
    

    }

using关键字用于定义类型别名。

第二章 面向过程的编程风格

(一)函数指针

函数指针是一种特殊类型的指针,它指向一个函数而不是一个数据对象。通过函数指针,您可以间接地调用函数,将函数作为参数传递给其他函数,或者在运行时选择要调用的函数。函数指针在C++和C(包括Objective-C)中都有广泛的应用。

#include <iostream>

// 函数声明
int add(int a, int b) {
    return a + b;
}

int main() {
    // 定义一个函数指针,指向add函数
    int (*func_ptr)(int, int) = &add;

    // 通过函数指针调用add函数
    int sum = func_ptr(1, 2);

    std::cout << "The sum is: " << sum << std::endl;

    return 0;
}   

第三章 泛型编程风格

(一)STL 的内容

STL 主要由两种组件构成:

  • 容器:包括vector、list、set、map等
  • 操作容器的泛型算法:包括 find()、sort()、replace()、merge()等等

(二)什么时候适合用vector ? 什么时候应该用 list?

在C++中,std::vector和std::list是两种不同的序列容器,它们在内存分配、数据访问和操作性能方面有所不同。以下是何时使用std::vector和std::list的一些建议:

使用std::vector的场景:

  1. 随机访问:std::vector提供了快速的随机访问,您可以使用下标运算符[]在常数时间内访问任意位置的元素。如果您的应用需要频繁地访问元素,std::vector可能是一个更好的选择。

  2. 空间效率:std::vector在内存中以连续的方式存储元素,这使得它在空间效率方面具有优势。在大多数情况下,std::vector比std::list占用更少的内存。

  3. 缓存友好:由于std::vector中的元素在内存中是连续存储的,它更适合利用CPU缓存。这可能在某些情况下提高性能。

使用std::list的场景:

  1. 插入和删除:std::list提供了在任何位置快速插入和删除元素的能力。如果您的应用需要频繁地在中间位置插入或删除元素,std::list可能是一个更好的选择。

  2. 不需要随机访问:std::list不支持下标运算符[],因此随机访问元素可能较慢。如果您的应用主要需要顺序访问元素(例如,使用迭代器遍历元素),std::list可能是一个合适的选择。

  3. 大型元素:对于包含大型元素的容器,std::list在插入和删除元素时可能具有更好的性能,因为它不需要移动元素。然而,请注意,在这种情况下,使用指针或智能指针存储元素并使用std::vector可能是一个更好的解决方案。

总之,选择使用std::vector还是std::list取决于您的具体需求,例如数据访问模式、操作性能和空间效率。在实际开发过程中,您可以根据这些因素来选择合适的容器。通常情况下,std::vector作为默认容器是一个不错的选择,因为它在许多方面具有优势。然而,在某些特定场景下,std::list可能更为合适。

(三)介绍一下 C++ 中的 iterator ,常用方法,以及和iOS的对比

在C++中,迭代器(iterator)是一种通用的、标准化的方式来访问和遍历容器(如std::vector、std::list、std::map等)中的元素。迭代器提供了一种类似于指针的抽象,允许您在不同类型的容器之间使用相同的访问和遍历模式。

以下是C++迭代器的一些常用方法:

  1. begin():返回一个指向容器中第一个元素的迭代器。

  2. end():返回一个指向容器中最后一个元素之后位置的迭代器。

  3. ++和–:递增或递减迭代器,用于访问容器中的下一个或上一个元素。

  4. *(解引用):访问迭代器当前指向的元素。

  5. ==和!=:比较两个迭代器是否相等或不等。通常用于检查迭代器是否到达容器的end()。

以下是一个使用迭代器遍历std::vector的示例:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // 使用迭代器遍历容器
    for (std::vector<int>::iterator it = numbers.begin(); it != numbers.end(); ++it) {
        std::cout << *it << std::endl;
    }

    return 0;
}   

(四)iOS 中有 enumerate 遍历方法,C++中对应的便捷遍历方法吗?

在C++11及更高版本中,您可以使用基于范围的for循环(range-based for loop)来方便地遍历容器(如std::vector、std::list、std::map等)。基于范围的for循环提供了一种简洁、高效的方式来遍历容器中的元素,类似于iOS(Objective-C)中的enumerate方法或快速枚举。

以下是一个使用基于范围的for循环遍历std::vector的示例:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // 使用基于范围的for循环遍历容器
    for (int number : numbers) {
        std::cout << number << std::endl;
    }

    return 0;
}   

在这个示例中,我们使用基于范围的for循环遍历numbers容器中的所有元素。这种遍历方法非常简洁,不需要显式地创建迭代器或检查边界条件。

总之,C++中的基于范围的for循环和iOS中的enumerate方法或快速枚举都是用于方便地遍历容器中元素的方法。在实际开发过程中,您可以根据所使用的编程语言和数据结构选择合适的遍历方法。

第四章 基于对象的编程风格

(一)class 定义式的骨干

class Stack {
    public:
        // ... public interface

    private:
        // ... private 的实现部分
}   

(二)C++中我新创建了一个类,我想写一个静态方法和一个成员函数,代码可以怎么写?

在C++中,您可以在类定义中声明静态方法和成员函数。静态方法使用static关键字声明,它们属于类本身,而不是类的实例。成员函数是类的实例方法,它们可以访问和修改类的实例变量。以下是一个包含静态方法和成员函数的C++类示例:

#include <iostream>

class MyClass {
public:
    // 静态方法
    static void static_method() {
        std::cout << "This is a static method." << std::endl;
    }

    // 成员函数
    void member_function() {
        std::cout << "This is a member function." << std::endl;
    }
};

int main() {
    // 调用静态方法
    MyClass::static_method();

    // 创建类的实例
    MyClass obj;

    // 调用成员函数
    obj.member_function();

    return 0;
}   

:: 属于静静的,这点记住,没错!

(三)析构函数

destructor 是C++中的析构函数,destructor 的名称有严格规定:class名称再加上 ~ 符号,它绝对不会返回值,也不会有任何参数。

(四)如果我在iOS项目工程中使用C++,算是在ARC环境下吗? 还需要我主动管理内存吗?

在iOS项目中,如果您将C++代码与Objective-C代码混合使用(即使用Objective-C++),ARC(Automatic Reference Counting)仅适用于Objective-C部分的代码。C++部分的代码不受ARC管理,您需要手动管理C++对象的内存。

对于Objective-C部分,您可以继续依赖ARC自动管理对象的内存。这意味着您不需要手动调用retain、release或autorelease,ARC会自动为您处理引用计数。

对于C++部分,您需要手动管理内存。这意味着您需要使用new和delete(或new[]和delete[])来分配和释放动态内存。为了简化内存管理,您可以考虑使用智能指针(如std::unique_ptr、std::shared_ptr和std::weak_ptr),它们可以自动管理对象的生命周期。

以下是一个在iOS项目中混合使用Objective-C和C++的示例:

  1. 首先,将Objective-C源文件的扩展名从.m更改为.mm,以便使用Objective-C++。

  2. 接下来,您可以在.mm文件中混合使用Objective-C和C++代码。例如:

#import <Foundation/Foundation.h>
#include <iostream>
#include <memory>

// C++类
class MyClass {
public:
    MyClass() {
        std::cout << "MyClass object created." << std::endl;
    }

    ~MyClass() {
        std::cout << "MyClass object destroyed." << std::endl;
    }
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 使用Objective-C对象(受ARC管理)
        NSString *str = [NSString stringWithFormat:@"Hello, World!"];
        NSLog(@"%@", str);

        // 使用C++对象(手动管理内存)
        MyClass *obj = new MyClass();
        delete obj;

        // 或者使用智能指针自动管理C++对象的内存
        std::unique_ptr<MyClass> smart_obj(new MyClass());
    }
    return 0;
}   

在这个示例中,我们在Objective-C项目中混合使用了Objective-C和C++代码。Objective-C部分的代码由ARC自动管理,而C++部分的代码需要手动管理内存。我们使用new和delete分配和释放C++对象的内存,还演示了如何使用智能指针自动管理内存。

总之,在iOS项目中使用C++时,ARC仅适用于Objective-C部分的代码。对于C++部分,您需要手动管理内存,或者使用智能指针来简化内存管理。

(五)帮我介绍一下C++中的智能指针,给个例子,并和iOS进行类比。有了智能指针,我就可以像iOS 的 arc环境一样不用管理内存了吗?如果还要的话给我个例子

C++中的智能指针是一种自动管理对象生命周期的资源管理类。它们在某种程度上类似于iOS中的ARC(Automatic Reference Counting),可以帮助您简化内存管理。C++11引入了三种智能指针:std::unique_ptr、std::shared_ptr和std::weak_ptr。

  1. std::unique_ptr:unique_ptr是一种独占所有权的智能指针,它确保同一时间只有一个智能指针可以拥有对象。当unique_ptr离开作用域或被销毁时,它将自动删除所拥有的对象。(开发过程中基本不使用unique_ptr)

  2. std::shared_ptr:shared_ptr是一种共享所有权的智能指针,允许多个shared_ptr指向同一个对象。shared_ptr使用引用计数来跟踪有多少个智能指针共享同一个对象。当最后一个shared_ptr离开作用域或被销毁时,它将自动删除所拥有的对象。

  3. std::weak_ptr:weak_ptr是一种弱引用智能指针,它不会更改对象的引用计数。weak_ptr通常与shared_ptr一起使用,以避免循环引用问题。

以下是一个C++智能指针的示例:

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() {
        std::cout << "MyClass object created." << std::endl;
    }

    ~MyClass() {
        std::cout << "MyClass object destroyed." << std::endl;
    }
};

int main() {
    // 使用std::unique_ptr
    {
        std::unique_ptr<MyClass> unique_obj(new MyClass());
    } // unique_obj离开作用域,对象被自动删除

    // 使用std::shared_ptr
    {
        std::shared_ptr<MyClass> shared_obj1(new MyClass());
        {
            std::shared_ptr<MyClass> shared_obj2 = shared_obj1;
        } // shared_obj2离开作用域,引用计数减1
    } // shared_obj1离开作用域,引用计数为0,对象被自动删除

    return 0;
}   

在这个示例中,我们使用std::unique_ptr和std::shared_ptr来自动管理MyClass对象的内存。当智能指针离开作用域或被销毁时,它们将自动删除所拥有的对象。

在iOS(Objective-C)中,ARC类似于C++中的std::shared_ptr。ARC使用引用计数来跟踪有多少个指针引用同一个对象。当对象的引用计数为零时,ARC将自动删除对象。

使用智能指针确实可以简化C++中的内存管理,使其类似于iOS的ARC环境。但请注意,智能指针仅适用于C++对象,而不适用于其他资源(如文件、网络连接等)。在这种情况下,您仍然需要手动管理这些资源。为了简化资源管理,您可以考虑使用RAII(Resource Acquisition Is Initialization)技术,例如使用C++11中的std::unique_lock、std::shared_lock或std::lock_guard来自动管理锁。

(六)哪些业务对象适合使用 unique_ptr?给我用iOS的代码示例说明一下

unique_ptr 适用场景一个词: 独占 !

  1. std::unique_ptr是C++中一种独占所有权的智能指针,适用于在其生命周期内只有一个拥有者的对象。以下是一些适合使用std::unique_ptr的场景和业务对象:

对象所有权在运行时转移:当一个对象在运行时从一个拥有者转移到另一个拥有者时,可以使用std::unique_ptr来确保资源管理的正确性。例如,在工厂模式中,工厂函数创建一个对象并将其所有权转移给调用者。

std::unique_ptr<MyClass> create_object() {
    return std::make_unique<MyClass>();
}

int main() {
    std::unique_ptr<MyClass> obj = create_object();
    // obj现在拥有从create_object()返回的对象
    return 0;
}   
  1. 资源管理:对于需要自动管理其生命周期的资源(如文件、数据库连接、互斥锁等),可以使用std::unique_ptr与自定义析构器来确保资源在离开作用域时正确释放。

    // 自定义析构器,用于关闭文件
    struct FileCloser {

     void operator()(FILE* file) const {
         if (file) {
             fclose(file);
         }
     }
    

    };

    int main() {

     std::unique_ptr<FILE, FileCloser> file(fopen("example.txt", "r"));
     // 使用文件...
    

    } // 文件在离开作用域时自动关闭

  2. 容器元素:当容器中的元素具有独占所有权时,可以使用std::unique_ptr。这有助于确保容器在销毁或清除元素时自动释放资源。

    #include

    int main() {

     std::vector<std::unique_ptr<MyClass>> container;
     container.push_back(std::make_unique<MyClass>());
     container.push_back(std::make_unique<MyClass>());
     // 使用容器和元素...
    

    } // 容器在离开作用域时自动销毁所有元素

  3. 树或图数据结构:当实现具有独占所有权的树或图数据结构时,可以使用std::unique_ptr。这样可以确保节点在删除或重新组织时自动释放资源。

    struct TreeNode {

     int value;
     std::unique_ptr<TreeNode> left;
     std::unique_ptr<TreeNode> right;
    

    };

第五章 基于对象的编程风格

说到面向对象就不得不提 继承 和 多态,与此同时在C++中不得不提的就是 虚函数。

(一)帮我介绍一下C++中的虚函数,给个例子,并和iOS进行类比

在C++中,虚函数(virtual function)是一种支持动态多态性的特性。虚函数允许派生类覆盖(override)基类中的成员函数,从而在运行时根据对象的实际类型调用相应的函数。要将一个成员函数声明为虚函数,您需要在类定义中使用virtual关键字。

以下是一个C++虚函数的示例:

#include <iostream>

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

class Derived : public Base {
public:
    void print() override {
        std::cout << "Derived class print." << std::endl;
    }
};

int main() {
    Base* base_ptr = new Base();
    Base* derived_ptr = new Derived();

    // 调用Base类的print()函数
    base_ptr->print(); // 输出:Base class print.

    // 调用Derived类的print()函数(通过基类指针)
    derived_ptr->print(); // 输出:Derived class print.

    delete base_ptr;
    delete derived_ptr;

    return 0;
}   

OC是默认实现了override的功能。

(二)帮我介绍一下C++中的多态,给个例子,并和iOS进行类比

在C++中,多态是一种允许派生类继承和覆盖基类的成员函数的特性。这使得您可以使用基类指针或引用来操作派生类对象,并在运行时根据对象的实际类型调用相应的函数。多态在C++中通过虚函数(virtual function)实现。

#include <iostream>

class Animal {
public:
    virtual void makeSound() const {
        std::cout << "The animal makes a sound." << std::endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() const override {
        std::cout << "The dog barks." << std::endl;
    }
};

class Cat : public Animal {
public:
    void makeSound() const override {
        std::cout << "The cat meows." << std::endl;
    }
};

void playSound(const Animal& animal) {
    animal.makeSound();
}

int main() {
    Dog dog;
    Cat cat;
    playSound(dog); // 输出 "The dog barks."
    playSound(cat); // 输出 "The cat meows."
    return 0;
}   

第七章 异常处理

异常处理机制有两个主要成分:

  • 异常的识别与发出
  • 异常的处理方式

(一)帮我介绍一下C++中的异常处理,给个例子,并和iOS进行类比

在C++中,异常处理是一种处理错误和异常情况的机制。异常处理允许您将错误检测和错误处理代码与正常执行代码分离。当程序遇到错误时,它可以抛出一个异常,然后在调用堆栈中查找合适的异常处理程序(catch block)来处理该异常。

C++异常处理使用try、catch和throw关键字。以下是一个C++异常处理的示例:

#include <iostream>
#include <stdexcept>

double divide(double a, double b) {
    if (b == 0) {
        throw std::runtime_error("Division by zero");
    }
    return a / b;
}

int main() {
    double x = 10, y = 0;

    try {
        // 尝试执行可能抛出异常的代码
        double result = divide(x, y);
        std::cout << "The result is: " << result << std::endl;
    }
    catch (const std::runtime_error& e) {
        // 处理异常
        std::cout << "Error: " << e.what() << std::endl;
    }

    return 0;
}   

在这个示例中,我们定义了一个divide函数,它接受两个参数并返回它们的商。如果除数为零,我们抛出一个std::runtime_error异常。在main函数中,我们使用try和catch关键字来捕获和处理异常。

(二)平时开发过程中使用 try catch throw的地方是不是也不多?

结论:是的,没必要都用上。

在实际开发过程中,try、catch和throw的使用频率取决于编程语言、编程范式和项目需求。在某些编程语言(如C++和Java)中,异常处理被广泛用于处理错误和异常情况。然而,在其他编程语言(如C和Objective-C)中,通常使用错误码和错误对象来处理错误,而不是依赖于异常处理。

在C++中,您可以使用try、catch和throw来处理可能导致程序异常的情况,例如:

  • 文件操作(如打开、读取或写入文件)
  • 内存分配(如动态分配内存时发生内存不足)
  • 网络操作(如连接、发送或接收数据时发生错误)
  • 用户输入验证(如检查用户输入是否有效或满足约束)

然而,在某些情况下,过度使用异常处理可能导致代码变得复杂且难以维护。异常处理可能会导致控制流程难以跟踪,因为它可以跳过函数调用堆栈中的多个层次。此外,异常处理可能会影响性能,因为它需要额外的开销来设置和处理异常。

在实际开发过程中,您应该权衡使用异常处理的优缺点,并根据项目需求和编程范式来选择合适的错误处理策略。以下是一些关于何时使用异常处理的一些建议:

当错误是非常罕见的或意外的情况时,可以使用异常处理。这些错误通常需要立即处理,并且可能导致程序无法继续运行。
当错误需要跨越多个函数或模块进行处理时,可以使用异常处理。这可以简化错误传播和处理代码。
当您需要确保在发生错误时清理资源(如内存、文件或网络连接)时,可以使用异常处理。这有助于确保资源在异常情况下被正确释放。
总之,在C++开发过程中,try、catch和throw的使用频率取决于项目需求和编程范式。在某些场景下,异常处理是一种强大的错误处理技术。然而,在其他情况下,您可能需要权衡异常处理的优缺点,并选择其他错误处理策略(如错误码或错误对象)。