C++ 自定义类型

自定义类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Message.h
#pragma once
#include <string>

using namespace std;

class Message {

public:
// 构造函数
Message(int fromUserId, int toUserId, string& messageContent);
void SendMessage();
const int MessageId;
const int ToUserId;
const int FromUserId;
const string& MessageContent;
static inline int MsgContent {0};

private:
int createMessageId();
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "Message.h"
#include <iostream>
#include <random>

// 构造函数,成员初始化列表
Message::Message(int fromUserId, int toUserId, std::string &messageContent) :
FromUserId{ fromUserId },
ToUserId{ toUserId },
MessageContent{ messageContent },
MessageId{ createMessageId() } {
MsgContent += 1;
}

void Message::SendMessage() {
cout << "From:" << FromUserId << endl
<< "To:" << ToUserId << endl
<< "Message:" << MessageContent << endl
<< "MessageId:" << MessageId << endl;
}

int Message::createMessageId() {
std::random_device dev;
return dev();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 实际使用
#include "Message.h"
#include <iostream>
#include <string>

using namespace std;

Message* CreateMessage() {
string msgContent{"测试 class 嘟嘟。"};
// 这里 msgContent 传递引用到构造函数,而 msgContent 会在 CreateMessage 函数结束后被回收
// 所以实际上 msgContent 在 Message 的实例中无法访问
Message* msg{new Message(12, 34, msgContent)};
return msg;
}

int main() {
auto msg {CreateMessage()};
msg->SendMessage();
delete msg;
return 0;
}

访问修饰符

  • public 后定义的都是公开成员,可访问:
    • 该类中的函数
    • 子类的函数
    • 该类的实例
  • private 后定义的是私有成员 ,可访问:
    • 该类中的函数
  • protected 可访问:
    • 该类的函数
    • 子类的函数
  • 同一访问修饰符可在类里多次使用,比如多个 publc: 或多个 private

构造函数

  • 和类同名
  • 没有返回值
  • 如果没有定义,编译器会添加一个空的构造函数(如果任一构造函数,则不会添加)
  • 可以有多个参数不同的构造函数

成员初始化列表

  • 先于构造函数执行
  • const 类型成员变量不允许重新复制,只能在成员初始化列表中赋值
  • 可以调用自定义函数甚至是另一个构造函数

静态成员

  • static 关键字标记静态成员
  • 在同一类里只有一份实例
  • 可以在同一类型的不同实例间共享数据
  • C++17 以后可以使用 inline 关键字成为内联的静态成员变量
    • C++17 只能在类声明外部初始化 int Message::MsgCount{0};
  • 可以通过实例访问 msg->MsgCount
  • 可以通过类访问 Message::MsgCount 推荐使用这个

析构函数

1
2
3
4
5
6
7
8
//头文件,public节
~Message(); //析构函数的定义
UserInfo* userInfo;

//源码文件:
Message::~Message() { //析构函数的实现
delete userInfo
}
  • 释放对象时自动执行
    • delete someInstance
    • 栈帧自动释放
  • 用来做一些收尾工作, 比如释放引用对象,关闭数据库链接等
  • 原则
    • 凡是使用 new 创建的实例都需要自己手动 delete
    • 永远不要在析构函数里抛出异常
    • 只能有一个
    • 没有参数
    • 必须是 public

赋值运算符重载

将一个对象的值赋值给另一个对象

  • 不推荐使用这个特性

  • ```c++
    // 定义
    class MyClass {
    public:

    // 类的成员和其他方法
    
    // 赋值运算符重载
    MyClass& operator=(const MyClass& other) {
        // 实现赋值操作
        if (this != &other) {  // 避免自赋值
            // 复制 other 的数据到当前对象
            // ...
        }
        // 返回当前对象的引用
        return *this;
    }
    

    };

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    - ```c++
    // 使用
    int main() {
    MyClass obj1;
    obj1.data = 42;

    MyClass obj2;
    obj2.data = 10;

    // 使用赋值运算符重载
    obj2 = obj1;

    std::cout << "obj2.data: " << obj2.data << std::endl; // 输出 42
    return 0;
    }
  • 如果没有手动创建,编译器会自动生成一个赋值运算符函数

    • 默认的赋值运算符函数会把 obj1 的所有成员变量赋值给 obj2 obj2 = obj1
  • 可以使用 delete 手动删除

    • YourType operator= (const YourType& msg) = delete;

复制构造函数

  • 不推荐使用这个特性

  • 编译器默认生成

  • 复制一个实例

    • ```c++
      Message a(9);
      Message b{ a };
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30

      - 可以删除

      - `YourType(const YourType& obj) = delete`

      ### 单例模式

      - 保证一个类在一定范围里只有一个实例

      - ```c++
      class Message
      {
      public:
      Message(const Message& msg) = delete;
      void operator= (const Message& msg) = delete;
      ~Message() {
      //堆空间被释放后,把静态变量instance置为nullptr,以备下次重新创建新的对象
      instance = nullptr;
      }
      // 使用 getInstance 创建实例,不再手动调用 new
      static Message* getInstance() {
      if (instance == nullptr) {
      instance = new Message();
      }
      return instance;
      }
      private:
      inline static Message* instance{ nullptr };
      };

结构: struct

  • 来源于 C 语言

  • 类似 class

  • 默认成员都是 public

    • struct MyStruct
      {
          int a = 2;
          void func() {
              cout << "func:" << endl;
          }
      };
      int main() {
          MyStruct myStruct;
          myStruct.func();
      }
      
  • 常用来聚合数据

C++ 引用

引用

int& ref_a {a},声明引用 ref_a

  • 引用作为一个变量的别名
  • 使用是无需解引用运算符
  • 不能为空
  • 避免值的复制,减少开销
  • 常量引用避免引用指向的变量被修改 const int& param
  • 主要用于函数参数传递和函数返回值,使得函数能够直接修改调用者提供的值
1
2
3
4
5
void ref() {
auto nn {123};
auto& ref_n {nn};
cout << "reference:" << ref_n << endl; // reference: 123
}

指针

int* pointer_a {1}, 声明指针 pointer_a

  • 指针直接存储变量的内存地址

  • 直接表现为 16 进制数字,进行四则运算表示地址的偏移

    • ```c++
      // 加法运算
      int arr[5] = {1, 2, 3, 4, 5};
      int* ptr = arr;
      ptr = ptr + 2; // 移动两个整数的位置,即 arr[2]
      1
      2
      3
      4
      5
      6

      - ```c++
      // 减法
      int arr[5] = {1, 2, 3, 4, 5};
      int* ptr = &arr[4];
      ptr = ptr - 2; // 移动两个整数的位置,即 arr[2]
  • 可以为空 nullptr

  • &操作符是取地址运算符,用于获取变量在内存中的地址

  • *是解引用运算符,用于找到指针所指向的内存的具体内容,*pointerA = *pointerA + 1;语句的作用就是修改了这块内存的内容

  • 可以指向栈上的地址

  • 可以指向堆上的地址

1
2
3
4
5
6
7
8
9
10
11
void pointer() {
int* pointerB { nullptr };
cout << pointerB << endl; // 0x0
int a = 1;
int* pointerA { &a };
cout << pointerA << endl; // ...
cout << *pointerA << endl; //1
*pointerA = *pointerA + 1;
cout << *pointerA << endl; //2
cout << a << endl; //2
}

C++ 变量声明与定义

1
2
auto a {12} // a 是 int 类型
auto pi {3.141592} // pi 是 double 类型
  • 使用 {} 初始变量,编译器会检查数据是否匹配类型,比如 int b {3.5} 会编译不通过,使用 = 号会通过编译,并将将 double 3.5 缩减为 int 3
  • auto 关键字自动推导类型, 代替那些冗长、复杂的变量类型声明。
1
2
3
// auto 不是银弹
auto str {"hello"}; //const char*
std::string str2 {"hello"}; //std::string

命名空间

  • 写法 std::string (std 是命名空间,string 是 std 中定义的类)
  • 避免变量名冲突
  • 封装复杂业务逻辑
1
2
3
4
std::string ss {"hello world"};
// 使用 using namespace 简化
using namespace std;
string s2 {"hello cpp"}

静态变量

  • 只会在程序开始运行时初始化一次
  • 在程序声明周期里一直存在
  • 存储在数据段
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;
void testStaticVar() {
static int a{1};
cout << a << endl;
a = a + 1;
}
int main() {
testStaticVar(); //输出:1
// 重复调用,a 不会重新初始化
testStaticVar(); //输出:2
return 0;
}

全局变量

  • 存储在数据段

局部变量

  • 局部块中的变量会隐藏外部块的同名变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;
int a{ 1 };
int main() {
int a { 123 };
cout << "外部的a:" << a << endl; //外部的a:123
{
cout << "外部的a:" << a << endl; //外部的a:123
int a{ 456 };
cout << "内部的a:" << a << endl; //内部的a:456
a = a + 1;
cout << "内部的a:" << a << endl; //内部的a:457
//使用限定符访问全局变量
cout << "全局的a:" << ::a << endl; //全局的a:1
}
cout << "外部的a:" << a << endl; //外部的a:123
cout << "全局的a:" << ::a << endl; //全局的a:1
}

数字

  • int, float, double
  • 可以使用 lone, short, unsigned, signed 等修饰符去改变 int 等类型的位数
  • C++ 在不同平台上同一类型可能是不同的位数,比如 int 可能是 2 或 4 位
  • math.h 标准库提供了一系列操作数字的方法,比如 floor, round 等

字符串

  • char 定义字符串
  • 字符\0 标志字符串终止
  • 标准库提供 string 类
  • R"()"原始字符串(类似 js 里的 模版字符串)

C++ 编译流程

编译流程

CompileAndLink.png

  • 预处理环节完成 #include 等宏和常量的替换
  • 编译过程,生成的机器指令放一堆 .obj 文件中
  • 链接过程将编译产生的 obj 文件和引用的库链接到一起,生成可执行文件或动态链接库

预处理指令 #pragma once

告诉预处理器这个文件只会被处理一次,避免多次引用同一文件后存在内容

应用程序的内存布局

  • 内核空间:用于存储操作系统和驱动程序为进程提供的临时机器指令和中间变量。
  • 映射段:用于装载或映射动态链接库,也常用于将文件内容映射到内存中。
  • 代码段:用于存放应用程序的机器指令,为了防止指令被其他程序修改,代码段是只读的。
  • 数据段:用于存储全局变量、静态变量(static)和常量数据(const)。
  • :用于存储应用程序运行过程中申请的内存空间,比如使用 malloc 方法或 new 关键字申请的内存。
  • :用于存储函数的局部变量、参数、返回值及调用者的上下文信息。

ProcessMemory.png

2023-11-07.md 栈与栈帧

stackMemory.png

  • 空间中分配内存的变量不需要程序员手动销毁,栈帧销毁时栈帧上的变量会被自动销毁。
  • 栈的总内存大小是固定的,而且非常小,递归操作容易爆栈
  • 操作栈上的内存效率很高

  • 使用 new 操作符返回堆空间地址(指针)
  • 需要开发者手动释放,使用 delete 关键字
  • 堆内存容量大,适合放大对象或数组

C++ 环境配置

使用 CLion 开发,

目录与依赖项

C++ 程序通常包含两种文件

  • 头文件 .h ,负责定义类型、方法和变量
  • 源码文件 .cpp, 负责实现头文件中的定义

源码文件需要引入头文件

1
2
// a.cpp 引入 .h
#include "a.h"

第三方也通过 include 引入,要注明相对路径(标准库或系统库不用,IDE 通常会自动配置)

1
2
3
// 双引号会同时搜索依赖库和用户文件,只是优先搜索用户文件;尖括号则仅搜索依赖库
#include "ohterlib/xxx.h"
#include <iostream>

字符集与预处理器定义

  • 字符编码在 IDE 里设置(CLion 默认是 UTF-8)

  • 自定义预处理器宏可以在源文件、头文件、CMakeLists.txt 文件中定义

    • ```c++
      // 源文件定义
      #define MY_MACRO 1
      int main() {
      if (MY_MACRO) {
      // 宏 MY_MACRO 的值为 1
      
      }
      return 0;
      }
      1
      2
      3
      4
      5
      6
      7

      - ```c++
      // CMakeLists.txt 中定义
      // 语法 add_definitions(<宏名> [<值>])
      add_definitions(-DMY_MACRO=1) // MY_MACRO 前面的 -D 表示预处理宏
      // 在CMake中,-D 选项用于定义预处理宏(Preprocessor Macros)。
      // -D 后面跟着宏的名称和可选的值。宏定义后,编译器会在编译源代码之前将其传递给预处理器

切换 debug 和 release

更改路径:设置 -> CMake -> 在 Build Type 下拉列表中,选择 DebugRelease

Debug 模式

  • 编译器会生成调试信息,以便调试器使用。
  • 程序会启动调试器。
  • 程序会在调试器的控制下运行。

Release 模式

  • 编译器会生成最优化的代码。
  • 程序不会启动调试器。
  • 程序将以最快的速度运行。

Node.js v21

Node.js 21

  • 内置 Websocket 客户端, 使用 --experimental-websocket 标志启用

  • writeFile 文件系统函数添加 flush 可选项。

    • 启用该选项,会在成功写入数据时强制同步刷新数据到永久存储(之前不会立即刷新到永久储存,从而使读取操作获取旧数据)
    • 已添加 flush 选项的函数
      • filehandle.createWriteStream
      • fsPromises.writeFile
      • fs.createWriteStream
      • fs.writeFile
      • fs.writeFileSync
  • 添加全局 navigator, 目前 navigator.hardwareConcurrency 这一个对象

  • Object 和 Map 添加了静态方法 groupBy()

  • 使用 --experimental-default-type 将默认模块类型设置为 ESM

Node.js 21.1.0

  • 新增 --experimental-detect-module 检测没有在 packge.json 标明 type 同时文件后缀不是 .mjs/.cjs 的普通 js 文件是否是 ES 模块(推荐所有项目都添加 type 字段,即使是 commonjs 模块)
  • filesystem appendFile 添加 flush option

Next.js App Router

Next.js 13.4

  • App Router (Stable)

    • 使用文件系统作为 API

      • ```js
        // app/layout.js

        // New: App Router ✨
        // The root layout is shared for the entire application
        export default function RootLayout({ children }) {
        return (

        <html lang="en">
          <body>{children}</body>
        </html>
        

        );
        }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16

        - 自由导入任何 CSS 文件

        - 与 React Suspense 深度集成,getServerSideProps 不再阻止交互

        - 默认所有组件都是 React Server 组件

        - ```js
        export default async function Page() {
        const res = await fetch('https://api.example.com/...');
        // The return value is *not* serialized
        // You can use Date, Map, Set, etc.
        const data = res.json();

        return '...';
        }
    • 默认将页面标记为 transitions,使路由转换能够中断

    • 自动代码分割

      • ```js
        import { getUser } from ‘./auth’;
        import { Dashboard, Landing } from ‘./components’;

        export default async function Layout() {
        const isLoggedIn = await getUser();
        // isLoggedIn 是 false 时, 不会被传输到客户端
        return isLoggedIn ? : ;
        }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25

        - Turbopack (Beta): 使用 `next dev --turbo` 启用

        - Server Actions (Alpha)

        - [Server Action 的背后原理,没有魔法,只是简单函数](https://zenn.dev/cybozu_frontend/articles/server-actions-deep-dive)

        - 在服务端执行任何操作,和客户端代码轻松集成,不需要再写 api 代码了

        ```js
        import kv from './kv';

        // 集成到 form
        export default function Page({ params }) {
        async function increment() {
        'use server';
        await kv.incr(`post:id:${params.id}`);
        }

        return (
        <form action={increment}>
        <button type="submit">Like</button>
        </form>
        );
        }
    • 自定义调用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      'use client';

      import { useTransition } from 'react';
      import { myAction } from './action';

      export function MyComponent() {
      const [_, startTransition] = useTransition();

      const handleClick = () => {
      startTransition(async () => {
      await myAction();
      });
      };

      return <button onClick={handleClick}>Custom Invocation</button>;
      }

在 Web 中渲染 Word 文档

在 Web 中渲染 Word 文档

  • 使用 office 厂商提供的服务,微软 office online, WPS 等
  • 转为 PDF
  • 前端解析直接解析 .docx 文件并渲染

文件解析

文档格式转换

版式布局

  • 跟随 word 展示效果,采用分页渲染
  • word 单元格转为 html table
  • 形状使用 svg 模拟
  • 公式使用 XSLTProcessor 将 Office Math 转为 MathML

bfcache

往返缓存 bfcache

bfcache 是内存缓存,可在用户离开网页时存储网页(包括 JavaScript 堆)的完整快照。将整个页面保存在内存中后,当用户决定返回时,浏览器就可以快速轻松地恢复该页面。

  • 暂停执行 JavaScript 任务队列中的所有待处理任务,从 bfcache 中恢复时继续执行
  • 监听 bfcache
    • 使用 pageshow 和 pagehide 事件
  • 阻止浏览器进行 bfcache 优化
    • 使用 unload 事件
    • 使用 Cache-Control: no-store, 可以使用 Cache-Control: no-cacheCache-Control: max-age=0 替代
    • 使用 window.opener 或 window.postMessage() 进行跨页面引用
  • 推荐用法
    • 在 pagehide 时,关闭 fetch/xhr 请求,indexedDB 连接,WebSocket/WebRTC 连接,在 pageshow 时重新连接
    • 确实部署前在 devtools 中测试

搜索技巧

搜索技巧

搜信息咨询

  • google
    • 搜索语法:双引号,intitle, allintitle, intext, inurl, site, imagesize, filetype
  • 油管
  • 微信公众号 -> 搜狗搜索
  • AI, chatgpt

搜索来源

  • 信息源头
  • 行业垂直网站
  • 确定目的内容形式搜索对应文件格式
  • 不知道哪些网站有我想要的内容,先搜索 best xxx ten site 找到相关网站

199it.com 行业报告

similarsites 寻找同类网站