c++补充

构造函数、析构函数

#include <iostream>
using namespace std;

// 构造函数、析构函数
//	---	"构造函数"类比生活中的"出厂设置"	---
//	---	"析构函数"类比生活中的"销毁设置"	---
// 如果我们不写这两种函数,编译器会提供默认的构造函数和析构函数,但是它们是空实现
class Person
{
private:
	int age;

public:
	// 构造函数(无参)
	Person()
	{
		cout << "构造函数的调用,无参" << endl;
	}

	// 构造函数(含参)
	Person(int a)
	{
		age = a;
		cout << "构造函数的调用, 含参" << endl;
	}

	// 构造函数(拷贝)
	Person(const Person& p)
	{
		this->age = p.age;
		cout << "构造函数的调用,拷贝" << endl;
	}

	// 析构函数
	~Person()
	{
		cout << "析构函数的调用" << endl;
	}

	// 查看年龄
	int getAge() { return age; }
};

void test()
{
	// 括号法
	//Person p1;		// 默认构造函数调用
	//Person p2(10);	// 括号法调用构造函数(含参)
	//Person p3(p2);	// 括号法调用构造函数(拷贝)
	//cout << p3.getMoney() << endl;	// 结果:10

	// 显示法
	Person p1;
	Person p2 = Person(10);		// 含参构造
	Person p3 = Person(p2);		// 拷贝构造
	// 注意事项 1:
	// Person(10);  // 匿名对象 -- 特点: 当前执行结束后,系统会立即回收掉匿名对象
	// 注意事项 2:
	// 不要利用拷贝构造函数去初始化匿名对象,即:Person(p3) 是不对的,
	// 编译器会认为 Person (p3) === Person p3;


	// 隐式转换法
	Person p4 = 10;		// 相当于写了 Person p4 = Person(10);   含参构造
	Person p5 = p4;		// 相当于写了 Person p5 = Person(p4);   拷贝构造
}


int main()
{
	test();
}

初始化列表

#include <iostream>
using namespace std;

// 初始化的方式
//	1. 传统的初始化
//	2. 初始化列表

class Person
{
public:
	int age;
	int height;

	/*
	// 1. 传统初始化操作
	Person(int a, int b)
	{
		age = a;
		height = b;
	}
	*/

	// 2. 初始化列表
	Person(int a, int b) :age(a), height(b)
	{
		;	// 用";"表示空语句,仅起占位的作用
	}
};

void test()
{	

	Person man(28, 180);
	cout << man.age << endl << man.height << endl;
}

int main()
{
	test();
}

常函数、常对象

#include <iostream>
using namespace std;

class Person
{
public:
	// 常函数:
	// this指针的本质是"指针常量",指针的指向是不可以修改的
	// const Person * const this;
	// 在成员函数后面加 const,修饰的是this指向,让指针指向的值也不可以修改
	void showPerson() const		// 函数的后面加了一个const,我们称为"常函数"
	{
		age = 30;  // 这段代码本质是 this->age = 30; 它是会报错的,因为它不可以修改
		height = 180;	// 如果非得要修改,请在定义的时候,在前面加上关键字 mutable
	}

	// 普通成员函数
	void change()
	{
		age = 40;
		height = 166;
	}

	int age;
	mutable int height;
};

void test1()
{
	Person p;
	p.showPerson();
}

void test2()
{
	// 常对象:
	// 在对象前加上 const,它将变为常对象
	const Person p;
	p.age = 22;		// 这里会报错,因为它不允许被修改
	p.height = 175;		// 这里不会报错,因为 mutable 修饰的 height 是一个特殊的变量

	// 常对象只能调用常函数
	p.showPerson();
	p.change();		// 常对象不可以调用普通成员函数,因为普通成员函数可以修改属性
}

int main()
{
	test1();
	test2();
}

运算符重载

+ 运算符 重载

#include <iostream>
using namespace std;

class Person
{
public:
	int age;
	int height;

	// 通过成员函数进行重载+号
	// Person p3 = p1 + p2; == Person p3 = p1.operator+(p2);
	Person operator+(const Person &p)
	{
		Person temp;
		temp.age = this->age + p.age;
		temp.height = this->height + p.height;
		return temp;
	}
};

// 通过全局函数重载+号
// Person p3 = p1 + p2; == Person p3 = operator+(p1, p2);
Person operator+(const Person& p1, const Person& p2)
{
	Person temp;
	temp.age = p1.age + p2.age;
	temp.height = p1.height + p2.height;
	return temp;
}

