[C++] Generic programming

        In order to make functions or classes more reusable, C++ introduces template technology. The idea of ​​allowing different data types to be used in the same function or class is also called generic programming.


1. Template

void Swap(int &left,int & right)
{
    int temp =left;
    left = right;
    right = temp;
}

        When learning C language, write a function to exchange variable data, you will find a problem that this function can only be used to exchange variables of type int, so what should I do if I want to exchange variables of other types, write a few more corresponding function? This method is actually not very good, because it will cause code redundancy.

        Using C++ templates here can easily solve

template<typename T> 
void Swap(T& left, T& right)
{
    T temp=left;
    left=right;
    right=temp;
}

int i=1,j=2;
double x=1.1,y=2.2;
Swap(i,j);
Swap(x,y);

        The template format template<typename (class can also be used) + the type name T after it is taken randomly>, Ty, K, V, usually capital letters or the first letter of a word is capitalized.

        T represents a template type (it can be used as a variable type such as int, char, or a custom type).

PS: Here, an intermediate variable is used to exchange variables, so can we directly use XOR to exchange? In fact, it is not possible, because XOR exchange can only be used with int type.

PS: The two calls of int and double types here are not actually calling the same function.

template<class T>
T Add(const T& left,const T& right)
{
    return left+right;
}

//编译器自动推演,隐式实例化
Add(1.1,2);     //这里推演实例化出错

//强制类型转换后可以继续使用
Add((int)1.1,2);
Add(1.1,(double)2);

        The template has an implicit type conversion, and the compiler can judge the type of variable based on the data directly by inputting the data, but it should be noted that if the two variable types are different, then it will not know which type you want to use variable.

//或者定义两个参数

template<class T1,class T2>
T1 Add(const T1& left,const T2& right)
{
    return left+right;
}

//这里就可以编译器自动推导。

Add(1.1,2);
Add(1,2.2); //这里会出警告,double到int会损失精度

        Define two types of parameters in the template, here the compiler will help you automatically convert the data type, here the double type is calculated with the int type, and the int type will be promoted to the double type.

//显示实例化
Add<int>(1.1,2);

        This method can also be used to solve it, and clearly tell the compiler that I just want to use a variable of type int.

template<class T>
T* func(int n)
{
    T* a = new T[n];
    return a;
}


//所以这里必需显示实例化
func<int>(20);

        This kind of template function cannot be automatically deduced, and the compiler does not know the type of the return value, so it must be clearly informed of the type of the variable to be returned when using it.

int Add(int left,int right)
{
    
}

template<class T>
T Add(T left,T right)
{
	return left+right;    
}


Add(1,2);

        Template functions and ordinary functions can exist at the same time. When calling, it will first check whether there is a function that specifically handles the int type, and if not, call the template.

       

Two, class template

         

typedef char STDateType;

class Stack
{
    private:
    	STDateType *a;
    	int top;
    	int capacity;
};

Stack st1;
Stack st2;

        In C language, in order to facilitate the use of variables of different types, macro definitions are generally used here. But it didn't solve the problem. Among the two objects, one stores the variable int and the other stores double.

        That is to say, all objects defined can only store one type of data . But in actual use, it needs to be flexible, and multiple types should be used together, including custom types. So classes can also be used as templates.

PS: The function can deduce the data type according to the incoming data, but the class template cannot, so when using the template class to instantiate an object, the instantiation must be displayed.

template<typename T>
class Stack
{
	public:
    	Stack(size_t capacity = 4)
            :_a(nullptr)
          	 ,_capacity(0)
             ,_top(0);
        {
            if(capacity > 0)
            {
                _a = new T[capacity];  
               _capacity = capacity;
                _top = 0;
            }
        }
    	
    	~Stack()
        {
            delete[] _a;
            _a = nullptr;
            _capacity = _top =0;
        }
    
