1.C++11中引入了哪些新的智能指针类型?
为什么要引用智能指针?
首先裸指针使用完没有及时释放会造成内存泄漏的问题,为了解决这个问题,引入了独占型智能指针unique_ptr,它不需要手动释放。但是unique指针不允许其他智能指针和它指向同一份资源。
共享型指针指针shared_ptr出现,shared_ptr允许智能指针和它指向同一份资源,但是又产生了新的问题,就是会造成循环引用的问题,导致资源无法被释放。
监控型智能指针weak_ptr出现,,它的特点是引用对象时引用计数不增加,但是智能成为资源的监督者,无法使用*和->操作资源。
unique_ptr(独占型智能指针)
unique指针不允许其他智能指针和它指向同一个资源
实现的方法就是在独占型指针指针的类里面把拷贝构造函数delete了
#include <iostream>
#include <memory>
using namespace std;
int main(){
unique_ptr<int> uPtr1(new int(1));
unique_ptr<int> uPtr2(uPtr1);//×
return 0;
}
可以使用move函数把一个unique指针的资源转移给另外一个unique指针
#include <iostream>
#include <memory>
using namespace std;
int main(){
unique_ptr<int> uPtr1(new int(1));
unique_ptr<int> uPtr2(move(uPtr1));
return 0;
}
不要用原始指针去初始化多个指针智能,会造成一份资源被多次释放
产生的原因:因为用原始指针去初始化智能指针,引用计数不会++
#include <iostream>
#include <memory>
using namespace std;
int main(){
int* oldPtr = new int(1);
unique_ptr<int> uPtr1(oldPtr);
unique_ptr<int> uPtr2(oldPtr);
return 0;
}
shared_ptr(共享型智能指针)
允许多个智能指针指向同一份资源
#include <iostream>
#include <memory>
using namespace std;
int main(){
shared_ptr<int> shrPtr1(new int(10));
shared_ptr<int> shrPtr2(shrPtr1);
shared_ptr<int> shrPtr3(shrPtr1);
shared_ptr<int> shrPtr4(shrPtr1);
shared_ptr<int> shrPtr5(shrPtr1);
cout<<shrPtr5.use_count()<<endl;
return 0;
}
会造成循环引用的问题,导致资源无法被释放
图例
代码
#include <iostream>
#include <memory>
using namespace std;
class B;
class A{
public:
int* a;
shared_ptr<B> ptrB;
A(int val){
a = new int(val);
}
void setShrPtr(shared_ptr<B> ptr){
this->ptrB = ptr;
}
~A(){
cout<<"A::析构函数"<<endl;
}
};
class B{
public:
int* b;
shared_ptr<A> ptrA;
B(int val){
b = new int(val);
}
void setShrPtr(shared_ptr<A> ptr){
this->ptrA = ptr;
}
~B(){
cout<<"B::析构函数"<<endl;
}
};
int main(){
shared_ptr<A> shrPtrA(new A(1));
shared_ptr<B> shrPtrB(new B(2));
shrPtrA->setShrPtr(shrPtrB);
shrPtrB->setShrPtr(shrPtrA);
cout<<"shrPtrA.use_count "<<shrPtrA.use_count()<<endl;
cout<<"shrPtrB.use_count "<<shrPtrB.use_count()<<endl;
//没有调用析构函数!!!!!!!!!!!!!!
// unique_ptr<int> unqPtr1(new int(100));
// unique_ptr<int> unqPtr2(std::move(unqPtr1));
// cout<<*unqPtr2<<endl;
return 0;
}
weak_ptr(监督型智能指针)
为了解决shared指针循环引用的问题,所以要和shared指针一起使用,用来监督shared_ptr的使用情况
代码
#include <iostream>
#include <memory>
using namespace std;
class B;
class A{
public:
int* a;
weak_ptr<B> ptrB;
A(int val){
a = new int(val);
}
void setShrPtr(shared_ptr<B> ptr){
this->ptrB = ptr;
}
~A(){
cout<<"A::析构函数"<<endl;
}
};
class B{
public:
int* b;
weak_ptr<A> ptrA;
B(int val){
b = new int(val);
}
void setShrPtr(shared_ptr<A> ptr){
this->ptrA = ptr;
}
~B(){
cout<<"B::析构函数"<<endl;
}
};
int main(){
shared_ptr<A> shrPtrA(new A(1));
shared_ptr<B> shrPtrB(new B(2));
shrPtrA->setShrPtr(shrPtrB);
shrPtrB->setShrPtr(shrPtrA);
shrPtrA->ptrB.lock()->b;
shrPtrB->ptrA.lock()->a;
cout<<"shrPtrA.use_count "<<shrPtrA.use_count()<<endl;
cout<<"shrPtrB.use_count "<<shrPtrB.use_count()<<endl;
// unique_ptr<int> unqPtr1(new int(100));
// unique_ptr<int> unqPtr2(std::move(unqPtr1));
// cout<<*unqPtr2<<endl;
return 0;
}
自定以MyShared_ptr实现
-------------------------------------MyShared_ptr.h--------------------------------------------------
#ifndef TEST02_MYSHARED_PTR_H
#define TEST02_MYSHARED_PTR_H
#include <iostream>
using namespace std;
template <typename T>
class Ref{
T* obj = nullptr;
int r_count = 0;
public:
Ref(T* oldPtr):obj(oldPtr){
increase();
}
inline void increase(){
r_count++;
}
inline void reduce(){
r_count--;
if(r_count == 0){
delete obj;
delete this;
}
}
T* getObj(){
return obj;
}
int getUseCount(){
return r_count;
}
};
//ptr1 ptr2 ptr3 ptr4 -->obj
template <typename T>
class MyShared_ptr {
Ref<T>* r = nullptr;
public:
MyShared_ptr() = default;
~MyShared_ptr(){
if(r) r->reduce();
}
MyShared_ptr(T* oldPtr){
cout<<"调用构造函数"<<endl;
r = new Ref<T>(oldPtr);
}
MyShared_ptr(const MyShared_ptr& other){
cout<<"拷贝构造"<<endl;
if(other.r){
this->r = other.r;
r->increase();
}
}
MyShared_ptr(MyShared_ptr&& other){
cout<<"移动构造"<<endl;
if(other.r){
this->r = other.r;
other.r = nullptr;
}
}
MyShared_ptr operator=(const MyShared_ptr& other){
if(other.r && this->r){
this->r->reduce();
this->r = other.r;
r->increase();
return *this;
}
}
MyShared_ptr operator=(MyShared_ptr&& other){
if(other.r && this->r){
this->r->reduce();
this->r = other.r;
return *this;
}
}
void reset(){
if(r) r->reduce();
r = nullptr;
}
void reset(T* trg){
if(r) r->reduce();
r = new Ref<T>(trg);
}
T operator*(){
if(r){
return *r->getObj();
}
}
T* operator->(){
if(r){
return r->getObj();
}
}
int use_count(){
if(r) return r->getUseCount();
return 0;
}
};
#endif //TEST02_MYSHARED_PTR_H
2.解释一下C++11中的右值引用和移动语义
右值引用接收右值
右值指定就是没有函数名不能寻址的值
移动语义
指的是将一个资源从一个对象1移动给另外一个对象2,对象1自动放弃了对该资源的持有,给到了资源2,不用进行资源的拷贝,可以提高性能。
通常通过移动构造和移动赋值运算符来实现
如果一个值为左值想使用移动语义,可以调用std::move函数把左值移动为右值
#include <iostream>
using namespace std;
class MyObject{
int* data;
public:
MyObject(): data(nullptr){
cout<<"Default Constructor"<<endl;
}
MyObject(int value): data(new int(value)){
cout<<"Regular Constructor"<<endl;
}
MyObject(MyObject&& other):data(other.data){
other.data = nullptr;
cout<<"Move constructor"<<endl;
}
~MyObject(){
delete data;
cout<<"Destructor"<<endl;
}
void printData() const {
if(data != nullptr) cout<<"Data: "<<*data<<endl;
else cout<<"Data is null"<<endl;
}
};
int main(){
MyObject obj1(10);
obj1.printData();
MyObject obj2(std::move(obj1));
obj2.printData();
obj1.printData();
return 0;
}
3.谈谈你对C++11中auto关键字的理解
为什么要有auto这个关键字
为了简化代码,auto可以自动推断出表达式的类型
auto
auto是C++11引入的一种自动推断类型的机制
auto的使用场景
使用STL容器的时候,它的迭代器类型可能会非常复杂,这时候使用auto关键字可以提高可读性
使用增强for循环的时候,不用指定下标他会自动从头到尾遍历
自动推断函数的返回值
auto add(int a,int b){
return a+b;
}
int main(){
vector<int> vec = {1,2,3,4};
auto iter = vec.begin();
for(auto it : vec){
cout<< it <<endl;
}
return 0;
}
使用泛型编程,当参数类型非常复杂且难以指定
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
template <typename T>
void process(const vector<T>& vec){
for(auto it : vec){
cout << it<<endl;
}
}
int main(){
vector<int> vec = {1,2,3,4};
process(vec);
return 0;
}
4.C++11的lambda表达式是什么?
为什么要有lambda表达式?
首先它可以简化代码,因为它可以不想传递的函数一样,需要通过函数调用来执行,而是直接在调用处把函数体放在了那里
lambda表达式
本质是一个匿名函数对象
格式
[捕获列表] (参数列表) -> ret {
//函数体
}
//捕获列表有值捕获和引用捕获 this在类中捕获成员变量
//返回值通常可以省略,可以自动判断
应用场景
直接使用
作为函数的参数
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
int main(){
auto add = [](int a, int b){
return a+b;
};
cout<<add(1,2)<<endl;
vector<int> vec = {5,3,1,4,2};
sort(vec.begin(),vec.end(),[](int l,int r){
return l<r;
});
for(auto it : vec){
cout<<it<<' ';
}
return 0;
}
5.C++11中的nullptr和C++98中NULL的区别
C++98中的NULL在函数重载的时候可能会造成指代不明确的问题,因为NULL是一个宏定义的0
而C++11中引入的nullptr则是真正的空指针,它的类型不是宏定义的0,而是nullptr_t可以自动转换成任意指针类型
/*
* C+11 nullptr 和 C++98 NULL 的区别
*/
void overloadFunc(int* ptr){
printf("void overloadFunc(int* ptr)\n");
}
void overloadFunc(int ptr){
printf("void overloadFunc(int ptr)\n");
}
6.C++11引入了哪些新的特性或改进?
a.引入了三个智能指针
独占型智能指针(unique_ptr)
共享型智能指针(shared_ptr)
为了解决shared_ptr循环问题的weak_ptr智能指针(weak_ptr)
b.提供了lambda表达式来定义匿名函数对象
c.引入了auto自动检测表达式类型的关键字
d.引入了移动语义
移动构造
移动赋值运算符
和move函数把左值改变成右值
e.为了解决98中NULL指代不明确的问题,C++11引入了nullptr
f.提供了thread、mutex、条件变量等有关线程的概念
g.提供了STL库,定义了一些常用的数据结构
例如stack、queue、priotity_queue、list、unordered_map、map等等常用的数据结构
h.引入了随机数函数库random
(*)i.提供了正则表达式库
7.解释一下C++中的增强for循环及其用法
为什么要引入增强for循环
感觉发现了一个规律,C++11引入的这些新特性大多数都是为了使代码变得更加简洁,增强for循环也不例外
增强for循环
格式
for(定义了一个变量 : 遍历的范围)
通常和auto自动检测类型的关键字一起使用
尤其是对于STL库中的容器而言,用增强for循环加auto配合使用会时代码变得更加简洁
应用场景
不写了 上面auto使用的时候有
8.C++11中如何初始化一个数组或容器
C++11中可以使用列表进行初始化
like
vector<int> vec = {1,2,3,4}
9.谈一谈C++11中的默认和删除函数
默认函数(default)
为什么要使用默认函数?
在一个类中,如果我们定义了一个有参构造了,系统就不会提供无参构造了,也就时说我们在main中使用MyClass myClass;这样时错的
为了解决这个问题,C++11为我们提供了默认函数。你也可以自己定义一个默认的构造,但是C++11的这种 = default更加简洁
#include <iostream>
using namespace std;
class MyClass{
public:
string name;
int age;
MyClass() = default;
MyClass(int a){
cout<<"有参构造"<<endl;
}
};
int main(){
MyClass obj;
cout<<"name "<<obj.name<<endl;
cout<<"age "<<obj.age<<endl;
return 0;
}
删除函数(delete)
在我的Qt网盘项目中客户端和服务器需要实现单例模式,即需要把构造函数私有化,禁止使用构造函数赋值运算符去创建对象,想要创建对象必须通过一个static全局访问结点来创建对象,由此来实现单例模式
而把拷贝构造和赋值运算符禁止使用的就时删除函数 =delete
#include <iostream>
using namespace std;
class SingleClass{
public:
static SingleClass getInstance();
private:
int* resource;
SingleClass();
SingleClass(const SingleClass& other) = delete;
SingleClass& operator=(const SingleClass& other) = delete;
};
int main(){
SingleClass::getInstance();
return 0;
}
10.谈一谈initalizer_list显示转换运算符
在C++11中,initializer_list是一个模板类,可以接收花括号这种形式的初始化参数列表作为参数,从而简化对象和容器的初始化
#include <iostream>
using namespace std;
class MyClass{
public:
MyClass(initializer_list<int> list){
for(auto it : list){
cout<<it<<' ';
}
}
};
int main(){
MyClass obj = {1,2,3,4};
return 0;
}
11.原子操作及其在多线程编程中的应用
为什么要引入原子操作?
为了解决多线程并发执行产生的安全性问题,C++11引入了原子操作的概念,通过 头文件提供了对原子类型的支持。
原子操作
原子操作是指再多线程环境中,对变量的操作可以在单个指令中完成,不会被其他线程打断,从而实现线程并发安全执行
12.谈一谈为什么move函数可以优化资源转化性能
首先move函数可以将对象转化为右值引用类型,从而可以使用移动构造、移动赋值运算符等移动语义来提高资源转化的效率
具体提高的方式是,移动语义下的资源转换不是想拷贝构造哪种,拷贝一份资源再去赋值,而是直接把你的东西给我,省略了拷贝的过程,从而提高了效率