void test()
{
	Person p1;
	p1.age = 22;
	p1.height = 166;

	Person p2;
	p2.age = 28;
	p2.height = 134;

	Person p3 = p1 + p2;	// 加号运算符重载,让编译器知道如何进行运算
	cout << p3.age << endl << p3.height << endl;	// 输出结果:50、300

	Person p3 = p3 + 66;	// 编译器不知道如何运行,将会报错!
	// 运算符重载,也可以使用"函数重载":
	// 我们可以通过"全局函数重载"实现一个 Person operator+(const Person& p1, int num) {...}
	// 或者可以通过"成员函数重载"实现一个 Person operator+(int num) {...}

	// 注意事项:
	// 1. 对于内置的数据类型的表达式的运算符是不可能改变的
	// 2. 不用滥用运算符重载
}

int main()
{
	test();
}

<< 运算符 重载

#include <iostream>
using namespace std;

class Person
{
	friend ostream& operator<<(ostream&, const Person&);

public:
	Person(int age, int height) :age(age), height(height)
	{
		cout << "调用了构造函数" << endl;
	}

private:
	int age;
	int height;
};

// 无法通过成员函数进行重载<<号
// 只能通过全局函数重载<<号
ostream& operator<<(ostream& cout, const Person& p)
{
	cout << "[ age = " << p.age << ", height = " << p.height << " ]";
	return cout;
}

void test()
{
	Person p1(22, 166);

	cout << p1 << endl;	// 输出结果:[ age = 22, height = 166 ]
}

int main()
{
	test();
}

自增运算符 重载

#include <iostream>
using namespace std;

class MyInteger
{
	friend ostream& operator<<(ostream&, MyInteger);

public:
	MyInteger(int a) :num(a)
	{
		cout << "调用了构造函数" << endl;
	}

	// 前置递增
	MyInteger& operator++()
	{
		num++;
		return *this;
	}

	// 后置递增
	MyInteger operator++(int)	// int 代表占位参数,可以用于区分前置和后置递增
	{
		// 先记录当前结果
		MyInteger temp = *this;
		// 后将自己进行递增
		num++;
		return temp;
	}

private:
	int num;
};

ostream& operator<<(ostream& cout, MyInteger obj)
{
	cout << obj.num;
	return cout;
}

void test()
{
	MyInteger myint = 0;
	cout << ++(++myint) << endl;	// 输出:2
	cout << myint << endl;			// 输出:2

	cout << myint++ << endl;		// 输出:2
	cout << myint<< endl;			// 输出:3
}

int main()
{
	test();
}

赋值运算符 重载

#include <iostream>
using namespace std;

class Person
{
public:
	Person(int age)
	{
		this->age = new int(age);
	}

	~Person()
	{
		if (age != NULL)
		{
			delete age;
			age = NULL;
		}
	}

	// 赋值运算符重载
	Person& operator=(const Person& obj)
	{
		*age = *obj.age;
		return *this;
	}

	int* age;
};

void test()
{
	Person p1(10);
	cout << "p1的年龄为:" << *p1.age << endl;	// 输出结果:10

	Person p2(20);
	cout << "p2的年龄为:" << *p2.age << endl;	// 输出结果:20

	Person p3(30);
	cout << "p3的年龄为:" << *p3.age << endl;	// 输出结果:30

	// 这不是拷贝构造函数,拷贝构造函数也是一种构造函数
	// 这里是赋值语句,对象的赋值,编译器默认的行为是:将某对象的所有属性复制一份到另一个对象里面
	// 因为默认行为的直接复制,对于需要浅拷贝的内容没什么影响,但是对于需要深拷贝的内容影响很大
	// 为了避免恶劣影响,我们需要重载赋值运算符
	p3 = p2 = p1;
	cout << "修改后的p2的年龄为:" << *p2.age << endl;		// 输出结果:10
	cout << "修改后的p3的年龄为:" << *p3.age << endl;		// 输出结果:10
}

int main()
{
	test();
}

关系运算符 重载

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

class Person
{
public:
	string name;
	int age;

	Person(string name, int age) :name(name), age(age)
	{
		;	// 空语句
	}

	// == 关系运算符重载
	bool operator==(const Person& obj)
	{
		if (name == obj.name && age == obj.age)
		{
			return true;
		}
		return false;
	}

	// != 关系运算符重载
	bool operator!=(const Person& obj)
	{
		if (name == obj.name && age == obj.age)
		{
			return false;
		}
		return true;
	}

};

void test()
{
	Person p1("Jack", 18);
	Person p2("Jack", 18);
	Person p3("Tom", 18);

	if (p1 == p2)	cout << "p1 和 p2 相等" << endl;
	if (p1 != p3)	cout << "p1 和 p3 不相等" << endl;
}

int main()
{
	test();
}

函数调用运算符 重载

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