    	void Push(const T& x)
        {	
             //如果插入满了,开辟新空间
             if(_top == _capacity){
				size_t NewCapacity = _capatcity == 0 ? 4 : _capacity*2;
                T* tmp =new T[NewCapacity];
                if(_a){
                    memcpy(tmp,_a,sizeof(T)*_top);
                    delete[] _a;
				}
                
                 _a = tmp;
                 _capacity = NewCapacity;
             }
            
            _a[_top]=x;
            ++_top;
        }
    	
    	void Pop()
        {
            assert(_top > 0);
            --_top;
        }
    
    
    	bool Empty()
        {
            return _top == 0;
        }
    
    	T& Top() //这边使用了引用,那么可以修改,不想修改要加const。
        {
            assert(_top > 0);
            return _a[_top-1];
        }
    
    private:
    	T *_a;
    	int _top;
    	int _capacity;

};

Stack<int> st1;
Stack<char> st2;
    
st1.Top()++;

        The two Stack type variables here can store various types of data, including custom types, and the template is actually created to better support custom types.

PS: The template does not support separate compilation, the declaration is placed in .h and the definition is placed in .cpp! ! ! ! But it can be written separately in the current file, so you can create a .hpp file, which is commonly known by convention, so that people can know what it means when they see it.
        

//声明定义在本文件分离。
template<class T>
class Stack
{
	public:
    	Stack(size_t capacity = 4)
            :_a(nullptr)
          	 ,_capacity(0)
             ,_top(0);
        {
            if(capacity > 0)
            {
                _a = new T[capacity];  
               _capacity = capacity;
                _top = 0;
            }
        }
    	
    	~Stack()
        {
            delete[] _a;
            _a = nullptr;
            _capacity = _top =0;
        }
    
    	void Push(const T& x);
    	void Pop();
    	bool Empty();
    	T& Top();

    private:
    	T *_a;
    	int _top;
    	int _capacity;

};

//注意这里的写法
template<class T>
void Stack<T>::Push(const T& x)
{	
       if(_top == _capacity){
                
            size_t NewCapacity = _capatcity == 0 ? 4 : _capacity*2;
                
            T* tmp =new T[NewCapacity];
                
            if(_a){
                memcpy(tmp,_a,sizeof(T)*_top);
                delete[] _a;
			}
                
                _a = tmp;
                _capacity = NewCapacity;
                
       }
            
       _a[_top]=x;
       ++_top;
}

        

3. STL

         

         Standard Template Library (Standard Template Library, STL) is a collective name for a series of software developed by HP Labs . It was developed by Alexander Stepanov, Meng Lee and David R Musser while working at Hewlett-Packard Labs . Although it appeared primarily in C++, the technique existed long before it was introduced into C++.

        STL code is broadly divided into three categories: algorithm (algorithm), container (container) and iterator ( iterator ), almost all of the code uses template classes and template functions, which is compared to the traditional Libraries of functions and classes provide better opportunities for code reuse.

        STL six components: container (data structure) , algorithm , iterator , adapter , functor , space configurator .   

sting:

#include<string>

//string是一个宏定义
typedef basic_string<char> string;

string str; //等价于
basic_string<char> str

        To use the string container to load the header file, and many of the functions are in the std namespace, string is the macro of basic_string<char>, where basic_string is the class name.        

         

        The basic_string macro defines multiple different containers according to different template parameters. The definition of multiple is to match the corresponding encoding, because different encodings have different sizes.

       Here, first learn the use of the string container and its commonly used functions. This is universal, because other containers are very similar, and they are also specially set by the person who wrote the library. After learning this one, other ones will also be used.

Constructor:

        string can be initialized with the same string object, or it can be initialized with a string (char*) (implicitly typed).

        You can also specify the data segment to be initialized, specify the starting position, and the length to be initialized. If the given length is greater than the length of the end of the string, then copy all of the end.

        Among them, if the length is not specified, the default default value is a static variable npos , the value is -1.

