模板进阶-C/C++IO流

模板进阶

模板参数分类类型形参与非类型形参.

  • 类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称**。**

  • 非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

非类型模板参数

整形常量.可以传缺省值,从右往左缺省且连续。

  • 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
  • 非类型的模板参数必须在编译期就能确认结果

C++11的array定长数组没啥用,C语言对于越界的检查是抽查,可能查不到。但是array就是对于越界访问检查更加严格。operator[],用assert检查。

template<class T,size_t N=300>
class MyStack
{
public:
	void push(const T& x)
	{}
private:
	T _a[N];
	size_t _top;
};
int main()
{
	MyStack<int, 100>st1;
	MyStack<int, 200>st1;
	return 0;
}

模板的特化

使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果

函数模板的特化

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出

class Date {};
template<class T>
bool ObjLess(const T& left, const T right)
{
	return left < right;
}

//参数匹配
//bool ObjLess(const Date*& left, const Date*& right)
//{
//	return *left < *right;
//}

//函数专用化
//template<>
//bool ObjLess<Date*>(Date* left,Date* right)
//{
//	return *left < *right;
//}
int main()
{
	MyStack<int, 100>st1;
	MyStack<int, 200>st1;
	Date* p1 = new Date();
	Date* p2 = new Date();
	ObjLess(p1,p2);

	return 0;
}

类模板的特化

比如需要针对某些类型特殊化处理.

全特化

全特化是将模板参数列表中的所有参数都确定化.

偏特化

针对模板参数进一步尽心够条件限制设计的特化版本.

  • 两种表现形式:

    • 部分特化(将模板参数类表中的一部分参数特化)

    • 参数更进一步的限制:针对模板参数进一步的条件限制设计出来的特化版本

#include<iostream>
using namespace std;
template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};
//全特化版本
template<>
class Data<int, char>
{
public:
	Data() { cout << "Data<int, char>" << endl; }
private:
	int _d1;
	char _d2;
};
//	偏特化
// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
	Data() { cout << "Data<T1, int>" << endl; }
private:
	T1 _d1;
	int _d2;
};
template <class T1>
class Data<T1, char>
{
public:
	Data() { cout << "Data<T1, char>" << endl; }
private:
	T1 _d1;
	char _d2;
};
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }

private:
	T1 _d1;
	T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
	Data(const T1& d1, const T2& d2)
		: _d1(d1)
		, _d2(d2)
	
	{
	cout << "Data<T1&, T2&>" << endl;
	}

private:
	const T1& _d1;
	const T2& _d2;
};
void test2()
{
	Data<double, int> d1; // 调用特化的int版本
	Data<int, double> d2; // 调用基础的模板 
	Data<int*, int*> d3; // 调用特化的指针版本
	Data<int&, int&> d4(1, 2); // 调用特化的引用版本
}
void TestVector()
{
	Data<int, int> d1;//偏特化int
	Data<int, char> d2;//全特化<int,char>
	//<T1,char>
	Data<char, char> d3;
	Data<double, char> d4;
}
int main()
{
	//TestVector();
	test2();
	return 0;
}
template<size_t N>
class A
{
public:
	A() { cout << "A<N>" << endl; }
};
template<>
class A<10>
{
public:
	A() { cout << "A<10>" << endl; }
};
int main()
{
	A<100>();//A<N>
	A<10>();//A<10>
	return 0;
}

模板的分离编译

  • 首先什么是分离编译

    声明和定义分离是为了方便维护,

    vector.h结构定义和函数声明,vector.cpp函数定义.

    .h文件了解框架设计基本功能.cpp了解具体实现细节

  • 编译链接步骤:
    预处理:头文件展开、条件编译、宏替换、去掉注释->vector.i test.i
    编译 :检查语法错误,生成汇编代码->vector.s test.s
    汇编:将汇编代码转化为二进制机械码 ->vector.o test.o
    链接:将多个obj文件合并在一起,并处理没有解决的地址问题

    链接之前,前面的文件是不会进行交互的

