【C++第三阶段】模板类模板通用数组实现案例

news2024/10/7 4:36:48

以下内容仅为当前认识,可能有不足之处,欢迎讨论!


文章目录

  • 模板
    • 怎么使用模板
    • 函数模板注意事项
    • 普通函数与函数模板的区别
    • 普通函数与函数模板调用规则
    • 函数模板限制
  • 类模板
    • 类模板语法
    • 类模板与函数模板区别
    • 类模板中成员函数创建时机
    • 类模板对象做函数参数
      • 打印类别
    • 类模板与继承
    • 类模板成员函数类外实现
    • 类模板分文件编写
    • 类模板与友元
    • 类模板案例-通用数组实现


本教程针对泛型编程和STL做技术讲解,探讨更深层的使用。

模板

什么是模板,模板的目的是什么?怎么用模板,优缺点,能用在哪里。

C++提供两种模板机制,函数模板和类模板。

什么是模板?

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

模板的目的是什么?

提高代码的复用性,同时解决函数重载的问题:不能根据情况指定不同的返回值类型。以及其他函数重载的问题(2024年3月26日20点44分暂时想不到了)

黑马:建立通用函数,其函数返回值类型和形参类型可以不具体指定,用一个虚拟的类型代表。

怎么使用模板

首先,明确模板的语法如下:

template<typename T>
//后写方法

其中

template 是创建模板的声明,说明下面接着的方法用到了模板。

typename是传入不同类型的参数,这个类型是typename。

黑马:typename-表明其后符号是一种数据类型,可以用class类型。

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

接着是模板使用具体案例:

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

//以下是模板的简单举例
//说明返回值是temp。
template <typename T>
T MySwap(T& param1, T& param2) {
	T temp = param1;
	param1 = param2;
	param2 = temp;
	return temp;
}

void test0326a() {
	int a = 10;
	int b = 20;
	cout << "now , a = " << a << " , b = " << b << " . " << endl;
	//隐式调用模板函数
	MySwap(a, b);
	cout << "After implicitly utilizing swap func , a= " << a << " , b = " << b << " . " << endl;

	//显式调用模板函数
	MySwap<int>(a, b);
	cout << "After explicitly utilizing swap func , a = " << a << " , b = " << b << " . " << endl;
}

int main() {

	test0326a();

	system("pause");
	return 0;
}

可以看到,有两种使用模板方式,隐式及显式。隐式直接传入参数即可,由编译器自行判断参数是何数据类型。显式需要在调用时在函数名与参数列表中用<>说明是何数据类型。

运行结果:

image-20240326231120001

函数模板注意事项

隐式:自动类型推导,必须推导出一致的数据类型T,才可以使用。

显式:模板必须要确定T的数据类型,才可以使用。

注意事项一:必须推导出一致的数据类型T才可以使用。比如下面的将C作为参数传入,识别出两个数据类型不一致。

image-20240326214133982

注意事项二:既然用了函数模板,就需要确定T的数据类型。

像下面的函数,如果隐式地让编译器自动确定数据类型,编译器不知道要确定哪个数据类型。就会报错。所以需要显式地指定数据类型。

image-20240326214501054

image-20240326214454022

所以,解决办法就是显式地指定数据类型。

void test0326c() {
	print<float>();
}

对应代码:

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

//以下是模板的简单举例
//说明返回值是temp。
template <typename T>
T MySwap(T& param1, T& param2) {
	T temp = param1;
	param1 = param2;
	param2 = temp;
	return temp;
}

template <typename T>
void print() {
	cout << "print 函数运行." << endl;
}

void test0326b() {
	int a = 10;
	int b = 20;
	char c = 'c';
	cout << "原来的a = " << a << " , b = " << b << " . " << endl;
	//MySwap(a, c);
}

void test0326c() {
	print<float>();
}

int main() {

	test0326c();

	system("pause");
	return 0;
}

运行结果:

image-20240326231048933

一个通用模板排序数组的案例代码:

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

template<typename T>
void MySwap(T &param1 , T &param2){
	T temp = param1;
	param1 = param2;
	param2 = temp;
}

template<typename T>
void print(T& array, int number) {
	//cout << "当前数组为:";
	for (int i = 0; i < number; i++) {
		if (array[i] != '\0') {

			cout << array[i] << "\t";
		}
	}
	cout << endl;
}

