罗剑锋的C++实战笔记学习(一):const、智能指针、lambda表达式

1、const

1)、常量

const一般的用法就是修饰变量、引用、指针,修饰之后它们就变成了常量,需要注意的是const并未区分出编译期常量和运行期常量,并且const只保证了运行时不直接被修改

一般的情况,const放在左边,表示常量:

const int x = 100; // 常量
const int& rx = x; // 常量引用
const int* px = &x; // 常量指针

给变量加上const之后变量就成了常量,只能读、禁止写,编译器会帮你检查出所有对它的写操作,发出警告,在编译阶段防止有意或者无意的修改。这样一来,const常量用起来就相对安全一点。所以在设计函数的时候,将参数用const修饰的话,一个是可以保证效率,另一个是保证安全

2)、修饰成员函数

除此之外,const还能声明在成员函数上,const被放在了函数的后面,表示这个函数是一个常量,函数的执行过程是const的,不会修改对象的状态(即成员变量),比如:

class DemoClass final {
private:const long MAX_SIZE = 256; // const成员变量int m_value; // 成员变量
public:int get_value() const // const成员函数{// error: Cannot assign to non-static data member within const member function 'get_value'm_value = 100;return m_value;}
};
3)、指针常量

const放在*的右边,表示指针是常量(const pointer to int),指针不能被修改,而指向的变量可以被修改:

    int x = 100;int b = 150;int *const px = &x;*px = 102; // successpx = &b; // error: Cannot assign to variable 'px' with const-qualified type 'int *const'

int *const pxconst int* px的区别:

  • int *const px定义了一个指针常量,指针本身的地址不可改变,但可以改变指针所指向的数据
  • const int* px定义了一个指向常量的指针,可以改变指针的地址使其指向其他int,但不能通过此指针修改所指向的int值
4)、小结
const非const
对象(实例)const T:对象只读,只能调用const成员函数可以修改对象,调用任意成员函数
引用const T&:引用的对象只读,只能调用const成员函数
指针*const:指针指向的对象只读,只能调用const成员函数
成员函数func() const:不允许修改成员变量可以修改成员变量
指针常量const T*:表示指针是常量,指针不能被修改,而其指向的变量可以被修改指针和其指向的变量都可以被修改

2、智能指针

1)、unique_ptr

unique_ptr是一种独占资源所有权的指针,它会自动管理初始化时的指针,在离开作用域时析构释放内存

#include <iostream>
#include <memory>int main() {std::unique_ptr<int> ptr1(new int(10)); // int智能指针assert(*ptr1 == 10); // 使用*取内容assert(ptr1 != nullptr); // 判断是否为空指针std::unique_ptr<std::string> ptr2(new std::string("hello")); // string智能指针assert(*ptr2 == "hello"); // 使用*取内容assert(ptr2->size() == 5); // 使用->调用成员函数return 0;
}

在C++14的时候新加入了make_unique()函数,可以利用它构造一个unique_ptr对象:

#include <iostream>
#include <memory>int main() {auto ptr1 = std::make_unique<int>(42); // 工厂函数创建智能指针assert(ptr1 && *ptr1 == 42);auto ptr2 = std::make_unique<std::string>("god of war"); // 工厂函数创建智能指针assert(!ptr2->empty());return 0;
}

unique_ptr的所有权:

unique_ptr表示指针的所有权是唯一的,不允许共享,任何时候只能有一个人持有它

为了实现这个目的,unique_ptr应用了C++的转移(move)语义,同时禁止了拷贝赋值,所以,在向另一个unique_ptr赋值的时候,要特别留意,必须用std::move()函数显式地声明所有权转移

赋值操作之后,指针的所有权就被转走了,原来的unique_ptr变成了空指针,新的unique_ptr接替了管理权,保证所有权的唯一性:

#include <iostream>
#include <memory>int main() {auto ptr1 = std::make_unique<int>(42); // 工厂函数创建智能指针assert(ptr1 && *ptr1 == 42); // 此时智能指针有效auto ptr2 = std::move(ptr1); // 使用move()转移所有权assert(!ptr1 && ptr2); // ptr1变成了空指针return 0;
}

