【八股】【C++】11新特性

news/2024/10/18 7:49:55/

这里写目录标题

  • auto
  • decltype
  • decltype(auto)
  • NULL与nullptr
  • RAII机制
  • 智能指针
    • auto_ptr
    • unique_ptr
    • shared_ptr
    • weak_ptr
  • 手写实现智能指针类需要实现哪些函数
  • Lambda
  • 左值与右值
  • 左值引用和右值引用
  • move

auto

auto是C++11新增的一个关键字,auto也叫类型说明符,它可以让编译器替我们去分析表达式所属的类型,它仅仅是一个占位符,在编译期间它会进行类型推导,然后被真正的类型所替代。
我对auto的理解就是它会根据后面的值,来自己推测前面的类型是什么。使用auto可以减少复杂的变量声明,使程序更清晰易读。

list<int> l1;
list<int>::iterator i = l1.begin();
auto i = l1.begin();

注意:auto声明的变量必须要初始化,否则编译器不能判断变量的类型,类似于const关键字。
auto 不能在函数参数和模板参数中使用,因为我们在定义函数的时候只是对参数进行了声明,指明了参数的类型,但并没有给它赋值,只有在实际调用函数的时候才会给参数赋值;而 auto 要求必须对变量进行初始化,所以这是矛盾的。

decltype

decltype是C++11新增的一个关键字,和auto的功能一样,用来在编译时期进行类型推导。
之所以会引入decltype,是因为使用auto关键字进行类型推断的时候有一些限制,比如定义变量的时候必须要初始化,那可能在某些特定环境下就不太适合,所以用decltype来解决auto的不足之处。

auto serven_1 = 10;
decltype (exp) varname = value;
auto是根据=右边的初始值来自动推导变量的类型,所以auto在定义的时候必须初始化;
decltype是根据表达式exp来自动推导出变量的类型,跟=右边的value没有关系,所以decltype在定义的时候可以不用初始化

decltype可以作用于变量、表达式及函数名。
①作用于变量直接得到变量的类型;
②作用于表达式,结果是左值的表达式得到类型的引用,结果是右值的表达式得到类型;
③作用于函数名会得到函数返回值类型,函数的返回值不能是void。

decltype(auto)

decltype(auto)是C++14新增的类型指示符,可以用来声明变量以及指示函数返回类型。在使用时,会将“=”号右边的表达式替换掉auto,再根据decltype的语法规则来确定类型。举个例子:

int e = 4;
const int* f = &e; // f是底层const
decltype(auto) j = f;//j的类型是const int* 并且指向的是e

NULL与nullptr

首先NULL和nullptr都可以用来表示空指针,其中NULL属于C 语言中的宏,而 nullptr 是C++11 中新增的关键字。
在 C语言 中,NULL被定义为没有类型的指针常量 (void*) 0
在 C++中,NULL 被定义为整形常量 0

也就是说在 C++中,下面的这两行代码是完全等价的,没有区别。
int* p1 = NULL;  
int* p2 = 0;

为什么要引入nullptr呢,因为在C++里,如果我们有时候我们使用 NULL 作为函数重载的参数,可能运行的结果和我们的期望不符合。

void test(int)void test(int*)int main()
{// 两次都会调test(int)。test(0);test(NULL);return 0;//因为在 C++中,字面常量 0 既可以表示一个整形常量 0,也可以表示无类型指针常量 (void*) 0,但是编译器默认把它看成是一个整形常量 0 如果把 0 当指针使用,就必须对其进行强转 (void*) 0 。为了解决这个问题,C++11标准增加了新的关键字 nullptr,保证在任何情况下都表示空指针。
}

在C++11中,sizeof(nullptr)sizeof((void*)0)所占字节相同,都为4。
为了提高代码的健壮性,使用表示空指针的时候最好使用nullptr。

RAII机制

RAII翻译过来是“资源获取就初始化”,是C++的一种管理资源、避免泄漏的惯用方法。
RAII的做法是使用一个对象,在对象生命期内控制对资源的访问,在对象析构的时候,释放构造时获取的资源。

智能指针

所谓的智能指针本质就是一个类模板,它可以创建任意的类型的指针对象,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间。
简而言之,智能指针就是帮管理动态分配的内存的,它会帮助我们自动释放new出来的内存,从而避免内存泄漏!
一共有四种智能指针:C++98里的auto_ptr、C++11里的unique_ptrshared_ptrweak_ptr

auto_ptr

auto_ptr比较重要的作用就是解决"有异常抛出时发生内存泄漏"的问题;因为如果抛出异常的时候,有可能导致指针所指向的空间得不到释放从而导致内存泄漏;