template <typename T>
void sort(T& array) {
	int member_number = sizeof(array) / sizeof(array[0]);
	print(array, member_number);
	for (int i = 0; i < member_number; i++) {
		for (int j = i; j < member_number-1; j++) {
			if (array[i] > array[j + 1]) {
				MySwap(array[i], array[j + 1]);
			}
		}
	}
	print(array, member_number);
}



int main() {
	int int_array[] = { 2,9 ,3, 5 , 1 };
	float float_array[] = { 3.1 , 9.1 , 3.6 , 5.2 };
	char char_array[] = "fezcda";
	sort(char_array);
	sort(int_array);
	sort(float_array);
	system("pause");
	return 0;
	}

运行结果:

image-20240326230951333

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

区别:是否会发生隐式类型转换。

A—普通函数传入参数时,会发生隐式类型转换。

B—a函数模板传入参数,如果是隐式地自动类型推导,不会发生隐式类型转换,会发生报错。

B—b函数模板传入参数,如果是显式地指定类型方式,会发生隐式类型转换。

根据以上三个编号,分别设计代码验证。

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

//设计简单的加法函数与加法重载
int MyAdd(int a, int b) {
	//如果是引用方式就不可以
	return a + b;
}

template<typename T>
T MyTempAdd(T a, T b) {//如果是引用方式就不可以
	return a + b;
}


int main() {
	int int_array[] = { 2,9 ,3, 5 , 1 };
	float float_array[] = { 3.1 , 9.1 , 3.6 , 5.2 };
	char char_array[] = "fezcda";

	int a = 10;
	int b = 13;
	char c = 'c'; //ASCII = 99
	cout <<"myadd a+b = " << MyAdd(a, b) << endl;
	cout << "myadd a+c = " << MyAdd(a, c) << endl;
	cout << "MyTempAdd a+b = " << MyTempAdd(a, b) << endl;
	cout << "MyTempAdd a+c = " << MyTempAdd<int>(a, c) << endl;//必须显式指定数据类型,否则会报错。
	system("pause");
	return 0;
}

image-20240326230719245

运行结果:

image-20240326230823336

问题:这里就不能再传入引用,如果引用,必须是对应的数据类型,无论是函数还是模板。

普通函数与函数模板调用规则

1.如果函数模板和普通函数都可以实现,优先调用普通函数。

2.可以通过空模板参数列表来强制调用函数模板。

3.函数模板也可以发生重载。

4.如果函数模板可以产生更好的匹配,优先调用函数模板。

1.优先调用普通函数

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

void print( int a) {
	cout << "调用普通函数" << endl;
}

template<typename T>
void print(T a) {
	cout << "调用函数模板" << endl;
}

void test0326e() {
	int a = 1;
	print(a);
}

int main() {
	test0326e();

	system("pause");
	return 0;
}

通过结果可以看出,优先调用了普通函数。

image-20240327000647436

2.可以通过空模板参数列表强制调用函数模板。

在前面加上空的英文书名号就可以。

更改test0326e()函数中代码为:

void test0326e() {
	int a = 1;
	print<>(a);
}

即可优先调用函数模板

image-20240327000801084

3.函数模板也可以发生重载。

参数个数不同,参数类型不同。

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


template<typename T>
void print(T a) {
	cout << "调用函数模板" << endl;
}

template<typename T>
void print(T a, T b) {
	cout << "调用重载的函数模板" << endl;
}

void test0326e() {
	int a = 1;
	int b = 2;
	print<>(a ,b);
}

int main() {
	test0326e();

	system("pause");
	return 0;
}

可以看到,调用了重载的函数模板。

image-20240327000930124

4.如果函数模板可以产生更好的匹配,优先调用函数模板。

怎么理解?比如下面代码,传入的是字符,在普通函数中,可以隐式转换数据类型到int,但是函数模板可以不用隐式转换直接到char。所以编译器会执行进函数重载中。

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

void print( int a) {
	cout << "调用普通函数" << endl;
}

template<typename T>
void print(T a) {
	cout << "调用函数模板" << endl;
}


void test0326e() {
	int a = 1;
	int b = 2;
	char c = 'c';
	print<>(c);
}

int main() {
	test0326e();

	system("pause");
	return 0;
}

运行结果:

image-20240327001147736

函数模板限制

普通的数据类型,当然可以运算。

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

template<typename T>
bool MyCompare(T a, T b) {
	if (a == b) {
		return true;
	}
	else {
		return false;
	}
}

