非类型的模板参数
类型形参即:出现在模板参数列表中,跟在
class
或者
typename
之后的参数类型名称
。
非类型形参,就是用一个常量作为类
(
函数
)
模板的一个参数,在类
(
函数
)
模板中可将该参数当成常量来使用
。
注意:
1.
浮点数、类对象以及字符串是不允许作为非类型模板参数的
。
2.
非类型的模板参数必须在编译期就能确认结果
。
#include<iostream>
#include<bitset>
using namespace std;
#define SIZEOF(type) sizeof(type)//宏可以传递类型
//#define SEQLIST_DEFAULT_SIZE 8//宏常量
template<typename T,size_t _N=8>//模板的类型参数
class Seqlist
{
public:
//Seqlist(int sz= SEQLIST_DEFAULT_SIZE)
Seqlist(int sz=_N)
{
//capacity = sz > SEQLIST_DEFAULT_SIZE ? sz : SEQLIST_DEFAULT_SIZE;
capacity = sz > _N ? sz : _N;
Base = new T[sz];
size = 0;
}
private:
//enum {SEQLIST_DEFAULT_SIZE=8};//枚举常量
//const int SEQLIST_DEFAULT_SIZE=8;//成员常量
T* Base;
size_t size;
size_t capacity;
};
void main()
{
Seqlist<int,100>list(10);
system("pause");
}
模板的特化:
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果。
此时,就
需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式
。模板特
化中分为
函数模板特化
与
类模板特化
。
注意:模板和非模板可以共存,参数匹配优先调非模板
函数模板的特化步骤:
1.
必须要先有一个基础的函数模板
2.
关键字
template
后面接一对空的尖括号
<>
3.
函数名后跟一对尖括号,尖括号中指定需要特化的类型
4.
函数形参表
:
必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。
#include<iostream>
using namespace std;
template<class T>
bool IsEqual(T left, T right)
{
cout << typeid(T).name() << endl;
return left == right;
//return strcmp(left, right) == 0;//需要特化
}
//template<>//模板特化
//bool IsEqual<char*>(char* left, char* right)
//{
// return strcmp(left, right) == 0;
//}
bool IsEqual (char* left, char* right)//模板和非模板可以共存,参数匹配优先调非模板
{
return strcmp(left, right) == 0;
}
void Test()
{
/*const char* p1 = "hello";
const char* p2 = "hello";*/
char p1[] = "hello";
char p2[] = "hello";
/*int p1 = 10, p2 = 20;*/
if (IsEqual(p1, p2))
cout <<"p1==p2"<< endl;
else
cout <<"p1!=p2"<< endl;
}
void main()
{
Test();
system("pause");
}
类模板的特化:
1.全特化 全特化即是将模板参数列表中所有的参数都确定化。
2.偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。
(1)偏特化有以下两种表现方式:
部分特化
将模板参数类表中的一部分参数特化。
(2)参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
#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;
};
//偏特化
template<typename T>
class Data<T, T>
{
public:
Data() { cout << "Data<T, T>" << endl; }
private:
int _d1;
char _d2;
};
template<typename T>
class Data<T, int>
{
public:
Data() { cout << "Data<T,int>" << endl; }
private:
T _d1;
int _d2;
};
template<class T1, class T2>
class Data<T1*,T2*>
{
public:
Data() { cout << "Data<T1*, T2*>" << endl; }
private:
T1* _d1;
T2* _d2;
};
template<class T1, class T2>
class Data<T1&, T2&>
{
public:
Data():_d1(T1()),_d2(T2()) { cout << "Data<T1&, T2&>" << endl; }
private:
T1& _d1;
T2& _d2;
};
void TestVector()
{
Data<int, short> d1;
Data<char, double>d3;
Data<int, char> d2;
Data<char, int>d4;
//Data<int, int>d6;//二义性
Data<int*,char*>d5;
Data<const int&, const char&>d6();
}
void main()
{
TestVector();
system("pause");
}
模板的分离编译:
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义:
// a.h
template<class T>
T Add(const T& left, const T& right);
// a.cpp
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
// main.cpp
#include"a.h"
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}
解决方法:
1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。
2. 模板定义的位置显式实例化
。这种方法不实用,不推荐使用。
模板总结:
【优点】
1. 模板复用了代码,节省资源,更快的迭代开发,
C++
的标准模板库
(STL)
因此而产生
2. 增强了代码的灵活性
【缺陷】
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
使用模板的典型例子--类型萃取
#include<iostream>
using namespace std;
class IntArray
{
public:
IntArray()
{
for (int i = 1; i <= 10; i++)
{
ar[i - 1] = i;
}
}
int GetSum(int time)
{
int sum = 0;
for (int i = 0; i < 10; i++)
{
sum += ar[i];
}
return sum*time;
}
private:
int ar[10];
};
class FloatArray
{
public:
FloatArray()
{
for (int i = 0; i < 10; i++)
{
ar[i]=i+1.11;
}
}
float GetSum(float time)
{
float sum = 0.0f;
for (int i = 0; i < 10; i++)
{
sum += ar[i];
}
return sum*time;
}
private:
float ar[10];
};
template<class type>
class NumTraits
{};
template<>
class NumTraits<IntArray>
{
public:
typedef int return_type;
typedef int arg_type;
};
template<>
class NumTraits<FloatArray>
{
public:
typedef float return_type;
typedef float arg_type;
};
template<class Type>
class CApply
{
public:
//需要完美兼顾
typename NumTraits<Type>::return_type GetSum(Type& obj,typename NumTraits<Type>::arg_type time)
{
return obj.GetSum(time);
}
};
void main()
{
IntArray intA;
//cout <<"int sum="<< intA.GetSum() << endl;
FloatArray floatA;
//cout << "float sum=" << floatA.GetSum() << endl;
CApply<IntArray> obj;
cout << "int sum=" << obj.GetSum(intA,3) << endl;
CApply<FloatArray> obj1;
cout << "int float=" << obj1.GetSum(floatA,2.3) << endl;
system("pause");
}
C
语言中我们用到的最频繁的输入输出方式就是
scanf ()
与
printf()
。
scanf():
从标准输入设备
(
键盘
)
读取数据,并将值存放在变量中
。
printf():
将指定的文字
/
字符串输出到标准输出设备
(
屏幕
)
。注意宽度输出和精度
输出控制。
C
语言借助了相应的缓冲区来进行输入与输出。
对
输入输出缓冲区
的理解:
1.
可以
屏蔽掉低级
I/O
的实现
,低级
I/O
的实现依赖操作系统本身内核的实现,所以如果能够屏蔽这部分的差异,可以
很容易写出可移植的程序
。
2.
可以
使用这部分的内容实现
“
行
”
读取的行为
,对于计算机而言是没有
“
行
”
这个概念,有了这部分,就可以
定义
“
行
”
的概念,然后解析缓冲区的内容,返回一个
“
行
”
。
C++标准IO流:
![](https://img-blog.csdnimg.cn/b309b4d2e3ba41e8b436252b971054c3.png)
C++
标准库提供了
4
个全局流对象
cin
、
cout
、
cerr
、
clog
,使用
cout
进行标准输出,即数据从内存流向控制台
(
显示器
)
。使用
cin
进行标准输入即数据通过键盘输入到程序中
,同时
C++
标准库还提供了
cerr
用来进行标准错误的输出
,以及
clog
进行日志的输出
,从上图可以看出,
cout
、
cerr
、
clog
是
ostream
类的三个不同的
对象,因此这三个对象现在基本没有区别,只是应用场景不同。
在使用时候必须要包含文件并引入
std
标准命名空间。
注意:
1. cin
为缓冲流。
键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿
。如果一次输入过多,会留在那儿慢慢用,
如果输入错了,必须在回车之前修改,如果回车键按下就无法挽回了
。
只有把输入
缓冲区中的数据取完后,才要求输入新的数据
。
2.
输入的数据类型必须与要提取的数据类型一致
,否则出错。出错只是在流的状态字
state
中对应位置位
(置
1
),程序继续。
3.
空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输入。但如果是
字符型和字符串,则空格(
ASCII
码为
32
)无法用
cin
输入,字符串中也不能有空格
。回车符也无法读
入。
4. cin
和
cout
可以直接输入和输出内置类型数据,原因:
标准库已经将所有内置类型的输入和输出全部重
载了。
5.
对于自定义类型,如果要支持
cin
和
cout
的标准输入输出,需要对
<<
和
>>
进行重载。
标准IO流:
#include<iostream>
#include<string>
//#include<stdio.h>
using namespace std;
void main()
{
/*cout << "help me";
cerr << "help me";
clog << "help me";
system("pause");*/
string str;
//while (cin >> str)
//{
// cout << str<<" ";
//}
while (getline(cin,str))
{
cout << str;
}
}
3.2 C++
文件
IO
流
C++
根据文件内容的数据格式分为
二进制文件
和
文本文件
。采用文件流对象操作文件的一般步骤:
1.
定义一个文件流对象
ifstream ififile(
只输入用
)
ofstream ofifile(
只输出用
)
fstream iofifile(
既输入又输出用
)
2.
使用文件流对象的成员函数打开一个磁盘文件,使得文件流对象和磁盘文件之间建立联系
3.
使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写
4.关闭文件
C语言文本文件流操作:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<assert.h>
using namespace std;
//文本方式
void test01()
{
int ar[] = { 12,35,78,91,100 };
int n = sizeof(ar) / sizeof(ar[0]);
FILE* fp = fopen("Test1.txt", "w");
assert(fp != NULL);
for (int i = 0; i < n; ++i)
{
fprintf(fp, "%d ", ar[i]);
}
fclose(fp);
}
void test02()
{
int ar[5];
FILE* fp = fopen("Test1.txt", "r");
assert(fp!=NULL);
for (int i = 0; i < 5; ++i)
{
fscanf(fp, "%d ", &ar[i]);
}
for (auto& e : ar)
{
cout << e << " ";
}
cout << endl;
fclose(fp);
}
void main()
{
test02();
system("pause");
}
C++文本文件流:
#include<iostream>
#include<assert.h>
#include<fstream>
using namespace std;
//文本方式
void test01()
{
int ar[] = { 12,35,78,91,100 };
int n = sizeof(ar) / sizeof(ar[0]);
ofstream ofile("test3.txt", ios::out);
//ofile.open("test2.txt", ios::out);
assert(ofile);
for (int i = 0; i < n; ++i)
{
ofile << ar[i] << " ";
}
ofile.close();
}
void test02()
{
int ar[5];
ifstream ifile;
ifile.open("test2.txt", ios::in);
assert(ifile);
for (int i = 0; i < 5; ++i)
{
ifile >> ar[i];
}
for (auto& e : ar)
{
cout << e << " ";
}
cout << endl;
ifile.close();
}
void main()
{
test02();
system("pause");
}
C语言二进制文件流
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<assert.h>
using namespace std;
文本方式
void test01()
{
int ar[] = { 12,35,78,91,100 };
int n = sizeof(ar) / sizeof(ar[0]);
FILE* fp = fopen("Test1.txt", "wb");
assert(fp != NULL);
fwrite(ar, n, sizeof(int), fp);
fclose(fp);
}
void test02()
{
int ar[5];
FILE* fp = fopen("Test1.txt", "rb");
assert(fp != NULL);
fread(ar, sizeof(int),5, fp);
for (auto& e : ar)
{
cout << e << " ";
}
cout << endl;
fclose(fp);
}
void main()
{
test02();
system("pause");
}
C++二进制文件流:
#include<iostream>
#include<assert.h>
#include<fstream>
using namespace std;
//文本方式
void test01()
{
int ar[] = { 12,35,78,91,100 };
int n = sizeof(ar) / sizeof(ar[0]);
ofstream ofile("test2.txt", ios::out|ios::binary);
//ofile.open("test2.txt", ios::out);
assert(ofile);
ofile.write((const char*)ar, sizeof(int) * n);
ofile.close();
}
void test02()
{
int ar[5];
ifstream ifile;
ifile.open("test2.txt", ios::in|ios::binary );
assert(ifile);
ifile.read((char*)ar,sizeof(int)*5);
for (auto& e : ar)
{
cout << e << " ";
}
cout << endl;
ifile.close();
}
void main()
{
test02();
system("pause");
}
文件操作例子:
#include<iostream>
#include<assert.h>
#include<fstream>
#include<string>
using namespace std;
struct ServerInfo
{
char _ip[32]; // ip
int _port; // 端口
};
class ConfigManager
{
public:
ConfigManager(string cfg = "hymsever.cfg"):_configFile(cfg)
{}
void ReadInfo(ServerInfo& info)
{
ifstream ifile(_configFile.c_str(), ios::in);
assert(ifile);
ifile >> info._ip >> info._port;
ifile.close();
}
void WriteInfo(ServerInfo& info)
{
ofstream ofile(_configFile.c_str(), ios::out);
assert(ofile);
ofile << info._ip <<" "<< info._port;
ofile.close();
}
private:
string _configFile;
};
void main()
{
ConfigManager config;
ServerInfo ser_info = {"192.168.5.5",8000};
config.WriteInfo(ser_info);
//config.ReadInfo(ser_info);
system("pause");
}
stringstream
的简单介绍
在
C
语言中,如果想要将一个整形变量的数据转化为字符串格式,如何去做?
1.
使用
itoa()
函数
2.
使用
sprintf()
函数
但是两个函数在转化时,都得
需要先给出保存结果的空间
,那空间要给多大呢,就不太好界定,而且
转化格式不匹配时,可能还会得到错误的结果甚至程序崩溃
。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<assert.h>
#include<string>
using namespace std;
void test01()
{
int a = 4618461;
char str[30] = { 0 };
//_itoa_s(a, str, 10);
sprintf(str, "%d", a);
cout <<"str="<< str << endl;
}
void main()
{
test01();
system("pause");
}
在
C++
中,可以使用
stringstream
类对象来避开此问题。
在程序中如果想要使用
stringstream
,必须要包含头文件
。在该头文件下,标准库三个类:
istringstream
、ostringstream
和
stringstream
,分别用来进行流的输入、输出和输入输出操作,本文主要介绍
stringstream
。
stringstream
主要可以用来:
1.
将数值类型数据格式化为字符串
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<assert.h>
#include<string>
#include<sstream>
using namespace std;
void test01()
{
int a = 123456789;
string str;
stringstream ss;
//str = a;
ss << a;
ss >> str;
cout << "str=" << str << endl;
ss.clear();
double d = 12.34;
ss << d;
ss >> a;
cout << str << endl;
}
void main()
{
test01();
system("pause");
}
2.字符串拼接
#include<iostream>
#include<sstream>
using namespace std;
void main()
{
stringstream sstream;
// 将多个字符串放入 sstream 中
sstream << "first" << " " << "string,";
sstream << " second string";
cout << "strResult is: " << sstream.str() << endl;
清空 sstream
//sstream.str("");
//sstream << "third string";
//cout << "After clear, strResult is: " << sstream.str() << endl;
string strobj = sstream.str();
const char* str = strobj.c_str();
cout << str << endl;
cout << strobj << endl;
}