泛型编程 之模板(template)

news2024/11/27 16:53:19

    C++另一种编程思想称为 泛型编程,主要利用的技术就是模板


        

目录

    C++另一种编程思想称为 泛型编程,主要利用的技术就是模板

          一、概念

          二、函数模板 

        1、语法与使用:

        2、函数模板注意事项

        3、普通函数与函数模板的区别

        4、普通函数与函数模板的调用规则

        5、模板的局限性

         三、类模板

作用:建立一个通用类,类中的成员属性的数据类型可以不具体指定,用一个虚拟的类型来表示

1、语法与使用

 2、类模板与函数模板的区别

 3、类模板中成员函数创建时机

4、类模板对象作函数参数

三种方式:

①指定传入的类型: 直接显示对象的数据类型(主要用的类型)

②参数模板化:        将对象中的参数变为模板进行传递

③整个类 模板化:   将这个对象类型 模板化进行传递

 5、类模板与继承

①当子类继承的父类是一个类模板时,子类在声明时,要指定出父类中T的类型

②如果不指定,编译器无法给子类分配内存,因为不知道父类中成员的大小

③如果想灵活指出父类中T的类型,子类也需要变成类模板

6、类模板成员函数类外实现

①类模板构造函数类外实现

②类模板成员函数类外实现

7、类模板分文件编写

①直接包含.cpp文件

②将声明与实现写在一个hpp文件中(约定俗成hpp,并不必须)

8、类模板与友元

①全局函数类内实现:直接在类内声明友元即可

②全局函数类外实现:先提取让编译器知道全局函数的存在

 9、类模板案例

 实现一个通用的数组类,要求:

①可以对内置数据类型以及自定义数据类型进行存储

②将数组中的数据存储到堆区

③构造函数中要传入数组的容量

④提供拷贝构造函数以及operator=,以防止浅拷贝问题

⑤提供尾插法与尾删法对数组中数据进行增加与删除

⑥通过下标的方式访问数组中的元素

⑦获取当前数组中元素的个数以及数组的容量


        一、概念

建立通用的模具,提高复用性

拍一寸照片的模板:

 制作PPT的模板

但是注意:模板并不是万能的,如人的1寸照片模板,不能用来拍其他生物的照片

而且,制作PPT时不能直接使用,需要自己加内容啥的


因此:

①模板不能直接使用,只是一个框架,需要自己合理使用

②模板是通用的,但不是万能的 


          二、函数模板 

作用:建立一个通用函数,其返回值类型与形参类型可以不指定,用一个虚拟的类型来代表


        1、语法与使用:

template<typename T>
函数声明或定义

template:声明创建模板

typename:表示其后面的符号是一种数据类型,可以用class

T:通用的数据类型,名称可以替换,通常为大写字母T


接下来使用交换函数swap来举例:

首先写出int整型交换函数float浮点型交换函数

// 整型 交换函数
void swapInt(int& x, int& y)
{
	int tmp = y;
	y = x;
	x = tmp;
}
// 浮点型 交换函数
void swapFloat(float& x, float& y)
{
	float tmp = y;
	y = x;
	x = tmp;
}

测试:

int main()
{
	int a = 10;
	int b = 20;
	swapInt(a, b);
	cout << a <<" " << b << endl;

	float c = 10.5;
	float d = 20.5;
	swapFloat(c, d);
	cout << c << " " << d << endl;

	return 0;
}

 可以正常交换并输出


但是,想要交换不同的数据,就必须实现不同的函数,改变返回值类型或者形参类型,但是具体代码又类似

因此,我们使用函数模板

template<typename T>
void Myswap(T& x, T& y)
{
	T tmp = y;
	y = x;
	x = tmp;
}

调用Myswap函数

	int a = 10;
	int b = 20;
	Myswap(a, b);
	cout << a <<" " << b << endl;

	float c = 10.5;
	float d = 20.5;
	Myswap(c, d);
	cout << c << " " << d << endl;

而我们在调用函数时,并未告知Myswap函数我们传入的类型是什么,使编译器自动推导出类型并实现

这叫做 自动推导类型


还有个 显式指定类型

	int e = 100;
	int f = 20;
	Myswap<int>(e, f);
	cout << e << " " << f << endl;

 就是调用函数时,在参数列表前加上<int>,这就相当于告知编译器刚才的类型T等于int


       2、函数模板注意事项