class MyPrint
{
public:
	// 重载函数调用运算符
	void operator()(string text, string end="\n")
	{
		cout << text << end;
	}
};

class MyAdd
{
public:
	// 重载函数调用运算符
	int operator()(int a, int b)
	{
		return a + b;
	}
};

void test()
{
	MyPrint print;
	MyAdd add;

	print("hello world");	// 由于使用起来非常类似于函数调用,因此称为仿函数
	string res = to_string(add(10, 20));
	print(res);

	// 匿名函数对象 -> MyAdd()
	cout << MyAdd()(100, 100) << endl;
}

int main()
{
	test();
}

继承知识补充

在这里插入图片描述

多态

基础应用

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

class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在叫" << endl;
	}
};

class Cat :public Animal
{
public:
	void speak()
	{
		cout << "猫在叫" << endl;
	}
};

class Dog :public Animal
{
public:
	void speak()
	{
		cout << "狗在叫" << endl;
	}
};

// 地址早绑定,在编译阶段确定函数地址
// 如果想要传入的参数cat能够调用speak(),那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定
void doSpeak(Animal& animal)	// Animal &animal = cat;
{
	animal.speak();
}

void test()
{
	Cat cat;
	Dog dog;

	doSpeak(cat);
	doSpeak(dog);
}

int main()
{
	test();
}

进阶应用

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

class abstractDrinking
{
public:
	// 煮水
	virtual void Boil() = 0;

	// 冲泡
	virtual void Brew() = 0;

	// 倒入杯中
	virtual void PourInCup() = 0;

	// 加入辅料
	virtual void PutSomething() = 0;

	// 制作饮品
	void make()
	{
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};

// 制作咖啡
class Coffee :public abstractDrinking	// 继承抽象类
{
	// 必须重写抽象类的纯虚函数,否则自己也会变成抽象类
public:
	// 煮水
	virtual void Boil()
	{
		cout << "煮熟自来水" << endl;
	}

	// 冲泡
	virtual void Brew()
	{
		cout << "冲泡咖啡" << endl;
	}

	// 倒入杯中
	virtual void PourInCup()
	{
		cout << "倒入迷你的咖啡杯中" << endl;
	}

	// 加入辅料
	virtual void PutSomething()
	{
		cout << "加入一些糖" << endl;
	}
};

// 制作茶水
class Tea :public abstractDrinking	// 继承抽象类
{
	// 必须重写抽象类的纯虚函数,否则自己也会变成抽象类
public:
	// 煮水
	virtual void Boil()
	{
		cout << "煮熟矿泉水" << endl;
	}

	// 冲泡
	virtual void Brew()
	{
		cout << "冲泡茶叶" << endl;
	}

	// 倒入杯中
	virtual void PourInCup()
	{
		cout << "倒入经典的茶杯中" << endl;
	}

	// 加入辅料
	virtual void PutSomething()
	{
		cout << "加入一些香料" << endl;
	}
};

// 制作饮料
void makeDrink(abstractDrinking* drink)
{
	drink->make();
	delete drink;			// 删除对象,释放内存
}

void test()
{
	// 制作咖啡
	makeDrink(new Coffee);	// 开辟内存,创建对象

	// 制作茶
	makeDrink(new Tea);
}

int main()
{
	test();
}

高级应用 (经典)

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


// ------------------
// 1. 抽象出每个零件(CPU、VideoCard、Memory)
// 2. 具体的厂商零件(Intel、Lenovo)
// 3. 电脑类->提供让电脑工作的函数(Computer)
// 4. 组装三台不同的电脑
// ------------------


// 1. 抽象出每个零件
class CPU
{
public:
	// 抽象计算函数
	virtual void calculate() = 0;
};

class VideoCard
{
public:
	// 抽象显示函数
	virtual void display() = 0;
};

class Memory
{
public:
	// 抽象存储函数
	virtual void storage() = 0;
};


// 2. 具体的厂商零件

// --- Intel 的 CPU、VideoCard、Memory
class IntelCPU :public CPU
{
public:
	virtual void calculate()	// 也可以省略 virtual 关键字,直接写成 void calculate()
	{
		cout << "Intel 的 CPU 开始工作了" << endl;
	}
};

class IntelVideoCard :public VideoCard
{
public:
	virtual void display()
	{
		cout << "Intel 的 VideoCard 开始工作了" << endl;
	}
};

class IntelMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Intel 的 Memory 开始工作了" << endl;
	}
};

// --- Lenovo 的 CPU、VideoCard、Memory
class LenovoCPU :public CPU
{
public:
	virtual void calculate()
	{
		cout << "Lenovo 的 CPU 开始工作了" << endl;
	}
};

