C++_05

1、友元

1.1 什么是友元

        在C++中,友元(friend)关键字用于给特定的外部函数或类访问某类的私有(private)和保护(protected)成员的权限。友元关系不是相互的,也不是可继承的。这意味着被声明为友元的函数或类可以访问原始类的私有和保护成员,但原始类不能访问友元的私有成员,除非它们也被声明为友元。

        友元主要有三种类型:

         (1)友元函数:一个普通函数(不是类的成员函数)可以被声明为一个类的友元函数。它可以访问该类的所有成员(公有、保护和私有)。

        (2)友元类:一个类可以被声明为另一个类的友元。这意味着友元类的所有成员函数都可以访问原始类的保护和私有成员。

        (3)友元成员函数:一个类的成员函数可以被声明为另一个类的友元。这意味着该成员函数可以访问另一个类的保护和私 有成员。

        为什么要使用友元:

        友元提供了一种机制,允许某些外部函数或类直接访问类的私有或保护成员。这在某些情况下非常有用,例如:实现运算符重载,如重载输入输出运算符( << >> );允许两个不同类的对象之间进行密切的交互;当类设计需要一种安全控制的方式来允许非成员函数或类访问私有成员时。

1.2 友元函数

        在下面这个例子中, showValue 函数被声明为 MyClass 的友元函数,它可以访问 MyClass 的私有成员 value

class MyClass {
private:
    int value;

public:
    MyClass(int v) : value(v) {}

    // 声明友元函数
    friend void showValue(MyClass& obj);
};

// 友元函数的定义
void showValue(MyClass& obj) {
    std::cout << "Value of MyClass: " << obj.value << std::endl;
}

int main() {
    MyClass obj(42);
    showValue(obj); // 可以访问 MyClass 的私有成员

    return 0;
}

        在C++中,重载输入输出运算符( << >> )通常需要使用友元函数。这是因为输入输出运算符通常需要访问类的私有成员,同时又需要符合标准库中流对象(如 std::ostream std::istream )的用法。

        下面是一个如何使用友元函数来重载 << 和 >> 运算符的例子:假设我们有一个 Point 类,表示二维空间中的一个点,我们想要重载 << >> 运算符以输出和输入点的坐标。

#include <iostream>

class Point {
private:
    int x, y;

public:
    Point() : x(0), y(0) {}
    Point(int x, int y) : x(x), y(y) {}

    // 重载 << 运算符(输出)
    friend std::ostream& operator<<(std::ostream& os, const Point& point) {
        os << "(" << point.x << ", " << point.y << ")";

        return os;
    }

    // 重载 >> 运算符(输入)
    friend std::istream& operator>>(std::istream& is, Point& point) {
        is >> point.x >> point.y;

        return is;
    }
};

int main() {
    Point p1(1, 2);
    std::cout << "Point p1: " << p1 << std::endl; // 输出点 p1

    Point p2;
    std::cout << "Enter coordinates for p2 (x y): ";
    std::cin >> p2; // 输入点 p2
    std::cout << "Point p2: " << p2 << std::endl; // 输出点 p2

    return 0;
}

        在这个例子中:<< 运算符用于将 Point 对象的内容输出到输出流(如 std::cout );>> 运算符用于从输入流(如 std::cin )中读取值到 Point 对象;这两个运算符都被声明为 Point 类的友元函数,以便它们可以访问类的私有成员变量 x y

        注:虽然友元提供了强大的功能,但也应谨慎使用:过度使用友元可能会破坏封装,使得代码难以维护和理解;保持友元的数量最小,仅在确实需要时使用,以维持良好的封装性。

1.3 友元类

        在C++中,一个类可以被声明为另一个类的友元类(friend class)。当一个类被声明为另一个类的友元类时,它可以访问后者的所有成员,包括私有(private)和保护(protected)成员。

        友元类是一种强大的特性,它允许在保持封装性的同时,提供对类内部的深入访问。然而,由于它允许对另一个类的内部细节进行直接操作,因此应谨慎使用,以免破坏封装性。

        假设我们有两个类 ClassA ClassB 。我们可以使 ClassB 成为 ClassA 的友元类,这样 ClassB就可以访问 ClassA 的所有成员,包括私有成员。

