当前位置: 首页 > news >正文

现代C++学习笔记

目录
  • CS106L Standard C++ Programming
    • philosophies of C++ design
    • stream
    • modern C++ data type
    • STL
    • template
    • class
      • move semantics
      • inheritance
    • namespace
    • RAII & samrt pointers
  • A Tour of C++ by Bjarne Stroustrup
    • const & constexpr (C++11)
    • NULL & nullptr(C++11)
    • enum
    • union & std::variant(<variant> C++17 )

CS106L Standard C++ Programming

philosophies of C++ design

  1. 程序员拥有完全的控制权,并需要对此负责
  2. 在代码中直接表达思想和意图
  3. 尽可能在编译时强制执行安全性
  4. 不浪费空间和时间
  5. 将杂乱的特性模块化
  6. 向下兼容

stream

std::ostringstream 定义一个写缓冲区,采用 << 从缓存区指针处写入字符串覆盖

std::istringstream 定义一个读缓存区,采用 >> 依据右变量的类型,从缓冲区指针开始读入并进行类型转换。更具体地说,指针会一直读取并向后移动直到遇到空白或制表符,然后指针重新指向空白或制表符的前一个位置。下一次进行缓存区读时,指针会跳过所有空白和制表符,直到下一个字符

事实上,std::istringstream是一种 std::istream,而 std::cin 是一个 std::istream类型的对象,std::cout同理

使用缓冲区而不是立即输出,避免了系统调用读写的昂贵开销,但 std::cerr是不使用缓存区直接输出

缓冲区的状态:Good/Fail/EOF/Bad bit,可通过一个缓冲区的成员函数进行访问 如.good()

当尝试读入的类型于缓存区中识别的类型不一致时,fail位会被设置为1,并且此后对该缓冲区的读入都会被冻结

modern C++ data type

类型别名 eg: using iter = std::vector <int>::iterator

auto 编译器从初值赋值自动推断类型 注意:用 constauto变量赋初值的时候不会有常量性

std::tuple类似 std::pair,用来表示多元组,通过 get<i>(x)访问多元组 x的第 i个元素,类似语法通过 set来设置元素的值

统一初始化:按照结构体中变量定义的顺序使用花括号进行初始化

STL

ez

template

C++ 使用 template 实现了类似泛型编程的功能

对于模板函数,可以使用 template <class T> 声明使用了类型为 T的参数

在编译的时候,编译器会对应不同的类型进行实例化,替换生成对应类型的函数;你也可以手动进行实例化 mymax<int>(114,514)加快编译速度

同样的,自定义的函数也可以作为模板函数的参数。注意在C++标准库中,部分模板函数只接受谓词函数,即返回布尔值的函数

lambda 函数:创建的是一个对象,但是表现得像一个轻量级函数,其声明如下