class LenovoVideoCard :public VideoCard
{
public:
	virtual void display()
	{
		cout << "Lenovo 的 VideoCard 开始工作了" << endl;
	}
};

class LenovoMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Lenovo 的 Memory 开始工作了" << endl;
	}
};

// 3. 电脑类
class Computer
{
public:
	// 构造函数中传入三个零件指针
	Computer(CPU* cpu, VideoCard* vc, Memory* mem) : cpu(cpu), vc(vc), mem(mem)
	{
		;	// 空语句
	}

	// 提供工作的函数
	void work()
	{
		cpu->calculate();
		vc->display();
		mem->storage();
	}

	// 提供析构函数,销毁电脑的时候,释放3个电脑零件
	~Computer()
	{
		// 释放CPU零件
		if (cpu != NULL)
		{
			delete cpu;
			cpu = NULL;
		}
		if (vc != NULL)
		{
			delete vc;
			vc = NULL;
		}
		if (mem != NULL)
		{
			delete mem;
			mem = NULL;
		}
	}

private:
	CPU* cpu;		// CPU零件指针
	VideoCard* vc;	// 显卡零件指针
	Memory* mem;	// 内存条零件指针
};


void test()
{
	// 4. 组装三台不同的电脑

	// 准备好'第一台电脑'的零件
	CPU* intelCpu = new IntelCPU;
	VideoCard* intelCard = new IntelVideoCard;
	Memory* intelMem = new IntelMemory;

	// 创建'第一台电脑'
	Computer* computer1 = new Computer(intelCpu, intelCard, intelMem);
	computer1->work();

	// 销毁'第一台电脑'
	delete computer1;
	computer1 = NULL;

	cout << "------------------------------" << endl;

	// 第二台电脑的组装
	Computer* computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);
	computer2->work();

	// 销毁'第二台电脑'
	delete computer2;
	computer2 = NULL;


	cout << "------------------------------" << endl;
	// 第三台电脑的组装
	Computer* computer3 = new Computer(new IntelCPU, new LenovoVideoCard, new LenovoMemory);
	computer3->work();

	// 销毁'第三台电脑'
	delete computer3;
	computer3 = NULL;
}

int main()
{
	test();
}

文件操作

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

// 文本文件
// 1. 写文本文件
// 2. 读文本文件

// 二进制文件
// 1. 写二进制文件
// 2. 读二进制文件


void writeText()
{
	// 1、包含头文件

	// 2、创建对象流
	ofstream file;

	// 3、打开文件,以追加的方式
	file.open("test.txt", ios::app);

	// 4、写内容
	file << "姓名:张三" << endl;
	file << "性别:男" << endl;
	file << "年龄:28" << endl;

	// 5、关闭文件
	file.close();
}

void readText()
{
	// 1、包含头文件

	// 2、创建对象流
	ifstream file;

	// 3、打开文件,并判断是否打开成功
	file.open("test.txt", ios::in);
	if (!file.is_open())
	{
		cout << "文件打开失败!" << endl;
		return;
	}

	// 4、读取内容
	/*
	// 方法一:
	char arr[1024] = { 0 };		// 数组
	while (file >> arr)
	{
		cout << arr << endl;
	}

	// 方法二:
	char arr[1024] = { 0 };		// 数组
	while (file.getline(arr, sizeof(arr)))
	{
		cout << arr << endl;
	}

	// 方法三:
	string content;
	while (getline(file, content))		// 调用 <string> 里面的 getline() 方法
	{
		cout << content << endl;
	}

	*/
	// 方法四:(不建议)
	char c;
	while ((c = file.get()) != EOF)	// EOF: end of file
	{
		cout << c;		// 每次读取一个字符,将其输出
	}

	// 5、关闭文件
	file.close();
}

class Person
{
public:
	char m_Name[64];
	int m_Age;
};

void writeBinary()
{
	// 1、包含头文件

	// 2、创建流对象
	ofstream file;

	// 3、打开文件
	file.open("person.txt", ios::out | ios::binary);

	// 4、写文件
	Person p = { "张三", 28 };
	file.write((const char*)&p, sizeof(Person));

	// 5、关闭文件
	file.close();
}

void readBinary()
{
	// 1、包含头文件

	// 2、创建流对象
	ifstream file;

	// 3、打开文件,判断文件是否打开成功
	file.open("person.txt", ios::in | ios::binary);
	if (!file.is_open())
	{
		cout << "文件打开失败" << endl;
	}

	// 4、读取文件
	Person p;
	file.read((char*)&p, sizeof(Person));
	cout << "姓名:" << p.m_Name << "年龄:" << p.m_Age << endl;

	// 5、关闭文件
	file.close();
}

