C++ study notes 22-class template and array class encapsulation case

22.0 Preface

I have learned the relevant knowledge of function templates before , and then I will learn the relevant knowledge of class templates.


22.1 Class Template Syntax

Class template role:

  • Suggest a general class, the data type of the members in the class can not be specified, but can be represented by a virtual type .

grammar:

template<typename T,typename U,typename V,...> //类中需要几个数据类型就写几个
类的定义

//类的实例化
类名<数据类型1,数据类型2,数据类型3,...> 变量名(参数)  //在实例化的时候需要将模板的数据类型参数列表依次指定。

explain:

  • template – declares the creation template.
  • typename - Indicates that the symbol behind it is a data type, which can be replaced by class.
  • T,U,V - Universal data types, names can be replaced, usually in uppercase letters.
  • Note: When the class template is called, the parameter list cannot be omitted.

Example:

#include<iostream>
using namespace std;
template<class NameType,class AgeType>
class Person1
{
    
    
public:
	Person1(NameType name, AgeType age)
	{
    
    
		this->name = name;
		this->age = age;
	}
	NameType name;
	AgeType age;
	void showPerson()
	{
    
    
		cout << "姓名 : " << this->name << endl;
		cout << "年龄 : " << this->age << endl;
	}
};
void test1()
{
    
    
	Person1<string, int> p("张三", 60);
	p.showPerson();
}

int main()
{
    
    
	test1();
	system("pause");
	return 0;
}

22.2 The difference between class templates and function templates

There are two main differences between class templates and function templates:

  1. Class templates have no way of using automatic type deduction.
  2. Class templates can have default parameters in the function parameter list.

Example:

#include<iostream>
#include<string>
using namespace std;

template<class NameType,class AgeType>
class Person2
{
    
    
public:
	Person2(NameType name,AgeType age)
	{
    
    
		this->name = name;
		this->age = age;
	}
	void showPerson()
	{
    
    
		cout << "name : " << this->name << endl;
		cout << "age : " << this->age << endl;
	}
	NameType name;
	AgeType age;
};

template<class NameType, class AgeType = int >
class Person2_02
{
    
    
public:
	Person2_02(NameType name, AgeType age)
	{
    
    
		this->name = name;
		this->age = age;
	}
	void showPerson()
	{
    
    
		cout << "name : " << this->name << endl;
		cout << "age : " << this->age << endl;
	}
	NameType name;
	AgeType age;
};

void test2()
{
    
    
	//Person2 p("孙悟空", 1000);  无法用自动类型推导
	Person2<string, int> p("孙悟空", 1000);  //显示指定是可以的
	p.showPerson();

	Person2_02<string> p2("猪八戒", 900.3);  //有默认参数,是默认的 
	p2.showPerson();

	//用法和函数的默认参数是相同的,也要遵循从右往左的规则
	Person2_02<string,double> p3("沙僧", 800.5);  
	p3.showPerson();
}

int main()
{
    
    
	test2();
	system("pause");
	return 0;
}

22.3 When to Create Member Functions in Class Templates

There is a difference in the creation timing of member functions in class templates and member functions in ordinary classes:

  • Member functions of ordinary classes can be created from the beginning
  • Member functions in class templates are created when they are called
#include<iostream>
using namespace std;

class Person3_1
{
    
    
public:
	void showPerson1()
	{
    
    
		cout << "Person1 show" << endl;
	}
};

class Person3_2
{
    
    
public:
	void showPerson2()
	{
    
    
		cout << "Person2 show" << endl;
	}
};

template<class T>
class MyClass3
{
    
    
public:
	T obj;
	//成员函数
	void func1()
	{
    
    
		obj.showPerson1();
	}
	void func2()
	{
    
    
		obj.showPerson2();
	}
	//这里showPerson1()和showPerson2()是两个不同类下的成员函数,但是这里用一个变量调用,从逻辑上是说不过去的
	//但是函数模板是没问题的,因为函数模板不会在一开始就创建这两个函数,只有在调用的时候才创建
};

void test3_01()
{
    
    
	MyClass3<Person3_1> p;
	p.func1();      //因为指明了T是Person3_1,所以可以调用
	//p.func2();	//因为类型不对,所以不能调用
}
int main()
{
    
    
	test3_01();
	system("pause");
	return 0;
}

22.4 Class template object as function parameter (with a little knowledge of data type syntax)

learning target:

  • Objects instantiated from class templates, how to pass parameters to functions.

There are three input methods:

  1. Specify the incoming type - directly display the data type of the object
  2. Parameter templating - turning the parameters in the object into templates for passing
  3. Entire class templated - pass this object type templated

Example:

#include<iostream>
#include<string>
using namespace std;