声明是一种承诺,告诉你有这个东西,编译时无法直接生成函数地址,在链接的时候会去根据声明的地址找他的定义.

  • 模板参数不能分离(函数模板)

image-20230203113529184

.cpp文件,也就是函数定义的地方,编译器不知道应该实例化为什么类型,函数模板无法实例化,类模板无法将T替换掉,不会编译生成汇编代码,就无法实现具体的函数,函数就没有被分配地址,

在main.obj中调用的函数,编译器只有在链接的时候才会找对应的地址,但是函数因为没有实例化生成具体代码,所以是链接时报错,在链接的时候就找不到函数.

模板不能分离编译并不是语法问题,只是现在的编译器不支持,未来可能会发展支持。

模板程序被编译两次,这是不能分离编译的原因所在

如何解决?
  • 将声明和定义放在.hpp中或者.h文件中,

    声明和定义不分离(对于模板来说)
    那么使用他的地方直接就有定义,头文件展开之后,直接就有模板定义和实例化,在编译时那么直接就可以填上函数调用地址,不需要链接的时候再去找了。

  • 显示实例化(类型不同还得不同的实例化代码很麻烦)

    template
    void F2<int>(const int& n);//将声明中未知的T实例化为int 
    

    函数模板例子:

    image-20230203125613106

  • 类模板的例子:将模板的声明和定义放在一起就行

    image-20230203132224435

小结

模板的优点:复用代码节约资源,更快的迭代开发。STL因此产生

模板的缺点:模板的代码膨胀问题,实例化不同类型,编译时间变长,实例化的过程消耗的

声明和定义分离之后有声明,可以使用该类型(定义一个指针)
但是如果初始化一个对象,这里需要调用构造函数,因为没有完成实例化,链接的时候链接不上

调用函数时,如果参数类型能够直接匹配,那么就直接调用相应的类型函数,非类型模板函数
如果进行了类型的特化,就会直接调用模板函数。

IO流

流fstream函数重载以及operator>>返回值的存在实现了不同类型的连续输入输出.

scanfprintf()只能支持内置类型,而coutcin能够通过重载实现自定义类型的输入输出.

scanf输入成功返回个数,如果数据读完了就会返回EOF.

while(cin>>str);cin返回值是istream重载了ios::operator bool值作为判定条件

image-20230203144808079

class B
{
    public:
    operator bool()
    {
        return _a!=0;//输入0就结束调用operatorbool()转为bool值
    }
    int _a;
};
int main()
{
    B b;
    while(b)//因为重载了operatorbool(),所以可以转化为bool值作为判定条件
    {
        cin>>b._a;
    }
    return 0;
}

C语言文件操作:

struct ServerInfo
{
	char _ip[32];
	int port;
};
//二进制读写-写出去的东西文件看不见
void TestC_W_BIn()
{
	ServerInfo info = { "127.0.0.1",80 };
	//fopen/flose
	//fwrite/fread-二进制读写
	//fprintf/fscanf-文本读写
	FILE* fout = fopen("test.bin", "w");
	fwrite(&info, sizeof(info), 1, fout);
	fclose(fout);
}
void TestC_R_BIn()
{
	FILE* fin = fopen("test.bin", "r");
	
	ServerInfo info;
	fread(&info, sizeof(info), 1, fin);
	fclose(fin);
	printf("%s:%d\n",info._ip,info.port);
}
void TestC_W_Text()
{
	FILE* fin = fopen("test.txt", "w");
	ServerInfo info = {"127.0.0.1",80};
	fprintf(fin,"%s %d", info._ip, info.port);

	fclose(fin);
}
void TestC_R_Text()
{
	FILE* fin = fopen("test.txt", "r");

	ServerInfo info;
	//字符组名字就是首元素地址
	fscanf(fin,"%s %d",info._ip,&info.port);
	//fscanf是以空格和换行作为分割符的,如果读的时候挨着就无法判断结束标志
	printf("%s:%d\n", info._ip, info.port);
	fclose(fin);
}

