文章目录
- 函数模板
- 函数模板的原理
- 函数模板的实例化
- 模板参数的匹配原则
- 类模板
- 类模板的定义格式
- 类模板的实例化
- string
函数模板
函数模板的原理
template <typename T> //模板参数 ——类型
void Swap(T& x1, T& x2)
{
T tmp = x1;
x1 = x2;
x2 = tmp;
}
int main()
{
int a = 0, b = 1;
double c = 1.1, d = 2.2;
swap(a, b);
swap(c, d);
int* p1 = &a;
int* p2 = &b;
swap(p1, p2);
return 0;
}
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此
函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化
1、隐式实例化:让编译器根据实参推演模板参数的实际类型
template<class T>
T Add(const T& left , const T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.1, d2 = 20.2;
//函数模板根据调用,自动推导模板参数的类型 ,实例化对应的参数
cout << Add(a1, a2) << endl;
cout << Add(d1, d2) << endl;
//实参传递的类型, 推演T的类型
cout << Add( a1, (int)d1 ) << endl;
cout << Add( (double)a1, d1) << endl;
//显示实例化 ,用指定的类型实例化 ,相当于隐式类型转换
cout << Add<int> (a1, d1) << endl;
cout << Add<double>(a1, d1) << endl;
return 0;
}
2、显式实例化:在函数名后的<>中指定模板参数的实际类型
template<class T>
T Add(const T& left , const T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.1, d2 = 20.2;
//函数模板根据调用,自动推导模板参数的类型 ,实例化对应的参数
cout << Add(a1, a2) << endl;
cout << Add(d1, d2) << endl;
//实参传递的类型, 推演T的类型
cout << Add( a1, (int)d1 ) << endl;
cout << Add( (double)a1, d1) << endl;
//显示实例化 ,用指定的类型实例化 ,相当于隐式类型转换
cout << Add<int> (a1, d1) << endl;
cout << Add<double>(a1, d1) << endl;
return 0;
}
template<class T >
T * Alloc(int n )
{
return new T[n];
}
int main()
{
//有些函数无法自动推导函数模板的类型,实例化对应的参数,只能显式实例化
double *p1 = Alloc <double>(10);
return 0;
}
模板参数的匹配原则
类模板
//类模板 ,无法推演实例化,所以类模板都是显式实例化
class Stack
{
public :
Stack(int capacity = 3)
{
_array= new T[capacity];
_size = 0;
_capacity = 0;
}
void Push(const T & data)
{
_array[_size++] = data;
}
~Stack()
{
free(_array);
_size = _capacity = 0;
}
private :
T * _array;
int _size;
int _capacity;
};
int main()
{
Stack <int> s1(); // int
Stack <double> s2();//double
Stack <char> s3();//char
//Stack <int ,doule> s2();
return 0;
}
类模板的定义格式
函数类模板的声明和定义分离
template<class T>
class Stack
{
public:
//声明
Stack(int capacity );
void Push(const T& data)
{
_array[_size++] = data;
}
~Stack()
{
free(_array);
_size = _capacity = 0;
}
private:
T* _array;
int _size;
int _capacity;
};
//定义
template<class T>
Stack<T>::Stack(int capacity )
{
_array = new T[capacity];
_size = 0;
_capacity = 0;
}
int main()
{
Stack <int> s1(); // int
Stack <double> s2();//double
Stack <char> s3();//char
//Stack <int ,doule> s2();
return 0;
}
对于普通类,类名和类型是一样的,但是对于类模板 ,类名和类型是不一样的 上面的代码中Stack是类名 ,但是Stack < T >是类型
类模板的实例化
string
一、string的定义方式
string(); //构造一个空字符串
string(const char* s); //复制s所指的字符序列
string(const char* s, size_t n); //复制s所指字符序列的前n个字符
string(size_t n, char c); //生成n个c字符的字符串
string(const string& str); //生成str的复制品
string(const string& str, size_t pos, size_t len = npos); //复制str中从字符位置pos开始并跨越len个字符的部分
使用示例:
string s1; //构造空字符串
string s2("hello string"); //复制"hello string"
string s3("hello string", 3); //复制"hello string"的前3个字符
string s4(10, 's'); //生成10个's'字符的字符串
string s5(s2); //生成s2的复制品
string s6(s2, 0, 4); //复制s2中从字符位置0开始并跨越4个字符的部分
二、string的插入
使用push_back进行尾插
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
s.push_back('C');
s.push_back('X');
s.push_back('Q');
cout << s << endl; //CXQ
return 0;
}
使用Insert插入
int main()
{
string s("C");
//insert(pos,str)在pos位置插入字符串str
s.insert(1, "X");
cout << s << endl;
//insert(pos, string) ,在pos位置插入string对象
string t("Q");
s.insert(2, t);
cout << s << endl;
//insert(pos, char)在pos位置插入char
s.insert(s.end(), 'A');
cout << s << endl;
return 0;
}
三、string的拼接
使用append函数完成string的拼接:
int main()
{
string s1("A");
string s2("B");
//append(string)完成两个sting对象的拼接
s1.append(s2);
cout << s1 << endl;
//append(str)完成string对象和str字符串的拼接
s1.append("C");
cout << s1 << endl;
//append(n, char)将n个字符串拼接到string对象后面
s1.append(1, 'D');
cout << s1 << endl;
return 0;
}
四、string的删除
int main()
{
string s("CXQ");
s.pop_back();
s.pop_back();
cout << s << endl;
return 0;
}
使用erase删除
int main()
{
string s("ABCDEFG");
//erase (pos , n )删除pos位置开始的n个字符
s.erase(2, 1);
cout << s << endl;//ABDEFG
//erase(pos)删除pos位置的字符
s.erase(s.end() - 1);
cout << s << endl;
//erase(pos1, pos2)删除[pos1pos2)上所有字符
s.erase(s.begin() + 1, s.end());
cout << s << endl;
return 0;
}
五、string的查找
1、使用find函数正向搜索第一个匹配项
int main()
{
string s1("http://www.cplusplus.com/reference/string/string/find/");
//find(string)正向搜索与string对象所匹配的第一个位置
string s2("www");
size_t pos1 = s1.find(s2);
cout << pos1 << endl;
//find(str)正向搜索与str字符串所匹配的第一个位置
char str[] = "cplusplus.com";
size_t pos2 = s1.find(str);
cout << pos2 << endl;
//find(char)正向搜索与char字符所匹配的第一个位置
size_t pos3 = s1.find('r');
cout << pos3 << endl;
return 0;
}
2、使用rfind函数反向搜索第一个匹配项
int main()
{
string s1("http://www.cplusplus.com/reference/string/string/find/");
//rfind反向搜索与string对象所匹配的第一个位置
string s2("string");
size_t pos1 = s1.rfind(s2);
cout << pos1 << endl;
//rfind(str)反向搜索与字符串str所匹配的第一个位置
char str[] = "reference";
size_t pos2 = s1.rfind(str);
cout << pos2 << endl;
//rfind(char)反向搜索与字符char所匹配的第一个位置
size_t pos3 = s1.rfind('/');
cout << pos3 << endl;
return 0;
}
六、string的比较
使用compare函数完成比较:
比较规则:
1、比较字符串中第一个不匹配的字符值较小,或者所有比较字符都匹配,但比较字符串较短,则返回小于0的值。
2、比较字符串中第一个不匹配的字符值较大,或者所有比较字符都匹配,但比较字符串较长,则返回大于0的值。
3、比较的两个字符串相等,则返回0。
int main()
{
string s1("hello world");
string s2("hello Linux");
cout << s1.compare(s2) << endl;//1
//ell 与hello Linux 比较
cout << s1.compare(1, 3, s2) << endl;//-1
//hello与hello比较
cout << s1.compare(0, 4, s2, 0, 4) << endl;//0
return 0;
}
注意:除了支持string类之间进行比较,compare函数还支持string类和字符串进行比较
七、string的替换
使用replace函数完成string的替换:
int main()
{
string s("hello world");
//replace(pos, len, str)将pos开始的len个字符替换成str
s.replace(6, 5, "Linux");//hello Linux
cout << s << endl;
//replace(pos, len, n, char)将pos位置开始的len个字符替换成n个字符char
s.replace(10, 1, 3, '!');
cout << s << endl;//hello Linu!!!
return 0;
}
八、string的交换
使用swap函数完成两个string类的交换:
int main()
{
string s1("hello");
string s2("Linux");
//使用string类的成员函数swap交换s1和s2
s1.swap(s2);
cout << s1 << endl;
cout << s2 << endl;
//使用非成员函数swap交换s1和s2
swap(s1, s2);
cout << s1 << endl;
cout << s2 << endl;
return 0;
}
九、string的大小和容量
1、使用size函数或length函数获取当前有效字符的个数
int main()
{
string s("hello");
cout << s.size() << endl;
cout << s.length() << endl;
return 0;
}
2、使用max_size函数获取string对象最多可包含的字符数
int main()
{
string s("hello");
cout << s.size() << endl;
cout << s.length() << endl;
cout << s.max_size() << endl;
return 0;
}
3、使用capacity函数获取当前对象所分配的存储空间的大小
int main()
{
string s("hello");
cout << s.size() << endl;
cout << s.length() << endl;
cout << s.max_size() << endl;
cout << s.capacity() << endl;
return 0;
}
4、使用resize改变当前对象的有效字符的个数
resize规则:
1、当n大于对象当前的size时,将size扩大到n,扩大的字符为c,若c未给出,则默认为’\0’。
2、当n小于对象当前的size时,将size缩小到n。
int main()
{
string s1("hello");
s1.resize(30);
cout << s1 << endl;
//resize(n)n大于对象当前的size时,将size扩大到n,扩大的字符默认为'\0'
cout << s1.capacity() << endl;
cout << s1.size() << endl;
string s2("ABCD");
// resize(n, char)n大于对象当前的size时,将size扩大到n,扩大的字符为char
s2.resize(20, 'x');
cout <<s2<< endl; //ABCDxxxxxxxxxxxxxxxx
cout << s2.size() << endl; //20
cout << s2.capacity() << endl; //当前容量
string s3("ABCD");
//resize(n)n小于对象当前的size时,将size缩小到n
s3.resize(2);
cout << s3 << endl;
cout << s3.capacity() << endl;//当前容量
cout << s3.size() << endl;//当前有效字符个数
return 0;
}
注意:若给出的n大于对象当前的capacity,则capacity也会根据自己的增长规则进行扩大。
5、使用reserve改变当前对象的容量大小
reserve规则:
1、当n大于对象当前的capacity时,将capacity扩大到n或大于n。
2、当n小于对象当前的capacity时,什么也不做。
int main()
{
string s("ABCD");
cout << s << endl;
cout << s.size() << endl;//当前有效字符的个数
cout << s.capacity() << endl; //当前容量
//reverse(n)当n大于对象当前的capacity时,将当前对象的capacity扩大为n或大于n
s.reserve(20);
cout << s << endl;
cout << s.size() << endl;//当前有效字符的个数
cout << s.capacity() << endl; //当前容量
// reverse(n)当n小于对象当前的capacity时,什么也不做
s.reserve(2);
cout << s << endl;
cout << s.size() << endl;//当前有效字符的个数
cout << s.capacity() << endl; //当前容量
return 0;
}
注意:此函数对字符串的size没有影响,并且无法更改其内容
6、使用clear删除对象的内容,删除后对象变为空字符串
int main()
{
string s("hello");
cout << s << endl;
s.clear();
cout << s << endl;
return 0;
}
7、使用empty判断对象是否为空
int main()
{
string s("hello");
cout << s << endl;
cout << s.empty() << endl;//判断对象是否为空
s.clear();//删除对象里的内容,对象将变为空字符串
cout << s.empty() << endl;//判断对象是否为空
return 0;
}
十、string中元素的访问
1、[ ]+下标
因为string类对[ ]运算符进行了重载,所以我们可以直接使用[ ]+下标访问对象中的元素。并且该重载使用的是引用返回,所以我们可以通过[ ]+下标修改对应位置的元素。
int main()
{
string s("ABCD");
//[]+下标访问对象元素
for (size_t i =0; i < s.size(); i++)
{
cout << s[i] << endl;;
}
//[]+下标修改对象元素内容
for (size_t i= 0; i < s.size(); i++)
{
s[i] = 'x';
}
cout << s << endl;
// string s1("hello world");
// char s3[] = "hello world";
// s3[1]++; // 等价于*(s3+1)
// s1[1]++; // 等价于s1.operator[](1);
return 0;
}
2、使用at访问对象中的元素
因为at函数也是使用的引用返回,所以我们也可以通过at函数修改对应位置的元素。
int main()
{
string s("ABCD");
for (size_t i = 0; i < s.size(); i++)
{
cout << s.at(i);
}
cout<< endl;
for (size_t i = 0; i < s.size(); i++)
{
s.at(i) = 'x';
}
cout << s << endl;
return 0;
}
3、使用范围for访问对象中的元素
需要特别注意的是:若是需要通过范围for修改对象的元素,则用于接收元素的变量e的类型必须是引用类型,否则e只是对象元素的拷贝,对e的修改不会影响到对象的元素。
范围for底层替换为正向迭代器,范围for有局限 :范围for不能支持反向迭代器
正向迭代器 : iterator const_iterator(只能读)
反向迭代器: reverse_iterator 、const_reverse_iterator(只能读)
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("CSDN");
//使用范围for访问对象元素
for (auto e : s)
{
cout << e;
}
cout << endl; //CSDN
//使用范围for访问对象元素,并对其进行修改
for (auto& e : s) //需要修改对象的元素,e必须是引用类型
{
e = 'x';
}
cout << s << endl; //xxxx
return 0;
}
4、使用迭代器访问对象中的元素
int main()
{
string s1("ABCD");
//使用迭代器访问对象元素
string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it <<" ";
++it;
}
cout << endl;
//使用迭代器访问对象元素,并对其进行修改
string s2("QWER");
string::iterator it2 = s2.begin();
while (it2 != s2.end())
{
*it2 += 1;
it2++;
}
cout << s2;
return 0;
}
iterator 提供了一种统一的方式访问和修改容器的数据,算法可以通过迭代器处理容器中的数据
十一、string中运算符的使用
1、operator=
string类中对=运算符进行了重载,重载后的=运算符支持string类的赋值、字符串的赋值以及字符的赋值。
int main()
{
string s1;
string s2("ABCD");
//支持string类的赋值
s1 = s2;
cout << s1 << endl;
//支持字符串的赋值
s1 = "hello";
cout << s1 << endl;
//支持字符的赋值
s1 = 'X';
cout << s1 << endl;
return 0;
}
2、operator+=
string类中对+=运算符进行了重载,重载后的+=运算符支持string类的复合赋值、字符串的复合赋值以及字符复合的赋值。
int main()
{
string s1;
string s2("hello");
//支持string类的复合赋值
s1 += s2;
cout << s1 << endl;
//支持字符串的复合赋值
s1 += " world";
cout << s1 << endl;
//支持字符的复合赋值
s1 += "X";
cout << s1 << endl;
return 0;
}
3、operator+
string类中对+运算符进行了重载,重载后的+运算符支持以下几种类型的操作:
string类 + string类
string类 + 字符串
字符串 + string类
string类 + 字符
字符 + string类
它们相加后均返回一个string类对象。
int main()
{
string s;
string s1("ABCD");
string s2("QWER");
char str[] = "DFGH";
char ch = '!';
//string类 + string类
s = s1 + s2;
cout << s << endl;
//string类 + 字符串
s = s1 + str;
cout << s << endl;
//字符串 + string类
s = str + s1;
cout << s << endl;
//string类 + 字符
s = s1 + ch;
cout << s << endl;
//字符+string类
s = ch + s1;
cout << s << endl;
return 0;
}
4、operator>> 和 operator<<
string类中也对>>和<<运算符进行了重载,这就是为什么我们可以直接使用>>和<<对string类进行输入和输出的原因。
int main()
{
string s;
cin >>s ;//输入
cout << s;//输出
return 0;
}
5、relational operators
string类中还对一系列关系运算符进行了重载,它们分别是==、!=、<、<=、>、>=。重载后的关系运算符支持string类和string类之间的关系比较、string类和字符串之间的关系比较、字符串和string类之间的关系比较。
int main()
{
string s1("abcd");
string s2("abde");
cout << (s1 > s2) << endl; //0
cout << (s1 < s2) << endl; //1
cout << (s1 == s2) << endl; //0
return 0;
}
注意:这些重载的关系比较运算符所比较的都是对应字符的ASCII码值。
十二、string中与迭代器相关的函数
1、与正向迭代器相关的函数
begin函数:返回一个指向字符串第一个字符的迭代器。
end函数:返回一个指向字符串结束字符的迭代器,即’\0’。
int main()
{
string s("hello string");
//正向迭代器
string::iterator it1 = s.begin();
while (it1 != s.end())
{
cout << *it1;
it1++;
}
cout << endl;
return 0;
}
2、与反向迭代器相关的函数
rbegin函数:返回指向字符串最后一个字符的反向迭代器。
rend函数:返回指向字符串第一个字符前面的理论元素的反向迭代器。
int main()
{
string s("hello string");
//反向迭代器
string::reverse_iterator rit = s.rbegin();
while (rit!= s.rend())
{
cout << *rit;
rit++;
}
cout << endl;
return 0;
}
十三、string与字符串之间的转换
1、将字符串转换为string
将字符串转换为string很简单,在前面讲string的定义方式时就有说到。
#include <iostream>
#include <string>
using namespace std;
int main()
{
//方式一
string s1("hello world");
//方式二
char str[] = "hello world";
string s2(str);
cout << s1 << endl; //hello world
cout << s2 << endl; //hello world
return 0;
}
2、使用c_str或data将string转换为字符串
区别:
在C++98中,c_str()返回 const char* 类型,返回的字符串会以空字符结尾。
在C++98中,data()返回 const char* 类型,返回的字符串不以空字符结尾。
但是在C++11版本中,c_str()与data()用法相同。
int main()
{
//将string 转换为字符串
string s("hello world ");
const char* str1 = s.data();
const char* str2 = s.c_str();
cout << str1 << endl;
cout << str2 << endl;
return 0;
}
十四、string中子字符串的提取
int main()
{
string s1("abcdef");
string s2;
//substr(pos, n)提取pos位置开始的n个字符序列作为返回值
s2 = s1.substr(2, 4);
cout << s2 << endl;
return 0;
}
2、使用copy函数将string的子字符串复制到字符数组中
int main()
{
string s("abcdef");
char str[20];
//copy(str, n, pos)复制pos位置开始的n个字符到str字符串
size_t lenth=s.copy(str, 4, 2);
//copy函数不会在复制内容的末尾附加'\0',需要手动加
str[lenth] = '\0';
cout << str << endl;
return 0;
}
十五、string中的getline函数
我们知道,使用>>进行输入操作时,当>>读取到空格便会停止读取,基于此,我们将不能用>>将一串含有空格的字符串读入到string对象中。
这时,我们就需要用getline函数完成一串含有空格的字符串的读取操作了。
用法一:
getline函数将从istream中提取到的字符存储到str中,直到读取到换行符’\n’为止。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
getline(cin, s); //输入:hello CSDN
cout << s << endl; //输出:hello CSDN
return 0;
}
getline函数将从is中提取到的字符存储到str中,直到读取到分隔符delim或换行符’\n’为止。
int main()
{
string s;
getline(cin, s, 'D');
cout << s << endl;
return 0;
}
如果你觉得这篇文章对你有帮助,不妨动动手指给点赞收藏加转发,给鄃鳕一个大大的关注
你们的每一次支持都将转化为我前进的动力!!!