STL list 模拟实现

news2024/11/27 11:47:53

list 概述

相比于 vector 的连续线性空间,list 采用的是零散的空间,它的好处是每次插入或删除一个元素,就配置或释放一个元素空间。

list 是支持常数时间从容器任何位置插入和移除元素容器,但不支持快速随机访问。list 通常实现为双向链表,与 forward_list 相比,list 的迭代器可以向前移动,但也因此需要在节点中多开辟一个指针变量,在空间上效率稍低。

接口总览

namespace qgw {
	/// @brief list 中每个节点
	/// @tparam T 节点存储的数据的类型
	template <class T>
	struct _list_node {
		_list_node(const T& data = val());	// 节点类的构造函数

		_list_node<T>* _prev;				// 指向前一节点
		_list_node<T>* _next;				// 指向后一节点
		T _data;							// 存储节点数据
	};

	/// @brief list 的迭代器
	/// @tparam T list 数据的类型
	/// @tparam Ref 数据的引用类型
	/// @tparam Ptr 数据的指针类型
	template <class T, class Ref, class Ptr>
	struct _list_iterator {
		typedef _list_iterator<T, T&, T*>		iterator;
		typedef _list_iterator<T, Ref, Ptr>		self;

		typedef T		value_type;
		typedef Ptr		pointer;
		typedef Ref		reference;
		typedef _list_node<T> list_node;
		
        // 构造函数
        _list_iterator(list_node* node = nullptr);
        
		// 各种运算符重载
		bool operator==(const self& x) const;
		bool operator!=(const self& x) const;
		reference operator*() const;
		pointer operator->() const;
		self& operator++();
		self operator++(int);
		self& operator--();
		self operator++(int);

		list_node* _node;	// 指向对应的 list 节点
	};

	template <class T>
	class list {
	public:
		typedef T value_type;
		typedef T* pointer;
		typedef T& reference;
		typedef _list_node<T>	list_node;
		typedef _list_iterator<T, T&, T*>             iterator;
		typedef _list_iterator<T, const T&, const T*> const_iterator;

	public:
		// 默认成员函数
		list();
		list(const list<T>& other);
		list<T>& operator=(const list<T>& other);
		~list();

		// 元素访问
		reference front();
		reference back();

		// 迭代器
		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;

		// 容量
		bool empty() const;
		size_t size() const;

		// 修改器
		void clear();
		iterator insert(iterator pos, const T& val);
		void push_front(const T& val);
		void push_back(const T& val);
		iterator erase(iterator pos);
		void pop_front();
		void pop_back();
		void swap(list& other);

		// 操作
		void splice(iterator pos, list& other);
		void splice(iterator pos, list& other, iterator it);
        void splice(iterator pos, list& other, iterator first, iterator last);
		void merge(list& other);
		void remove(const T& value);
		void reverse();
		void unique();

	private:
		list_node* _node;	// 指向链表头节点
	};
}

list 的节点

list 的节点我们设计成一个 _list_node 类,里面有三个成员变量,分别为前后指针和数据。

它的构造函数将数据初始化为给定数据,再将前后指针初始化为空即可。

/// @brief 节点类的构造函数
/// @param data 用来构造节点的初值
_list_node(const T& data = T()) 
    : _data(data) {
        _prev = nullptr;
        _next = nullptr;
    }

请添加图片描述

默认成员函数

默认构造函数

SGI list 不仅是一个双向链表,还是一个带头的循环链表。

/// @brief 构造一个空链表
list() {
    _node = new list_node;	// 创建一个头节点
    _node->_prev = _node;	// 前面指向自己
    _node->_next = _node;	// 后面指向自己
}

请添加图片描述

析构函数

list 的析构函数,先调用 clear 释放数据资源,再 delete 掉头节点即可。

/// @brief 释放资源
~list() {
    clear();
    delete _node;
    _node = nullptr;
}

拷贝构造函数

用另一容器创建新对象。

