《EssentialC++》笔记4—以Template进行编程

Essential c++ 第六章练习及类模板template class 知识点 知识点: 1、类模板形式 template< typename elemType> class T ,其中typename可以替换为class,写成template< class elemType> class T。 调用形式:T< str 阅读详情

本文唯一重点:使用VS2019写C++,在写模版类中用到了模板参数的成员函数时,该成员函数不能放到.cpp文件里,也就是说头文件不能与cpp文件分离。这就意味着,你头文件定义的含模版的地方必须在头文件中实现,没用模版定义的地方可以放在cpp中实现。


  我们在前面已经用了很多的模板类了,比如vector<int>等等,本章的目标是打造一个模板类二叉树。

  二叉树包含两个class,一个是BinaryTree,用来储存一个指针,指向根结点,另一个是BTnode,用来存储结点值以及链接左、右两棵子树,此处“结点值的类型”便是我们想要加以参数化的地方。

  我们希望我们的BinaryTree类提供插入、移除、搜索、清除、前序遍历、中序遍历、后续遍历接口。

  我们的插入的实现满足以下性质:

  第一个插入空树的值,会成为此树的根结点。接下来每个结点必须以特定规则插入:如果小于根结点,就被放置在左子树,如果大于根结点,就被放置在右子树。任何一个值只能在树中出现一次,但是此树有能力记录同一值的插入此树,也就是说,这是一颗二叉排序树。

6.1 被参数化的类型

  我们可以用template定义模板类,可以把类型名给参数化提取出来。

template <typename elemtype>
class BinaryTree {
public:
	
private:
	BTnode<elemtype>* _root;
	//以<elemtype>类型实例化的BTnode是以<elemtype>类型实例化BinaryTree类的成员
};
template <typename elemtype>
class BTnode {
public:
	friend class BinaryTree<elemtype>;
	//因为BinaryTree需要能访问此类的数据,所以声明成friend比较好
	//这里加上<type>是表明以type实例化的类是以type实例化的BTnode的朋友
private:
	BTnode* _left;
	BTnode* _right;
	type _val;
	int count;
};

6.2 Class Template的定义

  总体框架:

template<typename elemtype>//先声明BinaryTree是一个模板类
class BinaryTree;
template <typename elemtype>
class BTnode {
public:
	friend class BinaryTree<elemtype>;
	//因为BinaryTree需要能访问此类的数据,所以声明成friend比较好
	//这里加上<elemtype>是表明以elemtype实例化的BinaryTree类是类BTnode的朋友
	BTnode(const elemtype& val);
	void insert_val(const elemtype& val);
	void remove_val(const elemtype& val, BTnode*& prev);
	static void lchild_leaf(BTnode* leaf, BTnode* subtree);
    //辅助删除的函数
	void preorder(BTnode* root, ostream& os = cout) const;
	void inorder(BTnode* root, ostream& os = cout) const;
	void postorder(BTnode* root, ostream& os = cout) const;
private:
	BTnode* _left;
	BTnode* _right;
	elemtype _val;
	int _count;
};

template <typename elemtype>
class BinaryTree {
public:
	BinaryTree();//默认构造函数
	BinaryTree(BinaryTree&);//拷贝构造函数
	~BinaryTree();//析构函数
	BinaryTree& operator=(const BinaryTree&);
	bool empty() { return _root == nullptr; }
	void clear()//清空(释放堆区内存)这个链式二叉树的函数接口
	{
		if (_root)
		{
			clear(_root);
			_root = nullptr;
		}
	}
	void insert(const elemtype& val);//插入
	void remove(const elemtype& val);//删除
	void preorder(ostream& os = cout);
	void inorder(ostream& os = cout);
	void postorder(ostream& os = cout);
	
private:
	BTnode<elemtype>* _root;
	//以<elemtype>类型实例化的BTnode是以<elemtype>类型实例化BinaryTree类的成员
	void copy(BTnode<elemtype>*& tar, BTnode<elemtype>*& src);
	//将src所指的子树复制到tar所指子树
	void remove_root();//辅助删除的函数
	void clear(BTnode<elemtype>*);//递归实现删除链式二叉树的函数
	void _copy(BTnode<elemtype>*& tar, BTnode<elemtype>*& src);
    //递归实现赋值功能的子函数
};

  以template编程时,在类体外给成员函数的定义的语法比较复杂,如下:

template <typename elemtype>
inline BinaryTree<elemtype>::BinaryTree()
{
	_root = nullptr;
}

  首先以一个模板开始,第一个BinaryTree的作用是以elemtype实例化BinaryTree,然后提供一个类作用范围标识符,第二个BinaryTree()就在BinaryTree<elemtype>内了,下面是其他几个的实现。

//拷贝构造函数
template <typename elemtype>
inline BinaryTree<elemtype>::BinaryTree(const BinaryTree& Bit)
{
	copy(_root, Bit._root);
}