void test0326b() {
	int a = 10;
	int b = 10;
	bool res = MyCompare(a, b);
	if (res) {
		cout << "a == b" << endl;
	}
	else {
		cout << "a != b" << endl;
	}
}

int main() {
	test0326b();


	system("pause");
	return 0;
}

运行结果:

image-20240326235833087

如果是自定义的数据类型,也会出现无法运算的时候。

如果是自定义数据类型,就如果想要走特殊的通道到达对应的函数模板,则需要特殊操作。

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

class Person {
public:
	Person(int age, string name) {
		this->p_age = age;
		this->p_name = name;
	}

public:
	int p_age;
	string p_name;
};


template<typename T>
bool MyCompare(T a, T b) {
	if (a == b) {
		return true;
	}
	else {
		return false;
	}
}

template<> bool MyCompare(Person p1, Person p2) {
	if (p1.p_age == p2.p_age && p1.p_name == p2.p_name) {
		return true;
	}
	else {
		return false;
	}
}

void test0326b() {
	int a = 10;
	int b = 10;
	bool res = MyCompare(a, b);
	if (res) {
		cout << "a == b" << endl;
	}
	else {
		cout << "a != b" << endl;
	}
}

void test0326d() {
	Person tom(12, "Cooper");
	Person Jack(12, "Cooper");
	bool res = MyCompare(tom, Jack);

	if (res) {
		cout << "a == b" << endl;
	}
	else {
		cout << "a != b" << endl;
	}
}

int main() {
	test0326d();
	//test0326b();


	system("pause");
	return 0;
}

运行结果:

image-20240327000237165

类模板

类模板语法

目的/作用:

建立一个通用类,类中的成员数据类型可以不具体指定,用一个虚拟的类型来代表。

我理解:望名思义,以为是通用的类,类名也是通用的。但其实是类里面的属性或者方法,可以通用。

语法:

template <typename T1 ,typename T2 , typename ,Tn>
class{}

template 声明创建模板

typename 表明后面的符号是一种数据类型,具体是什么数据类型可以自己指定。

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

案例代码:

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

template<typename name_type , typename age_type>
class Person {
public:
	Person(name_type name, age_type age) :p_name(name), p_age(age) {}

public:
	name_type p_name;
	age_type p_age;

public:
	void print() {
		cout << "name = " << p_name << " , age = " << p_age << " . " << endl;
	}
};

void test0327a() {
	Person <string, int>p("Json", 23);
	p.print();
}

int main() {
	//test0326e();
	test0327a();

	system("pause");
	return 0;
}

运行结果:

image-20240327194736825

类模板与函数模板区别

区别①类模板不能自动推导类型。

函数模板可以通过传入参数,由编译器判断谁是什么数据类型,而类模板没有这个功能。

区别②类模板可以对类型赋默认值。

如果类中成员属性≥1,可以对成员属性的类型赋默认值。

以下是两点具体代码体现。

void test0327b() {
	Person p("docker", 99);
	p.print();
}

区别①会报错:

image-20240327200148738

区别②:

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

template<typename name_type , typename age_type = int>//体现区别2,可以对类型赋默认值。
class Person {
public:
	Person(name_type name, age_type age) :p_name(name), p_age(age) {}

public:
	name_type p_name;
	age_type p_age;

public:
	void print() {
		cout << "name = " << p_name << " , age = " << p_age << " . " << endl;
	}
};
}

void test0327b() {
	//Person p("docker", 99);
	//p.print();
}

void test0327c() {
	Person <string>p("Coco", 33);//体现2,可以对类型赋默认值,使得string数据类型是参数1,默认数据类型是参数2
	p.print();
}

int main() {
	test0327c();

	system("pause");
	return 0;
}

运行结果:

image-20240327200302791

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

类模板成员函数和普通类中成员函数创建时机有区别。

  • 普通类成员函数一开始创建。
  • 类模板成员函数调用时创建。

问题:什么叫调用时才创建?

因为具体的类有明确的数据类型,而类模板没有指定的数据类型,所以一开始时不会创建,在实际调用时才会创建。

代码示例:

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

class Per {
public:
	void print() {
		cout << "Per类调用print函数" << endl;
	}
};

class Son {
public:
	void show() {
		cout << "Son类调用show函数" << endl;
	}
};