先申请一个头节点,然后遍历 other 容器,将 other 中的数据逐一尾插到 *this 中。

/// @brief 用给定容器初始化
/// @param other 用来初始化的容器
list(const list<T>& other) {
    _node = new list_node; 
    _node->_next = _node;
    _node->_prev = _node;
    for (const auto& e : other) {
        push_back(e); 
    }
}

复制赋值函数

先创建给定容器的拷贝 tmp,然后交换 *this 和 tmp,最后返回 *this。

/// @brief 替换容器内容
/// @param other 用作数据源的另一容器
/// @return *this
list<T>& operator=(const list<T>& other) {
    // tmp 出了作用域就销毁了
    list<T> tmp(other);
    swap(tmp);
    // 返回引用可以连续赋值
    return *this;
}

list 的迭代器

list 的节点在内存中不是连续存储的,因此不能使用原生指针作为 list 的迭代器。list 的迭代器必须有能力指向 list 的节点,并能够正确的递增、递减、取值、成员存取等操作。正确的操作是指:递增时指向下一节点,递减时指向上一节点,取值时取的是节点的数据值,成员取用的是节点的成员。

由于 STL list 是一个双向链表(double linked-list),迭代器必须具备前移、后移的能力,所以 list 提供的是 Bidirectional Iterators。

请添加图片描述

构造函数

list 的迭代器中成员变量只有一个节点指针,将其指向给定节点即可。

/// @brief list 迭代器的构造函数
/// @param node 用来构造的节点
_list_iterator(list_node* node = nullptr) {
    _node = node;
}

operator==

判断两迭代器指向的节点是否为同一个,直接比较迭代器中节点的指针即可。切记不能比较指针中的值,因为不同节点的值可能相同。

/// @brief 判断两迭代器指向的节点是否相同
/// @param x 用来比较的迭代器
/// @return 相同返回 true,不同返回 false
bool operator==(const self& x) const {
    return _node == x._node;
}

operator!=

!= 的比较方法和 == 一样。

/// @brief 判断两迭代器指向的节点是否不同
/// @param x 用来比较的迭代器
/// @return 不同返回 true,相同返回 false
bool operator!=(const self& x) const {
    return _node != x._node;
}

operator*

迭代器是模仿指针的,让我们可以像使用指针一样。因此可以对迭代器进行解引用操作,该操作得到的是迭代器中节点指针指向的数据,并且返回引用,因为有可能修改该数据。

/// @brief 获取指向节点中的数据值
/// @return 返回指向节点数据的引用
reference operator*() const {
    return _node->_data;
}

operator->

-> 运算符的重载稍显复杂,让我们先看下面这个场景。

也就是 list 中存储的是自定义类型,自定义类型中又有多个成员变量,我们想取出指定的成员变量,当然这里用 . 也可以做到。

// 有一个学生类,里面有姓名和学号两个成员
struct Stu {
	string name;
    string id;
};

list<Stu> s;
Stu s1 = { "qgw", "001" };
Stu s2 = { "wlr", "002" };
s.push_back(s1);
s.push_back(s2);
list<Stu>::iterator ptr = s.begin();
// 输出第一个学生的姓名和学号
cout << (*ptr).name << endl;
cout << s.begin()->id << endl;
/// @brief 获取节点中数据的地址
/// @return 返回节点指向的数据的地址
pointer operator->() const {
    return &(operator*());
}

看到这你可能会疑惑,operator-> 返回的是节点的数据的地址,也是说上面 s.begin()-> 得到的是一个地址,那这条语句是怎么执行的?

实际上这里确实应该有两个箭头像这样 s.begin()->->id,但这种方式的可读性太差了,所以编译器对此做了优化,在编译为我们添加一个箭头。

operator++

operator++ 运算符的作用十分清晰,就是让迭代器指向链表中下一节点。

前置实现的思路是:通过迭代器中的节点指针找到下一节点,然后赋值给迭代器中的节点指针。