//析构函数
template <typename elemtype>
inline BinaryTree<elemtype>::~BinaryTree()
{
	clear();
}

//赋值操作符重载
template <typename elemtype>
inline BinaryTree<elemtype>& BinaryTree<elemtype>::operator=(const BinaryTree& BiT)
{
	if (this != &BiT)
	{
		clear();
		copy(_root, BiT._root);
	}
	return *this;
}

6.3 Template类型的处理

  考虑到我们指定的类型可能是某种类,所以我们在设计如find函数的时候,为了效率最好传引用,并且写构造函数的时候,_val最好用成员逐次初始化列表(这样如果_val的类型是某种类,就可以调用它的构造函数,因为它可能没有重载=运算符)。

template<typename elemtype>
BTnode<type>::BTnode(const elemtype& val):_val(val)
{
	_left = _right = nullptr;
	_count = 1;
}

//不建议下面这样写
template<typename elemtypetype>
BTnode<type>::BTnode(const elemtypetype& val)
{
    _val = val;
	_left = _right = nullptr;
	_count = 1;
}

  放在函数体内在elemtype是类的时候,效率明显比前者低。

  因为,构造函数体内对_val的赋值操作可以分成两个步骤:

  1. 函数体执行前,类的默认构造函数会先作用在_val上。
  2. 函数体会以赋值运算符将val复制给_val。

  如果我们使用放在函数体外的成员逐次初始化列表,就只需要一个步骤就可以完成工作:以复制构造函数将val复制给_val。

6.4 实现Class Template

  首先是向树插入结点:(注意模板参数名字要写的一样)

template <typename elemtype>
void BinaryTree<elemtype>::insert(const elemtype& val)
{
	if (_root == nullptr)
		_root = new BTnode<elemtype>(val);
	else
		_root->insert_val(val);
}

  new表达式的动作可以分成两步:

  1. 向程序的空闲空间请求内存。如果分配到足够的空间,就返回一个指针,指向新对象。(如果空间不足,就会抛出bad_alloc异常)
  2. 如果第一步成功了,并且外界制定了一个初值,这个新对象便会以最恰当的方式初始化。

  我们这里的恰当方式明显是会调用BTnode的构造函数。

  接下来是插入结点,在二叉排序树讲过,就是一个递归的过程。

template<typename elemtype>
void BTnode<type>::insert_val(const elemtype& val)
{
	if (_val == val)
	{
		++_count;
		return;
	}
	if (val < _val)//如果传入的值小于根结点 就插入在左子树
	{
		if (_left != nullptr)
		{
			_left->insert_val(val);
		}
		else
		{
			_left = new BTnode<elemtype>(val);
			return;
		}
	}
	else
	{
		if (_right != nullptr)
		{
			_right->insert_val(val);
		}
		else
		{
			_right = new BTnode<elemtype>(val);
			return;
		}
	}
}

  这里的移除算法也在二叉排序树中讲过,我们就不讲了。

template <typename elemtype>
void BTnode<type>::lchild_leaf(BTnode* leaf, BTnode* subtree)
{
	while (subtree->_left)
		subtree = subtree->_left;
	subtree->_left = leaf;
}
template <typename elemtype>
void BinaryTree<elemtype>::remove_root()
{
	if (!_root)
		return;
	BTnode<elemtype>* tmp = _root;
	if (_root->_right)
	{
		_root = _root->_right;
		if (tmp->_left)
		{
			BTnode<elemtype>* lc = tmp->_left;
			BTnode<elemtype>* newlc = _root->_left;
			if (!newlc)
				_root->_left = lc;
			else
				BTnode<elemtype>::lchild_leaf(lc, newlc);
		}
	}
	else
		_root = _root->_left;
	delete tmp;
}
template <typename elemtype>
void BTnode<type>::remove_val(const type& val, BTnode*& prev)
//为了修改指针的值,传
{
	if (val < _val)
	{
		if (!_left)
			return;
		else
			_left->remove_val(val, _left);
	}
	else if (val > _val)
	{
		if (!_right)
			return;
		else
			_right->remove_val(val, _right);
	}
	else
	{
		if (_right)
		{
			prev = _right;
			if (_left)
				if (!prev->_left)
					prev->_left = _left;
				else
					BTnode<type>::lchild_leaf(_left, prev->_left);
		}
		else
			prev = _left;
		delete this;
	}
}

  这里遇到了一个VS编译器的问题,如果头文件中用template声明的部分,必须在头文件中实现。

  其他一些功能没有什么新的知识,我们就不单独拿出来直接放到汇总里了。

汇总:

//Template.h
#pragma once
using namespace std;
#include <vector>
#include <string>
#include <algorithm>
#include <iterator>
#include <iostream>

