c++补充

C++ 知识补充 向量(vector)是一个封装了动态大小数组的顺序容器(SequenceContainer)。跟任意其它类型容器一样,它能够存放各种类型的对象。可以简单的认为,向量是一个能够存放任意类型的动态数组。C++中map提供的是一种键值对容器,里面的数据都是成对出现的,如下图:每一对中的第一个值称之为关键字(key),每个关键字只能在map中出现一次;第二个称之为该关键字的对应值。 阅读详情

构造函数、析构函数

#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;
	}
};
V4L2教程(1):Ubuntu V4L2开发环境搭建与设备探索 欢迎来到V4L2学习指南的第一篇!本文将带你从零开始,在Ubuntu系统上搭建完整的V4L2视频采集开发环境。无论你是想开发视频监控、视频会议还是机器视觉应用,掌握V4L2都是Linux视频开发的必经之路。 **本节学习目标**: - 理解V4L2的基本概念和架构 - 搭建Ubuntu下的V4L2开发环境 - 掌握v4l2-ctl等调试工具的使用 - 学会检测和配置USB摄像头设备 阅读详情

相关推荐

halcon 检测直线

图中后三个对象既是检测出的图像;

qq_59062726的博客 665

C++入门补充语法

C++入门语法

2301_81496280的博客 1312

angular中访问本地json文件