unique_ptr作为参数和返回值:

unique_ptr作为参数传递不会发生拷贝,但是会将对象所有权会转移到函数里,如下ptr会在main()方法结束之前被销毁:

#include <iostream>
#include <memory>class Resource {
public:Resource() { std::cout << "Resource acquired\n"; }~Resource() { std::cout << "Resource destroyed\n"; }friend std::ostream &operator<<(std::ostream &out, const Resource &res) {out << "I am a resource";return out;}
};void takeOwnership(std::unique_ptr<Resource> res) {if (res) {std::cout << *res << '\n';}
} // Resource对象会在这里销毁int main() {auto ptr{std::make_unique<Resource>()};// takeOwnership(ptr); // 不能这样写,unique_ptr禁止了拷贝赋值,需要使用std::move()函数显式地声明所有权转移takeOwnership(std::move(ptr));std::cout << "Ending program\n";return 0;
}

输出:

Resource acquired
I am a resource
Resource destroyed
Ending program

有时候不想对象的所有权转移到函数里,这时候可以通过get()方法获取对象,如下:

#include <iostream>
#include <memory>class Resource {
public:Resource() { std::cout << "Resource acquired\n"; }~Resource() { std::cout << "Resource destroyed\n"; }friend std::ostream &operator<<(std::ostream &out, const Resource &res) {out << "I am a resource";return out;}
};void takeOwnership(Resource *res) {if (res) {std::cout << *res << '\n';} else {std::cout << "No resource\n";}
}int main() {auto ptr{std::make_unique<Resource>()};takeOwnership(ptr.get());std::cout << "Ending program\n";return 0;
} // Resource对象会在这里销毁

输出:

Resource acquired
I am a resource
Ending program
Resource destroyed

unique_ptr可以直接作为返回值返回:

#include <iostream>
#include <memory>class Resource {
public:Resource() { std::cout << "Resource acquired\n"; }~Resource() { std::cout << "Resource destroyed\n"; }friend std::ostream &operator<<(std::ostream &out, const Resource &res) {out << "I am a resource";return out;}
};std::unique_ptr<Resource> createResource() {return std::make_unique<Resource>();
}int main() {auto ptr{createResource()};return 0;
}

在C++14及之前的版本中会使用std::move来返回Resource对象,在C++17及以后版本中进行了RVO优化(https://en.wikipedia.org/wiki/Copy_elision),尽管没有显式使用std::move,编译器依然能够识别并优化这个返回过程,直接将新创建的Resource对象的所有权从createResource函数内部转移到了main函数中的ptr变量,而无需实际执行移动构造函数

2)、shared_ptr

shared_ptr和unique_ptr不同是它的所有权是可以被安全共享的,也就是说支持拷贝赋值,允许被多个人同时持有,就像原始指针一样

在底层实现中,shared_ptr采用引用计数的方式实现。引用计数最开始的时候是1,表示只有一个持有者。如果发生拷贝赋值——也就是共享的时候,引用计数就增加(为了保证并发安全,引用计数器的加1,减1操作都是原子操作),而发生析构销毁的时候,引用计数就减少。只有当引用计数减少到0,也就是说,没有任何人使用这个指针的时候,它才会真正调用delete释放内存

#include <iostream>
#include <memory>class Resource {
public:Resource() { std::cout << "Resource acquired\n"; }~Resource() { std::cout << "Resource destroyed\n"; }
};int main() {auto ptr1 = std::make_shared<Resource>(); // 工厂函数创建智能指针assert(ptr1 && ptr1.unique()); // 此时智能指针有效且唯一{auto ptr2 = ptr1; // 直接拷贝赋值,不需要使用move()assert(ptr1 && ptr2); // 此时两个智能指针均有效assert(ptr1 == ptr2); // shared_ptr可以直接比较// 两个智能指针均不唯一,且引用计数为2assert(!ptr1.unique() && ptr1.use_count() == 2);assert(!ptr2.unique() && ptr2.use_count() == 2);std::cout << "Killing one shared pointer\n";} // ptr2离开了作用域,但是没有资源被销毁assert(ptr1 && ptr1.unique()); // 此时智能指针有效且唯一std::cout << "Killing another shared pointer\n";return 0;
} // ptr1离开了作用域,Resource对象会在这里销毁