template<typename elemtype>
class BinaryTree;
template <typename elemtype>
class BTnode {
public:
	friend class BinaryTree<elemtype>;
	//因为BinaryTree需要能访问此类的数据,所以声明成friend比较好
	//这里加上<type>是表明以type实例化的类是以type实例化的BTnode的朋友
	BTnode(const elemtype& val);
	void insert_val(const elemtype& val);
	void remove_val(const elemtype& val, BTnode*& prev);
	static void lchild_leaf(BTnode* leaf, BTnode* subtree);
	void preorder(BTnode* root, ostream& os = cout) const;
	void inorder(BTnode* root, ostream& os = cout) const;
	void postorder(BTnode* root, ostream& os = cout) const;
private:
	BTnode* _left;
	BTnode* _right;
	elemtype _val;
	int _count;
};

template<typename elemtype>
BTnode<elemtype>::BTnode(const elemtype& val):_val(val)
{
	_left = _right = nullptr;
	_count = 1;
}

template <typename elemtype>
class BinaryTree {
public:
	BinaryTree();
	BinaryTree(BinaryTree&);
	~BinaryTree();
	BinaryTree& operator=(const BinaryTree&);
	bool empty() { return _root == nullptr; }
	void clear()
	{
		if (_root)
		{
			clear(_root);
			_root = nullptr;
		}
	}
	void insert(const elemtype& val);
	void remove(const elemtype& val);
	void preorder(ostream& os = cout);
	void inorder(ostream& os = cout);
	void postorder(ostream& os = cout);
	
private:
	BTnode<elemtype>* _root;
	//以<elemtype>类型实例化的BTnode是以<elemtype>类型实例化BinaryTree类的成员
	void copy(BTnode<elemtype>*& tar, BTnode<elemtype>*& src);
	//将src所指的子树复制到tar所指子树
	void remove_root();
	void clear(BTnode<elemtype>*);
	void _copy(BTnode<elemtype>*& tar, BTnode<elemtype>*& src);
};
template <typename elemtype>
inline BinaryTree<elemtype>::BinaryTree()
{
	_root = nullptr;
}

template <typename elemtype>
inline BinaryTree<elemtype>::BinaryTree(BinaryTree& Bit)
{
	copy(_root, Bit._root);
}

template <typename elemtype>
inline BinaryTree<elemtype>::~BinaryTree()
{
	clear();
}

template <typename elemtype>
inline BinaryTree<elemtype>& BinaryTree<elemtype>::operator=(const BinaryTree& BiT)
{
	if (this != &BiT)
	{
		clear();
		copy(_root, BiT._root);
	}
	return *this;
}

template <typename elemtype>
void BinaryTree<elemtype>::insert(const elemtype& val)
{
	if (_root == nullptr)
		_root = new BTnode<elemtype>(val);
	else
		_root->insert_val(val);
}

template<typename elemtype>
void BTnode<elemtype>::insert_val(const elemtype& val)
{
	if (_val == val)
	{
		++_count;
		return;
	}
	if (val < _val)//如果传入的值小于根结点 就插入在左子树
	{
		if (_left != nullptr)
		{
			_left->insert_val(val);
		}
		else
		{
			_left = new BTnode<elemtype>(val);
			//_left->_val = val;
			return;
		}
	}
	else
	{
		if (_right != nullptr)
		{
			_right->insert_val(val);
		}
		else
		{
			_right = new BTnode<elemtype>(val);
			//_right->_val = val;
			return;
		}
	}
}

template <typename elemtype>
void BinaryTree<elemtype>::remove(const elemtype& val)
{
	if (_root)
	{
		if (_root->_val == val)
			remove_root();
		else
			_root->remove_val(val, _root);
	}
}

template <typename elemtype>
void BTnode<elemtype>::lchild_leaf(BTnode* leaf, BTnode* subtree)
{
	while (subtree->_left)
		subtree = subtree->_left;
	subtree->_left = leaf;
}

template <typename elemtype>
void BinaryTree<elemtype>::remove_root()
{
	if (!_root)
		return;
	BTnode<elemtype>* tmp = _root;
	if (_root->_right)
	{
		_root = _root->_right;
		if (tmp->_left)
		{
			BTnode<elemtype>* lc = tmp->_left;
			BTnode<elemtype>* newlc = _root->_left;
			if (!newlc)
				_root->_left = lc;
			else
				BTnode<elemtype>::lchild_leaf(lc, newlc);
		}
	}
	else
		_root = _root->_left;
	delete tmp;
}