int main()
{
	writeText();
	readText();
	writeBinary();
	readBinary();
}

函数模板的重载

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

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

	string m_Name;
	int m_Age;
};

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.m_Name == p2.m_Name && p1.m_Age == p2.m_Age)	return true;
	else return false;
}

void test()
{
	int a = 10;
	int b = 10;
	bool ret = myCompare(a, b);
	cout << ret << endl;		// 输出结果:1

	Person p1("张三", 18);
	Person p2("李四", 28);
	bool res = myCompare(p1, p2);
	cout << res << endl;		// 输出结果:0
}

int main()
{
	test();
}

类模板

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

// 类模板


template<class Type1, class Type2 = int>	// 类模板在模板参数列表中可以有默认参数
class Person
{
public:
	Person(Type1 name, Type2 age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}

	void showPerson()
	{
		cout << "name: " << m_Name << " age: " << m_Age << endl;
	}

	Type1 m_Name;
	Type2 m_Age;
};

void test()
{
	// ---类模板没有自动类型推导的使用方式
	Person<string, int> p1("张三", 18);
	p1.showPerson();

	// ---类模板在模板参数列表中可以有默认参数
	Person<string> p2("李四", 28);
	p2.showPerson();
}

int main()
{
	test();
}
#include <iostream>
#include <string>
using namespace std;

template<class T1, class T2>
class Person
{
public:
	Person(T1 name, T2 age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}

	void showPerson()
	{
		cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
	}

	T1 m_Name;
	T2 m_Age;
};

// 类模板对象做函数参数

// 1. 指定传入类型
void printPerson1(Person<string, int> &p)
{
	p.showPerson();
}

// 2. 参数模板化
template<class T1, class T2>
void printPerson2(Person<T1, T2>& p)
{
	p.showPerson();
}

// 3. 整个类
template<class T>
void printPerson3(T& p)
{
	p.showPerson();
}

void test()
{
	Person<string, int> p1("张三", 28);
	printPerson1(p1);

	Person<string, int> p2("李四", 18);
	printPerson2(p2);

	Person<string, int> p3("王五", 38);
	printPerson3(p3);
}

int main()
{
	test();
}

类模板与继承

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

// 类模板与继承
template <class T>
class Base
{
	T m_Name;
};

// 必须要知道父类中的T类型,才能继承给子类
class Son : public Base<int>
{

};

void test1()
{
	Son s1;
}

// 如果想灵活指定父类中T类型,子类也需要变类模板
template <class T1, class T2>
class Son2 : public Base<T2>
{
public:
	Son2()
	{
		cout << "T1 的类型为:" << typeid(T1).name() << endl;
		cout << "T2 的类型为:" << typeid(T2).name() << endl;
	}
};

void test2()
{
	Son2<int, char> s2;
}

int main()
{
	test1();
	test2();
}

类模板函数的类外实现

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

template<class T1, class T2>
class Person
{
public:
	Person(T1 name, T2 age);
	void showPerson();

	T1 m_Name;
	T2 m_Age;
};

// 类模板成员的类外实现(构造函数)
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) :m_Name(name), m_Age(age)
{
	cout << "构造函数被调用了" << endl;
}
// 类模板成员的类外实现(普通成员函数)
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
	cout << "姓名:" << m_Name << "年龄:" << m_Age;
}


void test()
{
	Person<string, int> p("Tom", 28);
}

int main()
{
	test();
}

类模板分文件编写

问题:
	类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决:
	解决办法1:直接包含.cpp源文件
	解决办法2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,.hpp是约定的名称,并不是强制
  • main.cpp 文件
#include <string>
using namespace std;


// 第一种解决方式:直接包含源文件
#include "person.cpp"

// 第二种解决方式:将.h和.cpp中的内容写在一起,将后缀名改为.hpp文件(约定俗成)
// #include "person.hpp"

void test()
{
	Person<string, int> p("Tom", 28);
	p.showPerson();
}

int main()
{
	test();
}
  • person.cpp 文件
#include <iostream>
#include "person.h"


// 类模板成员的类外实现(构造函数)
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) :m_Name(name), m_Age(age)
{
	std::cout << "构造函数被调用了" << std::endl;
}
// 类模板成员的类外实现(普通成员函数)
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
	std::cout << "姓名:" << m_Name << " 年龄:" << m_Age << std::endl;
}
  • person.h 文件
#pragma once	// 只要在头文件的最开始加入这条预处理指令,就能够保证头文件只被编译一次,防止头文件被重复引用


template<class T1, class T2>
class Person
{
public:
	Person(T1 name, T2 age);
	void showPerson();