后置实现的思路是:先保存当前位置迭代器,然后调用前置 ++,最后返回临时变量。

需要注意的是:前置 ++ 返回的是前进后迭代器的引用,后置 ++ 返回的是一个临时变量。

/// @brief 前置++
/// @return 返回前进一步后的迭代器
self& operator++() {
    _node = _node->_next;
    return *this;
}

/// @brief 后置++
/// @param  无作用,只是为了与前置 ++ 进行区分,形成重载
/// @return 返回当前的迭代器
self operator++(int) {
    self tmp = *this;
    // 直接调用前置 ++
    ++(*this);
    return tmp;
}

operator--

前置实现的思路是:通过迭代器中的节点指针找到前一节点,然后赋值给迭代器中的节点指针。

后置实现的思路是:先保存当前位置迭代器,然后调用前置 --,最后返回临时变量。

/// @brief 前置 --
/// @return 返回后退一步后的迭代器
self& operator--() {
    _node = _node->_prev;
    return *this;
}

/// @brief 后置 --
/// @param  无作用,只是为了与前置 -- 进行区分,形成重载
/// @return 返回当前的迭代器
self operator--(int) {
    self tmp = *this;
    --(*this);
    return tmp;
}

元素访问

front

front 获取第一个元素的引用,直接用 begin 获取指向第一个元素的迭代器,再解引用即可。

/// @brief 返回容器首元素的引用
/// @return 首元素的引用
reference front() {
    return *begin();
}	

back

end 获取最后一个元素的引用,先用 end 获取最后一个元素下一位置的迭代器,再回退一步,然后解引用就可以了。

/// @brief 返回容器中最后一个元素的引用
/// @return 最后元素的引用
reference back() {
    return *(--end());
}

迭代器

在 begin 和 end 实现之前,我们先来看下 list 的示意图,下图为有 3 个元素的链表:

请添加图片描述

begin

begin 获取的是首元素的迭代器,根据上图,begin 的实现也就非常清晰了,直接返回头节点的下一位置即可。

/// @brief 返回指向 list 首元素的迭代器
/// @return 指向首元素的迭代器
iterator begin() {
    // 根据节点指针构造迭代器
    return iterator(_node->_next);
}

// const 版本供 const 容器使用
const_iterator begin() const {
    return const_iterator(_node->_next);
}

end

end 获取的是最后一个元素下一个位置的迭代器,根据上图就是 _node 所指向的节点。

/// @brief 返回指向 list 末元素后一元素的迭代器
/// @return 指向最后元素下一位置的迭代器
iterator end() {
    // 调用 iterator 构造函数
    return iterator(_node);
}

const_iterator end() const {
    return const_iterator(_node);
}

容量

empty

begin 和 end 指向相同,说明链表此时只有一个头节点,链表为空。

/// @brief 检查容器是否无元素
/// @return 若容器为空则为 true,否则为 false
bool empty() const {
    return begin() == end();
}

size

size 函数的作用是返回容器中元素的数量。

在 C++ 11 前,该函数的复杂度可能是常数的,也可能是线性的。从 C++ 11 起该函数的复杂度为常数。

下面代码的时间复杂度是线性的,要想改成常数也很简单,只需要在 list 中开辟一个成员变量记录个数即可。

/// @brief 返回容器中的元素数
/// @return 容器中的元素数量
size_t size() const {
    size_t sz = 0;
    auto it = begin();
    while (it != end()) {
        ++it;
        ++sz;
    }
    return sz;
}

修改器

insert

下图为:只有 0、1 两个元素的链表,在 1 之前插入元素值为 2 的节点的示意图。
list insert

插入的思路比较清晰:

  1. 插入节点的 _next 指向 pos 位置的节点
  2. 插入节点的 _prev 指向 pos 前一位置的节点
  3. pos 前一位置的节点的 _next 指向插入的节点
  4. pos 位置节点的 _prev 指向插入的节点