auto fun = [capture-clause](parameters)->return-value{//body};

编译器实际上会把它转化成一个类,由于这个类的名称未知,需要使用 auto

其中的 capture-clause 规定了该lambda 函数能够捕获的外部变量

class

使用构造函数和析构函数,可以定义一个类被创建和回收时的行为

操作符重载分为成员函数和非成员函数两种,前者在类中被定义,后者在类外被定义

部分操作符只能以其中一种形式进行重载,如我们重载std::cout<<,使得其能够输出我们自己定义的一个分数类,就只能采用非成员函数进行定义。在成员函数中,可以使用*this得到一个指向当前类的指针

操作符重载的基本原则是和基本类型规则保持一致,如定义分数类的+=时,为了和普通int a(a += 1) += 2;规则相匹配,运算符的返回值应该是一个对当前变量的引用,而非一个拷贝值

采用friend定义友元函数,使得一个位于类外部的函数能够访问类的私有变量

对于一个类,编译器会自动为其生成四种函数

  1. 构造函数(default constructor):在该对象被创建时调用
  2. 拷贝构造函数(copy constructor):在创建一个新的对象,用旧有的对象对其进行初始化赋值时被调用
  3. 拷贝赋值函数(copy assignment):在对于一个已经存在的对象,用旧有的对象对其进行覆盖赋值时被调用
  4. 析构函数(destructor):在一个对象超出其作用域时被调用

编译器自动生成的函数可能与我们预期的行为不一样,如对于一个自己实现的变长数组的类,会采用指针指向一个地址,当进行拷贝的时候,另外的一个对象指针也会指向该地址,而不是我们期望的深拷贝,即创建该地址表示的数组的副本

move semantics

将一个对象插入\(vector\)的末尾,\(push\_back\)方法会先创建该对象的副本,再将该对象的一个副本插入\(vector\)末尾,这样就需要创建对象的两个副本;而使用\(emplace\_back\),会在\(vector\)内直接创建该对象的副本,更为高效

左值与右值:左值出现在等号左侧,右值在右侧。左值有名称和标识,可以使用取地址符找到该值的地址;右值没有名称或标识,是临时值。

左值引用:&

右值引用:&& 可以延长临时值的生命周期,如 auto&& prt = a +b;

类中还有着以下两个特殊成员函数:

  1. 移动构造函数(move constructor):在创建一个新的对象,用右值对其进行初始化赋值时被调用
  2. 移动赋值函数(move assignment):在对于一个已经存在的对象,用右值对其进行覆盖赋值时被调用

以上两个函数均以右值引用作为其参数,对右值引用进行拷贝,由于右值是临时的,保证了该类不会被通过右值的指针修改。在这两个移动函数中,传入的右值引用有地址,是左值,如果直接使用=会调用拷贝函数,造成额外的开销,可以使用std::move(rhs)将其转化为右值,将其视为临时值进行移动。

使用std::move实现高效的swap

template <class T>
void swap(T &a, T &b) {T tmp = std::move(a);a = std::move(b);b = std::move(a);
}

时间复杂度取决于类型T的移动构造函数和移动赋值函数的时间复杂度。如std::vector<T>二者都为O(1),总复杂度也为O(1)

inheritance

继承:将一个类(派生类)基于另一个类(基类)来构建

class Derived : public Base {public://somethingprivate://something
};

这被称为public继承,基类的所有成员在派生类中访问权限不变。而protected/private继承会将基类的publicprotected访问权限变为protected/private

在基类中,通过在成员函数前加入关键字virtual得到虚函数,使得该函数可以在派生类中被重写,一旦被声明为virtual,在整个继承链中都是虚函数

通过使用=0可以构建纯虚函数,必须在派生类中进行重写。至少包含一个纯虚函数的类称为“抽象类”,无法被实例化

派生类通过关键字override对基类的虚函数进行重写

基类的虚函数通过关键字final,禁止派生类对该函数重写

注意:当派生类定义了与基类成员中名称相同的变量时,基类的变量会被隐藏,即访问该变量时只能得到派生类的值

当派生类超出其生命周期时,会先调用派生类的析构函数,再调用基类的析构函数

class Animal {public:std::string name;int age;Animal(std::string n, int ag) {name = n; age = ag;}
};class Dog: public Animal {std::string type;public:Dog(std::string name, int ag, std::string tp) : Animal(name, ag),type(tp){} virtual void daily() = 0;virtual void eat() = 0;virtual void sleep() = 0;
};class My_Dog: public Dog {public:My_Dog(std::string name, int ag, std::string tp) : Dog(name, ag, tp) {}void eat() override final {std::cout << "eat meat" << '\n';}void sleep() override final {std::cout << "sleep 10 hours per day" << '\n';}void daily() override final {eat();sleep();}
};

模板被称为静态多态性,在编译时确定具体调用哪个函数;而继承被称为动态多态性,在运行时根据对象的实际类型确定调用哪个函数

namespace

ez

RAII & samrt pointers

考虑以下代码

void fun() {int *ptr = new int;dosomething...delete ptr;return;
}

new的作用是在堆上动态分配空间,在fun函数结束后,这些空间不会被释放,除非调用了delete。但是函数主体部分可能有提前return,抛出异常等情况导致delete为被执行。这样就会导致堆上的一块空间一直被占用,这被称为内存泄露

RAII(Resource Acquisition Is Initialization):若要获得资源,应该始终在构造函数中进行;若要释放资源,应该始终在析构函数中进行。
std::ifstream就符合RAII思想:采用文件名对其缓冲区进行初始化,在超出作用域调用析构函数是会关闭文件,而无需显式地关闭文件。

基于RAII思想,现代C++提供了智能指针,如std::unique_ptr std::shared_ptr
std::unique_ptr会在调用析构函数时自动释放其所指向的堆上空间,需要注意的是,如果对std::unique_ptr执行拷贝操作,可能导致堆上的空间被多次释放而出错,因此std::unique_ptr没有实现赋值拷贝和构造拷贝
而堆上的空间会在指向其的所有std::shared_ptr都调用析构函数后才会释放,注意std::shared_ptr效率上不如std::unique_ptr

A Tour of C++ by Bjarne Stroustrup

const & constexpr (C++11)

const修饰的值不会被修改,这个值可以在运行时被计算

constexpr修饰的是在编译时计算的常量,可以提高运行时性能

函数必须使用constexprconsteval(C++20)修饰,才能用于初始化一个constexpr常量;

二者区别在于前者修饰的函数也允许运行时计算,后者只能在编译时计算。同时都必须是纯函数,不能修改全局变量

NULL & nullptr(C++11)

NULL的本质是一个被定义为0的宏,当作为参数传入函数中时,会被编译器当做一个整数而非指针

nullptr有自己的类型std::nullptr_t,可以隐式转换为任意指针类型,或者采用static_cast进行显式转换

enum

enum(枚举)是一种简单的用户自定义类型,通过使用助记符代替整数,用于表示少量值的集合

C风格的枚举为enumC++则为enum class,后者有独立作用域,避免了命名污染,以下介绍都以enum class为例

enum class可以指定枚举的类型与值,默认为int0-index的整数,语法如下

enum class Days : int {Monday = 1,		//default = 0Tuesday = 2,	// 1Wednesday = 3,	// 2Thursday = 4,	// 3Friday = 5,		// 4Saturday = 6,	// 5Sunday = 7		// 6
};

类似命名空间,通过::访问成员,如Days today = Days::Friday;

枚举类不支持隐式类型转换,只能进行显式类型转换,如int today = static_cast<int> (Days::Friday);

枚举类成员只默认定义了赋值,初始化和比较,可以为其自定义其他操作符。占用的空间和一个枚举类型的大小相同

union & std::variant(<variant> C++17 )

union(联合)通过将不会被同时使用的成员放在同一个地址,避免了使用两段地址造成空间浪费。可以采用enum class+union来实现

例如当一棵树只有叶子节点需要保存值的时候,可以这么写

enum class Node_type : bool {node_ptr,val
};struct Node {Node_type t;union value {Node *p;long long val;}v;void fun() {if (t == Node_type::node_ptr) {//do something}else {std::cout << v.val << '\n';}}
};

可以使用std::variant简化以上代码

struct Node {std::variant <Node*, long long> v; void fun() {if (std::holds_alternative<Node *>(v)) {//do something}else {std::cout << std::get <long long> (v) << '\n';}}
};
http://www.jsqmd.com/news/350785/

相关文章:

  • 2026年企业资产管理系统选型指南:五大核心厂商能力全景解析 - 品牌2026
  • 2026年不动产与大型集团资产管理系统选型指南:五大优质服务商解析 - 品牌2026
  • 2026年主数据管理公司怎么选?数据经营分析与经营监控平台选型指南 - 品牌2026
  • 2026年重庆职业学校权威榜单 全景优质院校适配择校指南 覆盖升学 - 深度智识库
  • 为什么全文降AI比局部修改更有效?嘎嘎降AI实测分享 - 我要发一区
  • 学习进度 21
  • 2026年大型集团私有化部署资产管理系统推荐,涵盖房地产、产业园、物业、城投等资产管理系统 - 品牌2026
  • 2026 年大型集团不动产资产管理系统推荐,国有资产管理系统私有化部署公司怎么选 - 品牌2026
  • 主轴刹车器选购指南:从功能定位到关键指标详解 - 品牌推荐大师1
  • 2026年爬坡能力强的电动车排行榜,分析哪家电动车品牌速度快 - 睿易优选
  • 热销榜单:2026年智能马桶品牌排行,帮助你找到舒适性高且售后有保障的最佳选择 - 睿易优选
  • Libero PolarFire SoC ICICLE 开发板 新建一个简单的工程(四) 串口控制和LED控制 C代码 移植 FreeRTOS
  • 防噪音必看!四川三元+4大优质品牌,2025隔音窗厂家TOP5推荐 - 深度智识库
  • 2026国内GEO公司排名前十!权威榜单出炉,企业选型必看 - 品牌测评鉴赏家
  • LangGraph深度解析:打造可控可维护的大模型Agent应用
  • 【长沙学术会议】第二届人工智能与材料国际会议 (IEEE ICAIM 2026)
  • 2026年大型集团不动产资产管理系统选型指南:五大优质服务商深度解析 - 品牌2026
  • 第九天
  • 程序员必藏:大模型检索增强技术深度解析:RAG→Agentic RAG→Graph-R1演进之路
  • 2026年市场有实力的乳胶床垫实力厂家推荐排行榜:专业的乳胶床垫品牌大揭秘 - 睿易优选
  • 2026年减压阀代理商推荐:5 家优质供应商盘点,精准匹配工业流体控制需求 - 博客万
  • 【毕业设计】基于python的媒体资源管理系统设计与实现(源码+文档+远程调试,全bao定制等)
  • 【ai代码审计+黑盒测试】https://bugbunny.ai/ 是漏洞赏金猎人、 安全团队和渗透测试人员加速工作的完美助手
  • 程序员必看!大模型AI学习全攻略+资料包,助你快速入行高薪岗位,收藏不亏!大模型就业行情揭秘:哪些岗位最吃香?
  • 小白必看!2026年OpenClaw入坑指南!OpenClaw部署技术扫盲!
  • 超级浏览器哪个好用?超级浏览器选择建议! - Roxy指纹浏览器
  • 大模型Agent全领域应用:从电影生成到Web导航,附源码,程序员必收藏!
  • 煤炉Mercari被封?2026最全最新封号底层逻辑指南
  • ✅2026年小白必看的OpenClaw(Clawdbot)一键部署教程更新了!
  • 生成式搜索浪潮下,GEO 成品牌增长新引擎 - 品牌测评鉴赏家