程序员的资源宝库

网站首页 > gitee 正文

工作总结 工作总结个人

sanyeah 2024-03-29 17:24:28 gitee 8 ℃ 0 评论

一、本地相关环境配置

1、vscode配置

  (1)安装和相关插件安装

    (2)安装gcc编译器(MinGW)

  (3)vscode和gcc的关联?

  (4)在vscode中安装相关插件

  (5)创建相关文件并编译运行

  (6)调试

二、modern-cpp(C++11、13、14、17)

1、编译设置(修改task.json文件中编译器的设置,以支持编译C++代码)

2、元组(tuple)

  2.1 元组的基本概念和使用方法

  2.2 使用tuple_index(tu, i);的方法获取元组tu中的第i个元素,该函数时是要自己写的

    2.2.1 模板参数固定类型,类似template<int n, typename T0, typename... Ts>---n即为非模板参数

    2.2.2 std::in_place_index

    2.2.3 std::variant

              2.3.4std::visit

3、变长参数函数

  2.3.1 sizeof...()计算变长参数的个数

  2.3.2 递归法展开变长参数

  2.3.3 变参模板展开--C++17支持

4、引用相关

  2.4.1 左值引用、右值引用、常左值引用(可以引用右值)的概念

  2.4.2 万能引用

  2.4.3 移动语义

    2.4.3.1 引入

    2.4.3.2 移动语义介绍

    2.4.3.3 std::move()与移动语义

  2.4.4 完美转发

    2.4.4.1 引用折叠

    2.4.4.2 引入的一个小例子

    2.4.4.3 完美转发例子

5、指针指针 shared_ptr应用举例

6、unique_ptr使用举例

7、lambda表达式

n、关键字

  n1、constexpr

  n2、if constexpr

  n3、reinterpret_cast和const_cast一起使用的一个例子

  n4、关于结构体的一个使用技巧

三、《C++ templates》第二版

  3.1 第一章函数模板

    3.1.1 函数模板定义和模板类型推断

    3.1.2 当函数模板有多个调用参数时,返回类型如何确定?

    3.1.3 返回类型判断之尾返回类型--C++11支持

    3.1.4 普通函数和模板函数构成重载

四、CMake相关

  1、CMake的一个小例子

  2、以前写的Makefile

  3、cmake函数和函数参数解析方法cmake_parse_argumets

五、python脚本

  1、输入参数模块argparse的用法及相关参数介绍

六、读写json文件

       1、nlohmann::json读json文件方法 

       2、nlohmann::json写json

       3、C++提供的读写jsoncpp文件方法

七、乱七八糟的知识点

  1、宏定义实现的日志打印函数

  2、gcc编译选项之 --whole-archive     --no-whole-archive

  3、申请1MB内存方法

  4、cpp封装对外接口的一个例子

  5、日志打印系统-类似glog

  6、派生类中没实现某一函数,但在基类中实现,所以使用的是基类中的方法

  7、静态类方法和静态变量

  8、结构体检验大小

  9、允许将非const实参赋值给const形参

一、本地相关环境配置

1、vscode配置  

(1) 安装vscode编辑器

https://code.visualstudio.com/

下载速度可能很慢,解决方法如下:

在浏览器或者下载软件中就可以看到这么一个下载地址了,将其复制下来(如下图箭头所指)。

然后将红框内的部分更换为如下内容:

vscode.cdn.azure.cn 

下载速度就会很快了

(2)安装gcc编译器

编译工具我们选用gcc(全称GNU Compiler Collection 意思是GNU编译器套件),不过不是原版的gcc,而是它在Windows下的特制版MinGW(全称Minimalist GNU on Windows)。它实际上是将GCC 移植到了 Windows 平台下,并且包含了 Win32API ,因此可以将源代码编译为可在 Windows 中运行的可执行程序。而且还可以使用一些 Windows 不具备的,Linux平台下的开发工具。MinGW又分为MinGW-w64 与 MinGW ,区别在于 MinGW 只能编译生成32位可执行程序,而 MinGW-w64 则可以编译生成 64位 或 32位 可执行程序。MinGW 现已被 MinGW-w64 所取代,且 MinGW 也已停止了更新。

下载地址:

https://sourceforge.net/projects/mingw-w64/files/

下载下面的这个:

下载下来后是一个压缩文件,将它解压缩(解压缩软件推荐Bandizip)得到mingw64文件夹,然后把它拖动到一个合适的位置(或者直接解压缩到这个位置),地址中不要有中文,推荐C:\Program Files

压缩完后可以去如下路径下查看:

C:\Program Files\mingw64\bin

里面有很多后缀名是.exe 的可执行程序,这些就是开发时所需的工具,如:gcc.exe 是C语言程序的编译器,g++.exe 是C++语言的编译器,gdb.exe 是用来调试程序的 debug 工具。

将该路径添加到用户环境变量(注意:不是系统环境变量),方法如下:

我的电脑右击->属性

 

 

环境变量是 Windows 系统中用来指定运行环境的一些参数,它包含了关于系统及当前登录用户的环境信息字符串。当用户运行某些程序时,系统除了会在当前文件夹中寻找某些文件外,还会到环境参数的默认路径中去查找程序运行时所需要的系统文件。

输入:gcc --version验证是否安装成功,如下则说明安装成功

 (3)vscode和gcc的关联?

现在思考一个问题,我们搭的这套环境中编辑器选的是vscode,但理论上任何能处理文本的编辑器都能用来写代码,比如Windows自带的记事本,你可以在桌面新建一个txt文件,命名为hello,然后用记事本写个helloworld程序进去,再把这个文件后缀改成.c,这就是一个源代码文件了,我们该如何对它进行编译运行呢?答案是通过命令行,我们已经安装了编译器套装并把它添加进了环境变量,现在可以使用gcc命令了,但并不是在cmd中执行命令行,而是在vscode中执行命令行。

一般的,我们写一个hello.c文件,然后在cmd中执行gcc -o hello hello.c生成hello.exe可执行文件,然后再在命令行中输入hello.exe运行程序 。

这样每次都用命令行太麻烦了,我们希望用更快捷的方式执行这一过程,但记事本不是专门给你写代码的,它不能提供这样的配置,但是vscode就不一样了,专门写代码的编辑器当然有专门的方式让你快捷地编译运行。这是通过.vscode文件夹下的json配置文件实现的。

这些json文件怎么写是由vscode开发团队规定的(感兴趣可以去看官方的文档),其中一个是tasks.json,task是任务的意思,我们的编译和运行就是我们想要vscode执行的任务,为此我们要在tasks.json里写两个task:BuildRun(这里为什么不是Compile呢?是因为从源码到可执行的过程中不仅是编译(Compile),还有预编译、链接等过程,用构建(Build)来表述更合适)。除了编译和运行,我们还需要进行调试(Debug),这个就不是通过task来实现的了,而是通过launch.json文件来实现。

 

 

(4)在vscode中安装相关插件

汉化:

 

 C/C++插件,这是对语言的支持插件

后重启vscode

 (5)创建相关文件

在E盘创建一个text文件夹,后用vscode打开,创建.vscode文件夹,并在.vscode文件夹中创建tasks.jsonlaunch.json两个文件

 

 

 如下:

 

 其中两个json文件内容如下:

{
    "version": "2.0.0",
    "tasks": [
        {//这个大括号里是‘构建(build)’任务
            "label": "build", //任务名称,可以更改,不过不建议改
            "type": "shell", //任务类型,process是vsc把预定义变量和转义解析后直接全部传给command;shell相当于先打开shell再输入命令,所以args还会经过shell再解析一遍
            "command": "gcc", //编译命令,这里是gcc,编译c++的话换成g++
            "args": [    //方括号里是传给gcc命令的一系列参数,用于实现一些功能
                "${file}", //指定要编译的是当前文件
                "-o", //指定输出文件的路径和名称
                "${fileDirname}\\bin\\${fileBasenameNoExtension}.exe", //承接上一步的-o,让可执行文件输出到源码文件所在的文件夹下的bin文件夹内,并且让它的名字和源码文件相同
                "-g", //生成和调试有关的信息
                "-Wall", // 开启额外警告
                "-static-libgcc",  // 静态链接libgcc
                "-fexec-charset=GBK", // 生成的程序使用GBK编码,不加这一条会导致Win下输出中文乱码
                "-std=c11", // 语言标准,可根据自己的需要进行修改,写c++要换成c++的语言标准,比如c++11
            ],
            "group": {  //group表示‘组’,我们可以有很多的task,然后把他们放在一个‘组’里
                "kind": "build",//表示这一组任务类型是构建
                "isDefault": true//表示这个任务是当前这组任务中的默认任务
            },
            "presentation": { //执行这个任务时的一些其他设定
                "echo": true,//表示在执行任务时在终端要有输出
                "reveal": "always", //执行任务时是否跳转到终端面板,可以为always,silent,never
                "focus": false, //设为true后可以使执行task时焦点聚集在终端,但对编译来说,设为true没有意义,因为运行的时候才涉及到输入
                "panel": "new" //每次执行这个task时都新建一个终端面板,也可以设置为shared,共用一个面板,不过那样会出现‘任务将被终端重用’的提示,比较烦人
            },
            "problemMatcher": "$gcc" //捕捉编译时编译器在终端里显示的报错信息,将其显示在vscode的‘问题’面板里
        },
        {//这个大括号里是‘运行(run)’任务,一些设置与上面的构建任务性质相同
            "label": "run", 
            "type": "shell", 
            "dependsOn": "build", //任务依赖,因为要运行必须先构建,所以执行这个任务前必须先执行build任务,
            "command": "${fileDirname}\\bin\\${fileBasenameNoExtension}.exe", //执行exe文件,只需要指定这个exe文件在哪里就好
            "group": {
                "kind": "test", //这一组是‘测试’组,将run任务放在test组里方便我们用快捷键执行
                "isDefault": true
            },
            "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": true, //这个就设置为true了,运行任务后将焦点聚集到终端,方便进行输入
                "panel": "new"
            }
        }

    ]
}
launch.json
tasks.json
{
    "version": "0.2.0",
    "configurations": [
        {//这个大括号里是我们的‘调试(Debug)’配置
            "name": "Debug", // 配置名称
            "type": "cppdbg", // 配置类型,cppdbg对应cpptools提供的调试功能;可以认为此处只能是cppdbg
            "request": "launch", // 请求配置类型,可以为launch(启动)或attach(附加)
            "program": "${fileDirname}\\bin\\${fileBasenameNoExtension}.exe", // 将要进行调试的程序的路径
            "args": [], // 程序调试时传递给程序的命令行参数,这里设为空即可
            "stopAtEntry": false, // 设为true时程序将暂停在程序入口处,相当于在main上打断点
            "cwd": "${fileDirname}", // 调试程序时的工作目录,此处为源码文件所在目录
            "environment": [], // 环境变量,这里设为空即可
            "externalConsole": false, // 为true时使用单独的cmd窗口,跳出小黑框;设为false则是用vscode的内置终端,建议用内置终端
            "internalConsoleOptions": "neverOpen", // 如果不设为neverOpen,调试时会跳到“调试控制台”选项卡,新手调试用不到
            "MIMode": "gdb", // 指定连接的调试器,gdb是minGW中的调试程序
            "miDebuggerPath": "C:\\Program Files\\mingw64\\bin\\gdb.exe", // 指定调试器所在路径,如果你的minGW装在别的地方,则要改成你自己的路径,注意间隔是\\
            "preLaunchTask": "build" // 调试开始前执行的任务,我们在调试前要编译构建。与tasks.json的label相对应,名字要一样
    }]
}
launch.json

然后创建如下文件:

编译(构建),用快捷键ctrl+shift+B,你会发现终端面板打开了,显示如下:

注意必须要有bin文件夹,否则编译不通过

 

 运行前,需要设置一个快捷键,设置方法如下:

点击左下角小齿轮->键盘快捷方式->搜索任务->找到运行测试任务,点击左侧加号添加键绑定,这里我们设为Ctrl+Shift+R,

 

 按下Ctrl+Shift+R,貌似会重新编译,然后运行,效果如下:

 

 (6)调试

 

 要将上面program对应的路径修改如下:

 

否则会报错如下:

 

 找不到要调试的可执行文件的名字

如 当前打开的是test文件夹,当前的打开的是main.c,并有test / first / second / main.c

那么此变量代表的是  first / second / main.c

${fileBasename}  当前打开的文件名+后缀名,不包括路径

${fileBasenameNoExtension} 当前打开的文件的文件名,不包括路径和后缀名

${fileDirname} 当前打开的文件所在的绝对路径,不包括文件名

${fileExtname} 当前打开的文件的后缀名

${cwd} the task runner's current working directory on startup

 

 以上,参考博客:https://zhuanlan.zhihu.com/p/147366852

 

二、modern-cpp(C++11、13、14、17)

1、编译设置(修改task.json文件中编译器的设置,以支持编译C++代码)

使用的编译器:

gcc 8.1.0

1、找不到iostream头文件
文件名后缀必须时.cpp,如果是.c文件使用gcc8.1.0会报错找不到iostream头文件
2、cc1plus.exe: warning: command line option '-std=c11' is valid for C/ObjC but not for C++
task.json中要修改编译命令为g++
备注:一般的,gcc是用来编译c代码的,如果要用gcc去编译c++代码,得手动链接上C++的库,如要使用gcc去编译hello.cpp要使用如下命令:
gcc -l stdc++ hello.cpp

但是如果使用g++去编译C++代码就不会出现上面的问题了

使用的task.json如下:

{
    "version": "2.0.0",
    "tasks": [
        {//这个大括号里是‘构建(build)’任务
            "label": "build", //任务名称,可以更改,不过不建议改
            "type": "shell", //任务类型,process是vsc把预定义变量和转义解析后直接全部传给command;shell相当于先打开shell再输入命令,所以args还会经过shell再解析一遍
            "command": "g++", //编译命令,这里是gcc,编译c++的话换成g++
            "args": [    //方括号里是传给gcc命令的一系列参数,用于实现一些功能
                "${file}", //指定要编译的是当前文件
                "-o", //指定输出文件的路径和名称
                "${fileDirname}\\bin\\${fileBasenameNoExtension}.exe", //承接上一步的-o,让可执行文件输出到源码文件所在的文件夹下的bin文件夹内,并且让它的名字和源码文件相同
                "-g", //生成和调试有关的信息
                "-Wall", // 开启额外警告
                "-static-libgcc",  // 静态链接libgcc
                "-fexec-charset=GBK", // 生成的程序使用GBK编码,不加这一条会导致Win下输出中文乱码
                "-std=c++17", // 语言标准,可根据自己的需要进行修改,写c++要换成c++的语言标准,比如c++11
            ],
            "group": {  //group表示‘组’,我们可以有很多的task,然后把他们放在一个‘组’里
                "kind": "build",//表示这一组任务类型是构建
                "isDefault": true//表示这个任务是当前这组任务中的默认任务
            },
            "presentation": { //执行这个任务时的一些其他设定
                "echo": true,//表示在执行任务时在终端要有输出
                "reveal": "always", //执行任务时是否跳转到终端面板,可以为always,silent,never
                "focus": false, //设为true后可以使执行task时焦点聚集在终端,但对编译来说,设为true没有意义,因为运行的时候才涉及到输入
                "panel": "new" //每次执行这个task时都新建一个终端面板,也可以设置为shared,共用一个面板,不过那样会出现‘任务将被终端重用’的提示,比较烦人
            },
            "problemMatcher": "$gcc" //捕捉编译时编译器在终端里显示的报错信息,将其显示在vscode的‘问题’面板里
        },
        {//这个大括号里是‘运行(run)’任务,一些设置与上面的构建任务性质相同
            "label": "run", 
            "type": "shell", 
            "dependsOn": "build", //任务依赖,因为要运行必须先构建,所以执行这个任务前必须先执行build任务,
            "command": "${fileDirname}\\bin\\${fileBasenameNoExtension}.exe", //执行exe文件,只需要指定这个exe文件在哪里就好
            "group": {
                "kind": "test", //这一组是‘测试’组,将run任务放在test组里方便我们用快捷键执行
                "isDefault": true
            },
            "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": true, //这个就设置为true了,运行任务后将焦点聚集到终端,方便进行输入
                "panel": "new"
            }
        }

    ]
}
task.json

其余两个json文件不变

2、元组(tuple)

2.1 元组的基本概念和使用方法

 1 #include<iostream>
 2 #include<tuple>
 3 using namespace std;
 4 
 5 tuple<int,double,string> f()
 6 {
 7     return make_tuple(1, 2.0, "hello");
 8 }
 9 int main()
10 {
11     auto [x,y,z] = f();             
12 
13     cout<< x <<","<< y <<"," <<"z" <<endl;
14 
15     return 0;
16 }
返回值为元组tuple

创建一个远足的方法:

tuple<int,double,string> t = tuple<int,double,string>(1,2.0,"hello");   //创建一个tuple元组t  

可以使用auto去定义元组t

auto t = tuple<int,double,string>(1,2.0,"hello");   //创建一个tuple元组t  

那么如何获取元组t中的值呢?

1、使用std::get<变量类型>(元组变量名)方法

 1 #include<iostream>
 2 #include<tuple>
 3 using namespace std;
 4 
 5 int main()
 6 {
 7     auto t = tuple<int,double,string>(1,2.0,"hello");   //创建一个tuple元组t  
 8 
 9     cout << get<string>(t) << endl;  
10     cout << get<double>(t) << endl;    //get方法在名称空间std中
11     cout << get<int>(t) << endl;  
12 
13     return 0;
14 }
使用tuple创建元组,get方法获取元组中的值

2、使用tie拆包方法

 1 #include<iostream>
 2 #include<tuple>
 3 using namespace std;
 4 
 5 int main()
 6 {
 7     int t1 = 0;
 8     double d1 = 0.0;
 9     string s1 = "";       //必须先定义tie中使用的变量 
10 
11     tie(t1, d1 ,s1) = tuple<int,double,string>(1,2.0,"hello");   //创建一个tuple元组t  
12 
13     cout << t1 << endl;  
14     cout << d1 << endl;    //get方法在名称空间std中
15     cout << s1 << endl;  
16 
17     return 0;
18 }
使用tuple创建元组,tie拆包获取元组中的值

3、使用make_tuple方法创建元组,get<元组内元素索引>或tie拆包方法获取元组中的值

 1 #include<iostream>
 2 #include<tuple>
 3 using namespace std;
 4 
 5 int main()
 6 {
 7     /* 1、使用make_tuple创建元组,tie拆包方法获取元组内的值 */
 8     int t1 = 0;
 9     double d1 = 0.0;
10     string s1 = "";                                //必须先定义tie中使用的变量 
11 
12     tie(t1, d1 ,s1) = make_tuple(1,2.0,"hello");   //使用make_tuple就可以不用写元组内元素的类型
13 
14     cout << t1 << endl;  
15     cout << d1 << endl;    //get方法在名称空间std中
16     cout << s1 << endl;  
17 
18     /* 2、使用tuple创建元组,get方法获取元组内元素的值 */
19     auto t = tuple<string,int,double>("I'm tiple", 111, 1.0);
20     cout << get<string>(t) << endl;        //获取元组t内类型为string元素的值,C++14支持,必须使用tuple创建元组
21     cout << get<int>(t) << endl;           //获取元组t内类型为double元素的值,C++14支持
22     cout << get<double>(t) << endl;         //获取元组t内类型为int元素的值,C++14支持
23 
24     /* 3、使用make_tuple创建元组,get<index>(元组名字)方法获取元组内元素的值 */
25     auto t2 = make_tuple("world", 12, "hello");
26     cout << get<0>(t2) <<"," << get<1>(t2) << "," << get<2>(t2) << endl;   //使用get<index>(元组名字)方法获取元组元素的值
27 
28     return 0;
29 }
View Code

运行结果:

 2.2 使用tuple_index(tu, i);的方法获取元组tu中的第i个元素,该函数时是要自己写的

2.2.1 模板参数固定类型,类似template<int n, typename T0, typename... Ts>

如下函数模板:

1 template<int n = 0, typename T0, typename... Ts>
2 void my_print(T0 value, Ts... args)
3 {
4      //在该函数体内即可使用变量n  
5 }
6 
7 //使用方法如下:
8 my_print("hello", 122,12.2);      //不指定模板参数n的值,此时将n将使用默认值0,此外“hello”传递给调用参数value, 122, 12.2传递给调用参数包args
9 my_print<13>("hello", 122,12.2);  //将13传给模板参数中的n,“hello”传递给调用参数value, 122, 12.2传递给调用参数包args

其中n的类型也可以使用auto,即如下:

1 template<auto n, typename T0, typename... Ts>
2 void my_print(T0 value, Ts... args)
3 {
4      //在该函数体内即可使用变量n  
5 }

此时n没有使用默认值,因为n的类型时不定的(auto)不可以指定默认值。

用例:

 1 #include <iostream>
 2 
 3 template<int n = 0, typename T0, typename... Ts>
 4 void my_print(T0 value, Ts... args)
 5 {
 6     std::cout << n << ",";              //从模板参数中传递而来
 7     std::cout << value << std::endl;
 8     if constexpr (sizeof...(args) > 0)
 9     {
10         my_print(args...);             //由于这里没有指定模板参数n的值,故下一次调用的时候就会使用n的默认值0
11     }
12 }
13 
14 int main()
15 {
16     my_print<1228>("hello","12.2");
17 
18     return 0;
19 }
View Code

执行结果:

1228,hello
0,12.2

 

2.2.2 std::in_place_index

 见下面  传送

 

2.2.3 std::variant
C++17中引入了std::variant,目的是为了弥补union的不足,其中union的不足表现如下:
    (1)不可知道union对象当前持有的值的类型;
    (2)c++11之前,union中数据成员是不允许有构造函数,如string变量不可以称为union的成员;
    (3)不能从union中派生类。
于是在std::variant解决了上述所有问题,即:
    (1)可以使用index成员获取当前值类型对应的下标;
    (2)可以使用任何数据类型作为variant对象的成员;
    (3)可以让variant作为基类,派生出一个类。

使用方法:

 1 #include <iostream>
 2 #include <string>
 3 #include <variant>
 4 
 5 using std::string;
 6 using std::cout;
 7 using std::endl;
 8 using std::variant;
 9 
10 int main()
11 {
12     //创建varialt对象和使用
13     variant<string, double, float,int> var1(3.1f);  //使用float初始化var1
14     cout << var1.index() <<endl;                    //打印float对应的下表2
15 
16     variant<string, double, float,int> var2(3.0);  //使用double初始化var2
17     cout << var2.index() <<endl;                   //打印double对应的下表1
18 
19 
20     return 0;
21 }
std::variant的基本使用方法

目前,一个std::variant对象中可能有多个类型,为了避免出现歧义,使用std::in_palace_index进行标记是要为那个类型指定值

如下:

 1 #include <iostream>
 2 #include <string>
 3 #include <variant>
 4 
 5 using std::string;
 6 using std::cout;
 7 using std::endl;
 8 using std::variant;
 9 using std::in_place_index;
10 
11 int main()
12 {
13     //为了避免出现歧义,使用std::in_palace_index进行标记是要为那个类型指定值
14     variant<string, double, float, int> var{in_place_index<1>, 3.3}; //in_place_index<1>表示要对double进行赋多个值
15     cout << std::get<1>(var) << endl;
16 
17     cout << "hello" << endl;
18 
19 
20     return 0;
21 }
使用std::in_place_index指定初始化哪个类型

获取值的方法

1 variant<string, double, float, int> var{in_place_index<1>, 3.3}; //in_place_index<1>表示要对double进行赋多个值
2 cout << std::get<1>(var) << endl;           //使用下标获取值
3 cout << std::get<double>(var) <<endl;       //使用变量类型获取值

 

2.3.4std::visit

 用来访问对象中的数据,必须明确地为每种可能的类型提供函数调用操作符。然后,使用相应的重载来处理当前的备选项类型。

 1、使用类重载的方式访问

 1 #include <iostream>
 2 #include <string>
 3 #include <variant>
 4 
 5 using std::string;
 6 using std::cout;
 7 using std::endl;
 8 using std::variant;
 9 using std::in_place_index;
10 
11 struct myVisit
12 {
13     void operator()(int & i) const  //对操作符()进行重载,参数为引用表明可以修改传入的值,也可以不使用引用
14     {
15         i *= 2;
16         cout << i << endl;
17     }
18     void operator()(double & d) const // 
19     {
20         d *= 2;
21         cout << d << endl;
22     }
23     void operator()(string & s) const
24     {
25         s = s + s;
26         cout << s << endl;
27     }
28     void operator()(float & f) const    //必须有这个重载,因为variant对象中有float成员,否则会报错!
29     {
30         f *= 2;
31         cout << f << endl;
32     }
33 };
34 
35 int main()
36 {
37     variant<string, double, float, int> var1("hello"), var2(3.14), var3(12);  //3.14默认为double类型
38     std::visit(myVisit(), var1);   //执行myVisit中的void operator()(string & s)
39     std::visit(myVisit(), var2);   //执行myVisit中的void operator()(double & d) 
40     std::visit(myVisit(), var3);   //执行myVisit中的void operator()(int & i) 
41 
42     cout << std::get<int>(var3) << " " << std::get<double>(var2) << " " << std::get<string>(var1) << endl; 
43 
44 
45     return 0;
46 }
std::visit使用类重载的方式访问variant对象中的数据

执行结果如下:

hellohello
6.28
24
24 6.28 hellohello

注意,虽然在variant中并没有使用float成员,但是在myVisit中也必须实现void operator()(float & f) const;函数,否则会报如下错误:

Program Files/mingw64/lib/gcc/x86_64-w64-mingw32/8.1.0/include/c++/bits/invoke.h:89:5: error: no type named 'type' in 'struct std::__invoke_result<myVisit, float&>

2、使用lambda表达式访问

 1 #include <iostream>
 2 #include <string>
 3 #include <variant>
 4 
 5 using std::string;
 6 using std::cout;
 7 using std::endl;
 8 using std::variant;
 9 using std::in_place_index;
10 
11 auto myVisit = [](auto & a)
12 {
13     a = a + a;
14     cout << a << endl;
15 };
16 
17 int main()
18 {
19     variant<string, double, float, int> var1("hello"), var2(3.14), var3(12);  //3.14默认为double类型
20     std::visit(myVisit, var1);   //执行myVisit中的void operator()(string & s)
21     std::visit(myVisit, var2);   //执行myVisit中的void operator()(double & d) 
22     std::visit(myVisit, var3);   //执行myVisit中的void operator()(int & i) 
23 
24     cout << std::get<int>(var3) << " " << std::get<double>(var2) << " " << std::get<string>(var1) << endl; 
25 
26     return 0;
27 }
std::visit使用lambda表达式访问variant对象

运行结果:

hellohello
6.28
24
24 6.28 hellohello

 

3、变长参数函数

2.3.1 sizeof...()计算变长参数的个数

变长参数函数的形式和使用sizeof...()计算变长参数的个数

 1 #include<iostream>
 2 using namespace std;
 3 
 4 //变长参数函数
 5 template<typename... Ts>
 6 void magic(Ts... args)
 7 {
 8     cout << sizeof...(args) << endl;    //使用sizeof...来计算变长参数的个数
 9 }
10 
11 int main()
12 {
13     magic(21);                      //打印1
14     magic(11,22);                   //打印2
15     magic(11,2.0,"hello world");    //打印3
16 
17     return 0;
18 }
View Code

运行结果:

 

 使用的编译命令:

g++ e:\textCPP\src\main.cpp -o e:\textCPP\src\bin\main.exe -g -Wall -static-libgcc -fexec-charset=GBK -std=c++2a

 

2.3.2 递归法展开变长参数

 1 #include<iostream>
 2 using namespace std;
 3 
 4 //普通模板函数
 5 template<typename T0>
 6 void magic(T0 arg)
 7 {
 8     cout << arg << endl;
 9 }
10 
11 //变长参数函数
12 template<typename T0, typename... Ts>
13 void magic(T0 t, Ts... args)
14 {
15     cout << t << ",";
16     magic(args...);               //需要特别注意的时这里要写agrs...只写agrs会报错
17 }                                 //等到args只包含一个元素时,执行普通模板函数
18 
19 int main()
20 {
21     magic(11,2.1,"hello world");    //打印3
22 
23     return 0;
24 }
25 
26 /*
27 第一次运行magic(11,2.1,"hello world");会执行变长参数magic,将11赋值给t,将2.1,"hello world"赋值给args...
28 后打印t
29 后执行magic(2.1,"hello world");,执行变长参数magic,将2.1赋值给t,将"hello world"赋值给args...
30 后打印t
31 后执行magic("hello world");此时会执行普通模板函数magic(T0 arg),将"hello world"赋值给arg并打印
32 
33 */
View Code