class ClassA {
private:
    int value;

public:
    ClassA(int v) : value(v) {}
    // 声明 ClassB 为友元类
    friend class ClassB;
};

class ClassB {
public:
    void showValue(ClassA& a) {
        // 可以访问 ClassA 的私有成员
        std::cout << "Value of ClassA: " << a.value << std::endl;
    }
};

int main() {
    ClassA a(100);
    ClassB b;
    b.showValue(a); // 输出 "Value of ClassA: 100"

    return 0;
}

        在这个示例中, ClassB 能够访问 ClassA 的私有成员 value ,因为它被声明为 ClassA 的友元类。

        注:(1)谨慎使用:友元类应该谨慎使用,因为它们可能会破坏对象的封装性和隐藏性。

        (2)非相互性:如果 ClassA ClassB 的友元类,这并不意味着 ClassB 自动成为 ClassA 的友元类。友元关系是单向的。

        (3)非继承性:友元关系不会被继承。如果类 C 继承自类 B ,并且 B A 的友元类,那么 C 不自动成为 A 的友元类,除非 A 明确声明 C 为友元类。

        友元类提供了一种机制,允许其他类访问私有成员,但应在不破坏封装性的前提下谨慎使用。

1.4 友元成员函数

        在C++中,除了可以将整个类或单独的函数声明为友元外,还可以将特定类中的某个成员函数单独声明为另一个类的友元。这样做可以让这个成员函数访问另一个类的所有成员(包括私有和受保护的成员),而无需将整个类声明为友元,从而提供了更细粒度的访问控制。

        假设我们有两个类 ClassA ClassB ,我们希望 ClassB 的一个特定成员函数 showValue 能够访问ClassA 的私有成员。我们可以将 ClassB showValue 函数声明为 ClassA 的友元。

#include <iostream>

class ClassB; // 前向声明

class ClassA {
private:
    int value;

public:
    ClassA(int v) : value(v) {}
    // 声明 ClassB 的成员函数为友元
    friend void ClassB::showValue(ClassA& a);
};

class ClassB {
public:
    void showValue(ClassA& a) {
        // 访问 ClassA 的私有成员
        std::cout << "Value of ClassA: " << a.value << std::endl;
    }
};

int main() {
    ClassA a(100);
    ClassB b;
    b.showValue(a); // 输出 "Value of ClassA: 100"

    return 0;
}

        在这个示例中, ClassB::showValue 成为 ClassA 的友元函数。这意味着 showValue 可以访问ClassA 的私有成员 value 。注意,我们在 ClassA 前提供了 ClassB 的前向声明。这是必须的,因为在声明 ClassA ClassB 还未完全定义。

        注:(1)精确控制:与将整个类声明为友元相比,将特定成员函数声明为友元可以更精确地控制访问权限。

        (2)破坏封装:即使是单个成员函数,过度使用友元也可能破坏类的封装性。应当谨慎使用。

        (3)前向声明:在一个类中声明另一个类的成员函数为友元时,可能需要前向声明另一个类,特别是在两个类相互引用对方的成员函数时。

        友元成员函数是C++中一种强大的特性,允许开发者在保持类封装性的同时提供必要的访问权限。然而,应当谨慎使用,以保持代码的清晰性和维护性。

2、模板

        在 C++ 中,模板(Template)是一种通用的编程工具,允许程序员编写泛型代码,使得类或函数能够适用于多种不同的数据类型而不需要重复编写相似的代码。C++ 提供了两种主要类型的模板:类模板和函数模板。

2.1 类模版

        类模板允许定义通用的类,其中某些类型可以作为参数。这样的类可以处理不同类型的数据,而不需要为每个数据类型编写单独的类。

// 定义一个通用的类模板
template <typename T>

class MyTemplate {
private:
    T data;

public:
    MyTemplate(T d) : data(d) {}
    T getData() {
        return data;
    }
};

int main() {
    // 使用类模板创建对象
    MyTemplate<int> intObject(5);
    MyTemplate<std::string> stringObject("Hello");

    // 调用类模板中的函数
    std::cout << intObject.getData() << std::endl; // 输出 5
    std::cout << stringObject.getData() << std::endl; // 输出 Hello

    return 0;
}

2.2 函数模板

        函数模板允许编写通用的函数,可以处理多种不同类型的数据。