/// @brief 插入元素到容器中的指定位置
/// @param pos 	将内容插入到 pos 之前
/// @param val 要插入的元素值
/// @return 指向被插入 val 的迭代器
iterator insert(iterator pos, const T& val) {
    list_node* tmp = new list_node(val);	// 创建要插入的节点
    tmp->_next = pos._node;					// (1)
    tmp->_prev = pos._node->_prev;			// (2)
    (pos._node->_prev)->_next = tmp;		// (3)
    pos._node->_prev = tmp;					// (4)
    return tmp;
}

push_front

push_front 的作用是在第一个元素之前插入一个节点,直接调用 insert 在 begin 之前插入就行。

/// @brief 添加给定元素 val 到容器起始
/// @param val 要添加的元素值
void push_front(const T& val) {
    insert(begin(), val);
}

push_back

push_back 的作用是在容器的最后添加一个节点,直接调用 insert 在 end 之前插入就行。

/// @brief 添加给定元素 val 到容器尾
/// @param val 要添加的元素值
void push_back(const T& val) {
    insert(end(), val);
}

erase

下图为:有三个元素 0、1、2 的链表,删除 pos 指向节点(值为 1)的示意图。

list erase

删除的思路也很清晰:

  1. 将 pos 前一节点的 _next 指针指向 pos 的下一节点
  2. 将 pos 下一节点的 _prev 指针指向 pos 的前一节点
  3. delete 释放掉 pos 所指向的节点
/// @brief 从容器擦除指定的元素
/// @param pos 指向要移除的元素的迭代器
/// @return 最后移除元素之后的迭代器
iterator erase(iterator pos) {
    list_node* nextNode = pos._node->_next;		// 记录 pos 指向节点的下一节点
    list_node* prevNode = pos._node->_prev;		// 记录 pos 指向节点的前一节点
    prevNode->_next = nextNode;					// (1)
    nextNode->_prev = prevNode;					// (2)
    delete (pos._node);
    return (iterator)nextNode;
}

pop_front

pop_front 移除容器第一个元素,也就是 begin 指向的节点。

/// @brief 移除容器首元素
void pop_front() {
    erase(begin());
}

pop_back

pop_back 移除容器最后一个节点,也就是 end 指向的前一个节点。

/// @brief 移除容器的末元素
void pop_back() {
    erase(--end());
}

clear

clear 用于清空容器所有数据,不清理头节点。

采用遍历的方式调用 erase 删除每一个节点。

/// @brief 从容器擦除所有元素
void clear() {
    iterator it = begin();
    while (it != end()) {
        it = erase(it);
    }
}

swap

swap 用来交换两个 list 容器,不用 list 中每个元素的值,直接交换 _node 指针即可。

/// @brief 将内容与 other 的交换
/// @param other 要与之交换内容的容器
void swap(list& other) {
    std::swap(_node, other._node);
}

操作

splice

在实现该函数之前,先来看下 list 内部的一个函数 transfer。

list 内部提供一个迁移操作(transfer):将某连续范围的元素迁移都某个特定位置之前。

这个函数比上面所写的要复杂的多,要对着图仔细思考。

/// @brief 将 [first, last) 范围的所有元素移动到 pos 之前
/// @param pos 将内容移动到 pos 之前
/// @param first 范围起始位置
/// @param last 范围结束位置
void transfer(iterator pos, iterator first, iterator last) {
    if (pos != last) {
        last._node->_prev->_next = pos._node;		// (1)
        first._node->_prev->_next = last._node;		// (2)
        pos._node->_prev->_next = first._node;		// (3)
        list_node* tmp = pos._node->_prev;			// (4)
        pos._node->_prev = last._node->_prev;		// (5)
        last._node->_prev = first._node->_prev;		// (6)
        first._node->_prev = tmp;					// (7)
    }
}

transfer

有了上面的函数,splice 的实现也就非常简单了,下面共有三个重载实现:

根据要转移的元素选择调用不同的函数。

/// @brief 将 other 接合于 pos 所指位置之前,两者不能是同一 list
/// @param pos 将内容插入到它之前
/// @param other 要从它转移内容的另一容器
void splice(iterator pos, list& other) {
    if (!other.empty()) {
        transfer(pos, other.begin(), other.end());
    }
}

/// @brief 将 it 所指元素接合到 pos 所指位置之前
/// @param pos 	将内容插入到它之前
/// @param other 要从它转移内容的另一容器
/// @param it 从 other 转移到 *this 的元素
void splice(iterator pos, list& other, iterator it) {
    // 取得一个 [i, j) 的范围,使得能调用 transfer
    iterator j = it;
    ++j;
    // 检查是否有必要执行
    // pos == it 时说明 pos 和 it 指向的是同一节点
    // pos == j 时说明,it 刚好在 pos 之前
    if (pos == it || pos == j) {
        return;
    }
    transfer(pos, it, j);
}

/// @brief 将 [first, last) 内的所有元素接合于 pos 所指位置之前
/// @param pos 将内容插入到它之前
/// @param first 起始位置
/// @param last 结束位置
void splice(iterator pos, list& other, iterator first, iterator last) {
    if (first != last) {
        transfer(pos, first, last);
    }
}

merge

merge 函数和归并排序中的合并操作类似,该函数的作用是:合并两个已递增排序的链表。

/// @brief 合并两个递增链表,合并到 *this 上
/// @param other 要合并的另一容器
void merge(list& other) {
    iterator first1 = begin();
    iterator end1 = end();
    iterator first2 = other.begin();
    iterator end2 = other.end();

    while (first1 != end1 && first2 != end2) {
        if (*first2 < *first1) {
            iterator next = first2;
            transfer(first1, first2, ++next);
            first2 = next;
        } else {
            ++first1;
        }
    }
    if (first2 != end2) {
        // 将 other 链表剩余元素合并到 *this 中
        transfer(first1, first2, end2);
    }
}

remove

remove 用来删除 list 中值等于 val 的元素,直接遍历链表,找到就删除。

/// @brief 移除等于 val 元素
/// @param val 要移除的元素的值
void remove(const T& val) {
    iterator first = begin();
    iterator last = end();
    while (first != last) {
        iterator next = first;
        ++next;
        if (*first == val) {
            erase(first);
        }
        first = next;
    }
}

reverse

reverse 的作用是逆转容器中的元素顺序。

该函数的思路简单,从第 2 个元素开始,以次头插到链表头部。

/// @brief 将 *this 的内容逆置
void reverse() {
    // 空链表或只有一个元素直接返回
    if (_node->_next == _node || _node->_next->_next == _node) {
        return;
    }

    iterator first = begin();
    ++first;
    while (first != end()) {
        iterator old = first;			// 第一次循环时指向第 2 个元素
        ++first;						// 第一次循环时指向第 3 个元素
        transfer(begin(), old, first);
    }
}

unique

unique 用来移除数值相同的连续元素,只保留一个。

该函数利用的是双指针的思想,first 和 next 分别指向前后两个元素,相同就删除后一个。

/// @brief 移除数值相同的连续元素
void unique() {
    iterator first = begin();
    iterator last = end();
    if (first == last) {
        return;
    }

    iterator next = first;
    while (++next != last) {
        // 此时 next 指向 first 下一个节点
        if (*first == *next) {
            // 连续的相同,就删除后一个
            erase(next);
        } else {
            // 不相同 first 向 end 移动
            first = next;
        }
        // 使 next 再次指向 first
        // 这样再进 while 时,++next 又使 next 指向 first 下一个
        next = first;
    }
}

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

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

相关文章

Linux操作系统之进程间通讯—共享内存与消息队列