运行结果

 

 使用的编译命令:

g++ e:\textCPP\src\main.cpp -o e:\textCPP\src\bin\main.exe -g -Wall -static-libgcc -fexec-charset=GBK -std=c++2a

扩展:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 //普通模板函数
 5 template<typename T0>
 6 void magic(T0 arg)
 7 {
 8     cout << arg << endl;
 9 }
10 
11 //变长参数函数
12 template<typename T0, typename... Ts>
13 void magic(T0 t, Ts... args)
14 {
15     cout << t << ",";
16     magic(args...);               //需要特别注意的时这里要写agrs...只写agrs会报错
17 }                                 //等到args只包含一个元素时,执行普通模板函数
18 
19 int main()
20 {
21     magic(22);                      //直接执行普通模板函数magic(T0 arg)
22     magic(11,2.1,"hello world");    //打印3
23 
24     return 0;
25 }
View Code

 2.3.3 变参模板展开--C++17支持

上面的递归法展开函数模板的缺点是必须提供一个终止函数,那么在C++17中增加了变参模板展开的支持,于是你可以在一个函数中完成 magic函数编写:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 template<typename T0, typename... Ts>
 5 void magic(T0 t, Ts... args)
 6 {
 7     cout << t << ",";
 8     if constexpr (sizeof...(args) > 0)
 9     {
10         magic(args...);
11     }
12 }
13 
14 int main()
15 {
16     magic(22);                       //直接调用magic(T0 t, Ts... args),将T0为int型、22赋值给t,args内参数为空
17     magic(1.1, 22, "Hello world");   //将T0定义为double、1.1赋值给t;22,"Hello world"赋值给args
18 
19     return 0;
20 }
View Code

需要注意的是:对于变参函数模板,

1 template<typename T0, typename... Ts>
2 void magic(T0 t, Ts... args)               //args可以接收多个参数,也可以接收0个参数,如调用magic(11)的时候,args接收到的参数个数即为0
3 {
4     //...
5 }

 

4、引用相关

2.4.1 左值引用、右值引用、常左值引用(可以引用右值)的概念

下面的代码中有关于左值引用、右值引用、常左值引用的一些概念及用途

 

 1 #include <iostream>
 2 #include <string>
 3 #include <vector>
 4 
 5 using namespace std;
 6 
 7 string func()
 8 {
 9     int value = 10;
10     return value;                            //value是一个将亡值(右值)
11 }
12 
13 int main()
14 {
15     string str1 = "hello";
16 
17     string && rvalue = "newyear";           //rvalue为右值引用,"newyear"是一个纯右值
18     string &  lvalue1 = str1;               //lvalue为左值引用,str1是一个左值
19     const string & lvalue2 = "newtime";     //lvalue2必须为常量左值引用才可以引用右值
20 
21     int && xvalue = func();                 //延长func()函数中value的生命周期
22     cout << xvalue << endl;
23 
24     const int & xvalue2 = func();
25 
26     return 0;
27 
28 }

 

 

2.4.2 万能引用

 概念:如果一个变量或者参数被声明为T&&,其中T是被推导的类型,那这个变量或者参数就是一个万能引用(universal reference)

在实践当中,几乎所有的万能引用都出现在以下场合中:

  • 函数模板的形参中   如:   template<typename T> void func(T && param);  其中param就是一个万能引用
  • auto声明的变量的类型  如 auto && param;         prame也是一个万能引用

判断以下是否是万能引用:

1 template<typename T>
2 void f(std::vector<T> && param);  //param不是万能引用,只是一个右值引用,因为param不具有T&&的形式
3 
4 template<typename T>
5 void f(const T && param);         //param不是万能引用,只是一个右值引用,加了一个const也不是T&&的形式

即使具有T&&的形式,也不一定是万能引用,如下:

1 template<typename T>
2 clsss Vector
3 {
4 public:
5     void push_back(T && x);      //这里的x不是万能引用,理由见该函数的声明
6 };
7 
8 template<typename T>             //声明
9 Vector<T>::push_back(T && x);  

push_back不能离开std::vector<T>这个类而独立存在。但如果我们有了一个叫做std::vector<T>的类,我们就已经知道了T是什么东西,那就没必要推导T。

 万能引用的使用

#include <iostream>

template<typename T>
void func(T && t)
{
    //...
}

int main()
{
    int a = 1;

    func(10);    //此时func()中的万能引用t被一个右值初始化,于是t就变成了一个右值引用,t的类型时int &&
    func(a);     //此时func()中的万能引用t被一个左值初始化,于是t就变成了一个左值引用,t的类型时int &

    return 0;
}

2.4.3 移动语义

2.4.3.1 引入

如下正常的只包含构造函数、拷贝构造函数、析构函数的一个例子:

 1 #include <iostream>
 2 using std::endl;
 3 using std::cout;
 4 
 5 class T
 6 {
 7 public:
 8     T(int sz) : size(sz > 0 ? sz:1)
 9     {
10         //使用[]为pc申请一个数组大小的内存. pc = new int(sz); 是申请一个int大小的内存,并将值初始化为sz
11         pc = new int[sz];
12         cout << "T's Construct is called pc:" << pc << endl;
13     }
14 
15     T(const T & t)                   //拷贝构造函数(使用深拷贝,不需要delete [] t.pc)
16     {
17         //释放pc原指向的内存空间---windows上会有报错
18         // if(this->pc != nullptr)
19         // {
20         //     delete [] pc;
21         //     pc = nullptr;
22         // }
23 
24         size = t.size;
25         pc = new int[t.size];
26 
27         for(int index = 0; index < t.size; index++)
28         {
29             pc[index] = t.pc[index];
30         }
31         cout << "T's Copy Construct is called pc:" << pc << endl;
32     }
33 
34     ~T()
35     {
36         if(pc != nullptr)
37         {
38             // 注意:必须要判断pc是否为空,因为可能会有匿名对象和临时对象的pc指向是同一块地址,不判断pc可能会导致重复delete
39             delete [] pc;
40             pc = nullptr;
41         }
42         cout << "T's Deconstruct is called pc:" << pc << endl;
43     }
44 
45 private:
46     //pc作为一个数组使用,size是pc的大小
47     int size;
48     int* pc;
49 };
50 
51 T ReturnValue()
52 {
53     return T(5);
54 }
55 
56 
57 int main()
58 {
59     T t = ReturnValue();
60 
61     return 0;
62 }
View Code

执行结果如下:

T's Construct is called pc:0x3d1700          #在ReturnValue()函数中的T(5)生成匿名对象,该匿名对象是一个右值
T's Copy Construct is called pc:0x3d1720     #在ReturnValue()函数中匿名对象执行拷贝构造函数,做深拷贝,生成临时对象
T's Deconstruct is called pc:0               #匿名对象析构
T's Copy Construct is called pc:0x3d1700     #在main()函数中,临时对执行拷贝构造函数,做深拷贝,将临时对象拷贝到变量t
T's Deconstruct is called pc:0               #临时对象析构
T's Deconstruct is called pc:0               #t对象析构

如果将main()函数中的:

1 T t = ReturnValue();

替换为:

1 T && t = ReturnValue();

则临时对象拷贝到t对象的时候,不会执行拷贝构造函数,因为此时t就是临时对象(临时对象是t对象的右值引用),此时会少执行一次拷贝构造和析构

执行结果如下:

1 T's Construct is called pc:0x391700        #在ReturnValue()函数中的T(5)生成匿名对象,该匿名对象是一个右值           
2 T's Copy Construct is called pc:0x391720   #在ReturnValue()函数中匿名对象执行拷贝构造函数,做深拷贝,生成临时对象
3 T's Deconstruct is called pc:0             #匿名对象析构
4 T's Deconstruct is called pc:0             #t对象即临时对象析构

但即使这样,拷贝构造函数执行深拷贝也会很影响性能,且t.pc是否可以使用匿名对象.pc指向的内存呢?答案是可以的,方法是使用移动构造函数,如下介绍。

 

2.4.3.2 移动语义介绍

 移动构造函形参为右值引用,一般的实参为右值,其定义如下:

 1 T(T && rt)    //移动构造函数(不涉及深拷贝,只设计指针的传递)
 2 {
 3     size = rt.size;
 4     pc = rt.pc;
 5 
 6     //下面这一句非常重要,因为匿名对象.pc和临时对象.pc指向的是同一块内存,当匿名变量析构时,匿名对象.pc=nullptr
 7     //但是临时对象.pc还是原来的值,虽然此时临时对象.pc指向的内存delete了,但是临时对象.pc不是nullptr,所以临时对象
 8     //析构时,会再次delete 临时对象.pc,造成重复delete
 9     rt.pc = nullptr;
10 }

用法如下:

 1 #include <iostream>
 2 using std::endl;
 3 using std::cout;
 4 
 5 class T
 6 {
 7 public:
 8     T(int sz) : size(sz > 0 ? sz:1)
 9     {
10         //使用[]为pc申请一个数组大小的内存. pc = new int(sz); 是申请一个int大小的内存,并将值初始化为sz
11         pc = new int[sz];
12         cout << "T's Construct is called pc:" << pc << endl;
13     }
14 
15     T(const T & t)                   //拷贝构造函数(使用深拷贝,不需要delete [] t.pc)
16     {
17         //释放pc原指向的内存空间---windows上会有报错
18         // if(this->pc != nullptr)
19         // {
20         //     delete [] pc;
21         //     pc = nullptr;
22         // }
23 
24         size = t.size;
25         pc = new int[t.size];
26 
27         for(int index = 0; index < t.size; index++)
28         {
29             pc[index] = t.pc[index];
30         }
31         cout << "T's Copy Construct is called pc:" << pc << endl;
32     }
33 
34     T(T && rt)    //移动构造函数(不涉及深拷贝,只设计指针的传递)
35     {
36         size = rt.size;
37         pc = rt.pc;
38 
39         //下面这一句非常重要,因为匿名对象.pc和临时对象.pc指向的是同一块内存,当匿名变量析构时,匿名对象.pc=nullptr
40         //但是临时对象.pc还是原来的值,虽然此时临时对象.pc指向的内存delete了,但是临时对象.pc不是nullptr,所以临时对象
41         //析构时,会再次delete 临时对象.pc,造成重复delete
42         rt.pc = nullptr;
43         cout << "Move Construct is called pc:" << pc << endl;
44     }
45 
46     ~T()
47     {
48         if(pc != nullptr)
49         {
50             // 注意:必须要判断pc是否为空,因为可能会有匿名对象和临时对象的pc指向是同一块地址,不判断pc可能会导致重复delete
51             delete [] pc;
52             pc = nullptr;
53         }
54         cout << "T's Deconstruct is called pc:" << pc << endl;
55     }
56 
57 private:
58     //pc作为一个数组使用,size是pc的大小
59     int size;
60     int* pc;
61 };
62 
63 T ReturnValue()
64 {
65     return T(5);
66 }
67 
68 
69 int main()
70 {
71     T t = ReturnValue();
72 
73     return 0;
74 }
View Code

执行结果:

1 T's Construct is called pc:0x5f1700    #在ReturnValue()函数中的T(5)生成匿名对象,该匿名对象是一个右值 
2 Move Construct is called pc:0x5f1700   #在ReturnValue()函数中匿名对象执行移动构造函数,做浅拷贝,生成临时对象
3 T's Deconstruct is called pc:0         #匿名对象析构
4 Move Construct is called pc:0x5f1700   #在main()函数中,临时对执行拷贝移动函数,做浅拷贝,将临时对象拷贝到变量t
5 T's Deconstruct is called pc:0         #临时对象析构
6 T's Deconstruct is called pc:0         #t变量析构

在main()中替换为 

1 T && t = ReturnValue();

执行结果如下:

1 T's Construct is called pc:0x391700   #在ReturnValue()函数中的T(5)生成匿名对象,该匿名对象是一个右值 
2 Move Construct is called pc:0x391700  #在ReturnValue()函数中匿名对象执行移动构造函数,做浅拷贝,生成临时对象
3 T's Deconstruct is called pc:0        #匿名对象析构  
4 T's Deconstruct is called pc:0        #t对象即临时对象析构

总结:

(1) 如上所示,无论在main()中是否使用右值引用来接收ReturnValue()返回值,在程序执行期间生成的匿名对象.pc、临时对象.pc 和 t.pc指向的内存都是同一块,从而减少了内存申请和释放次数,提高了性能。

(2) 当产生临时对象(右值)时,如果类内有移动构造函数,需要进行赋值操作时,会执行移动构造函数,如果没有,退而求其次,使用拷贝构造函数;使用移动构造函数而不是拷贝构造函数的目的在于减少因临时对象、匿名对象的构造和析构而产生的性能浪费。

 

2.4.3.3 std::move()与移动语义

 std::move()的作用是将一个左值转化为右值,当与移动语义一起使用时,要注意如下错误:

 1 #include <iostream>
 2 using std::endl;
 3 using std::cout;
 4 
 5 class T
 6 {
 7 public:
 8     T(int sz) : size(sz > 0 ? sz:1)
 9     {
10         pc = new int[sz];
11     }
12 
13     T(T && rt)    //移动构造函数(不涉及深拷贝,只设计指针的传递)
14     {
15         size = rt.size;
16         pc = rt.pc;
17         rt.pc = nullptr;
18     }
19 
20     ~T()
21     {
22         if(pc != nullptr)
23         {
24             delete [] pc;
25             pc = nullptr;
26         }
27     }
28 
29 private:
30     //pc作为一个数组使用,size是pc的大小
31     int size;
32     int* pc;
33 };
34 
35 
36 int main()
37 {
38     T t(5);
39     T t_1(std::move(t));  
40 
41     cout << *(t_1.pc) << endl;
42 
43     return 0;
44 }
View Code
1 int main()
2 {
3     T t(5);
4     T t_1(std::move(t));   //是为了执行移动构造函数,而不是执行拷贝构造函数,故加上了std::move(t),将左值t变成右值
5 
6     cout << *(t_1.pc) << endl; //此时是错误的,因为在移动构造函数里面已经把t_1.pc设置为nullptr了
7 
8     return 0;
9 }