	T1 m_Name;
	T2 m_Age;
};

类模板与友元

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

// 总结:
// 1. 全局函数,如果在类外实现的话,比较简单;如果在类外实现的话,比较复杂,需要提前让编译器知道全局函数的存在
// 2. 建议使用类内实现的方式,简单易懂


// 提前让编译器知道Person类存在(才能实现全局函数,内类声明,类外实现)
template <class T1, class T2>
class Person;

// 全局函数,内类声明,类外实现(这个函数模板实现必须要在类实现之前)
template <class T1, class T2>
void printPerson2(Person<T1, T2> p)
{
	cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
}


template <class T1, class T2>
class Person
{
	// 全局函数,类内实现
	friend void printPerson(Person<T1, T2> p)
	{
		cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
	}

	// 全局函数,类内声明,类外实现
	// 因为类型不一样(实现是函数模板,而声明是函数),所以需要加上空模板参数列表
	// 让它统一类型才不会报错,最终是(实现是函数模板,声明也是函数模板)
	friend void printPerson2<>(Person<T1, T2> p);

public:
	Person(T1 name, T2 age) :m_Name(name), m_Age(age)
	{
		cout << "成功构造了一个对象" << endl;
	}

private:
	T1 m_Name;
	T2 m_Age;
};


void test()
{
	// 全局函数,类内实现
	Person<string, int> p1("Jack", 29);
	printPerson(p1);

	// 全局函数,类外实现
	Person<string, int> p2("Tom", 30);
	printPerson2(p2);
}

int main()
{
	test();
}

模板案例

  • main.cpp 文件
#include <iostream>
#include <string>
#include "MyArray.hpp"
using namespace std;

// 基本数据类型-数组测试
void test1()
{
	// 创建一个int类型,数组长度为10的数组对象 arr1
	MyArray<int> arr1(10);			// 调用构造函数
	MyArray<int> arr2 = arr1;		// 调用拷贝构造函数

	MyArray<int> arr3(20);
	arr3 = arr1;					// 赋值运算符重构

	bool ret;
	for (int i = 0; i < arr3.getCapacity(); i++)
	{
		ret = arr3.append(100 - i);		// 尾追加
		cout << ret << endl;
	}
	cout << "----------" << endl;

	arr3.pop();						// 尾删除
	cout << arr3[9] << endl;		// []运算符重载		输出结果:91
	arr3[9] = 666;
	cout << arr3[9] << endl;		// []运算符重载		输出结果:666
}


// 自定义数据类型-数组测试
class Person
{
public:
	Person() {}
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;
};

void test2()
{
	MyArray<Person> arr(3);

	Person p1("Jack", 25);
	Person p2("Tom", 28);
	Person p3("Jim", 29);

	// 将数据插入到数组中
	arr.append(p1);
	arr.append(p2);
	arr.append(p3);

	// 打印数组
	for (int i = 0; i < arr.getCapacity(); i++)
	{
		cout << "姓名:" << arr[i].m_Name << "\t年龄:" << arr[i].m_Age << endl;
	}
}

int main()
{
	// 基本数据类型-数组测试
	test1();

	cout << "*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*" << endl;

	// 自定义数据类型-数组测试
	test2();

	system("pause");
	return 0;
}

/*
	运行结果:
		1
		1
		1
		1
		1
		1
		1
		1
		1
		1
		----------
		91
		666
		*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
		姓名:Jack      年龄:25
		姓名:Tom       年龄:28
		姓名:Jim       年龄:29
*/
  • MyArray.hpp 文件
// 实现一个通用的数组类
#pragma once
#include <iostream>
#include <string>

template <class T>
class MyArray
{
private:
	T* pAddress;		// 指针指向堆区开辟的真实数组
	int m_Capacity;		// 数组容量
	int m_Size;			// 数组元素个数

public:
	// 构造函数
	MyArray(int capacity)
	{
		this->m_Capacity = capacity;
		this->m_Size = 0;
		this->pAddress = new T[this->m_Capacity];
	}