①自动推导类型,必须推导出一致的数据类型T,才可以正常使用

以我们上面的交换函数Myswap举例:

template<typename T>
void Myswap1(T& x)
{
	T tmp = y;
	y = x;
	x = tmp;
}

我们写2个不同的数据类型,尝试调用

	int a = 10;
	float b = 22.4;
	Myswap1(a, b);

直接报错

 因此不能使用

不过,如果在函数模板里写2个typename,则可以正常调用

template<typename T,typename Y>
void Myswap1(T& x, Y& y)
{
	Y tmp = y;
	y = x;
	x = tmp;
}

2个数据类型

	int a = 10;
	float b = 22.4;
	Myswap1(a, b);

 此时可以正常输出,但是输出结果都是int整型

②模板必须要确定T的数据类型,才可以使用

template<typename T>
void test01()
{
	cout << "ko" << endl;
}

此时函数内未声明T的数据类型

尝试调用

直接报错

因为未确定T的函数类型


解决:

在函数调用参数列表前加上<int>,即随便指定个数据类型,因为函数中没有使用具体的类型

 


实例1:

使用函数模板实现选择排序不同数据类型的数组进行降序排序

首先使用模板实现选择排序

template<typename T>
void Select_sort(T arr[],int len)
{
	int index = 0;
	for (int i = 0; i < len - 1; i++)
	{
		index = i;
		for (int j = i + 1; j < len; j++)
		{    // 我们认定的最大值比数组中某个值小
			if (arr[index] < arr[j])
			{   // 交换两者下标
				index = j;
			}
		}
		if (index != i) // 如果交换了下标(不等于原来的i)
		{
			MYswap(arr[index], arr[i]);//进行数组中数据的交换
		}
	}
}

最后一步有数据的交换,我们接着使用模板实现交换函数