正确使用std::move的例子如下:

 1 #include <iostream>
 2 using std::endl;
 3 using std::cout;
 4 
 5 class T
 6 {
 7 public:
 8     T(int sz) : size(sz > 0 ? sz:1)
 9     {
10         pc = new int[sz];
11     }
12 
13     T(T && rt)    //移动构造函数(不涉及深拷贝,只设计指针的传递)
14     {
15         size = rt.size;
16         pc = rt.pc;
17         rt.pc = nullptr;
18     }
19 
20     ~T()
21     {
22         if(pc != nullptr)
23         {
24             delete [] pc;
25             pc = nullptr;
26         }
27     }
28 
29 private:
30     //pc作为一个数组使用,size是pc的大小
31     int size;
32     int* pc;
33 };
34 
35 class MoveAble
36 {
37 public:
38     MoveAble() : pint(new int(3)), t(1024)
39     {
40 
41     }
42 
43     MoveAble(MoveAble && mv) : pint(mv.pint), t(std::move(mv.t))  //std::move(mv.t)将mv.t这个左值转成了右值,故会指向T类的移动构造函数
44     {
45         mv.pint = nullptr;
46     }
47 
48 private:
49     int* pint;
50     T t;
51 };
52 
53 MoveAble GetTemp()
54 {
55     return MoveAble();
56 }
57 
58 int main()
59 {
60     MoveAble m(GetTemp());
61 
62     return 0;
63 }
View Code

 总结:

C++11中,实际拷贝/移动构造函数有以下三个版本:
T Object(T &)             不常用
T Object(const T &)    常用,因为该拷贝构造函数的形参是常左值引用,既可以让左值作为实参,也可以让右值作为实参,当没有移动构造函数时,传入一个临时对象会执行该函数
T Object(T &&)
??一般来说,编译器会隐式的生成一个移动构造函数,不过如果我们自己声明了自定义的拷贝构造函数、拷贝赋值函数、移动赋值函数、析构函数中的一个或多个,那么编译器都不会再生成默认版本。默认版本的移动构造一般也是按位拷贝,这对实现移动语义来说是不够的,通常情况下,如果要实现移动语义,都需要我们自定义移动构造函数。当然,如果类中不包含堆内存,实不实现移动语义都不重要。
??考虑到常量的左值引用是万能的,假设我们传入参数类型为右值,但是又没有实现移动语义会怎么样呢?那么就会进入常量拷贝构造函数,这就确保了即使移动构造不成,还可以拷贝。
参考博客:https://blog.csdn.net/wdl20170204/article/details/111615408

 

2.4.4 完美转发

2.4.4.1 引用折叠

左值引用和右值引用相互叠加时候,叠加后的属性如下表:

1 - 左值-左值 T& &          结果:T&
2 - 左值-右值 T& &&         结果:T&
3 - 右值-左值 T&& &         结果:T&
4 - 右值-右值 T&& &&        结果:T&&

引用折叠的一个例子:

 1 #include <iostream>
 2 
 3 //注这里必须使用万能引用,否则func(a)会找不到定义
 4 template<typename T>
 5 int func(T && t)
 6 {
 7     std::cout << "void func(T && t) is called" << std::endl;
 8     return 0;
 9 }
10 
11 int main()
12 {
13     int a = 3;
14     func(a); //执行func(a),a是一个左值,会被推到为func(int & &&)形式, int & &&会被折叠为int &,故最终形式为func(int &).注:左值会被推到为左值引用
15     func(3); //执行func(3),3是一个右值,会被推到为func(int && &&)形式,int && &&会被折叠为int &&,故最终形式为func(int &&).注:右值会被推到为右值引用
16 
17     return 0;
18 }
19 
20 /*
21 执行结果:
22 void func(T && t) is called
23 void func(T && t) is called
24 */
View Code

 在该例子中func是一个万能引用,因此func(5)和func(b)都可以调用到该万能引用,这是由于发生了引用折叠。

2.4.4.2 引入的一个小例子

引入例子1,该例子中有左值引用、右值引用为形参的函数(注:右值引用的不是万能引用,形参为右值引用的函数不是模板函数),分别使用左值和右值的实参调用对应的函数:

 1 #include <iostream>
 2 
 3 int func(int & t)
 4 {
 5     std::cout << "void func(T & t) is called" << std::endl;
 6     return 0;
 7 }
 8 
 9 int func(int && t)
10 {
11     std::cout << "void func(T && t) is called" << std::endl;
12     return 0;
13 }
14 
15 int main()
16 {
17     int a = 3;
18     func(a);    //func(a)中的a是一个左值,故会调用左值引用func(int & t)
19     func(3);    //func(3)中的3是一个右值,故会调用左值引用func(int && t)
20 
21     return 0;
22 }
23 
24 /*
25 执行结果:
26 void func(T & t) is called
27 void func(T && t) is called
28 */
View Code

执行结果:

void func(T & t) is called
void func(T && t) is called

如果都func()写成模板函数,则会报错,这是因为对于func(a)来说func(int && t)和func(int & t)都是何时的候选调用对象,编译器不知道调用哪个,故报错:而对于func(3)来说,只会选择func(T && t),不会报错。

 1 #include <iostream>
 2 
 3 template<typename T>
 4 int func(T & t)
 5 {
 6     std::cout << "void func(T & t) is called" << std::endl;
 7     return 0;
 8 }
 9 
10 template<typename T>
11 int func(T && t)
12 {
13     std::cout << "void func(T && t) is called" << std::endl;
14     return 0;
15 }
16 
17 int main()
18 {
19     int a = 3;
20     func(a); 
21     func(3);    
22 
23     return 0;
24 }
在linux上会报错,但是在windows上不会报错

引入例子2:加入一个转发模板函数 PerfectForward

 1 #include <iostream>
 2 
 3 int func(int & t)
 4 {
 5     std::cout << "void func(T & t) is called" << std::endl;
 6     return 0;
 7 }
 8 
 9 int func(int && t)
10 {
11     std::cout << "void func(T && t) is called" << std::endl;
12     return 0;
13 }
14 
15 template<typename T>
16 void PerfectForward(T && t)
17 {
18     func(t);
19 }
20 
21 int main()
22 {
23     int a = 3;
24     PerfectForward(a); 
25     PerfectForward(3);    
26 
27     return 0;
28 }
View Code

执行结果:

1 void func(T & t) is called
2 void func(T & t) is called

发现在main()中使用 PerfectForward(a)、PerfectForward(3),最终调用的都是func(int & t),这与在main()中实参为右值时调用浅拷贝函数(func(int && t))、实参为左值时调用深拷贝(func(int & t))的初衷是背离的,这里假设了func(int && t)会实现浅拷贝、func(int & t)会实现深拷贝。于是会造成性能浪费,解决方法是在PerfectForward()函数中使用std::forward()将t转换一下,如下介绍。

这里再介绍一下为啥在PerfectForwad中始终都是调用func(int & t):

1 template<typename T>
2 void PerfectForward(T && t)
3 {
4     func(t);
5     //这里的t虽然是一个右值引用,但是t本身是一个左值,故在该函数中的func(t)始终调用的是func(int & t)
6 }

 

2.4.4.3 完美转发例子

实现完美转发的方法是在PerfectForward中使用std::forward或static_cast将t做一下转换,如下:

1 template<typename T>
2 void PerfectForward(T && t)
3 {
4     func(static_cast<T &&>(t)); //或 func(std::forward<T>(t));
5 }

完整的例子如下:

 1 #include <iostream>
 2 
 3 int func(int & t)
 4 {
 5     std::cout << "void func(T & t) is called" << std::endl;
 6     return 0;
 7 }
 8 
 9 int func(int && t)
10 {
11     std::cout << "void func(T && t) is called" << std::endl;
12     return 0;
13 }
14 
15 template<typename T>
16 void PerfectForward(T && t)
17 {
18     // func(static_cast<T &&>(t)); //
19     func(std::forward<T>(t));
20 }
21 
22 int main()
23 {
24     int a = 3;
25     PerfectForward(a); 
26     PerfectForward(3);    
27 
28     return 0;
29 }
完美转发函数PerfectForward()

执行结果:

1 void func(T & t) is called
2 void func(T && t) is called

这样,计时经过了PerfectForward转发,在main()函数中通过PerfectForward转发,通过PerfectFordward调用func()时候,分别向PerfectForward传入左值、右值,也可以分别调用到func(int & t)、func(int && t)。这样,在实际开发中,func(int && t)中就可以实现浅拷贝,当传入临时对象、匿名对象时候,将节省内存。

5、指针指针 shared_ptr应用举例 

shared_ptr使用demo,先new出来一块堆内存,然后新建一个智能指针,把new出来的这块堆内存地址交给新建的智能指针去管理,具体如下:

 1 #include <iostream>
 2 #include <memory>
 3 
 4 using std::shared_ptr;
 5 using std::cout;
 6 using std::endl;
 7 
 8 struct data_pool_t
 9 {
10  int byte_size;
11  int type;
12  void * ptr;
13 };
14 
15 struct input_t
16 {
17  int byte_size;
18  int type;
19  data_pool_t* data_pool;
20  int num_frame;
21 };
22 
23 int process_data(data_pool_t* pIn)
24 {
25  cout << "none shared_ptr" << endl;
26  
27  return 0;
28 }
29 
30 int process_data(shared_ptr<data_pool_t> pIn)
31 {
32  cout << "shared_ptr.use_count: " << pIn.use_count() << endl;  //shared_ptr.use_count: 2
33  return 0;
34 }
35 
36 int main()
37 {
38  input_t* pInput = new input_t;
39  data_pool_t* pool = new data_pool_t;
40  pInput->data_pool = pool;
41  pInput->byte_size = sizeof(input_t);
42  pInput->type = 1;
43  
44  cout << "address show pInData:" << pInput << ",pool:" << pool << endl;
45  
46  auto release_data = [pInput](data_pool_t* p_pool)   //值捕获上面的pInput,这样就可以当 pool 过期时,不只是可以释放pool的内存,还可以释放pInput的内存
47  {
48   cout << "address show in release_data pInData:" << pInput << ",data_pool in pInput:" << pInput->data_pool << ",p_pool:" << p_pool << endl;
49   cout << "release_data is processed, pInData and data_pool in input are deleted\n";
50   data_pool_t* pool_in_input = pInput->data_pool;
51   delete pool_in_input; pInput->data_pool = nullptr;
52   delete pInput; pInput = nullptr;
53  };
54  
55  // shared_ptr<data_pool_t> data_pool_sharptr = shared_ptr<data_pool_t>(pool,release_data);  //把pool转为智能指针,当data_pool_sharptr过期时,执行删除器release_data
56  shared_ptr<data_pool_t> data_pool_sharptr;
57  data_pool_sharptr.reset(pool, release_data);
58 
59 
60  cout << "shared_ptr.use_count:" << data_pool_sharptr.use_count() << endl;   //pInData是一个shared_ptr类对象,use_count()是该类下的一个成员函数,类对象访问类成员函数式要.而不是->
61  
62  process_data(data_pool_sharptr.get());  //data_sharptr.get()返回一个input_t*,故调用的是process_data(input_t* pInData)
63  
64  process_data(data_pool_sharptr);        //直接使用的是智能指针,故调用的是process_data(shared_ptr<input_t> pInData)
65  
66  
67  return 0;
68 }
shared_ptr应用举例

执行结果:

address show pInData:0x697010,pool:0x697030
shared_ptr.use_count:1
none shared_ptr
shared_ptr.use_count: 2
address show in release_data pInData:0x697010,data_pool in pInput:0x697030,p_pool:0x697030
release_data is processed, pInData and data_pool in input are deleted

在上面的例子中,使用指针指针去接管堆内存时,为该指针指针指定了删除器。

啥时候必须为指针指针指定删除器呢?

当一个指针指针指向一个数组时候,由于删除该智能指针时使用delete删除,而不是使用delete []删除,故此时可以为该智能指针指定一个删除器,在该删除器内使用delete [] 删除智能指针。

 1 #include <iostream>
 2 #include <memory>
 3 
 4 using std::shared_ptr;
 5 using std::cout;
 6 using std::endl;
 7 
 8 
 9 int main()
10 {
11  auto del_func = [](int * p_arr) 
12  {
13   delete [] p_arr;
14  };  //注意:这里有个分号
15  
16  int * arr = new int[10];
17  {
18   //指定了删除器,此时不会有内存泄漏的问题
19   shared_ptr<int> p_arr = shared_ptr<int>(arr, del_func); 
20  }
21  
22  return 0;
23 }
必须为智能指针指定删除器的场景

 6、unique_ptr指针

uniuqe_ptr指针是独占的,不允许unique_ptr指针变量执行拷贝、赋值操作,但是可以作为函数形参返回(如下例子中没有)。举例如下:

 1 #include <iostream>
 2 #include <memory>
 3 
 4 
 5 int main()
 6 {
 7     int* value = new int(5);
 8 
 9     std::cout << value << ", " << *value << std::endl; 
10 
11     {
12         std::unique_ptr<int> pvalue(value); //ok
13 
14         // std::unique_ptr<int> pvalue = value;  // 非智能指针赋值给智能指针 not ok
15 
16         // std::unique_ptr<int> pcopy = pvalue;  // unique_ptr是独占式智能指针, 不可以赋值 not ok
17 
18         std::unique_ptr<int> pvalue_move = std::move(pvalue);  // 转移pvalue的所有权,现在pvalue指向的内存由pvalue_move接管, pvalue为nullptr
19 
20 
21         std::cout << (pvalue.get()) << ", " << *(pvalue.get()) << std::endl;    //出去pvalue的作用域之后, value指向的内存会被delete
22     }
23 
24     std::cout << value << ", " << *value << std::endl; 
25 
26     return 0;
27 }
unique_ptr指针使用举例

 

7、lambda表达式

lambda表达式模板

1 [捕获列表](形参)->返回值类型
2 {
3       //函数体  
4 };

值捕获

在新建lambda表示前捕获变量的值,并拷贝到lambda表达式,在新建的lambda表达式后面如果再修改变量的值,不会影响lambda表达式内的变量的值。

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 int main(int argc, char* argv[])
 6 {
 7     int value = 9;
 8 
 9     auto copy_value = [value](int a)  //值捕获,此时value是一个int值,此时会把9拷贝给捕获列表
10     {
11         return value * a;
12     };
13 
14     value = 100;
15 
16     auto v = copy_value(2);
17 
18     cout << "value: " << value << ",copy_value: " << v << endl; //打印结果:value: 100,copy_value: 18
19 
20     return 0;
21 }
值捕获