//int main()
//{
//	
//	TestC_W_Text();
//	TestC_R_Text();
//	return 0;
//}

C++文件操作

#define _CRT_SECURE_NO_WARNINGS

#include<iostream>
#include<fstream>
#include<string>
#include<sstream>
using namespace std;
class Date
{
	friend ifstream& operator>>(ifstream& ifs, Date& d);

	friend ofstream& operator<<(ofstream& ofs, Date& d);
public:
	Date(int y=1, int m=1, int d=1)
		:_year(y)
		, _month(m)
		, _day(d)
	{}
private:
	int _year;
	int _day;
	int _month;
};

ofstream& operator<<(ofstream& ofs, Date& d)
{
	ofs << d._year << " " << d._month << " " << d._day << endl;
	return ofs;
}
ifstream& operator>>(ifstream& ifs, Date& d)
{
	ifs >> d._year >> d._month >> d._day;
	return ifs;
}
//序列化时重载使用
ostringstream& operator<<(ostringstream& ofs, Date& d);
istringstream& operator>>(istringstream& ifs, Date& d);


struct ServerInfo
{
	char _ip[32];
	int port;

	Date _d;
};

class ConfigManager
{
public:
	ConfigManager(const char* filename)
		:_filename(filename)
	{}
	void WriteBin(ServerInfo& info)
	{
		ofstream ofs(_filename.c_str(), ios_base::out | ios_base::binary);
		ofs.write((const char*) & info, sizeof(ServerInfo));
	}
	void ReadBin(ServerInfo&info)
	{
		ifstream ifs(_filename.c_str(), ios_base::in | ios_base::binary);
		ifs.read((char*)&info, sizeof(ServerInfo));
	}
	//C++文本写支持流插入
	void WriteText(ServerInfo& info)
	{
		ofstream ofs(_filename.c_str());
		ofs << info._ip <<" " << info.port<<" ";
		ofs << info._d;
		//写入的时候注意分割符的添加,否则读的时候读不出来
        //ofs为什么不能连续接收呢?因为返回值类型不同(见下文)
	}
	void ReadText(ServerInfo& info)
	{
		ifstream ifs(_filename.c_str());
		ifs >> info._ip >> info.port;
		ifs >> info._d;
	}
private:
	string _filename;
};
class PersonInfo
{
public:
	string _name;
	int _age;

};
int main()
{
	//组合结构化的信息-序列化
	PersonInfo s = {"张三",20};
	ostringstream oss;
	oss << s._name <<" " << s._age;
	string str = oss.str();//转成字符串

	//解析字符串->结构化数据-反序列化
	istringstream iss(str);
	string name;
	int age;
	iss >> name >> age;
	//ServerInfo info = { "127.0.0.1",80 };

	/*ConfigManager cm("config.bin");
	cm.WriteBin(info);

	cm.ReadBin(info);
	cout << info.port<<":"<<info._ip;*/
	
	/*ServerInfo winfo={ "127.0.0.1",80,Date(2023,2,3)};
	ConfigManager cm("config.txt");
	cm.WriteText(winfo);

	ServerInfo rinfo = {"",0,Date()};

	cm.ReadText(rinfo);*/
	return 0;
}
  • ofs为什么不能连续接收呢?因为返回值类型不同

场景:

int main()
{
    int x=100;
    Date d1(2022,3,4);
    Date d(2023,3,4);
    ofstream ofs("text.txt");
    ofs<<d1<<d2;
    ofs<<x<<d1;//报错,返回值类型不同
    //先int 返回值是ostream和Date类重载的ofstream 就匹配不上了
    
    ostringstream ss;
    ss<<d1;//也不支持,也是继承的ostream
    return 0;
}

image-20230204192649475

  • 从左到右继承关系(父到子)

    image-20230204193252979

  • 解决方案:

    • 为了让以上两种情况都支持和利用继承关系,只需要将共同的父类ostream的的operator<<重载出来就行了,具体的那两个子类的都可以不写.因为两个子类对象都可以给给父类对象(切片).
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值