// 定义智能指针
auto_ptr<Test> test(new Test);

三个常用函数
(1)get() 获取智能指针托管的指针地址

// 定义智能指针
auto_ptr<Test> test(new Test);
Test *tmp = test.get();		// 获取指针返回
cout << "tmp->debug:" << tmp->getDebug() << endl;

(2)release() 取消智能指针对动态内存的托管

// 定义智能指针
auto_ptr<Test> test(new Test);
Test *tmp2 = test.release();	// 取消智能指针对动态内存的托管
delete tmp2;	// 之前分配的内存需要自己手动释放
也就是智能指针不再对该指针进行管理,改由管理员进行管理!

(3)reset() 重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉

// 定义智能指针
auto_ptr<Test> test(new Test);
test.reset();			// 释放掉智能指针托管的指针内存,并将其置NULL
test.reset(new Test());	// 释放掉智能指针托管的指针内存,并将参数指针取代之

reset函数会将参数的指针(不指定则为NULL),与托管的指针比较,如果地址不一致,那么就会析构掉原来托管的指针,然后使用参数的指针替代之。然后智能指针就会托管参数的那个指针了。
C++11后不建议使用auto_ptr,使用unique_ptr替代!
主要原因:复制或者赋值都会改变资源的所有权

auto_ptr<string> p1(new string("I'm Li Ming!"));
auto_ptr<string> p2(new string("I'm age 22."));
// p1: 012A8750   P2:012A8510
// p2赋值给p1后,首先p1会先将自己原先托管的指针释放掉,然后接收托管p2所托管的指针,
// 然后p2所托管的指针制NULL,也就是p1托管了p2托管的指针,而p2放弃了托管。
p1 = p2;	
// P1: 012A8510	 P2:00000000

unique_ptr

unique_ptr 和 auto_ptr用法几乎一样,只不过更严谨一些,它直接把拷贝构造函数和赋值重载函数给禁用掉了,不允许进行拷贝和赋值,所以也就直接禁止两个指针指向同一个资源,提高了代码的严谨性和安全性。

shared_ptr

auto_ptr和unique_ptr中有个问题就是它们都具有排他性,也就是都不支持多个智能指针指向同一块资源,比较局限。而shared_ptr就是为了解决这个问题,shared_ptr会允许多个智能指针指向同一块资源,并且能够保证共享的资源只会被释放一次。
shared_ptr采用的是引用计数原理来实现多个shared_ptr对象之间共享资源:

shared_ptr会维护一份引用计数,用来记录当前资源被几个对象共享。
当复制或者拷贝的时候,引用计数加+1,当一个shared_ptr对象被销毁的时候,会调用析构函数把这个计数-1。
当计数为零的时候,代表已经没有指针指向这块内存,那么我们就释放它。

缺点:
假设我们要使用定义一个双向链表,把链表上节点都定义成shared_ptr智能指针,当其中两个节点互相引用的时候,就会出现循环引用的现象。

最开始node1和node2的引用计数都是1.
node1的next指向node2所指向的资源时,node2的引用计数+1,变成 2.
node2的pre指向noede1所指向的资源时,node1的引用计数+1,变成 2.
当这两个智能指针使用完后,调用析构函数,引用计数都-1,都变成1。
但是由于引用计数不为0,所以node1和node2所指向的对象都不会被释放,造成循环引用。

weak_ptr

weak_ptr也叫弱指针,引入weak_ptr的初衷就是为了解决刚才说的shared_ptr可能出现的循环引用问题,它只能从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会影响到引用数。
所以在刚才的例子里,我们可以用weak_ptr声明链表节点的pre和next两个指针,那么节点在指向前一个或后一个节点后并且不会改变shared_ptr的引用计数,当node1计数为0时,node1指向的空间就会被销毁掉。node2计数为0时,node2指向的空间也会被销毁掉,所以weak_ptr指针搭配shared_ptr指针可以很好解决循环引用的问题。

手写实现智能指针类需要实现哪些函数

我们需要一个指针对象,需要一个引用计数的指针设定对象的值,并将引用计数计为1,需要一个构造函数。新增对象还需要一个构造函数,析构函数负责引用计数减少和释放内存。
通过覆写赋值运算符,才能将一个旧的智能指针赋值给另一个指针,同时旧的引用计数减1,新的引用计数加1
一个构造函数、拷贝构造函数、复制构造函数、析构函数、移动函数;

Lambda

Lambda函数是C++ 11的新特性之一,使用Lambda我们可以编写内嵌的匿名函数,以此来简化编程工作。我们平时调用函数的时候,都是需要被调用函数的函数名,但是使用lambda函数就不需要函数名,直接写在需要调用的地方就可以。