// 定义一个通用的函数模板
template <typename T>

T add(T a, T b) {
    return a + b;
}

int main() {
    // 使用函数模板调用通用函数
    int result1 = add(5, 10);
    double result2 = add(3.5, 2.7);

    // 输出函数模板调用结果
    std::cout << "Result 1: " << result1 << std::endl; // 输出 Result 1: 15
    std::cout << "Result 2: " << result2 << std::endl; // 输出 Result 2: 6.2

    return 0;
}

        模板提供了一种在编写代码时更具通用性的方法,能够处理不同类型的数据而无需为每种类型编写特定的函数或类。通过使用模板,可以提高代码的重用性和可维护性。

2.3 模板特化

        模板特化(Template Specialization)是 C++ 中模板的一个概念,它允许针对特定的数据类型或特定的模板参数提供定制化的实现。模板特化允许您为模板提供一个特殊的实现,以覆盖或扩展默认的模板行为。有两种类型的模板特化:完全特化(Full Specialization)和部分特化(Partial Specialization)。

2.3.1 完全特化

        完全特化是对模板中的所有模板参数都进行特化的情况。在完全特化中,模板参数被指定为特定的类型,为特定的类型提供独特的实现。

        以下是一个示例,演示了对模板函数的完全特化:

#include <iostream>

// 定义一个通用的模板函数
template <typename T>

T maximum(T a, T b) {
    return (a > b) ? a : b;
}

// 对模板函数进行完全特化,针对 char* 类型
template <>
const char* maximum<const char*>(const char* a, const char* b) {
    return strcmp(a, b) > 0 ? a : b;
}

int main() {
    int intMax = maximum(3, 5); // 调用模板函数
    std::cout << "Maximum of integers: " << intMax << std::endl;
    const char* charMax = maximum("apple", "orange"); // 调用特化的函数
    std::cout << "Maximum of strings: " << charMax << std::endl;

    return 0;
}

        在这个示例中, maximum 是一个模板函数,可以比较不同类型的数据,并返回较大的值。然后,为了特化针对 const char* 类型,我们提供了一个特化版本的 maximum 函数,该函数使用 strcmp 函数来比较字符串,并返回较大的字符串。

2.3.2 部分特化

        部分特化是指对模板中的部分参数进行特化,允许更具体地特化某些模板参数。这通常在模板类中使用。

        以下是一个示例,展示了对模板类的部分特化:

#include <iostream>

// 定义一个通用的模板类
template <typename T, typename U>

class MyTemplate {
public:
    void display() {
        std::cout << "Generic Display" << std::endl;
    }
};

// 对模板类进行部分特化,针对特定的类型组合
template <typename T>
class MyTemplate<T, int> {
public:
    void display() {
        std::cout << "Specialized Display for T and int" << std::endl;
    }
};

int main() {
    MyTemplate<float, double> obj1;
    obj1.display(); // 输出 "Generic Display"
    MyTemplate<int, int> obj2;
    obj2.display(); // 输出 "Specialized Display for T and int"
    
    return 0;
}

        在这个示例中, MyTemplate 是一个模板类,然后我们对 MyTemplate 进行部分特化,当第二个模板参数是 int 类型时,提供了特殊的实现。因此,对于特定的类型组合,我们可以提供自定义的实现。

3、标准模板库STL

3.1 容器

        当谈到 C++ 的标准模板库(STL)容器时,通常有一些常见的容器类型。以下是一些常见的 STL 容器及其特点的简要总结:

容器类型
描述
特点
vector
动态数组,支持快速随机访问和尾部插入/ 删除
支持动态大小调整,适用于需要随机访问和动态增删元素的场景
list
双向链表,支持快速插入/ 删除操作
插入 / 删除元素快速,但不支持随机访问元素
deque
双端队列,支持在两端快速插入/ 删除操作
支持在头尾快速插入 / 删除元素,不同于vector 在头部操作时效率较低,但随机访问效率比 list
stack
后进先出( LIFO )的堆栈数据结构
基于 deque vector 实现,只允许在栈顶进行插入和删除操作
queue
先进先出( FIFO )的队列数据结构
基于 deque list 实现,只允许在队尾进行插入,在队头进行删除操作
priority_queue
优先队列,按照一定顺序维护元素
基于 vector 实现,默认情况下是最大堆,可以通过自定义比较函数来实现不同的优先级顺序
set
有序不重复元素集合,基于红黑树实现
自动排序,插入 / 查找元素的平均时间复杂度为 O(log n) ,不允许重复元素
multiset
有序可重复元素集合,基于红黑树实现
允许存储重复元素,按序存储,插入 / 查找元素的平均时间复杂度为 O(log n)
map
键值对集合,基于红黑树实现
存储键值对,按键自动排序,不允许重复键,插入/ 查找元素的平均时间复杂度为O(log n)
multimap
键值对集合,允许重复键,基于红黑树实现
允许存储重复键值对,按键自动排序,插入/ 查找元素的平均时间复杂度为 O(log n)
unordered_set
无序不重复元素集合,基于哈希表实现
不按顺序存储元素,插入 / 查找 / 删除元素的平均时间复杂度为 O(1) ,不允许重复元素
unordered_multiset
无序可重复元素集合,基于哈希表实现
允许存储重复元素,不按顺序存储,插入 / 查找/ 删除元素的平均时间复杂度为 O(1)
unordered_map
无序键值对集合,基于哈希表实现
键值对无序存储,插入 / 查找 / 删除元素的平 均时间复杂度为 O(1) ,不允许重复键
unordered_multimap
无序键值对集合,允许重复键,基于哈希表实现
允许存储重复键值对,键值对无序存储, 插入/ 查找 / 删除元素的平均时间复杂度为 O(1)

3.2 vector

        在C++的标准模板库(STL)中, std::vector 是一种动态数组容器。它提供了动态大小的数组功能,能够在运行时根据需要自动调整大小,允许在其尾部高效地进行元素的插入和删除操作。

        (1 )动态大小: std::vector 允许动态增加或减少其大小。它会自动处理内存分配和释放,无需手动管理内存。

        (2)随机访问: 支持使用索引进行快速的随机访问,因为它底层基于连续的内存块。

        (3)尾部插入/删除: 在数组的尾部插入或删除元素的操作非常高效,时间复杂度为常数时间(Amortized Constant Time)。

        (4)连续内存存储: std::vector 中的元素在内存中是连续存储的,这有助于提高访问速度和缓存利用率。

        (5)动态增长策略: 当向 std::vector 添加元素时 ,如果当前容量不足,它会动态地重新分配更大的内存空间,并将现有元素复制到新的内存位置。这种动态增长策略确保了插入操作的高效性。

        std::vector 的缺点是,在执行插入或删除操作时,如果不是在容器的末尾进行,可能会导致较高的时间复杂度,因为需要移动后续元素。

        以下是一个使用 C++ STL 中的 vector 容器的简单示例:

#include <iostream>
#include <vector>

int main() {
    // 创建一个空的 vector 容器
    std::vector<int> myVector;

    // 向 vector 容器尾部添加元素
    myVector.push_back(3);
    myVector.push_back(7);
    myVector.push_back(12);

    // 使用迭代器遍历 vector 容器并输出其中的元素
    // 在 C++ 中,auto 是一个关键字,用于声明变量时的类型推断。
    // 它允许编译器根据变量的初始化表达式推断出变量的类型,从而简化代码书写过程。
    std::cout << "Vector elements: ";
    for (auto it = myVector.begin(); it != myVector.end(); ++it) {
        std::cout << *it << " ";
    }

    std::cout << std::endl;

    // 获取 vector 容器的大小和访问特定位置的元素
    std::cout << "Vector size: " << myVector.size() << std::endl;
    std::cout << "Element at index 1: " << myVector[1] << std::endl;
    
    // 修改特定位置的元素
    myVector[2] = 20;

    // 使用范围-based for 循环遍历 vector 并输出元素
    std::cout << "Modified Vector elements: ";
    for (int num : myVector) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 清空 vector 容器
    myVector.clear();

    // 检查 vector 是否为空
    if (myVector.empty()) {
        std::cout << "Vector is empty." << std::endl;
    } else {
        std::cout << "Vector is not empty." << std::endl;
    }

    return 0;
}

        这个例子展示了如何创建 vector 容器、向其尾部添加元素、使用迭代器和索引访问元素、修改元素值、清空容器以及检查容器是否为空。 vector 是一个动态数组,可以根据需要动态地增加或减少其大小,适合需要随机访问元素且频繁进行尾部插入/删除操作的场景。

        当涉及到 std::vector 的 API 方法时,参数列表、返回值以及参数的说明可以帮助更清楚地了解每个方法的使用和含义。以下是 std::vector 常用 API 列表,包括参数列表、返回值和参数说明:

API
描述
函数原型
参数说明
push_back()
vector尾部添加一个元素
void push_back(const T& value);
value :要添加到尾部的元素
pop_back()
删除 vector 尾部的一个元 素
void pop_back();
无参数
size()
返回 vector 中元素的数量
size_type size() const noexcept;
无参数
capacity()
返回 vector 当前可容纳的元素数量
size_type capacity() const noexcept;
无参数
resize()
改变 vector 的大小,可以增加或减少元素数量
void resize(size_type count);
void resize(size_type count, const T& value);
count :新的 vector大小
value :若添加元素,初始化值为 value
reserve()
修改 vector 的容量,预留足够的存储 空间
void reserve(size_type new_cap);
new_cap :新的
vector: 容量
clear()
清空 vector 中的所有元素
void clear() noexcept;
无参数
empty()
检查 vector 是否为空
bool empty() const noexcept;
无参数
at()
返回指定位置的元素,并进行边界检查
reference at(size_type pos);
const_reference at(size_type pos) const;
pos :要访问的位置。如果超出范围,会引发
std::out_of_range 异常
operator[]
重载操作符,用于访问指定位置的元素
reference operator[] (size_type pos);
const_reference operator[](size_type pos) const;
pos :要访问的位置。不进行边界检查,如果超出范围,行为未定义
front()
返回vector 中第一个元素的引用
reference front();
const_reference front() const;
无参数
back()
返回 vector 中最后一个元素的引用
reference back();
const_reference back() const;
无参数
begin()
返回指向 vector 第 一个元素的迭代器
iterator begin() noexcept;
const_iterator begin() const noexcept;
无参数
end()
返回指向 vector 末尾(最后一个元素的后面)的迭代器
iterator end() noexcept;
const_iterator end() const noexcept;
无参数
rbegin()
返回指向 vector 最后一个元素的逆向迭代器(逆向开始迭代)
reverse_iterator rbegin() noexcept;
const_reverse_iterator rbegin() const noexcept;
无参数
rend()
返回指向 vector 第一个元素之前的逆向迭代器(逆向结束迭代)
reverse_iterator rend()
noexcept;
const_reverse_iterator rend() const noexcept;
无参数
insert()
在指定位置插入一个或多个元素
iterator insert(const_iterator pos, const T& value);
void insert(const_iterator pos, size_type count, const T& value);
pos :插入位置
value :要插入的元素
count :要插入的元素个数
erase()
删除指定位置或指定范围内的一个或多个元素
iterator erase(const_iterator
pos);
iterator erase(const_iterator first, const_iterator last);
pos :要删除的元素位 置或范围的起始位置
first last :要删除的范围
swap()
交换两个 vector 容器的内容
void swap(vector& other);
other :要交换内容的另一个 vector 容器
emplace()
在指定位置就地构造一个元素
iterator emplace(const_iterator pos, Args&&... args);
pos :就地构造的位置
args :构造元素所需的 参数
emplace_back()
vector 尾部就地构造一个元素
void emplace_back(Args&&... args);
args :构造元素所需的 参数

3.3 list

        STL 中的 list 是双向链表(doubly linked list)的实现,它是 C++ 标准模板库中的一个容器,提供了一种能够高效进行插入、删除操作的数据结构。与 vector 不同, list 不支持随机访问,但它允许在任意位置快速插入和删除元素。

        以下是关于 std::list 的一些特点和说明:

        (1)双向链表结构: std::list 使用双向链表来组织其元素,每个节点都包含指向前一个节点和后一个节点的指针,因此在任意位置进行插入和删除操作的开销较小。

        (2)不支持随机访问: vector 不同, list 不支持通过索引直接访问元素,因为它不具备随机访问能力。要访问 list 中的元素,需要使用迭代器进行顺序遍历。

        (3)动态大小调整: list 具有动态大小调整的特性,可以动态增加或减少元素的数量。对于大量的插入和删除操作, list 往往比 vector 更高效。

        (4)迭代器操作: 使用迭代器可以对 list 中的元素进行访问、插入和删除。 list 提供了begin() 、 end() rbegin() rend() 等迭代器相关方法,支持正向和逆向迭代。

        (5)插入和删除操作效率高: list 中,在任意位置进行插入和删除操作的时间复杂度是 O(1),因为只需要调整相邻节点的指针,无需移动大量元素。

        (6)空间开销: 相比于 vector list 需要额外的空间来存储指向前一个和后一个节点的指针,可能会导致更高的存储开销。

        下面是一个简单的示例,演示了如何使用 STL 中的 std::list 容器。在这个案例中,我们创建了一个std::list 来存储整数,并展示了一些基本的操作,如插入、删除、迭代等。