输出:

Resource acquired
Killing one shared pointer
Killing another shared pointer
Resource destroyed

在上面的例子中, ptr2在自己的作用域中被创建,然后出了作用域后ptr2虽然被销毁,但是所管理的资源却在main方法结束后才被销毁

enable_shared_from_this:

如果不小心直接在类里面返回this对象想要获得该对象的shared_ptr,那么会让一个对象被delete两次,如下:

#include <iostream>
#include <memory>class Resource {
public:Resource() { std::cout << "Resource acquired\n"; }~Resource() { std::cout << "Resource destroyed\n"; }std::shared_ptr<Resource> GetSPtr() {return std::shared_ptr<Resource>(this);}
};int main() {auto sptr1 = std::make_shared<Resource>();auto sptr2 = sptr1->GetSPtr();return 0;
}

输出:

Resource acquired
Resource destroyed
studyProject(6959,0x7ff854f04700) malloc: *** error for object 0x6000035ed1f8: pointer being freed was not allocated
studyProject(6959,0x7ff854f04700) malloc: *** set a breakpoint in malloc_error_break to debug

上面的代码其实会生成两个独立的shared_ptr,他们的控制块是独立的,所以导致Resource被释放了两次

使用enable_shared_from_this可以避免上述情况:

#include <iostream>
#include <memory>class Resource : public std::enable_shared_from_this<Resource> {
public:Resource() { std::cout << "Resource acquired\n"; }~Resource() { std::cout << "Resource destroyed\n"; }std::shared_ptr<Resource> GetSPtr() {return shared_from_this();}
};int main() {auto sptr1 = std::make_shared<Resource>();auto sptr2 = sptr1->GetSPtr();return 0;
}

shared_ptr的循环引用问题:

shared_ptr的引用计数也导致了一个新的问题,就是循环引用,这在把shared_ptr作为类成员的时候最容易出现,典型的例子就是链表节点

#include <iostream>
#include <memory>class Node {
public:using this_type = Node;using shared_type = std::shared_ptr<this_type>;Node(const std::string &name) : name(name) {std::cout << name << " created\n";}~Node() {std::cout << name << " destroyed\n";}std::string name;shared_type next; // 使用智能指针来指向下一个节点
};int main() {auto n1 = std::make_shared<Node>("n1");auto n2 = std::make_shared<Node>("n2");assert(n1.use_count() == 1); // 引用计数为1assert(n2.use_count() == 1);n1->next = n2; // 两个节点互指,形成了循环引用n2->next = n1;assert(n1.use_count() == 2); // 引用计数为2assert(n2.use_count() == 2); // 无法减到0,无法销毁,导致内存泄漏return 0;
}

输出:

n1 created
n2 created

上面的代码中,两个节点指针刚创建时,引用计数是1,但指针互指(即拷贝赋值)之后,引用计数都变成了2

这个时候,shared_ptr意识不到这是一个循环引用,多算了一次计数,后果就是引用计数无法减到0,无法调用析构函数执行delete,最终导致内存泄漏

3)、weak_ptr

weak_ptr是专门为打破循环引用而设计,它实际上不会托管对象,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。在需要的时候,可以调用weak_ptr的成员函数lock(),获取shared_ptr(强引用)