[capture](parameters) mutable throw() -> return_type { /* ... */ }[capture] :也称为Lambda导入器,编译器根据导入器判断接下来的代码是否是Lambda函数[]内为捕捉外部变量的传递方式:值、引用等
mutable: 默认情况下Lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空)
-> 后为lambda函数的返回类型。一般情况下,编译器能够推出lambda函数的返回值,所以这部分可以省略。// lambda函数定义后返回的是函数指针类型
auto addFunction= [](int a,int b) ->int {  return a + b;   };
int result  = addFunction(1,2);

实际上编译器会把Lambda表达式生成一个匿名类的匿名对象,并在类中重载函数调用运算符,实现了一个operator()方法。
Lambda可以忽略参数列表和返回值,但必须包含捕获列表和函数体;
优点:
可以直接在需要调用函数的位置定义代码量不多的函数,而不需要预先定义好函数再使用,相比于普通函数它的结构层次更加明显、代码可读性更好。
缺点:
语法灵活增加了阅读代码的难度,不能进行函数复用。

左值与右值

一个对象被用作右值时,使用的是它的内容(值),被当作左值时,使用的是它的地址。
简单说,左值以变量的形式存在,指向内存,生命周期比较长,我们可以对左值进行各种操作;而右值通常以常量的形式存在,是一个临时值,不能被程序的其它部分访问,生命周期很短。

int x = 8;
x是左值,8是右值;

判断某个值是左值还是右值:
① 可以在赋值号(=)左侧的表达式是左值;只能位于赋值号(=)右侧的表达式就是右值;
② 有名称的,能取到地址的表达式是左值,反之是右值;

左值引用和右值引用

概念
简单来说:传统的C++中引用就是左值引用。
右值引用关联到右值时,右值会被存储到特定位置,右值引用指向这个特定位置,虽然右值虽然无法获取地址,但是右值引用是可以获取地址的,这个地址表示临时对象的存储位置。
无论左值引用还是右值引用,都是给对象取别名

左值引用
左值引用只能引用左值,不能直接引用右值。
但是const左值引用既可以引用左值,也可以引用右值

// 1.左值引用只能引用左值
int t = 8;
int& rt1 = t;int& rt2 = 8;  // 编译报错,因为10是右值,不能直接引用右值
const int& rt3 = t;	// 但是const左值引用既可以引用左值

右值引用
右值引用只能引用右值,不能直接引用左值。
但是右值引用可以引用被move的左值

// 右值引用只能引用右值
int t = 10;
int&& rrt = t;  // 编译报错,不能直接引用左值// 但是右值引用可以引用被move的左值
int&& rrt = std::move(t);
const int&& rr6 = std::move(b);

实际意义
左值引用:我们知道函数参数传值和传值返回都会产生拷贝,代价很大,左值引用有两个使用场景:函数传参、函数返回值,相比于值传递减少了拷贝次数从而提高效率。
问题:当返回局部对象时,出了函数局部对象被析构,会调用拷贝构造出临时对象再返回。

string operator+(const string& s, char ch)
{string ret(s);ret.push_back(ch);return ret;
}
如果用右值引用就可以解决这个问题

右值引用的应用场景场景主要有两个:移动语义、完美转发
移动语义:如果我们把赋值操作看作资源转移,那传统的资源转让是通过拷贝实现的,需要两份空间。而移动语义是通过移动来实现资源转让,只使用一个空间。移动语义具体实现是基于移动构造和移动赋值。

拷贝构造函数的参数是 const左值引用,接收左值或右值;左值做参数,那么就会调用拷贝构造函数,做一次拷贝
移动构造函数的参数是右值引用,接收右值或被 move 的左值。右值做参数,那么就会调用移动构造,而调用移动构造就会减少拷贝

完美转发

void notPerfectForward(int &&i) {printValue(i);  i会被当作左值处理
}

这个转发过程中,i最开始是右值引用,但再次传递时却变成了左值。失去了右值引用的特性,不是我们的预期。这种情况适合使用完美转发。
  完美转发指函数模板转发给内部调用的其他函数时转发参数的左右值属性不变。也就是说参数是左值引用,转发给下一个函数还是左值引用;参数是右值引用,转发给下一个函数还是右值引用。
  完美转发的实现基于std::forward

template<typename T>
void PerfectForward(T &&i) {printValue(std::forward<T>(i)); 这个i会被当作右值处理
}

move

