0%

gmock

gmock 用来模拟接口,使得测试脱离函数间的依赖

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//demo.h
#include <string>
using namespace std;

class Parent {
public:
virtual ~Parent() {}

virtual int getNum() const = 0;
virtual void setResult(int value) = 0;
virtual void print(const string &str) = 0;
virtual int calc(int a, double b) = 0;
};

class Target {
public:
Target(Parent *parent) :
parent_(parent)
{ }

int doThis()
{
int v = parent_->getNum();
parent_->setResult(v);
while (v -- > 0) {
parent_->print(to_string(v));
}
return parent_->getNum();
}

int doThat()
{
return parent_->calc(1, 2.2);
}

private:
Parent *parent_;
};

//demo.cc
#include "demo.h"
#include <gmock/gmock.h>

class MockParent : public Parent {
public:
//! MOCK_[CONST_]METHODx(方法名, 返回类型(参数类型列表));
MOCK_CONST_METHOD0(getNum, int()); //! 由于 getNum() 是 const 成员函数,所以要使用 MOCK_CONST_METHODx
MOCK_METHOD1(setResult, void(int));
MOCK_METHOD1(print, void(const string &));
MOCK_METHOD2(calc, int(int, double));
};

using ::testing::Return;
using ::testing::_;

TEST(demo, 1) {
MockParent p;
Target t(&p);

//! 设置 p.getNum() 方法的形为
EXPECT_CALL(p, getNum())
.Times(2) //! 期望被调两次
.WillOnce(Return(2)) //! 第一次返回值为2
.WillOnce(Return(10)); //! 第二次返回值为10

//! 设置 p.setResult(), 参数为2的调用形为
EXPECT_CALL(p, setResult(2))
.Times(1);

EXPECT_CALL(p, print(_)) //! 表示任意的参数,其中 _ 就是 ::testing::_ ,如果冲突要注意
.Times(2);

EXPECT_EQ(t.doThis(), 10);
}

TEST(demo, 2) {
MockParent p;
Target t(&p);

EXPECT_CALL(p, calc(1, 2.2))
.Times(1)
.WillOnce(Return(3));

EXPECT_EQ(t.doThat(), 3);
}
由于gmock是用来测试c++的,但是在实际测试中有对c函数测试的需求,我们使用一下库来对c函数打桩

https://github.com/apriorit/gmock-global

1
2
3
4
5
6
7
8
9
10
11
12
13
//1.引入头文件
#include <gmock/gmock.h>
#include "gmock-global.h"

// 2. mockc函数
MOCK_GLOBAL_FUNC2(popen,FILE*(const char*,const char*));

// 3. 使用函数
TEST(ConfigLoader,shell){
std::string cmd = "ls";
EXPECT_GLOBAL_CALL(popen,popen(cmd.c_str(), "r")).Times(2).WillOnce(::testing::Return((FILE*)NULL));
shell("pwd"); //shell是实现的一个函数不是 cmd-shell
}

lcov 测试代码覆盖度 https://www.cnblogs.com/chwei2ch/p/10678467.html

eclipse-iceoryx/iceoryx

github

ARCH

1
2
3
4
5
6
7
MemPoolCollectionMemoryBlock m_introspectionMemPoolBlock;
MemPoolSegmentManagerMemoryBlock m_segmentManagerBlock;
PosixShmMemoryProvider m_managementShm;


结构:
IceOryxRouDiComponents

socket_un process require msg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1 //client require
sendBuffer
<< IpcMessageTypeToString(IpcMessageType::REG) //注册类型
<< m_runtimeName //客户端标识名字
<< cxx::convert::toString(pid) //进程id
<< cxx::convert::toString(posix::PosixUser::getUserOfCurrentProcess().getID()) // posixuser进程id
<< cxx::convert::toString(transmissionTimestamp) //时间戳
<< static_cast<cxx::Serialization>(version::VersionInfo::getCurrentVersion()).toString(); //版本信息

2 // server qesponse
runtime::IpcMessage sendBuffer;

auto offset = rp::BaseRelativePointer::getOffset(m_mgmtSegmentId, m_segmentManager);

sendBuffer << runtime::IpcMessageTypeToString(runtime::IpcMessageType::REG_ACK) //消息类型
<< m_roudiMemoryInterface.mgmtMemoryProvider()->size() //共享内存大小
<< offset //共享内存快偏移位置(?)
<< transmissionTimestamp //时间戳
<< m_mgmtSegmentId; // 内存段id

iox 消息交互

client:发送客户端信息 //获取iox-roudi 共享内存信息
server:返回给客户端 关于共享内存的信息

内存分配过程

// 1 个memory provide 分配内存过程

  1. 构造memory block (可能n个不同size的block) 统计memory block并记录到memory provider中
  2. memory provider 分配内存空间, 并为该内存空间分配segment id, 然后根据记录的 block信息(每一个block的size可能不同),将该内存空间,拆分到N个block中,每一个memroy block中纪录拆分后内存的首地址。

// 1 个memeory provider 消费内存过程

对象内存管理过程

一个memroy manager 包含多个 memory provider;
一个memroy provider 包含多个 memory block;
实际分配内存的对象是 memory provider,然后分配segmentID,再然后拆分到 n个 memroy block中去;

客户端在使用内存的时候 就是按照segmentID来先获取 segment内存,然后,

ROS2

ros2 创建一个pkg

ros2 pkg create

1
2
3
4
5
6
//生成的pkg结构
test //pkg_name
├── CMakeLists.txt //
├── include
├── package.xml
└── src

ros2 命令行

  1. topic发布
  • 查看topic: ros2 topic list
  • 查看topic详细信息: ros2 topic info

$ros2 topic pub <msg/msg_type> //msg 是需要yaml格式
egs. ros2 topic pub /chatter std_msgs/String “data: Hello!”

  1. topic订阅

ros2 topic echo
egs. ros2 topic echo /chatter

  1. ros2 service
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $ ros2 service list //查看服务
    $ ros2 service list -t //带数据类型查看服务
    $ ros2 service type <service_name> //查看服务类型
    $ros2 service call <service_name> <service_type> <arguments> //发送服务请求

    //egs ROS2 调试
    $ros2 service call /Service_XXX_SM_Perceive XXX_msgs/srv/XXXsrvs "{header: {stamp: {sec: 0, nanosec: 1}, frame_id: hid}, msg_type: MSG_TYPE, msg_data: MSG_DATA}"

    //调试XXXsrvsArray
    $ros2 service call /Service_XXX_SM_Perceive XXX_msgs_array/srv/XXXsrvsArray "{header: {stamp: {sec: 0, nanosec: 1}, frame_id: hid}, msg_type: XXX.Header, msg_data: MSG_DATA}"
  2. ros2 param 参考
1
2
3
4
5
$ ros2 param list //查看参数列表
$ ros2 param get <node_name> <parameter_name> //获取节点参数信息
$ros2 param set <node_name> <parameter_name> <value> //设置节点参数信息
$ ros2 param dump <node_name> //将参数保存成文件,生成node_name.yaml
$ros2 run <package_name> <executable_name> --ros-args --params-file <file_name> //从文件加载参数启动节点
  1. ros2 action 参考
