类中特殊的关键字
1> explicit :该关键字修饰的类,不能进行隐式调用
当类中的构造函数只有一个形参时,可以进行隐士调用该构造函数 使用格式: 类名 对象名 = 形参类型变量;
如果该构造函数前加了该关键字,那么,就不允许上面的隐士调用,只能进行显式调用构造函数
2> delete:该关键字用于类中,可以表示将类中的某些成员函数删除
3> default:该关键字用于类中,可以表示使用该类中自动提供的某些特殊成员函数
4> noexcept:该关键字修饰的函数,表示在使用过程中一定不会抛出异常
#include <iostream>
using namespace std;
class MtClass
{
private:
string name;
int age;
public:
MtClass() = default; //表示使用系统提供的默认的无参构造
explicit MtClass(string n)noexcept:name(n) {} //不能再进行隐式调用了
explicit MtClass(int a):age(a) {}
MtClass(string n, int a):name(n), age(a) {}
MtClass(const MtClass &) = delete; //表示该类,不能在使用拷贝构造函数
~MtClass() {}
void show()
{
cout<<"name = "<<name<<" age = "<<age<<endl;
}
};
int main()
{
MtClass c1(20); //显式调用有参构造函数
MtClass c2("张三"); //显式调用有参构造
MtClass c4; //无参构造
//MtClass c3(c1); //类中删除了拷贝构造函数,所以不能进行该操作
// MtClass c3 = 18; //隐式调用
// MtClass c4 = string("李四"); //隐式调用
return 0;
}
list:链表
1> 常用函数
1、构造函数
list(); //无参构造
list( size_type count, const T& value, const Allocator& alloc = Allocator()); //使用count个元素进行初始化一个链表
explicit list( size_type count ); //构造一个链表,有count个随机元素
list( const list& other ); //拷贝构造
list( list&& other ); //移动构造
list( InputIt first, InputIt last, const Allocator& alloc = Allocator() ); //构造拥有范围 [first, last) 内容的容器。
2、back()函数返回一个引用,指向list的最后一个元素。
3、begin()函数返回一个迭代器,指向list的第一个元素
4、clear()函数删除list的所有元素。
5、empty()函数返回真(true)如果链表为空,否则返回假。
6、end()函数返回一个迭代器,指向链表的末尾。
7、iterator erase( iterator pos ); //删除指定位置的元素
iterator erase( iterator start, iterator end ); //删除范围内的元素
8、 front()函数返回一个引用,指向链表的第一个元素
9、 iterator insert( iterator pos, const TYPE &val ); //在指定位置前插入一个元素
void insert( iterator pos, size_type num, const TYPE &val ); //在指定位置前插入num个元素
void insert( iterator pos, input_iterator start, input_iterator end ); //在指定位置前插入给定区间内的元素
10、pop_back()函数删除链表的最后一个元素。
11、 pop_front()函数删除链表的第一个元素。
12、push_back()将val连接到链表的最后
13、push_front()函数将val连接到链表的头部。
14、void remove( const TYPE &val ); //删除链表中所有值为val的元素
15、reverse()函数把list所有元素倒转。
16、 void sort(); //默认从小到大升序排序
void sort( Comp compfunction ); //加策略的排序
17、 unique()函数删除链表中所有重复的元素
2> 链表实现实例
#include <iostream>
#include<list> //链表头文件
using namespace std;
int main()
{
list<int> l1; //定义一个存储整形数据的链表 无参构造
list<int> l2(3,520); //定义一个存储整形数据的链表,初始化为3个 520 有参构造
int arr[5] = {1,2,3,4,5};
list<int> l3(arr, arr+5); //定义一个存储整形数据的链表,初始化为一个数组中的内容
cout<<l3.size()<<endl; //求大小
cout<<l3.max_size()<<endl; //计算机能给链表分配的总大小
cout<<l3.front()<<endl; //访问第一个元素
l3.front() = 666; //可读可写
cout<<l3.back()<<endl; //最后一个元素 可读可写
l3.push_front(520); //头插
l3.push_back(1314); //尾插
for(auto val:l3)
{
cout<<val<<" ";
}
cout<<endl;
l3.pop_back(); //尾删
l3.pop_front(); //头删
l3.insert(l3.begin(), 100); //在第一个位置前添加一个元素
l3.insert(l3.end(), 10000); //在最后一个位置前添加一个元素
for(auto val:l3)
{
cout<<val<<" ";
}
cout<<endl;
l3.remove(666); //将链表中为666的元素删除
l3.reverse(); //将链表翻转
for(auto val:l3)
{
cout<<val<<" ";
}
cout<<endl;
l3.sort(); //将链表排序
for(auto val:l3)
{
cout<<val<<" ";
}
return 0;
}
智能指针
一、引入背景
1> C++中在堆区申请空间和释放空间需要使用new和delete完成
2> 多个指针指向同一个内存空间,释放其中一个的空间,另一个还在继续使用(悬空指针)
3> 只是申请了内存空间,使用后忘记释放内存空间,堆区对象没有得到析构
4> 栈取申请的对象空间,在脱离对象空间后,会自动调用析构函数完成资源的回收,但是堆区的不会自动释放
#include <iostream>
using namespace std;
class Test
{
private:
string name;
public:
Test() {cout<<"Test::无参构造"<<endl;}
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;}
~Test() {cout<<name<<"::析构函数"<<endl;}
};
int main()
{
Test *p = new Test("张飞");
cout << "Hello World!" << endl;
return 0;
}
效果图
5> 总结:上述例子中,在堆区申请一个对象空间,但是没有手动释放空间,造成对象没有析构,为了保证堆区空间使用的安全性,我们引入了智能指针,目的是更加安全的使用堆区空间。
二、智能指针的种类
1> C++11标准提供了两种智能指针,分别是unique_ptr(独占智能指针)、shared_ptr(共享智能指针)
2> 除了上述两种指针外,还有auto_ptr(自动智能指针,已弃用)、weak_ptr(弱智能指针,辅助shared_ptr作用)
3> 以上所有智能指针都在头文件:#include<memory>中
4> 智能指针是类模板,在栈区创建智能指针对象
5> 把普通指针交给智能指针管理
6> 智能指针对象过期时,析构函数会自动释放智能指针管理的指针空间
三、unique_ptr(独占智能指针)
3.1 独占智能指针的概述
独占智能指针会“拥有”它所指向的对象,某一时刻,只能有一个unique_ptr指向给定的对象,当该指针被销毁时,指向的对象也会随之释放。
3.2 类的原型
template <typename T, typename D = default_delete<T>>
class unique_ptr
{
public:
explicit unique_ptr(pointer p) noexcept; // 不可用于转换函数。
~unique_ptr() noexcept;
T& operator*() const; // 重载*操作符。
T* operator->() const noexcept; // 重载->操作符。
unique_ptr(const unique_ptr &) = delete; // 禁用拷贝构造函数
unique_ptr& operator=(const unique_ptr &) = delete; // 禁用赋值函数
unique_ptr(unique_ptr &&) noexcept; // 右值引用。
unique_ptr& operator=(unique_ptr &&) noexcept; // 右值引用
private:
pointer ptr; // 内置的指针。
};
3.3 初始化
方法一:
unique_ptr<AA> p0(new AA("西施"));// 分配内存并初始化。
方法二:
unique_ptr<AA> p0 = make_unique<AA>("西施"); // C++14标准。
方法三:
AA* p = new AA("西施");
unique_ptr<AA> p0(p); // 用已存在的地址初始化。
1> 方法三
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
int main()
{
Test *p1 = new Test("张三"); //在堆区申请空间
unique_ptr<Test> up1(p1); //使用原始指针实例化一个指针指针
cout<<"name = "<<(*p1).name<<endl; //指针找到对象使用
cout<<"name = "<<p1->name<<endl; //原始指针直接使用
cout<<"name = "<<(*up1).name<<endl; //智能指针访问
cout<<"name = "<<up1->name<<endl; //智能指针访问
cout << "********程序到此结束!********" << endl;
return 0;
}
2> 使用方式1:
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
int main()
{
unique_ptr<Test> up1(new Test("张三")); //使用原始指针实例化一个指针
cout<<"name = "<<(*up1).name<<endl; //智能指针访问
cout<<"name = "<<up1->name<<endl; //智能指针访问
cout << "********程序到此结束!********" << endl;
return 0;
}
3> 使用方法2只有C++14标准后才能使用,C++11标准不支持
3.4 独占智能指针的注意事项
1> 重载的*和->运算符重载,所以可以跟像使用普通指针一样使用该智能指针
2> 不支持拷贝构造和拷贝赋值函数
3> 不要使用一个原始指针初始化多个unique_ptr
4> get()函数可以返回该智能指针的原始指针
5> 不要使用unique_ptr指向非new申请的空间
6> 有智能指针指向原始指针后,就不要再用原始指针手动释放了
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
int main()
{
Test *p1 = new Test("张三");
unique_ptr<Test> up1(p1); //使用原始指针实例化一个指针指针
unique_ptr<Test> up2(p1); //使用一个原始指针实例化多个智能指针
cout<<"name = "<<(*up1).name<<endl; //智能指针访问
cout<<"name = "<<up1->name<<endl; //智能指针访问
cout << "********程序到此结束!********" << endl;
return 0;
}
7> 智能指针作为函数参数传递时,传递引用,不要传递值,因为智能指针没有拷贝构造函数
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
//void fun(unique_ptr<Test> up) //定义时不会报错,但是使用时会报错
void fun(unique_ptr<Test> &up)
{
}
int main()
{
Test *p1 = new Test("张三");
unique_ptr<Test> up1(p1); //使用原始指针实例化一个指针指针
cout<<"name = "<<(*up1).name<<endl; //智能指针访问
cout<<"name = "<<up1->name<<endl; //智能指针访问
//调用函数传递智能指针的值
fun(up1); //报错,会告诉没有拷贝构造函数
cout << "********程序到此结束!********" << endl;
return 0;
}
8> 智能指针不支持:+、-、++、--
3.5 智能指针的更多技巧
1> 将一个智能指针unique_ptr赋值给另一个指针时,可以赋值右值(移动构造存在),但是不能赋值左值(拷贝构造不存在)
2> 使用nullptr给unique_ptr赋值时,会自动释放对象
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
int main()
{
Test *p1 = new Test("张三");
unique_ptr<Test> up1(p1); //使用原始指针实例化一个指针指针
cout<<"name = "<<(*up1).name<<endl; //智能指针访问
cout<<"name = "<<up1->name<<endl; //智能指针访问
up1 = nullptr; //用空指针给智能指针赋值时,会释放智能指针指向的对象
cout << "********程序到此结束!********" << endl;
return 0;
}
3> 使用release()函数,可以释放unique_ptr的指针,并且将原指针返回
4> std::move函数,可以转移原始指针的控制权,本质上调用的时移动构造函数
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
//函数1需要一个指针,但是不对这个指针负责
void fun1(const Test *p)
{
cout<<"fun1::"<<p->name<<endl;
}
//函数2需要一个指针,并且对这个指针负责
void fun2(Test *p)
{
cout<<"fun2::"<<p->name<<endl;
delete p;
}
//函数3需要一个智能指针,但是不对这个指针负责
void fun3(unique_ptr<Test> &up)
{
cout<<"fun3::"<<up->name<<endl;
}
//函数4需要一个智能指针,并对这个指针负责
void fun4(unique_ptr<Test> up)
{
cout<<"fun3::"<<up->name<<endl;
}
int main()
{
unique_ptr<Test> up1(new Test("张飞")); //在堆区空间实例化一个对象,托管到智能指针
cout<<"函数调用开始"<<endl;
// fun1(up1.get()); //调用功能1函数,不会对指针负责
// fun2(up1.release()); //调用功能2函数,会对指针负责
// fun3(up1); //需要使用智能指针的引用,函数内不会对指针负责
fun4(move(up1)); //使用一个智能指针,函数内对指针负责
cout << "********程序到此结束!********" << endl;
return 0;
}
5> reset()函数释放对象
函数原型:void reset(pointer __p = pointer())
使用方法:
up->reset(); //释放up对象指向的资源
up->reset(nullptr); //释放up对象指向的资源
up->reset(new Test("夏侯")); //释放up对原对象的资源,并指向新对象
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
int main()
{
unique_ptr<Test> up1(new Test("张飞")); //在堆区空间实例化一个对象,托管到智能指针
cout<<"函数调用开始"<<endl;
//up1.reset();
up1.reset(new Test("夏侯")); //释放原来对象的空间,指向新的空间
cout << "********程序到此结束!********" << endl;
return 0;
}
6> swap()函数交换两个unique_ptr的使用权
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
int main()
{
unique_ptr<Test> up1(new Test("张飞")); //在堆区空间实例化一个对象,托管到智能指针
unique_ptr<Test> up2(new Test("夏侯"));
cout<<"函数调用开始"<<endl;
up1.swap(up2); //调用两个指针的使用权
cout<<"up1->name = "<<up1->name<<endl; //夏侯
cout<<"up2->name = "<<up2->name<<endl; //张飞
cout << "********程序到此结束!********" << endl;
return 0;
}
7> unique_ptr也可以有原始指针的特性,当指向一个继承体系内的基类对象时,也有多态的特性,如同原始指针管理基类对象和派生类对象一样
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
virtual void show()
{
cout<<"我是父类"<<endl;
}
};
class Demo:public Test
{
public:
void show() override
{
cout<<"我是子类"<<endl;
}
};
int main()
{
unique_ptr<Test> up1(new Demo); //父类指针指向子类对象
cout<<"函数调用开始"<<endl;
up1->show(); //调用子类中重写的父类的虚函数
cout << "********程序到此结束!********" << endl;
return 0;
}
8> 智能指针也不是绝对安全的,当程序内有exit()退出程序时,全局智能指针会释放空间,但是局部智能指针不会
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
virtual void show()
{
cout<<"我是父类"<<endl;
}
};
int main()
{
unique_ptr<Test> up1(new Test("张飞")); //父类指针指向子类对象
cout<<"函数调用开始"<<endl;
exit(0); //手动退出程序
cout << "********程序到此结束!********" << endl;
return 0;
}
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
virtual void show()
{
cout<<"我是父类"<<endl;
}
};
//全局定义智能指针
unique_ptr<Test> up1(new Test("张飞")); //父类指针指向子类对象
int main()
{
cout<<"函数调用开始"<<endl;
exit(0); //手动退出程序
cout << "********程序到此结束!********" << endl;
return 0;
}
9> unique_ptr提供了支持数组的具体化版本数组版本的unique_ptr,重载了[]运算符,返回的是引用,可以作为左值使用
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
int main()
{
//1、普通指针指向数组的使用
// Test *p = new Test[2];
Test *p = new Test[2]{string("张飞"), string("夏侯")};
// p[0].name = "张飞";
// p[1].name = "夏侯";
// cout<<"p[0].name = "<<p[0].name<<" p[1].name = "<<p[1].name<<endl;
// delete [] p;
//2、使用智能指针指向数组
unique_ptr<Test[]> up(new Test[2]); //类型后需要加一个空的中括号
//unique_ptr<Test[]> up(new Test[2]{string("张飞"), string("夏侯")});
up[0].name = "张飞";
up[1].name = "夏侯";
cout<<"up[0].name = "<<up[0].name<<" up[1].name = "<<up[1].name<<endl;
cout << "********程序到此结束!********" << endl;
return 0;
}
四、shared_ptr(共享智能指针)
4.1 概述
shared_ptr共享他指向的对象,多个共享指针可以指向(关联)相同的对象,在内部采用计数器机制来实现
当新的shared_ptr与对象关联时,引用计数器增加1
当shared_ptr超出作用域时,引用计数器减1
当引用计数器变为0时,则表示没有任何shared_ptr与对象关联,则释放该对象
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
int main()
{
Test *p = new Test("张飞"); //使用原始指针指向一个对象
shared_ptr<Test> sp1(p); //构造一个共享智能指针
cout<<"sp1.use_count = "<<sp1.use_count()<<endl; //显式当前指针的引用计数器 1
cout << "********程序到此结束!********" << endl;
return 0;
}
4.2 基本使用
shared_ptr的构造函数也是explicit,但是没有删除拷贝构造函数和拷贝赋值函数
1> 初始化
方法一:
shared_ptr<AA> p0(new AA("西施"));// 分配内存并初始化。
方法二:
shared_ptr<AA> p0 = make_share<AA>("西施"); // C++11标准,推荐使用,效率更高
方法三:
AA* p = new AA("西施");
shared_ptr<AA> p0(p); // 用已存在的地址初始化。
方法四:
shared_ptr<AA> p0(new AA("西施"));// 分配内存并初始化。
shared_ptr<AA> p1(p0); //拷贝构造
shared_ptr<AA> p2 = p0; //拷贝构造
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
int main()
{
Test *p = new Test("张飞"); //使用原始指针指向一个对象
shared_ptr<Test> sp1(p); //构造一个共享智能指针
cout<<"sp1.use_count = "<<sp1.use_count()<<endl; //显式当前指针的引用计数器 1
shared_ptr<Test> sp2(sp1); //拷贝构造
shared_ptr<Test> sp3 = sp2; //拷贝构造
cout<<"sp1.use_count = "<<sp1.use_count()<<endl; //显式当前指针的引用计数器 3
cout<<"sp2.use_count = "<<sp2.use_count()<<endl; //显式当前指针的引用计数器 3
cout<<"sp3.use_count = "<<sp3.use_count()<<endl; //显式当前指针的引用计数器 3
cout << "********程序到此结束!********" << endl;
return 0;
}
2> 使用方法
1、智能指针重载了*和->操作符,可以像使用原始指针一样使用shared_ptr
2、use_count()函数返回引用计数器的个数
3、unique()函数,如果use_count()为1,返回true,否则返回false
4、支持普通的拷贝和赋值,左值的shared_ptr的计数器减1,右值的shared_ptr的计数器将加1
left = right;
5、get()函数返回原始指针
6、不要用同一个原始指针区初始化多个shared_ptr
7、不要使用shared_ptr管理不是new分配的空间内存
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
int main()
{
shared_ptr<Test> spa1(new Test("张飞")); //构造一个共享智能指针
shared_ptr<Test> spa2(spa1); //拷贝构造
shared_ptr<Test> spa3 = spa2; //拷贝构造
cout<<"spa1.use_count = "<<spa1.use_count()<<endl; //张飞的指针个数 3
shared_ptr<Test> spb1(new Test("夏侯")); //构造夏侯的指针
shared_ptr<Test> spb2(spb1);
cout<<"spb1.use_count = "<<spb1.use_count()<<endl; //夏侯的引用计数器 2
spa2 = spb2; //夏侯的引用计数器将加1,张飞的引用计数器减1
cout<<"spa1.use_count = "<<spa1.use_count()<<endl; //张飞的指针个数 2
cout<<"spb1.use_count = "<<spb1.use_count()<<endl; //夏侯的引用计数器 3
cout << "********程序到此结束!********" << endl;
return 0;
}
3> 用于函数参数
跟unique_ptr一样
4> 不支持指针的运算:+、-、++、--
4.3 更多细节
1> 使用nullptr给shared_ptr赋值时,将计数器减1,如果计数器为0,释放对象资源,空的shared_ptr = nullptr;
2> str::move() 可以转移对原始指针的控制权,还可以将unique_ptr转变成shared_ptr
3> reset()改变与资源的关联关系
pp.reset(); //解除与资源的关系,资源的引用计数器减1
pp.reset(new AA("bbb")); //解除与资源的关系,引用计数器减1,关联新资源
4> swap()函数交换两个shared_ptr的控制权
5> shared_ptr也可以像普通指针那样,完成多态的性质,使用方式跟原始指针一致
6> shared_ptr不是绝对安全的,如果程序中调用了exit()退出,全局的shared_ptr会自动释放,局部的shared_ptr无法释放
7> shared_ptr提供的智能指针数组的具体化版本,重载了[],返回值是一个引用,可以作为左值使用
8> shared_ptr的线程安全性:
shared_ptr的引用计数器是线程安全的(引用计数器是原子操作)
多个线程同时读一个shared_ptr对象是线程安全的
如果多个线程对同一个shared_ptr对象进行读和写,则需要加锁
多线程读写shared_ptr所指向的同一个对象,不管是相同的shared_ptr对象,还是不同的shared_ptr对象,都要加锁进行保护
9> 实际开发过程中,unique_ptr能解决的问题,尽量不要使用shared_ptr,因为unique_ptr效率更高,所用资源更少
五、智能指针的删除器(了解)
在默认情况下,智能指针过期的时候,用 delete 原始指针;
释放管理的资源程序员也可以自定义删除器,改变智能指针释放资源的行为
删除器可以是全局函数、仿函数以及Lambda表达式,形参为原始指针
#include <iostream>
#include<memory>
using namespace std;
class Test
{
public:
string name; //名字
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
//全局函数作为删除器
void deletefunc(Test *p)
{
cout<<"自定义删除器:全局函数实现"<<endl;
delete p;
}
//定义仿函数作为删除器
class deleteclass
{
public:
void operator()(Test *p)
{
cout<<"仿函数实现删除器"<<endl;
delete p;
}
};
//定义lambda作为删除器
auto deleteLamb = [](Test *p){
cout<<"lambda表达式作为删除器"<<endl;
delete p;
};
int main()
{
//shared_ptr<Test> spa1(new Test("张飞")); //使用默认的删除器
/*shared_ptr<Test> spa1(new Test("张飞"),deletefunc);*/ //使用全局函数作为删除器
// shared_ptr<Test> spa1(new Test("张飞"),deleteclass()); //使用仿函数作为删除器
// shared_ptr<Test> spa1(new Test("张飞"),deleteLamb);
//独占智能指针的删除器的使用
// unique_ptr<Test, decltype (deletefunc)*> up1(new Test("夏侯"), deletefunc); //全局函数作为删除器
unique_ptr<Test, void (*)(Test *)> up1(new Test("夏侯"), deletefunc); //使用函数指针推导
// unique_ptr<Test, deleteclass> up1(new Test("夏侯"), deleteclass()); //仿函数作为删除器
// unique_ptr<Test, decltype (deleteLamb)> up1(new Test("夏侯"), deleteLamb); //lambda表达式作为删除器
cout << "********程序到此结束!********" << endl;
return 0;
}
六、weak_ptr(弱智能指针)
6.1 引入目的
shared_ptr内部维护了一个共享的引用计数器,多个shared_ptr可以指向同一个资源如果出现了循环引用的情况,引用计数器将永远无法归0,资源不会被释放
#include <iostream>
#include<memory>
using namespace std;
class Demo;
class Test
{
public:
string name; //名字
shared_ptr<Demo> sp; //共享智能指针
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
class Demo
{
public:
string name; //名字
shared_ptr<Test> sp; //共享智能指针
Demo() {cout<<name<<"::无参构造"<<endl;} //无参构造
Demo(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Demo() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
int main()
{
shared_ptr<Test> sp1(new Test("张飞"));
shared_ptr<Demo> sp2(new Demo("夏侯"));
//将他们的彼此的共享指针成员指向彼此
sp1->sp = sp2;
sp2->sp = sp1;
cout << "********程序到此结束!********" << endl;
return 0;
}
此时可以使用弱智能指针解决
#include <iostream>
#include<memory>
using namespace std;
class Demo;
class Test
{
public:
string name; //名字
weak_ptr<Demo> sp; //弱智能指针
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
class Demo
{
public:
string name; //名字
weak_ptr<Test> sp; //弱智能指针
Demo() {cout<<name<<"::无参构造"<<endl;} //无参构造
Demo(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Demo() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
int main()
{
shared_ptr<Test> sp1(new Test("张飞"));
shared_ptr<Demo> sp2(new Demo("夏侯"));
//将他们的彼此的共享指针成员指向彼此
sp1->sp = sp2;
sp2->sp = sp1;
cout << "********程序到此结束!********" << endl;
return 0;
}
6.2 weak_ptr是什么
1> weak_ ptr 是为了配合shared_ ptr 而引入的,它指向一个由shared_ ptr 管理的资源但不影响资源的生命周期。也就是说,将-个weak_ ptr 绑定到一个shared_ ptr 不会改变shared_ ptr 的引用计数。
2> 不论是否有weak_ ptr 指向,如果最后一个指向资源的shared_ ptr 被销毁,资源就会被释放。
3> weak_ ptr更像是shared_ ptr 的助手而不是智能指针。
6.3 如何使用weak_ptr
1> weak_ptr没有重载->和*运算符,不能直接访问资源
2> operator=(); 把shared_ptr或weak_ptr赋值给weak_ptr
3> expired(); 判断它指向的资源释放已经过期
4> lock(); 返回shared_ptr,如果资源已经过期,返回空的shared_ptr
5> reset(); 将当前的weak_ptr置空
6> swap(); 交换
7> weak_ptr不控制对象的生命周期,但是,它知道对象是否还活着
用lock()函数可用把它提升为shared_ptr,如果对象还活着,返回有效的shared_ptr,如果对象已经死了,提升会失败,返回一个空的shared_ptr 提升的行为(lock)是线程安全的
6.4 weak_ptr的使用
1> 单线程可用,多线程不安全版本
#include <iostream>
#include<memory>
using namespace std;
class Demo;
class Test
{
public:
string name; //名字
weak_ptr<Demo> sp; //弱智能指针
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
class Demo
{
public:
string name; //名字
weak_ptr<Test> sp; //弱智能指针
Demo() {cout<<name<<"::无参构造"<<endl;} //无参构造
Demo(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Demo() {cout<<name<<"::析构函数"<<endl;
} //析构函数
};
int main()
{
shared_ptr<Test> sp1 = make_shared<Test>("张飞"); //创建一个共享智能指针
{
shared_ptr<Demo> sp2 = make_shared<Demo>("夏侯"); //在局部空间创建一个新对象
//两个类对象的弱指针成员指向对方
sp1->sp = sp2;
sp2->sp = sp1;
if(sp1->sp.expired() == true)
{
cout<<"语句块内部:sp1->sp已经过期"<<endl;
}else
{
cout<<"语句块内部:sp1->sp.lock()->name = "<<sp1->sp.lock()->name<<endl;
}
}
if(sp1->sp.expired() == true)
{
cout<<"语句块外部:sp1->sp已经过期"<<endl;
}else
{
cout<<"语句块外部:sp1->sp.lock()->name = "<<sp1->sp.lock()->name<<endl;
}
cout << "********程序到此结束!********" << endl;
return 0;
}
2> 多线程安全版本
#include <iostream>
#include<memory>
using namespace std;
class Demo;
class Test
{
public:
string name; //名字
weak_ptr<Demo> sp; //弱智能指针
Test() {cout<<name<<"::无参构造"<<endl;} //无参构造
Test(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Test() {cout<<name<<"::析构函数"<<endl;} //析构函数
};
class Demo
{
public:
string name; //名字
weak_ptr<Test> sp; //弱智能指针
Demo() {cout<<name<<"::无参构造"<<endl;} //无参构造
Demo(string n):name(n) {cout<<name<<"::有参构造"<<endl;} //有参构造
~Demo() {cout<<name<<"::析构函数"<<endl;
} //析构函数
};
int main()
{
shared_ptr<Test> sp1 = make_shared<Test>("张飞"); //创建一个共享智能指针
{
shared_ptr<Demo> sp2 = make_shared<Demo>("夏侯"); //在局部空间创建一个新对象
//两个类对象的弱指针成员指向对方
sp1->sp = sp2;
sp2->sp = sp1;
//定义一个共享智能指针,接收提升后的weak_ptr
shared_ptr<Demo> t = sp1->sp.lock(); //原子操作,线程安全
if(t == nullptr)
{
cout<<"语句块内部:sp1->sp已经过期"<<endl;
}else
{
cout<<"语句块内部:t->name = "<<t->name<<endl;
}
}
//定义一个共享智能指针,接收提升后的weak_ptr
shared_ptr<Demo> t = sp1->sp.lock(); //原子操作,线程安全
if(t == nullptr)
{
cout<<"语句块外部:sp1->sp已经过期"<<endl;
}else
{
cout<<"语句块外部:t->name = "<<t->name<<endl;
}
cout << "********程序到此结束!********" << endl;
return 0;
}
七、C++中关键字总结
1> C++中一共有63个关键字,如上图所示,其中标红的为c语言中的关键字,有32个
2> 数据类型相关的关键字
bool、true、false:对于bool类型数据的相关处理,值为true和false
char、wchar_t:char是单字符数据,wchar_t多字符数据
int、short、float、double、long:整数和实数的数据类型
signed、unsigned:定义有符号和无符号数据的说明符
auto:在c语言中,是存储类型,但是在C++中,是类型自动推导,注意事项有两个:
i> 连续定义多个变量时,初始值必须是相同数据类型,否则报错
ii> auto p=&m; 与auto* p = &m;规定是一样
explicit:防止数据隐式转换
typedef:类型重定义
sizeof:求数据类型的字节运算
3> 语句相关的关键字
switch、case、default:实现多分支选择结构
do、while、for:循环相关的关键字
break、continue、goto:跳转语句
if、else:选择结构
inline:内联函数
return:函数返回值
4> 存储类型相关的关键字
static、const、volatile、register、extern、auto
5> 构造数据类型相关
struct、union:结构体和共用体
enum:枚举
class:类
6> 访问权限:public、protected、private
7> 异常处理:throw、try、catch
8> 类中相关使用关键字
this:指代自己的指针
friend:友元
virtual:虚
delete、default:对类的特殊成员函数的相关使用
例如:Test(const Test &) = default; ~Test() = delete;
mutable:取消常属性
using:引入数据,有三种使用方式
i> 使用命名空间的关键字
ii> 相当于类型重定义
iii> 修改子类中从父类继承下来成员的权限
operator:运算符重载关键字
9> 类型转换相关的关键字
static_cast、dynamic_cast、const_cast、reinterpret_cast
10> 模板相关的关键字:template、typename
11> 命名空间相关:using、namespace
12> export:导入相关模板类使用
13> 内存分配和回收:new、delete
补充的枚举内容
using namespace std;
//使用enum可以定义枚举,枚举值都是整型常量
//如果没有指定值,默认时从0开始
//枚举的作用:防止魔鬼数字
enum Color
{
red = 10, //10
orange, //11
yellow, //12
green = 100, //100
cyan, //101
blue, //102
puple //103
};
int main()
{
Color c1 = yellow;
cout<<c1<<endl; //12
cout<<blue<<endl; //102
if(c1 == green)
{
cout<<"**********"<<endl;
}else
{
cout<<"--------------"<<endl;
}
return 0;
}
介绍QT
1. QT主要用于图形化界面的开发, QT是基于C++编写的一套界面相关的类库,
进程线程库,网络编程的库,数据库操作的库,文件操作的库.....
2. 学习QT,
掌握QT中的类库是做什么,如何使用这个类库
类库实例化对象(构造函数) --> 学习类库中方法(函数)的使用 --> 后台逻辑的实现
3. QT是一个跨平台的GUI图形化界面开发工具
4. QT的使用场合
汽车仪表盘
打印机
医疗器械
自动化的大型设备
5. QT的优点
1.跨平台,具有较为完备的图形开发库,你所能想到的的图形的实现基本都有,比window的MFC的库更强大。所以很多之前做桌面开发用MFC的都转了Qt。
2.接口的封装性好,易上手,学习QT框架对学习计算机图形框架有很重要的参考意义。
3.Qt内部基本上有一套自己的简易好用的内存回收机制,对提高C++水平有帮助。
4.有很好的社区环境,市场份额在缓慢上升。
5.轻量级的开发环境,可以做嵌入式开发
软件安装
下载网址: https://download.qt.io/archive/online_installers/4.2/ 下载网址:
Windows : qt-unified-windows-x86-4.2.0-beta-online.exe
linux : qt-unified-windows-x86-4.2.0-beta-online.run
MAC : qt-unified-windows-x86-4.2.0-beta-online.dmg
QT工具介绍
1. Assistant ---> QT类库的帮助手册的工具
2. Designer ---> 用来设计图形化界面
对应的界面文件为***.ui (ui文件中的内容是一种标记性的语言)
3. uic.exe ---> 将***.ui文件转换为标准的C++的代码 ui_***.h
C:\Qt\5.15.2\mingw81_64\bin\uic.exe
在cmd终端下输入以下命令:
C:\Qt\5.15.2\mingw81_64\bin\uic.exe designer.ui -o ui_designer.h
4. moc.exe ---> 元对象编辑器工具
C:\Qt\5.15.2\mingw81_64\bin\moc.exe
将QT中非标准的信号和槽,转换为标准的C++的代码
5. rcc.exe ---> 资源管理器
C:\Qt\5.15.2\mingw81_64\bin\rcc.exe
将QT资源文件(图片,音频文件,视频文件),转换为标准的C++代码
6. qmake ---> 工程管理的工具
QT工程文件的后缀为***.pro工程文件,
qmake工具可以根据***.pro文件,生成Makefile文件,
通过Makefile文件编译C++的代码。
7. QTcreater --> QT集成开发环境工具(IDE)
将上边的所有的工具都集成到一起了。
Assistant帮助文档的使用
1> qt中的类名就是头文件的名字
2> 有些类需要在.pro文件中引入对应的类库后,才能引入头文件,进而对类的使用
3> qt中的类,大部分都是多级继承关系
QLineEdit --> QWidget --> QObject and QPaintDevice
QPushButton --> QAbstractButton --> QWidget --> QObject and QPaintDevice
QLabel --> QFrame --> QWidget --> QObject and QPaintDevice
设计师界面的介绍
QT工程项目各文件初始程序的介绍
1> 配置文件:.pro文件
QT += core gui sql network
# QT工程所需的类库 core是核心库 gui图形化界面相关类库
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
#版本超过4.0,会加上widgets
CONFIG += c++11
#该编译器支持C++11后的版本
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
#管理源文件
SOURCES += \
main.cpp \
mywnd.cpp
#管理头文件
HEADERS += \
mywnd.h
#管理ui文件
FORMS += \
mywnd.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
2> 头文件
#ifndef MYWND_H
#define MYWND_H //防止文件重复包含
#include <QWidget> //QWidget类所在的头文件,父类头文件
QT_BEGIN_NAMESPACE
namespace Ui { class MyWnd; } //命名空间的声明
QT_END_NAMESPACE
//定义属于自己的类 MyWnd是类名,公共继承自QWidget
class MyWnd : public QWidget
{
Q_OBJECT //信号与槽的元对象
public:
MyWnd(QWidget *parent = nullptr); //构造函数的声明,有一个默认参数的形参
~MyWnd(); //析构函数额声明
private:
Ui::MyWnd *ui; //后期可以通过ui指针找到ui界面上拖拽出来的组件
};
#endif // MYWND_H
3> 源文件
#include "mywnd.h" //自己的头文件
#include "ui_mywnd.h" //ui界面对应的头文件
MyWnd::MyWnd(QWidget *parent) //构造函数的定义
: QWidget(parent) //显性调用父类的构造函数完成对子类从父类继承下来成员的初始化工作
, ui(new Ui::MyWnd) //对自己类中的指针成员开辟空间
{
ui->setupUi(this); //给拖拽出来的组件实例化空间
}
MyWnd::~MyWnd() //析构函数的定义
{
delete ui; //释放ui指针的内存
}
4> 主程序
#include "mywnd.h" //图形化界面的头文件
#include <QApplication> //应用程序的头文件
int main(int argc, char *argv[])
{
QApplication a(argc, argv); //实例化一个应用程序的对象,调用的是有参构造
MyWnd w; //在栈区实例化自定义类的对象
w.show(); //调用show函数,展示图形化界面,该函数是父类提供的,直接用即可
return a.exec(); //为了阻塞界面不被关闭,等待相关事情发生
//等待信号与槽、事件处理、等待用户操作
}
5> 各文件之间调用方式
第一个QT界面
1> 创建自定义类时需要指定父类
2> 第一个界面的相关操作
this->resize(QSize(800,600)); //使用匿名对象,调用重新设置尺寸函数
qDebug() << "size = " << this->size();
qDebug()<<"width = "<<this->width(); //输出组件宽度
qDebug()<<"height = "<<this->height(); //获取高度
//2、设置尺寸最值
this->setMaximumSize(1000,800); //设置最大尺寸
this->setMinimumSize(400,300); //设置最小尺寸
this->setFixedSize(540, 410); //设置固定尺寸
//3、窗口标题
qDebug()<<this->windowTitle();
this->setWindowTitle("My First Window");
qDebug()<<this->windowTitle();
//4、设置窗口的icon
this->setWindowIcon(QIcon("C:\\Users\\鹏程万里\\Desktop\\windowIcon.png"));
//5、设置背景色,一般使用样式表完成
this->setStyleSheet("background-color:skyblue;");
//6、设置窗口透明度
this->setWindowOpacity(0.8);
//8、设置纯净窗口
this->setWindowFlag(Qt::FramelessWindowHint);
//9、移动窗口位置
this->move(50,100);
常用类与组件
1.1 信息调试类(QDebug)
使用方式:
1、qDebug("", 输出内容); //类似于printf
2、qDebug() << 输出内容; //类似于cout
1.2 按钮组件(QPushButton)
1> 该类提供了一个命令按钮
2> 所需头文件:#include<QPushButton>
3> 常用函数
//1、使用无参构造添加一个按钮
QPushButton *btn1 = new QPushButton; //无参构造
//btn1->show();
btn1->setParent(this); //给组件指定父组件,让其依附于界面而存在
btn1->setText("按钮1"); //给组件设置文本内容
qDebug()<<btn1->size(); //界面大小
btn1->resize(QSize(70,35)); //设置按钮组件的大小
btn1->move(200,0); //移动组件位置
btn1->setStyleSheet("background-color:red; " //设置样式表
"border-radius:10px; "
"color:white;");
//2、构造一个按钮时,指定父组件
QPushButton *btn2 = new QPushButton(this); //将当前界面设置成父组件
btn2->setText("按钮2");
btn2->resize(btn1->size()); //使用其他按钮的大小设置该组件的大小
btn2->move(btn1->x(), 80);
btn2->setEnabled(false);
btn2->setIcon(QIcon("C:/Users/鹏程万里/Desktop/windowIcon.png")); //设置图标
//3、构造按钮时给定文本内容以及父组件
QPushButton *btn3 = new QPushButton("按钮3", this);
btn3->resize(btn1->size());
btn3->move(btn2->x(), btn2->y()+50);
btn3->setIcon(QIcon("C:/Users/鹏程万里/Desktop/windowIcon.png"));
//4、构造一个按钮,构造时给定父组件、文本内容、icon
QPushButton *btn4 = new QPushButton(QIcon("C:/Users/鹏程万里/Desktop/windowIcon.png"),
"按钮4", this);
btn4->resize(btn1->size());
btn4->move(btn3->x(), btn3->y()+50);
行编辑器类(QLineEdit)
1> 该类提供了一个行编辑器,输入文本只在一行内展示
2> 所需头文件:#include<QLineEdit>
3> 常用函数:
//1、构造一个行编辑器,构造时给定父组件
QLineEdit *edit1 = new QLineEdit(this);
//edit1->setText("请输入。。。"); //设置编辑器中的文本内容
edit1->setPlaceholderText("QQ/手机/邮箱"); //设置编辑器的占位文本
edit1->resize(200,40); //设置尺寸
edit1->move(btn1->x()+80, 0); //移动位置
edit1->setEnabled(false); //设置不可用状态
//2、构造一个行编辑器,构造时给定父组件以及文本内容
QLineEdit *edit2 = new QLineEdit("啦啦啦啦啦啦,我是卖报的小行家", this);
qDebug() << edit2->text(); //获取行编辑器中文本内容
edit2->resize(edit1->size());
edit2->move(edit1->x(), edit1->height()+40);
edit2->setEchoMode(QLineEdit::Password); //设置回显模式
标签类(QLabel)
1> 该类提供的能够展示文本内容或者图片的容器
2> 所需头文件:#include<QLabel>
3> 常用函数
//1、实例化一个标签
QLabel *lab1 = new QLabel("账户", this);
lab1->resize(50,50);
lab1->setStyleSheet("background-color:yellow");
lab1->setPixmap(QPixmap("C:/Users/鹏程万里/Desktop/tang.png"));
lab1->setScaledContents(true); //设置内容自适应
QT += core gui
#如果Qt版本大于4,则添加widgets模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
#启用C++11编译标准
CONFIG += c++11
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
#定义宏,使编译器在遇到已弃用的Qt特性时发出警告
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
#列出项目中所有的源文件
SOURCES += \
main.cpp \
mainwindow.cpp
#列出项目中所有的头文件
HEADERS += \
mainwindow.h
#列出项目中所有的用户界面文件
FORMS += \
mainwindow.ui
#默认的部署规则
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
#include "mainwindow.h" //包含主窗口类的头文件
#include <QApplication> //包含QApplication类的头文件,这是所有Qt应用程序必须的
int main(int argc, char *argv[]) //main函数,程序的入口点
{
QApplication a(argc, argv); //创建一个QApplication对象,传递命令行参数
MainWindow w; //创建MainWindow类的实例
w.show(); //显示主窗口
return a.exec(); //进入Qt的事件循环,等待用户操作
}
#include "mainwindow.h" //包含主窗口类的头文件
#include "ui_mainwindow.h" //包含由Qt Designer生成的用户界面类的头文件
//MainWindow类的构造函数
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) //调用QMainWindow的构造函数,传递父窗口指针
, ui(new Ui::MainWindow) //创建Ui::MainWindow类的实例,并初始化ui成员变量
{
ui->setupUi(this); //调用setupUi函数,设置用户界面
}
//MainWindow类的析构函数
MainWindow::~MainWindow()
{
delete ui; //删除Ui::MainWindow类的实例,释放内存
}