template<class T>
class Person {
public:
	T obj;//这一点在自己编写时忘记了
public:
	void p_print(){
		obj.print();
	}
	void p_show() {
		obj.show();
	}
};

void test0327d() {
	Person <Per> per;
	per.p_print();
	//per.p_show();
	Person <Son> son;
	//son.p_print();
	son.p_show();
}
int main() {
	test0327d();

	system("pause");
	return 0;
}

如果不注释per.p_print();son.p_print();,就会报错:image-20240327202523442

注释掉之后,能有运行结果:

image-20240327202555263

类模板对象做函数参数

掌握:类模板实例化出的对象,向函数传参的方式

什么意思?就是将对象作为参数传入函数中。但此时的对象是类模板对象,所以又有模板数据类型不确定的问题在里面。就会有对应三种类模板对象做函数参数的方法。

三种传入方式:

①指定传入类型——直接显示对象的数据类型。

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

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

第一种方法,指定传入类型,就是在将类模板对象作为参数传入普通函数时,直截了当的在函数定义时就说明类模板对象的模板数据类型是什么。

第二种,参数模板化——将对象中的参数作为模板传递。

此时函数不再是普通函数,而是模板函数。类模板对象中的模板数据类型需要与模板函数中的模板数据类型保持一致。

第三种,整个类模板化——将这个对象类型作为模板进行传递。

示例代码:

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

template<class T1 , class T2>
class Person {
public:
	Person(T1 name, T2 age):p_name(name),p_age(age) {}

public:
	T1 p_name;
	T2 p_age;

public:
	void print() {
		cout << "name = " << p_name << " , age = " << p_age << " . " << endl;
	}
};

//方式1:指定类模板对象模板数据类型,把类模板对象作为参数传入函数中。
void CommonPrintPerson(Person<string , int> &p) {
	//现在这是个普通函数
	p.print();
}

//方式2:参数模板化,使用模板函数,把类模板对象中的成员属性类型作为模板数据类型传入。
template<typename T1 , typename T2>
void TemplatePrintPerson(Person <T1, T2>& p) {
	p.print();
}

//方式3:对象模板化,将类模板对象也作为模板数据类型传入函数中
template <class T3>
void TemplateClassPrintPerson(T3 p) {
	p.print();
}


void test0327e() {
	Person<string, int>p("Jack", 22);
	Person<string, int>e("Eoa", 32);
	Person<string, int>r("Ross", 32);
	//普通函数调用时,指定模板数据类型将类模板对象作为参数传入函数中。
	CommonPrintPerson(p);
	TemplatePrintPerson<string, int>(e);
	TemplateClassPrintPerson(r);
}

int main() {

	test0327e();

	system("pause");
	return 0;
}


运行结果:

image-20240327210559275

打印类别

补充的一点是可以通过typeid(要查看的数据类型).name()获得是什么类别。

比如

类模板与继承

当类模板碰到继承时,需要注意以下几点:

如果子类继承的父类是一个类模板时,子类在声明时,需要指定父类中T的类型。如果不指定,编译器无法给子类分配内存。

如果想灵活指定父类中T的类型,子类也需要变为类模板。

为什么不指定父类T中的类型,编译器无法给子类分配内存呢?因为不确定父类有什么数据类型。

所以,对于第一点显式指定父类数据类型,指定的模板数据类型需要写在父类的右边。

image-20240327213010633

如果想要灵活指定父类中的模板数据类型,子类也需要变为类模板。

代码示例如下:

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

template <class T>
class Base {
public:
	T member;
};

//显式指定父类模板数据类型
class Under :public Base<int> {
public:
	Under() {
		cout << "父类继承子类,显式指定类模板数据类型" << endl;
		cout << "类模板数据类型为:" << typeid(member).name() << "."<<endl;
	}

};

//灵活指定父类模板,需要子类也是类模板
template <class T1 , class T2>
class Bottom :public Base<T2> {
public:
	Bottom() {
		cout << "灵活指定父类模板,需要子类也是类模板" << endl;
		//cout << "父类类模板数据类型为:" << typeid(member).name() << "." << endl;
		//需要注释掉才能运行,说明子类先构造,父类才构造。而显式指定的已经指定了。
		cout << "子类类模板数据类型为:" << typeid(B_member).name() << "." << endl;
	}
public:
	T2 B_member;
};

void test0327f() {
	Under u;
	Bottom<int, char>bo;
}

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

运行结果:

image-20240327213707604