#include <iostream>
#include <memory>class Node {
public:using this_type = Node;using shared_type = std::weak_ptr<this_type>; // 注意这里,别名改用weak_ptrNode(const std::string &name) : name(name) {std::cout << name << " created\n";}~Node() {std::cout << name << " destroyed\n";}std::string name;shared_type next;
};int main() {auto n1 = std::make_shared<Node>("n1");auto n2 = std::make_shared<Node>("n2");n1->next = n2; // 两个节点互指,形成了循环引用n2->next = n1;assert(n1.use_count() == 1); // 因为使用了weak_ptr,引用计数为1assert(n2.use_count() == 1); // 打破循环引用,不会导致内存泄漏if (!n1->next.expired()) { // 检查指针是否有效auto ptr = n1->next.lock(); // lock()获取shared_ptrassert(ptr == n2);}return 0;
}

输出:

n1 created
n2 created
n2 destroyed
n1 destroyed
4)、小结

unique_ptr是一种独占资源所有权的指针,它会在栈上分配,然后在离开作用域之后进行释放,删除里面持有的对象,它只能使用move语义转移对象。所以如果你想操作一个指针,在进入作用域的时候分配好内存,然后在离开作用域的时候安全释放对象,那么可以使用它

shared_ptr所管理的资源可以被多个对象持有,并且使用引用计数策略来释放对象,如果计数没有清零,那么它所管理的资源不会释放

weak_ptr不管理对象,只是shared_ptr对象管理的资源的观察者,所以它不影响共享资源的生命周期,它用于解决shared_ptr循环引用

3、lambda表达式

1)、lambda基本使用
#include <iostream>int main() {auto func = [](int x) // 定义一个lambda表达式{std::cout << x * x << std::endl; // lambda表达式的具体内容};func(3); // 调用lambda表达式return 0;
}

C++里的lambda表达式除了可以像普通函数那样被调用,还有一个普通函数所不具备的特殊本领,就是可以捕获外部变量,在内部的代码里直接操作

#include <iostream>int main() {int n = 10; // 一个外部变量auto func = [=](int x) // lambda表达式,用=捕获{std::cout << x * n << std::endl; // 直接操作外部变量};func(3); // 调用lambda表达式return 0;
}
2)、使用lambda的注意事项

1)lambda的形式

嵌套定义lambda表达式

#include <iostream>int main() {auto f1 = []() // 定义一个lambda表达式{std::cout << "lambda f1" << std::endl;auto f2 = [](int x) // 嵌套定义lambda表达式{return x * x;}; // lambda f2std::cout << f2(10) << std::endl;}; // lambda f1f1();return 0;
}

匿名lambda表达式

#include <iostream>int main() {std::vector<int> v = {3, 1, 8, 5, 0}; // 标准容器std::cout << *find_if(begin(v), end(v), // 标准库里的查找算法[](int x) // 匿名lambda表达式,不需要auto赋值{return x >= 5; // 用做算法的谓词判断条件})<< std::endl; // 语句执行完,lambda表达式就不存在了return 0;
}

2)lambda的变量捕获

lambda的变量捕获要点:

  • [=]:表示按值捕获所有外部变量,表达式内部是值的拷贝,并且不能修改
  • [&]:按引用捕获所有外部变量,内部以引用的方式使用,可以修改
  • 可以在[]里明确写出外部变量名,指定按值或者按引用捕获
int main() {int x = 33; // 一个外部变量auto f1 = [=]() // lambda表达式,用=按值捕获{//x += 10; // x只读,不允许修改};auto f2 = [&]() // lambda表达式,用&按引用捕获{x += 10; // x是引用,可以修改};auto f3 = [=, &x]() // lambda表达式,用&按引用捕获x,其他的按值捕获{x += 20; // x是引用,可以修改};return 0;
}

3)泛型的lambda

在C++14里,lambda表达式可以实现泛型化,相当于简化了的模板函数