template<class T1,class T2>
class Person4
{
    
    
public:
	Person4(T1 name, T2 age)
	{
    
    
		this->name = name;
		this->age = age;
	}
	void showPerson()
	{
    
    
		cout << "名字是: " << name << endl;
		cout << "年龄是: " << age << endl;
	}
	T1 name;
	T2 age;
};
//1.
void printPerson1(Person4<string,int>& p)  //一般用这种,因为这样代码安全,不容易出错。
{
    
    
	p.showPerson();
}

//2.
template<class T1,class T2>
void printPerson2(Person4<T1, T2>& p)
{
    
    
	cout << "T1 : " << typeid(T1).name() << endl;  //这是看数据类型的方法,可以查看类型或者变量的数据类型.
	cout << "T2 : " << typeid(T2).name() << endl;
	p.showPerson();
}

//3.
template<class T>
void printPerson3(T& p)
{
    
    
	cout << typeid(T).name() << endl;
	p.showPerson();
}

void test4_01()
{
    
    
	//1.指定传入类型
	Person4<string, int> p1("孙悟空", 100);
	printPerson1(p1);

	//2.参数模板化
	Person4<string, int> p2("猪八戒", 90);
	printPerson2(p2);	

	//3.整个类模板化
	Person4<string, int> p3("沙僧", 30);
	printPerson3(p3);
}
int main()
{
    
    
	test4_01();
	system("pause");
	return 0;
}

Check out the datatype syntax:

typeid(变量/数据类型).name()

Summarize:

  • Objects created through class templates can pass parameters to functions in three ways.
  • The most widely used is the first one, which directly specifies the incoming type.

22.5 Class Templates and Inheritance

When class templates encounter inheritance, you need to pay attention to the following points:

  • When the parent class inherited by the subclass is a class template, when defining the definition, it is necessary to specify the type of T in the parent class. The syntax is:class Son:继承方式 Base<数据类型>
  • If not specified, the compiler cannot allocate memory for the subclass.
  • If you want to flexibly specify the type of T in the parent class, the subclass also needs to become a class template.

Example:

#include<iostream>
using namespace std;
template<class T>
class Base
{
    
    
public:
	Base()
	{
    
    
		cout << "T类型:" << typeid(T).name() << endl;
	}
	T m;
};

class Son :public Base<int>       //子类是普通函数,不指定数据类型,编译器无法分配内存.
{
    
    
public:
};

//如果想灵活指定父类T的类型,子类也需要变类模板
template<class T1, class T2>
class Son2 :public Base<T1>   //根据传递规则可知,此时父类中的数据类型T就是现在的数据类型T1
{
    
    
public:
	Son2()
	{
    
    
		cout << "T1类型:" << typeid(T1).name() << endl;
		cout << "T2类型:" << typeid(T2).name() << endl;
	}
	T2 obj;
};
void test5_01()
{
    
    
	Son s1;
	Son2<double,char> s2; //T1为int ,T2为char
}
int main()
{
    
    
	test5_01();
	system("pause");
	return 0;
}

22.6 Out-of-class implementation of class template member functions

grammar:

类内定义
template<class T1,class T2,...>
//类内
函数声明;

//类外
template<class T1,class T2,...>
作用域<T1, T2,...>::函数名(){
    
    }

Summary:
When a member function in a class template is implemented outside the class, a template parameter list needs to be added (whether the member function uses template parameters or not, it needs to be added , which can be recorded as a fixed way of writing).

Example:

#include<iostream>
using namespace std;

template<class T1,class T2>
class Person6 
{
    
    
public:
	Person6(T1 name, T2 age);  //类内声明
	//{
    
    
	//	this->name = name;
	//	this->age = age;
	//}
	void showPerson();
	//{
    
    
	//	cout << "姓名:" << name << endl;
	//	cout << "年龄:" << age << endl;
	//}
	T1 name;
	T2 age;
};

template<class T1,class T2>
Person6<T1, T2>::Person6(T1 name, T2 age) 
{
    
    
	this->name = name;
	this->age = age;
}
template<class T1, class T2>
void Person6<T1, T2>::showPerson()
{
    
    
	cout << "姓名:" << name << endl;
	cout << "年龄:" << age << endl;
}

void test6()
{
    
    
	Person6<string,int> p1("孙悟空",20);
	p1.showPerson();
}
int main()
{
    
    
	test6();
	system("pause");
	return 0;
}

22.7 Class Template File Compilation

question:

The conventional sub-file writing is to write the member function declaration of the class in the .h header file, and write the member function definition of the class in the .cpp source file.
The problem with this wrong writing is-because the member functions in the class will not be created by the compiler from the very beginning . When your execution file contains the header file, the compiler sees the declaration of the member function in the header file, and the compilation will not go wrong, but the compiler will not go to the corresponding .cpp file to find the definition of these member functions , which leads to The definitions of these member functions are not included in the execution file, so when calling, the compiler cannot find the definition corresponding to the declaration, and cannot parse the command.