类模板成员函数类外实现

学习目标:掌握类模板外的成员函数类外实现

成员函数类外实现,需要加上类模板的说明,同时加上类模板参数。

代码说明:

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

template<class T1 , class T2 >
class Person {
public:
	Person(T1 name , T2 age);
public:
	T1 p_name;
	T2 p_age;
public:
	void show();
};

template<class T1 , class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
	this->p_name = name;
	this->p_age = age;
}

template<class T1 , class T2>
void Person<T1 , T2>::show() {
	cout << "name = " << this->p_name << " , age = " << this->p_age << " . " << endl;
}

void test0327g() {
	Person <string, int>p("Ross", 33);
	p.show();
}

int main() {
	test0327g();


	system("pause");
	return 0;
}

运行结果:

image-20240327221140056

类模板分文件编写

问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到。

解决:

①直接包含cpp源文件。

②将声明和实现写入同一个文件中,并改后缀名为.hpp,约定名称,不强制。

类模板中的成员函数创建时机是在调用阶段,调用才会创建,一开始不创建。所以要么包含源码—cpp文件,要么写入同一个文件中,后缀名是’hpp’。

防止头文件重复包含

类模板与友元

学习目标:掌握类模板配合友元函数的类内和类外实现

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

全局函数类外实现👉需要提前让编译器知道全局函数的存在。

我的理解:加了友元标识符之后,类内的函数就变成了全局函数。所以可以在类外直接调用。

弹幕:printPerson函数不加friend时是一个私有的成员函数,但加了friend之后就变成了一个全局函数,因为自身的成员函数默认就可以调用自身的成员属性不需要友元,反之需要友元的都不是成员函数。

全局函数类内实现就一步到位。

代码示例:

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

template<class T1 , class T2>
class Person {
	//类内全局函数
	friend void printPerson(Person<T1,T2> p) {
		cout << "全局函数类内实现" << endl;
		cout << "name = " << p.p_name << " , age = " << p.p_age << " . " << endl;
	}
public:
	Person(T1 name, T2 age) :p_name(name), p_age(age) {}
private:
	T1 p_name;
	T2 p_age;
};

void test0327h() {
	Person <string, int> p("张三", 34);
	printPerson(p);
}

int main() {
	test0327h();


	system("pause");
	return 0;
}

运行结果:

image-20240327231909161

全局函数类外实现,由于模板函数的特殊,若将类外模板函数的实现写于声明后,编译器不认识。并且,类内的函数声明是普通的函数声明,需要将其转换成模板函数的声明,

image-20240327232647811

如图,是全局函数类外实现写在了声明后,编译器不认识。

如果写在声明前,由于传入了Person类,编译器也不认识Person类,还需要把Person类的声明也写在前面。

image-20240327232355135

接下来,还是不行,因为这样相当于全局函数是普通函数,需要加上空模板参数列表让编译器认为它是函数模板。

最后,终于成功生成。

image-20240327232714203

完整代码:

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

template<class T1, class T2>
class Person;

template<class T1, class T2>
void showPerson(Person<T1, T2> p) {
	cout << "全局函数类外实现" << endl;
	cout << "name = " << p.p_name << " , age = " << p.p_age << " . " << endl;
}

template<class T1 , class T2>
class Person {
	//类内全局函数
	friend void printPerson(Person<T1,T2> p) {
		cout << "全局函数类内实现" << endl;
		cout << "name = " << p.p_name << " , age = " << p.p_age << " . " << endl;
	}

	friend void showPerson<>(Person <T1, T2> p);
public:
	Person(T1 name, T2 age) :p_name(name), p_age(age) {}
private:
	T1 p_name;
	T2 p_age;
};


void test0327h() {
	Person <string, int> p("张三", 34);
	//printPerson(p);
	showPerson(p);
}

int main() {
	test0327h();


	system("pause");
	return 0;
}

运行结果:

image-20240327232732006

看弹幕:友元不是类的成员,也不接受它所在区域访问控制级别的约束。

所以,一般都是全局函数类内实现。当然,也可以直接在类里面写上函数模板。

template<class T1 , class T2>
friend void showPerson(Person <T1, T2> p);

一样可以运行。

类模板案例-通用数组实现