template <typename elemtype>
void BTnode<elemtype>::remove_val(const elemtype& val, BTnode*& prev)
{
	if (val < _val)
	{
		if (!_left)
			return;
		else
			_left->remove_val(val, _left);
	}
	else if (val > _val)
	{
		if (!_right)
			return;
		else
			_right->remove_val(val, _right);
	}
	else
	{
		if (_right)
		{
			prev = _right;
			if (_left)
				if (!prev->_left)
					prev->_left = _left;
				else
					BTnode<elemtype>::lchild_leaf(_left, prev->_left);
		}
		else
			prev = _left;
		delete this;
	}
}

template <typename elemtype>
void BinaryTree<elemtype>::clear(BTnode<elemtype>* pt)
{
	if (pt)
	{
		clear(pt->_left);
		clear(pt->_right);
		delete pt;
	}
}

template <typename elemtype>
void BinaryTree<elemtype>::copy(BTnode<elemtype>*& tar, BTnode<elemtype>*& src)
{
	if (tar != nullptr)
	{
		clear(tar);
	}
	_copy(tar, src);
}

template <typename elemtype>
void BinaryTree<elemtype>::_copy(BTnode<elemtype>*& tar, BTnode<elemtype>*& src)
{
	if (src == nullptr)
	{
		tar = nullptr;
		return;
	}
	tar = new BTnode<elemtype>(src->_val);
	_copy(tar->_left, src->_left);
	_copy(tar->_right, src->_right);
}

template <typename elemtype>
void BTnode<elemtype>::preorder(BTnode* root, ostream& os) const
{
	if (root)
	{
		os << root->_val << ' ';
		if (root->_left)
			preorder(root->_left, os);
		if (root->_right)
			preorder(root->_right, os);
	}
}

template <typename elemtype>
void BTnode<elemtype>::inorder(BTnode* root, ostream& os) const
{
	if (root)
	{
		if (root->_left)
			preorder(root->_left, os);
		os << root->_val << ' ';
		if (root->_right)
			preorder(root->_right, os);
	}
}

template <typename elemtype>
void BTnode<elemtype>::postorder(BTnode* root, ostream& os) const
{
	if (root)
	{
		if (root->_left)
			preorder(root->_left, os);
		if (root->_right)
			preorder(root->_right, os);
		os << root->_val << ' ';
	}
}

template <typename elemtype>
void BinaryTree<elemtype>::preorder(ostream& os)
{
	_root->preorder(_root, os);
}

template <typename elemtype>
void BinaryTree<elemtype>::inorder(ostream& os)
{
	_root->inorder(_root, os);
}

template <typename elemtype>
void BinaryTree<elemtype>::postorder(ostream& os)
{
	_root->postorder(_root, os);
}
#include "Template.h"
int main()
{
	BinaryTree<string> bt;
	bt.insert("Piglet");
	bt.insert("Eeyore");
	bt.insert("Roo");
	bt.insert("Tigger");
	bt.insert("Chris");
	bt.insert("Pooh");
	bt.insert("Kanga");
	bt.inorder();
	cout << '\n';
	BinaryTree<string> bt1(bt);
	bt1.preorder();
	bt.remove("Piglet");
	cout << '\n';
	bt.preorder();
}

6.5 以一个模板函数完成输出运算符

  输出二叉树的元素类型不同,我们自然想到利用template机制实现一个通用类型输出的函数。

template <typename elemtype>
inline ostream& operator<<(ostream& os, BinaryTree<elemtype>& bt)
{
	os << "PreorderTraverse: ";
	bt.preorder(os);
	os << '\n';
	os << "InorderTraverse: ";
	bt.inorder(os);
	os << "\nPosorderTraverse: ";
	bt.postorder(os);
    os << endl;
	return os;
}

6.6 常量表达式做template参数和默认参数值

  Template的参数并不一非得是某种类型不可,我们也可以用常量表达式作为template的参数,例如我们对数列类继承体系可以template化,将“对象所含元素个数”参数化。

template <int len>
class num_sequence {
public:
    num_sequence(int beg_pos = 1);
    //
};

template<int len>
class Fibonacci : public num_sequence<len> {
public:
    Fibonacci(int beg_pos = 1)
        :num_sequence<int>(beg_pos){}
}

  当我们产生Fibonacci对象的时候:

Fibonacci<16> fib1;
Fibonacci<17> fib2(17);

  其基类num_sequence会因为参数len而导致元素个数为16和17,同样的,我们还可以把长度和起始位置一起初始化:

template <int len, int beg_pos>
class num_sequence;

  大部分数列起始位置是1,如果能为起始位置提供默认值就更好了,template机制是可以做到这一点的。

template <int len, int beg_pos = 1>

  以template重写一下之前的数列类,这样的好处是不用再储存长度和起始位置这俩data member了。

//模板类num_sequence的总体框架
template <int len, int beg_pos>
class num_sequence {
public:
	virtual ~num_sequence() {};
	int elem(int pos) const;
	const char* what_am_i() const
	{
		return typeid(*this).name();
	}
	static int max_elems() { return _max_elems; }
	ostream& print(ostream& os = cout) const;
protected:
	virtual void gen_elems(int pos) const = 0;
	num_sequence(vector<int>* ps):_pelems(ps) {}
	static const int _max_elems = 1024;
	vector<int>* _pelems;
};

  输出运算符:

template <int len, int beg_pos>
ostream& operator<<(ostream& os, const num_sequence<len, beg_pos>& ns)
{
	return ns.print(os);
}

  检查以及补充数列元素的函数:

template <int len, int beg_pos>
int num_sequence<len, beg_pos>::elem(int pos) const
{
	if (!check_integrity(pos, _pelems->size()))
		return 0;
	return (*_pelems)[pos - 1];
}

template <int len, int beg_pos>
bool num_sequence<len,beg_pos>::check_integrity(int pos, int size) const
{
	if (pos > max_elems() || pos <= 0)
		return false;
	if (size < pos)
		gen_elems(pos);
	return true;
}

  打印函数:

template <int len, int beg_pos>
ostream& num_sequence<len, beg_pos>::print(ostream& os) const
{
	int elem_pos = beg_pos - 1;
	int end_pos = elem_pos + len;
	if (!check_integrity(end_pos, _pelems->size()))
		return os;
	os << "("
		<< beg_pos << ","
		<< len 
		<< ")";
	while (elem_pos < end_pos)
		os << (*_pelems)[elem_pos++] << ' ';
	return os;
}

  Fibonacci类:

template <int len, int beg_pos = 1>
class Fibonacci :public num_sequence<len, beg_pos>
{
public:
	Fibonacci() :num_sequence<len, beg_pos>(&_elems);
protected:
	static vector<int> _elems;
	virtual gen_elems(int pos) const;
};

  完成它的虚函数部分

template <int len, int beg_pos>
static vector<int> Fibonacci<len, beg_pos>::_elems;

template<int len, int beg_pos>
void Fibonacci<len, beg_pos>::gen_elems(int pos) const
{
	if (_elems.empty())
	{
		_elems.push_back(1);
		_elems.push_back(1);
	}
	if (_elems.size() < pos)
	{
		int i = _elems.size();
		int pprev = _elems[i - 2];
		int prev = _elems[i - 1];
		for (; i <= pos; i++)
		{
			int elem = pprev + prev;
			_elems.push_back(elem);
			pprev = prev;
			prev = elem;
		}
	}
}

  测试

int main()
{
	Fibonacci<6,12> fib1;
	Fibonacci<1, 10> fib2;
	Fibonacci<8> fib3;
	Fibonacci<8, 8> fib4;
	Fibonacci<8, 12> fib5;
	cout << "fib1:" << fib1
		<< "\nfib2:" << fib2
		<< "\nfib3:" << fib3
		<< "\nfib4:" << fib4
		<< "\nfib5:" << fib5
		;
}

在这里插入图片描述

  我们知道,函数名是一种地址常量,因此它也可以做为template的参数,如下:

//pf是一个指向特定的函数类型,产生pos个元素,放到vector seq内的函数指针
template<void (*pf) (int pos, vector<int>& seq)>
class num_sequence
{
public:
    num_sequence(int len, int beg_pos = 1)
    {
        assert(pf);
        _len = len > 0 ? len : 1;
        _beg_pos = beg_pos > 0 ? beg_pos : 1;
        pf(beg_pos + len - 1, _elems);
    }
private:
    int _len;
    int _beg_pos;
    vector<int> _elems;
};

  使用的时候我们就可以只去实现那个产生数列到pos位置的元素并放到vector容器里的函数就行。

void Fibonacci(int pos, vector<int>& seq)
{
    if (seq.empty())
    {
        seq.push_back(1);
        seq.push_back(1);
    }
    if (pos > seq.size())
    {
        int i = seq.size();
        int prev = seq[i - 1];
        int pprev = seq[i - 2];
        for(;i <= pos; i++)
        {
            int elem = prev + pprev;
            seq.push_back(elem);
            pprev = prev;
            prev = elem;
        }
    }
}

int main()
{
    num_sequence<Fibonacci> fib(1,10);
    return 0;
}

6.7 以Template参数作为一种设计策略

  现在我们可以利用template机制把LessThan函数对象要绑定的元素类型参数化化:

template <typename elemtype>
class LessThan
{
public:
   LessThan(elemtype& val):_val(val){}
   bool operator()(elemtype val) const
   {
       return val < _val;
   }
   void re_set(elemtype& val)
   {
       _val = val;
   }
   elemtype& get_set() const
   {
       return _val;
   }
private:
    elemtype _val;
};

  这样写有一个潜在问题,如果用户提供的类型没有指定小于运算符怎么办呢?

  一种可行的策略是这样的,我们同样以一个template参数Cmp来传递用户自己定义的小于运算符,如果用户没有定义小于运算符,我们也救不了了,就默认使用less<elemtype>运算符吧。

