目录
泛型编程
推演实例化
显示实例化
类模板
类模板的声明和定义分离
STL
string
string的构造和拷贝构造
选取特定字符串拷贝
解析:
关于npos的解析
验证
从一个字符串中拷贝前几个字符
解析:
注意:
验证:
size()& length()
string::operator[ ]
迭代器对string进行访问
capacity
capacity的扩容机制
在g++下
编辑
在VS22下
reserve
编辑
无数据扩容:
在VS22底下运行
在g++底下运行
编辑
无数据缩容:
在VS22底下
在g++底下
有数据缩容
在VS22底下
在g++底下
总结:
resize
重新分配字符串长度
在VS22
在g++下
resize是既影响空间,又影响数据
operator+=
insert
pos位置插入一个实例对象
pos位置插入一个实例对象的子字符串
pos位置直接插入字符串
erase
编辑
清楚部分字符串
find
rfine
c_str
substr
find_first_of
getline
泛型编程
泛型编程是跟具体类型无关的代码,关键字template(模板)
什么是模板?
意思就是说写一个swap函数,针对不同的类型,我需要写很多很多种
void swap(int a, int b);
void swap(double a, double b);
void swap(char a, char b);
void swap(short a, short b);
……
有了模板之后,就可以套模板 ,就相当于套公式一样的简单了
模板有推演实例化和显示实例化
推演实例化
template<typename T>
typename 可以改为 class,typename是类型名的意思
void swap(T& a,T& b){
T temp = a;
a = b;
b = temp;
}
int main() {
int a = 1, b = 2;
double c = 1.1, d = 2.2;
printf("原来我的a是%d,b是%d\n", a, b);
Swap(a, b);
printf("现在我的a是%d,b是%d\n", a, b);
printf("原来我的c是%lf,d是%lf\n", c, d);
Swap(c, d);
printf("现在我的c是%lf,d是%lf\n", c, d);
return 0;
}
所以面对要写众多函数,我一个函数模板就搞定了
当然还可以多参数
template<class A,class B>
void fun(A& a, B& b) {
cout << a << " " << b << endl;
}
void test2() {
int a = 1;
double b = 2.0;
char c = 'd';
int* p = &a;
fun(a, c);
fun(b, p);
}
int main() {
test2();
return 0;
}
给它参数,让模板自己推那是什么吧
以上都是推演实例化
显示实例化
下面看一个返回类型的模板
template<class T>
T* fun1() {
T* p = new T[10];
return p;
}
当我们创建出这个模板的时候,我们就会面临一个问题,就是当我们没有办法去推演这个T应该是什么类型的时候
此刻,推演不了,那我们就要告诉它,它应该返回什么类型的值
void test3() {
int* ret = fun1<int>();
}
在函数后面告诉这个函数,你应该是个什么样的值,函数名<类型>
当两者功能一样的普通函数和函数模板同时存在的时候
如果参数是普通函数有的,则直接用现成的,如果不是普通参数有的,则会先走函数模板
类模板
类模板都是显示实例化
普通类中,类名就是类型
类模板中,类名就不是类型了,要类名<数据类型>才是整个类的类型
那么为什么要有类模板
当我写一个栈的时候,正常的写法是:
class Stack {
private:
int* _a;
int _top;
int _capacity;
public:
Stack(int n = 4){
_a = new int[n];
_top = 0;
_capacity = n;
}
~Stack(){
delete[] _a;
_a = nullptr;
_top = _capacity = 0;
}
};
当我想要构建整型栈的时候很容易,很轻松,直接实例化对象即可
那,当我想要构建 double 栈的时候呢???整型栈类还能派上用场吗,显然是不能的
需要将 int* _a; 改为 double* _a;
需要将 _a = new int [n] 改为 _a = new double [n];
这就意味着我要写两个类呀,极其不方便,所以有了类模板
template<class T>
class Stack {
private:
T* _a;
int _top;
int _capacity;
public:
Stack(int n = 4) {
cout << "wogouzaole" << endl;
_a = new T[n];
_top = 0;
_capacity = n;
}
~Stack(){
cout << "xigou" << endl;
delete[] _a;
_a = nullptr;
_top = _capacity = 0;
}
};
int main() {
Stack<int> s1;
Stack<double> s2;
return 0;
}
比如Stack<int> 和 Stack<double>是两个不同的类型
他们两个不是同一个类型,即类模板实例化是不同类型,得到的类也是不同的
问题:
那么写出了不同类型的模板后,相应的构造函数是否也需要跟着改变呢?
回答:
我们首先要知道构造函数是函数名跟类名相同,而 Stack<T>是什么,是类型,不是类名
所以构造函数不能够
Stack<T>() {
}
而要继续
Stack(){
}
类模板的声明和定义分离
1,类模板的函数声明和定义分离要在同一个文件
2,定义要在前头加上类的类型 ,即 类<类型>
template<class T>
class Stack {
private:
T* _a;
int _top;
int _capacity;
public:
Stack(int n = 4);
~Stack();
};
template<class T>
Stack<T>::Stack(int n) {
cout << "wogouzaole" << endl;
_a = new T[n];
_top = 0;
_capacity = n;
}
template<class T>
Stack<T>::~Stack() {
cout << "xigou" << endl;
delete[] _a;
_a = nullptr;
_top = 0;
_capacity = 0;
}
STL
STL只是C++标准库的其中一部分,STL是标准模板库的缩写,它是C++标准库的重要组成部分
STL数据结构和算法运用较为广泛,下面的string就是STL里面的一个模板之一
string
使用string模板的时候需要引入头文件 #include<string>
string的构造和拷贝构造
跟我们平常写的类差不多类型
string s1; //(1)
string s2("hell0 string");//(4)
string s3 = s2; //(2)
选取特定字符串拷贝
substring (3) | string (const string& str, size_t pos, size_t len = npos); |
---|
解析:
(3) substring constructor
Copies the portion of str that begins at the character position pos and spans len characters (or until the end of str, if either str is too short or if len is string::npos).
从str对象里拷贝一部分字符串,长度从你选取开始的地方到跨越的长度,如果字符串太短或者len = -1 的话,就直接到末尾
关于npos的解析
npos是size_t 类型的,即无符号整数
而-1是什么,-1在底层的补码形式是 11111111 11111111 11111111 11111111
如果给到无符号整数的话,那么这个值就可以认为其是无穷大的,所以 npos = -1 的时候就可以认为是无限大
验证
从一个字符串中拷贝前几个字符
from sequence (5) | string (const char* s, size_t n); |
---|
解析:
Copies the first n characters from the array of characters pointed by s.
注意:
它跟string (const string& str, size_t pos, size_t len = npos);不同的是,它的第一个显示参数是const char* s ,而不是str
所以如果用str的话,会被当作string (const string& str, size_t pos, size_t len = npos);来执行,执行结果就是从str的第五个位置开始,将此后的所有字符串拷贝,违背了我们想要拷贝前几个字符的初衷
验证:
size()& length()
功能:计算字符串长度,size和length不包含\0
string::operator[ ]
它有两种重载,一种const,一种非const
它的功能是返回的是字符串一个指定位置的引用,用法可以参考数组
for (size_t i = 0; i < s2.size(); i++) {
cout << s2[i] << " ";
}
cout << endl;
s[i]的本质是s.operator[ ](i)
如果没有const修饰的话,还可以修改:s[i] = 'x';
迭代器对string进行访问
string::iterator it = s2.begin();
while (it != s2.end()) {
cout << *it << " ";
it++;
}
iterator 就像是用指针的方式进行遍历,iterator的访问方式是可以线性的访问,在今后的在物理空间上非连续的容器中能够大展身手,基于此特点迭代器是容器的主流访问形式
const_iterator 和 const iterator 有着本质的区别:
const_iterator的本质是保护迭代器指向的数据*it 不被修改
const iterator 的本质是保护迭代器本身不能修改,即 it 不能修改
capacity
capacity的扩容机制
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 int main(){
6 string s1("hello string");
7 size_t old = s1.capacity();
8 cout<<old<<endl;
9 for(size_t i=0;i<1000;i++){
10 s1.push_back('x');
11 if(old!=s1.capacity()){
12 cout<<s1.capacity()<<endl;
13 old = s1.capacity();
14 }
15 }
16 return 0;
17 }
在g++下
g++很省,基本上是你字符串有多少就给你多少空间,并且每次达到扩容机制的时候都是2倍2倍的扩,很有规律
在VS22下
capacity在VS22下大概是每次呈1.5倍以此扩容
reserve
无数据扩容:
确定自己需要多少空间,就可以使用reserve,有时候给多了可能是因为考虑到了对齐规则,不同的平台可能是不同的,扩容机制和空间机制都是一样的,根据平台而定
int main() {
string s1;
cout<<s1.capacity()<<endl;
s1.reserve(100);
cout << s1.capacity() << endl;
s1.reserve(200);
cout << s1.capacity() << endl;
return 0;
}
在VS22底下运行
在g++底下运行
Linux会老老实实的开
无数据缩容:
在VS22底下
在g++底下
在无数据地下会缩成我们想要的结果
有数据缩容
在VS22底下
int main() {
string s1 = "hello reverse";
cout << "s1的长度是" << s1.size() << endl;
cout << "s1的原始容量是" << s1.capacity() << endl;
s1.reserve(10);
cout << "缩容后s1的长度是" << s1.size() << endl;
cout << "缩容后s1的长度是" << s1.capacity() << endl;
return 0;
}
换成13
它的数据量受到保护不会变,容量也不会变
在g++底下
总结:
reserve扩容机制可根据自己想要的空间来去指定,但最后得到的空间可能会有多不少,具体看不同的编译器;缩容的时候,会很好的保护数据。
resize
重新分配字符串长度
在VS22
int main() {
string s1 = "hello resize";
cout << "s1的长度是" << s1.size() << endl;
cout << "s1的原始容量是" << s1.capacity() << endl;
cout << endl;
s1.resize(100);
cout << "resize重分配后s1的长度是" << s1.size() << endl;
cout << "resize重分配后s1的原始容量是" << " ";
cout<< s1.capacity() << endl;
return 0;
}
resize重新开辟的空间,补充的是'\0'
在g++下
g++还是一如既往的省吃俭用
resize是既影响空间,又影响数据
可以看出,一旦resize规划的大小比原来小,就会把一些数据除去,留下前几位
>capacity 扩容+尾插
capacity>=resize()>size() 尾插
size()>resize() 容量不变,但数据会直接丢失,即删除数据,保留前n个
resize更多的是开空间和初始化
operator+=
想要什么直接在它屁股后面加就好了,不用管那么多的
insert
pos位置插入一个实例对象
string& insert (size_t pos, const string& str);
pos位置插入一个实例对象的子字符串
string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);
Inserts a copy of a substring of str. The substring is the portion of str that begins at the character position subpos and spans sublen characters (or until the end of str, if either str is too short or if sublen is npos).
大致意思就是插入一个实例对象的子字符串,从子字符串的pos位置开始,到某个长度为止
eg:从字符串s1的第六个位置插入,选择s2中,从第0个位置开始的后9个字符插入
pos位置直接插入字符串
string& insert (size_t pos, const char* s);
Inserts a copy of the string formed by the null-terminated character sequence (C-string) pointed by s.
从s字符串中拷贝一份插入到第pos位置
pos位置插入n个连续的字符
string& insert (size_t pos, size_t n, char c);
Inserts n consecutive copies of character c.
还有很多就不一一举例了
erase
作用就是,清除字符串,减少长度
清楚部分字符串
string& erase (size_t pos = 0, size_t len = npos);
find
find既能找字符串,又能找字符
rfine
与fine查找方向是相反的
c_str
可以使一个实例对象转换为由一个指针指向的字符串数组
一般在文件转换的时候能够使用到
int main() {
string file_name("test.cpp");
FILE* fout = fopen(file_name.c_str(), "r");
char ch = fgetc(fout);
while (ch != EOF) {
cout << ch;
ch = fgetc(fout);
}
}
substr
切割字符串 ,参数是起始位置跟长度
find_first_of
Find character in string
Searches the string for the first character that matches any of the characters specified in its arguments.
就是找任何在目标字符串里面的字符
int main ()
{
std::string str ("Please, replace the vowels in this sentence by asterisks.");
std::size_t found = str.find_first_of("aeiou");
while (found!=std::string::npos)
{
str[found]='*';
found=str.find_first_of("aeiou",found+1);
}
std::cout << str << '\n';
return 0;
}
Pl**s*, r*pl*c* th* v*w*ls *n th*s s*nt*nc* by *st*r*sks.
结果就是屏蔽掉了所有有关"aeiou"
它跟find_last_of 的区别就是,last是反着来找
与之相反的是
find_first_not_of 和 find_last_not_of
getline
我们在事件中不免会用上输入输出
如果我们想要对一个string实例化对象s1,进行输入的时候我们会先如何
string s1;
cin >> s1;
接着再用cout对其进行打印出来
但是我们却发现,只打印出了hello,我的getline却不见了
但我换成这样又能读了
但是我的getline却给了s2
这是为何?
在 C++ 中,使用
cin
读取字符串时,如果输入的字符串中包含空格,那么cin
会一直读取到遇到第一个空格为止。因此,当我输入 "hello getline" 时,cin
会首先读取 "hello" 并将其存储在s1
中。此时,"getline" 仍然在输入缓冲区中,因为
cin
没有读取它。接下来,当我尝试读取
s2
时,使用同样的cin >> s2;
,cin
会立即从输入缓冲区中读取下一个单词 "getline",因为它是在之前的输入中留下的。这就是为什么不需要再次输入,s2
就已经被赋值了
因此为了避免这种情况,需要使用getline这个函数接口
此刻便能一整句的将字符串输出来
涉及到两个参数,一个是流提取,另一个是实例化对象
以上便是本次博文的学习内容了,如有大佬能够指点出论文错误,将不胜感激,谢谢阅读!