文章目录一、共享内存1、共享内存的原理2、共享内存的实现三、消息队列1、消息队列原理2、消息队列实现一、共享内存 1、共享内存的原理 共享内存为多个进程之间共享和传递数据提供了一种有效的方式。共享内存是先在物理内存上申请一块空间&#xff0c;多个进程可以将其映射到…

dp (四) 打家劫舍

打家劫舍(一)_牛客题霸_牛客网 描述 你是一个经验丰富的小偷&#xff0c;准备偷沿街的一排房间&#xff0c;每个房间都存有一定的现金&#xff0c;为了防止被发现&#xff0c;你不能偷相邻的两家&#xff0c;即&#xff0c;如果偷了第一家&#xff0c;就不能再偷第二家&#…

离线和实时

离线和实时 一、数仓基本概念 1. 数据仓库架构 我们在谈数仓之前&#xff0c;为了让大家有直观的认识&#xff0c;先来谈数仓架构&#xff0c;“架构”是什么&#xff1f;这个问题从来就没有一个准确的答案。这里我们引用一段话&#xff1a;在软件行业&#xff0c;一种被普遍…

8种将pdf转化成excel的方法,亲测实用又有效!

PDF 到 Excel 的在线或离线转换工具可帮助您将原始或扫描的 PDF 文件转换为 Excel 格式。将 PDF 转换为 Excel 主要是为了获得可编辑的 Excel 文件或满足其他目标&#xff1b; 通过消除容易出错的手动复制粘贴来保持数据准确性。在需要使用 Excel 格式的大量 PDF 数据时节省时…

药物临床试验数据分析(靶点|适应症|企业|登记信息)

临床试验相关工作者在对药物进行系统性研究时都需要对药物做临床试验&#xff0c;且在对药物进行临床试验前和临床试验期间都需要对相关药物临床试验数据信息进行全面的分析及了解&#xff0c;有助于目标药物临床试验的顺利开展。药物临床试验数据覆盖非常宽泛&#xff0c;包含…

【进阶C语言】自定义类型——结构体+枚举+联合体

文章目录一.结构体1.内存对齐存在的原因规则举例2.位段二.枚举定义枚举的优点三.联合体定义特点内存计算一.结构体 1.内存对齐 存在的原因 平台原因(移植原因)&#xff1a; 不是所有的硬件平台都能访问任意地址上的任意数据的&#xff1b;某些硬件平台只能在某些地址处取某些…

【PHPWord】使用PHPWord自动生成TOC根据内容的目录完整示例 | table of contents (TOC)

目录 一、什么是Word中的目录二、目录的生成在Word中是如何操作的三、PHPWord中目录的生成1. 插入目录2.添加页码3.修改目录的字体样式4.修改目录的目录样式5.修改生成目录的标题等级四、完整示例代码和效果图一、什么是Word中的目录 在我们日常使用中,经常需要在文档中插入目…

微信小程序项目实例——心情记事本

微信小程序项目实例——心情记事本 文章目录微信小程序项目实例——心情记事本一、项目展示二、首页三、效果图文末项目代码见文字底部&#xff0c;点赞关注有惊喜 一、项目展示 心情记事本是一款可以记录当前心情和生活的记事本 用户可以选择当前的心情&#xff08;开心、平淡…

自己写一个简单的工作流引擎V1

1.需求 市面上常见的工作流组件一般都是前端通过拖拉拽配置流程图&#xff0c;后端流程引擎解析流程配置&#xff0c;这里我们手写一个简单的流程引擎&#xff0c;先实现串行流程&#xff0c;例如下&#xff1a; 小明提交了一个申请单&#xff0c;然后经过经理审批&#xff0…

【学习】Meta Learning、

文章目录一、Meta Learning什么是元学习&#xff1f;元学习–第1步元学习–第2步元学习–步骤3架构ML和Meta回顾GD学习好的初始化参数学习学习率NAS寻找网络结构data augmentationSample ReweightingFew-shot Image Classification元学习与自我监督学习元学习和知识蒸馏元学习和…

语音识别综述