PS: The type of npos is size_t, which is an unsigned integer, giving a value of -1 will become a very large integer. Then the meaning is very clear, if you do not enter the length of the pointer, all the objects to be initialized will be given from the beginning position to the end.

   

        This method of object initialization is common in STL, and other containers are not bad.

        

Overloaded operator[ ]:

        We know that a string is actually an array of characters, and each individual character can be accessed through [ ], so the operator [ ] is overloaded in string, which is used for us to access characters.

        [ ] returns a character reference , so you can modify a single character through this operator.

iterators:

        Each container of the iterator has different implementation methods because of its different characteristics. In the string, because its essence is to use an array to store data, the iterator here is a pointer.

        Since [ ] and pointers can traverse strings, why define an iterator? Because not all containers use pointers to implement iterators. But the method used by the iterator is the same, which is a general method for STL to traverse the container .

        

        Use a macro definition iterator in the string to define a variable it, it is the iterator of the string container, and its usage is similar to the pointer.

it!=s.end();

it<s.end();

        Here, when the iterator judges whether it has reached the end of the container, it is better to use != instead of the < symbol, although the two effects are the same here (because the underlying iterator of string is a pointer). But error occurs in its container (such as list).

for (auto ch : s)
{
	cout << ch << endl;
}

//想修改用使用引用
for(auto & ch : it)
{
    ch++;
}

        The bottom layer of the range for is the iterator.

PS: The iterator in the range for can automatically traverse, iterate, and judge the end automatically.

reverse iterator:

        In fact, you can probably understand what it means by looking at the name, which is to traverse from the end of the container to the starting position.

PS: Note that the iterator ++ here goes to the front of the container (left), and -- goes to the back of the container (right).

Constant iterators:

        

Constant reverse iterator:

PS: The constant iterator cannot modify the data in the object. 

push_back():

        The push_back() function can only insert one character at the end of the string .

        

append():

        

        Insert a string at the end of string, and you can specify the start position and length of the inserted string.

 

Overloading the += sign:

        

        

        The usage is similar to append(), but it looks easier to understand and convenient to use.

insert(): 

        

         Insert a string at the specified position, and also specify the start position and length of the inserted string.

reserve() and resize():

        When inserting data, the function has already helped us judge that if the container is full, it will re-expand the space. But if we know the size of the input data, we can open up space in advance.

        reserve() opens up space, and resize() opens up space and initializes.

PS: string has a built-in function capacity(), which can know the current capacity of string.

c_str:

        Sometimes it needs to be compatible with C language, so it contains functions compatible with C language. The c_str() function returns the type of const char*.

string filename("test.cpp");
FILE* fp = fopen(filename.c_str,"r");
char ch=getc(fp);

while(ch != EoF){
    cout<<ch;
    char ch=getc(fp);
}

        There is a difference between filename and filename.c_str, and filename is based on the size() of the container. filename.c_str is a constant character string, ending with "\0".

find():

         Search the string, if found, return the position of the first character of the matching string, if not found, return npos.

        find(), when searching for a string, you can specify the position to start searching, and you can also specify the length of the string to search for. But be careful, if there are multiple strings that can be matched, only the position on the first match is returned here.

        

find_first_of():

        

        This function is more difficult to understand, it is not to find out the exact matching string position. It is the character of the matching string, whoever appears first in the main string, returns the matching position there .

        Here e is the first to appear in the main string, so 1 is returned here. If the matching fails, return npos.

Fourth, the simulation implementation string

namespace STR
{

    class string
    {    
    public:

        //构造函数 使用初始化列表
		string(const char* str = "")           //这里的缺省值是 \0
			:_str(new char[strlen(str)+1])     //加1 是加上 \0的空间
			,_size(strlen(str))
			,_capacity(strlen(str))     //capacity不包括 \0
		{
			strcpy(_str, str);
		}