	// 析构函数
	~MyArray()
	{
		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;
		}
	}

	// 拷贝构造
	/*
		针对以下问题:
			MyArray<int> arr1(8);
			MyArray<int> arr2 = arr1;
	*/
	MyArray(const MyArray& arr)
	{
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		// 深拷贝
		this->pAddress = new T[arr.m_Capacity];
		for (int i = 0; i < arr.m_Size; i++)
		{
			this->pAddress[i] = arr.pAddress[i];
		}
	}

	// 赋值运算符重构
	/*
		针对以下问题:
			MyArray<int> arr1(11), arr2(8), arr3(13);
			arr1 = arr2 = arr3;
	*/
	MyArray& operator=(const MyArray& arr)
	{
		// 先判断原来堆区是否有数据,如果有先释放
		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;
			this->m_Capacity = 0;
			this->m_Size = 0;
		}
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		// 深拷贝
		this->pAddress = new T[arr.m_Capacity];
		for (int i = 0; i < arr.m_Size; i++)
		{
			this->pAddress[i] = arr.pAddress[i];
		}
		return *this;
	}

	// 尾追加
	/*
		针对以下问题:
			MyArray<int> arr1(8);
			arr1.append()
	*/
	bool append(const T& value)
	{
		// 判断是否已经满了,如果满了,无法插入
		if (this->m_Size == this->m_Capacity)	return false;

		// 插入操作
		this->pAddress[m_Size] = value;
		m_Size++;
		return true;
	}

	// 尾删除
	/*
		针对以下问题:
			MyArray<int> arr1(8);
			arr1.pop()
	*/
	void pop()
	{
		// 判断数组是否为空
		if (this->m_Size == 0)	return;

		// 删除操作
		m_Size--;
	}

	// []运算符重构
	/*
		针对以下问题:
			通过下标的方式"访问"数组中的元素 cout << a[0] << endl;
			通过下标的方式"修改"数组中的元素 a[0] = 99;
	*/
	T& operator[](int index)
	{
		return this->pAddress[index];
	}

	// 获取元素个数
	int getSize()
	{
		return this->m_Size;
	}

	// 获取数组容量
	int getCapacity()
	{
		return this->m_Capacity;
	}
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

定制k8s域名解析------CoreDns配置实验

定制k8s域名解析------CoreDns配置实验 1. 需求 k8s集群内通过CoreDns互相解析service名. 同时pana.cn域为外部dns解析,需要通过指定dns服务器进行解析 再有3个服务器,需要使用A记录进行解析 2. K8s外DNS服务器 查看解析文件 tail -3 /var/named/pana.cn.zone 解析内容 ww…

STM32G431RBT6之时钟树配置与生成工程