#include <iostream>int main() {auto f = [](const auto &x) // 参数使用auto声明,泛型化{return x + x;};std::cout << f(3) << std::endl; // 参数类型是intstd::cout << f(0.618) << std::endl; // 参数类型是doublestd::string str = "matrix";std::cout << f(str) << std::endl; // 参数类型是stringreturn 0;
}
3)、小结
  1. lambda表达式是一个闭包,能够像函数一样被调用,像变量一样被传递
  2. 可以使用auto自动推导类型存储lambda表达式,但C++鼓励尽量就地匿名使用,缩小作用域
  3. lambda表达式使用[=]的方式按值捕获,使用[&]的方式按引用捕获,空的[]则是无捕获(也就相当于普通函数)
  4. 捕获引用时必须要注意外部变量的生命周期,防止变量失效
  5. C++14里可以使用泛型的lambda表达式,相当于简化的模板函数

参考:

07 | const/volatile/mutable:常量/变量究竟是怎么回事?

C++ 中让人头晕的const & constexpr

08 | smart_ptr:智能指针到底“智能”在哪里?

写给[C++ ]新人智能指针避坑指南

10 | lambda:函数式编程带来了什么?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/41853.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决Docker Desktop启动异常 Docker Desktop- WSL distro terminated abruptly

异常 当打开Docker Desktop时候&#xff0c;启动docker引擎时&#xff0c;提示 加粗样式文本信息 Docker Desktop - WSL distro terminated abruptly A WSL distro Docker Desktop relies on has exited unexpectedly. This usually happensas a result of an external entit…

Vue2基础 14:自定义指令

自定义指令 1 函数式1.1 案例--v-text放大10倍 2 对象式2.1 案例--v-fbind默认获取焦点&#xff08;函数式&#xff09;2.2 案例--v-fbind默认获取焦点&#xff08;对象式&#xff09; 3 自定义指令容易犯的错4 全局指令写法&#xff08;参考过滤器写法&#xff09;&#xff1a…

Go 依赖注入设计模式

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

系统重装

待更新 重置win11 双系统删除其中一个&#xff0c;并将格式化后的空间并入

跟着峰哥学java 第四天 商品分类 前后端显示

1.后端 1.1mybatis-plus分页查询配置 在商品热卖数据中&#xff0c;只让其显示八条数据 将要使用分页 也就是service.page方法 此时需要配置 mp拦截器 Configuration public class MybatisPlusConfig {Beanpublic PaginationInterceptor paginationInterceptor() {return …

模型训练之数据集

我们知道人工智能的四大要素&#xff1a;数据、算法、算力、场景。我们训练模型离不开数据 目标 一、数据集划分 定义 数据集&#xff1a;训练集是一组训练数据。 样本&#xff1a;一组数据中一个数据 特征&#xff1a;反映样本在某方面的表现、属性或性质事项 训练集&#…

星辰宇宙动态页面vue版,超好看的前端页面。附源码与应用教程(若依)

本代码的html版本&#xff0c;来源自“山羊の前端小窝”作者&#xff0c;我对此进行了vue版本转换以及相关应用。特此与大家一起分享~ 1、直接上效果图&#xff1a; 带文字版&#xff1a;文字呼吸式缩放。 纯净版&#xff1a; 默认展示效果&#xff1a; 缩放与旋转后&#xf…

mysql5.6的安装步骤

1.下载mysql 下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 在这里我们下载zip的包 2.解压mysql包到指定目录 3. 添加my.ini文件 # For advice on how to change settings please see # http://dev.mysql.com/doc/refman/5.6/en/server-configurat…

tongweb+ths6011测试websocket(by lqw)

本次使用的tongweb版本7049m4&#xff0c;测试包ws_example.war&#xff08;在tongweb安装目录的samples/websocket下&#xff09;&#xff0c;ths版本6011 首先在tongweb控制台部署一下ws_example.war,部署后测试是否能访问&#xff1a; 然後ths上的httpserver.conf的參考配…

本地部署到服务器上的资源路径问题

本地部署到服务器上的资源路径问题 服务器端的源代码的静态资源目录层级 当使用Thymeleaf时&#xff0c;在templates的目录下为返回的html页面&#xff0c;下面以两个例子解释当将代码部署到tomcat时访问资源的路径配置问题 例子一 index.html&#xff08;在templates的根目录…