        //构造函数  另一种写法
        string(const char* str = "") //这里的缺省值是 \0
		{

            //这样写可以复用变量,如果写在初始列表,变量初始顺序要严格匹配声明顺序。

			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];    //加1 是加上 \0的空间
			
			strcpy(_str, str);
		}


        //析构函数
        ~string()
        {
            delete[] _str;
            _str = nullptr;
            _capacity = _size = 0;
        }
    

    private:
        char* _str;
		size_t _size;    //字符串的长度 不包括\0
		size_t _capacity;    //数组的容量 不统计\0


        static size_t nops_t;
    };

}

//静态变量 类外面定义
size_t STR::string::nops_t = -1;

PS: Here, in order to distinguish it from the string in the library, a namespace STR is customized here.

        When you start writing the copy constructor here, you should pay attention to it. If an array is defined in the member variable, then you must pay attention to the problem of deep and shallow copying. In order to avoid shallow copying, a space for _str should be re-opened when initializing here.

Traditional writing:

//拷贝构造函数  传统写法
string(const string& str)
	:_str(new char[str._capacity + 1])    //新开辟一段空间
	, _size(str._size)
	, _capacity(str._capacity)
{

    strcpy(_str, str._str);

}

 Modern writing:

const char* c_str() const  //const string* const this
{
	return _str;
}


void swap(string& str)
{
    std::swap(_str, str._str);
    std::swap(_size, str._size);
    std::swap(_capacity, str._capacity);
}

//拷贝构造函数  现代写法
string(const string& str)
	   :_str(nullptr)
		, _size(0)
		, _capacity(0)
{
    //注意这里是str._str是char*类型的字符串,这里复用了构造函数
    string tmp(str._str); 

    swap(tmp);
}

        The modern way of writing the copy constructor is very clever and concise, and borrows the constructor for deep copying.

        A swap function swap() is defined here, which reuses swap() in the library to exchange data.

        The swap() in the library has been templated and can directly perform custom type swaps, but here the copy construction will be called multiple times, wasting efficiency .

        So here, a swap() function is overloaded inside the string, which itself only exchanges variable data, so the efficiency is relatively high.  

        The idea of ​​modern writing is to regenerate the passed string into an object, and then exchange the data of the object with itself, and since tmp is a local variable inside the function, the destructor will be called to destroy it when the function exits, and there is no need to manually release the address .

        This way of writing can also be used on overloaded assignment symbols. When overloading assignment symbols, the problem of deep and shallow copying should also be considered .

//传统写法
string& operator=(const string& str)
{

	if (this!=&str) {

		delete[] _str;
		_str = new char[str._capacity + 1];  //+1是给\0留的空间
		strcpy(_str, str._str);
		_capacity = str._capacity;
		_size = str._size;

	}

		return *this;
}

//现代写法
string& operator=(const string& str)
{
	if (this != &str) {

		string tmp(str._str);
		swap(tmp);
	}
		return *this;
}

        Like the routine, regenerate the incoming string into an object, and then exchange the data of the object with itself.

string& operator=(const string& str)
{
	if(this!=&str){
        string tmp(str);
        std::swap(*this,tmp); //这里使用库里面的swap,会造成错误
    }
    return *this;
}

        Another point, if you overload the assignment symbol, use the swap() in the library to exchange, swap (*this, tmp), the assignment symbol used inside the function will call the assignment again, so there will be an infinite loop here, causing the stack overflow.

//重载[]  返回pos位置的字符的引用

//常量的调用 ,只能读,不能写
const char& operator[](size_t pos) const
{
	assert(pos < _size);
	return _str[pos];
}

//普遍调用, 可以读,可以写
char& operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}

        Overloading [ ] is very simple, just reuse the [ ] of the array.

//扩容
void reserve(size_t n)
{
	if (n > _capacity) {

		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;

		_str = tmp;
		_capacity = n;

	}

}