template <typename elemtype, typename Cmp = less<elemtype>>
class Lessthan
{
    public:
   Lessthan(elemtype& val):_val(val){}
   bool operator()(elemtype val) const
   {
       return Cmp(val,_val);
   }
   void re_set(elemtype& val)
   {
       _val = val;
   }
   elemtype& get_set() const
   {
       return _val;
   }
private:
    elemtype _val;
}

  比如用户指定以下比较大小的函数:

bool cmp(string& s1, string& s2)
{
    return si.size() < s2.size();
}

  由于存在默认参数值,可以这样使用:

Lessthan<int> lt(72);
//相当于Lessthan<int,less<int>> lt(72);
Lessthan<string, cmp> lt("hhahahahaha");

6.8 模板成员函数

  类中的成员函数也可以定义为模板函数,以一个打印类为例:

class PrintIt {
public:
    PrintIt(ostream& os):_os(os){}
    template <typename elemtype>
    void print(elemtype& elem, elemtype& delimiter = '\n')
    {
        _os << elem << delimiter;
    }
private:
    ostream& _os;
};

  甚至我们可以再把输出流也参数化一下:

template <typename Ostream>
class PrintIt
{
public:
    PrintIt(Ostream& os):_os(os){}
    template <typename elemtype>
    void print(elemtype& elem, elemtype& delimiter = '\n')
    {
        _os << elem << delimiter;
    }
private:
    Ostream _os;
};
template进行编程 前言:Template(模板)能根据用户指定的特定值或特定类型,自动产生一个函数或类。 为什么要有模板呢,举一个例子: (假定已经想好要定义的BTnode,用以存储结点数值,和左右孩子指针) •这是二叉树的结点定义。 •为了存储不同类型的值,我们必须还要实现不同的BT_node类,比如int_BTnode,double_BTnode类 •template机制把“与类型相关的”和“独立于类型之外”的两部分分离开来。 •一些成员函数的操作行为,不会随着包含这些成员函数的类不同而不同,普通模板也是。. 阅读详情

相关推荐

速腾16线激光雷达(RS-16)在ubuntu18.04系统下跑SC-A-LOAM算法

说明: 简要记录一下速腾16线激光雷达即RS-16在ubuntu18.04系统下跑SC-A-LOAM算法的过程,因为SC-A-LOAM算法源码不支持RS雷达的数据,大致思想就是先将rs雷达发布的激光数据转换为velodyne类型的数据,考虑到接下来还要基于RS-16实时运行SC-A-LOAM算法,这里提前将RS_SDK驱动安装好。 1 搭建SC-A-LOAM算法运行环境 首先需要在电脑上搭建SC-A-LOAM算法的运行环境,见文章《ubuntu18.04搭建SC-A-LOAM环境并运行》 本文所使用的系统架

超爱吃小蛋糕的66的博客 2372

EssentialC++_chapter06 binary tree 的学习笔记记录及问题

Essential C++ chapter06 binary tree 的学习笔记记录及问题 Author:Dargon Note date:2021/02/28 Source: 《Essential C++》 and 《C ++ primer plus》 Note: 关于二叉树的构建 插入 移除 和打印 利用C++ template 文章目录Essential C++ chapter06 binary tree 的学习笔记记录及问题1 BTnode class template 的构建2 Binary

Dallas01的博客 387

MATLAB编程实战:通过SCPI命令高效读取普源示波器数据

本文详细介绍了如何利用MATLAB通过SCPI命令高效读取普源示波器数据,实现自动化测试与数据分析。文章从硬件连接到软件配置,再到核心代码编写与数据解析,提供了完整的实战指南,帮助工程师和科研人员摆脱手动操作,直接从示波器内存中精准读取数据,并利用MATLAB进行FFT频谱分析,大幅提升工作效率。

weixin_29176179的博客 544

6、以template进行编程

