目录
思维导图:
学习内容:
1. Lambda表达式
1.1 基本概念
lambda表达式相当于在函数中定义一个轻量版函数,可以直接使用,也可以赋值给其他函数指针变量使用
1.2 定义格式
1> 格式 : [捕获列表](函数形参列表)[mutable] [->返回值类型]{函数体内容};
2> 解析
1、捕获列表:分为值捕获和引用捕获
值捕获:值捕获时,表达式中的数据和外界的数据属于不同的数据,并且在非mutable的lambda表达式中值捕获数据可读不可写
[变量1,变量2,。。。,变量n]:将这些数据都进行值捕获
[=]:将外界的变量全部进行值捕获
[=,&变量1,&变量2]:除了将变量1和变量2进行引用捕获外,其他都是值捕获
引用捕获:表达式中的数据和外界数据属于相同的数据,对数据操作时可读可写
[&变量1,&变量2,。。。,&变量n]:将这些数据都进行引用捕获
[&]:将外界的变量全部进行引用捕获
[&,变量1,变量2]:除了变量1和变量2是值捕获外,其他都是引用捕获
2、(函数形参列表):表达式的形参,跟函数形参格式一致,用于外界传递数据使用
3、mutable:该关键字修饰的lambda表达式,运行对值捕获的数据进行修改
4、->返回值类型:表示lambda表达式的返回值类型,如果不写,默认为void,此时如果表达式体内,写了return,则返回结果根据return后的类型而定
一般情况下省略
5、{}:中是函数体内容
6、表达式结果后,需要使用分号结束
#include <iostream>
using namespace std;
//外部定义
void show()
{
cout<<"hello world"<<endl;
}
//auto关键字的使用
void auto_test()
{
auto num = 520.0; //让系统自动推导变量的类型
//auto key = 1314, value = 20.0; //连续定义多个变量时,不能给定不同类型的初始值
//auto key; //使用auto自动推导时,必须指定初始值
auto ptr = # //此时会自动将ptr推导成指针类型
auto *qtr = # //跟上面的指针推导一样
cout<<"*ptr = "<<*ptr<<" *qtr = "<<*qtr<<endl;
cout<<"num = "<<num<<" sizeof(num) = "<<sizeof(num)<<" typeid(num) = "<<typeid (num).name()<<endl;
auto ref1 = num; //定义一个普通变量ref1,初始值为num
auto &ref2 = num; //定义了num的一个引用
//常用用法:一般用于循环中
for(auto i=0; i<10; i++)
{
cout<<"i = "<<i<<" ";
}
cout<<endl;
//注意:auto不能当作函数的形参使用
}
int main()
{
show(); //内部调用
cout<<"***************************"<<endl;
int num = 520;
int key = 1314;
int value = 999;
cout<<"main::&num = "<<&num<<endl;
cout<<"main::&key = "<<&key<<endl;
cout<<"main::&value = "<<&value<<endl;
#if 0
//定义lambda表达式:进行值捕获
//auto f = [num,key,value](string msg){ //auto f = [=](string msg){
auto f = [num,key,value](string msg)mutable{ //mutable关键字可以取消值捕获的数据的常属性
cout<<"Lambda::hello world:"<<msg<<endl;
num = 666; //值捕获的数据可读不可写
cout<<"num = "<<num<<endl;
cout<<"lambda::&num = "<<&num<<endl; //值捕获的数据和原数据不是同一个数据
cout<<"lambda::&key = "<<&key<<endl;
cout<<"lambda::&value = "<<&value<<endl;
};
#else
//定义lambda表达式:引用捕获
auto f = [&num,&key,&value](string msg){ //auto f = [&](string msg){
cout<<"Lambda::hello world:"<<msg<<endl;
num = 666; //引用捕获的数据可读可写
cout<<"num = "<<num<<endl;
cout<<"lambda::&num = "<<&num<<endl; //引用捕获和外界的数据是同一数据
cout<<"lambda::&key = "<<&key<<endl;
cout<<"lambda::&value = "<<&value<<endl;
return 0;
};
#endif
//调用函数
f("今天天气不错"); //跟普通函数调用一致
return 0;
}
1.3 常用情况
lambda表达式常用于算法的相关策略
#include <iostream>
#include<algorithm> //算法库
//定义全局函数作为策略
int comp(int a, int b)
{
return a>b; //前面的数据大于后面的数据
}
//定义仿函数当作策略
class comp1
{
public:
comp1() {}
int operator()(int a, int b)
{
return a>b;
}
};
using namespace std;
int main()
{
int arr[] = {3,7,2,1,4,9,3,6};
int len = sizeof(arr)/sizeof(arr[0]); //求数组容量
//将数组进行排序
//sort(arr, arr+len); //:如果不加策略,默认是升序排序
//sort(arr, arr+len, comp); //加了策略的排序函数:全局函数作为策略
//sort(arr, arr+len, comp1()); //仿函数当作执行策略
sort(arr, arr+len, [](int a, int b){return a>b;}); //lambda表达式当作策略
//输出数据
cout<<"数组中的数据为:";
for(auto val:arr)
{
cout<<val<<" ";
}
cout<<endl;
return 0;
}
二、异常处理
2.1 什么是异常处理
1> 异常就是程序执行过程中出现的问题
2> "异常"问题并不是经常出现
3> C语言中处理异常使用的是函数返回值完成,可以根据不同的异常返回不同的结果
4> C++中支持异常处理机制,完成一些不能使用返回值来完成的异常情况
2.2 何时使用异常处理
1> 异常处理主要解决进程同步过程中出现的异常情况,不能解决进程异步过程中的情况
2> 经常处理的错误:数组溢出、算数溢出、内存分配不足、指针越界、构造空间不足。。。
2.3 异常处理的格式
1、在可能产生异常的地方使用关键字:throw 抛出异常
2、try
{ 可能会抛出异常的语句 }
catch(接收异常的形参)
{ 处理异常 }
注意:任何函数在定义时,可以指定能抛出的异常格式如下
返回值类型 函数名(形参列表) throw(异常类型1,异常类型2,。。。。)
如果某个函数一定不会抛出异常,格式为
返回值类型 函数名(形参列表) noexcept
2.4 异常实例
#include <iostream>
using namespace std;
//定义两数相除的函数
double division(const double &op1, const double &op2) throw(string, double)
{
if(op2 == 0)
{
throw string("除数不能为0"); //抛出异常对象
}
//人为创建一个异常
if(op1 == op2)
{
throw 0.0;
}
return op1/op2;
}
int main()
{
//使用时将可能产生异常的代码放入到try中
try {
cout<<"结果为"<<division(1,1)<<endl;
cout<<"此处省略一万行代码"<<endl; //如果前面的代码产生异常了,后面的程序就不再执行
} catch (string &e) {
cout<<"异常信息:"<<e<<endl;
} catch(double &e)
{
cout<<"错误值为:"<<e<<endl;
}
cout<<"愿世界和平"<<endl;
return 0;
}
2.5 构造和析构中的异常
1> 关于析构的步骤
1、程序收到一个异常
2、初始化异常参数
3、将从对应的try语句块内处理异常,并调用析构函数完成对对象的析构
4、处理最后一个catch语句
2> 自定义异常类及测试析构函数在异常中的调用
#include <iostream>
using namespace std;
//自定义异常类
class MyException
{
public:
MyException() {}
MyException(string m):err_msg(m) {}
~MyException() {}
string what(){return err_msg;} //返回本次异常的错误信息
private:
string err_msg;
};
//定义一个测试类
class Demo
{
public:
Demo() {cout<<"Demo ::构造函数"<<endl;}
~Demo() {cout<<"Demo ::析构函数"<<endl;}
};
//定义全局函数
void fun() throw(MyException)
{
Demo d; //构造一个对象
throw(MyException("抛出异常")); //抛出自定义的异常
cout<<"此处省略一万行代码"<<endl;
}
int main()
{
try {
fun();
cout<<"---------"<<__LINE__<<"---------"<<endl;
} catch (MyException &e) {
cout<<"异常信息:"<<e.what()<<endl;
}
cout<<"______________愿世界和平_________________"<<endl;
return 0;
}
2.6 系统提供异常类
1> 系统提供的异常类都是由 exception 类派生出来的
2> 系统的异常一般如下
#include <iostream>
#include<exception>
using namespace std;
int main()
{
int arr[10] = {0};
int num;
try {
int index;
cin >> index;
if(index <0 || index >=10)
{
throw out_of_range("数组下标越界");
}
num = arr[index];
} catch (string &e) {
cout<<"异常信息: "<<e<<endl;
} catch(exception &e)
{
cout<<"异常信息: "<<e.what()<<endl;
}
cout<<"num = "<<num<<endl; //如果没有异常,则能够得到正确结果
cout<<"——————————————愿世界和平-----------"<<endl;
return 0;
}
三、C++中文件操作
3.1 文件流对象的介绍
1> C++中提供文件流对象,来完成对文件的操作
2> 对于文件的操作,使用的时 fstream 类的对象
3.2 关于文件的操作
1> 打开文件和关闭文件
1、使用fstream的有参构造函数来完成
2、使用fstream类调用无参构造实例一个文件对象 调用成员函数open来完成
#if 0
//方式1:使用构造函数打开文件
fstream file("./test.txt", ios_base::out|ios_base::in|ios_base::trunc);
//判断文件有没有打开
if(!file.is_open())
{
cout<<"文件打开失败"<<endl;
return -1;
}
cout<<"文件打开成功"<<endl;
//关闭文件
file.close(); //关闭文件
#else
//方式2:使用无参构造一个文件对象,调用成员函数打开文件
fstream file; //无参构造
file.open("./test.txt", ios_base::out|ios_base::in|ios_base::trunc); //打开文件
if(!file)
{
cerr<<"文件打开失败"<<endl;
return -1;
}
//关闭文件
file.close();
#endif
2> 读写内容
C++也提供了多种数据的读写
1、使用插入和提取运算符重载函数完成
#include <iostream>
#include<fstream> //文件流对象
using namespace std;
int main()
{
#if 0
//方式1:使用构造函数打开文件
fstream file("./test.txt", ios_base::out|ios_base::in|ios_base::trunc);
//判断文件有没有打开
if(!file.is_open())
{
cout<<"文件打开失败"<<endl;
return -1;
}
cout<<"文件打开成功"<<endl;
//关闭文件
file.close(); //关闭文件
#else
//方式2:使用无参构造一个文件对象,调用成员函数打开文件
fstream file; //无参构造
file.open("./test.txt", ios_base::out|ios_base::in|ios_base::trunc); //打开文件
if(!file)
{
cerr<<"文件打开失败"<<endl;
return -1;
}
//向文件中写入数据
file<<"hello world"<<endl; //使用运算符重载函数,向文件中写入一个字符串
file.put('A'); //调用成员函数向文件中写入一个字符
string str = "愿世界和平";
file.write(str.data(), str.length()); //使用成员函数向文件中写入一个字符串
//偏移文件光标
file.seekg(0, ios_base::beg); //lseek(fd, 0, SEEK_SET);
//从文件中读取数据
char ch = 0;
file.get(ch); //读取一个字符
cout<<"ch = "<<ch<<endl;
char buf[128] = "";
file.read(buf, 10); //从文件中读取一个字符串
cout<<"buf = "<<buf<<endl;
//关闭文件
file.close();
#endif
return 0;
}
四、C++中线程支持
4.1 线程支持类的引入
1> 需要引入类 thread
2> 所在头文件 #include
4.2 线程的相关使用
1> 创建线程:可以使用构造函数完成
注意:线程体函数,跟C语言的有所不同
也可以使用匿名对象完成构造
2> 线程体Id的获取:this_thread::get_id()
3> 线程资源回收:join()
4> 线程分离态:detach();
5> 线程体函数可以是全局函数、类中成员函数、仿函数、lambda表达式
#include <iostream>
#include<thread> //线程支持库
using namespace std;
//定义线程体函数
void task()
{
while(1)
{
cout<<"我是分支线程:"<<this_thread::get_id()<<endl; //求当前线程id号
this_thread::sleep_for(chrono::seconds(2));
}
}
//定义线程体2
void task1(const int &num, const string &msg)
{
cout<<"我是线程体2:"<<" num = "<<num<<" msg = "<<msg<<endl;
this_thread::sleep_for(chrono::seconds(5));
cout<<"线程体2退出"<<endl;
return;
}
int main()
{
//实现方式1:实例化一个无参构造线程对象
thread th; //无参构造
th = thread(task); //创建一个分支线程
//实现方式2:创建线程,向线程体内传参,使用有参构造完成
thread th1(task1, 520, "hello");
//lambda表达式当作线程体函数
thread th3([](){
while(1)
{
cout<<"我是线程体3"<<endl;
this_thread::sleep_for(chrono::seconds(1));
}
});
//此处为主线程
int num = 10;
while(num--)
{
cout<<"我是主线程"<<endl;
this_thread::sleep_for(chrono::seconds(1));
}
//判断线程体1
if(th.joinable())
{
//阻塞等待线程的结束
th.join();
//th.detach(); //将线程设置成分离态
}
if(th1.joinable())
{
th1.join();
}
if(th3.joinable())
{
th3.join();
}
cout<<"____________愿世界和平————————————————————————"<<endl;
return 0;
}
五、模板
5.1 模板的引入
1> 有时候程序性定义函数或者定义类时,可能由于参数的类型不同,导致相同功能的函数或者相同功能的类需要定义多个,造成程序的冗余
2> 此时我们就可以引入模板的概念:当调用函数时或者使用类进行实例化对象时,不仅将实参值传递过去,而且也要讲类型作为参数传递过去
3> 模板分为模板函数和模板类
5.2 模板函数
1> 所谓模板函数,就是在定义函数时,函数的参数的类型和参数值都不给定,等到函数调用时,根据传进来的实参的类型和值来确定该函数的具体实现
2> 定义格式
tamplate <typename 类型参数1,typename 类型参数2,typename 类型参数n>
类型 函数名(参数类型1 参数名1, 参数类型2,参数名2, 。。。)
{函数体内容}
3> 调用格式:
1、跟普通函数的调用一样(隐式调用)
2、调用函数时,在函数名后面使用<>给定类型参数(显式调用)
4> 同一个模板生命下,只能定义一个函数,如果要定义多个,需要声明多个模板
5> 显性调用时的原则:尖找尖 圆找圆
#include <iostream>
using namespace std;
/*
//两整数qiu和
int sum(int m, int n)
{
return m+n;
}
//两个小数求和
double sum(double m, double n)
{
return m+n;
}
//两个字符串求和
string sum(string m, string n)
{
return m+n;
}
*/
//定义模板函数
template<typename T>
T sum(T m, T n)
{
return m+n;
}
template<typename T>
T sum(T m, T n, T k)
{
return m+n+k;
}
//与上一个模板函数构成重载关系
template <class T1, class T2>
T2 sum(T1 m, T2 n)
{
return m+n;
}
int main()
{
cout << sum(3,7) << endl; //10 隐士调用
cout << sum<int, double>(3,7.5) << endl; //10.7 显示调用:尖找尖 圆找圆
cout << sum(string("3"),string("7")) << endl; //37
return 0;
}
5.3 模板函数的特化
1> 允许定义模板函数时,给某些参数指定类型,这样的模板就是特化模板
2> 当基础模板和特化模板同时存在时
如果时隐式调用函数,则调用的是基础模板
如果是显示调用函数,则调用的是特化模板
#include <iostream>
using namespace std;
//定义模板函数
template<typename T>
T sum(T m, T n)
{
cout<<"_______基础模板___________"<<endl;
return m+n;
}
//定义特化模板
template<typename T>
T sum(int m, int n)
{
cout<<"_______特化模板__________"<<endl;
return m+n;
}
int main()
{
sum(520, 1314); //模板函数的隐式调用时,如果基础模板和特化模板同时存在,则调用基础模板
sum<double>(520,1314); //模板函数的显示调用时,如果基础模板和特化模板同时存在,则调用特化模板
return 0;
}
5.4 模板类
1> 程序员在定义类的过程中,可能会因为类型的不同,导致同一功能的类,需要定义多个
例如:定义一个链表中的节点,由于数据域类型的不同,导致节点需要定义多个
2> 定义格式
tamplate <typename 类型参数1,typename 类型参数2,typename 类型参数n>
class 类名 { 类型 成员名; }
#include <iostream>
using namespace std;
//定义节点,使用模板类
template<typename T>
class Node
{
private:
T data; //数据域
public:
Node *next; //指针域
public:
Node() {}
Node(T d); //类内声明
~Node(); //类内声明
void show();
};
//内外定义时,用到类名时,必须显性调用,此时需要使用一个模板,重新定义模板即可
template<typename T1>
void Node<T1>::show()
{
cout<<"数据域为:"<<data<<endl;
}
//不同的成员函数类外定义,都需要重新定义模板
template<typename T>
Node<T>::Node(T d):data(d), next(nullptr)
{
}
template<typename T>
Node<T>::~Node()
{
}
int main()
{
Node<int> n1(520); //类模板必须显式调用
n1.show();
Node<double> n3(520.1); //类模板必须显式调用
n3.show();
Node<string> n2("hello");
n2.show();
Node<int> n4(1111); //类模板必须显式调用
n4.show();
Node<int> *ptr = &n1;
ptr->next = &n4;
return 0;
}
5.5 STL标准模板库(非常重要)
C++ STL (Standard Template Library标准模板库) 是通用类模板和算法的集合,它提供给程序员一些标准的数据结构的实现如 queues(队列), lists(链表), 和 stacks(栈)等.
C++ STL 提供给程序员以下三类数据结构的实现:
顺序结构
C++ Vectors
C++ Lists
C++ Double-Ended Queues
容器适配器
C++ Stacks
C++ Queues
C++ Priority Queues
联合容器
C++ Bitsets
C++ Maps
C++ Multimaps
C++ Sets
C++ Multisets
程序员使用复杂数据结构的最困难的部分已经由STL完成. 如果程序员想使用包含int数据的stack, 他只要写出如下的代码:
stack myStack;
接下来, 他只要简单的调用 push() 和 pop() 函数来操作栈. 借助 C++ 模板的威力, 他可以指定任何的数据类型,不仅仅是int类型. STL stack实现了栈的功能,而不管容纳的是什么数据类型.
5.5.1 vector动态数组
1> 常用函数
1、构造函数 vector(); //无参构造
vector( size_type num, const TYPE &val ); //有参构造,使用 num个val来构造一个
vector vector( const vector &from ); //拷贝构造
vector( input_iterator start, input_iterator end ); //使用一个容器的起始位置到终止为止的内容构造一个vector
2> 实例演示
#include <iostream>
#include<vector> //将头文件引入
using namespace std;
int main()
{
vector<int> v1; //定义一个存放整数的vector 无参构造
//判空函数 empty
if(v1.empty())
{
cout<<"v1 is empty"<<endl;
}else
{
cout<<"v1 is not empty"<<endl;
}
cout<<v1.size()<<endl; //求容器的实际大小 size
cout<<v1.capacity()<<endl; //求当前容器的最大容量 capacity
for(int i=0; i<20; i++)
{
v1.push_back(i+1); //向容器尾部进行放入数据 push_back
cout<<"size = "<<v1.size()<<" capacity = "<<v1.capacity()<<endl;
}
//遍历当前的容器 方式1
cout<<"默认容器中的数据分别时:";
for(auto val:v1)
{
cout<<val<<" ";
}
cout<<endl;
//遍历当前的容器 方式2
cout<<"默认容器中的数据分别时:";
for(unsigned int i=0; i<v1.size(); i++)
{
cout<<v1[i]<<" "; //该容器中重载了 [] 括号运算符
}
cout<<endl;
//遍历当前的容器 方式3
cout<<"默认容器中的数据分别时:";
for(unsigned int i=0; i<v1.size(); i++)
{
cout<<v1.at(i)<<" "; //访问成员函数 at
}
cout<<endl;
//访问第一个元素
cout<<"v1[0] = "<<v1[0]<<" "<<v1.at(0)<<" "<<v1.front()<<endl; //front函数
//访问最后一个元素
cout<<"最后一个元素:"<<v1.back()<<endl; //back函数
//删除最后一个元素
v1.pop_back(); //pop_back
cout<<"默认容器中的数据分别时:";
for(unsigned int i=0; i<v1.size(); i++)
{
cout<<v1.at(i)<<" "; //访问成员函数 at
}
cout<<endl;
//清空容器的内容
v1.clear();
cout<<v1.size()<<endl; //求容器的实际大小 size
cout<<v1.capacity()<<endl; //求当前容器的最大容量 capacity
cout<<"*************************************************"<<endl;
//使用有参构造,构造一个容器
vector<string> v2(5, "hello");
cout<<"此时容器中的数据分别时:";
for(auto val:v2)
{
cout<<val<<" ";
}
cout<<endl;
cout<<v2.size()<<endl; //求容器的实际大小 size 8
cout<<v2.capacity()<<endl; //求当前容器的最大容量 capacity
//向容器中增加数据
for(int i=0; i<20; i++)
{
v2.push_back("nihao"); //向容器尾部进行放入数据 push_back
cout<<"size = "<<v2.size()<<" capacity = "<<v2.capacity()<<endl;
}
cout<<"*************************************************"<<endl;
//使用一个容器的范围内的数据给另一个容器初始化
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
vector<int> v3(arr+2, arr+8); //将[arr+2,arr+8)的所有数据给v3初始化
cout<<"此时容器中的数据分别时:";
for(auto val:v3)
{
cout<<val<<" ";
}
cout<<endl;
v3.swap(v1); //交互两个容器的内容
for(auto val:v1)
{
cout<<val<<" ";
}
cout<<endl;
return 0;
}