制作一个通用的数组类,可以满足如下要求:

  1. 对内置数据类型以及自定义数据类型的数据进行存储。
  2. 将数组中的数据存储到堆区。
  3. 构造函数中可以传入数组的容量。
  4. 提供对应的拷贝构造函数以及operator=防止浅拷贝问题。
  5. 提供尾插法和尾删法对数组中的数据进行增加和删除。
  6. 可以通过下标的方式访问数组中的元素。
  7. 可以获取当前数组中的元素个数和数组的容量。

自己的分析:

①,可以用类模板存储元素。

②,在每个数据存储时,也就是构造函数,需要new一个数据到堆区。同时析构函数需要释放内存。

③,怎么理解?——传入数组容量,才能确定开辟多少内存空间。

④,这个拷贝构造函数,说的是类的拷贝。重写=,就是在类中重写=号运算符。

⑤,尾插法,尾删法。提供两种方法,参数是什么?对于插入,参数就是新的数据元素;对于删除,可以没有参数。内存空间的扩充怎么做?参考职工管理系统。内存空间的删除,就是直接将最后一个元素内存地址释放,并将数组个数-1。

⑥传入对应的数字,获取其东西。

CommonArray.hpp

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

当前元素个数和数组容量。
*/
template <class T>
class CommonArray;




template<class T>
class CommonArray {
public:
	CommonArray(int num);
	CommonArray(CommonArray& ca);
	~CommonArray();
public:
	
	//这个代表什么意思?为什么要是创建一个T类型的东西?这个东西再指向一个数组?
	T* array;
private:
	int ca_num;
	int ca_size;
public:
	void tail_insert(const T &element);
	void tail_delete();
	int get_array_size();
	int get_element_number();
	int get_common_array_cost();
	CommonArray& operator=(const CommonArray& ca);
	T& operator[](int index);
};

template<class T>
CommonArray<T>::CommonArray(int size) {

	//cout << "有参构造函数调用" << endl;
	//容量:数组内一共可以放ca_num个元素。
	this->ca_size = size;
	//新建一个T类型的数组指针,并把地址设为null
	//this->array = NULL;
	//开辟这么大一个空间的数组。
	this->array = new T[this->ca_size];
	this->ca_num = 0;
};

template<class T>
CommonArray<T>::CommonArray(CommonArray& ca) {

	//cout << "拷贝构造函数调用" << endl;
	this->ca_size = ca.ca_size;
	this->ca_num = ca.ca_num;
	this->array = new T[ca.ca_size];
	for (int i = 0; i < ca.ca_size; i++) {
		this->array[i] = ca.array[i];
	}
}

//拷贝构造函数和重写=运算符防止编译器提供的两个函数造成浅拷贝现象。
//如果浅拷贝,删除时会导致堆区数据重复释放。

template<class T>//返回值左值存在,应该要返回引用。
CommonArray<T>& CommonArray<T>::operator=(const CommonArray<T>& ca) {

	//cout << "operator=函数调用" << endl;
	this->ca_size = ca.ca_size;
	this->ca_num = ca.ca_num;
	this->array = new T[ca.ca_size];
	for (int i = 0; i < ca.ca_size; i++) {
		this->array[i] = ca.array[i];
	}
	return *this;
}

template<class T>
CommonArray<T>::~CommonArray() {
	if (this->array) {

		delete[] this->array;
		this->array = 0;
		this->ca_size = 0;
		this->ca_num = 0;
	}
}

//为了防止T被修改,一般用引用的方式传入,使用const
template<class T>
void CommonArray<T>::tail_insert(const T &element) {
	if (this->ca_num != this->ca_size) {
		this->array[this->ca_num] = element;
		this->ca_num += 1;
	}
	else {
		cout << "数组已满,无法新增。" << endl;
		system("pause");
		return;
	}
}

template<class T>
void CommonArray<T>::tail_delete() {
	this->ca_num--;
}

template<class T>
int CommonArray<T>::get_array_size() {
	return this->ca_size;
}


template<class T>
int CommonArray<T>::get_element_number() {
	return this->ca_num;
}

template<class T>
int CommonArray<T>::get_common_array_cost() {
	int cost;
	cost = sizeof(T) * this->ca_size;
	return cost;
}

template<class T>
//返回值是T是因为要作为返回值,又因为想让它作为左值存在,比如int i = 0 ,就需要让他放回一个引用。。
T& CommonArray<T>::operator[](int index) {
	return this->array[index];
}

测试文件.cpp

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

#include"CommonArray.hpp"