1、类型限定 为class template定义一个inline函数,做法就像为non-template class定义一个inline函数一样;类似empty()函数。但是在类体外,class template member function的定义语法却不一样。 template <typename elemType> class BinaryTree { public: BinaryTree(); BinaryTree( const vector< elemType >

伍粟的博客 446

告别第三方插件:官方Fused Library如何重塑Android库的合并与发布流程

本文详细介绍了Android官方推出的Fused Library如何替代第三方fat-aar插件,优化Android库的合并与发布流程。通过原生支持、智能依赖管理等优势,Fused Library解决了版本兼容性、依赖传递等核心痛点,提供了更稳定高效的开发体验。文章还提供了迁移实操指南和工程实践建议,帮助开发者顺利过渡到官方方案。

weixin_42561040的博客 252

基于Python的Booking酒店数据爬虫实战:最新技术与完整代码解析

本文将详细介绍如何使用Python构建一个高效的Booking酒店数据爬虫,涵盖从基础爬取到高级反反爬技术的完整实现。我们将使用最新的Python爬虫技术栈,包括requests-html、Playwright、智能代理轮换等技术,并提供完整的可运行代码示例。文章将深入探讨爬虫的各个组成部分,包括数据提取、存储、反反爬策略以及性能优化等方面。关键词:Python爬虫、Booking数据采集、反反爬技术、Playwright、数据存储。

2201_76125261的博客 936

Essential c++ 第六章二叉树的完整实现

初步深入学习C++,还很菜,自己整理的,有些不完整,但也不例外愿拿来供大家来参考,不完整的地方还望大家来补充!  #include #include  using namespace std;// class BTnode declaretemplate class BinaryTree;template class BTnode{ public:  frien

笃行致远 1018

Oracle EBS AP发票被预付款核算创建会计科目时间超长

在客户职能部门的水电、通信和物业等费用统一管理或对接部门报销时,用户将所有费用分摊到各个末级部门,导致AP发票行数高达上千行。这引发了以下问题:1)用户过账时,创建会计科目的请求执行超过20小时未完成,只能手动取消;2)取消请求后,系统一直处于回滚状态;3)通过SQL脚本分析,发现预付发票核销分配行表记录数量庞大,单笔AP发票记录高达90多万条,类似发票共有5笔。

陈显平的专栏-Oracle EBS 352

C++11新特性之常量表达式 constexpr

1 常量表达式在C++11中引入了一个新的关键字“constexpr”,用来修饰一个函数或者变量使其变成一个常量表达式或值。比如下面的代码将函数GetConstValue变成一个常量表达式,并用这个常量表达式声明一个数组:constexpr int GetConstValue() {return 10;}; int a[GetConstValue()];有了constexpr关键字,编译器就可以在...

姜的理想国 3156

Windows 11 - 打开操作安全中心提示 “需要使用新应用以打开此 Windowsdefender 链接“ 解决方案(电脑更新了 Win11 系统版本后,想关闭病毒防护盾牌的时候提示打不开)

Windows 11 - 打开操作安全中心提示 "需要使用新应用以打开此 Windowsdefender 链接" 解决方案(电脑更新了 Win11 系统版本后,想关闭病毒防护盾牌的时候提示打不开),当您点击任务栏上要关闭安全中心防护设置时,这种情况打不开怎么解决,怎么办,Windows安全中心,病毒和威胁保护,账户保护,防火墙和网络保护,设备安全性。

🏆 前端领域优质创作者 1万+

ardupilot BMI088加速度陀螺仪学习

本节主要记录ardupilot 中BMI088传感器的学习过程,欢迎批评指正。BMI088是一款高性能6轴惯性传感器,由16位数字三轴±24g加速度计和16位数字三轴±2000°/ s陀螺仪组成。 BMI088允许高精度测量方向和沿三个正交轴的运动检测。BMI088具有高振动鲁棒性和3 x 4.5 x0.95mm³的小尺寸,是用于无人机和机器人应用等恶劣环境的高性能IMU中的独特产品。 BMI088专门设计用于有效抑制由于PCB上的共振或整个系统的结构而可能发生的振动。 除了高振动稳定性外,BMI088的出

陌城烟雨 2053

EssentialC++ 以template进行编程

这一章通过讲解二叉树的template的实现过程,来讲解template的语法,以及一些需要注意的地方。 首先了解一下二叉树的一些基本操作,二叉树支持插入,删除,遍历的操作。第一个安插至空白树的值,会成为此树的根节点。接下来的每个节点按特定的规则插入。如果小于根节点,就被置于左侧指数,大于根节点就被置于右子树。string类型按照字典排序。如下图 遍历又分前序遍历,中序遍历,后序遍历。

sweetdark的专栏 2080

AutoClip AI视频剪辑工具:Docker一键部署指南

摘要:AutoClip是一个基于Docker容器化部署的智能视频剪辑系统,采用微服务架构设计,包含Web界面、后端API、异步任务处理和Redis缓存等组件。系统支持一键快速部署,提供完整的监控和日志功能,最低要求4GB内存和10GB存储空间。部署过程仅需三个步骤:克隆代码、配置环境变量、启动容器服务。生产环境支持性能调优、数据持久化和网络安全管理。该系统解决了环境配置难题,实现"一次配置,到处运行"的目标,适合需要自动化视频处理的用户。

wjianwei666的专栏 1001

(转)EssentialC++ 以template进行编程

这一章通过讲解二叉树的template的实现过程,来讲解template的语法,以及一些需要注意的地方。 首先了解一下二叉树的一些基本操作,二叉树支持插入,删除,遍历的操作。第一个安插至空白树的值,会成为此树的根节点。接下来的每个节点按特定的规则插入。如果小于根节点,就被置于左侧指数,大于根节点就被置于右子树。string类型按照字典排序。如下图     遍历又分前序遍历,中序遍历,...