#include <iostream>
#include <list>

int main() {
    // 创建一个存储整数的 list 容器
    std::list<int> myList;

    // 在 list 尾部插入元素
    myList.push_back(10);
    myList.push_back(20);
    myList.push_back(30);

    // 在 list 头部插入元素
    myList.push_front(5);
    myList.push_front(15);

    // 使用迭代器遍历 list 并输出元素
    std::cout << "List elements: ";
    for (auto it = myList.begin(); it != myList.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 删除 list 中特定的元素值
    myList.remove(20);

    // 使用范围-based for 循环遍历 list 并输出元素
    std::cout << "List elements after removal: ";
    for (int num : myList) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 检查 list 是否为空
    if (myList.empty()) {
        std::cout << "List is empty." << std::endl;
    } else {
        std::cout << "List is not empty." << std::endl;
    }

    return 0;
}

        这个示例演示了如何创建 std::list 容器,并对其进行插入、删除和迭代操作。在实际应用中,std::list 还有许多其他的功能和方法可以使用,比如 splice() merge() sort() 等,用于更复杂的操作。

        以下是 std::list 常用的 API 方法,包括参数说明,整理成表格形式:

API
描述
函数原型
参数说明
push_back()
list 尾部添加一个元素
void push_back(const T & value);
value :要添加到尾部的元素
push_front()
list 头部添加一个元素
void push_front(const T & value);
value :要添加到头部的元素
pop_back()
删除 list 尾部的一个元素
void pop_back();
无参数
pop_front()
删除 list 头部的一个元素
void pop_front();
无参数
size()
返回 list 中元素的数量
size_type size() const noexcept;
无参数
empty()
检查 list 是否为空
bool empty() const noexcept;
无参数
clear()
清空 list 中的所有元素
void clear() noexcept;
无参数
begin()
返回指向 list 第一个元素的迭代器
iterator begin() noexcept;
const_iterator begin() const noexcept;
无参数
end()
返回指向 list 末尾
(最后一个元素的后面)的迭代器
iterator end() noexcept;
const_iterator end() const noexcept;
无参数
rbegin()
返回指向 list 最后一
个元素的逆向迭代器(逆向开始迭代)
reverse_iterator rbegin() noexcept;
const_reverse_iterator rbegin() const noexcept;
无参数
rend()
返回指向 list 第一个元素之前的逆向迭代器(逆向结束迭代)
reverse_iterator rend() noexcept;
const_reverse_iterator rend() const noexcept;
无参数
insert()
在指定位置插入一个或多个 元素
iterator insert(const_iterator pos, const T& value);
void insert(const_iterator pos, size_type count, const T& value);
pos :插入位
value :要插
入的元素
count :要插 入的元素数
erase()
删除指定位置或指定范围内的一个或多个元素
iterator erase(const_iterator pos);
iterator erase(const_iterator first, const_iterator last);
pos :要删除 的元素位或范围的起始位置
first 、last :要删除的范围
splice()
在指定位置插入另一个 list 中的元素
void splice(const_iterator pos, list& other);
void splice(const_iterator pos, list& other, const_iterator it);
void splice(const_iterator pos, list& other, const_iterator first, const_iterator last);
pos :插入位置
other :要插 入的另一个 list
it :要插入的元素
first 、last :要插入的范围
merge()
合并两个已排序的 list
void merge(list& other);
void merge(list&& other);
other :要合 并的另一个 list
unique()
移除 list 中重复的元素
void unique();
void unique(BinaryPredicate p);
p :可选的谓 词函数,用于 比较元素是否相等
sort()
list 进行排序
void sort();
void sort(Compare comp);
comp :可选的比较函数,用于元素排序

3.4 set

        std::set 是 C++ 标准模板库中的关联容器,用于存储唯一值的集合。它基于红黑树实现,保持了元素的有序性,且不允许重复的元素存在。

        以下是关于 std::set 的一些特点和说明:

        (1)唯一性: std::set 中的元素是唯一的,不允许有重复的元素存在。当尝试向 set 中插入重复的元素时,新元素将不会被插入。

        (2)有序性: std::set 中的元素是根据元素值进行排序的,这使得元素按照一定顺序存储,并且支持对元素的快速搜索。

        (3)红黑树实现: std::set 的底层实现通常是基于红黑树的,这保证了插入、删除和查找操作的时间复杂度为对数时间(O(log n))。

        (4)动态操作: 可以对 std::set 进行动态操作,如插入、删除和查找元素。插入和删除操作的性能较好,不会影响其他元素的位置。

        以下是 std::set 常用的一些方法:

        (1)insert() : 向 set 中插入一个元素。

        (2)erase() : 删除 set 中指定值的元素。

        (3)find() : 查找指定值在 set 中的位置。

        (4)size() : 返回 set 中元素的数量。

        (5)empty() : 检查 set 是否为空。

        (6)clear() : 清空 set 中的所有元素。

        下面是一个简单的示例,演示了如何使用 std::set :

#include <iostream>
#include <set>

int main() {
    // 创建一个存储 int 类型值的 set 容器
    std::set<int> mySet;

    // 向 set 中插入元素
    mySet.insert(10);
    mySet.insert(20);
    mySet.insert(30);

    // 尝试插入重复元素
    mySet.insert(20); // 不会插入重复的元素

    // 使用迭代器遍历 set 并输出元素
    std::cout << "Set elements: ";
    for (auto it = mySet.begin(); it != mySet.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 查找特定值在 set 中的位置
    int searchValue = 20;
    auto found = mySet.find(searchValue);
    if (found != mySet.end()) {
        std::cout << "Found " << searchValue << " in the set." << std::endl;
    } else {
        std::cout << searchValue << " not found in the set." << std::endl;
    }

    // 删除特定值的元素
    mySet.erase(30);

    // 检查 set 是否为空
    if (mySet.empty()) {
        std::cout << "Set is empty." << std::endl;
    } else {
        std::cout << "Set is not empty." << std::endl;
    }

    return 0;
}

3.5 map

        std::map 是 C++ 标准模板库中的关联容器,用于存储键值对。它基于红黑树实现,保持了元素的有序性,其中每个元素都是一个键值对,键和值之间存在映射关系。

        以下是关于 std::map 的一些特点和说明:

        (1)有序性: std::map 中的元素是根据键值排序的,这使得元素按照一定顺序存储,并且支持对元素的快速搜索。

        (2)唯一键: std::map 中的键是唯一的,每个键对应一个值。如果尝试使用相同的键向 map 中插入值,则会更新键对应的值。

        (3)红黑树实现: std::map 的底层实现通常是基于红黑树的,这保证了插入、删除和查找操作的时间复杂度为对数时间(O(log n))。

        (4)动态操作: 可以对 std::map 进行动态操作,如插入、删除和查找键值对。插入和删除操作的性能较好,不会影响其他元素的位置。

        以下是 std::map 常用的一些方法:

        (1)insert() : 向 map 中插入一个键值对。

        (2)erase() : 删除 map 中指定键的键值对。

        (3)find() : 查找指定键在 map 中的位置。

        (4)operator[] : 通过键访问对应的值。

        (5)size() : 返回 map 中键值对的数量。

        (6)empty() : 检查 map 是否为空。

        (7)clear() : 清空 map 中的所有键值对。

        下面是一个简单的示例,演示了如何使用 std::map :

#include <iostream>
#include <map>

int main() {

    // 创建一个存储 string 类型键和 int 类型值的 map 容器
    std::map<std::string, int> myMap;

    // 向 map 中插入键值对
    myMap["Alice"] = 25;
    myMap["Bob"] = 30;
    myMap["Charlie"] = 20;

    // 使用迭代器遍历 map 并输出键值对
    std::cout << "Map elements: " << std::endl;
    for (auto it = myMap.begin(); it != myMap.end(); ++it) {
        std::cout << it->first << ": " << it->second << std::endl;
    }

    // 查找特定键在 map 中的位置
    std::string searchKey = "Bob";
    auto found = myMap.find(searchKey);
    if (found != myMap.end()) {
        std::cout << "Found " << searchKey << " with value: " << found->second <<
        std::endl;
    } else {
        std::cout << searchKey << " not found in the map." << std::endl;
    }
    
    // 删除特定键的键值对
    myMap.erase("Charlie");

    // 检查 map 是否为空
    if (myMap.empty()) {
        std::cout << "Map is empty." << std::endl;
    } else {
        std::cout << "Map is not empty." << std::endl;
    }

    return 0;
}

4、异常

4.1 异常基本

        在C++ 中,异常处理是一种机制,用于处理程序在运行时发生的异常情况。异常是指程序执行期间发生的意外事件,比如除以零、访问无效的内存地址等。通过使用异常处理机制,可以使程序更健壮,并能够处理这些意外情况,避免程序崩溃或产生不可预测的结果。

        在 C++ 中,异常处理通常包括以下关键词和概念:

        (1)try-catch try 块用于标识可能会引发异常的代码块,而 catch 块用于捕获和处理异常。catch 块可以针对不同类型的异常进行处理。

        (2)throw 关键词throw 用于在程序中显式抛出异常。当发生异常情况时,可以使用 throw 来抛出一个特定的异常类型。

        (3)异常类型:异常可以是任何类型的数据,但通常是标准库中的异常类或自定义的异常类。标准库提供了一些常见的异常类,如 std::exception 及其派生类,用于表示不同类型的异常情况。

        下面是一个简单的示例,演示了异常处理的基本用法:

#include <iostream>

void divide(int numerator, int denominator) {
    try {
        if (denominator == 0) {
            throw "Division by zero is not allowed!";
        }

        int result = numerator / denominator;
        std::cout << "Result of division: " << result << std::endl;

    } catch (const char* errorMessage) {
        std::cout << "Exception caught: " << errorMessage << std::endl;
    }
}

int main() {
    int a = 10;
    int b = 0;
    divide(a, b);

    return 0;
}

        在这个示例中, divide() 函数尝试对 numerator 除以 denominator 进行除法运算。如果denominator 为零,就会抛出一个字符串类型的异常。在 main() 函数中调用 divide() 函数时,由于 b 的值为零,因此会抛出异常,然后在 catch 块中捕获并处理异常,输出错误消息。

        在实际的程序开发中,可以根据具体情况设计和抛出自定义的异常类,以及使用多个 catch 块来处理不同类型的异常,使程序能够更好地处理各种异常情况

 4.2 自定义异常

        在 C++ 中,可以通过继承标准库的 std::exception 类或其派生类来自定义异常类。自定义异常类通常用于表示特定类型的异常情况,并允许你提供有关异常的额外信息。

        以下是一个示例,演示了如何创建自定义的异常类:

#include <iostream>
#include <exception>

// 自定义异常类,继承自 std::exception
class MyException : public std::exception {
private:
    const char* message; // 异常消息

public:
    // 构造函数,接受异常消息作为参数
    MyException(const char* msg) : message(msg) {}

    // 覆盖基类的 what() 方法,返回异常消息
    virtual const char* what() const throw() {
        return message;
    }
};

// 一个函数,演示如何抛出自定义异常
void myFunction() {
    // 在这个示例中,函数总是抛出自定义异常
    throw MyException("This is a custom exception!");
}

int main() {
    try {
        myFunction();
    } catch (const MyException& e) {
        std::cout << "Caught MyException: " << e.what() << std::endl;
    }

    return 0;
}

        在这个示例中, MyException 类继承自 std::exception 类,并重写了基类的 what() 方法,以返回异常消息。在 myFunction() 中,我们抛出了一个 MyException 类型的异常,并在 main() 函数中的try-catch 块中捕获并处理该异常。

        通过自定义异常类,可以根据需要添加其他成员变量、方法或构造函数,以便更好地描述和处理特定类型的异常情况。这种方式可以提高程序的可读性和可维护性,并允许你更精确地控制异常处理。

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值