例外:如果传入到lambda表达式是一个指针,在lambda表达式后修改指针指向的内存中的数时,此时lambda表达式中的指针访问其指向的内存时,访问到到的是修改后的值,举例如下:

 1 /*
 2 lambda表达式基本语法:
 3 [捕获列表](参数列表) mutable(可选)异常属性 -> 返回类型
 4 {
 5     //函数体
 6 };
 7 */
 8 
 9 #include <iostream>
10 
11 using namespace std;
12 
13 int main(int argc, char* argv[])
14 {
15     int* value = new int;
16     *value = 9;
17 
18     auto copy_value = [value](int a)  //值捕获,将value指针的地址拷贝给值捕获列表 注:C++11不支持a的类型写成auto, C++14才开始支持
19     {
20         return (*value) * a;
21     };
22 
23     *value = 100;
24 
25     auto v = copy_value(2);
26 
27     cout << "value: " << *value << ",copy_value: " << v << endl; //打印结果:value: 100,copy_value: 200
28 
29     delete value;
30 
31     return 0;
32 }
值捕获列表中的参数是一个指针,此时该指针指向内存的值会受到外部变化的影响

以上,虽然是值捕获,但是value是个指针,中间又修改了指针指向的内存存储的值,再次访问这块内存的值,当然是使用修改后的值了

引用捕获

lamnda表达式中的值随着变量的值的变化而变化

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 int main(int argc, char* argv[])
 6 {
 7     int value = 9;
 8 
 9     auto copy_value = [&value](int a)  //引用捕获,此时会捕获列表是value的引用,会随着的value的变化而跟着变化(当value的栈内存不存在了,在lambda表达式中也不会找到value的值了)
10     {
11         return value * a;
12     };
13 
14     value = 100;
15 
16     auto v = copy_value(2);
17 
18     cout << "value: " << value << ",copy_value: " << v << endl; //打印结果:value: 100,copy_value: 200
19 
20     return 0;
21 }
引用捕获

 

 

n、关键字 m2n

n1、constexpr

C++11 提供了 constexpr 让用户显式的声明函数、变量或对象构造函数在编译期会成为常量表达式,constexpr定义的变量对应的值不会改变,并且在编译过程就能得到计算结果的表达式,即constexpr定义的不是一个变量,而是一个(常量)表达式;而const定义的是一个变量,只不过编译器不允许去修改const变量的值。

常量表达式的优点是将计算过程转移到编译时期,那么运行期就不再需要计算了,程序性能也就提升了。

如下使用变量去定一个数是非法的,而使用宏定义去定义一个常量,即合法

1 int len = 10;
2 char arr[len];  //非法
3 
4 #define LEN 10
5 char arr[LEN];  //合法

那么可以使用constexpr使len成为一个常量表达式,进而可以去定义数组,

1 constexpr int len = 10; //此时,此时len是一个常量表达式
2 char arr[len];          //合法

参考博客:地址

n2、if constexpr

C++11 引入了 constexpr 关键字,它将表达式或函数编译为常量结果。一个很自然的想法是,如果我们把这一特性引入到条件判断中去,让代码在编译时就完成分支判断,岂不是能让程序效率更高?C++17 将 constexpr 这个关键字引入到 if 语句中,允许在代码中声明常量表达式的判断条件,考虑下面的代码:
 1 #include<iostream>
 2 using namespace std;
 3 
 4 //普通模板函数
 5 template<typename T0>
 6 auto magic(T0 arg)
 7 {
 8     if constexpr (is_integral<T>::value)  //如果T是int,is_integral<T>::value值为true
 9     {
10         return arg + 1;
11     }
12     else
13     {
14         return arg + 0.01;
15     }
16 }
17 
18 int main()
19 {
20     magic(22);
21     magic(1.1);
22 
23     return 0;
24 }

在编译后,代码将会变成如下形式:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 int magic(int t)
 5 {
 6     return t + 1;
 7 }
 8 
 9 double magic(double t)
10 {
11     return t + 0.01;
12 }
13 
14 int main()
15 {
16     magic(22);
17     magic(1.1);
18 
19     return 0;
20 }

上面代码可以正常运行,不会报错,即 int magic(int t)和 double magic(double t)构成了重载的关系

n3、reinterpret_cast和const_cast一起使用的一个例子

reinterpret_cast介绍

reinterpret_cast用来将一个指针转换成任意类型的指针,如可以将一个int*转换成char*,具体如下:

 1 #include <iostream>
 2 using namespace std;
 3 int main(int argc, char** argv)
 4 {
 5     int num = 0x00636261;//用16进制表示32位int,0x61是字符'a'的ASCII码
 6     int * pnum = &num;
 7     char * pstr = reinterpret_cast<char *>(pnum);
 8     cout<<"pnum指针的值: "<<pnum<<endl;
 9     cout<<"pstr指针的值: "<<static_cast<void *>(pstr)<<endl;//直接输出pstr会输出其指向的字符串,这里的类型转换是为了保证输出pstr的值
10     cout<<"pnum指向的内容: "<<hex<<*pnum<<endl;
11     cout<<"pstr指向的内容: "<<pstr<<endl;
12     return 0;
13 }
将一个int*指针转换成char*

注意:不同类型的指针转换存在巨大的风险,如将上面的int num = 0x00636261 换种写法 int num  = 0x636261将会导致程序崩溃。这是由于转换成char*后,没有换行符0x00了,导致指针会一直往下解析,直到遇到换行符\0,在这个中间可能会非法访问内存,进而导致崩溃。

const _cast介绍

用来将一个const指针转换成非const指针

 https://zhuanlan.zhihu.com/p/372320706

 

n4、关于结构体的一个使用技巧

下面的代码用到的知识点包括:

(1) 创建了一个test_struct_common结构体,里面定义了一个void*数组source_info,可指向任意类型的结构体,指向的结构体只要给struct_type成员赋值即可,获取struct_type的方法可使用宏定义:GET_STRUCT_TYPE,把source_info[i]传入即可;

(2)shared_ptr变量在创建后,其引用计数就是1了,后面只要是由赋值、传参的操作,引用技术都会加1,详细可见read_struct_type()函数。

  1 //关于结构体的一些使用技巧
  2 
  3 #include <iostream>
  4 #include <string.h>  //for memset
  5 #include <memory>    //for shared_ptr
  6 
  7 #define MAX_TEST_STRUCT_NUM 9
  8 
  9 enum test_struct_type
 10 {
 11     TEST_STRUCT_TYPE_DEFAULT = 0,
 12     TEST_STRUCT_TYPE_ONE = 1,
 13     TEST_STRUCT_TYPE_TWO = 2,
 14 };
 15 
 16 
 17 struct test_struct_head
 18 {
 19     int byte_size;     //结构体大小
 20     int struct_type;   //结构体类型
 21 };
 22 
 23 struct test_struct_common
 24 {
 25     int     byte_size;
 26     int     struct_type;
 27     void*   source_info[MAX_TEST_STRUCT_NUM];  // 可根據GET_STRUCT_SIZE()获取类型 类型区分:test_struct_type
 28     int     source_num;                       //source_info的个数
 29 };
 30 
 31 
 32 struct test_struct_one
 33 {
 34     int byte_size;     //结构体大小
 35     int struct_type;   //结构体类型
 36 
 37     int width;
 38     int height;
 39 };
 40 
 41 struct test_struct_two
 42 {
 43     int byte_size;     //结构体大小
 44     int struct_type;   //结构体类型
 45 
 46     int color;
 47     int space;
 48 };
 49 
 50 
 51 #define GET_STRUCT_TYPE(data) (((test_struct_head*)data)->struct_type)
 52 #define GET_STRUCT_SIZE(data) (((test_struct_head*)data)->byte_size)
 53 
 54 int read_struct_type(std::shared_ptr<test_struct_common> common)
 55 {
 56     if(common.get() == nullptr)
 57     {
 58         return -1;
 59     }
 60     //此时common这个结构体变量可以作为参数传给其他函数,在其他函数中可以使用如下方法解析
 61     for(int stuct_index = 0; stuct_index < common->source_num; stuct_index++)
 62     {
 63         if(GET_STRUCT_TYPE(common->source_info[stuct_index]) == TEST_STRUCT_TYPE_TWO)
 64         {
 65             test_struct_two* two_others = (test_struct_two*)common->source_info[stuct_index];
 66             std::cout << "color:" << two_others->color << ",space:" << two_others->space << std::endl;
 67         }
 68         else if(GET_STRUCT_TYPE(common->source_info[stuct_index]) == TEST_STRUCT_TYPE_ONE)
 69         {
 70             test_struct_one* one_others = (test_struct_one*)common->source_info[stuct_index];
 71             std::cout << "height:" << one_others->height << ",width:" << one_others->width << std::endl;
 72         }
 73     }
 74 
 75     std::cout << common.use_count() << std::endl;  // 2
 76 
 77     return 0;
 78 }
 79 
 80 int main()
 81 {
 82 
 83     test_struct_one one;
 84     memset(&one, 0, sizeof(test_struct_one));
 85     one.byte_size   = sizeof(test_struct_one);
 86     one.struct_type = TEST_STRUCT_TYPE_ONE;
 87     one.width       = 1920;
 88     one.height      = 1080;
 89 
 90     test_struct_two two = {0};
 91     two.byte_size = sizeof(test_struct_two);
 92     two.struct_type = TEST_STRUCT_TYPE_TWO;
 93     two.color = 12;
 94     two.space = 12;
 95 
 96 
 97     test_struct_common* pcommon = new test_struct_common;
 98     memset(pcommon,0,sizeof(test_struct_common));
 99 
100     pcommon->byte_size = sizeof(test_struct_common);
101     pcommon->struct_type = 1;  //可不填
102     pcommon->source_info[pcommon->source_num++] = &one;
103     pcommon->source_info[pcommon->source_num++] = &two;
104 
105 
106     std::shared_ptr<test_struct_common> share_common;
107 
108     {
109         share_common.reset(pcommon);
110 
111         read_struct_type(share_common);
112         std::cout << share_common.use_count() << std::endl;  // 1
113     }
114 
115     std::cout << share_common.use_count() << std::endl;  // 1
116 
117     return 0;
118 }
结构体使用技巧

 

 

三、《C++ templates》第二版

3.1 第一章函数模板

3.1.1函数模板定义和模板类型推断

函数模板的定义

 1 #include<iostream>
 2 using namespace std;
 3 
 4 template<typename T>
 5 T max(const T & a, const T & b)
 6 {
 7     return a > b ? a : b;
 8 }
 9 
10 int main()
11 {
12     ::max(11,22);     //合法,T被推断为int;   加上作用域限制符::,即表示使用自己定义的max,否则会使用std::max
13     ::max(1.5, 2.3);  //合法,T被推断为double
14 
15     //::max(11,25.1);   //非法,T要被推断为int还是double,即出现了歧义
16 
17     ::max<double>(11, 25.1);  //合法,此时显式的指出T的类型为double,则int类型的11将会被转换为double类型传给a
18 
19     ::max(static_cast<double>(11), 25.1);  //合法,使用static_cast()函数将int类型的11强制转换为double类型
20 
21     return 0;
22 }
View Code

类型推断

对于如下模板函数伪代码,ParamType不一定就是T,因为ParamType可能是T经过修饰符修改后得到的,如ParamType可能是 const T &。根据ParamType是指针(引用)、一般形式进行讨论

1 template<typename T>
2 void f(ParamType param);
3 f(expr);

1、ParamType是指针(引用)

  • 如果expr的类型是引用,忽略引用部分。
  • 然后将expr的类型同ParamType进行模式匹配来最终决定T。
 1 template<typename T>
 2 void f(T &param);
 3 
 4 int x = 27;             //x为int
 5 const int cx = x;       //cx为const int
 6 const int & rx = x;     //rx为指向const int额引用
 7 
 8 f(x);                  //T被推断为int,param的类型被推断为 int &
 9 f(cx);                 //T被推断为const int,param的类型被推断为const int &
10 f(rx);                 //T被推断为const int(这里的引用会忽略),param的类型被推断为const int &

参考博客:地址

 3.1.2当函数模板有多个调用参数时,返回类型如何确定?

 在此之前,需要明确两个概念:模板参数和调用参数

模板参数,定义在函数模板前面的尖括号里:

template<typename T> // T 是模板参数

调用参数,定义在函数模板名称后面的圆括号里:

T max (T a, T b)   // a 和 b 是调用参数

考虑如下模板函数

1 template<typename T1, typename T2>
2 ?max(T1 a, T2 b)
3 {
4     return a > b ? a : b;
5 }

该模板函数即支持不同实参类型,如下

1 ::max(11, 22.5);   //返回值类型为double
2 ::max(11, 10.5);   //返回类型为int

但是返回值类型是T1还是T2呢?因此可以引入第三个模板参数RT作为返回值类型

1 template<typename T1, typename T2,typename RT>
2 RT max(T1 a, T2 b)
3 {
4     return a > b ? a : b;
5 }
6 
7 //调用需要显式的指出所有模板参数类型
8 ::max<int, double, double>(11, 22.5);  //显式定义T1为int、T2为double、RT为double
9 ::max<int, double, int>(11, 10.5);     //显式定义T1为int、T2为double、RT为int

但是也可以将模板参数RT放在最前,只显式的指出模板参数RT的类型,其他调用参数T1和T2根据传入的参数做类型推断即可,如下:

1 template<typename RT, typename T1, typename T2>
2 RT max(T1 a, T2 b)
3 {
4     return a > b ? a : b;
5 }
6 
7 ::max<double>(11, 22.5);  //RT的类型被显式定义为double,T1和T2根据传入参数做推断
8 ::max<int>(11, 10.5);     //RT的类型被显式定义为int,T1和T2根据传入参数做推断

 3.1.3 返回类型判断之尾返回类型--C++11支持

 使用C++11中支持的尾返回类型来判断返回值的类型

 1 #include<iostream>
 2 using namespace std;
 3 
 4 template<typename T1, typename T2>
 5 auto max(T1 v1, T2 v2)->decltype(v1 > v2 ? v1 : v2)
 6 {
 7     return v1 > v2 ? v1 : v2;
 8 }
 9 
10 int main()
11 {
12     cout << ::max(11,22) << endl;
13     cout << ::max(2.1, 3.2) <<endl;
14     cout << ::max("abc", "bbb") <<endl;
15 
16     return 0;
17 }