VBA初学:零件成本统计之三(获取材料外协的金额)

第三步&#xff0c;从K3的数据库中获取金额 我这里是使用循环&#xff0c;通过任务单号将金额汇总出来&#xff0c;如果使用数组的话&#xff0c;还要按任务单写GROUP&#xff0c;还要去对应&#xff0c;不如循环直接一点 获取材料和外协金额的表格Sub getje()Dim rowcount A…

leetcode-每日一题

3101. 交替子数组计数https://leetcode.cn/problems/count-alternating-subarrays/ 给你一个 二进制数组 nums 。 如果一个 子数组 中 不存在 两个 相邻 元素的值 相同 的情况&#xff0c;我们称这样的子数组为 交替子数组 。 返回数组 nums 中交替子数组的数量。 示例 …

3-2 梯度与反向传播

3-2 梯度与反向传播 主目录点这里 梯度的含义 可以看到红色区域的变化率较大&#xff0c;梯度较大&#xff1b;绿色区域的变化率较小&#xff0c;梯度较小。 在二维情况下&#xff0c;梯度向量的方向指向函数增长最快的方向&#xff0c;而其大小表示增长的速率。 梯度的计算 …

如何第一次从零上传项目到GitLab

嗨&#xff0c;我是兰若&#xff0c;今天想给大家说下&#xff0c;如何上传一个完整的项目到与LDAP集成的GitLab&#xff0c;也就是说这个项目之前是不在git上面的&#xff0c;这是第一次上传&#xff0c;这样上传上去之后&#xff0c;其他小伙伴就可以根据你这个项目的git地址…

Lua语言入门

目录 Lua语言1 搭建Lua开发环境1.1 安装Lua解释器WindowsLinux 1.2 IntelliJ安装Lua插件在线安装本地安装 2 Lua语法2.1 数据类型2.2 变量全局变量局部变量命名规范局部变量作用域 2.3 注释单行注释多行注释 2.4 赋值2.5 操作符数学操作符比较操作符逻辑操作符连接操作符取长度…

moonlight+sunshine+ParsecVDisplay ipad8-windows 局域网串流

1.sunshine PC 安装 2.设置任意账户密码登录 3.setting 里 network启用UPNP IPV4IPV6 save apply 4.ParsecVDisplay虚拟显示器安装 5.ipad appstore download moonlight 6.以ipad 8 为例 2160*1620屏幕分辨率 7.ParsecVDisplay里面 custom设置2160*1620 240hz&#xff0c;…

银河麒麟V10 SP1 审计工具 auditd更新

前言 银河麒麟V10 SP1 审计工具 auditd 引发的内存占用过高&#xff0c; 内存使用率一直在 60% 以上&#xff0c; 内存一直不释放 排查 可以使用ps或者top查看系统进程使用情况 ps -aux|sort -k4nr|head -n 5 发现银河麒麟审计工具 auditd 一直占用内存不释放 解决 办法一…

进程的初步认识

目录 一、硬件方面介绍 1.冯诺依曼体系结构 2.存储分级 二、软件 方面 1.操作系统是一款进行管理的软件&#xff0c;它可以管理硬件也可以管理软件 2.操作系统如何管理&#xff1f; 三、进程 1.概念 总结 四、linux中对进程的管理 1.task_ struct内容分类 2.查看进…

PageCache页缓存

一.PageCache基本结构 1.PageCache任务 PageCache负责使用系统调用向系统申请页的内存,给CentralCache分配大块儿的内存,以及合并前后页空闲的内存,整体也是一个单例,需要加锁. PageCache桶的下标按照页号进行映射,每个桶里span的页数即为下标大小. 2.基本结构 当每个线程的…

如何使用uer做多分类任务

如何使用uer做多分类任务 语料集下载 找到这里点击即可 里面是这有json文件的 因此我们对此要做一些处理&#xff0c;将其转为tsv格式 # -*- coding: utf-8 -*- import json import csv import chardet# 检测文件编码 def detect_encoding(file_path):with open(file_path,…