First, can a stack be pressed with a pointer?
Of course, write a simple stack here
When it comes to pointers, you may think of strings. We can pass in the following types
string str; The simplest input, thanks to the powerful string class provided by C + +, we can easily complete this operation
char str[size]; C-style string, but it may not be available for some template class methods
char * str = new char[size];
For the class we implemented, at least it is not feasible not to modify the input data
template<class Type> bool Stack<Type>::push(const Type &t) { if(now < MAX) { items[now++] = t; return true; }else{ return false; } }
We can see that the element pressing the stack is a pointer. Without human intervention, the pointer points to the memory and will not change. It is meaningless for us to input str repeatedly;
The appropriate approach is to let the program provide an array of pointers (generally a two-dimensional array), in which each pointer points to a different string. The task of the stack is to manage pointers, not to create pointers.
// // Created by JAN on 2022/1/26. // #ifndef C___STACK_H #define C___STACK_H template <class T> class Stack { private: enum { MAX = 10}; T items[MAX]; int now; public: Stack() : now(0) {} bool push(T & t); bool pop(); bool empyt() const; const T& top() const; }; template <class T> bool Stack<T>::push(T &t) { if(now < MAX){ items[now++] = t; return true; }else return false; } template<class T> bool Stack<T>::empyt() const { if(now == 0) return true; else return false; } template<class T> const T &Stack<T>::top() const { //int temp = now-1; return items[now-1]; } template<class T> bool Stack<T>::pop() { if(now > 0){ now--; return true; }else return false; } #endif //C___STACK_H
// // Created by JAN on 2022/1/25. // #ifndef C___STACK_H #define C___STACK_H template<class Type> class Stack { private: enum { MAX = 5 }; Type items[MAX+1]; //It can also be written in this form, but don't forget to create a new space in the constructor and free up space with a destructor //Type *items; int now; public: Stack(); bool empty(); bool push(const Type & t); bool pop(); Type top(); }; template<class Type> Stack<Type>::Stack() { now = 0; } template<class Type> bool Stack<Type>::empty() { return now == 0; } template<class Type> bool Stack<Type>::push(const Type &t) { if(now < MAX) { items[++now] = t; return true; }else{ return false; } } template<class Type> bool Stack<Type>::pop() { if(now > 0) { now--; return true; }else return false; } template<class Type> Type Stack<Type>::top() { if(now > 0){ return items[now]; } } #endif //C___STACK_H
Operation results
You can see the repeated output eee
Recursive use of templates
We know that there is an array template class. The so-called recursion is just a template set template
array< array<int, 3>, 5> arr;
The array in the outer layer contains five # array templates with three int s
It may be difficult to describe as a language, but you can understand it by looking at the source code
#include <iostream> #include <valarray> #include <array> using namespace std; int main() { array< array<int, 3>, 5> arr; for(int i=0;i<5;i++){ for(int j=0;j<3;j++){ cin >> arr[i][j]; } } for(int i=0;i<5;i++){ for(int j=0;j<3;j++){ cout << arr[i][j] << " "; } cout << endl; } return 0; }
It can operate like a two-dimensional array
Accept multiple types of templates
Suppose we use a binary to store data, and the two meta types may be the same or different. How do we write a template class
Simply simulate the binary pair. It's very simple. You may write it with your hands
pair.h
#ifndef PAIR_H #define PAIR_H //Binary //template_name <class type_name, class type_name ... some argument> //We can call the above template class prototype template <class Type1, class Type2> class Pair { private: Type1 t1; Type2 t2; public: Pair() = default; Pair(const Type1 & _t1, const Type2 & _t2) : t1(_t1), t2(_t2) {} const Type1 & first() const; const Type2 & second() const; }; template <class Type1, class Type2> const Type1 & Pair<Type1, Type2>/*The scope here is the template prototype, which is easy to forget*/::first() const { return t1; } template <class Type1, class Type2> const Type2 & Pair<Type1, Type2>::second() const { return t2; } #endif
main.cpp
#include <iostream> #include "pair.h" using namespace std; int main() { Pair<int, double> p(3,4.12); cout << p.first() << endl; cout << p.second() << endl; return 0; }
Default type template
Just like the default function parameter, if it is not declared, it can automatically become the default value. If the template class is not declared, it will automatically become the default type
template <class Type1=int, class Type2=int> ... Pair <> p; //Pair<int, int> p;
Concretization of template class
1. Implicit instantiation
Just like the template function, when the template class does not indicate the instantiation object, the compiler will help us automatically instantiate the type we pass in. Implicit instantiations are not generated until the compiler needs the object.
array <int,3> * parr;
This just declares a template class pointer and does not instantiate it.
2. Display instantiation
When a class is declared using the keyword template and indicating the required type, the compiler generates an explicit instantiation of the class declaration. The declaration must be in the namespace of the template.
The following explicitly declares a class of < string, int >
template class class_name <string,int>
In this case, although no object is created or mentioned, the compiler will also generate class declarations (including method definitions). Like implicit instantiation, materialization will also be generated according to the general template.
3. Explicit concretization
Explicit materialization is the definition of a specific type. For some types, we need to have specific template class methods to deal with it. At this time, explicit concretization is used. Explicit concretization is a further supplement to the generality of template classes.
The definition format is as follows
template class class_name <sprcialized-type-name> { do sometings };
4. Partial concretization
That is to limit the generality of the template. If we write a template class that can only handle integers and don't want floating-point types to use this template, we can do so
template <class Type, int> class class_name { some things; }
In this way, the second type of template class can only be an integer
If there are multiple templates to choose from, the compiler will choose the most specific one
Pay special attention to the template materialization provided for pointers
template <class T> class A { ... }; template <class T*> class A { ... }; ------------------------------------------------------ A<char *> a;
Without a second template declaration, the compiler changes T to char*
The second is not to change T to char * *, but to char
Member template
Templates can be used as members of structures, classes, or templates. To fully implement the design of STL, this feature must be used. Here is a simple template class nesting
#include <iostream> using std::endl; using std::cout; //Define template class template <class T> class beat { private: template<class V> class hold { private: V val; //Create a type variable public: //Constructor hold(V v=0) : val(v) {} //class method` void show() const { cout << val << endl; } V value() const { return val; } }; //After defining the inner template, we can create template classes within two template classes //The beat Class treats these two template classes as members hold<T> q; //And the outermost class instantiate a data type hold<int> n; //Explicitly instantiate int type public: //class method //Constructor is actually calling the constructor in hold beat(T t, int i) : q(t), n(i) {} template<class U> //Here is the template function //U u is unique to this template function. T t and t instantiation of large template class are one type U blab (U u, T t) { return (n.value()+q.value()) * u / t; } void show() const { q.show(); n.show(); } }; int main() { //beat(T t, int i) : q(t), n(i) beat<double> guy(3.5, 3); cout << "T was set to double\n"; guy.show(); cout << "V was set to T, which is double, then V was set to int\n"; //Since it is T is double, the following 2.3 is also double if it is changed to 2 cout << guy.blab(10,2.3) << endl; cout << "U was set to int\n"; cout << guy.blab(10.0,2.3) << endl; cout << "U was set to double\n"; return 0; }
Take template class as parameter
Template classes can contain either type parameters (type) or non type parameters (int a). Similarly, template classes can also contain parameters that are themselves templates. They are features of C++11 and are used to implement STL
In fact, the recursion of template class takes the template class as a parameter
The grammatical form is easy to understand
Note that there is no comma in the middle
template < /**/template <typename type_name> class/**/ type_name> class class_name { do something; };
Where / * * / template < typename type_name > class / * * / is a type, followed by type_name is the parameter
If we want to stack the binary, our previous stack will not work because the binary is also a template class. The previous stack does not support the template class as a type parameter. We can transform the stack to receive the binary template class. On this basis, we can write a new template class and integrate the stack and the binary, This class accepts two template classes as objects. It can also write another template class and accept a stack template class. Two template stack class objects are instantiated in this template class to represent binary.
The second method seems troublesome, but its universality may be better. The third method is simple and easy to implement
Here we use the last form
#include <iostream> #include "stack.h" using namespace std; template <template<class T> class S, class T1, class T2> class IDE { private: S<T1> stack1; S<T2> stack2; public: IDE() = default; //class method bool push(T1 & t1, T2 & t2); bool pop(); void top(T1 & t1, T2 & t2) const; }; template <template<class T> class S, class T1, class T2> bool IDE<S, T1, T2>::push(T1 &t1, T2 &t2) { return stack1.push(t1) && stack2.push(t2); } template <template<class T> class S, class T1, class T2> bool IDE<S, T1, T2>::pop() { return stack1.pop() &&stack2.pop(); } template <template<class T> class S, class T1, class T2> void IDE<S, T1, T2>::top(T1 &t1, T2 &t2) const { t1 = stack1.top(); t2 = stack2.top(); } int main() { IDE<Stack/*no Stack <argument>*/,int ,double> stk; int a; double b; cin >> a >> b; stk.push(a,b); cin >> a >> b; stk.push(a,b); int c; double d; stk.top(c,d); cout << c << " " << d << endl; stk.pop(); stk.top(c,d); cout << c << " " << d; stk.pop(); return 0; }
Obviously, we can find that we cannot implement the top function. We cannot return two elements. If we return, we can only return one structure, or change the practice of top. However, other methods can directly return a pair, because the pair template class is directly pushed into the stack.
Template classes and friends
Just as a class can declare friends, a template class can also declare friend functions. The friends of the template are divided into three categories.
1. Non template friends
2. Constraint template friend
That is, the type of friend depends on the type when the class is instantiated
3. Unconstrained template friends
That is, all materializations of friends are each materialized friend of the class
It doesn't matter if you don't understand, but only if you understand.
1. Non template friends
Declare a regular function as a friend in the template
Because it is not a template function, each instantiation of a template function needs to write a friend function separately
My friend is my friend, and your friend is your friend. Although we are materialized from the template class like brothers, although we are very similar, my friend is not my brother's friend, and my friend is not my brother's friend - the template class says.
template <class T> class A { friend void func(); ... };
Can you do this
template <class T> class A { friend void func(A &); ... };
Of course not, because there is no such object as A, which can only be done by specific materialization
We can do this
template <class T> class A { friend void func(A<T>); ... };
Because func is not a class method, but a friend function. It does not belong to a class or a template function, so it should be explicitly materialized when used. During compilation, if the user passes in int type, the compiler will replace T with int. for func, this is an explicit materialization.
Overloaded version
friend void func(A<int>); friend void func(A<double>);
A small example
#include <iostream> using namespace std; template<class T> class A { private: T data; static int total; public: A(T a) : data(a) { total++; } friend void report(); friend void show(const A<T> &); }; template<class T> int A<T>::total = 0; //This must be explicitly materialized void show(const A<int> &t) { cout << t.data << endl; } //Here too void show(const A<double> &t) { cout << t.data << endl; } void report() { cout << " <int> is " << A<int>::total << endl; cout << " <double> is " << A<double>::total << endl; } int main() { report(); A<int> a(1); report(); A<double> b(3.14); report(); A<int> c(10); report(); show(a); show(b); show(c); return 0; }
Don't forget to initialize static variables!
Operation results
It can also be seen that a < int >, a < double >, are two classes!!! And they're from the same mold.
2. Constraint template friend
Because < int >, < double > are different classes, each materialization of the class should be given a materialization that matches the friend
To put it bluntly, the template function is a friend, which corresponds to the above.
You can modify the previous example so that the friend function itself is called a template
Including the following three supplements
First, define the template of each friend function in front of the class
there T Is a template function T,Is not a template class T,T After instantiation, it is a template class, such as<int>,<string> template <class T> void show(T &); template <class T> void report();
Then, declare the function as a friend again in the template class
template <T> class A { friend void show <> (A<T> &); friend void report <T> (); ... };
The < > in the statement indicates the concretization of the template. For show, it can be null because the template type parameters can be inferred from the function parameters
amount to
void show <A<T> (A<T> &);
Finally, you can define a friend function like a template function.
This is exactly the concretization of template function.
It is best to distinguish the Type of friend from the Type of template class
#include <iostream> using std::cout; using std::endl; template <class T> void counts(); template <class T> void report(T &); template<class TT> class HasFriend { private: TT item; static int ct; public: HasFriend(const TT& i) : item(i) { ct++; } ~HasFriend() { ct--; } friend void counts <TT>(); //Why is hasfriend < TT > & not T & t here friend void report <>(HasFriend<TT>&); }; template <class T> void counts() { cout << "template size: " << sizeof(HasFriend<T>) << ";"; cout << "template counts(): " << HasFriend<T>::ct << endl; } template<class T> void report(T & t) { cout << t.item << endl; } //Note that the initialization should also use the template, which means that it is a static member of the template class template <class TT> int HasFriend <TT>::ct= 0; int main() { counts<int>(); HasFriend <int> hfi1(10); HasFriend <int> hfi2(20); HasFriend <double> hfi3(10.5); report(hfi1); report(hfi2); report(hfi3); cout << "counts<int>() output:\n"; counts<int>(); cout << "counts<double>() output:\n"; counts<double>(); return 0; }
As the program can see, the size of the two counts reports is different, which proves that each type has its own friend function.
It corresponds to the materialization of each count.
3. Unconstrained template friends
It's simple
class A { public: template<class C, class D> friend void show(C & c, D & d); };
Outside the class, you only need to remove the semicolon of the declaration, remove friend, and then define it with curly braces.
Template alias
In order to simplify the code, you may use typedef as an alias for the template class, which has a similar function in C++11
template <class T> using arrtype = std::array<T,12>;
Array type is the alias of STD:: array < T, 12 >. More powerful than typedef.
//using arrtype = std::array<T,12>; arrtype<int>; //Equivalent to STD:: array < int, 12 > arrtype<string>; //Equivalent to STD:: array < string, 12 >
using can also be used for non template classes. This syntax is equivalent to typedef when used for non templates
Finally, some descriptions about materialization and instantiation
Class definitions (instantiations) are generated when a class object is declared and a specific type is specified. For example, the declaration of main will cause the compiler to declare a class and replace T in the template class with the actual type sotr in the declaration
class IC <sotr> sic;
Here, the class name is IC < sotr >, not IC. IC < sotr > is called template materialization
In this case, the compiler will use the generic template to generate an int materialization -- CI < int >, although the object of this class has not been requested
It can provide explicit materialization - override the concrete class declaration defined by the template class. If the template class can't handle a character array well, we can explicitly materialize one
Method starts with template < > (that is, syntax), followed by the template class name, with angle brackets (including the type to be materialized).
template <> class IC(char *) { // T variable_name; char * str; public: IC(const char * s) : str(s) {} ... }
In this way, you can obtain a template that specifically deals with character arrays instead of using general template class methods, which is also very similar to the concretization of template functions.