template<typename T>
void MYswap(T& x, T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

这里就完成了,不过为了使数组中内容可以呈现在屏幕上,我再额外实现一个打印函数

template<typename T>
void print(T arr, int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}

①int类型数组测试

void int_test()
{
	// 整型数组测试
	int arr[] = { 2,4,6,1,3,26,7,1,2,4,9,0,2,3 };
	int len = sizeof(arr) / sizeof(arr[0]);
	print(arr, len);
	Select_sort(arr, len);
	cout << endl;
	print(arr, len);
}

②char类型数组测试

void char_test()
{
	// 字符数组测试
	char arr2[] = "zcnasijda";
	int len2 = sizeof(arr2) / sizeof(char);
	print(arr2, len2);
	Select_sort(arr2, len2);
	cout << endl;
	print(arr2, len2);
}

成功进行降序排序 


        3、普通函数与函数模板的区别

普通函数调用时,可以发生自动类型转换,(隐式类型转换

函数模板调用时,如果使用自动类型推导,则不会发生隐式类型转换

函数模板调用时,如果使用显式指定类型,则可以发生隐式类型转换


首先创建普通函数

// 普通函数
void add01(int x, int y)
{
	cout << x + y << endl;
}

调用:

	add01(10, 20);

 创建字符变量c并调用

	char c = 'c';
	add01(10, c);

 因为c的ASCII码值是99,99+10=109,函数在内部进行隐式类型转换

下面用函数模板实现

template<typename T>
void add02(T x, T y)
{
	cout << x + y << endl;
}

使用自动类型推导调用10与字符c

 直接报错,因为不能进行隐式类型转换

而我们使用显式指定类型:

 可以正常输出,因为进行了隐式类型转换,无论你传入什么数据,都给你转成int,转不成就报错


        4、普通函数与函数模板的调用规则

①如果普通函数与函数模板都可以实现,优先调用普通函数

实现一个大致相同普通函数与函数模板

void print(int x, int y)
{
	cout << "普通函数" << endl;
}
template<typename T>
void print(T x, T y)
{
	cout << "函数模板" << endl;
}

调用生成

② 在情况①下,通过空模板参数列表,可以强制调用函数模板

 即在函数调用的参数列表前加<>

③函数模板也可以发送重载

template<typename T>
void print(T x, T y)
{
	cout << "函数模板" << endl;
}
template<typename T>
void print(T x, T y,T z) // 重载
{
	cout << "函数模板" << endl;
}

④如果函数模板可以发生更好的匹配,优先调用函数模板

 2个char类型的变量,传入普通函数需要进行隐式类型转换,而传入函数模板只需要进行自动类型推导,因此优先调用函数模板


        5、模板的局限性

局限性:模板的通用并不万能

举例:写一个判断变量是否相等的模板,以及相等输出相等,否则输出不等的函数

template<typename T>
bool My_compare(T a, T b)
{
	if (a == b)
	{
		return true;
	}
	else
	{
		return false;
	}
}
void judge(bool a)
{
	if (a)
	{
		cout << "相等" << endl;
	}
	else
	{
		cout << "不等" << endl;
	}
}

测试:

void test01()
{
	int a = 10;
	int b = 10;
	bool ret = My_compare(a, b);
	judge(ret);
}

很明显,a==b

 但是,如果a和b的类型是一个类class

class person
{
public:
	person(string name,int age)
	{
		m_age = age;
		m_name = name;
	}
	int m_age;
	string m_name;
};

void test02()
{
	person p1("Tim", 22);
	person p2("Tim", 22);
	bool ret = My_compare(p1, p2);
	judge(ret);
}

很明显,p1==p2,但是编译器无法正常运行

 因为person是自定义数据类型,编译器不知道咋办


因此,我们使用具体化person的版本实现代码,具体化优先调用

在下面重写一个模板,前面加template<>,后面数据类型写person

template<>bool My_compare(person& p1, person& p2)
{
	if (p1.m_age == p2.m_age && p1.m_name == p2.m_name)
	{
		return true;
	}
	else
	{
		return false;
	}
}

再次尝试运行

void test02()
{
	person p1("Tim", 22);
	person p2("Tim", 22);
	bool ret = My_compare(p1, p2);
	judge(ret);
}

成功,而传入数组判断相等也是同样的问题


总结:

①利用具体化的模板,可以解决自定义类型的通用化

②学习模板不是为了写模板,而是为了在STL能够运用系统提供的模板


三、类模板

作用:建立一个通用类,类中的成员属性的数据类型可以不具体指定,用一个虚拟的类型来表示


1、语法与使用

template<class T>
类

与函数模板基本相同,只要把typename改成class就行,而且二者可以互换,效果相同


例:写一个类模板,其中有属性name与age,不指定类型,调用输出

template<class Name_type,class Age_type >
class person
{
public:
	person(Name_type name, Age_type age)
	{
		m_name = name;
		m_age = age;
	}
	void show()
	{
		cout << this->m_name << this->m_age << endl;
	}
	Name_type m_name;
	Age_type m_age;
};

测试:

void test01()
{
	person<string, int> p1("Joyce", 22);
	p1.show();
}


 2、类模板与函数模板的区别

①类模板没有自动类型推导使用方式

对于刚才的类模板

template<class Name_type,class Age_type >
class person
{
public:
	person(Name_type name, Age_type age)
	{
		m_name = name;
		m_age = age;
	}
	void show()
	{
		cout << this->m_name << this->m_age << endl;
	}
	Name_type m_name;
	Age_type m_age;
};

如果我们使用自动类型推导:

直接报错

 而只能用显式指定类型

②类模板在模板参数列表中可以有默认参数

我们在模板的参数列表中加入默认参数

 让Age_type直接等于int

void test02()
{
	person<string> p2("tatina", 22); // 显式指定类型
	p2.show();
}

 在调用时,我们不用写其类型也可以正常运行


 3、类模板中成员函数创建时机

普通类中:成员函数一开始就可创建

类模板中:成员函数在调用时才创建


例:

首先创建2个类,区分为类1与类2,内部分别创建函数输出数字1与2

class person1
{
public:
	void show1()
	{
		cout << "1" << endl;
	}
};
class person2
{
public:
	void show2()
	{
		cout << "2" << endl;
	}
};

下面实现类模板

template<class T>
class Myclass
{
public:
	T obj;
	void m_show1()
	{
		obj.show1();
	}
	void m_show2()
	{
		obj.show2();
	}
};

测试:参数列表传person1,调用m_show1函数

void test02()
{
	Myclass<person1> m;
	m.m_show1();
}

输出1


尝试调用2:

 报错

此时就已经说明类模板中成员函数一开始没有创建,只有在调用了,才能确定对象的类型,才能创建成员函数

而我们类型传入person2,运行结果相反


4、类模板对象作函数参数

三种方式:

①指定传入的类型: 直接显示对象的数据类型(日常主要用的类型)

②参数模板化:        将对象中的参数变为模板进行传递

③整个类 模板化:   将这个对象类型 模板化进行传递


创建一个类模板参数类型为T1与T2

template<class T1,class T2>
class person
{
public:
	person(T1 name, T2 age)
	{
		m_name = name;
		m_age = age;
	}
	void showinfo()
	{
		cout << this->m_name << this->m_age << endl;
	}
	T1 m_name;
	T2 m_age;
};

①指定传入的类型:

void print1(person<string, int>& p)// 直接把下面的类型拿来使用
{
	p.showinfo();
}
void test01()
{
	person<string, int> p1("joyce", 21);
	print1(p1);
}

可见,print1函数参数直接拿p1的数据类型使用,这就是指定传入的类型


②参数模板化:

void test02()
{
	person<string, int> p2("tatina", 20);
	print2(p2);
}

print2函数

template<class T1, class T2> 
void print2(person<T1, T2>& p2)// 参树模板化为T1与T2
{
	p2.showinfo();
	cout << typeid(T1).name() << endl;// 查看T1的类型
	cout << typeid(T2).name() << endl;// 查看T2的类型
}

就是将参数也模板化为T1与T2

同时,如果想查看数据的类型,可以使用typeid().name()函数


③整个类模板化

 同样的测试函数

void test03()
{
	person<string, int> p3("yomi", 1);
	print3(p3);
}

然后是print3,将整个类模板化

template<class T>
void print3(T& p)// 直接用T,让编译器推导出类型
{
	p.showinfo();
}

看一下T的类型


 5、类模板与继承

注意:

①当子类继承的父类是一个类模板时,子类在声明时,要指定出父类中T的类型

②如果不指定,编译器无法给子类分配内存,因为不知道父类中成员的大小

③如果想灵活指出父类中T的类型,子类也需要变成类模板


首先,创建父类base类模板

template<class T>
class base
{
public:

	T m_a;
};

尝试创建子类

class son :public base
{
	;
};

直接报错:

需要指定父类中T的类型

 

 这样就可以

不过此时,父类中的T只能是指定的T类型,为了解决这个问题:

②将子类也类模板

template<class T1,class T2>
class son2 :public base<T2>
{
	T1 m_a;
};
void test02()
{
	son2<string,int> s2; // 显式指定类型
}

string就是T1,int就是T2

T2就是继承父类base,并指定父类中T的类型为int


接着我们输出T1与T2的类型

 


6、类模板成员函数类外实现

掌握类模板中的成员函数类外实现


首先,我们写一个常规的person类模板,其中有属性m_name与m_age,类型分别为T1与T2

带有构造函数与void show函数

template<class T1,class T2>
class person
{
public:
	person(T1 name,T2 age)
	{
		this->m_name = name;
		this->m_age = age;
	}
	void show()
	{
		cout << this->m_name << " " << this->m_age << endl;
	}
	T1 m_name;
	T2 m_age;
};

①类模板构造函数类外实现

接下来,我们先将类模板中的构造函数的实现部分屏蔽只留下声明,在类模板实现构造函数

template<class T1,class T2>
person<T1, T2>::person(T1 name, T2 age)
{
	this->m_name = name;
	this->m_age = age;
}

即在构造函数person(T1 name, T2 age)前加上作用域person::以及参数列表<T1,T2>

②类模板成员函数类外实现

template<class T1,class T2>
void person<T1,T2>::show() // 虽未用到T1T2,但由于其是类模板中成员函数,因此仍要写入T1/T2
{
	cout << this->m_name << " " << this->m_age << endl;
}

一样的加作用域与参数列表


测试:

void test01()
{
	person<string, int> p("joyce",21);
	p.show();
}


7、类模板分文件编写

首先,分文件编写类模板时(类模板.h头文件,其中的成员函数在.cpp源文件实现),成员函数创建时机是在调用阶段,导致编写时链接不到


先创建person.h头文件,其中写入person类模板

template<class T1, class T2>
class person
{
public:
	person(T1 name, T2 age);

	void show();

	T1 m_name;
	T2 m_age;
};

然后创建person.cpp源文件,其中实现person的函数,并包含.h头文件

// 构造函数
template<class T1, class T2>
person<T1, T2>::person(T1 name, T2 age)
{
	this->m_name = name;
	this->m_age = age;
}
// 成员函数
template<class T1, class T2>
void person<T1, T2>::show()
{
	cout << this->m_name << " " << this->m_age << endl;
}

到目前为止,代码运行没有任何问题

 但是,我们在测试函数中调用构造函数与成员函数

#include"person.h"

void test01()
{
	person<string, int> p("joyce",21);
	p.show();
}

直接报错 ,因为编译器无法链接类模板成员函数的实现cpp文件


①直接包含.cpp文件

我们将源文件包含的person.h文件改为person.cpp文件,这样编译器直接链接到了cpp文件与h文件 

成功实现


②将声明与实现写在一个hpp文件中(约定俗成名为hpp,并非必须)

 上面是声明,下面是类外实现

 在源文件中包含

成功运行 


8、类模板与友元

2种实现:

①全局函数类内实现:直接在类内声明友元即可

②全局函数类外实现:先提前让编译器知道全局函数的存在


首先,写一个person类模板,其中有属性m_age与m_name,以及构造函数person

template<class T1, class T2>
class person
{
public:
	person(T1 name, T2 age)
	{
		this->m_name = name;
		this->m_age = age;
	}

private:
	T1 m_name;
	T2 m_age;
};

①全局函数类内实现

接下来,在其中加上友元的全局函数

	// 全局函数 类内实现
	friend void print1(person <T1, T2>p)
	{
		cout << p.m_name << " " << p.m_age << endl;
	}

测试:

void test01()
{
	person<string,int> p("joyce", 21);
	print1(p);
}

②全局函数类外实现

首先在类内写上声明

 然后在类外实现

template<class T1,class T2>
void print2(person <T1, T2>p) // 因为有T1/T2,因此需要加上类模板
{
	cout << p.m_name << " " << p.m_age << endl;
}

测试:

void test02()
{
	person<string, int> p("joyce", 21);
	print2(p);
}

 依然是链接错误,因为类模板内我们写的是普通函数的声明,而下面实现写的是函数模板的实现,二者毫不相干


解决:①我们在声明的函数名后加上<>空参数列表

此时,还未完成,仍然无法使用,紧接着:

先将类外实现部分移动到类模板的最上方

 然后在类外实现部分的上面加上类模板的声明

这样下来,才能正确运行


 9、类模板案例

 实现一个通用的数组类,要求:

①可以对内置数据类型以及自定义数据类型进行存储

②将数组中的数据存储到堆区

③构造函数中要传入数组的容量

④提供拷贝构造函数以及operator=,以防止浅拷贝问题

⑤提供尾插法与尾删法对数组中数据进行增加与删除

⑥通过下标的方式访问数组中的元素

⑦获取当前数组中元素的个数以及数组的容量


首先,创建my_array类模板,包含属性p_array存储开辟的数组,m_capacity数组容量,m_size数组当前大小

template<class T>
class my_array
{
public:

	// 防止浅拷贝问题的拷贝构造函数与operator=
	my_array(int capacity);// 有参构造函数

	my_array(const my_array &p);// 拷贝构造函数

	my_array& operator=(const my_array& p); // 复制运算符的重载,返回引用才是返回自身

	void push_back();// 尾插

	void pop_back();  // 尾删

	int get_capacity();// 获取容量

	int get_size();	   // 获取大小

	~my_array();// 析构函数

private:
	T* p_array;    //	存储开辟的数组
	int m_capacity;//	数组的容量
	int m_size;	   //	数组当前大小
};

然后开始实现各个函数

①有参构造函数

	my_array(int capacity)// 有参构造函数
	{
		this->m_capacity = capacity;
		this->m_size = 0;
		this->p_array = new T[this->m_capacity]; // 开辟堆区空间以存储p_array
		cout << "有参构造函数" << endl;
	}

②拷贝构造函数

	my_array(const my_array &p)// 拷贝构造函数
	{
		this->m_capacity = p.m_capacity; 
		this->m_size = p.m_size;
		//this->p_array = p.p_array;// 带来浅拷贝问题
		this->p_array = new T(p.m_capacity);// 根据p的大小重新开辟空间以赋值
		// 将p中的数据也拷贝过来
		for (int i = 0; i < p.m_size; i++)
		{
			this->p_array[i] = p.p_array[i];
		}
		cout << "拷贝构造函数" << endl;
	}

③operetor=等号重载

	my_array& operator=(const my_array& p) // 赋值运算符的重载,返回引用才是返回自身
	{
		if (this->p_array != NULL) // 判断是否有属性在堆区,有则释放
		{
			delete[] this->p_array;
			this->p_array = NULL;
			this->m_capacity = 0;
			this->m_size = 0;
		}
		// 深拷贝
		this->m_capacity = p.m_capacity;
		this->m_size = p.m_size;
		this->p_array = new T[p.m_capacity]; // 重新开辟空间
		for (int i = 0; i < p.m_size; i++) // 数据拷贝
		{
			this->p_array[i] = p.p_array[i];
		}
		cout << "operator=" << endl;
		return *this;
	}

④析构函数

	~my_array()// 析构函数
	{
		if (this->p_array != NULL)
		{
			delete[] this->p_array;
			this->p_array = NULL;
		}
		cout << "析构函数" << endl;
	}

此时,我们先运行一下各个函数

#include"my_array.hpp"
int main()
{
	my_array<int> arr1(5); // 有参构造
	my_array<int> arr2(arr1);// 拷贝构造
	my_array<int> arr3(100); 
	arr1 = arr3; // 自定义类型=赋值
return0;
}


⑤尾插 数据

首先,判断数组当前元素个数size是否等于容量capacity,如果相等则数组满了,直接return。

否则,直接将传入的数据赋给数组第size个元素,arr[size],因为当前个数为size个,数组下标为0-size-1的元素都有了,下一个为空的就是第size个,因此赋给第size个

	void push_back(const T& val)// 尾插
	{
		// 先判断数组是否满
		if (this->m_size == this->m_capacity)
		{
			return;
	/*		int new_capacity = m_capacity + 1;
			this->p_array = new T(new_capacity);*/
		}
		this->p_array[this->m_size ] = val; // 尾插数据
		this->m_size++;	// 当前大小+1
	}

⑥尾删 数据

使用户访问不到最后一个元素即可,即 使size-1,这样数组元素就少了一个

	void pop_back()  // 尾删
	{
		if (this->m_size == 0)
		{
			cout << "数组为空!" << endl;
			return;
		}
		this->m_size--;
	}

⑦以下标方式访问数组元素

由于my_Array是我们自己创建的数组类型,其中的元素数据类型都是T,因此我们不能直接用[]访问到元素,需要我们重载[]运算符


我们使用[]下标访问元素是为了获取一个数据,所以我们重载就直接返回一个元素的数据就行

	T& operator[](int index)
	{
		return this->p_array[index];
	}

返回值类型就是我们数组my_array中的数据类型T,同时保证返回前后是同一个元素我们用&


⑧获取大小与获取容量

直接返回size与capacity即可

	int get_capacity() // 获取容量
	{
		return this->m_capacity;
	}

	int get_size()	   // 获取大小
	{
		return this->m_size;
	}

代码完成,接下来

①我们创建数组,并打印输出

template<class T>
void print(T&arr) // 打印函数
{
	for (int i = 0; i < 5; i++)
	{
		cout << arr[i] << " ";
	}
}
int main()
{
	my_array<int> arr1(5); // 有参构造
	for (int i = 0; i < 5; i++)
	{
		arr1.push_back(i);
	}
	print(arr1);

	return 0;
}

成功输出范围内的数据

② 我们再看一下我们创建数组的容量和大小

	cout << "capacity" << arr1.get_capacity() << endl;
	cout << "size" << arr1.get_size() << endl;

 ③我们拷贝arr1构造arr2,并尾删数据,再打印输出

void test01()
{
	my_array<int> arr1(5); // 有参构造

	for (int i = 0; i < 5; i++)
	{
		arr1.push_back(i);
	}
	print(arr1);
	cout << "capacity" << arr1.get_capacity() << endl;
	cout << "size" << arr1.get_size() << endl;

	my_array<int> arr2(arr1);// 拷贝构造
	print(arr2);
	arr2.pop_back();
	print(arr2);
	cout << "capacity" << arr2.get_capacity() << endl;
	cout << "size" << arr2.get_size() << endl;
}

 ④我们创建自定义数据类型,然后使用尾插尾删等的函数

先创建自定义数据类型person

class person
{
public:
	person() {};
	person( string name,int age )
	{
		this->m_age = age;
		this->m_name = name;
	}
	int m_age;
	string m_name;
};

创建打印函数print2(以我们自定义数据类型为准来创建)

void print2(my_array<person>& arr)
{
	for (int i = 0; i < arr.get_size(); i++)
	{
		cout << arr[i].m_name <<arr[i].m_age<<endl;
	}
	cout << endl;
}

创建数组arr并初始化,先输出每个元素的信息与大小容量

然后尾删2个元素后,再次输出每个元素的信息与大小容量

void test02()
{
	// 创建数组并初始化
	my_array<person>arr(10);
	person a1("joyce", 21);
	person a2("tatina", 20);
	person a3("knaz", 40);
	person a4("nana", 20);
	person a5("yomi", 1);
	// 尾插到数组中
	arr.push_back(a1);
	arr.push_back(a2);
	arr.push_back(a3);
	arr.push_back(a4);
	arr.push_back(a5);
	// 打印数组中的每个元素的数据与大小容量
	print2(arr);
	cout << "capacity" << arr.get_capacity() << endl;
	cout << "size" << arr.get_size() << endl;
	// -----------------------------------------------------
	// 尾删2个元素
	arr.pop_back();
	arr.pop_back();
	// 打印数组中的每个元素的数据与大小容量
	print2(arr);
	cout << "capacity" << arr.get_capacity() << endl;
	cout << "size" << arr.get_size() << endl;
}


至此全部完成

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

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

相关文章

“探究二叉搜索树:从原理到实现“

&#x1f4d6;作者介绍&#xff1a;22级树莓人&#xff08;计算机专业&#xff09;&#xff0c;热爱编程&#xff1c;目前在c&#xff0b;&#xff0b;阶段>——目标Windows&#xff0c;MySQL&#xff0c;Qt&#xff0c;数据结构与算法&#xff0c;Linux&#xff0c;多线程&…

数量形状遗传率及计算方法

数量性状的遗传率/遗传力(heritability) (1)表型值及其方差的分量 1. 表型值及其剖分 某数量性状的表型值就是实际所度量或观察到的数值。表型值受许多外界因素如士壤、肥力、水分、光照、温度等的改变而发生变异&#xff0c;这种变异归因于环境因素。任何一个数量性状的表现…

SentiBank Dector上手指南

​ 官网链接&#xff1a;https://www.ee.columbia.edu/ln/dvmm/vso/download/sentibank.html SentiBank Detector可以抽取图片中的形容词-名词对&#xff0c;之前一直看到&#xff0c;这次复现模型才第一次用到&#xff0c;上手的时候有点手足无措&#xff0c;因为官网在如何使…

傅里叶变换解析

p.s.本文无论是cos还是sin&#xff0c;都统一用“正弦波”(Sine Wave)一词来代表简谐波。 一、什么是频域 从我们出生&#xff0c;我们看到的世界都以时间贯穿&#xff0c;股票的走势、人的身高、汽车的轨迹都会随着时间发生改变。这种以时间作为参照来观察动态世界的方法我们称…

自动驾驶技术的优势、局限性及未来发展趋势

自动驾驶技术是当前汽车行业的热门话题之一。该技术的发展&#xff0c;不仅可以提高车辆的安全性和行驶效率&#xff0c;还可以为人们的出行带来更多便利。但与此同时&#xff0c;自动驾驶技术也存在着许多争议和挑战。接下来从以下四个方面谈一下我对自动驾驶技术的看法。 一…

贝叶斯决策理论

贝叶斯决策理论的相关知识 贝叶斯的思想&#xff1a;顾名思义&#xff0c;贝叶斯决策论是利用概率来进行决策&#xff0c;是概率框架下的方法。贝叶斯决策论是利用概率的不同分类决策与相应的决策代价之间的平衡&#xff0c;核心思想是决策问题可以通过概率的形式来描述。 1.…

数量性状基因座QTL及其作图

数量性状基因座作图原理与步骤 经典的数量遗传分析方法 ->只能分析控制数量性状表现的众多基因的综合遗传效应,无法准确鉴别基因的数目、单个基因在染色体上的位置和遗传效应 (1)数量性状基因座(QTL) Quantitative trait loci: QTL 数量性状位点(基因座) 所谓QTL是指通过…

基于TCP的C/S模型代码实现

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

自学C#,要懂得善用MSDN

很多初学者学习编程&#xff0c;都会通过看别人写的教程、或者录制的视频&#xff0c;来学习。 这是一个非常好的途径&#xff0c;因为这个是非常高效的。 但是这样&#xff0c;存在两个问题&#xff1a; 1、教程不够全面&#xff1a;任何再好的教程&#xff0c;都无法囊括所…

Java 输出机制 数据类型 基本数据类型转换 基本数据类型和String类型的转换

目录 一、输出机制 1.print和println的差别 2.可接收不同类型参数 3.输出函数中 符号的使用 二、数据类型 1.整型类型 2.浮点类型 3.字符类型 三、基本数据类型转换 1.自动类型转换 2.强制类型转换 3.练习题 四、基本数据类型和String类型的转换 1.基本类型转S…

论文写作精品课程

本文介绍了一些论文写作的在线课程&#xff0c;方便读者朋友们自学&#xff0c;提高论文写作的能力。论文写作的在线课程非常多&#xff0c;读者朋友们也可以在网上自行搜索&#xff0c;选择适合自己的在线课程进行学习。如需要打开课程的网站&#xff0c;请复制课程的网址到浏…

PostgreSQL16中pg_dump的LZ4和ZSTD压缩

PostgreSQL16中pg_dump的LZ4和ZSTD压缩 pg_dump压缩lz4和zstd LZ4和ZSTD压缩算法合入了PG16。LZ4补丁的作者是Georgios Kokolatos。由Tomas Vondra提交。由Michael Paquier、Rachel Heaton、Justin Pryzby、Shi Yu 和 Tomas Vondra 审阅。提交消息是&#xff1a; Expand pg_dum…

【Java EE初阶】计算机简介及多线程之创建线程

目录 1.计算机发展史 2.冯诺依曼体系 3.操作系统 操作系统的作用&#xff1a; 4.进程 1.PID&#xff08;进程编号&#xff09; 2.内存指针 应用程序申请到的内存中的首地址 3.文件描述符表 问&#xff1a;什么是并发&#xff1f;什么是并行&#xff1f; 4.进程的优先级&a…

Tomcat8和Tomcat9乱码问题

今天新开了一个小项目&#xff0c;我丢&#xff0c;乱码了&#xff0c;咋回事&#xff0c;好久没遇到过了&#xff0c;都忘了咋回事。今天必须记录下来&#xff0c;避免继续踩坑 Tomcat 8 不需要进行任何配置即可&#xff0c;它默认的是GBK&#xff0c;而win10 win7 默认的也是…

[Data structure]单链表 | 一文介绍线性数据结构之一的单链表(Java实现)

⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 ⭐所属专栏&#xff1a;数据结构。数据结构专栏主要是在讲解原理的基础上拿Java实现 ⭐如果觉得文章写的不错&#xff0c;欢迎点个关注一…

R语言 | 数据框

目录 一、认识数据框 7.1 建立第一个数据框 7.2 验证与设定数据框的列名和行名 二、认识数据框的结构 三、获取数据框内容 3.1 一般获取 3.2 特殊字符$ 3.3 再看取得的数据 四、使用rbind()函数增加数据框的行数据 五、使用cbind()函数增加数据框的列数据 5.1 使用$符号…

《LearnUE——基础指南:开篇—3》——基础概念

目录 程序之祖——HellowWorld 0.3.1 创建HellowWorld工程 0.3.2编译类型 0.3.3 平台支持 0.3.4 命名约定 程序之祖——HellowWorld 0.3.1 创建HellowWorld工程 接上文准备工作完成之后&#xff0c;双击运行Unreal Engine快捷图标&#xff0c;或者双击生成的UE4Editor.exe…

【计算机图形学】图形变换(以任意直线为对称轴的对称变换)

模块3-2 图形变换 一 实验目的 编写图形各种变换的算法 二 实验内容 1&#xff1a;任意直线的对称变换。要求将变换矩阵写在实验报告中&#xff0c;并与代码匹配。求对任意直线AxByC0的对称变换矩阵。 实验结果如下图所示&#xff1a; 1&#xff1a;预设图形初始化 2&#…

数据结构与算法(小议递归二)

文章目录 前言一、例二二、为什么总结 前言 前面说到了递归在裴波那契数列计算中并不怎么适用&#xff0c;那么它适合什么样的场景呢&#xff1f; 我们继续举例和python3对比测试来说明。 一、例二 下面我们试试阶乘&#xff0c;在前面的代码上稍稍改一下就可以了&#xff1a…

ApachePOI操作Excel快速入门使用

简介 Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目&#xff0c;主要任务是创建和维护Java API&#xff0c;以基于Office Open XML标准&#xff08;OOXML&#xff09;和Microsoft的OLE 2复合文档格式&#xff08;OLE2&#xff09;处理各种文件格式&#xff0…