Solved :

  • Method 1: Directly include .cpp source files (generally not commonly used)
  • Method 2: Write the declaration and implementation into the same file, and change the suffix to .hpp. hpp is the agreed name, not mandatory (it is generally created in the header file).

Example (method 1 is very simple, only method 2 is shown here):

Create a person.hpp header file, write

#pragma once
#include<iostream>
using namespace std;
template<class T1, class T2>
class Person7
{
    
    
public:
	Person7(T1 name, T2 age);
	void showPerson();
	T1 name;
	T2 age;
};

template<class T1, class T2>
Person7<T1, T2>::Person7(T1 name, T2 age)
{
    
    
	this->name = name;
	this->age = age;
}
template<class T1, class T2>
void Person7<T1, T2>::showPerson()
{
    
    
	cout << "姓名: " << name << endl;
	cout << "年龄: " << age << endl;
}

To create an executable file, write

#include"person.hpp"
#include<string>
//类模板分文件编写问题及解决

void test7()
{
    
    
	Person7<string, int> p1("孙悟空", 1000);
	p1.showPerson();
}
int main()
{
    
    
	test7();
	system("pause");
	return 0;
}

22.8 Class templates and friends


Class templates cooperate with in-class and out-of-class implementations of friend functions:
  • Implementation of global functions within the class - just declare friends directly within the class.
  • Out-of-class implementation of global functions - the compiler needs to be informed of the existence of global functions in advance.
#include<iostream>
using namespace std;

//通过全局函数来打印Person8的信息

template<class T1,class T2>
class Person8;
//类外实现
// 
//对于普通的函数和类都是一开始就由编译器创建,因为这里只声明了类而没有定义,所以会报错.
//因为函数模板同样不是一开始就被创建,而是在调用的时候才创建,所以这里不用担心类的声明和定义顺序
//因为在调用此函数模板的时候,类模板的相关定义已经被创建完毕.
template<class T1, class T2>    //函数模板的实现
void printPerson2(Person8<T1, T2> p)
{
    
    
	cout << "姓名 : " << p.name << endl;
	cout << "年龄 : " << p.age << endl;
}

template<class T1,class T2>
class Person8
{
    
    
	//全局函数 类内实现    据说只能在vs上通过
	friend void printPerson(Person8<T1, T2> p)
	{
    
    
		cout << "姓名 : " << p.name << endl;
		cout << "年龄 : " << p.age << endl;
	}
	//加空模板参数列表
	//如果全局函数是类外实现,需要让编译器知道这个函数的存在
	friend void printPerson2<>(Person8<T1, T2> p);  //声明为函数模板

public:
	Person8(T1 name, T2 age) 
	{
    
    
		this->name = name;
		this->age = age;
	}
private:
	T1 name;
	T2 age;
};

void test01()
{
    
    
	Person8<string, int> p1("汤姆", 20);
	printPerson(p1);
	printPerson2(p1);
}
int main()
{
    
    
	test01();
	system("pause");
	return 0;
}

The implementation of global functions outside the class is still very complicated.
Note:
printPerson2The members of the class are called in the function, and this class has only declarations and no definitions before this function. This cannot be compiled for ordinary classes. You can refer to the friends of the class and the definition and declaration order of the class .
But it is allowed for class templates and function templates, because function templates are not created at the beginning, but are created when they are called. The template of the class has been created before calling the function template, so there is no need to worry about the class The order of declaration and definition.


22.9 Class Template Case - Requirements Analysis of Array Class Encapsulation

Case description: To implement a general array class, the requirements are as follows:

  • Data of built-in data types and custom data types can be stored
  • Store the data in the array to the heap
  • The capacity of the array that can be passed in to the constructor
  • Provide corresponding copy constructor and operator= to prevent shallow copy problems
  • Provide tail insertion and tail deletion methods to add and delete data in the array
  • The elements in the array can be accessed by subscripting
  • You can get the current number of elements in the array and the capacity of the array

Example:
Create a header file:MyArray.hpp

#pragma once //防止重复包含
#include<iostream>
using namespace std;

template<class T>
class MyArray
{
    
    
public:
	//有参构造,参数是容量
	MyArray(int capacity)
	{
    
    
		cout << "MyArray有参构造调用" << endl;
		this->capacity = capacity;
		this->size = 0;
		this->pAddress = new T[this->capacity]; //capacity表示开辟的数组中的元素个数
	}

	//拷贝构造
	MyArray(const MyArray& arr)
	{
    
    
		cout << "MyArray拷贝构造调用" << endl;
		//浅拷贝
		this->capacity = arr.capacity;
		this->size = arr.size;
		//深拷贝
		this->pAddress = new T[arr.capacity];
		for (int i = 0; i < this->size; i++)
		{
    
    
			this->pAddress[i] = arr.pAddress[i];
		}
	}