默认大家都下载了蓝桥杯嵌入式资源包了哈. 首先,打开cubumx,修改RCC与SYS. 打开并观察原理图,发现晶振是24Mhz. 第一步,打开Clock Configuration. 第二步,修改晶振为原理图相对应的24Mhz. 第三步,切换到HSE. 第四步,切换到PLLCLK. 第五步,设置HCLK为80Mhz(15届真题要求为8…

【信号处理】基于EEG脑电信号的自闭症预测典型方法实现

理论 自闭者主要受到遗传和环境因素的共同影响。由于自闭症是一种谱系障碍&#xff0c;因此每个自闭症患者都有独特的优势和挑战。自闭症患者学习、思考和解决问题的方式可以是高技能的&#xff0c;也可以是严峻的挑战。研究表明&#xff0c;高质量的早期干预可以改善学习、沟…

Java web应用性能分析之【MySQL安装注意事项】

本文主要是针对以前LAMP&#xff0c;以及默认用apt安装的mysql。数据文件、日志文件都在一起&#xff1b;innodb_buffer_pool默认用128M。如果你排查问题&#xff0c;最后发现是因为mysql的安装配置不对&#xff0c;是否一口老血要喷出来。同时给MySQL数据库安装做参考。 关于M…

ZYNQ NVME高速存储之EXT4文件系统

前面文章分析了高速存储的各种方案&#xff0c;目前主流的三种存储方案是&#xff0c;pcie switch高速存储方案&#xff0c;zynq高速存储方案&#xff0c;fpga高速存储方案。虽然三种高速存储方案都可以实现高速存储&#xff0c;但是fpga高速存储方案是最烂的&#xff0c;fpga…

23.组件注册方式

组件注册方式 一个 Vue 组件在使用前需要先被“注册”&#xff0c;这样 Vue 才能在渲染模板时找到其对应的实现。组件注册有两种方式&#xff1a;全局注册和局部注册 全局注册 import { createApp } from vue import App from ./App.vue import GlobalComponent from ".…

C++三大特性之一:继承

文章目录 前言一、继承方式二、继承类型继承中构造和析构的顺序继承中的内存分配多继承语法(非重点)继承中同名静态成员的处理继承一般在哪里用到进阶&#xff1a;菱形继承和虚拟继承 总结 前言 C三大特性&#xff1a;继承、多态和封装。继承是面向对象编程的一个核心概念&…

实在IDP文档审阅产品导引

实在IDP文档审阅&#xff1a;智能文档处理的革新者 一、引言 在数字化转型的浪潮中&#xff0c;文档处理的智能化成为企业提效的关键。实在智能科技有限公司推出的实在IDP文档审阅&#xff0c;是一款利用AI技术快速理解、处理文档的智能平台&#xff0c;旨在为企业打造专属的…

在PostgreSQL中如何进行全文搜索,以及如何优化全文搜索性能?

文章目录 如何进行全文搜索1. 创建全文搜索向量2. 执行全文搜索查询 如何优化全文搜索性能1. 使用GIN索引2. 限制搜索范围3. 优化文本处理4. 使用并发搜索5. 监控和调整配置 在PostgreSQL中&#xff0c;全文搜索通常通过使用tsvector和tsquery类型&#xff0c;以及to_tsvector和…

2024第十五届蓝桥杯 C/C++ B组 参赛经历分享(以及部分题解)

前言 emmmmmm&#xff0c;dp杯居然不考dp了&#xff0c;蓝桥一直没怎么出过的高精度居然也考了&#xff08;当时居然因为没太复习那块知识直接模拟混分了&#xff09;&#xff0c;题量也改了&#xff0c;总的来说反而简单了&#xff1f;。。。还好天津竞赛弱省&#xff0c;但愿…

使用HTML和CSS和PHP实现一个简单的简历制作项目

实 验 目 的 掌握HTML表单作用&#xff0c;以及action和method属性&#xff1b; 掌握HTML输入域作用、类型、标签&#xff0c;以及name和value属性&#xff1b; 掌握$_REQUEST变量的作用、语法和使用&#xff1b; 掌握注释&#xff0c;以及变量的作用、命名、赋值和输出&#…

交换机端口类型——操控vlan tag

拓扑图 按照上图vlan及端口类型&#xff0c;操控vlan标签&#xff0c;实现PC1、PC2、PC3互通。 配置 sysname SW1 # vlan 10 # interface GigabitEthernet0/0/1port link-type accessport default vlan 10 # interface GigabitEthernet0/0/24port link-type accessport defaul…

【新版】小剧场短剧影视小程序源码

风口项目 &#xff0c;短剧app 小程序 H5 多端程序 全网首家对接了易支付&#xff0c; 修复了众多BUG 目前已知BUG全部修复 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/89070544 更多资源下载&#xff1a;关注我。

redhatcsa学习笔记--题目+答案

一、semanage命令 semanage命令 – 查询与修改安全上下文 semanage命令来自英文词组“SELinux manage”的缩写&#xff0c;其功能是用于查询与修改安全上下文。semanage的功能类似于chcon命令&#xff0c;它们都可以用于设置文件的SELinux安全上下文策略&#xff0c;而semana…

Linux内核之文件系统访问:目录项、inode、物理磁盘访问关系(五十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

(2022级)成都工业学院数据库原理及应用实验六: SQL DML(增、删、改)

写在前面 1、基于2022级软件工程/计算机科学与技术实验指导书 2、成品仅提供参考 3、如果成品不满足你的要求&#xff0c;请寻求其他的途径 运行环境 window11家庭版 Navicat Premium 16 Mysql 8.0.36 实验要求 在实验三的基础上完成下列查询&#xff1a; 1、在科室表…

C++ 速成

C 概述 c 融合了3中不同的编程方式&#xff1a; C语言代表的过程性语言C 在C语言基础上添加的类代表的面向对象语言C 模板支持的泛型编程 C 标准 一种描述C 的一些语法规则的代码准则 C11 C 应用 游戏 C 效率是一个很重要的原因&#xff0c;绝大部分游戏殷勤都是C写的 网…

【Java网络编程】网络编程中的基本概念及实现UDP、TCP客户端服务器程序

目录 一、什么是网络编程&#xff1f; 二、网络编程中的基本概念 1. 客户端和服务器 2. 请求和响应 三、Socket套接字 UDP数据报套接字编程 1. DatagramSocket 2. DatagramPacket 3. UDP回显客户端服务器程序 4. UDP字典客户端服务器程序 TCP流套接字编程 1. Serve…

论文笔记:Large Language Model for Participatory Urban Planning

202402 arxiv 大模型城市规划 引入了一个基于LLM的多代理协作框架&#xff0c;模拟规划师和数千名具有不同特征和背景的居民&#xff0c;用于参与式城市规划——>生成考虑居民多样化需求的城市区域土地利用规划为了提高讨论的效率&#xff0c;论文采用了鱼缸讨论机制&#…

政安晨:【Keras机器学习示例演绎】(五)—— 利用视觉变换器进行物体检测

目录 导言 导入和设置 准备数据集 实施多层感知器&#xff08;MLP&#xff09; 实施补丁创建层 显示输入图像的补丁 实施补丁编码层 构建 ViT 模型 运行实验 评估模型 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与…