语音识别的基本单位 Phoneme&#xff1a; 音位&#xff0c;音素 a unit of sound 是声音的最基本单位**&#xff0c;每个词语token的声音由多个 phoneme 组成** Grapheme&#xff08;字位&#xff09; smallest unot of a writing system 每个单词书写最基本的单位&#xff…

Vue初识系之Webpack

文章目录一 Webpack简介二 Webpack的安装和使用2.1 安装Webpack2.2 配置参数初识2.3 使用webpack一 Webpack简介 webpack本质上是一个现代JavaScript应用程序的静态模块打包器&#xff08;modulebundler&#xff09;。当webpack处理应用程序时&#xff0c;它会递归地构建一个依…

LeetCode(String)2194. Cells in a Range on an Excel Sheet

1.问题 A cell (r, c) of an excel sheet is represented as a string “” where: denotes the column number c of the cell. It is represented by alphabetical letters. For example, the 1st column is denoted by A, the 2nd by B, the 3rd by C, and so on. is the ro…

Java抽象类:概述

1.抽象类 在Java中abstract是抽象的意思&#xff0c;可以修饰类、成员方法。 abstract修饰类&#xff1a;这个类就是抽象类。 abstract修饰方法&#xff1a;这个方法就是抽象方法。 修饰符 abstract class 类名{修饰符 abstract 返回值类型 方法名(形参列表); } public ab…

助力旅游业复苏,IPIDEA让旅游资源聚合更简单

目前我国疫情防控政策的优化&#xff0c;极大的简化了出境手续&#xff0c;对于深受疫情影响的旅游业来说&#xff0c;这无疑是一个好消息。随着旅游消费需求持续的增长&#xff0c;旅游业将会逐渐进入全面复苏的新进程&#xff0c;焕发新的活力。 全球旅游市场都在关注着中国…

ABAP 内表的定义,与PERFORM传值的定义<转载>

很早之前就想总结一下内表和定义和perform的传值定义&#xff0c;结果要么没时间&#xff0c;要么有时间忘了。 今天在网上看到一个博文写的还比较清楚&#xff0c;故读书人窃来一用 ^ - ^ 原文链接&#xff1a;https://blog.csdn.net/lmf496891416/article/details/117702217 …

5 UML views and the 9+4 UML Diagrams 关系

Refer&#xff1a;UML2.5图概述-Lib教程 UML旨在通过的建模图形Diagram&#xff0c;可视化 5 种不同的视图View。 这五个视图是&#xff1a; 一、Users View : 用户视图 1. Use case Diagram&#xff1a;用例图性 二、Structural Views : 结构视图 2. Class Diagrams&#xf…

数码钢琴行业市场运行态势及投资战略规划分析

2023-2029年中国数码钢琴行业市场运行态势及投资战略规划报告 报告编号&#xff1a;1691312 免费目录下载&#xff1a;http://www.cninfo360.com/yjbg/jdhy/sxjd/20230109/1691312.html 本报告著作权归博研咨询所有&#xff0c;未经书面许可&#xff0c;任何组织和个人不得以…

C语言进阶(5)——内存操作函数的解析

1.memcpy函数 void * memcpy ( void * destination, const void * source, size_t num ); 用途&#xff1a;各种数据类型&#xff0c;从源数组拷贝num个字节到指定目标空间里面。 要点&#xff1a; &#xff08;1&#xff09;函数memcpy从source的位置开始向后复制num个字节的数…

【虹科案例】虹科数字化仪在超声波中的应用以及如何选择合适的数字化仪

图 1&#xff1a;虹科M4i.4451-x8——用于采集超声波信号的 PCIe 4 通道 14 位 500 MS/s 数字化仪 超声波是一种频率大于人类听觉范围上限的声学声压波。超声波设备的工作频率从 20 kHz 到几千兆赫兹。表 1 总结了一系列常见的超声波应用的特性。 每个应用中使用的频率范围反映…