先在assets文件夹下写一个json文件,这样在打完包之后也可以手动修改json文件内容 1、在需要访问本地json文件的ts文件里面引入 import{HttpClient}from'@angular/common/http'; 2、用ajax请求调用,我这里是用已经封装好的ajax请求调用 gethttp = ()=>{ var self = this self.ajax.getAjax("../../assets/server.json","g...

蜗牛~~ 1828

C++11特性补充

对C++11特性进行补充,重点介绍了四种智能指针之间的区别,以及设计一些特殊类的方法和技巧。

2401_87944878的博客 2982

无人机巡检提速3倍!YOLOv8n+OpenVINO让Jetson Nano跑25FPS,还能多飞25分钟

摘要(149字): 针对无人机巡检中Jetson Nano算力不足导致的帧率低、首次推理延迟高、续航短等问题,提出YOLOv8n+OpenVINO联合优化方案: 模型轻量化:YOLOv8n参数量仅3.2M(较v8s减少71%),Jetson Nano帧率从8FPS提升至15FPS(原生),绝缘子裂纹召回率仅降3%; OpenVINO加速:AUTO模式动态分配CPU/GPU算力,首次推理延迟从500ms降至200ms,帧率再优化至25FPS; 能效提升:功耗降低40%,单次续航延长25分钟。方案已落地电力/林

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1242

2024年淘宝平板电脑销售数据分析

本项目的目标是从淘宝电商平台上自动化爬取商品信息,并通过数据分析帮助了解价格、销量等相关指标,为商业决策提供支持。使用Selenium实现数据爬取,结合Python的数据处理与可视化工具,进一步分析和展示结果。

2301_76728517的博客 1474

c++基础补充

内存四区的意义:不同区域存放的数据,赋予不同的生命周期,给我们带来更大的灵活编程。野指针 指向的位置是不可知的(随机的、不正确的、没有明确限制的)坏指针,是指没有初始化的指针,或者非法赋值的指针。作用:让代码更加清晰。

zhouhe_的博客 604

如何使用MCP2518FD外部CAN FD控制器实现速速CAN通信

1、SPI时序MCP2518FD外部CAN FD控制器具有小尺寸和SPI接口,可实现轻松连接。可将CAN FD通道轻松添加到缺少CAN FD外设或没有所需CAN FD通道的微控制器上。MCP2518FD支持经典格式 (CAN 2.0B) 和CAN灵活数据速率 (CAN FD) 格式中的CAN帧格式,符合ISO11898-1:2015标准。框图:本文实验板MCU:CAN FD控制器:MCP2518FDCAN FD收发器:ATA6560开发环境:想了解开发板更详细信息,请点击。

qq_57798018的博客 1万+

C++语言相关补充

简单记录一些遇到的C++语法和注意事项

weixin_43992162的博客 389

UI自动化控制PC端微信获取当前群聊的群友列表

在一个月前,某个群友在获取整个群的群友信息时遇到了一点问题: 对整个群进行群友数据获取经测试确实有点棘手,下面我将我的解决过程公布给大家。 基础教程详见: https://blog.youkuaiyun.com/as604049322/article/details/121391639 人工打开要抓取的群聊窗口后,首先获取微信窗口并点击聊天信息按钮: import uiautomation as auto auto.uiautomation.SetGlobalSearchTimeout(1) wechatWind

小小明-代码实体的专栏 3万+

基于simulink 的不间断电源ups的项目实例

UPS系统的核心组件包括整流器、逆变器、电池管理系统和控制系统。- **Three-Phase Controlled Rectifier**:设置触发角为0度,用于控制整流器的输出电压。- **Three-Phase Inverter**:设置逆变器的输出频率为60Hz,用于将直流电转换为交流电。- **Control System**:编写MATLAB函数,用于监测市电状态并控制各模块的工作模式。- **Step**:设置阶跃时间,例如0.05秒,阶跃前后的值分别为0和1,用于模拟市电中断。

xiaoheshang_123的博客 709

C++入门再补充:

我们今天主要是来讲解我们的类和对象;但是我们的上一节课我们的C++入门还有部分没有讲完,我们今天就先把他们讲完吧。我们之前讲了内联inline。

daiwoliyunshang的博客 872

196、NPU的编译器开发:教程与示例代码编写

NPU编译器开发实战指南 摘要:本文分享了NPU编译器开发中的核心挑战与实战经验,通过具体代码示例展示了关键实现技术: 指令生成:必须考虑硬件特性(如地址对齐)和芯片配置抽象化 图优化:重点处理常见冗余结构(Reshape合并、BN融合),并注意数值稳定性 算子拆分:采用分块计算策略适配有限SRAM,需特别注意边界条件处理 内存分配:基于张量使用频率的贪心算法优化SRAM使用效率 文中包含多个真实踩坑案例: DMA地址未对齐导致硬件错误 BN融合时的除零风险 tile边界处理不当引起的精度下降 这些经验为开

m0_50546716的博客 79

C++项目——集群聊天服务器项目(八)用户登录业务(补充)

当客户端A向客户端B发送消息的时候,该消息通过A和服务器的连接传给服务器,服务器通过与B的连接主动推送给B,所以这些连接应该是长连接,同时需要存储不同客户端和服务器的连接。主要原因是为了,使得锁的作用范围最小,当然可以进函数就加锁,出函数解锁,但是那样就不能体现多线程的优势了,那样就类似于单线程串行了。对象在多个线程之间共享,那么在修改状态时就需要加锁,以确保线程安全。在原指导网站后面, 有些没有写全,在这里进行补充,在二后面进行补充。所以需要去相应的头文件和源文件进行补充。//更新用户的状态信息。

qq_43233606的博客 357

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

C++原始指针的补充

C++中const的位置顺序引起的指针变化,还有双层指针。

2301_81280642的博客 1418

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

C++ ——补充知识点

include<iomanip>//C语言int num=7;//C++//setw():使用前需要导入<iomanip>头文件//正数在前面补空格,负数在后面补空格return 0;

2301_81699298的博客 755

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

对C++中string类的补充

COW 机制依赖引用计数来判断数据当前是否被共享。没有引用计数,COW 无法高效地确定何时需要执行实际的拷贝。引用计数本身解决了资源释放和共享所有权的问题。COW 则是在此基础上,为了避免不必要的深拷贝(尤其是在拷贝后不修改或只修改一部分的场景)而引入的一种惰性拷贝优化策略。它利用引用计数提供的信息来决定拷贝操作是否可以延迟到真正需要修改数据时才执行。在 COW 的实现中:拷贝操作:增加引用计数,共享数据。析构操作:减少引用计数,计数为 0 时释放数据。

LXdian_LX的博客 333

Cpp-友元知识补充

Cpp-友元知识补充 在有些情况必须得允许非成员函数访问一个类的私有成员,同时可以组织一般的访问。比如重载操作符,输入或者输出操作符,经常在类中需要访问类的私有成员。友元这个机制允许一个类将其非公有成员的访问权授予指定的函数或者类,友元的声明用friend开始。它只表明在类定义的内部,友元可以出现在类中的任何地方。通常来说一般将友元声明放在类定义的开始或者结尾。 1,友元函数 友元函数虽然不

Cutepingping的博客 434

C++补充知识+专题思维导图

NULL在源码中就是一个0,因此可能会出现一些二义性的问题。在C++11中使用nullptr代替NULL,作为空指针的表示方式,在C++中,可以用作空指针常量,表示指针不指向任何有效的内存地址。使用auto关键字可以推导类型,C++11引入的。decltypt可以推导表达式的类型,需要注意,decltypt只会分析表达式的类型,不会具体计算表达式的值。C++11中引入了列表初始化语法,可以使用{}对对象进行初始化。C++11可以对整数进行不同进制的输出。

Zz3528966192的博客 1017

C++补充

C++基本语法介绍

Protein_zmm的博客 929

C++模板的补充

用一个简单的栈例子讲类模板。

努力学习C、C++、Linux等! 346

C到C++知识补充

首先是作为笔记,也可以是对C到C++快速入门一文的补充,涉及到了C++的基本语法、unordered_map以及智能指针。

m0_75034791的博客 146

c++概念——类和对象补充概念

c++ 类与对象的最后部分概念补充

2301_79705195的博客 1168

C++基础(补充)

设置一个命名空间——命名空间域。namespace,只影响使用,不影响生命周期。专门构建出来的一道防线。防止自己定义的东西和库里冲突,但加了这一句毁于一旦。这是C语言的缺陷,C语言没办法解决类似的命名冲突问题,所以C++提出了。//C++标准库的命名空间。,一般为了方便写小的代码可以展开。如果展开相当于没有命名空间了。C语言库中有一个函数,rand。此时我们自己定义的和库里面相。此时这个代码正常运行,因为每个变量a都不在一个域中。但一般情况下不建议全局展开。,或项目组多个人定义的。1.指定命名空间访问。

weixin_73236117的博客 572

C++ 模板补充

文章摘要:本文介绍了C++模板的进阶用法,重点讲解了非类型模板参数的使用限制、函数模板和类模板特化的实现方法(包括全特化和偏特化),以及模板分离编译的常见问题及解决方案。针对模板分离编译问题,分析了编译链接过程,并提出了两种解决方法:显式实例化和在头文件中定义模板。文章通过代码示例详细说明了各种模板技术的具体应用场景和注意事项。

2302_80453405的博客 687

C++20新特新——02特性的补充

至此,我们对协程的学习就更进一步了;

weixin_47702917的博客 920
上一篇: 如何使用 git 提交项目到 github上
下一篇: STL-标准模板库
code_stream
博客等级 码龄4年 1238粉丝 169原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值