//开空间并初始化
void resize(size_t n,char ch='\0')
{
	//1.比当前的空间大
	if (n>_size) {

		reserve(n);
		for (size_t i = _size; i < n; i++) {

			_str[i] = ch;
					
	    }

		_str[n] = '\0';
		_size = n;
	}
	else {	//2.比当前的空间小
		_str[n] = '\0';
		_size = n;

    }
}

        The expansion function, if there is already content inside the string, the data should be copied to the newly opened space. When opening up space, one more byte should be reserved for "\0".

//插入一个字符串
void push_back(char cn)
{

	if (_size == _capacity) { //如果空间满了,要扩容

		//如果string容器里面没有数据,开4个字节的空间。
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}

		_str[_size] = cn;
		_size++;
		_str[_size] = '\0';

}

//插入一个字符串
void append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity) {

		reserve(_size + len);

	}

	strcpy(_str + _size, str);
	//strcat(_str,str) 也可以使用这个函数追加,不过要找\0,效率低
	_size += len;
}


void append(const string& str)
{
	append(str._str);
}


void append(size_t n, char ch)
{

	for (size_t i = 0; i < n; i++) {
		push_back(ch);
	}

}


string& operator+=(char ch)
{
	push_back(ch);
	return *this;
}


string& operator+=(const char* str)
{
	append(str);
	return *this;
}

        Inserting characters or strings is actually relatively simple to implement. The main thing is to pay attention to judging that if the capacity of the array is full, it needs to re-open up space.

        

//插入字符
string& insert(size_t pos, char ch)
{
	assert(pos <= _size);
	if (_size == _capacity) {

		reserve(_capacity == 0 ? 4 : _capacity * 2);

	}

	size_t end = _size + 1;
	while (end > pos) {

		_str[end] = _str[end - 1];
		end--;
	}

	_str[pos] = ch;
	_size++;

	return *this;
}

//插入字符串
string& insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);

	if (_size + len > _capacity) {
		reserve(_size + len);
	}

	size_t end = _size + len;
	while (end >= pos + len) {

		_str[end] = _str[end - len];
		end--;	
	}

	strncpy(_str + pos, str, len);
	_size += len;

	return *this;
}

        When inserting at the specified position, the data behind the insertion position should be moved backward. Do not use strcpy() here to copy \0, so strncay() is used here;

size_t end = _size;
while (end >= pos) {
    
    //这里pos==0,end减到最后变为-1的时候,会变得非常大,造成越界。		
    _str[end + 1] = _str[end];
    end--;
}

size_t end = _size+1;
while (end > pos) {
				
    _str[end] = _str[end-1];
    end--;
}

PS: There are two ways to write data here. It is best to use the following way, because when inserting data at the beginning, the end will become -1 at the end, and here end is the data of type size_t, which will become very large and cause errors .

void push_back(char ch)
{
    insert(_size,ch);
}

void append(const char* str)
{
    intsert(_size,str);
}

        In fact, you can also implement insert() first, and then reuse push_back() and append().

//删除
void erase(size_t pos, size_t n = nops_t) //如果不写,那么全删除了
{
	assert(pos < _size);

	//如果没给参数,或者给的参数过大,则pos后面的全部删除
	if (n == nops_t || n + pos > _size) {
		_str[0] = '\0';
		_size = 0;
	}
	else
	{
		//把要删除的数据,用后面的数据覆盖
		strcpy(_str + pos, _str + pos + n);
		_size -= n;

	}
}

void clear()
{
	_str[0] = '\0';
	_size = 0;
}

        The delete function is relatively simple and there is nothing to pay attention to.

//查找字符
size_t find(char ch, size_t pos = 0) 
{
	assert(pos < _size);
	for (size_t i = pos; i < _size;i++) {
		if (ch==_str[i]) {
			return i;
		}
	}

	return nops_t;
}

