概述
C++提高编程
- 一、 模板
- 1.函数模板
- 2.类模板
- 二、STL之容器
- 1. STL六大组件
- 2.string
- 2.vector
- 3.deque
- 4.list
- 5.set和multiset
- 6.map
- 7.stack
- 8. queue 常用接口
- 9.总结
- 三、STL常用算法
- 1.函数对象
- 1.概念
- 2.谓词的概念
- 3.一些内部仿函数
- 2.STL常用算法
- 1.遍历算法
- 2.查找算法
- 3.排序算法
- 4.cope和replace
- 5.accumulate和fill
- 6.集合算法(两个集合必须是有序序列)
- 四、其他
- 1.输入函数和字符函数
一、 模板
1.函数模板
在调用函数模板时,可以不指定类型,但前提是可以让编译器可以推出其类型
template<class T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
// 1、自动类型推导,必须推导出一致的数据类型T,才可以使用
void test01()
{
int a = 10;
int b = 20;
char c = 'c';
mySwap(a, b); // 正确,可以推导出一致的T
//mySwap(a, c); // 错误,推导不出一致的T类型
}
// 2、模板必须要确定出T的数据类型,才可以使用
template<class T>
void func()
{
cout << "func 调用" << endl;
}
void test02()
{
//func(); //错误,模板不能独立使用,必须确定出T的类型
func<int>(); //利用显示指定类型的方式,给T一个类型,才可以使用该模板
}
int main() {
test01();
test02();
system("pause");
return 0;
}
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
普通函数与函数模板的调用规则
1. 如果函数模板和普通函数都可以实现,优先调用普通函数
2. 可以通过空模板参数列表来强制调用函数模板
3. 函数模板也可以发生重载
4. 如果函数模板可以产生更好的匹配,优先调
//普通函数与函数模板调用规则
void myPrint(int a, int b)
{
cout << "调用的普通函数" << endl;
}
template<typename T>
void myPrint(T a, T b)
{
cout << "调用的模板" << endl;
}
template<typename T>
void myPrint(T a, T b, T c)
{
cout << "调用重载的模板" << endl;
}
void test01()
{
//1、如果函数模板和普通函数都可以实现,优先调用普通函数
// 注意 如果告诉编译器 普通函数是有的,但只是声明没有实现,或者不在当前文件内实现,就会报错找不到
int a = 10;
int b = 20;
myPrint(a, b); //调用普通函数
//2、可以通过空模板参数列表来强制调用函数模板
myPrint<>(a, b); //调用函数模板
//3、函数模板也可以发生重载
int c = 30;
myPrint(a, b, c); //调用重载的函数模板
//4、 如果函数模板可以产生更好的匹配,优先调用函数模板
char c1 = 'a';
char c2 = 'b';
myPrint(c1, c2); //调用函数模板
}
int main() {
test01();
system("pause");
return 0;
}
2.类模板
类模板与函数模板区别主要有两点:
1. 类模板没有自动类型推导的使用方式
2. 类模板在模板参数列表中可以有默认参数
类模板中成员函数和普通类中成员函数创建时机是有区别的:
1.普通类中的成员函数一开始就可以创建
2.类模板中的成员函数在调用时才创建,因为类模板中的成员函数可能会调用类模板中为确定的变量类型,所以不能在调用之前创建。
类模板成员函数可以类外实现
#include <string>
//类模板
template<class NameType, class AgeType = int>
class Person
{
public:
//成员函数类内声明
Person(NameType name, AgeType age);
void showPerson()
{
cout << "name: " << this->mName << " age: " << this->mAge << endl;
}
public:
NameType mName;
AgeType mAge;
};
//构造函数 类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
//1、类模板没有自动类型推导的使用方式
void test01()
{
// Person p("孙悟空", 1000); // 错误 类模板使用时候,不可以用自动类型推导
Person <string ,int>p("孙悟空", 1000); //必须使用显示指定类型的方式,使用类模板
p.showPerson();
}
//2、类模板在模板参数列表中可以有默认参数
void test02()
{
Person <string> p("猪八戒", 999); //类模板中的模板参数列表 可以指定默认参数
p.showPerson();
}
int main() {
test01();
test02();
system("pause");
return 0;
}
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
当类模板碰到继承时,需要注意一下几点:
- 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定出父类中T的类型,子类也需变为类模板
template<class T>
class Base
{
T m;
};
//class Son:public Base //错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
class Son :public Base<int> //必须指定一个类型
{
};
void test01()
{
Son c;
}
//类模板继承类模板 ,可以用T2指定父类中的T类型
template<class T1, class T2>
class Son2 :public Base<T2>
{
public:
Son2()
{
cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;
}
};
void test02()
{
Son2<int, char> child1;
}
int main() {
test01();
test02();
system("pause");
return 0;
}
二、STL之容器
1. STL六大组件
STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
- 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据。
- 算法:各种常用的算法,如sort、find、copy、for_each等
- 迭代器:扮演了容器与算法之间的胶合剂。
- 仿函数:行为类似函数,可作为算法的某种策略。
- 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
- 空间配置器:负责空间的配置与管理。
2.string
string出的对象不是字符串的首地址,和字符指针和字符数组有区别的,其实例出来的对象实际代表字符串本身,所以我们在sizeof时,不是其地址的长度,而是string类型的固定长度28字符。
string s1; //创建空字符串,调用无参构造函数
const char* str = "hello world";
string s2(str); //把c_string转换成了string
string s3(s2); //调用拷贝构造函数
string s4(10, 'a'); //s4赋值10个a
str1 = "hello world";
str2 = str1;
str3 = 'a';
str4.assign("hello c++");
str7.assign(5, 'x');
str3.append(" love ");
str3.append("game abcde", 4); //把字符串s的前n个字符连接到当前字符串结尾
pos = str1.find("de");//vector独有
pos = str1.rfind("de");从后往前找到str1第一个de的位置
str1.replace(1, 3, "1111"); //替换从pos开始n个字符为字符串str
int ret = s1.compare(s2);,比较s1和s2是否相同
s1.at(1)
s1[1]
str.insert(1, "111");,
str.erase(1, 3); //从1号位置开始3个字符
string subStr = str.substr(1, 3)
string和char和int类型转换
char*/char[]直接可以转换成string:
char c[]={“1234”};
string ss=c;
但string不能转换成char*/char[],必须用.c_str()函数:
string s="4568";
const char *c=s.c_str();
我们把字符指针转换成数字atoi,atoi把字符指针和字符数组转换成数字:
string s="4568";
const char *c=s.c_str();
int i=atoi(c);
数字转换成字符串
int a = 1000;
char c1[10];
_itoa_s(a, c1, 10);
string ss = c1;
cout << ss;
2.vector
实例化
vector<int> v1; //无参构造
vector<int>v2(v1)
vector<int>v2=v1
vector<int>v3(10,100)
vector<int>v4(v.begin(),v.end())
赋值
v2 = v1;
v3.assign(v1.begin(), v1.end());
v4.assign(10, 100);
有关vector容量的成员函数
empty(); ` //判断容器是否为空
capacity();` //容器的容量
size();` //返回容器中元素的个数
resize(int num);` //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
resize(int num, elem);` //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除
v1.push_back(10);
v1.pop_back();
v1.erase(v1.begin(), v1.end());
v1.clear();
v1.insert(v1.begin(), 100);
v1.swap(v2);
v.resize(3);
v1.at(i)
v1[i]
v1.front()
v1.back()
读取vector的每个元素
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
3.deque
vector有的成员函数deque都有
deque独有的成员函数(对头部插入和删除实现容易)
d.push_front(100);//可以两端操作
d.pop_front()
4.list
list数据读取不支持随机读取,不支持insert。
front/back返回容器中的值,而begin和end返回的是其指针。
list<int>::iterator it = L1.begin();
L1.front()
L1.end()
L1.at(1)//不支持
排序
L.reverse();
L.sort();
5.set和multiset
set集合不能存放重复的元素,multiset可以存放重复的元素。
独有的成员函数
- find(key);` //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回
- count(key);` //set一个或没有
6.map
map和multimap区别:
- map不允许容器中有重复key值元素
- multimap允许容器中有重复key值元素
map的迭代器有两个值it->first,it->second
#include <map>
class MyCompare {
public:
bool operator()(int v1, int v2) {
return v1 > v2;
}
};
void test01()
{
//默认从小到大排序
//利用仿函数实现从大到小排序
map<int, int, MyCompare> m;
m.insert(make_pair(1, 10));
m.insert(make_pair(2, 20));
m.insert(make_pair(3, 30));
m.insert(make_pair(4, 40));
m.insert(make_pair(5, 50));
for (map<int, int, MyCompare>::iterator it = m.begin(); it != m.end(); it++) {
cout << "key:" << it->first << " value:" << it->second << endl;
}
}
int main() {
test01();
system("pause");
return 0;
}
7.stack
功能描述:栈容器常用的对外接口
构造函数:
stack<T> stk;
//stack采用模板类实现, stack对象的默认构造形式stack(const stack &stk);
//拷贝构造函数
赋值操作:
stack& operator=(const stack &stk);
//重载等号操作符
数据存取:
push(elem);
//向栈顶添加元素pop();
//从栈顶移除第一个元素top();
//返回栈顶元素
大小操作:
empty();
//判断堆栈是否为空size();
//返回栈的大小
#include <stack>
//栈容器常用接口
void test01()
{
//创建栈容器 栈容器必须符合先进后出
stack<int> s;
//向栈中添加元素,叫做 压栈 入栈
s.push(10);
s.push(20);
s.push(30);
while (!s.empty()) {
//输出栈顶元素
cout << "栈顶元素为: " << s.top() << endl;
//弹出栈顶元素
s.pop();
}
cout << "栈的大小为:" << s.size() << endl;
}
int main() {
test01();
system("pause");
return 0;
}
8. queue 常用接口
功能描述:栈容器常用的对外接口
构造函数:
queue<T> que;
//queue采用模板类实现,queue对象的默认构造形式queue(const queue &que);
//拷贝构造函数
赋值操作:
queue& operator=(const queue &que);
//重载等号操作符
数据存取:
push(elem);
//往队尾添加元素pop();
//从队头移除第一个元素back();
//返回最后一个元素front();
//返回第一个元素
大小操作:
empty();
//判断堆栈是否为空size();
//返回栈的大小
#include <queue>
#include <string>
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
void test01() {
//创建队列
queue<Person> q;
//准备数据
Person p1("唐僧", 30);
Person p2("孙悟空", 1000);
Person p3("猪八戒", 900);
Person p4("沙僧", 800);
//向队列中添加元素 入队操作
q.push(p1);
q.push(p2);
q.push(p3);
q.push(p4);
//队列不提供迭代器,更不支持随机访问
while (!q.empty()) {
//输出队头元素
cout << "队头元素-- 姓名: " << q.front().m_Name
<< " 年龄: "<< q.front().m_Age << endl;
cout << "队尾元素-- 姓名: " << q.back().m_Name
<< " 年龄: " << q.back().m_Age << endl;
cout << endl;
//弹出队头元素
q.pop();
}
cout << "队列大小为:" << q.size() << endl;
}
int main() {
test01();
system("pause");
return 0;
}
9.总结
vector /map/string 共有的库函数 swap、size、empty、erase
vector是不方便头部操作的,所以没有front_pop/front_push函数
deque除了么有find、rfind函数,其他基本上都有
list,访问中间元素困难,所以没有insert,at(i),list.[i]等函数
multiset、set一个可以存放重复元素一个不行
map/mulitmap每个元素都有first和second
另外list、multiset/set,map/mulitmap没有assign函数。
三、STL常用算法
1.函数对象
1.概念
- 重载函数调用操作符的类,其对象常称为函数对象
- 函数对象使用重载的()时,行为类似函数调用,也叫仿函数
2.谓词的概念
概念:
- 返回bool类型的仿函数称为谓词
- 如果operator()接受一个参数,那么叫做一元谓词
- 如果operator()接受两个参数,那么叫做二元谓词
#include <vector>
#include <algorithm>
//1.一元谓词
struct GreaterFive{
bool operator()(int val) {
return val > 5;
}
};
void test01() {
vector<int> v;
for (int i = 0; i < 10; i++)
{
v.push_back(i);
}
vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
if (it == v.end()) {
cout << "没找到!" << endl;
}
else {
cout << "找到:" << *it << endl;
}
}
int main() {
test01();
system("pause");
return 0;
}
3.一些内部仿函数
运算仿函数
template<class T> T plus<T>
//加法仿函数template<class T> T minus<T>
//减法仿函数template<class T> T multiplies<T>
//乘法仿函数template<class T> T divides<T>
//除法仿函数template<class T> T modulus<T>
//取模仿函数template<class T> T negate<T>
//取反仿函数
比较仿函数
template<class T> bool equal_to<T>
//等于template<class T> bool not_equal_to<T>
//不等于template<class T> bool greater<T>
//大于template<class T> bool greater_equal<T>
//大于等于template<class T> bool less<T>
//小于template<class T> bool less_equal<T>
//小于等于
逻辑仿函数
template<class T> bool logical_and<T>
//逻辑与template<class T> bool logical_or<T>
//逻辑或template<class T> bool logical_not<T>
//逻辑非
#include <vector>
#include <functional>//内部仿函数的头文件
#include <algorithm>
void test01()
{
vector<bool> v;
v.push_back(true);
v.push_back(false);
v.push_back(true);
v.push_back(false);
for (vector<bool>::iterator it = v.begin();it!= v.end();it++)
{
cout << *it << " ";
}
cout << endl;
//逻辑非 将v容器搬运到v2中,并执行逻辑非运算
vector<bool> v2;
v2.resize(v.size());
transform(v.begin(), v.end(), v2.begin(), logical_not<bool>());
for (vector<bool>::iterator it = v2.begin(); it != v2.end(); it++)
{
cout << *it << " ";
}
cout << endl;
}
int main() {
test01();
system("pause");
return 0;
}
2.STL常用算法
1.遍历算法
for_each(iterator beg, iterator end, _func);
_func函数对象(重载运算符的类)
transform(iterator beg1, iterator end1, iterator beg2, _func);
2.查找算法
算法简介:
找不到元素时,返回v.end(),end作为容器节点不指向任何元素。
find
//查找元素find_if
//按条件查找元素adjacent_find
//查找相邻重复元素binary_search
//二分查找法count
//统计元素个数count_if
//按条件统计元素个数
函数原型:
find(iterator beg, iterator end, value);
// 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
函数原型:
find_if(iterator beg, iterator end, _Pred);
// 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
函数原型:
adjacent_find(iterator beg, iterator end);
// 查找相邻重复元素,返回相邻元素的第一个位置的迭代器
函数原型:
bool binary_search(iterator beg, iterator end, value);
// 查找指定的元素,查到 返回true 否则false 注意: 在无序序列中不可用
#include <algorithm>
#include <vector>
class Greater4
{
public:
bool operator()(int val)
{
return val >= 4;
}
};
int main() {
int num = count_if(v.begin(), v.end(), AgeLess35());
vector<int>::iterator it = adjacent_find(v.begin(), v.end());
system("pause");
return 0;
}
3.排序算法
算法简介:
sort(iterator beg, iterator end, _Pred);
//对容器内元素进行排序random_shuffle(iterator beg, iterator end);
//洗牌 指定范围内的元素随机调整次序merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
// 合并的两个容器必须的有序序列,容器元素合并,并存储到另一容器中reverse(iterator beg, iterator end)
// 反转指定范围的元素
sort(v.begin(), v.end());
sort(v.begin(), v.end(), greater<int>());```
random_shuffle(v.begin(), v.end());
merge(v1.begin(), v1.end(), v2.begin(), v2.end(), vtarget.begin());
for_each(vtarget.begin(), vtarget.end(), myPrint());
4.cope和replace
-
copy(iterator beg, iterator end, iterator dest);
-
replace(iterator beg, iterator end, oldvalue, newvalue);
-
replace_if(iterator beg, iterator end, _pred, newvalue);
-
swap(container c1, container c2);
v2.resize(v1.size());
copy(v1.begin(), v1.end(), v2.begin());
replace(v.begin(), v.end(), 20,2000);
replace_if(v.begin(), v.end(), ReplaceGreater30(), 3000);
swap(v1, v2);
5.accumulate和fill
-
accumulate(iterator beg, iterator end, value);
value起始值 -
fill(iterator beg, iterator end, value);
int total = accumulate(v.begin(), v.end(), 0);
fill(v.begin(), v.end(), 100);
6.集合算法(两个集合必须是有序序列)
// 注意:两个集合必须是有序序列而且返回值都是目标容器的迭代器最后地址
-
set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
-
set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
-
set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);
vector<int> vTarget;
//取两个里面较小的值给目标容器开辟空间
vTarget.resize(min(v1.size(), v2.size()));
//返回目标容器的最后一个元素的迭代器地址
vector<int>::iterator itEnd =
set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
——————————————————————————————————————————————————————————————————————————————————
vTarget.resize(v1.size() + v2.size());
//返回目标容器的最后一个元素的迭代器地址
vector<int>::iterator itEnd =
set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
//取两个里面较大的值给目标容器开辟空间
vTarget.resize( max(v1.size() , v2.size()));
————————————————————————————————————————————————————————————————————————————————————————————————————————
//返回目标容器的最后一个元素的迭代器地址
vector<int> vTarget;
vector<int>::iterator itEnd =
set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
for_each(vTarget.begin(), itEnd, myPrint());
cout << endl;
四、其他
1.输入函数和字符函数
1.cin可以读取缓冲区的内容,如果缓冲区的下一个字符是空格或缓冲则跳个这个字符。
2.我们在cin>>c,c是char类型,我们可能会按下aaaaaaaaaa’n’,c读取a,但还有aaaaaaaaa’n’在缓冲中,这是我们执行getline(cin,str)时读取的是aaaaaaaaa’n’,所以我们在执行getline前,先执行cin.ignore(1024.‘n’),将缓冲区的内容清除后在读取新输入的数据。
int i;
cin>>i; //输入45,i的值就是int类型45,我们在命令窗口输入字符(串)类型45,编译器已经做了字符转换成int类型的 45了
string input;
getline(cin,input);//getline(cin, str) 可以存放“ ”往input中
cin.ignore(int intExp, char chExp);//将缓冲区的内容清除,如果缓冲区内容达到intexp或者缓冲区出现chexp字符时,清空缓冲区;
float output=1.2224
cout <<setprecision(3)<<output <<endl;//存放3个有效数字
————————————————————————————————————————————————————————————————————————————————————————————————————
char *strcpy(char *dest, const char *src);
int strcmp(const char *str1, const char *str2)
size_t strlen(const char *str)//不包含‘/0’
char str1[100];
char str2[100];
int flg; //记录strcmp()的返回值
strcpy(str1, "abc"); //输入abc
strcpy(str2, "xyz"); //输入xyz
int l=strlen(str1);
flg=strcmp(str1, str2); //以ASCALL码比较
——————————————————————————————————————————————————————————————————————————————————————————————
void *memset(void *str, int c, size_t n)
//演示 strlen() 函数的用法
char str[10]={'