此外,在C++14中支持将" ->decltype(v1 > v2 ? v1 : v2) "去掉,即下面也是可以的

 1 #include<iostream>
 2 using namespace std;
 3 
 4 template<typename T1, typename T2>
 5 auto max(T1 v1, T2 v2)
 6 {
 7     return v1 > v2 ? v1 : v2;
 8 }
 9 
10 int main()
11 {
12     cout << ::max(11,22) << endl;
13     cout << ::max(2.1, 3.2) <<endl;
14     cout << ::max("abc", "bbb") <<endl;
15 
16     return 0;
17 }

执行结果:

22
3.2
abc

 3.1.4 普通函数和模板函数构成重载

当 普通函数和模板函数构成重载,且参数类型二者均满足时,普通函数的优先级高于模板函数

 1 #include<iostream>
 2 using namespace std;
 3 
 4 int max(int a, int b)
 5 {
 6     cout <<  "(" << a << "," << b << ")"  << " ,non temlate fun is called\n";
 7     return a > b ? a : b;
 8 }
 9 
10 template<typename T1, typename T2>
11 auto max(T1 v1, T2 v2)                 //使用了C++14中的返回值类型判断方法
12 {
13     cout << "(" << v1 <<"," <<v2 << ")" <<" ,template func is called\n";
14     return v1 > v2 ? v1 : v2;
15 }
16 
17 int main()
18 {
19     ::max(11,22);                     //模板函数和非末班函数都符合,但非模板函数优先级大于模板函数,故调用非模板函数
20     ::max(2.1, 3.2);                  //非模板函数参数类型不合适,故调用的是模板函数
21     ::max<>(11,22);                   //显式的指定了模板列表(虽然是空的),但会调用模板函数
22     ::max<double, double>(11,22);    //显式的制定了参数类型,且11和22会被转换为double类型
23     ::max(2.0, 3.0);                 //调用模板函数,T会被实例化为double类型
24     ::max('a', 'b');                 //调用模板函数,T会被实例化为char
25     ::max("abc", "bcd");             //调用模板函数,T会被实例化为string
26     cout << ::max('a', 11) <<endl;   //由于模板函数不允许,由执行结果可见,调用的是模板函数,将T实例化为了char
27     
28 
29     return 0;
30 }

执行结果:

(11,22) ,non temlate fun is called
(2.1,3.2) ,template func is called
(11,22) ,template func is called
(11,22) ,template func is called
(2,3) ,template func is called
(a,b) ,template func is called
(abc,bcd) ,template func is called
(a,11) ,template func is called
97

 

四、CMake相关

1、CMake的一个小例子

 例子的文件层级:

CMakeDemo
|--bin             #存放可执行文件
|--build
|--|--build.sh     #在该路径下执行脚本 ./build.sh  如果使用sh build.sh的话,如果没有sh脚本解析器就会报错
|---src
|--|---main.c      #用于生成可执行文件 
|--|---add.c       #用于生成linfunc.so
|--|---sub.c       #用于生成libfunc.so
|--include
|--|---func.h
|--lib               #存放生成的libfunc.so
|--CMakeLists.txt

CMakeLists.txt

#CMake最低版本要求
cmake_minimum_required(VERSION 2.8)

#指定项目名称
project(test3)

#定义变量 ROOT_DIR = CMakeDemo   其中PROJECT_BINARY_DIR=CMakeDemo/build
set(ROOT_DIR ${PROJECT_BINARY_DIR}/../)

#设置编译器路径
set(CMAKE_C_FLAGS "usr/bin/cc")

#传入参数打印
message("PLAT:" ${PLAT})
message("BUILD_TYPE:" ${BUILD_TYPE})

#判断是否需要编译debug库(是否需要添加-g编译选项)
if(${BUILD_TYPE} STREQUAL "Debug")
    #添加编译选项
    set(CMAKE_C_FLAGS "-Wall -Wextra -Werror -g" ${CMAKE_C_FLAGS})       #只针对C编译器的编译选项(或只对C代码添加编译选项)
    #set(CMAKE_CXX_FLAGS "-Wall -Wextra -Werror -g" ${CMAKE_CXX_FLAGS}") #只针对C++编译器的编译选项(或只对C++代码添加编译选项)
    #add_compile_options(-Wall -Wextra -Werror -g)                       #编译可执行文件和动态库都添加了-g编译选项
    #add_definitions("-Wall -Wextra -Werror -g")
    message("debug lib and debug exe is builded")
endif(${BUILD_TYPE} STREQUAL "Debug")

#指定编译demo的源文件main.c所在的路径
set(DIR_SRCS_DEMO ${ROOT_DIR}/src/main.c)

#指定main.c需要的头文件路径
include_directories(${ROOT_DIR}/include)

#指定可执行文件保存路径为CMakeDemo/bin
set(EXECUTABLE_OUTPUT_PATH ${ROOT_DIR}/bin)

#生成可执行文件 指定可执行文件名字为demo 生成demo的源文件路劲为${DIR_SRCS_DEMO}
add_executable(demo ${DIR_SRCS_DEMO})


#指定编译libfunc.so需要的源文件
set(DIR_SRCS_LIB ${ROOT_DIR}/src/sub.c
                 ${ROOT_DIR}/src/add.c)

#指定动态库文件libfunc.so保存位置
set(LIBRARY_OUTPUT_PATH ${ROOT_DIR}/lib)

#生成libfunc.so 
add_library(func SHARED ${DIR_SRCS_LIB})

#添加链接 将demo和libfunc.so链接
target_link_libraries(demo func)

message("PROJECT_BINARY_DIR       = ${PROJECT_BINARY_DIR}")         #/home/yiya/CMakeDemo/build
message("PROJECT_SOURCE_DIR       = ${PROJECT_SOURCE_DIR}")         #/home/yiya/CMakeDemo
message("CMAKE_SOURCE_DIR         = ${CMAKE_SOURCE_DIR}")           #/home/yiya/CMakeDemo
message("CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")   #/home/yiya/CMakeDemo
message("CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")   #/home/yiya/CMakeDemo/build
message("demo_BINARY_DIR          = ${demo_BINARY_DIR}")            #无
View Code

其中PROJECT_BINARY_DIR、CMAKE_C_FLAGS、CMAKE_CXX_FLAGS等为CMake系统变量

 build.sh:

rm -rf ./CMakeFiles
rm CMakeCache.txt
rm cmake_install_.cmake

cmake -DPLAT=stm32 -DDEBUG_TYPE=Debug ..
make clean;make
gdb ../bin/demo
build.sh
#include "stdio.h"
#include "func.h"

int main()
{
    int a = 0, b = 0;
    printf("a+b %d\n",add(a,b));
    printf("a-b=%d\n",sub(a,b));

    printf("hello world\n");

    return 0;
}
main.c
#include "func.h"

int add(int a, int b)
{
    return a+b;
}
add.c
#include "func.h"

int sub(int a, int b)
{
    return a-b;
}
sub.c
#ifndef _FUNC_H
#define _FUNC_H

int add(int a, int b);
int sun(int a, int b);

#endif
func.h

 注:需要将 CMakeLists.txt中的 set(CMAKE_C_FLAGS "usr/bin/cc") 注释掉,否则会报如下错误:

[20%] Building C object CMakeFiles/func.dir/src/sub.c.o

cc:  fatal error: no input files

 以上error是在执行玩cmake生成makefiles后,执行make的时候报的错,将设置CMAKE_C_FLAGS去掉就不会报错了,原因正在找。

 3、cmake函数和函数参数解析方法cmake_parse_argumets

一下cmake编译脚本定义了my_parse函数,并在该函数中使用cmake_parse_arguments来解析调用my_parse时传入的参数,具体如下:

 1 #CMake最低版本要求
 2 cmake_minimum_required(VERSION 3.2)
 3 #cmake_minimum_required(VERSION 2.8)
 4 
 5 #指定项目名称
 6 project(test3)
 7 
 8 #定义变量 ROOT_DIR = CMakeDemo   其中PROJECT_BINARY_DIR=CMakeDemo/build
 9 set(ROOT_DIR ${PROJECT_BINARY_DIR}/../)
10 
11 #定义函数
12 function(my_parse)
13 
14     #set(prefix STUDENT)
15     set(options ADULT CHILD)
16     set(oneValueArgs NAME AGE)
17     set(multiValueArgs SCORE FILE)
18     #cmake_parse_arguments(MY_STUDENT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${AGRN} )  #这一行不可以正常解析参数,感觉是字符编码的问题 在win下编码
19     cmake_parse_arguments(MY_STUDENT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )   #这一行是可以正常解析的 在linux下编码
20     #cmake_parse_arguments(MY_STUDENT "ADULT;CHILD" "NAME;AGE" "SCORE" ${AGRN})  #这一行不可以正常解析参数,感觉是字符编码的问题 在win下编码
21     #cmake_parse_arguments(MY_STUDENT "ADULT;CHILD" "NAME;AGE" "SCORE" ${ARGN} )  #这一行是可以正常解析的 在linux下编码
22 
23     #cmake_parse_arguments(MY_STUDENT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
24     message("isadult = ${MY_STUDENT_ADULT}")
25     message("ischild = ${MY_STUDENT_CHILD}")
26     message("name    = ${MY_STUDENT_NAME}")
27     message("age     = ${MY_STUDENT_AGE}")
28     message("score   = ${MY_STUDENT_SCORE}")
29     
30 endfunction()
31 
32 my_parse(CHILD NAME zhang_san AGE 20 SCORE 100 200 300)
33 
34 #my_parse(OPTIONAL TARGETS foo bar DESTINATION bin)
35 
36 message("hhjj123")
37 message(${ROOT_DIR})
38 
39 #指定可执行文件保存路径为CMakeDemo/bin
40 set(EXECUTABLE_OUTPUT_PATH ${ROOT_DIR}/bin)
41 
42 add_executable(DEMO ${ROOT_DIR}/src/main.cpp)
CMakeLists.txt

rm -rf ./CMakeFiles
rm ./CMakeCache.txt
rm ./cmake_install.cmake
rm ./Makefile

cmake ..
make

执行结果:

isadult = FALSE
ischild = TRUE
name = zhang_san
age = 20
score = 100;200;300

https://blog.csdn.net/weixin_39766005/article/details/123378761

 

五、python脚本

1、输入参数模块argparse的用法及相关参数介绍

 argparse:命令行解析的python库,python2.7版本及以上支持。

(1)基本的使用方法

创建fun_test.py文件,内容如下

 1 import argparse
 2 
 3 def main():
 4     parser = argparse.ArgumentParser(description="Demo of argparse")
 5     parser.add_argument('--name', default=' Li ')
 6     parser.add_argument('--year', default='20')
 7     args = parser.parse_args()
 8     print(args)
 9     name = args.name
10     year = args.year
11     print('Hello {}  {}'.format(name,year))
12 
13 if __name__ == '__main__':
14     main()

此时,直接使用--name和--year来接收输入参数,后执行如下命令

python fun_test.py --name ZhangM --year 22

执行结果如下:

Hello Zhang 22

(2) 使用-n --name  和 -y --year

创建fun_test.py文件,内容如下

 1 import argparse
 2 
 3 def main():
 4     parser = argparse.ArgumentParser(description="Demo of argparse") #创建一个argparse对象 输入python fun_test.py -h时,会有description信息
 5     parser.add_argument('-n','--name', default=' Li ') #接收输入命令中-n后面的参数,后使用args.name来获取-n后面的参数
 6     parser.add_argument('-y','--year', default='20')
 7     args = parser.parse_args()                         #解析输入的参数
 8     print(args)
 9     name = args.name                                   #访问输入-n后的的参数
10     year = args.year
11     print('Hello {}  {}'.format(name,year))
12 
13 if __name__ == '__main__':
14     main()

当输入参数即存在'-'和'--'时,不区分‘-’和'--',后执行如下命令

python fun_test.py -n ZhangM --year 22

执行结果如下:

Hello Zhang 22

(3)type参数、choise参数、required参数

 1 import argparse
 2 
 3 def main():
 4     parser = argparse.ArgumentParser(description="Demo of argparse")
 5     parser.add_argument('-n','--name', default=' Li ', type=string, choices=['ZZ','HH'], required=True) #choices参数表示-n后面的参数必须从ZZ和HH选,否则编译会报错.required=True表示必须要有-n这个参数
 6     parser.add_argument('-y','--year', default='20', type=int, choices=[22,24], required=True) #type=int表示year(-y后面的参数)的类型必须是int,否则会报错;
 7     args = parser.parse_args()
 8     print(args)
 9     name = args.name
10     year = args.year
11     print('Hello {}  {}'.format(name,year))
12 
13 if __name__ == '__main__':
14     main()

(3)dest参数

argparse默认的变量名是---后面的字符串,但是你也可以通过dest=xxx来设置参数的变量名,然后在代码中用args.xxx来获取参数的值。

import argparse

def main():
    parser = argparse.ArgumentParser(description="Demo of argparse")
    parser.add_argument('-n', dest=name, default=' Li ')
    parser.add_argument('-y','--year', default='20')
    args = parser.parse_args()
    print(args)
    name = args.name
    year = args.year
    print('Hello {}  {}'.format(name,year))

if __name__ == '__main__':
    main()

 

 

六、nlohmann::json

nlohmann::json是非常好用的一个json开源解析库.nlohmann/json的源码是基于C++11标准写的,整个源码就是一个文件 nlohmann/json.hpp,引用非常方便。

文件下载地址:https://github.com/nlohmann/json,将整个工程下载下来后,需要注意的是要使用json-develop\single_include\nlohmann下的json.hpp

将该头文件包含到自己的工程路径下即可使用。

1、读json文件方法

(1)简单的json文件中字段读取方法

json:

{
    "pi":3.1415,
    "happy":true,
}

方法一

using json = nlohmann::json;

double pi    = j.at("pi");     //01) 使用at()方法直接读json文件中的字段
bool   happy = j.at("happy");  
cout << "pi:" << pi << ",happy:" << happy << endl;

方法二:

using json = nlohmann::json;