//查找字符串
size_t find(const char* sub, size_t pos = 0)
{
	assert(pos < _size);
	const char* ret = strstr(_str + pos,sub);
	if (ret == nullptr) {
		return nops_t;
	}
	else {
		return ret - _str;
	}

}

        The string matching function in the library is used here, and here you can also write a KMP algorithm to find it, but the efficiency is similar.

        Here, the method of returning the location of the searched string is clever. It uses the knowledge point of subtracting the pointer from the pointer to obtain the number of intermediate elements, and _str is the starting address of the array, so what is returned here is the searched one. location .

bool operator>(const string& str)const
{
	//大于0就是真
	return strcmp(_str, str._str) > 0;
}

bool operator==(const string& str)const
{
	return strcmp(_str, str._str) == 0;
}
		
bool operator<=(const string& str)const
{
	return  !(*this > str);
}
		
bool operator>=(const string& str)const
{
	return *this > str || *this == str;
}
		
bool operator<(const string& str)const
{
	return !(*this >= str);
}
		
bool operator!=(const string& str)const
{
	return !(*this == str);
}

        In the C language, strings can be compared in size, so we need to overload it here. Here, strcmp() is directly reused. When comparing the size of strings in the future, operators can be used directly, which is very convenient and easy to understand.

string substr(size_t pos, size_t len = nops_t)const
{
	assert(pos < _size);
	size_t realLen = len;
    //如果没给参数,或者给的参数过大,则pos后面的全部返回为子串
	if (len == nops_t || pos + len > _size)
	{
		realLen = _size - pos;
	}

	string sub;
	for (size_t i = 0; i < realLen;i++) {
		sub += _str[pos + i];
	}

	return sub;
}

        Return a piece of data in the main string as a substring. It is often used when splitting strings.

//重载流提取和流插入
//实现成全局函数,避免和this指针的位置问题。
std::ostream& operator<<(std::ostream& out, const string& str);


std::istream& operator>>(std::istream& in, string& str);


std::ostream& STR::operator<<(std::ostream& out, const string& str)
{
	for (size_t i = 0; i < str.size(); i++)
	{
		out << str[i];
	}

	return out;
}



//这种写法+=多次,扩容多次
std::istream& STR::operator>>(std::istream& in, string& str)
{
	
	char ch;
	//in>>ch 不能使用这个,这个无法检查到空格
	ch = in.get();
	while (ch != ' ' && ch != '\n') {

	    str += ch;
		ch = in.get();

	}

	return in;
}



std::istream& STR::operator>>(std::istream& in, string& str)
{
	str.clear();
	char ch;

	const size_t N = 32;
	char buff[N]={0};
	size_t i = 0;

	ch = in.get();
	//其思路是,buff满了,再追加到str里面
	while (ch != ' ' && ch != '\n') {
	
		buff[i++] = ch;
		if (i == N-1) {
			
			buff[i] = '\0';
			str += buff;
			i = 0;

		}
		ch = in.get();
	
	}

	buff[i] = '\0';
	str += buff;

	return in;

}

        Stream extraction is easy to understand here, but stream insertion should be noted that stream insertion can insert multiple objects continuously.

        But use cin>>ch; this way of writing cannot receive spaces and newlines , because it checks that spaces and newlines are the input intervals, which will cause the loop to exit here, so here we use the get() that comes with cin function.

        If the input content is very long, using += is very inefficient. Therefore, an array is used here to receive certain data first, and the data in the array is added to the object when it is full (similar to the concept of a buffer).


//迭代器
typedef char* iterator;
//const 迭代器
typedef const char* const_iterator;
iterator begin()
{
	return _str;
}

iterator end()
{
	return _size + _str;
}

        The implementation of the iterator is also relatively simple, because string is a string stored in an array, and can also be accessed by a pointer. Here, the pointer is just defined as a macro, but this way of writing can also use the range for.

 

Guess you like

Origin blog.csdn.net/weixin_45423515/article/details/126593257