在C++11中,move并不能移动任何东西,它唯一的功能是将一个左值强制转化为右值引用,以实现移动语义,然后就可以通过右值引用使用该值,以用于移动语义。
从实现上讲,std::move基本等同于一个类型转换:static_cast<T&&>(lvalue);

C++ 标准库使用比如vector::push_back 等这类函数时,会对参数的对象进行复制,连数据也会复制.这就会造成对象内存的额外创建, 本来原意是想把参数push_back进去就行了,通过std::move,可以避免不必要的拷贝操作。
std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝所以可以提高利用效率,改善性能。

#include <utility>
int main()
{string str = "Hello";vector<string> v;//调用常规的拷贝构造函数,新建字符数组,拷贝数据v.push_back(str);cout << "After copy, str is \"" << str << "\"\n";//调用移动构造函数,掏空str,掏空后,最好不要使用strv.push_back(std::move(str));cout << "After move, str is \"" << str << "\"\n";cout << "The contents of the vector are \"" << v[0]<< "\", \"" << v[1] << "\"\n";
}After copy, str is "Hello"
After move, str is ""
The contents of the vector are "Hello", "Hello"
string s("Hello World11111111111111111");
string s1 = s;  // s是左值,所以调用拷贝构造函数
string s2 = move(s);  // s被move后变为右值,所以调用移动构造函数,s的资源会被转移用来构造s2
// 要注意的是,move一般是不这样用的,因为s的资源被转走了

http://www.ppmy.cn/news/665483.html

相关文章

最美手机 多图图赏魅族魅蓝X

2016年的魅族给不少人都留下了深刻的印象&#xff0c;凭借出色的机身设计与做工&#xff0c;魅族再次证明了“工匠精神”的魅力所在。不过在2016年的众多机型中&#xff0c;有一款手机却在众多的魅族手机中独具特色&#xff0c;这就是颜值爆表的魅蓝X。 在魅蓝X身上我们并没有看…

RocketMQ安装(Docker)

一、RocketMQ安装之docker 1.下载RockerMQ需要的镜像 docker pull rocketmqinc/rocketmq docker pull styletang/rocketmq-console-ng 2.启动NameServer服务 创建NameServer数据存储路径 mkdir -p /home/rocketmq/data/namesrv/logs /home/rocketmq/data/namesrv/store启动…

Springboot 处理BigDecimal 数据科学计数格式,展示E+8的问题

如图&#xff1a; 处理方式&#xff1a; 我们基于springboot的 JsonSerializer &#xff0c;我们自定义一下 针对BigDecimal类型属性的JsonSerializer处理。 import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import…

AndroidViewModel是ViewModel的一个子类,可以直接调用getApplication(),由此可以访问应用的全局资源

结合SharedPreferences实现保存数据 MainActivity.java package com.tjjingpan.study.viewmodelshp;import androidx.appcompat.app.AppCompatActivity; import androidx.databinding.DataBindingUtil; import androidx.lifecycle.SavedStateViewModelFactory;import android…

Linux卸载OpenJDK

1、JDK版本 java -version2、查看当前系统OPENJDK rpm -qa | grep jdk3、卸载 sudo rpm -e --nodeps java-1.8.0-openjdk sudo rpm -e --nodeps java-1.8.0-openjdk-headless sudo rpm -e --nodeps java-1.8.0-openjdk-devel sudo rpm -e --nodeps copy-jdk-configs4、其他 …

快速测试网速软件,‎App Store 上的“测网速大师 - 一键测网速”

测网速大师是一款简单易用且精准测试的测网速工具&#xff0c;一键解决3G,4G,WIFI网络测速。让您随时随地查看当前网络状态。 特性&#xff1a; 1、提供下载&#xff0c;上传和延时检测 2、保留详细测试记录&#xff0c;方便随时查看 3、为下载图片、视频等提供时长参考建议 拥…

更新win11网络卡顿浏览器网速慢

描述:从win10使用官方的升级助手升级到win11后使用浏览器特别卡,但是使用360测网速竟然有网 ps:wifi我用的是校园网,手机热点不卡 解决方案:卸载无线网卡驱动使用360驱动大师安装,驱动精灵没有扫描出来我才用的360驱动大师,然后在修改你所连接wifi的dns设置改为8.8.8.8 和114…

Win10怎么把网速调整到最快? Win10网速调整最快的方法

电脑网速很慢就会非常影响用户的使用体验&#xff0c;其实可以自己设置提高网速。下面小编就给大家带来一篇Win10把网速调到最快的方法教程。希望可以帮助到你呀&#xff01; 方法介绍&#xff1a; 1、右键单击“开始菜单”&#xff0c;选择“运行”&#xff0c;输入“gpedit.m…