double pi    = j["pi";     
bool   happy = j.["happy"];  
cout << "pi:" << pi << ",happy:" << happy << endl;

方法三:

using json = nlohmann::json;

double pi    = j["pi"].get<double>();     
bool   happy = j.["happy"].get<bool>();  
cout << "pi:" << pi << ",happy:" << happy << endl;

(2) 带复合json对象

json格式:

{
    "param":
    {
        "img_info":
        {
            "img_w":1920,
            "img_h":1080
        },
        "img_addr":"./demo/fface.avi"
    }
}

读取方法

1 if(j.at("param").contains("img_info"))                      //01) 判断prame字段下是否有img_info字段
2 {
3       json j_img = j.at("param").at("img_info");
4       int img_w = j_img.at("img_w");                         //02) 使用简介变量j_mg读json文件中的img_w字段
5       int img_h = j.at("param").at("img_info").at("img_h");  //03) 不使用j_img也可
6       string img_addr = j.at("param").at("img_addr");
7       cout << "img_w:" << img_w << ",img_h:" << img_h << ",img_addr:" << img_addr << endl; 
8 }

(3)含有数组的json文件

json文件格式

"rule":
    [
        {
            "type":"rect",
            "rect_11":
            [
                [17,600],
                [870,600]
            ]
        },
        {
            "type":"points",
            "points":
            [
                [17,470],
                [870,470],
                [870,995],
                [17,995]
            ]
        }
    ]

读取方法

 1 json j_rule = j.at("rule");
 2 cout << "rule.size() = " << j_rule.size() << endl;
 3 for(size_t idx = 0; idx < j_rule.size(); idx++)            //06) 使用size()方法获取json文件中rule数组内元素个数
 4 {
 5     if(j_rule[idx].at("type") == "rect")
 6     {
 7         for(size_t idx_rect = 0; idx_rect < j_rule[idx].at("rect_11").size(); idx_rect++)
 8         {
 9             //rect_11[0][0]=17  rect[0][1]=600
10             //rect_11[1][0]=870 rect[1][1]=600
11             cout << j_rule[idx].at("rect_11")[idx_rect][0] << "," << j_rule[idx].at("rect_11")[idx_rect][1] << endl;
12         }
13     }
14 }

以上完整的文件和读取方法

 1 {
 2     "pi":3.1415,
 3     "happy":true,
 4     "param":
 5     {
 6         "img_info":
 7         {
 8             "img_w":1920,
 9             "img_h":1080
10         },
11         "img_addr":"./demo/fface.avi"
12     },
13     "rule":
14     [
15         {
16             "type":"rect",
17             "rect_11":
18             [
19                 [17,600],
20                 [870,600]
21             ]
22         },
23         {
24             "type":"points",
25             "points":
26             [
27                 [17,470],
28                 [870,470],
29                 [870,995],
30                 [17,995]
31             ]
32         }
33     ]
34 }
data.json
 1 /* https://github.com/nlohmann/json */
 2 
 3 #include "json.hpp"
 4 #include <iostream>
 5 #include <fstream>   //for std::ifstream  std::ofstream
 6 
 7 using namespace std;
 8 using json = nlohmann::json;
 9 
10 int read_json()
11 {
12     json j;
13     ifstream json_file("./data.json");
14     json_file >> j;
15 
16     double pi    = j.at("pi");     //01) 使用at()方法直接读json文件中的字段
17     //float pi = j["pi"].get<float>();  //获取值方法二
18     bool  happy = j.at("happy");   //或直接使用 j["happy"]取到json中happy字段的值
19     cout << "pi:" << pi << ",happy:" << happy << endl;
20 
21     if(j.contains("pi"))          //02) 判断json文件中是否包含pi字段
22     {
23         cout << "data.json contains pi" << endl;
24     }
25 
26     if(j.at("param").contains("img_info"))      //03) 由于param字段下又一个json对象,故可以直接再使用 contains方法
27     {
28         json j_img = j.at("param").at("img_info");
29         int img_w = j_img.at("img_w");                        //04) 使用简介变量j_mg读json文件中的img_w字段
30         int img_h = j.at("param").at("img_info").at("img_h");  //05) 不使用j_img也可
31         string img_addr = j.at("param").at("img_addr");
32         cout << "img_w:" << img_w << ",img_h:" << img_h << ",img_addr:" << img_addr << endl; 
33     }
34 
35     // 开始解析rule字段,rule是一个数组rule[0]是第一个元素
36     json j_rule = j.at("rule");
37     cout << "rule.size() = " << j_rule.size() << endl;
38     for(size_t idx = 0; idx < j_rule.size(); idx++)            //06) 使用size()方法获取json文件中rule数组内元素个数
39     {
40         if(j_rule[idx].at("type") == "rect")
41         {
42             for(size_t idx_rect = 0; idx_rect < j_rule[idx].at("rect_11").size(); idx_rect++)
43             {
44                 //rect_11[0][0]=17  rect[0][1]=600
45                 //rect_11[1][0]=870 rect[1][1]=600
46                 cout << j_rule[idx].at("rect_11")[idx_rect][0] << "," << j_rule[idx].at("rect_11")[idx_rect][1] << endl;
47             }
48         }
49     }
50 
51     json_file.close();
52 
53     return 0;
54 }
55 
56 int write_json()
57 {
58     ofstream json_file_out("write.json");
59     json j;
60 
61     j["pi"]    = 3.1415;
62     j["happy"] = true;
63 
64     json_file_out << std::setw(4) << j << endl;  //序列化 std::setw(4)用于设置增加打印空格
65 
66     json_file_out.close();
67 
68     return 0;
69 }
70 
71 int main()
72 {
73     read_json();
74     write_json();
75 
76     return 0;
77 }
main.cpp

(4) 使用get方法读取自定义数据类型:需要重写to_json和frome_json方法

 1 //Animal.h文件
 2 //定义Animal类,其包含两个成员变量kind与height
 3 using nlohmann::json;
 4 class Animal
 5 {
 6     public:
 7         Animal(std::string kind,double height){
 8             this->kind=kind;
 9             this->height=height;
10         }
11     std::string kind;
12     double height;
13 }
14 //定义from_json(const json& j,T& value)函数,用于序列化
15 void from_json(const json& j,Animal& animal)
16 {
17     animal.kind=j["kind"].get<std::string>();
18     animal.height=j["height"].get<double>();
19 }
20 
21 //定义to_json(json& j,const T& value)函数,用于反序列化
22 void to_json(json& j,const Animal& animal)
23 {
24     j["kind"]=animal.kind;
25     j["height"]=animal.height;
26 }
27 
28 //main.cpp文件
29 int main()
30 {
31     Animal animal{"dog",50};
32     nlohmnn::json j=animal;//像basic value一样将自定义对象赋值给json value
33     j["height"]=60;//修改数据
34     Animal animalNew = j.get<Animal>();//像basic value一样通过get函数获取值,将其值直接赋值给自定义对象
35     std::cout<<animal.height;//输出60
36     return 0;
37 }
View Code

 

2、写json

 直接看函数吧

 1 int write_json()
 2 {
 3     ofstream json_file_out("write.json");
 4     json j;
 5 
 6     j["pi"]    = 3.1415;
 7     j["happy"] = true;
 8 
 9     json_file_out << std::setw(4) << j << endl;  //序列化 std::setw(4)用于设置增加打印空格
10 
11     json_file_out.close();
12 
13     return 0;
14 }
写json子函数

 

 

3、C++提供的读写jsoncpp文件方法

JsonCpp 是一个C++库,用来解析和写json文件,在使用时需要将源文件和头文件包含到自己的工程目录下,源文件和头文件下载地址:https://github.com/open-source-parsers/jsoncpp

 参考博客:地址  地址2

 

 

 

七、乱七八糟的知识点

  1、宏定义实现的日志打印函数

如下代码实现了宏定义 LOGE 和 LOGW,通过全局变量g_log_level定义日志打印等级。

详细代码如下:

 1 #include <iostream>
 2 #include <stdio.h>
 3 
 4 #define NONE                 "\e[0m"
 5 #define BLACK                "\e[0;30m"
 6 #define L_BLACK              "\e[1;30m"
 7 #define RED                  "\e[0;31m"    
 8 #define L_RED                "\e[1;31m"    //高亮红色
 9 #define GREEN                "\e[0;32m"
10 #define L_GREEN              "\e[1;32m"
11 #define BROWN                "\e[0;33m"
12 #define YELLOW               "\e[1;33m"
13 #define BLUE                 "\e[0;34m"
14 #define L_BLUE               "\e[1;34m"
15 #define PURPLE               "\e[0;35m"
16 #define L_PURPLE             "\e[1;35m"
17 #define CYAN                 "\e[0;36m"
18 #define L_CYAN               "\e[1;36m"
19 #define GRAY                 "\e[0;37m"
20 #define WHITE                "\e[1;37m"
21 
22 #define BOLD                 "\e[1m"
23 #define UNDERLINE            "\e[4m"
24 #define BLINK                "\e[5m"
25 #define REVERSE              "\e[7m"
26 #define HIDE                 "\e[8m"
27 #define CLEAR                "\e[2J"
28 
29 #define ALG_NAME "log_test"
30 
31 int g_log_level = 0;
32 
33 enum LOGLEVEL
34 {
35     LOG_LEVEL_ERR = 1,
36     LOG_LEVEL_WARN
37 };
38 
39 #define LOGW(fmt,args ...) \
40 do \
41 { \
42     if(g_log_level & LOG_LEVEL_WARN) \
43     { \
44         printf(YELLOW "[%s]WARN[%s|%d]" fmt, ALG_NAME,__func__,__LINE__,##args); \
45         printf(NONE); \
46     } \
47 } \
48 while(0)
49 
50 #define LOGE(fmt,args ...) \
51 do \
52 { \
53     if(g_log_level & LOG_LEVEL_ERR) \
54     { \
55         printf(L_RED "[%s]ERROR (%s|%d): " fmt, ALG_NAME, __func__, __LINE__, args); \
56         printf(NONE); \
57     } \
58 } \
59 while(0)
60 
61 int add(const int & a, const int & b)
62 {
63     LOGW("process is in add(%d,%d)\n",a,b);
64     LOGE("process is in add(%d,%d)\n",a,b);  //fmt = process is in add(%d,%d)  剩余的参数(a和b)给到了args中
65     return a+b;
66 }
67 
68 int main()
69 {
70     g_log_level = 3;
71     int c =  add(1,2);
72 
73     return 0;
74 }
宏定义实现的日志打印函数

注意1:##args和args的区别:

##args支持可变参数为空,而args不支持,如下:
 1 #define LOGE(fmt,args ...) \
 2 do \
 3 { \
 4     if(g_log_level & LOG_LEVEL_ERR) \
 5     { \
 6         printf("[%s]ERROR (%s|%d): " fmt, ALG_NAME, __func__, __LINE__, args); \
 7     } \
 8 } \
 9 while(0)
10 
11 int main()
12 {
13      LOGE("there is no args...")
14      return 0;
15 } 

如上代码中没有使用##agrs,所以在使用LOGE的时候,如果只是一个字符串,args...为空,则会编译报错,解决的方法如下代码:

1 #define LOGE(fmt,args ...) \
2 do \
3 { \
4     if(g_log_level & LOG_LEVEL_ERR) \
5     { \
6         printf("[%s]ERROR (%s|%d): " fmt, ALG_NAME, __func__, __LINE__, ##args); \
7     } \
8 } \
9 while(0)

注意二:以上代码中的颜色#define RED                  "\e[0;31m"    在windows不支持

2、gcc编译选项之 --whole-archive     --no-whole-archive

作用一:默认情况下, 对于未使用到的符号(函数是一种符号), 链接器不会将它们链接进共享库和可执行程序,这个时候, 可以启用链接参数“--whole-archive”来告诉链接器, 将后面库中所有符号都链接进来, 参数“-no-whole-archive”则是重置, 以避免后面库的所有符号被链接进来。

如:g++ -static main.cpp -g -o main --whole-archive ./libfunc1.a -no-whole-archive ./libfunc1.a
此时libfunc1.a中的符号会全部别加载到main, libfunc2.a中只有在main.cpp中用到的符号才会被加载到main中去;

此外, -static参数告诉编译驱动程序,链接器应该构建一个完全链接的可执行目标,它可以加载到存储器并执行,在加载时无需更进一步的链接。
作用二:添加该编译选项之后, 不用关心库和库之间、库和可执行文件之间的链接顺序。

 3、申请1MB内存方法

 1 int malloc_mb(int mb)
 2 {
 3     int nbytes = mb * 0x100000;   //0x100000字节(Byte) = 1048576 Byte = 1024KByte = 1MB
 4 
 5     char *ptr = (char*)malloc(nbytes);  //申请nbytes字节
 6     if(ptr == NULL)
 7     {
 8         cout << "malloc error!" << endl;
 9         return -1;
10     }
11 
12     cout << "malloc:" << mb << "MB\n";
13     return 0;
14 }

 4、cpp封装对外接口的一个例子--nested class

一个private的nested class隐藏了实现细节,适合那些绝对不会暴露在外的部分。比起其它解决方案(namespace detail等) ,封装的更为彻底

 1 #pragma onece
 2 
 3 #include <memory>  //for std::shared_ptr
 4 
 5 /**< Base类的声明可以放在对外头文件中,供外部继承使用,而Base::Impl声明可以在内部头文件中,供内部实际实现,即封装 */
 6 class Base {
 7 private:
 8     class Impl;
 9     std::shared_ptr<Impl> impl;
10 public:
11     Base();
12     virtual ~Base();
13     virtual int initialize();
14     virtual int create();
15 };
16 
17 /**< 由于Impl是在类Base中声明的,故在声明Impl类时,要加上Base:: */
18 class Base::Impl {
19 public:
20     Impl(){}
21     ~Impl(){}
22 
23     int initializeImpl();
24     int createImpl();
25 };
nested_class.h
 1 #include "nested_class.h"
 2 #include <iostream>
 3 
 4 int main(int agrc, char *argv[])
 5 {
 6     Base b;
 7     b.initialize();
 8     b.create();
 9 
10     return 0;
11 }
main.cpp
 1 #include "nested_class.h"
 2 #include <iostream>
 3 
 4 /**< Base类内方法实现 */
 5 Base::Base() : impl(new Impl){}
 6 Base::~Base(){}
 7 
 8 int Base::initialize()
 9 {
10     std::cout << "Base::initializ is called" << std::endl;
11     impl->initializeImpl();
12     return 0;
13 }
14 
15 int Base::create()
16 {
17     std::cout << "Base::create is called" << std::endl;
18     impl->createImpl();
19     return 0;
20 }
21 
22 /**< Base类内声明的Impl类方法实现 */
23 int Base::Impl::createImpl()
24 {
25     std::cout << "Base::Impl::createImpl is called" << std::endl;
26     return 0;
27 }
28 
29 int Base::Impl::initializeImpl()
30 {
31     std::cout << "Base::Impl::initializeImpl is called" << std::endl;
32     return 0;
33 }
nested_class.cpp

g++ main.cpp nested_class.cpp nested_class.h -o test -g -std=c++11

 执行结果:

Base::initializ is called
Base::Impl::initializeImpl is called
Base::create is called
Base::Impl::createImpl is called

 5、日志打印系统-类似glog

 1、宏定义找那个##的使用:

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 
 6 #define _LOGINFO 1
 7 
 8 #define LOG(level) _LOG ## level  //把_LOG和入参level代表的字符串拼接起来,注:如果没_LOGINFO的定义编译会报错
 9 
10 
11 int main(int argc, char* argv[])
12 {
13     std::cout << LOG(INFO) << std::endl;  //打印1
14 
15     return 0;
16 }
main.cpp

2、已经实现了基本打印功能的草稿一:

 1 #include <iostream>
 2 #include <sstream>
 3 
 4 using namespace std;
 5 
 6 
 7 
 8 namespace my_glog
 9 {
10     class MY_LOG
11     {
12     private:
13         std::ostringstream out;
14         int log_level;
15 
16     public:
17         MY_LOG(int v) : log_level(v){ std::cout << "MY_LOG(int v) is called\n"; }
18         
19         template<typename T>
20         MY_LOG& operator<<(const T & a)
21         {
22             std::cout << "operator = is called\n";
23             if(log_level)
24             {
25                 out << a;
26                 std::cout << out.str() << std::endl;
27             }
28             return *this;
29         }
30 
31         MY_LOG(my_glog::MY_LOG&&) { std::cout << "MY_LOG(my_glog::MY_LOG&&) is called\n"; };
32         MY_LOG(my_glog::MY_LOG&) {std::cout << "MY_LOG(my_glog::MY_LOG&) is called\n";};
33     };
34     
35     enum MY_LEVEL
36     {
37         ERROR = 0,
38         WARN,
39         INFO,
40         DEBUG,
41     };
42 
43 }//end my_glog
44 
45 
46 
47 struct glog_info
48 {
49     int level;
50     //待扩展
51 };
52 
53 struct glog_verify
54 {
55     glog_info log_info;
56 
57     glog_verify(){log_info.level = 1;}        //默认值3
58     glog_verify(int v){log_info.level = v;}             
59 
60     int vevify(int value)
61     {
62         std::cout << ((1<<value) & log_info.level) << std::endl;
63         return ((1<<value) & log_info.level);
64     }
65 };
66 
67 static glog_verify g_test;
68 
69 int glog_veiry_other(int level)
70 {
71     return g_test.vevify(level);
72 }
73 
74 
75 // #define _LOGINFO my_glog::MY_LOG(my_glog::INFO)
76 // #define _LOGERROR my_glog::MY_LOG(my_glog::ERROR)
77 // #define _LOGWARN my_glog::MY_LOG(my_glog::WARN)
78 // #define _LOGDEBUG my_glog::MY_LOG(my_glog::DEBUG)
79 
80 
81 #define _LOGINFO   !glog_veiry_other(my_glog::ERROR) ? 0 : my_glog::MY_LOG(my_glog::INFO)
82 
83 #define LOG(level) _LOG ## level  //把_LOG和入参level代表的字符串拼接起来,注:如果没_LOGINFO的定义编译会报错
84 
85 
86 int main(int argc, char* argv[])
87 {
88     LOG(INFO) << "hello";
89 
90     return 0;
91 }
main.cpp

g++ log_glog.cpp -g -o log_glog -std=c++11

执行结果:

1
MY_LOG(int v) is called
operator = is called
hello
MY_LOG(my_glog::MY_LOG&) is called

 3、版本2:修改了代码逻辑,增加了颜色打印

  1 #include <iostream>
  2 #include <sstream>
  3 #include <unordered_map>
  4 
  5 using namespace std;
  6 
  7 static const char green[]       = "\033[0;32;32m";
  8 static const char red[]         = "\033[0;32;31m";
  9 static const char yellow[]      = "\033[1;33m";
 10 static const char cyan[]        = "\033[0;36m";
 11 static const char purple[]      = "\033[0;35m";
 12 static const char blue[]        = "\033[0;32;34m";
 13 static const char no_color[]    = "\033[m";
 14 
 15 enum MY_LEVEL
 16 {
 17     UNKNOW = 0,
 18     ERROR,
 19     WARN,
 20     DEBUG,
 21     INFO,
 22 };
 23 
 24 static std::unordered_map<int, const char*> map_log_color = 
 25 {
 26     {ERROR, red},
 27     {WARN,  yellow},
 28     {DEBUG, purple},
 29     {INFO,  green},
 30 };
 31 
 32 const char* get_log_color(int level)
 33 {
 34     return map_log_color.count(level) ? map_log_color.at(level) : red;
 35 }
 36 
 37 namespace my_glog
 38 {
 39     class MY_LOG
 40     {
 41     private:
 42         std::ostringstream out;
 43         int log_level;
 44 
 45     public:
 46         MY_LOG(int v, const char* module_name, const char* file_name, const char* function_name, const int line) : log_level(v)
 47         {
 48             // 添加一些颜色打印,和模块名字、文件名字、行号打印
 49             out << get_log_color(log_level) << "[" << module_name << "][" << file_name << " " << function_name << ":" << line << "] ";
 50         }
 51         
 52         template<typename T>
 53         MY_LOG& operator<<(const T & a)
 54         {
 55             out << a;
 56             return *this;
 57         }
 58 
 59         MY_LOG(my_glog::MY_LOG&& L) : log_level(L.log_level) {};
 60         MY_LOG(const my_glog::MY_LOG& L) : log_level(L.log_level) {};
 61         ~MY_LOG() {}
 62 
 63         void log_end()
 64         {
 65             out << no_color;  //带颜色打印后恢复无颜色
 66             std::cout << out.str() << std::endl;
 67             std::cout.flush();
 68         }
 69     };
 70 
 71 }//end my_glog
 72 
 73 
 74 /**< 以下可定义在glog_verify.h中,在log_glog.cpp中包含进来 */
 75 struct glog_info
 76 {
 77     int level;
 78     //待扩展
 79 };
 80 
 81 struct glog_verify
 82 {
 83     glog_info log_info;
 84 
 85     glog_verify(){log_info.level = 3;}        //默认值3
 86     glog_verify(int v){log_info.level = v;}             
 87 
 88     glog_info & get_log_info()
 89     {
 90         return log_info;
 91     }
 92 
 93     int set_log_info(int value)
 94     {
 95         log_info.level = value;
 96     }
 97 };
 98 
 99 static glog_verify g_test;
100 
101 //一下是三个对外接口
102 int glog_log_is_on(int level)
103 {
104     glog_info & log = g_test.get_log_info();
105     return (level) & log.level;
106 }
107 
108 bool glog_log_is_off(int level)
109 {
110     return !glog_log_is_on(level);
111 }
112 
113 void glog_log_set_level(int value)
114 {
115     g_test.set_log_info(value);
116     return;
117 }
118 
119 /**< end of glog_verify.h */
120 
121 
122 struct log_write
123 {
124     //这里必须使用引用,否则传进来的实参和形参的地址不一样!!
125     void operator && (my_glog::MY_LOG & L) { L.log_end(); }
126 };
127 
128 #define __MODULENAME__ "mg_glog"
129 #define __FILENAME__ "log_glog.cpp"
130 #define _LOGPERFIX(level) my_glog::MY_LOG(level, __MODULENAME__, __FILENAME__, __FUNCTION__, __LINE__)
131 //这里必须使用(void)0,否则会报错:third operand to the conditional operator is of type ‘void’, but the second operand is neither a throw-expression nor of type ‘void’
132 //my_glog::MY_LOG(my_glog::INFO)返回的MY_LOG对象大概率是作为了log_write类中operator&&方法的实参了
133 #define _LOGINFO   glog_log_is_off(INFO)  ? (void)0 : log_write() && _LOGPERFIX(INFO)
134 #define _LOGERROR  glog_log_is_off(ERROR) ? (void)0 : log_write() && _LOGPERFIX(ERROR)
135 #define _LOGWARN   glog_log_is_off(WARN)  ? (void)0 : log_write() && _LOGPERFIX(WARN)
136 
137 #define LOG(level) _LOG##level  //把_LOG和入参level代表的字符串拼接起来,注:如果没_LOGINFO的定义编译会报错
138 
139 
140 int main(int argc, char* argv[])
141 {
142     glog_log_set_level(15);
143 
144     LOG(INFO) << "INFO" << " you " << 007;
145     LOG(WARN) << "WARN" << " you " << 007;
146     LOG(ERROR) << "ERROR" << " you " << 007;
147 
148     return 0;
149 }
main.cpp

 g++ log_glog.cpp -g -o log_glog -std=c++11

 执行结果:暂无。

6、派生类中没实现某一函数,但在基类中实现,所以使用的是基类中的方法

Normal类中没有实现func2方法,但是基类中方法有func2,所以调用的是基类中的方法func2

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class Base
 6 {
 7 public:
 8     int a_base;
 9     Base(int a):a_base(a){}
10     Base(const Base & b):a_base(b.a_base){}
11     ~Base(){}
12     virtual void func1(){ cout << "Base::func1" << endl; }
13     void func2();
14     virtual void func3();
15 };
16 
17 void Base::func2()
18 {
19     cout << "Base::func2" << endl; 
20     Base::func3();
21 }
22 void Base::func3()
23 {
24     cout << "Base::func3" << endl;
25 }
26 
27 class Normal : public Base
28 {
29 public:
30     int n_base;
31     Normal(int n, int a):Base(a),n_base(n){}
32     Normal(const Normal& n):Base(0),n_base(n.n_base){}
33     ~Normal(){}
34     virtual void func1(){ cout << "Normal::func1" << endl; }
35     virtual void func3();
36 };
37 
38 void Normal::func3()
39 {
40     cout << "Normal::func3" << endl;
41 }
42 
43 
44 int main()
45 {
46     Base* b = new Normal(12,13);
47 
48     b->func1();
49     b->func2();
50     return 0;
51 }
代码实现

执行结果:
Normal::func1 b->func1() 基类指向了派生类对象,而func1是基类和派生类中的虚函数,故调用的是派生类中的func1
Base::func2 func2只在基类中实现,而派生了又共有集成了基类中的所有方法,故这里调用的是基类中的func2
Base::func3 在Base::func2中调用了基类和派生类都实现了的虚函数func3,我们也可以显示的指定使用哪个类中的func3方法

7、静态类方法和静态变量

类中有静态变量,类对象1和类对象2共有该静态变量。

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class Base
 6 {
 7 public:
 8     Base(int p) : point(p){ *total_point+= p; }
 9     ~Base(){}
10     static int get_point();
11     static void init_point();
12 
13 private:
14     static int* total_point;
15     int point;
16 };
17 
18 int* Base::total_point = nullptr;  //静态变量必须在类外声明一下,可以先不申请内存 
19 
20 void Base::init_point()
21 {
22     total_point = new int(0);  //在这里即可调用开源库中封装的函数对静态变量进行初始化
23 }
24 
25 int Base::get_point()
26 {   
27     return *total_point;
28 }
29 
30 int main()
31 {
32     Base::init_point();  //必须在创建Base对象之前调用,因为在Base构造函数中使用了total_point,若此时为申请内存,则段错误
33 
34     Base b1(10),c(20);
35 
36     std::cout << "total point:" << Base::get_point() <<std::endl;  //total point:30
37 
38     return 0;
39 }
code

 

8、结构体检验大小

 

 1 #include <iostream>
 2 
 3 using uchar_8  = unsigned char;
 4 using sint_32  = signed int;
 5 using uint_32  = unsigned int;
 6 
 7 //以下宏定义类似定义一个uchar_8类型的数组unused##label,用于指针8字节对齐
 8 #define MY_STRUCT_ALIGNE8(label) uchar_8 unused##label[8-sizeof(void*)]
 9 //以下宏定义类似定义一个uchar_8类型的数组check##label,如果大小不相等,则数组大小为-1,进而编译出错,用于检测结构体大小是否正确
10 #define CHECK_SIZE(label,size) uchar_8 check##label[sizeof(label)==size ? 0:-1]
11 
12 struct data
13 {
14     sint_32 size;
15     uint_32 type;  //32位,4个字节
16 
17     sint_32* value;
18     MY_STRUCT_ALIGNE8(value);
19 
20     sint_32 len;
21 
22     sint_32 reverse[11];
23 };
24 CHECK_SIZE(data, 64);
25 
26 
27 
28 int main()
29 {
30     std::cout << "sizeof(data):" << sizeof(data) << std::endl;
31     return 0;
32 }
结构体大小检验大小

执行结果:

//32位编译选项-m32
[yiya@centos7 struct_align]$ g++ struct_align.cpp -o struct_align -g -std=c++11 -m32
[yiya@centos7 struct_align]$ ./struct_align
sizeof(data):64
//64位编译选项-m64
[yiya@centos7 struct_align]$ g++ struct_align.cpp -o struct_align -g -std=c++11 -m64
[yiya@centos7 struct_align]$ ./struct_align
sizeof(data):64

9、允许将非const实参赋值给const形参

 

 1 可以将非const变量作为实参,传递给const变量形参
 2 
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 enum CMD_TEST
 8 {
 9     STRUCT_TEST_UNKNOWN = 0,
10     STRUCT_TEST_V1 = 1
11 };
12 
13 
14 struct struct_test_v1
15 {
16     int v1;
17     float v2;
18 };
19 
20 
21 int working(int cmd, const void * param)
22 {
23   if(cmd == STRUCT_TEST_V1)
24   {
25     //先将param从const void*转成const struct_test_v1*,再使用const_cast去掉const属性
26     struct_test_v1* test_v1 = const_cast<struct_test_v1*>(reinterpret_cast<const struct_test_v1*>(param));
27   }
28   //之后即可使用没有const属性的param了
29   
30   return 0;
31 }
32 
33 
34 
35 int main()
36 {
37     struct_test_v1 v1 = {0};
38 
39     working(1, &v1);  //即可以将非const变量传参传递给const变量
40 
41     return 0;
42 }
举例

g++ const_test.cpp -o const_test -g -std=c++11
以上编译没有问题

 

 

 

 

 

 

 

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表