	MyArray& operator=(const MyArray& arr)
	{
    
    
		cout << "MyArray的operator=构造调用" << endl;
		//判断原来堆区是否有数据
		if (this->pAddress != NULL)
		{
    
    
			delete[] this->pAddress;
			this->capacity = 0;
			this->size = 0;
		}
		//深拷贝
		this->capacity = arr.capacity;
		this->size = arr.size;
		this->pAddress = new T[arr.capacity];
		for (int i = 0; i < this->size; i++)
		{
    
    
			this->pAddress[i] = arr.pAddress[i];
		}
		return *this;
	}

	//尾插法
	void Push_Back(const T& value)
	{
    
    
		if (this->size == this->capacity) 
		{
    
    
			return;
		}
		this->pAddress[this->size] = value;
		this->size++;
	}
	//尾删法
	void Pop_Back()
	{
    
    
		//让用户访问不到删除最后一个元素,即为尾删,逻辑删除
		if (this->size == 0)
		{
    
    
			return;
		}
		this->size--;
	}
	
	//通过下标方式访问数组的元素 ,如果想让这个值作为左值,需要用引用类型,否则不会改变原数组元素. arr[0]=100
	T& operator[](int index)
	{
    
    
		return this->pAddress[index];
	}

	//返回数组的容量
	int getCapacity()
	{
    
    
		return this->capacity;
	}
	int getSize()
	{
    
    
		return this->size;
	}

	//析构函数
	~MyArray()
	{
    
    
		if (this->pAddress != 0)
		{
    
    
			cout << "MyArray析构函数调用" << endl;
			delete[] this->pAddress; //释放堆区数组
			this->pAddress = 0;
		}
	}
private:
	T* pAddress;    //指向堆区开辟的真实数组

	int capacity; //数组容量

	int size; //数组大小
};

Create executable file:

#include<iostream>
using namespace std;
#include"MyArray.hpp"

void printArray(MyArray<int>& arr)
{
    
    
	for (int i = 0; i < arr.getSize(); i++)
	{
    
    
		cout << arr[i] << endl;   //索引
	}
}
void test9_01()
{
    
    
	MyArray<int> arr1(10);
	MyArray<int> arr2(arr1);
	MyArray<int> arr3(100);
	arr3 = arr1;
}

void test9_02() 
{
    
    
	MyArray<int> arr1(10);
	for (int i = 0; i < 6; i++)
	{
    
    
		//尾插法插入数据
		arr1.Push_Back(i);
	}
	cout << "arr1的打印输出为: " << endl;
	printArray(arr1);

	cout << "arr1的容量为: " << arr1.getCapacity() << endl;
	cout << "arr1的大小为: " << arr1.getSize() << endl;

	MyArray<int> arr2(arr1);
	cout << "arr2的打印输出为: " << endl;
	printArray(arr2);
	arr2.Pop_Back();
	cout << "尾删后\narr2的打印输出为: " << endl;
	printArray(arr2);
}

//测试自定义数据类型
class Person9
{
    
    
public:
	Person9() {
    
    };   //必须写无参构造,向堆区开辟内存的时候是没有参数的,如果不写无参构造函数,会报错.
	Person9(string name, int age)
	{
    
    
		this->name = name;
		this->age = age;
	}
	string name;
	int age;
};
void printArray(MyArray<Person9>& arr)   //函数重载
{
    
    
	for (int i = 0; i < arr.getSize(); i++)
	{
    
    
		cout << "姓名: " << arr[i].name << "  年龄: " << arr[i].age << endl;
	}
}
void test9_03()
{
    
    
	//在向堆区开辟内存的时候如果有有参构造函数,就不会调用无参构造函数,而这里又没有传入参数,那么就无法开辟内存而报错
	MyArray<Person9> arr(10); 
	Person9 p1("孙悟空", 999);
	Person9 p2("猪八戒", 900);
	Person9 p3("沙僧", 850);
	Person9 p4("赵云", 550);
	Person9 p5("关羽", 650);

	//数据插入
	arr.Push_Back(p1);
	arr.Push_Back(p2);
	arr.Push_Back(p3);
	arr.Push_Back(p4);
	arr.Push_Back(p5);

	printArray(arr);

	//输出容量
	cout << "arr的容量为: " << arr.getCapacity() << endl;
	//输出大小
	cout << "arr的大小为: " << arr.getSize() << endl;
}
int main()
{
    
    
	test9_01();
	test9_02();
	test9_03();
	system("pause");
	return 0;
}

Guess you like

Origin blog.csdn.net/qq_49030008/article/details/123569976