wpf814533631 248

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Essential C++ 以template进程编程

通过`Essential C++`笔记的形式对C++相关`重点知识`进行汇总,读者通读此系列文章就可以轻松的把该语言基础捡起来。

yaoming168的专栏 376

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

【Essential C++学习笔记】第六章 以Template进行编程

工作之余,学习C++

weixin_43284350的博客 1168

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

《Essential C++》读书笔记 之 目录导航

《Essential C++》读书笔记 之 目录导航 2014-07-06   第一章:《Essential C++》读书笔记 之 C++编程基础 第二章:《Essential C++》读书笔记 之 面向过程编程风格 第三章:《Essential C++》读书笔记 之 泛型编程风格 第四章:《Essential C++》读书笔记 之 基于对象编程风格 第五章:《Essential C...

weixin_34162629的博客 123

(转)C++程序员的阅读清单

转载自:http://blog.jobbole.com/21544/ 多读一些优秀的书籍,对于开发者稳固编程基础、提高编程技能有很大帮助。但是,大多时候,初学者不知道应该读什么书入门,有一定基础的开发者不知道如何进阶。 本文总结了一个C++优秀书籍清单,希望能够为你带来一定的帮助。这些书大多数都有中文版,并且可以很容易在网上找到。 阶段 1 《Essential C++》 这是一本内容不...

weixin_30881367的博客 86

c++优秀书籍清单

多读一些优秀的书籍,对于开发者稳固编程基础、提高编程技能有很大帮助。但是,大多时候,初学者不知道应该读什么书入门,有一定基础的开发者不知道如何进阶。     本文总结了一个 C++ 优秀书籍清单,希望能够为你带来一定的帮助。这些书大多数都有中文版,并且可以很容易在网上找到。     阶段 1     《Essential C++》   这是一本内容不多但很实用的 C++ 入门书籍,强

cdrqingtian的专栏 424

【Essential C++课后练习】纯代码(更新中)

【代码】【Essential C++课后练习】

weixin_43284350的博客 1070

C++中的类模板详细讲述

一、类模板定义及实例化 1. 定义一个类模板: 1 templateclass 模板参数表> 2 3 class 类名{ 4 5 // 类定义...... 6 7 };   其中,template 是声明类模板的关键字,表示声明一个模板,模板参数可以是一个,也可以是多个,可以是类型参数 ,也可以是非类型参数。类型参数由关键字class或type

Neo的专栏 1421

【C++】模板

前言 在前面认识模板章节,我们大概解了模板在函数和类中的简单使用,但是模板可不止这么简单,它还有更多的用法,本章就来对一些用法进行学习。 1. 非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。 非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。 例如: #include<iostream> using namespace std; .

Edward_Asia的博客 3692

[ C++ ] template 模板进阶 (特化,分离编译)

本篇内容包括C++ 非典型模板参数,类模板的特化,模板的分离编译。

优快云博客 2220

6-以template进行编程

在数据结构中,所谓树(tree)乃是由节点(node或vertice)以及连接不同节点的链接(link)组成。所谓二叉树,维护着每个节点与下层另两个节点间的两条链接,一般将此下层二节点称为左子节点(left child)和右子节点(right child)。最上层第一个节点称为根节点(root)。无论是左子节点或右子节点,都可能扮演另一棵“子树(subtree)”的根节点。一个节点如果不再有任何子节点,便称为叶节点(leaf)。 我们的二叉树包含两个class:一个是BinaryTree,用以储存一个指针,

itzyjr的专栏 546

模板类声明和定义为何要写一起

http://www.gois.ws/showfile.asp?id=460:0:0如何组织编写模板程序 发表日期: 1/21/2003 12:28:58 PM 发表人: Nemanja Trifunovic 前言常遇到询问使用模板到底是否容易的问题,我的回答是:“模板的使用是容易的,但组织编写却不容易”。看看我们几乎每天都能遇到的模板类吧,如STL, ATL, WTL, 以及Boost的

~。~||| 1万+

模板化编程

模板概念: 最初设计C++时,template称为被参数化的类型。参数化是因为类型的相关信息可以从template定义中剥离。类型是指每一个类模板或函数模板基本上都随着它作用或它所内含的类型而有性质上的变化。 而后template被通俗的称为模板。作用:可以根据用户指定的特定类型或特定值,自动产生一个函数或类。 实现模板: 在学习或是做题中我们...

Nemo 1209
上一篇: 《EssentialC++》笔记3—面向对象的编程风格
下一篇: 八大排序的思想讲解与排序算法可视化
魔术师的徒弟
博客等级 码龄5年 1199粉丝 142原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值