class Person {
public:
	Person() {}
	Person(string name, int age) :p_name(name),p_age(age) {}
public:
	string p_name;
	int p_age;
};

void Print_Array(CommonArray<int>& common_array) {
	for (int i =0 ; i< common_array.get_element_number(); i++){
		cout << common_array.array[i] << " ";
	}
}

void Print_Person_Array(CommonArray<Person>& common_array_persons) {
	for (int i = 0; i < common_array_persons.get_element_number(); i++) {
		cout << "数组成员" << i + 1 << ": 姓名:" << common_array_persons.array[i].p_name<<"\t,";
		cout << "\t年龄:" << common_array_persons.array[i].p_age << " \t. " << endl;
	}

}

void test0328() {
	CommonArray<int> ca(5);
	for (int i = 0; i < ca.get_array_size(); i++) {
		ca.tail_insert(i);
	}
	cout << "ca原先为:" ;
	Print_Array(ca);

	ca.tail_delete();
	cout << "现在的ca容量大小为:" << ca.get_array_size() << endl;
	cout << "现在的ca 元素个数为:" << ca.get_element_number() << endl;
	cout << "ca现在为:";
	Print_Array(ca);
	cout << endl;
	CommonArray<Person> persons(5);
	Person p1("孙悟空", 999);
	Person p2("韩信", 19);
	Person p3("爪云", 39);
	Person p4("张飞", 29);
	Person p5("安其拉", 18);

	persons.tail_insert(p1);
	persons.tail_insert(p2);
	persons.tail_insert(p3);
	persons.tail_insert(p4);
	persons.tail_insert(p5);
	
	Print_Person_Array(persons);

}

int main() {
	test0328();


	system("pause");
	return 0;
}

image-20240402203442273


以上是我的学习笔记,希望对你有所帮助!
如有不当之处欢迎指出!谢谢!

学吧,学无止境,太深了

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

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

相关文章

Linux网络协议栈从应用层到内核层④

文章目录 1、网卡接受数据2、网络设备层接收数据3、ip层接受数据4、tcp层接受数据5、上层应用读取数据6、数据从网卡到应用层的整体流程 1、网卡接受数据 当网卡收到数据时&#xff0c;会触发一个中断&#xff0c;然后就会调用对应的中断处理函数&#xff0c;再做进一步处理。…

WSL安装与使用

开启之后&#xff0c;会提示你重启电脑才能使配置生效&#xff0c;我们重启即可。 电脑重启后&#xff0c;打开Microsoft Store搜索WSL&#xff0c;既可以看到支持的操作系统&#xff0c;我们选择Ubuntu即可&#xff0c;我们选择第一个就可以。 随后我们打开&#xff0c;发现报…

【WebKit架构讲解】

&#x1f308;个人主页:程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

开源知识管理和协作平台:插件丰富,主题精美 | 开源日报 No.209

logseq/logseq Stars: 27.8k License: AGPL-3.0 logseq 是一个注重隐私的开源平台&#xff0c;用于知识管理和协作。 提供强大的知识管理、协作、PDF 标注和任务管理工具支持多种文件格式&#xff0c;包括 Markdown 和 Org-modeWhiteboard 功能可使用空间画布组织想法&#x…

vue源码解析——vue如何将template转换为render函数

Vue 将模板&#xff08;template&#xff09;转换为渲染函数&#xff08;render function&#xff09;是 Vue 编译器的核心功能&#xff0c;它是 Vue 实现响应式和虚拟 DOM 的关键步骤。在 Vue 中&#xff0c;模板&#xff08;template&#xff09;是开发者编写的类似 HTML 的代…

SAP FI学习笔记03 - 应付账款

上一章讲了MM与FICO的集成。 SAP FI学习笔记02 - 基础知识 - MM与FICO集成-CSDN博客 本章讲应付账款。 上一章也讲了应付账款&#xff08;買掛金&#xff09;&#xff0c;它的来源可以是 - 购买发注&#xff0c;入库&#xff0c;请求书照合 之后系统自动生成 &#xff08;具…

南京大学提出用于大模型生成的动态温度采样法,简单有效!

在自然语言处理&#xff08;NLP&#xff09;的领域&#xff0c;大语言模型&#xff08;LLMs&#xff09;已经在各种下游语言任务中展现出了卓越的性能。这些任务包括但不限于问答、摘要、机器翻译等。LLMs的强大能力在于其生成的文本质量和多样性。为了控制生成过程&#xff0c…