1
2
3
$ros2 action list //查看动作
$ros2 action list -g //带数据类型查看动作
$ros2 action send_goal <action_name> <action_type> <values> //发布action目标
  1. lifecycle
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ros2 lifecycle nodes        // 列出所有 LC 节点
    ros2 lifecycle get <node> // 列出指定节点或所有 LC 节点的当前状态。
    ros2 lifecycle list <node> // 列出指定节点可能的下一个状态和相应的转换调用(名称和 ID)。
    ros2 lifecycle set <node> <transition> // 在 LC 节点上触发转换(按名称或 ID)。

    // 可以调用的状态
    ● 配置(configure)

    ● 激活(activate)

    ● 停用(deactivate)

    ● 清理(cleanup)

    ● 关闭(shutdown

ros2自定义消息

  1. 创建包

    $ ros2 pkg create test_msgs //包名一定是xxx_xxx格式,不要是XX2XX格式

  2. 创建msg目录和文件
  3. 创建目录

    $ mkdir -p test_msgs/msg //创建msg目录

  4. 编写*.msg文件

    $ vim msg/TestMsgs.msg //首字母大写 后边生成的hpp文件名根这个名字有关。 [TestMsgs <–> test_msgs.hpp] [Testmsgs <–> testmsgs.hpp
    // TestMsgs.msg内容:
    int32 Num

  5. 修改CMmakeList.txt 规则

    1
    2
    3
    4
    5
    find_package(rosidl_default_generators REQUIRED)

    rosidl_generate_interfaces(${PROJECT_NAME}
    "msg/TestMsgs.msg"
    )
    1. 修改package.xml规则

      1
      2
      3
      4
      5
       <build_depend>rosidl_default_generators</build_depend>

      <exec_depend>rosidl_default_runtime</exec_depend>

      <member_of_group>rosidl_interface_packages</member_of_group>
    2. 构建

      $ colcon build

    3. 设置环境变量

      $ source ./install/setup.bash
      // 之后就可以查看时候设置到ros2的环境中了
      ros2 msg show [tab自动补全] //如果发现有test_msgs/TestMsgs 就证明成功,之后在其他节点引用将用到

    4. 测试自定义消息

      1
      2
      3
      4
      5
      6
      7
      8
      9
       //消息使用格式:
      <pkg_name>::msg::<*.msg文件名>

      test_msgs::msg::TestMsgs tms = test_msgs::msg::TestMsgs();//就是一个数据结构,实际应该是解析成一个对象(我猜的)
      //访问结构成员
      tms.num

      // 头文件引用格式
      #include "test_msgs/msg/test_msgs.hpp"//TestMsgs.msg
XXX 消息
  • XXXmsgs.msg

    1
    2
    3
    std_msgs/Header header
    string msg_type
    string msg_data
  • XXXmsgsarray.msg

    1
    2
    3
    4
    std_msgs/Header header
    XXX_msgs/msg/XXXmsgs[] msgs //这个需要系统先有XXX_msgs/msg/XXXmsgs

    //也就是说 依赖的消息要先编译好加载到系统里去

ROS2 错误

1
2
3
//ros2 没有使用init初始化
terminate called after throwing an instance of 'rclcpp::exceptions::RCLInvalidArgument'
what(): failed to create interrupt guard condition: context argument is null, at /opt/ros2_ws/src/ros2/rcl/rcl/src/rcl/guard_condition.c:65
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

XXX_msgs_array::srv::XXXsrvsArray::Response res;

auto message = XXX_msgs::msg::XXXmsgs();

uint64_t t = std::chrono::system_clock::now().time_since_epoch().count();
message.header.stamp.sec = t / uint64_t(1e9);
message.header.stamp.nanosec = t % uint64_t(1e9);
message.msg_type="Jay_ICOVS_TYPE";
message.msg_data = "Jay_XXX_DATA";

std::vector<XXX_msgs::msg::XXXmsgs> ivec_msg = {message};

res.msgs = ivec_msg; //我们可以使用赋值的方式初始

res.msgs[0].msg_data = "server 2 client data";

res.msgs[0].msg_type = "XXX.VehicleReport";

-DXXX_AP_PATH=/share/XXX-CORE/core

core/develop_jay[ros2]
  • 构建

    • 工程依赖:

data_stream_framework 依赖ros2_XXX_msg/XXX_msgs, ros2_XXX_msg/XXX_msgs_array, XXX_api,
构建控制脚本为.cmake/ros2build.cmake。//构建过程中需要的依赖可以再该脚本中增加/修改。典型依赖是protobuf_util.so
再依赖工程下有build_alone.sh构建脚本,构建需要修改-DXXX_AP_PATH=/share/XXX-CORE/core变量

  1. 构建ro2 msgs

其中ros2_XXX_msg/XXX_msgs_array 依赖 ros2_XXX_msg/XXX_msgs工程,用户需依次构建[构建指令:colcon build],每个工程构建完并source/install/setup.bash将自定义消息加载到ros2系统,

  1. 查询ros2 msg加载成功

$ros2 msg list //出现XXX_msg/xx/xx 和 XXX_msg_array/xx/xx 即表示成功

  1. 构建XXX_api

    使用工程下build_alone.sh //注意修改-DXXX_AP_PATH=/share/XXX-CORE/core变量

  2. 构建data_stream_framework

    使用工程下build_alone.sh //注意修改-DXXX_AP_PATH=/share/XXX-CORE/core变量

  • 构建环境:

    docker image:172.16.8.120/XXX/XXX0.8:v1.0.9

  • 构建平台:

    x64-ros2

  • 测试

    1. 测试demo
      demo目录下.cc文件

    2. 测试yaml

    data_stream_framwork/Debug/conf
    server使用test_copy_server client使用test_copy_client
    pub/sub使用test

功能层软件构建方法
  1. copy XXX_api/pacakge.xml YOUP_PORJECT_DIR

  2. 修改XXX中XXX=CMakeLists.txt中project(NAME)NAME.

  3. copy XXX_api/build_alone.sh YOU_PROJECT_DIR

  4. 修改build_alone.sh中-DXXX_AP_PATH=/YOU_PROJECT_DIR/core

  5. bash build_alone.sh //即可构建功能层软件,构建成功会在Debug下生成目标文件。

可能的错误

  1. 找不到protobuf_util头文件
    在ros2build.cmake中有指定protobuf_util的头文件和lib,用户需要指定该路径,两种方法1. 你可以指定已经构建好的protobuf-util[x64]; 2. 你可以使用common/develop_jay直接在172.16.8.120/XXX/XXX0.8:v1.0.9下构建后指定该路径

  2. 找不到jsoncpp头文件
    apt-get install libjsoncpp-dev

core/.cmake/ros2build.cmake下指定protobuf_utils路径

#protobuf_utils
list(APPEND protobuf_utils_INCLUDE_DIR

/share/XXX-COMMON/common/protobuf_utils/protobuf_utils_cpp/include
/share/XXX-COMMON/common/protobuf_utils/protobuf_utils_cpp/pb_src

)

list(APPEND protobuf_utils_LIBRARIES

/share/XXX-COMMON/common/protobuf_utils/protobuf_utils_cpp/Debug/lib/libprotobuf_utils.so

)

ros2 rcl logger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Logger
// ret = rcl_logging_configure_with_output_handler(
// &context.global_arguments,
// allocator,
// NULL);

// int default_log_level = rcutils_logging_get_default_logger_level();
// rcutils_logging_set_default_logger_level(10);
// int fix_log_level = rcutils_logging_get_default_logger_level();

// set name_logger level
//rcutils_ret_t rcutils_logging_set_logger_level(const char * name, int level);

//set logger handler
// rcutils_logging_set_output_handler() //

ros2 rclcpp executor 事件循环

// 将node添加到executor中(publisher subcriptiion service client)

  1. executor.add_node(){
    weak_nodes_; //添加node
    guard_conditions_; //添加node 的 guard_conditions

    然后在spin循环中先查找any_exec [subscription_handles_,service_handles_,client_handles_,timer_handles_,waitable_handles_],如果exec_存在,则执行对应的回调,如果不存在,collect_entities,设置wait_set_然后使用rcl_wait监控wait_set_, 如果有fd[rmw_subscriptions,rmw_guard_conditions,rmw_services,rmw_clients,rmw_events]被触发,然后根据wait_set_清理无关实体[比如说waitable_handles_中没有数据的(IPM)[ipm中is_ready(wait_set)中wait_set无用,直接检测buffer_->has_data()]],然后继续获取any_exec,获取到exec后执行exec对应的回调。
    }

  1. ros2/rmw_fastrtps/rmw_wait.cpp:29:check_wait_set_for_data
    第38行:data的获取
    第39行:custom_subscriber_into->listener_->hasData();// 需要分析多线程竞争条件 predicate

ros2 发布和订阅逻辑

发布数据的时候,发布者会判断intra_process_is_enabled_, 如果使用ipm则继续判断是否多个订阅者订阅一个发布者,如果有多个订阅者,则将msg转为shared_ptr 然后发布

doip

doip的主要目的是:
  • 车辆网络集成(IP地址分配)
  • 车辆公告和车辆发现
  • 车辆基本状态信息检索(例如诊断电源模式)
  • 连接建立(如并发通信尝试)、连接维护和车辆网关控制
  • 车辆子部件之间的数据路由
  • 错误处理(例如物理网络断开连接)

vscode-extension develop

传送门
JDebugger
StockInfo

1.概览

1.1 vscode插件可以做什么

vscode编辑器是可高度自定义的,我们使用vscode插件几乎可以对vscode编辑器进行所有形式的自定义,只要你想做,基本上没有不能实现的。

vscode插件开发的官方文档为:
https://code.visualstudio.com/api
中文文档:
https://liiked.github.io/VS-Code-Extension-Doc-ZH/#/
vscode插件可以实现

  • 自定义命令
  • 快捷键
  • 自定义菜单项
  • 自定义跳转
  • 自动补全
  • 悬浮提示
  • 新增语言支持
  • 语法检查
  • 语法高亮
  • 代码格式化
    ····

1.2 如何创建插件

可以通过官方脚手架来生成vscode插件模板工程。

首先安装脚手架
npm install -g yo generator-code
然后进入工作目录,使用脚手架
yo code

1.png

通过上图可以看到,生成一个vscode插件工程时可以选择是创建一个已有的语言的插件还是一个全新的语言的插件,并且可以选择插件开发语言。
本文以创建一个新语言的插件为例。
vscode插件开发可以使用TypeScript开发,也可以使用JS,两种方式能实现的功能是一样的。
下面是自动生成的插件工程文件

3.png

其中,最核心的两个文件是package.jsonextension.jspackage.json是整个插件工程的配置文件,extension.js则是工程的入口文件。下面将对这两个文件进行详细的介绍。

1.3 package.json详解

4.png
5.png

:<u>以上配置项在刚创建完的工程文件中不全存在,本文为了更全面介绍配置项,所以后面人为添加了一些配置项。</u>

activationEvents

activationEvents配置项配置插件的激活数组,即在什么情况下插件会被激活,目前支持以下8种配置:

  • onLanguage: 在打开对应语言文件时
  • onCommand: 在执行对应命令时
  • onDebug: 在 debug 会话开始前
  • onDebugInitialConfigurations: 在初始化 debug 设置前
  • onDebugResolve: 在 debug 设置处理完之前
  • workspaceContains: 在打开一个文件夹后,如果文件夹内包含设置的文件名模式时
  • onFileSystem: 打开的文件或文件夹,是来自于设置的类型或协议时
  • onView: 侧边栏中设置的 id 项目展开时
  • onUri: 在基于 vscode 或 vscode-insiders 协议的 url 打开时
  • onWebviewPanel: 在打开设置的 webview 时
  • *: 在打开 vscode 的时候,如果不是必须一般不建议这么设置
contributes

contributes配置项是整个插件的贡献点,也就是说这个插件有哪些功能。contributes字段可以设置的key也基本显示了vscode插件可以做什么。

  • configuration:通过这个配置项我们可以设置一个属性,这个属性可以在vscodesettings.json中设置,然后在插件工程中可以读取用户设置的这个值,进行相应的逻辑。
  • commands:命令,通过cmd+shift+p进行输入来实现的。
  • menus:通过这个选项我们可以设置右键的菜单
  • keybindings:可以设置快捷键
  • languages:设置语言特点,包括语言的后缀等
  • grammars:可以在这个配置项里设置描述语言的语法文件的路径,vscode可以根据这个语法文件来自动实现语法高亮功能
  • snippets:设置语法片段相关的路径
    . . . . .

extension.js

extension.js是插件工程的入口文件,当插件被激活,即触发package.json中的activationEvents配置项时,extension.js文件开始执行。
extension.js中对需要的功能进行注册,主要使用vscode.commands.register...相关的api,来为package.json中的contributes配置项中的事件绑定方法或者监听器。
vscode.commands.register...相关的api主要有:

  • vscode.languages.registerCompletionItemProvider()
  • vscode.commands.registerCommand()
  • vscode.languages.registerCodeActionsProvider()
  • vscode.languages.registerCodeLensProvider()
  • vscode.languages.registerHoverProvider()
    . . . . .
    6.png

1.4 插件生命周期

下面我们运行一下这个插件工程,按F5运行插件,这个时候会自动打开一个新的vscode界面,我们按cmd+shift+p,在命令框输入plugin-demo.helloWorld命令,既可以看到在vscode的界面的右下角弹出一个弹框,弹框显示Hello World from plugin-demo2,这正是我们在extension.js中为plugin-demo.helloWorld中为plugin-demo.helloWorld命令绑定的事件。

7.png

下面我们梳理一下这个弹框出现的整个流程:


8.png
  • 1.activationEvents:在package.jsonactivationEvents配置项中设置插件激活时机,这里设置的是onCommand:plugin-demo.helloWorld,即输入命令onCommand:plugin-demo.helloWorld时激活。
  • 2.contributespackage.json中的contributes配置项表示这个插件增加了哪些功能,这里设置了commands,增加的命令,在这一项中声明了一个命令plugin-demo.helloWorld
  • 3.Register:在extension.js文件中的activate(context)方法中,使用vscode.commands.registerCommand()这一API为命令plugin-demo.helloWorld绑定事件,绑定的事件为vscode.window.showInformationMessage('Hello World from plugin-demo2!'),即弹出弹框。
  • 4.在命令框中输入plugin-demo.helloWorld,此时插件被激活,进入extension.js中执行activate()方法,由于已经在contributes配置项中声明了命令plugin-demo.helloWorld,所以在activate()方法中为该命令绑定一个事件,由于在命令框中输入了这个命令,所以命令绑定的事件立即被触发执行,所以在vscode的右下角弹出了弹出框。

VSCode的插件都运行在一个独立的进程里, 被称为 Extension Host, 它加载并运行插件, 让插件感觉自己好像在主进程里一样, 同时又严格限制插件的响应时间, 避免插件影响主界面进程。

9.png

2.具体的功能介绍

命令

关于命令我们之前在分析插件的生命周期的时候就已经讲过,首先在package.jsoncontributes配置项中声明命令:

"commands": [
    {
        "command": "plugin-demo.helloWorld",
        "title": "Hello World"
    }
]

然后在extension.jsactivate()中去注册该命令,绑定事件:

let disposable = vscode.commands.registerCommand('plugin-demo.helloWorld', function () {

    vscode<span class="token punctuation">.</span>window<span class="token punctuation">.</span><span class="token function">showInformationMessage</span><span class="token punctuation">(</span><span class="token string">'Hello World from plugin-demo2!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

所有注册类的API执行后都要将将结果放到context.subscriptions中去:

context.subscriptions.push(disposable);

这样当插件被激活后,输入命令,命令绑定的事件就会被执行。

菜单

菜单也是通过和命令关联起来来实现其功能的

"menus": {
    "editor/title": [{
    "when": "editorFocus",
    "command": "plugin-demo.helloWorld",
    "alt": "",
    "group": "navigation"
    }]
}

以上是一个菜单项的完整配置.

  • editor/title: 定义这个菜单出现在哪里,这里是定义出现在编辑标题菜单栏。
  • when: 菜单在什么时候出现,这里是有光标的时候出现
  • command: 点击这个菜单要执行的命令
  • alt: 按住alt再选择菜单时应该执行的命令
  • group: 定义菜单分组

菜单项对应的命令为plugin-demo.helloWorld,我们再在contributionscommands中找到这个命令:

"commands": [
    {
        "command": "plugin-demo.helloWorld",
        "title": "菜单栏测试"
    }
        ]

这里命令的title将作为菜单项的名字显示,当然我们也可以设置菜单项的icon
我们之前已经在extension.js中注册过这个命令了,因此不用再注册。

F5运行插件,保证插件被激活后,使光标出现,在编辑器的右上角我们可以看到出现一个新增的菜单:

10.png

当我们点击这个菜单时,其会执行关联的commandextension.js中绑定的事件。

快捷键

快捷键的设置比较简单,其执行功能同样依赖于其关联的命令command

"keybindings": [
    {
        "command": "plugin-demo.helloWorld",
        "key": "ctrl+{",
        "mac": "cmd+{",
        "when": "editorTextFocus"
    }
]
  • command: 快捷键关联的命令
  • key: Windows平台对应的快捷键
  • mac: mac平台对应的快捷键
  • when: 什么时候快捷键有效

当插件被激活后,并且满足快捷键有效的时间,按快捷键就可以找到extension.js中与快捷键关联的command所不绑定的事件并执行。

悬停提示

悬停提示的思路是在extension.js中注册一个悬停事件,然后根据提供的docuemntposition已经文件名,文件路径等信息作出相应的逻辑。

主要API:

function registerHoverProvider(selector: DocumentSelector, provider: HoverProvider): Disposable;
这一API返回一个HoverProvider对象,这一对象需要加入到context.subscription中。

provideHover(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<Hover>;
这一API返回一个PrioviderResult对象,当我们把光标放在某个位置时显示的内容,就是这个对象封装的。

下面我们写一个简单的demo,我们对package.json文件中的main这个单词进行悬停提示:

function activate(context) {

<span class="token keyword">const</span> hover <span class="token operator">=</span> vscode<span class="token punctuation">.</span>languages<span class="token punctuation">.</span><span class="token function">registerHoverProvider</span><span class="token punctuation">(</span><span class="token string">'json'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
    <span class="token function">provideHover</span><span class="token punctuation">(</span><span class="token parameter">document<span class="token punctuation">,</span> position<span class="token punctuation">,</span> token</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> fileName <span class="token operator">=</span> document<span class="token punctuation">.</span>fileName<span class="token punctuation">;</span>
        <span class="token keyword">const</span> word <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getText</span><span class="token punctuation">(</span>document<span class="token punctuation">.</span><span class="token function">getWordRangeAtPosition</span><span class="token punctuation">(</span>position<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token regex">/\/package\.json$/</span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>fileName<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token regex">/\bmain\b/</span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>word<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">vscode<span class="token punctuation">.</span>Hover</span><span class="token punctuation">(</span><span class="token string">"测试悬停提示"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">return</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

context<span class="token punctuation">.</span>subscriptions<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>hover<span class="token punctuation">)</span><span class="token punctuation">;</span>

}

运行插件,保证插件被激活的状态下,将光标放在package.json文件的main单词上:

12.gif

代码片段

代码片段也叫snippets,就是输入一个前缀,会得到一个或多个提示,然后回车带出很多代码。

想要在vscode插件中实现snippets的功能,首先要在package.jsoncontributes配置项中配置代码提示文件的文件路径:

"snippets": [
    {
        "language": "lizard",
        "path": "./snippets.json"
        }
    ]

这里language设置了snippets作用于何种语言,path设置了服务于snippets的文件的路径。
再看一下在snippets.json文件中:

{
    "View组件": {
        "prefix": "View",
        "body": [
            "<View>",
            "${1}",
            "</VIew>"
        ],
        "description": "View组件"
    }
}
  • "View组件": snippet的名称
  • "prefix": 前缀,即输入什么可以出现snippets的提示
  • "body": 按回车后出现的一大段代码,是一个数组,数组里面是字符串,每个字符串代表一行代码,${1}表示第一个光标的位置,同样,${2}表示第二个光标的位置
  • "description": 对于这个snippet的描述,当我们选中这个snipets提示时,描述会出现在后面。
    现在,我们运行插件,并保证插件被激活,在规定的语言下,输入View:
    11.gif

3.详细讲解的插件功能

代码高亮

当我们为一个已有的语言创建插件时,package.json中默认不会有代码高亮相关的配置,当我们为一个新语言开发插件时,插件工程的package.json文件中默认有语法高亮相关的配资。
这一配置仍然在contributes中:

20.png

上图中grammarspath项设置了描述新语言的语法的文件路径。
然后我们看到这个语法文件lizard.tmLanguage.json,vscode会根据这个语法文件自动实现语法高亮的功能。我们找到该文件中的一段:

21.png

先不管这里每一项表示什么含义,首先运行代码,输入forreturnifwhile等关键字中的其中一个,会发现关键字出现了高亮,这便实现了简单的高亮功能。
上面的的代码中。
lizard.tmLanguage.json中的语法是TextMate语法,关于TextMate的介绍:
https://macromates.com/manual/en/language_grammars
https://www.apeth.com/nonblog/stories/textmatebundle.html

22.png

上面的代码中:
match是一个正则表达式,但是使用的是ruby regular expression,进行匹配,name是被匹配的表达式的scope selector,关于scope selector的介绍见链接:
https://macromates.com/manual/en/scope_selectors
vscode根据这个scope selector进行上色。
下面介绍一下本文为新语言写语法文件的案例:
为属性结构写语法,属性结构模板为:style = {width:8, height:9}

23.png

代码提示

代码提示是我们使用vscode开发的时候不可获取的一个功能,即当我们输入代码的一部分的时候,这时候vscode显示一个提示列表,我们可以选择一个提示项,然后回车,这样代码的剩余部分就自动补全了。

代码提示相关的主要的API是:
registerCompletionItemProvider(selector: DocumentSelector, provider: CompletionItemProvider, ...triggerCharacters: string[]): Disposable;

  • 第一个参数是实现代码提示的文件的类型。
  • 第二个参数是一个CompletionItemProvider类型的对象,在创建这个对象内部,我们需要根据documentposition等信息进行逻辑处理,返回一个CompletionItem的数组,每一个CompletionItem就代表一个提示项。
  • 第三个参数是可选的触发提示的字符列表。

下面列出一些与代码提示相关的其他的一些API,这些API大多与文本、单词的处理相关,因为我们进行代码提示时需要知道当前光标所在单词的上下文,这样才能很好的给出智能提示,而要得到当前光标的上下文,就需要对光标附近乃至整个文件进行文本分析。

  • 与TextDocument相关
    TextDocument的对象实际是当前文件对象,所以我们可以根据该对象得到当前文件与文本相关的所有信息。
  • lineAt(line: number): TextLine; 根据行数返回一个行的对象
  • lineAt(position: Position): TextLine; 根据一个位置返回这一行的行对象
  • getText(range?: Range): string; 根据范围,返回这个范围的文本
  • getWordRangeAtPosition(position: Position, regex?: RegExp): Range | undefined; 根据position返回这个位置所在的单词。
  • text.charAt() 返回字符串在某个位置的字符

下面写一个代码提示的简单的demo:

function activate(context) {

<span class="token keyword">const</span> provider <span class="token operator">=</span> vscode<span class="token punctuation">.</span>languages<span class="token punctuation">.</span><span class="token function">registerCompletionItemProvider</span><span class="token punctuation">(</span><span class="token string">'plaintext'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
    <span class="token function">provideCompletionItems</span><span class="token punctuation">(</span><span class="token parameter">document<span class="token punctuation">,</span> position</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> completionItem1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">vscode<span class="token punctuation">.</span>CompletionItem</span><span class="token punctuation">(</span><span class="token string">'Hello World!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">const</span> completionItem2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">vscode<span class="token punctuation">.</span>CompletionItem</span><span class="token punctuation">(</span><span class="token string">'World Peace!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token punctuation">[</span>completionItem1<span class="token punctuation">,</span> completionItem2<span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

context<span class="token punctuation">.</span>subscriptions<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>provider<span class="token punctuation">)</span><span class="token punctuation">;</span>

}

我们在这里创建了两个CompletionItem对象,这样,当我们输入Hello World!World Peace的一部分时,插件会自动显示提示项,回车即可进行补全。

13.gif

自定义语言实现代码提示

上面的demo中我们实现了一个简单的代码提示的demo,但是这种异常简单的代码提示机会是没有任何价值的,为一个语言实现代码提示必须要结合当前光标位置的上下文来实现,根据上下文来分析当前光标所在单词属于类名、变量名、函数名等等,再提供相对应的提示。

为一个语言实现代码提示的主要方式有两种,第一种是使用抽象语法树,分析语法节点,分析当前位置属于哪一节点,第二种方式是直接使用正则匹配等方式来粗略判断当前位置的上下文,目前成熟的开发语言的代码提示均使用第一种方式,但是第一种方法同时也要处理语法错误时的分析,因此对个人而言难度相对比较大,本文采用第二种方式对新语言提供代码提示。

下面是新语言的一个模板:


14.png

其中主要包括两种结构:组件全局变量,这两种结构的形式都是非常固定的,组件 的一般结构如下:

<ComponentName propertyName = {key: value, key: value} propertyName2 = {}···/>

或者

<ComponentName propertyName = {key: value, key: value} propertyName2 = {}···></ComponentName>

全局变量的结构如果我们把globalVar =这部分结构忽略,只看{}里面的内容,很容易发现其结构与json无异,这提醒我全局变量的结构可以把它当成一个json对象进行解析,当然在此之前还需要做许多额外工作,保证解析的正确进行。

简而言之,对这个新语言的代码提示主要集中在5个部分:组件名称、组件的属性名称、组件的属性名称里的key、组件的属性名称里的value(有一些value是枚举值,因此需要进行提示)、全局变量的key。

我们前面一直提到实现代码提示要结合当前光标的上下文进行分析,其实质就是根据光标位置的上下文分析当前光标的位置属于哪一类,是属于组件名还是属性名等等。

因此问题就转化为如何根据当前光标的上下文得到光标处属于哪一类。本文处理次问题的逻辑如下:

  • 1.首先从当前位置开始往前寻找,找到象征一个组件开始的<,在此过程中如果遇见一个组件结尾标志的/>或者</NAME>结构,则停止寻找,说明当前光标不在组件里,可以判断当前光标是在全局变量处。
  • 2.在1中对光标在组件内还是组件外进行了区分。如果光标在组件内,则需要判断属于组件内的组件名、属性名、属性key、属性value的哪一种。
  • 3.使用api找到当前光标所在单词的起始位置,然后判断该其起始位置与<位置之间是否有除空格外的其他字符,若没有,则当前位置是组件名,若有,则当前位置不是组件名,需要继续区分,通过是否在括号内判断当前位置是不是属性名。
  • 4.{}内是keyvalue,使用:来进行区分。

这样就对五种情况进行了区分,然后可以针对每种情况给出有正对性的提示。
下面是最后的实现效果:

代码自动补全

html中当我们输入<label 再输入一个>时,这时候vscode会自动帮我们添加上</label>,不需要我们敲回车就能完成,这种自动补全的方式能提高开发效率,下面就谈一下其实现。

下面就以<label></label>的实现为例:
当敲入>时,首先要计算得到组件名componnetName,然后:

16.png

实现效果:

17.gif

加载本地的文件

有时候插件可能想要读取用户自己自定义的文件,来实现某个功能,这个时候就需要把用户的文件路径传递给插件。

解决这个问题的办法可以是给vscodesettings增加有一个设置项,用户填写对应的值,vscode插件就可以读取这个值,进而读取相关的文件。
settings增加设置项可以通过package.json文件的contributes进行配置:

18.png

然后我们就可以在settings中设置customPath这个设置项的值:

19.png

最后在插件工程中读取customPath的值:
const path = vscode.workspace.getConfiguration('lizard').get('customPath');

4.打包、发布、升级

发布插件到插件市场

  • 1.安装vsce(Visual Studio Code Extension)
    npm install -g vsce
  • 2.在网站https://dev.azure.com/vscode获取一个access token,这个token用来创建一个publisher
  • 3.创建publisher
    vscr create-publisher (publisher name)
  • 4.登入一个publisher
    vscde login (publisher name)
  • 5.打包
    vsce package
  • 6.发布
    vsce publish

升级

  • 1.首先在package.json文件中修改插件的版本号。
  • 2.使用命令vsce publish升级

参考

vscode插件开发官方文档

vscode插件开发中文文档

TextMate官方介绍
对理解TextMate极有帮助的文档
https://www.apeth.com/nonblog/stories/textmatebundle.html

知乎讲vscode原理的
https://zhuanlan.zhihu.com/p/99198980
vscode入门的博文教程
https://www.cnblogs.com/liuxianan/p/vscode-plugin-overview.html


原文地址https://www.jianshu.com/p/e642856f6044

npm use
  1. 在nodejs工程中新增依赖包package.json:

typescript:
npm install –save-dev webpack webpack-dev-server typescript ts-loader

protobuf3

protobuf3 消息定义

  1. 消息格式
1
2
3
4
5
6
// msg.proto
syntax = "proto3"

message jmsg {
string name = 1;
}
  1. 如果有修饰符
    所指定的消息字段修饰符必须是如下之一:
    singular:一个格式良好的消息应该有0个或者1个这种字段(但是不能超过1个)。
    repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。

在proto3中,repeated的标量域默认情况虾使用packed。

定义一个消息类型

先来看一个非常简单的例子。假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了:

1
2
3
4
5
6
7
syntax = "proto3";
 
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

  

  • 文件的第一行指定了你正在使用proto3语法:如果你没有指定这个,编译器会使用proto2。这个指定语法行必须是文件的非空非注释的第一个行。
  • SearchRequest消息格式有3个字段,在消息中承载的数据分别对应于每一个字段。其中每个字段都有一个名字和一种类型。

指定字段类型

在上面的例子中,所有字段都是标量类型:两个整型(page_number和result_per_page),一个string类型(query)。当然,你也可以为字段指定其他的合成类型,包括枚举(enumerations)或其他消息类型。

分配标识号

正如你所见,在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。

最小的标识号可以从1开始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]( (从FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber))的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。同样你也不能使用早期保留的标识号。

指定字段规则

所指定的消息字段修饰符必须是如下之一:

  • singular:一个格式良好的消息应该有0个或者1个这种字段(但是不能超过1个)。
  • repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。

    在proto3中,repeated的标量域默认情况虾使用packed。

    你可以了解更多的pakced属性在Protocol Buffer 编码

添加更多消息类型

在一个.proto文件中可以定义多个消息类型。在定义多个相关的消息的时候,这一点特别有用——例如,如果想定义与SearchResponse消息类型对应的回复消息格式的话,你可以将它添加到相同的.proto文件中,如:

1
2
3
4
5
6
7
8
9
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
 
message SearchResponse {
 ...
}

  

添加注释

向.proto文件添加注释,可以使用C/C++/java风格的双斜杠(//) 语法格式,如:

1
2
3
4
5
message SearchRequest {
  string query = 1;
  int32 page_number = 2// Which page number do we want?
  int32 result_per_page = 3// Number of results to return per page.
}

  

保留标识符(Reserved)

如果你通过删除或者注释所有域,以后的用户可以重用标识号当你重新更新类型的时候。如果你使用旧版本加载相同的.proto文件这会导致严重的问题,包括数据损坏、隐私错误等等。现在有一种确保不会发生这种情况的方法就是指定保留标识符(and/or names, which can also cause issues for JSON serialization不明白什么意思),protocol buffer的编译器会警告未来尝试使用这些域标识符的用户。

1
2
3
4
message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

  

注:不要在同一行reserved声明中同时声明域名字和标识号

从.proto文件生成了什么?

当用protocol buffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。

  • 对C++来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
  • 对Java来说,编译器为每一个消息类型生成了一个.java文件,以及一个特殊的Builder类(该类是用来创建消息类接口的)。
  • 对Python来说,有点不太一样——Python编译器为.proto文件中的每个消息类型生成一个含有静态描述符的模块,,该模块与一个元类(metaclass)在运行时(runtime)被用来创建所需的Python数据访问类。
  • 对go来说,编译器会位每个消息类型生成了一个.pd.go文件。
  • 对于Ruby来说,编译器会为每个消息类型生成了一个.rb文件。
  • javaNano来说,编译器输出类似域java但是没有Builder类
  • 对于Objective-C来说,编译器会为每个消息类型生成了一个pbobjc.h文件和pbobjcm文件,.proto文件中的每一个消息有一个对应的类。
  • 对于C#来说,编译器会为每个消息类型生成了一个.cs文件,.proto文件中的每一个消息有一个对应的类。

你可以从如下的文档链接中获取每种语言更多API(proto3版本的内容很快就公布)。API Reference

标量数值类型

一个标量消息字段可以含有一个如下的类型——该表格展示了定义于.proto文件中的类型,以及与之对应的、在自动生成的访问类中定义的类型:

 

.proto TypeNotesC++ TypeJava TypePython Type[2]Go TypeRuby TypeC# TypePHP Type
double   double double float float64 Float double float
float   float float float float32 Float float float
int32 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代 int32 int int int32 Fixnum 或者 Bignum(根据需要) int integer
uint32 使用变长编码 uint32 int int/long uint32 Fixnum 或者 Bignum(根据需要) uint integer
uint64 使用变长编码 uint64 long int/long uint64 Bignum ulong integer/string
sint32 使用变长编码,这些编码在负值时比int32高效的多 int32 int int int32 Fixnum 或者 Bignum(根据需要) int integer
sint64 使用变长编码,有符号的整型值。编码时比通常的int64高效。 int64 long int/long int64 Bignum long integer/string
fixed32 总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。 uint32 int int uint32 Fixnum 或者 Bignum(根据需要) uint integer
fixed64 总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。 uint64 long int/long uint64 Bignum ulong integer/string
sfixed32 总是4个字节 int32 int int int32 Fixnum 或者 Bignum(根据需要) int integer
sfixed64 总是8个字节 int64 long int/long int64 Bignum long integer/string
bool   bool boolean bool bool TrueClass/FalseClass bool boolean
string 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 string String str/unicode string String (UTF-8) string string
bytes 可能包含任意顺序的字节数据。 string ByteString str []byte String (ASCII-8BIT) ByteString string

你可以在文章Protocol Buffer 编码中,找到更多“序列化消息时各种类型如何编码”的信息。

  1. 在java中,无符号32位和64位整型被表示成他们的整型对应形似,最高位被储存在标志位中。
  2. 对于所有的情况,设定值会执行类型检查以确保此值是有效。
  3. 64位或者无符号32位整型在解码时被表示成为ilong,但是在设置时可以使用int型值设定,在所有的情况下,值必须符合其设置其类型的要求。
  4. python中string被表示成在解码时表示成unicode。但是一个ASCIIstring可以被表示成str类型。
  5. Integer在64位的机器上使用,string在32位机器上使用

默认值

当一个消息被解析的时候,如果被编码的信息不包含一个特定的singular元素,被解析的对象锁对应的域被设置位一个默认值,对于不同类型指定如下:

  • 对于strings,默认是一个空string
  • 对于bytes,默认是一个空的bytes
  • 对于bools,默认是false
  • 对于数值类型,默认是0
  • 对于枚举,默认是第一个定义的枚举值,必须为0;
  • 对于消息类型(message),域没有被设置,确切的消息是根据语言确定的,详见generated code guide

    对于可重复域的默认值是空(通常情况下是对应语言中空列表)。

    注:对于标量消息域,一旦消息被解析,就无法判断域释放被设置为默认值(例如,例如boolean值是否被设置为false)还是根本没有被设置。你应该在定义你的消息类型时非常注意。例如,比如你不应该定义boolean的默认值false作为任何行为的触发方式。也应该注意如果一个标量消息域被设置为标志位,这个值不应该被序列化传输。

    查看generated code guide选择你的语言的默认值的工作细节。

枚举

当需要定义一个消息类型的时候,可能想为一个字段指定某“预定义值序列”中的一个值。例如,假设要为每一个SearchRequest消息添加一个 corpus字段,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一个。 其实可以很容易地实现这一点:通过向消息定义中添加一个枚举(enum)并且为每个可能的值定义一个常量就可以了。

在下面的例子中,在消息格式中添加了一个叫做Corpus的枚举类型——它含有所有可能的值 ——以及一个类型为Corpus的字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

  

如你所见,Corpus枚举的第一个常量映射为0:每个枚举类型必须将其第一个类型映射为0,这是因为:

  • 必须有有一个0值,我们可以用这个0值作为默认值。
  • 这个零值必须为第一个元素,为了兼容proto2语义,枚举类的第一个值总是默认值。

    你可以通过将不同的枚举常量指定位相同的值。如果这样做你需要将allow_alias设定位true,否则编译器会在别名的地方产生一个错误信息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    enum EnumAllowingAlias {
      option allow_alias = true;
      UNKNOWN = 0;
      STARTED = 1;
      RUNNING = 1;
    }
    enum EnumNotAllowingAlias {
      UNKNOWN = 0;
      STARTED = 1;
      // RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
    }

    枚举常量必须在32位整型值的范围内。因为enum值是使用可变编码方式的,对负数不够高效,因此不推荐在enum中使用负数。如上例所示,可以在 一个消息定义的内部或外部定义枚举——这些枚举可以在.proto文件中的任何消息定义里重用。当然也可以在一个消息中声明一个枚举类型,而在另一个不同 的消息中使用它——采用MessageType.EnumType的语法格式。

    当对一个使用了枚举的.proto文件运行protocol buffer编译器的时候,生成的代码中将有一个对应的enum(对Java或C++来说),或者一个特殊的EnumDescriptor类(对 Python来说),它被用来在运行时生成的类中创建一系列的整型值符号常量(symbolic constants)。

    在反序列化的过程中,无法识别的枚举值会被保存在消息中,虽然这种表示方式需要依据所使用语言而定。在那些支持开放枚举类型超出指定范围之外的语言中(例如C++和Go),为识别的值会被表示成所支持的整型。在使用封闭枚举类型的语言中(Java),使用枚举中的一个类型来表示未识别的值,并且可以使用所支持整型来访问。在其他情况下,如果解析的消息被序列号,未识别的值将保持原样。

    关于如何在你的应用程序的消息中使用枚举的更多信息,请查看所选择的语言generated code guide

    使用其他消息类型

    你可以将其他消息类型用作字段类型。例如,假设在每一个SearchResponse消息中包含Result消息,此时可以在相同的.proto文件中定义一个Result消息类型,然后在SearchResponse消息中指定一个Result类型的字段,如:

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    message SearchResponse {
      repeated Result results = 1;
    }
     
    message Result {
      string url = 1;
      string title = 2;
      repeated string snippets = 3;
    }

    嵌套类型

    你可以在其他消息类型中定义、使用消息类型,在下面的例子中,Result消息就定义在SearchResponse消息内,如:

    1
    2
    3
    4
    5
    6
    7
    8
    message SearchResponse {
      message Result {
        string url = 1;
        string title = 2;
        repeated string snippets = 3;
      }
      repeated Result results = 1;
    }

    如果你想在它的父消息类型的外部重用这个消息类型,你需要以Parent.Type的形式使用它,如:

1
2
3
message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

  

更新一个消息类型

如果一个已有的消息格式已无法满足新的需求——如,要在消息中添加一个额外的字段——但是同时旧版本写的代码仍然可用。不用担心!更新消息而不破坏已有代码是非常简单的。在更新时只要记住以下的规则即可。

  • 不要更改任何已有的字段的数值标识。
  • 如果你增加新的字段,使用旧格式的字段仍然可以被你新产生的代码所解析。你应该记住这些元素的默认值这样你的新代码就可以以适当的方式和旧代码产生的数据交互。相似的,通过新代码产生的消息也可以被旧代码解析:只不过新的字段会被忽视掉。注意,未被识别的字段会在反序列化的过程中丢弃掉,所以如果消息再被传递给新的代码,新的字段依然是不可用的(这和proto2中的行为是不同的,在proto2中未定义的域依然会随着消息被序列化)
  • 非required的字段可以移除——只要它们的标识号在新的消息类型中不再使用(更好的做法可能是重命名那个字段,例如在字段前添加“OBSOLETE_”前缀,那样的话,使用的.proto文件的用户将来就不会无意中重新使用了那些不该使用的标识号)。
  • int32, uint32, int64, uint64,和bool是全部兼容的,这意味着可以将这些类型中的一个转换为另外一个,而不会破坏向前、 向后的兼容性。如果解析出来的数字与对应的类型不相符,那么结果就像在C++中对它进行了强制类型转换一样(例如,如果把一个64位数字当作int32来 读取,那么它就会被截断为32位的数字)。
  • sint32和sint64是互相兼容的,但是它们与其他整数类型不兼容。
  • string和bytes是兼容的——只要bytes是有效的UTF-8编码。
  • 嵌套消息与bytes是兼容的——只要bytes包含该消息的一个编码过的版本。
  • fixed32与sfixed32是兼容的,fixed64与sfixed64是兼容的。
  • 枚举类型与int32,uint32,int64和uint64相兼容(注意如果值不相兼容则会被截断),然而在客户端反序列化之后他们可能会有不同的处理方式,例如,未识别的proto3枚举类型会被保留在消息中,但是他的表示方式会依照语言而定。int类型的字段总会保留他们的

Any

Any类型消息允许你在没有指定他们的.proto定义的情况下使用消息作为一个嵌套类型。一个Any类型包括一个可以被序列化bytes类型的任意消息,以及一个URL作为一个全局标识符和解析消息类型。为了使用Any类型,你需要导入import google/protobuf/any.proto

1
2
3
4
5
6
import "google/protobuf/any.proto";
 
message ErrorStatus {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}

  

对于给定的消息类型的默认类型URL是type.googleapis.com/packagename.messagename

不同语言的实现会支持动态库以线程安全的方式去帮助封装或者解封装Any值。例如在java中,Any类型会有特殊的pack()unpack()访问器,在C++中会有PackFrom()UnpackTo()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);
 
// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const Any& detail : status.details()) {
  if (detail.Is<NetworkErrorDetails>()) {
    NetworkErrorDetails network_error;
    detail.UnpackTo(&network_error);
    ... processing network_error ...
  }
}

  

目前,用于Any类型的动态库仍在开发之中 
如果你已经很熟悉proto2语法,使用Any替换拓展

Oneof

如果你的消息中有很多可选字段, 并且同时至多一个字段会被设置, 你可以加强这个行为,使用oneof特性节省内存.

Oneof字段就像可选字段, 除了它们会共享内存, 至多一个字段会被设置。 设置其中一个字段会清除其它字段。 你可以使用case()或者WhichOneof() 方法检查哪个oneof字段被设置, 看你使用什么语言了.

使用Oneof

为了在.proto定义Oneof字段, 你需要在名字前面加上oneof关键字, 比如下面例子的test_oneof:

1
2
3
4
5
6
message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}

  

然后你可以增加oneof字段到 oneof 定义中. 你可以增加任意类型的字段, 但是不能使用repeated 关键字.

在产生的代码中, oneof字段拥有同样的 getters 和setters, 就像正常的可选字段一样. 也有一个特殊的方法来检查到底那个字段被设置. 你可以在相应的语言API指南中找到oneof API介绍.

Oneof 特性

  • 设置oneof会自动清楚其它oneof字段的值. 所以设置多次后,只有最后一次设置的字段有值.
1
2
3
4
5
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message();   // Will clear name field.
CHECK(!message.has_name());

  

  • 如果解析器遇到同一个oneof中有多个成员,只有最会一个会被解析成消息。
  • oneof不支持repeated.
  • 反射API对oneof 字段有效.
  • 如果使用C++,需确保代码不会导致内存泄漏. 下面的代码会崩溃, 因为sub_message 已经通过set_name()删除了
1
2
3
4
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name");      // Will delete sub_message
sub_message->set_...            // Crashes here

  

  • 在C++中,如果你使用Swap()两个oneof消息,每个消息,两个消息将拥有对方的值,例如在下面的例子中,msg1会拥有sub_message并且msg2会有name
1
2
3
4
5
6
7
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());

  

向后兼容性问题

当增加或者删除oneof字段时一定要小心. 如果检查oneof的值返回None/NOT_SET, 它意味着oneof字段没有被赋值或者在一个不同的版本中赋值了。 你不会知道是哪种情况,因为没有办法判断如果未识别的字段是一个oneof字段。

Tage 重用问题:

  • 将字段移入或移除oneof:在消息被序列号或者解析后,你也许会失去一些信息(有些字段也许会被清除)
  • 删除一个字段或者加入一个字段:在消息被序列号或者解析后,这也许会清除你现在设置的oneof字段
  • 分离或者融合oneof:行为与移动常规字段相似。

Map(映射)

如果你希望创建一个关联映射,protocol buffer提供了一种快捷的语法:

map<key_type, value_type> map_field = N;

其中key_type可以是任意Integer或者string类型(所以,除了floating和bytes的任意标量类型都是可以的)value_type可以是任意类型。

例如,如果你希望创建一个project的映射,每个Projecct使用一个string作为key,你可以像下面这样定义:

map<string, Project> projects = 3;
  • Map的字段可以是repeated。
  • 序列化后的顺序和map迭代器的顺序是不确定的,所以你不要期望以固定顺序处理Map
  • 当为.proto文件产生生成文本格式的时候,map会按照key 的顺序排序,数值化的key会按照数值排序。
  • 从序列化中解析或者融合时,如果有重复的key则后一个key不会被使用,当从文本格式中解析map时,如果存在重复的key。

生成map的API现在对于所有proto3支持的语言都可用了,你可以从API指南找到更多信息。

向后兼容性问题

map语法序列化后等同于如下内容,因此即使是不支持map语法的protocol buffer实现也是可以处理你的数据的:

复制代码
message MapFieldEntry {
  key_type key = 1;
  value_type value = 2;
}

repeated MapFieldEntry map_field = N;

复制代码

当然可以为.proto文件新增一个可选的package声明符,用来防止不同的消息类型有命名冲突。如:

1
2
package foo.bar;
message Open { ... }

  在其他的消息格式定义中可以使用包名+消息名的方式来定义域的类型,如:

1
2
3
4
5
message Foo {
  ...
  required foo.bar.Open open = 1;
  ...
}

  

包的声明符会根据使用语言的不同影响生成的代码。

  • 对于C++,产生的类会被包装在C++的命名空间中,如上例中的Open会被封装在 foo::bar空间中; - 对于Java,包声明符会变为java的一个包,除非在.proto文件中提供了一个明确有java_package
  • 对于 Python,这个包声明符是被忽略的,因为Python模块是按照其在文件系统中的位置进行组织的。
  • 对于Go,包可以被用做Go包名称,除非你显式的提供一个option go_package在你的.proto文件中。
  • 对于Ruby,生成的类可以被包装在内置的Ruby名称空间中,转换成Ruby所需的大小写样式 (首字母大写;如果第一个符号不是一个字母,则使用PB_前缀),例如Open会在Foo::Bar名称空间中。
  • 对于javaNano包会使用Java包,除非你在你的文件中显式的提供一个option java_package
  • 对于C#包可以转换为PascalCase后作为名称空间,除非你在你的文件中显式的提供一个option csharp_namespace,例如,Open会在Foo.Bar名称空间中

包及名称的解析

Protocol buffer语言中类型名称的解析与C++是一致的:首先从最内部开始查找,依次向外进行,每个包会被看作是其父类包的内部类。当然对于 (foo.bar.Baz)这样以“.”分隔的意味着是从最外围开始的。

ProtocolBuffer编译器会解析.proto文件中定义的所有类型名。 对于不同语言的代码生成器会知道如何来指向每个具体的类型,即使它们使用了不同的规则。

定义服务(Service)

如果想要将消息类型用在RPC(远程方法调用)系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根。如,想要定义一个RPC服务并具有一个方法,该方法能够接收 SearchRequest并返回一个SearchResponse,此时可以在.proto文件中进行如下定义:

1
2
3
service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

  

最直观的使用protocol buffer的RPC系统是gRPC一个由谷歌开发的语言和平台中的开源的PRC系统,gRPC在使用protocl buffer时非常有效,如果使用特殊的protocol buffer插件可以直接为您从.proto文件中产生相关的RPC代码。

如果你不想使用gRPC,也可以使用protocol buffer用于自己的RPC实现,你可以从proto2语言指南中找到更多信息

还有一些第三方开发的PRC实现使用Protocol Buffer。参考第三方插件wiki查看这些实现的列表。

JSON 映射

Proto3 支持JSON的编码规范,使他更容易在不同系统之间共享数据,在下表中逐个描述类型。

如果JSON编码的数据丢失或者其本身就是null,这个数据会在解析成protocol buffer的时候被表示成默认值。如果一个字段在protocol buffer中表示为默认值,体会在转化成JSON的时候编码的时候忽略掉以节省空间。具体实现可以提供在JSON编码中可选的默认值。

proto3JSONJSON示例注意
message object {“fBar”: v, “g”: null, …} 产生JSON对象,消息字段名可以被映射成lowerCamelCase形式,并且成为JSON对象键,null被接受并成为对应字段的默认值
enum string “FOO_BAR” 枚举值的名字在proto文件中被指定
map object {“k”: v, …} 所有的键都被转换成string
repeated V array [v, …] null被视为空列表
bool true, false true, false  
string string “Hello World!”  
bytes base64 string “YWJjMTIzIT8kKiYoKSctPUB+”  
int32, fixed32, uint32 number 1, -10, 0 JSON值会是一个十进制数,数值型或者string类型都会接受
int64, fixed64, uint64 string “1”, “-10” JSON值会是一个十进制数,数值型或者string类型都会接受
float, double number 1.1, -10.0, 0, “NaN”, “Infinity” JSON值会是一个数字或者一个指定的字符串如”NaN”,”infinity”或者”-Infinity”,数值型或者字符串都是可接受的,指数符号也可以接受
Any object {“@type”: “url”, “f”: v, … } 如果一个Any保留一个特上述的JSON映射,则它会转换成一个如下形式:{"@type": xxx, "value": yyy}否则,该值会被转换成一个JSON对象,@type字段会被插入所指定的确定的值
Timestamp string “1972-01-01T10:00:20.021Z” 使用RFC 339,其中生成的输出将始终是Z-归一化啊的,并且使用0,3,6或者9位小数
Duration string “1.000340012s”, “1s” 生成的输出总是0,3,6或者9位小数,具体依赖于所需要的精度,接受所有可以转换为纳秒级的精度
Struct object { … } 任意的JSON对象,见struct.proto
Wrapper types various types 2, “2”, “foo”, true, “true”, null, 0, … 包装器在JSON中的表示方式类似于基本类型,但是允许nulll,并且在转换的过程中保留null
FieldMask string “f.fooBar,h” 见fieldmask.proto
ListValue array [foo, bar, …]  
Value value   任意JSON值
NullValue null   JSON null

选项

在定义.proto文件时能够标注一系列的options。Options并不改变整个文件声明的含义,但却能够影响特定环境下处理方式。完整的可用选项可以在google/protobuf/descriptor.proto找到。

一些选项是文件级别的,意味着它可以作用于最外范围,不包含在任何消息内部、enum或服务定义中。一些选项是消息级别的,意味着它可以用在消息定义的内部。当然有些选项可以作用在域、enum类型、enum值、服务类型及服务方法中。到目前为止,并没有一种有效的选项能作用于所有的类型。

如下就是一些常用的选择:

  • java_package (文件选项) :这个选项表明生成java类所在的包。如果在.proto文件中没有明确的声明java_package,就采用默认的包名。当然了,默认方式产生的 java包名并不是最好的方式,按照应用名称倒序方式进行排序的。如果不需要产生java代码,则该选项将不起任何作用。如:
option java_package = "com.example.foo";
  • java_outer_classname (文件选项): 该选项表明想要生成Java类的名称。如果在.proto文件中没有明确的java_outer_classname定义,生成的class名称将会根据.proto文件的名称采用驼峰式的命名方式进行生成。如(foo_bar.proto生成的java类名为FooBar.java),如果不生成java代码,则该选项不起任何作用。如:
option java_outer_classname = "Ponycopter";

optimize_for(文件选项): 可以被设置为 SPEED, CODE_SIZE,或者LITE_RUNTIME。这些值将通过如下的方式影响C++及java代码的生成: 

    • SPEED (default): protocol buffer编译器将通过在消息类型上执行序列化、语法分析及其他通用的操作。这种代码是最优的。
    • CODE_SIZE: protocol buffer编译器将会产生最少量的类,通过共享或基于反射的代码来实现序列化、语法分析及各种其它操作。采用该方式产生的代码将比SPEED要少得多, 但是操作要相对慢些。当然实现的类及其对外的API与SPEED模式都是一样的。这种方式经常用在一些包含大量的.proto文件而且并不盲目追求速度的 应用中。
    • LITE_RUNTIME: protocol buffer编译器依赖于运行时核心类库来生成代码(即采用libprotobuf-lite 替代libprotobuf)。这种核心类库由于忽略了一 些描述符及反射,要比全类库小得多。这种模式经常在移动手机平台应用多一些。编译器采用该模式产生的方法实现与SPEED模式不相上下,产生的类通过实现 MessageLite接口,但它仅仅是Messager接口的一个子集。
1
option optimize_for = CODE_SIZE;

  

  • cc_enable_arenas(文件选项):对于C++产生的代码启用arena allocation
  • objc_class_prefix(文件选项):设置Objective-C类的前缀,添加到所有Objective-C从此.proto文件产生的类和枚举类型。没有默认值,所使用的前缀应该是苹果推荐的3-5个大写字符,注意2个字节的前缀是苹果所保留的。
  • deprecated(字段选项):如果设置为true则表示该字段已经被废弃,并且不应该在新的代码中使用。在大多数语言中没有实际的意义。在java中,这回变成@Deprecated注释,在未来,其他语言的代码生成器也许会在字标识符中产生废弃注释,废弃注释会在编译器尝试使用该字段时发出警告。如果字段没有被使用你也不希望有新用户使用它,尝试使用保留语句替换字段声明。
int32 old_field = 6 [deprecated=true];

自定义选项

ProtocolBuffers允许自定义并使用选项。该功能应该属于一个高级特性,对于大部分人是用不到的。如果你的确希望创建自己的选项,请参看 Proto2 Language Guide。注意创建自定义选项使用了拓展,拓展只在proto3中可用。

 

 

编译protobuf-c:
  1. aarch64

    $./configure CC=aarch64-target-linux-gnu-gcc CXX=aarch64-target-linux-gnu-g++ –host=aarch64-target-linux –disable-protoc –prefix=/home/jay/Work/Tools/protobuf-c/protobuf-c-1.4.0/build-armx

  2. x86_64

    $./configure –prefix=/home/dev/Tools/protobuf-c-x64/protobuf-c-1.4.0/build-x64 protobuf_LIBS=/home/dev/Tools/protobuf-cpp-x64/protobuf-3.5.1/build-x64/lib/libprotobuf.so protobuf_CFLAGS=’-L/home/dev/Tools/protobuf-cpp-x64/protobuf-3.5.1/build-x64/lib/ -I/home/dev/Tools/protobuf-cpp-x64/protobuf-3.5.1/build-x64/include’

protobuf-c 编译依赖 protobuf-cpp 所以 请先编译protobuf-cpp。 protobuf-cpp中包含google/compiler/*

其他错误 protoc(protoc –version) 的版本应该跟protobuf-cpp的本版对应 如果不对应会提示

编译protobuf-cpp
  1. aarch64

$ ./configure CC=aarch64-target-linux-gnu-gcc CXX=aarch64-target-linux-gnu-g++ –host=aarch64-target-linux –prefix=/home/dev/Tools/protobuf-cpp-aarch64/protobuf-3.5.1/build-aarch64 –with-sysroot=/home/dev/App/MDC_Cross_Compiler/sysroot/ –with-protoc=/home/dev/Tools/protobuf-cpp-x64/protobuf-3.5.1/build-x64/bin/protoc //protc最好先生成x64的备用,后边针对.proto文件会有一个版本的验证
如果出现as:xxxxxxx”64”:是因为使用了编译器的as,使用export=/usr/bin/as/“$PATH 更新成系统的as就可以了

  1. x86

    $./configure –prefix=/home/dev/Tools/protobuf-cpp-x64/protobuf-3.5.1/build-x64

linux 使用systemd管理启动文件

service新建文件位置

/etc/

service文件结构

其中[Unit][Service][Install]不可缺少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Unit]
Description=
Documentation=
After=network.target
Wants=
Requires=

[Service]
ExecStart=/home/downey/test.sh
ExecStop=
ExecReload=/home/downey/test.sh
Type=simple

[Install]
WantedBy=multi-user.target
service文件配置项解析
1
2
3
4
5
6
7
8
9
10
11
Description:运行软件描述
Documentation:软件的文档
After:因为软件的启动通常依赖于其他软件,这里是指定在哪个服务被启动之后再启动,设置优先级
Wants:弱依赖于某个服务,目标服务的运行状态可以影响到本软件但不会决定本软件运行状态
Requires:强依赖某个服务,目标服务的状态可以决定本软件运行。
ExecStart:执行命令
ExecStop:停止执行命令
ExecReload:重启时的命令
Type:软件运行方式,默认为simple
WantedBy:这里相当于设置软件,选择运行在linux的哪个运行级别,只是在systemd中不在有运行级别概念,但是这里权当这么理解。
> 还有其他的配置项

Qt MultiMedia

qt 多媒体模块介绍
类名 英文描述 中文描述
|类名|英文描述|中文描述|
|:-|:-:|:-:|
|a|2|3|

|QAudioBuffer|Represents a collection of audio samples with a specific format and sample rate|表示具有特定格式和采样率的音频样本的集合|
QAudioBuffer::StereoFrame| Simple wrapper for a stereo audio frame |立体声音频框架的简单包装|

|QAudioDecoder |Allows decoding audio 允许解码音频|
|QAudioDeviceInfo |Interface to query audio devices and their functionality |用于查询音频设备及其功能的接口|
|QAudioFormat |Stores audio stream parameter information 存储音频流参数信息|
|QAudioInput |Interface for receiving audio data from an audio input device |用于从音频输入设备接收音频数据的接口|
|QAudioOutput|Interface for sending audio data to an audio output device |用于将音频数据发送到音频输出设备的接口|
|QAudioProbe|Allows you to monitor audio being played or recorded |允许您监视正在播放或录制的音频|
||QAbstractAudioDeviceInfo |Base class for audio backends |音频后端的基础类|
QAbstractAudioInput Access for |QAudioInput to access the audio device provided by the plugin |访问QAudioInput访问插件提供的音频设备|
|QAbstractAudioOutput |Base class for audio backends |音频后端的基础类|
||QAudioSystemPlugin |Abstract base for audio plugins |音频插件抽象基础|
|QSound |Method to play .wav sound files |播放.wav声音文件的方法|
|QSoundEffect |Way to play low latency sound effects |播放低延时音效的方式|
|QCamera::FrameRateRange |A FrameRateRange represents a range of frame rates as minimum and maximum rate |FrameRateRange表示帧速率的范围为最小和最大速率|
|QCamera |Interface for system camera devices |系统相机设备接口|
|QCameraExposure |Interface for exposure related camera settings |曝光相关摄像机设置界面|
|QCameraFocus |Interface for focus and zoom related camera settings |用于对焦和变焦相关设置的界面|
|QCameraFocusZone |Information on zones used for autofocusing a camera |关于用于自动对焦相机的区域的信息|
|QCameraImageProcessing |Interface for image processing related camera settings |图像处理相关摄像机设置界面|
|QCameraInfo |General information about camera devices |有关相机设备的一般信息|
|QCameraViewfinderSettings |Set of viewfinder settings |设置取景器设置|
|QAudioDecoderControl |Access to the audio decoding functionality of a QMediaService |访问QMediaService的音频解码功能|
|QAudioEncoderSettingsControl |Access to the settings of a media service that performs audio encoding |访问执行音频编码的媒体服务的设置|
|QAudioInputSelectorControl |Audio input selector media control |音频输入选择器媒体控制|
|QAudioOutputSelectorControl |Audio output selector media control |音频输出选择器媒体控制|
|QAudioRoleControl |Control over the audio role of a media object |控制媒体对象的音频角色|
|QCameraCaptureBufferFormatControl |Control for setting the capture buffer format |用于设置捕获缓冲区格式的控制|
|QCameraCaptureDestinationControl |Control for setting capture destination |控制设置捕获目的地|
|QCameraControl |Abstract base class for classes that control still cameras or video cameras |用于控制静态相机或摄像机的类的抽象基类|
|QCameraExposureControl |Allows controlling camera exposure parameters |允许控制相机曝光参数|
|QCameraFeedbackControl |Allows controlling feedback (sounds etc) during camera operation |允许在相机操作期间控制反馈(声音等)|
|QCameraFlashControl |Allows controlling a camera’s flash |允许控制相机的闪光灯|
|QCameraFocusControl |Supplies control for focusing related camera parameters |用于控制相关的相机参数|
|QCameraImageCaptureControl |Control interface for image capture services |图像捕获服务控制界面|
|QCameraImageProcessingControl |Abstract class for controlling image processing parameters, like white balance, contrast, saturation, sharpening and denoising |用于控制图像处理参数的抽象类,如白平衡,对比度,饱和度,锐化和去噪|
|QCameraInfoControl |Camera info media control |相机信息媒体控制|
|QCameraLocksControl |Abstract base class for classes that control still cameras or video cameras |用于控制静态相机或摄像机的类的抽象基类|
|QCameraViewfinderSettingsControl |Abstract class for controlling camera viewfinder parameters |用于控制摄像机取景器参数的抽象类|
|QCameraViewfinderSettingsControl2 |Access to the viewfinder settings of a camera media service |访问摄像机媒体服务的取景器设置|
|QCameraZoomControl |Supplies control for optical and digital camera zoom |用于光学和数码相机变焦的耗材控制|
|QImageEncoderControl |Access to the settings of a media service that performs image encoding |访问执行图像编码的媒体服务的设置|
|QMediaAudioProbeControl |Allows control over probing audio data in media objects |允许控制媒体对象中的探测音频数据|
|QMediaAvailabilityControl |Supplies a control for reporting availability of a service |提供报告服务可用性的控制|
|QMediaContainerControl |Access to the output container format of a QMediaService |访问QMediaService的输出容器格式|
|QMediaGaplessPlaybackControl |Access to the gapless playback related control of a QMediaService |访问无缝播放相关控件的QMediaService|
|QMediaNetworkAccessControl |Allows the setting of the Network Access Point for media related activities |允许为媒体相关活动设置网络接入点|
|QMediaPlayerControl |Access to the media playing functionality of a QMediaService |访问QMediaService的媒体播放功能|
|QMediaRecorderControl |Access to the recording functionality of a QMediaService |访问QMediaService的录制功能|
|QMediaStreamsControl |Media stream selection control |媒体流选择控制|
|QMediaVideoProbeControl |Allows control over probing video frames in media objects |允许控制媒体对象中的探测视频帧|
|QMetaDataReaderControl |Read access to the meta-data of a QMediaService’s media |读取对QMediaService媒体元数据的访问权限|
|QMetaDataWriterControl |Write access to the meta-data of a QMediaService’s media |写入对QMediaService媒体元数据的访问|
|QRadioDataControl |Access to the RDS functionality of the radio in the QMediaService |访问QMediaService中无线电的RDS功能|
|QRadioTunerControl |Access to the radio tuning functionality of a QMediaService |访问QMediaService的无线电调谐功能|
|QVideoDeviceSelectorControl |Video device selector media control |视频设备选择器媒体控制|
|QVideoEncoderSettingsControl |Access to the settings of a media service that performs video encoding |访问执行视频编码的媒体服务的设置|
|QVideoRendererControl |Media control for rendering video to a QAbstractVideoSurface |媒体控制,用于将视频渲染到QAbstractVideoSurface|
|QVideoWindowControl |Media control for rendering video to a window |用于将视频渲染到窗口的媒体控制|
|QMediaContent |Access to the resources relating to a media content |访问与媒体内容相关的资源|
|QMediaPlayer |Allows the playing of a media source |允许播放媒体源|
|QMediaPlaylist |List of media content to play |要播放的媒体内容列表|
|QMediaResource |Description of a media resource |媒体资源的描述|
|QMediaBindableInterface |The base class for objects extending media objects functionality |扩展媒体对象功能的对象的基类|
|QMediaControl |Base interface for media service controls |媒体服务控制的基本界面|
|QMediaObject |Common base for multimedia objects |多媒体对象的共同基础|
|QMediaService |Common base class for media service implementations |媒体服务实现的通用基类|
|QMediaServiceCameraInfoInterface |Interface provides camera-specific information about devices supported by a camera service plug-in |接口提供有关相机服务插件支持的设备的相机特定信息|
|QMediaServiceDefaultDeviceInterface |Interface identifies the default device used by a media service plug-in |接口标识介质服务插件使用的默认设备|
|QMediaServiceFeaturesInterface |Interface identifies features supported by a media service plug-in |接口识别媒体服务插件支持的功能|
|QMediaServiceProviderPlugin |Interface provides an interface for QMediaService plug-ins |接口为QMediaService插件提供了一个接口|
|QMediaServiceSupportedDevicesInterface Interface identifies the devices supported by a media service plug-in |接口标识媒体服务插件支持的设备|
|QMediaServiceSupportedFormatsInterface |Interface identifies if a media service plug-in supports a media format |接口识别媒体服务插件是否支持媒体格式|
|QMediaTimeInterval |Represents a time interval with integer precision |表示整数精度的时间间隔|
|QMediaTimeRange |Represents a set of zero or more disjoint time intervals |表示一组零个或多个不相交的时间间隔|
|QRadioData |Interfaces to the RDS functionality of the system radio |与系统无线电的RDS功能的接口|
|QRadioTuner |Interface to the systems analog radio device |与系统模拟无线电设备的接口|
|QAudioRecorder |Used for the recording of audio |用于录制音频|
|QAudioEncoderSettings |Set of audio encoder settings |音频编码器设置|
|QImageEncoderSettings |Set of image encoder settings |设置图像编码器设置|
|QVideoEncoderSettings |Set of video encoder settings |视频编码器设置集|
|QMediaRecorder |Used for the recording of media content |用于录制媒体内容|
|QAbstractPlanarVideoBuffer |Abstraction for planar video data |平面视频数据的抽象|
|QAbstractVideoBuffer |Abstraction for video data |视频数据抽象|
|QAbstractVideoFilter |Represents a filter that is applied to the video frames received by a VideoOutput type |表示应用于VideoOutput类型接收到的视频帧的过滤器|
|QVideoFilterRunnable |Represents the implementation of a filter that owns all graphics and computational resources, and performs the actual filtering or calculations |表示具有所有图形和计算资源的过滤器的实现,并执行实际的过滤或计算|
|QAbstractVideoSurface |Base class for video presentation surfaces |视频演示表面的基类|
|QVideoFrame |Represents a frame of video data |表示一帧视频数据|
|QVideoProbe |Allows you to monitor video frames being played or recorded |允许您监视正在播放或录制的视频帧|
|QVideoSurfaceFormat |Specifies the stream format of a video presentation surface |指定视频演示表面的流格式|
|

[TOC]

  • 指定变量存储位置

    • 对于函数内的局部变量,都是在栈内存区存放,函数结束后就自动释放,但是全局的和函数内的 static 修饰的变量都是存放在数据区的,且只存一份,直到整个程序结束后才自动释放
    • 由于 static 修饰的变量只存一份地址,即同一地址。所以不管函数调用多少次,函数内定义 static 变量的语句只会在第一次调用时执行,后面调用都不再执行也不再初始化,而是对该地址内的数据进行操作(即只初始化一次)。
  • C++ 类中 static 关键字修饰的成员变量

    • 类中被 static 关键字修饰的成员变量属于类,而不是属于某个实例。只有一个地址保存一份数据存放于数据区。在类中只是声明,并不是定义,因此不分配内存,对类用 sizeof 求大小也不会将 static 变量的大小计算在内。
    • 必须在类的外部,对类的 static 成员变量进行定义(定义之后才会分配唯一内存,此时该类静态成员变量相当于是一个全局的静态变量了,只是调用时需要使用类名加 ::)。 若只定义不初始化,则默认初始化为 0
    • public 的静态成员变量既可以通过类名引用,也可以通过对象名引用(会自动转换为类名引用)。
  • C++ 类中被 static 关键字修饰的成员函数

    • 只能调用本类的静态成员变量或函数,不能调用本类的非静态成员函数和变量。因为非静态成员函数和变量在类成员函数中调用时,都是由形参中隐含一个指向当前实例对象的 this 指针来调用,而静态成员函数没有这个 this 指针形参。

[^1]参考:https://www.jianshu.com/p/0b2d9679a9f2[^1]

  • 请说出static和const关键字尽可能多的作用
    • static
      • 函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值
      • 在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问
      • 在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内
      • 在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝
      • 在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量
    • const
      • 欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了
      • 对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const
      • 在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值

C++ 中的四种 cast 转换

  • const_cast
    • 去除指针或引用的 const 属性, 并且仍然指向原来的对象。
  • static_cast
    • 在编译期对变量进行强制类型转换,相当于 C 语言中的强制类型转换语法。
    • 转换数据类型,类的上下行转换
    • 转换数据类型,由于没有运行时类型检查来保证转换的安全性,不安全
    • 类的上下行转换,由于没有运行时类型检查,下行转换不安全
    • static_cast 不能转换掉原有类型的 const、volatile、或者 __unaligned 属性
    • c++ 的任何的隐式转换都是使用 static_cast 来实现
  • dynamic_cast
    • 安全的上下行转换。
    • 需要注意的是,dynamic_cast的使用必须同时满足以下所有条件:
      • 被转换的变量的类型为基类指针或引用,且其确实存放了一个继承类指针或引用
      • 基类具有虚表,即基类必须至少定义了一个虚函数
  • reinterpret_cast
    • 这个强制类型转换的作用是提供某个变量在底层数据上的重新解释。当我们对一个变量使用reinterpretcast后,编译器将无视任何不合理行为,强行将被转换变量的内存数据重解释为某个新的类型。需要注意的是,reinterpret_cast 要求转换前后的类型所占用内存大小一致,否则将引发编译时错误。
    • 进行无关类型的转换
    • 用在任意的指针间的转换,任意引用间的转换,指针和足够大的整型之间的转换,整型到指针的转换。

[^2]参考:https://www.cnblogs.com/zeppelin5/p/10075569.html[^2]
[^3]参考:https://baijiahao.baidu.com/s?id=1643939248815582630&wfr=spider&for=pc[^3]

指针和引用的区别

  • 引用必须被初始化,而指针不用,可以为空。
  • 引用不是对象,而指针本身是对象
  • 引用指向对象本身,而指针指向的对象地址。

__cplusplus 编译宏

1
2
3
4
5
6
7
8
9
10
11
12
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif

例如,假设某个函数的原型为:
void foo(int x, int y);
该函数被C编译器编译后在 symbol 库中的名字为 _foo,而C++编译器则会产生像 _foo_int_int之类的名字。_foo_int_int 这样的名字包含了函数名和函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。
为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern "C"来解决名字匹配问题,函数声明前加上 extern "C"后,则编译器就会按照C语言的方式将该函数编译为 _foo,这样C语言中就可以调用C++的函数了。

_builtin_expect

  • GUNC 下的跳转优化宏,提高执行效率. 下面是 folly Likely.h 的定义示范
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #undef LIKELY
    #undef UNLIKELY

    #if defined(__GNUC__)
    #define LIKELY(x) (__builtin_expect((x), 1))
    #define UNLIKELY(x) (__builtin_expect((x), 0))
    #else
    #define LIKELY(x) (x)
    #define UNLIKELY(x) (x)
    #endif

union 联合

  • 联合是一种特殊的类,一个union可以有多个数据成员,这些数据成员共同使用同一片存储空间,分配给一个union对象的存储空间至少要能容纳它的最大数据成员。
  • union不能含有引用类型的成员,默认情况下,union的成员都是公有的,这一点和struct相同.
  • union既不能继承自其他类,也不能作为基类使用,所以在union中不能含有虚函数
  • 为union的一个数据成员赋值时会令其它数据成员变成未定义的状态
  • 它的所有成员相对于基地址的偏移量都为0
  • union 定义可以指定成员所占的空间:
    1
    2
    3
    union u {
    unsigned int bb : 7; // (bit 0-6)
    };
  • 匿名 union. 匿名union是一个未命名的union,一旦我们定义了一个匿名union,编译器就自动为该union创建一个未命名对象。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int main(void) {
    union {
    unsigned char value1;
    unsigned short value2;
    unsigned int value3;
    };
    value3 = 0x90909090;
    printf("%x\n", value1);//0x90
    printf("%x\n", value2);//0x9090
    printf("%x\n", value3);//0x90909090
    system("pause");
    return 0;
    }
  • union 应用场景
    • 用来节省内存空间(多个变量使用同一处内存)
    • 用来判断大端还是小端,和解决TCP黏包问题.
      1
      2
      3
      4
      5
      6
      union littleOrBig {
      int number;
      char numByte;
      };

      使用的时候,给number赋值1,然后判断numByte是否等于1是大端还是小端,等于1是小端
      1
      2
      3
      4
      5
      同理,解决TCP黏包的核心在于每次发送数据时,需要把数据的长度,放在数据的前面一起发送出去。我们使用联合体,就可以很容的把数据长度转成byte。联合体如下:
      union tcpUnion {
      int number;
      char numByte[4];
      };

[^4]参考:https://www.jianshu.com/p/35b187613ec6[^4]
[^5]参考:https://blog.csdn.net/HFUTWXY/article/details/102574101[^5]

大端和小端

  • 大端: 低地址存高数据位,高地址存底数据位
  • 小端: 低地址存低数据位,高地址存高数据位
    1
    2
    3
    4
    5
    把 0x112233 这个数存放到存储器中,采用大小端方式存储就是如下方式:
    大端模式: 11 22 33
    低地址 --> 高地址
    小端模式: 33 22 11
    低地址 --> 高地址

C++ 和 C 区别

  • 设计思想上
    • C++是面向对象的语言,而C是面向过程的结构化编程语言
  • 语法上
    • C++具有封装、继承和多态三种特性
    • C++相比C,增加多许多类型安全的功能,比如强制类型转换
    • C++支持范式编程,比如模板类、函数模板等

请你说一下你理解的c++中的 smart pointer 四个智能指针:shared_ptr, unique_ptr, weak_ptr, auto_ptr

  • auto_ptr(c++98的方案,cpp11已经抛弃)
    • 采用所有权模式
      1
      2
      3
      4
      5
      auto_ptr< string> p1 (new string ("I reigned lonely as a cloud."));
      auto_ptr<string> p2;
      p2 = p1; //auto_ptr不会报错.

      此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!
  • unique_ptr (替换 auto_ptr)
    • unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如”以 new 创建对象后因为发生异常而忘记调用 delete”)特别有用。
    • 采用所有权模式
    • C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。
    • 代码示例:
      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
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      #include <iostream>
      #include <memory>

      int main() {
      // member fuction reset
      std::unique_ptr<int> up; // empty

      up.reset(new int); // takes ownership of pointer
      *up = 5;
      std::cout << *up << '\n';

      up.reset(new int); // deletes managed object, acquires new pointer
      *up = 10;
      std::cout << *up << '\n';

      up.reset(); // deletes managed object

      // member function swap
      std::unique_ptr<int> foo(new int(10));
      std::unique_ptr<int> bar(new int(20));

      foo.swap(bar);
      std::cout << "foo: " << *foo << '\n';
      std::cout << "bar: " << *bar << '\n';
      // output: foo:20 bar: 10

      // member function release
      std::unique_ptr<int> auto_pointer(new int);
      int *manual_pointer;

      *auto_pointer = 10;

      manual_pointer = auto_pointer.release();
      // (auto_pointer is now empty)

      std::cout << "manual_pointer points to " << *manual_pointer << '\n';

      delete manual_pointer;

      // output: manual_pointer points to 10

      return 0;
      }
  • shared_ptr
    • shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。
    • shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。
    • 成员函数:
      • use_count 返回引用计数的个数
      • unique 返回是否是独占所有权( use_count 为 1)
      • swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
      • reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
      • get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptr sp(new int(1)); sp 与 sp.get()是等价的
  • weak_ptr
    • weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr.* weak_ptr 只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr 是用来解决 shared_ptr 相互引用时的死锁问题,如果说两个 shared_ptr 相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和 shared_ptr 之间可以相互转化,shared_pt 可以直接赋值给它,它可以通过调用 lock 函数来获得 shared_ptr。
      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
      class B;
      class A {
      public:
      shared_ptr<B> pb_;
      ~A(){
      cout<<"A delete\n";
      }
      };
      class B {
      public:
      shared_ptr<A> pa_;
      ~B() {
      cout<<"B delete\n";
      }
      };
      void fun() {
      shared_ptr<B> pb(new B());
      shared_ptr<A> pa(new A());
      pb->pa_ = pa;
      pa->pb_ = pb;
      std::cout << pb.use_count() << std::endl;
      std::cout << pa.use_count() << std::endl;
      }
      int main() {
      fun();
      return 0;
      }

      可以看到fun函数中pa ,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A B的析构函数没有被调用),如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb_; 改为weak_ptr pb_; 运行结果如下,这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减一,同时pa析构时使A的计数减一,那么A的计数为0,A得到释放。
      注意的是我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print(); 英文pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:shared_ptr p = pa->pb_.lock(); p->print();

new 和 make_shared 区别

  • make_shared 比 new 性能更好,因为 new 实际上是有两次内存分配(new 需要为对象分配一次内存,还要为控制块再分配一次), 而 make_shared 只需要分配一块独立的内存用来保存对象和控制块。
  • new 可能会产生内存碎片,原因同上
  • 在作为参数传入函数时, new 有可能能造成内存泄漏
    [^6]https://blog.csdn.net/coolmeme/article/details/43405155[^6]
    • 请你回答一下为什么析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数
    • 将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们 new 一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。
    • C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数

请你来说一下fork函数

  • fork:创建一个和当前进程映像一样的进程可以通过 fork() 系统调用

    1
    2
    3
    #include <sys/types.h>
    #include <unistd.h>
    pid_t fork(void);
  • 成功调用 fork() 会创建一个新的进程,它几乎与调用 fork() 的进程一模一样,这两个进程都会继续运行。在子进程中,成功的 fork() 调用会返 回0。在父进程中 fork() 返回子进程的 pid。如果出现错误,fork() 返回一个负值。

  • 最常见的 fork() 用法是创建一个新的进程,然后使用 exec() 载入二进制映像,替换当前进程的映像。这种情况下,派生(fork)了新的进程,而这个子进程会执行一个新的二进制可执行文件的映像。这种“派生加执行”的方式是很常见的。

  • 在早期的Unix系统中,创建进程比较原始。当调用fork时,内核会把所有的内部数据结构复制一份,复制进程的页表项,然后把父进程的地址空间中的内容逐页的复制到子进程的地址空间中。但从内核角度来说,逐页的复制方式是十分耗时的。现代 的Unix系统采取了更多的优化,例如Linux,采用了写时复制的方法,而不是对父进程空间进程整体复制。

    写入时复制思想(写时复制)

  • 写入时复制是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这个过程对其他的调用者是透明的(transparently)。此作法的主要优点是如果调用者没有修改该资源,就不会有副本(private copy)被建立,因此多个调用者只是读取操作是可以共享同一份资源。

  • 应用

    • 虚拟内存管理中的写时复制: 一般把这种被共享访问的页面标记为只读。当一个task试图向内存中写入数据时,内存管理单元(MMU)抛出一个异常,内核处理该异常时为该task分配一份物理内存并复制数据到此内存,重新向MMU发出执行该task的写操作。
    • 数据存储中的写时复制: [Linux]等的文件管理系统使用了写时复制策略。[数据库]服务器也一般采用了写时复制策略,为用户提供一份snapshot。
    • 软件应用中的写时复制: [C++标准程序库]中的[std::string]类,在C++98/C++03标准中是允许写时复制策略。但在[C++11]标准中为了提高并行性取消了这一策略。 GCC从版本5开始,std::string不再采用COW策略

    [^7]参考:https://www.jianshu.com/p/8292e285e26a[^7]

析构函数调用顺序

  • 1)派生类本身的析构函数;2)对象成员析构函数;3)基类析构函数。

C++ STL 容器

array

  • 内存连续,大小在编译期确定无法改变, 元素的分配不是依赖 allocator.
  • 元素访问方法和数组一样。
  • array 可以作为一个 tuple 对象操作, 它重载了 get 接口, 可以使用 tuple_size 和 tuple_element.
  • 交换两个 array 的效率是非常低的,因为它是对两个 array 中的元素一一交换。

    deque

  • 在两端插入和删除元素效率比较高,在两端之外的地方频繁插入和删除元素效率比较差。
  • 不保证元素是连续存储的,分配新内存不需要将原来的元素拷贝到新内存。
  • push_back push_front 新增元素会导致所有的迭代器失效,但指针和引用不会失效。

    vector

  • 和数组一样可以通过下标访问元素且效率一样高,但大小可动态改变。
  • 每次分配内存都会把原来的元素拷贝到新内存,导致效率比较低。所以分配内存会预先分配大约一倍的大小(GNU), vs 编译器下是分配原来的 1/2。
  • 随机访问元素和在末端添加和删除元素效率很高,但在 vector 中间插入和删除元素效率很低
  • push_back 新增元素时, 如果 vector 因为内存不够而发生自动重新分配内存的动作那么所有的这个 vector 相关的迭代器、指针和引用都将失效(原因为第 2 条)。如果没有发生,则失效的仅仅是末尾的迭代器.

    forward_list

  • 实质是一个单链表(内存不连续),且实质上与其在 C 中实现相比无任何开销。
  • 插入和删除效率比 list 要高一点(list 为双链表,增加了一个前向指针)
  • 相比 array、vector、deque 这些序列容器,forward_list 在任何位置插入、删除和移动元素效率比较高,因此在算法中应用比较多比如 排序算法。但不支持随机访问。

    list

  • 实质为一个双向链表
  • 参考 froward_list

    map

  • Maps are typically implemented as binary search trees.
  • key 是唯一的
  • map 中的数据都是有序的,默认是升序.

    multimap

  • Key 可以重复
  • 数据有序

    unordered_map

  • 数据无序
  • 通过 key 访问元素比 map 速度快。但是要查询某一范围内的key值时比map效率低

    queue(队列适配器 FIFO)

  • 因为 queue 支持 empty、size、front、back、push_back、pop_front 操作,所以 queue 基于 deque 实现(但也可以用 list 或 vector 实现)

    priority_queue

  • 基于 vector 实现(也可以用 deque 实现)

    set

  • 基于 binary search trees 实现
  • key 即值,即只保存关键字。
  • 数据有序
  • 元素默认为 const,元素值不能修改,但可以插入和删除.

    unordered_set

  • 和 set 区别参考 map vs unordered_map

    stack(栈适配器)

  • 基于 deque 实现

Zero-copy 技术

GDB 调试

Linux 下死循环等问题调试方法

多线程

多进程

网络编程

类与类之间的关系

依赖

  • A类使用到了B类一部分属性或方法。不会主动改变B类内的内容。
  • 代码化一些
    • 类A把类B的实例作为方法里的参数使用
    • 类A的某个方法里使用了类B的实例作为局部变量
    • 类A调用了类B的静态方法
      1
      2
      3
      4
      5
      6
      class Season{
      };
      class Goose{
      public:
      void Migrate(Season season); //或Migrate(Season *season)、Migrate(Season &season)
      };
  • 因为依赖和被依赖关系比较弱, 所以UML 图里使用虚线+箭头表示. 箭头指向被依赖者.而且一般是单向关系.

    关联

  • 关联是一种弱关系,但并不是从属关系,关联的连个的类可以看作是平等的,比如一只大雁和老鹰的关系, 就可以看作关联关系
  • A类需要B类作为它的属性,以进行一定的读操作。
  • 因为比依赖关系更强,所以在 UML 图中使用是实线+箭头,双向关联可以省略箭头
  • C++中,通过定义其他类指针类型的成员来实现关联,下面是双向关联的实现方法
    1
    2
    3
    4
    5
    6
    class Egle{
    class Goose *food;
    };
    class Goose{
    class Egle *predator;
    };

    聚合

  • 聚合是一种弱所属关系,比如一只大雁和雁群,就是一种”聚合”关系。和组合相比,被聚合的对象可以属于多个聚合对象,比如,一只大雁可能属于多个雁群。
  • 在 UML 图中使用空心菱形+实线+箭头表示, 箭头指向部分, 菱形指向整体。
  • 在C++语法中,通过类的指针来实现聚合
    1
    2
    3
    4
    5
    6
    class Goose{
    };
    class Geese{
    public:
    Goose member[10];
    };

    组合

  • 组合是将一个对象(部分)放到另一个对象里(组合)。它是一种 “has-a” 的关系。相比”聚合”,组合是一种强所属关系,组合关系的两个对象往往具有相同的生命周期,被组合的对象是在组合对象创建的同时或者创建之后创建,在组合对象销毁之前销毁。一般来说被组合对象不能脱离组合对象独立存在,而且也只能属于一个组合对象。比如,鸟类和翅膀类就是组合关系,在创建一个鸟类对象时,一定要同时或之后创建一个翅膀类对象,销毁一个鸟类对象时,一定要先同时或之前销毁翅膀对象。
  • 在 UML 图中,实心菱形 + 实线 + 箭头表示, 菱形指向整体, 箭头指向部分
  • 在C++语法中,使用在一个类中包含另外一个类类型的成员来实现组合。
    1
    2
    3
    4
    5
    class Wing{
    };
    class Bird{
    Wing wing;
    };

    继承(泛化)

  • 继承是面向对象的三大特征之一,是一种最能体现面向对象代码复用的类关系,对于继承,可以使用”is a”来表示,比如,小轿车(类B)”is a”车(类A),是对车(类A)的进一步刻画,那么这两个类就是”继承”关系。
    1
    2
    3
    class Goose : public Bird{
    //子类扩展属性和方法
    };
  • UML 图中使用带三角箭头的实线表示, 箭头指向父类或父接口.

    实现

  • 实现对应的是面向对象中的”接口”,即动物都要移动,但是每种移动的方式不一样,鸟要实现自己独有的移动的方法。
  • UML 中使用带三角箭头的虚线表示, 箭头指向接口
  • 在 C++ 中,接口通过的纯虚函数来实现, C++ 的多态就是通过虚函数来实现的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Animal{
    public:
    vitual void move();
    };
    class Bird: public Animal{
    void move(){
    //鸟的移动方式,飞
    }
    };
    [^9]参考:https://www.cnblogs.com/h-hg/p/8784232.html[^9]
    [^10]参考:https://blog.csdn.net/killfat/article/details/81275798[^10]
    [^11]参考:https://www.jianshu.com/p/f35fab1640c6[^11]

操作系统

Linux 系统信号

  • SIGHUP

    本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。

  • SIGINT

    程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。

  • SIGQUIT

    和SIGINT类似, 但由QUIT字符(通常是Ctrl-/)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。

  • SIGILL

    执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。

  • SIGTRAP

    由断点指令或其它trap指令产生. 由debugger使用。

  • SIGABRT

    调用abort函数生成的信号。

  • SIGBUS

    非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。

  • SIGFPE

    在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误

  • SIGKILL

    用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。

  • SIGUSR1

    留给用户使用

  • SIGSEGV

    试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.

  • SIGUSR2

    留给用户使用

  • SIGPIPE

    管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。

  • SIGALRM

    时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.

  • SIGTERM

    程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。

  • SIGCHLD

    子进程结束时, 父进程会收到这个信号。
    如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程来接管)。

  • SIGCONT

    让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符

  • SIGSTOP

    停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.

  • SIGTSTP

    停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号

  • SIGTTIN

    当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.

  • SIGTTOU

    类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.

  • SIGURG

    有”紧急”数据或out-of-band数据到达socket时产生.

  • SIGXCPU
    超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。

  • SIGXFSZ

    当进程企图扩大文件以至于超过文件大小资源限制。

  • SIGVTALRM

    虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.

  • SIGPROF

    类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.

  • SIGWINCH

    窗口大小改变时发出.

  • SIGIO

    文件描述符准备就绪, 可以开始进行输入/输出操作.

  • SIGPWR

    Power failure

  • SIGSYS

    非法的系统调用。

  • 在以上列出的信号中,程序不可捕获、阻塞或忽略的信号有
    SIGKILL,SIGSTOP

  • 不能恢复至默认动作的信号有
    SIGILL,SIGTRAP

  • 默认会导致进程流产的信号有
    SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ

  • 默认会导致进程退出的信号有
    SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM

  • 默认会导致进程停止的信号有
    SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU

  • 默认进程忽略的信号有
    SIGCHLD,SIGPWR,SIGURG,SIGWINCH

  • 此外,SIGIO在SVR4是退出,在4.3BSD中是忽略;SIGCONT在进程挂起时是继续,否则是忽略,不能被阻塞。

工程

请你回答一下git中, merge 和 rebase 区别

  • merge 会自动根据两个分支的共同祖先和两个分支的最新提交 进行一个三方合并, 然后将合并中修改的内容生成一个新的 commit,即 merge 合并两个分支并生成一个新的提交, 并且仍然后保存原来分支的 commit 记录
  • rebase 会从两个分支的共同祖先开始提取当前分支上的修改,然后将当前分支上的所有修改合并到目标分支的最新提交后面,如果提取的修改有多个,那 git 将依次应用到最新的提交后面。rebase 后只剩下一个分支的 commit 记录

思想(设计模式/思想, 编程思想)

关注点分离思想(Separation of concerns, SOC)

  • 就是在软件开发中,通过各种手段,将问题的各个关注点分开。
  • 如果一个问题能分解为独立且较小的问题,就是相对较易解决的.问题太过于复杂,要解决问题需要关注的点太多,而程序员的能力是有限的,不能同时关注于问题的各个方面。
  • 实现关注点分离的方法主要有两种,一种是标准化,另一种是抽象与封装。
    • 标准化: 标准化就是制定一套标准,让使用者都遵守它,将人们的行为统一起来,这样使用标准的人就不用担心别人会有很多种不同的实现,使自己的程序不能和别人的配合。Java EE就是一个标准的大集合。每个开发者只需要关注于标准本身和他所在做的事情就行了。就像是开发镙丝钉的人只专注于开发镙丝钉就行了,而不用关注镙帽是怎么生产的,反正镙帽和镙丝钉按标来就一定能合得上。
    • 抽象与封装:不断地把程序的某些部分抽像差包装起来,也是实现关注点分离的好方法。一旦一个函数被抽像出来并实现了,那么使用函数的人就不用关心这个函数是如何实现的,同样的,一旦一个类被抽像并实现了,类的使用者也不用再关注于这个类的内部是如何实现的。诸如组件,分层,面向服务,等等这些概念都是在不同的层次上做抽像和包装,以使得使用者不用关心它的内部实现细节。
    • 核心依然是 “高内聚,低耦合”

      OOP的设计模式的五项原则

开放-封闭原则

  • open模块的行为必须是开放的、支持扩展的,而不是僵化的。
  • closed在对模块的功能进行扩展时,不应该影响或大规模影响已有的程序模块。一句话概括:一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的。
  • 核心思想就是对抽象编程,而不对具体编程。

    单一职责原则

  • 单一职责有2个含义,一个是避免相同的职责分散到不同的类中,另一个是避免一个类承担太多职责。减少类的耦合,提高类的复用性。

    接口隔离原则(Interface Segregation Principle)

  • 这个原则就是说:使用多个隔离的接口,比使用单个接口要好。其实就是让我们 尽可能地细化接口,把每个完成某特定功能的方法都放在一个专门的接口里,而不是写成一个包含很多功能的胖接口。
  • 该原则的两个目的:
    • 一是降低互相关联的类之间的耦合性
    • 二是不让用户接触到它不需要的接口

      里氏替换原则(Liskov Substitution Principle)

  • 只要 父类 能出现的地方 子类 就可以出现,而且替换为 子类 也不产生任何异常错误,反之则不然。这主要体现在,我们经常使用抽象类/基类做为方法参数,具体使用哪个子类作为参数传入进去,由调用者决定。
  • 主要针对继承的设计原则
    • 父类的方法都要在子类中实现或者重写,并且派生类只实现其抽象类中生命的方法,而不应当给出多余的,方法定义或实现。
    • 在客户端程序中只应该使用父类对象而不应当直接使用子类对象,这样可以实现运行期间绑定。

      依赖倒置原则(Dependence Inversion Principle)

  • 高层模块不要依赖低层模块,所以依赖都应该是抽象的,抽象不应该依赖于具体细节而,具体细节应该依赖于抽象
    • 低层模块:不可分割的原子逻辑就是 低层模块
    • 高层模块:低层模块的组装合成后就是 高层模块
    • 抽象:C++ 中体现为基类,抽象类,而不单指抽象类
    • 细节:体现为子类,实现类
  • 通俗点讲,该原则包含以下几点要素
    • 模块间的依赖应该通过抽象发生,具体实现类之间不应该建立依赖关系
    • 接口或者抽象类不依赖于实现类,否则就失去了抽象的意义
    • 实现类依赖于接口或者抽象类

常见的设计模式

单例模式

  • 单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象
  • 优点:
    • 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
    • 2、避免对资源的多重占用(比如写文件操作)。
  • 缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
  • 使用场景:
    • 1、要求生产唯一序列号。
    • 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
    • 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
  • C++的实现有两种,一种通过局部静态变量,利用其只初始化一次的特点,返回对象。另外一种,则是定义全局的指针,getInstance判断该指针是否为空,为空时才实例化对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template<typename T>
    class Singleton {
    public:
    static T& getInstance() {
    static T instance;
    return instance;
    }
    private:
    Singleton();
    };
    [^8]参考:https://blog.csdn.net/q5707802/article/details/79251148[^8]

工厂模式

  • 工厂模式主要解决接口选择的问题。该模式下定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,使其创建过程延迟到子类进行。
  • 优点
    • 解耦,代码复用,更改功能容易。

      观察者模式

  • 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
  • 观察者模式中分为观察者和被观察者,当被观察者发生装填改变时,观察者会受到通知。主要为了解决对象状态改变给其他对象通知的问题,其实现类似于观察者在被观察者那注册了一个回调函数。

    装饰器模式

  • 装饰器模式主要是为了动态的为一个对象增加新的功能,装饰器模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。
  • 优点
    • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
  • 缺点
    • 多层装饰比较复杂
  • 使用场景
    • 1、扩展一个类的功能。
    • 2、动态增加功能,动态撤销。

      编程

      写出完整版的strcpy函数

      1
      2
      3
      4
      5
      6
      char *strcpy(char *dst, char *src){
      assert((dst != NULL) && (src != NULL));
      char *address = dst;
      while((*dst++ = *src++) != '\0');
      return address;
      }

写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个

1
#define MIN(A,B) ((A) <= (B) ? (A) : (B))

编写一个函数,作用是把一个char组成的字符串循环右移n个。比如原来是“abcdefghi”如果n=2,移位后应该是“hiabcdefg”

1
2
3
4
5
6
7
void LoopMove(char *str, int steps) {
int len = strlen(str);
char tmp[MAXSIZE];
memcpy(tmp, str+len-steps, steps);
memcpy(str+steps, str, len-steps);
memcpy(str, tmp, steps);
}

编写类String的构造函数、析构函数和赋值函数,已知类String的原型为:

1
2
3
4
5
6
7
8
9
10
11
12
13
class String
{
public:
String(const char *str = NULL); // 普通构造函数
String(const String &other); // 拷贝构造函数
~ String(void); // 析构函数
String & operator =(const String &other); // 赋值函数
friend ostream &operator<<(ostream &os, String&s);
friend istream &operator>>(istream &is, String&s);
friend bool operator==(const String &s1, const String &s2);
private:
char *m_data; // 用于保存字符串
};
  • 答案:
    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    //普通构造函数
    String::String(const char *str) {
    if(str == NULL) {
    m_data = new char[1]; // 得分点:对空字符串自动申请存放结束标志'\0'的空
    //加分点:对m_data加NULL 判断
    *m_data = '\0';
    } else {
    int length = strlen(str);
    m_data = new char[length+1];
    strcpy(m_data, str);
    }
    }
    // String的析构函数
    String::~String(void) {
    delete [] m_data; // 或delete m_data;
    }
    //拷贝构造函数
    // 得分点:输入参数为const型
    String::String(const String &other) {
    int length = strlen(other.m_data);
    m_data = new char[length+1];     
    strcpy(m_data, other.m_data);
    }
    //赋值函数
    // 得分点:输入参数为const型
    String & String::operator =(const String &other) {
    //得分点:检查自赋值
    if(this == &other)   
    return *this;
    delete [] m_data;     //得分点:释放原有的内存资源
    int length = strlen( other.m_data );
    m_data = new char[length+1];  
    strcpy( m_data, other.m_data );
    return *this;         //得分点:返回本对象的引用
    }

    // 重载是否相等运算符
    bool operator==(const String &s1, const String &s2) {
    for (int i = 0; s1.m_data[i] != '\0' && s2.m_data[i] != '\0'; i++ ) {
    if (s1.m_data[i] == s2.m_data[i]) {
    continue;
    } else {
    return false;
    }
    }
    }

    // 重载输出运算符
    ostream &operator<<(ostream &os, String &s) {
    os << s.m_data;
    return os;
    }

    // 重载输入运算符
    istream &operator>>(istream &is, String &s) {
    is >> s.m_data;
    return is;
    }

    剖析
    能够准确无误地编写出String类的构造函数、拷贝构造函数、赋值函数和析构函数的面试者至少已经具备了C++基本功的60%以上!
    在这个类中包括了指针类成员变量m_data,当类中包括指针类成员变量时,一定要重载其拷贝构造函数、赋值函数和析构函数,这既是对C++程序员的基本要求,也是《Effective C++》中特别强调的条款。
    仔细学习这个类,特别注意加注释的得分点和加分点的意义,这样就具备了60%以上的C++基本功!

Docker使用

docker常用的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
docker images //1.查看本地docker images
docker search key //2.搜索带key的images

docker run images bash //3.创建并启动一个容器
docker run -it images bash //3.创建并启动一个容器 退出容器后会停止容器
docker run -dit image bash //3.穿件一个后台运行的容器

docker start 容器id //启动一个已经存在的容器

docker ps -a //4.查询本地已经存在的容器 如果没运行可以使用 docker start 容器id 来启动容器
docker ps -s //5.查询本地已经运行的容器 //有一个字段是NAME 代表容器的名字

docker exec -it NAME bash //6.操作一个已经启动的名为NAME的容器

docker commit -a "user name" -m "info" 已经存在的容器的id/name 新镜像的名字 //提交到镜像 会更新镜像内容

注:启动的容器不管是运行中的还是停止的只要是容器存在,那么操作过的容器内容就会一直存在这个容器中.直到将这个容器销毁数据将不存在.即:只要容器不删除 操作的数据就一直在

8.删除一个docker image
$docker rmi image_name
$docker rmi image_id
使用image 名字 或者 image id来指定要删除的镜像,当有相同的镜像的时候只能使用指定名字来删除

9.删除一个docker 容器
$docker rm container_name
$docker rm container_id
使用container 名字或这ID来删除一个容器
注意,容器在删除的时候必须已经停止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#杀死所有正在运行的容器
docker kill $(docker ps -a -q)

#删除所有已经停止的容器
docker rm $(docker ps -a -q)

#删除所有未打 dangling 标签的镜像
docker rmi $(docker images -q -f dangling=true)

#删除所有镜像
docker rmi $(docker images -q)

#强制删除镜像名称中包含“doss-api”的镜像
docker rmi --force $(docker images | grep doss-api | awk '{print $3}')

#删除所有未使用数据
docker system prune

#只删除未使用的volumes
docker volume prune
还有一个是 docker 数据持久化

docker run -v hostpath:/home/user/web xximg

docker容器创建后 只要不使用docker rm name/id 删除容器 对启动的容器操作的内容一直存在

docker push image 到私有仓库

1
2
3
4
5
6
# 将本地镜像打tag 准备push到私有仓库
# docker tag image:tag 私有仓库地址/image:tag
docker tag hello-world:0.1 127.0.0.1:5000/nginx:latest
# push 本地镜像到私有仓库
# docker push "docker tag 打好的镜像
docker push 127.0.0.1:5000/nginx:latest

docker 使用dockerfile创建镜像

1
2
3
4
5
#使用dockerfile创建镜像
# -t tag //生成的 image:tag
docker build -t server:v0.1 .

docker build -t server:v0.1 -f Dockerfile.txt . //使用Dockerfile.txt生成镜像

docker 安装以及使用

docker 更改root dir

ubuntu:docker-19.x:/etc/default下添加:
DOCKER_OPTS=–graph=”/vdb1/docker”
然后重启docker

非root用户使用docker

1.docker在安装的时候会创建一个docker组,只需要将非root用户添加到docker组后就可以操作docker,具体操作如下:
linux将用户添加组别操作:
a1. usermod -a -G docker jay //将jay添加到docker组别
a2. gpassword -M jay docker //将jay添加到docker组别
b. nergrp docker //刷新组别

docker 映射主机端口到容器内端口