任意文件下载漏洞

1.文件下载漏洞存在的位置 文件经过php处理可能存在文件下载漏洞&#xff0c;配合目录遍历漏洞使用 2.目录遍历漏洞检验方法 测试是否存在目录遍历漏洞&#xff1a;在网站网址中间添加随意写一个文件名../&#xff08;返回上一级&#xff09;进行测试&#xff0c;没有报错就…

UART通信

UART——通用异步收发传输器&#xff0c;UART 作为异步串口通信协议的一种&#xff0c;工作原理是将传输数据的每个字符一位接一位地传输。在应用程序开发过程中使用频率较高的数据总线。 基于UART的数据传输是异步形式的串行数据传输。基于UART的串行数据传输不需要使用时钟信…

基于springboot+vue实现的酒店客房管理系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;spring…

[Python学习篇] Python创建项目

新建项目 打开开发工具 PyCharm 选择 New Project 目录结构如下 运行 hello world 选中项目&#xff0c;右键 New -> Python File 进行创建文件 运行项目

STM32学习和实践笔记(4): 分析和理解GPIO_InitTypeDef GPIO_InitStructure (b)

继续上篇博文&#xff1a;STM32学习和实践笔记&#xff08;4&#xff09;: 分析和理解GPIO_InitTypeDef GPIO_InitStructure (a)-CSDN博客 往下写&#xff0c; 为什么&#xff1a;当GPIO_InitStructure.GPIO_PinGPIO_Pin_0 ; 时&#xff0c;其实就是将对应的该引脚的寄存器地…

Python搭建编程环境—安装Python3解释器

✅作者简介&#xff1a;CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1&#x1f3c6; &#x1f4c3;个人主页&#xff1a;hacker707的csdn博客 &#x1f525;系列专栏&#xff1a;零基础学Python &#x1f4ac;个人格言&#xff1a;不断的翻越一…

HarmonyOS 应用开发之启动指定页面

当PageAbility的启动模式设置为单例时&#xff08;具体设置方法和典型场景示例见 PageAbility的启动模式 &#xff0c;缺省情况下是单实例模式&#xff09;&#xff0c;若PageAbility已被拉起&#xff0c;再次启动PageAbility会触发onNewWant回调&#xff08;即非首次拉起&…

JavaScript基础代码练习之冒泡排序

一、要求对一个数组进行冒泡排序&#xff0c;并将排序后的结果输出到控制台。在代码中&#xff0c;数组 arr 包含了一组数字&#xff0c;然后使用嵌套的循环来进行冒泡排序。 二、编写代码 <!DOCTYPE html> <html lang"en"><head><meta chars…

doccano标注工具|为机器学习建模做数据标注

目录 一、标记流程 二、配置环境 2.1 安装 2.2 运行doccano 三、案例 3.1 创建项目 3.2 上传数据 3.3 定义标签 3.4 添加成员 3.5 开始标注 3.6 导出数据 3.7 导出数据 doccano doccano是开源的数据…

【C++进阶】AVL树(来自二叉搜索树的复仇)

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;c大冒险 总有光环在陨落&#xff0c;总有新星在闪烁 引言&#xff1a; 之前我们学…

每日面经分享(Spring Boot: part3 Service层)

SpringBoot Service层的作用 a. 封装业务逻辑&#xff1a;Service层负责封装应用程序的业务逻辑。Service层是控制器&#xff08;Controller&#xff09;和数据访问对象&#xff08;DAO&#xff09;之间的中间层&#xff0c;负责处理业务规则和业务流程。通过将业务逻辑封装在S…

什么是智慧驿站?智慧驿站主要应用有哪些?新型智慧公厕解说

智慧驿站是一种融合了创意设计和多项功能的新型智慧公厕&#xff0c;它在信息化公共厕所的基础上&#xff0c;以创意的外观设计、全金属结构用材、快速制作整体运输、快速部署落地使用等价值特点&#xff0c;所打造了一个集购物、互动、休憩等多种功能于一体的城市基础设施。无…

【智能算法】金枪鱼群优化算法(TSO)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.代码展示4.参考文献 1.背景 2021年&#xff0c;Xie等人受到自然界中金枪鱼狩猎行为启发&#xff0c;提出了金枪鱼优化算法&#xff08;Tuna swarm optimization&#xff0c;TSO&#xff09;。 2.算法原理 2.1算法思想 TSO模…