zzm99


  • Home

  • Tags

  • Categories

  • Archives

  • Search

移动网络安全与应用-实验一-SSL实验

Posted on 2019-09-29 | In web2.0 and 移动网络安全技术 |
Words count in article: 0 | Reading time ≈ 1

1
1
1
1

web2.0作业三:动态按钮

Posted on 2019-09-29 | In web2.0 and 移动网络安全技术 |
Words count in article: 806 | Reading time ≈ 4
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
<!DOCTYPE html>
<html>
<head>
<title>环形按钮</title>
<meta charset="utf-8">
<link rel="icon" sizes="196x196" href="assets/images/favicon.png">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="at-plus-container">
<div id="bottom-positioner">
<div id="button">
<div id="info-bar">
<div style="display:block" class="info">
<ul>
<li title="2人参与讨论" class="page user"><img src="assets/images/people.png"><span>14</span></li>
<li title="3条评论" class="page comment"><img src="assets/images/comment.png"><span>20</span></li>
<li title="我有6条评论" class="mine posted"><img src="assets/images/post.png"><span>3</span></li><li title="被赞6次" class="mine like"><img src="assets/images/like.png"><span>4</span></li>
</ul>
</div>
</div>
<div id="control-ring-container">
<ul id="control-ring">
<li title="蒙板" class="mask button"></li>
<li title="历史记录" class="history button"></li>
<li title="消息" class="message button"><span class="unread">2</span></li>
<li title="设置" class="setting button"></li>
<li title="登录/注册" class="sign button"></li>
</ul>
</div>
<div class="apb">
<div class="icon"></div>
</div>
</div>
</div>
</div>
</body>
</html>
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}

#at-plus-container {
width: 200px;
height: 100%;
margin: 0 auto;
position: relative;
}

#info-bar {
position: absolute;
background-color: #7E7E7E;
border-radius: 50%;
height: 26px;
width: 26px;
bottom: 97px;
left: 77px;
opacity: 0;
transition: 0.5s;
}

#button:hover #info-bar {
border-radius: 50%;
background-color: #7E7E7E;
width: 130px;
height: 130px;
bottom: 150px;
left: 25px;
opacity: 1;
}

#button ul{
margin: 0;
padding: 0;
}

#button li{
list-style: none;
text-align: center;
color: white;
margin-top: 5px;
margin-bottom: 5px;
}

#info-bar li.comment {
font-size: 30px;
}

#info-bar li.comment img {
width: 30px;
height: 30px;
padding-right: 2px;
}

#info-bar li span {
margin-left: 8px;
}

#info-bar li img {
vertical-align: middle;
}

#info-bar li.mine {
display: inline-block;
width: 50%;
color: #26E79E;
font-weight: bold;
box-sizing: border-box;
}

#info-bar li.posted {
padding-left: 15px;
}

#info-bar li.like {
padding-right: 5px;
}

#control-ring-container {
position: absolute;
width: 200px;
height: 200px;
border-radius: 50%;
bottom: -10px;
left: 30px;
}

#control-ring-container ul{
margin-left: 45px;
margin-top: 60px;
}


#control-ring li{
display: block;
background: #686868 center no-repeat;
position: absolute;
list-style: none;
width: 30px;
height: 30px;
background-size: 16px 16px;
border-radius: 50%;
transition: 0.5s;
opacity: 0;
}

#control-ring .unread{
display: block;
position: absolute;
width: 18px;
height: 18px;
border-radius: 50%;
background-color: red;
color: #E0D6DC;
margin-left: 18px;
margin-top: -5px;
font-size: 9pt;
}

#button:hover #control-ring li{
opacity: 1;
}

#button:hover #control-ring > li.mask.button{
margin-top: -32px;
margin-left: -55px;
background-image: url('assets/images/nomask.png');
}

#button:hover #control-ring > li.history.button{
margin-top: 5px;
margin-left: -70px;
background-image: url('assets/images/history.png');
}

#button:hover #control-ring > li.message.button{
margin-top: 44px;
margin-left: -55px;
background-image: url('assets/images/message.png');
}

#button:hover #control-ring > li.setting.button{
margin-top: 68px;
margin-left: -15px;
background-image: url('assets/images/setting.png');
}

#button:hover #control-ring > li.sign.button{
margin-top: 60px;
margin-left: 30px;
background-image: url('assets/images/signin.png');
}

#button .apb{
background: url('assets/images/atplus_white.png') center no-repeat;
background-size: 20px 15px;
position: absolute;
width: 26px;
height: 26px;
bottom: 97px;
left: 77px;
border-radius: 50%;
background-color: #989FCF;
transition: 0.5s;
}

#button:hover .apb {
background: url('assets/images/atplus_green.png') center no-repeat;
background-size: 40px 30px;
width: 50px;
height: 50px;
bottom: 85px;
left: 65px;
border-radius: 50%;
background-color: #303F9F;
}

1

1

Weekly Contest 156

Posted on 2019-09-29 | In leetcode , Weekly-Contest |
Words count in article: 1.4k | Reading time ≈ 7

Unique Number of Occurrences

Given an array of integers arr, write a function that returns true if and only if the number of occurrences of each value in the array is unique.

Constraints:

  • 1 <= arr.length <= 1000
  • -1000 <= arr[i] <= 1000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
bool uniqueOccurrences(vector<int>& arr) {
vector<int> countarr(2005, 0);
vector<int> countnum(1000,0);
for(int i=0; i<arr.size(); i++)
{
countarr[arr[i]+1000]++;
}
for(int i=0; i<=2000; i++)
{
if(countarr[i] != 0)
{
if(countnum[countarr[i]] != 0)
return false;
else
countnum[countarr[i]]++;
}
}
return true;

}
};

Get Equal Substrings Within Budget

给你两个长度相同的字符串,s 和 t。

将 s 中的第 i 个字符变到 t 中的第 i 个字符需要 |s[i] - t[i]| 的开销(开销可能为 0),也就是两个字符的 ASCII 码值的差的绝对值。

用于变更字符串的最大预算是 maxCost。在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。

如果你可以将 s 的子字符串转化为它在 t 中对应的子字符串,则返回可以转化的最大长度。

如果 s 中没有子字符串可以转化成 t 中对应的子字符串,则返回 0。

Instead of two string, we can imagine an array of the same size with absolute differences. Then, the problem is to find the longest subarray with the sum not exceeding maxCost.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int equalSubstring(string s, string t, int maxCost) {
int i=0;
int j=0;
while(i < s.size())
{
maxCost -= abs(s[i]-t[i++]);
if(maxCost < 0) maxCost += abs(s[j] - t[j++]);
}
return i-j;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int equalSubstring(string s, string t, int maxCost) {
vector<int> ab(s.size(), 0);
for(int i=0; i<s.size(); i++)
{
ab[i] = abs(s[i]-t[i]);
}

int ii = 0;
int jj = 0;
while(jj < s.size())
{
maxCost -= ab[jj++];
if(maxCost < 0) maxCost += ab[ii++];
}
return jj-ii;
}
};

Remove All Adjacent Duplicates in String II

Given a string s, a k duplicate removal consists of choosing k adjacent and equal letters from s and removing them causing the left and the right side of the deleted substring to concatenate together.

We repeatedly make k duplicate removals on s until we no longer can.

Return the final string after all such duplicate removals have been made.

It is guaranteed that the answer is unique.

1
2
3
4
5
6
Input: s = "deeedbbcccbdaa", k = 3
Output: "aa"
Explanation:
First delete "eee" and "ccc", get "ddbbbdaa"
Then delete "bbb", get "dddaa"
Finally delete "ddd", get "aa"
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
class Solution {
public:
string removeDuplicates(string s, int k) {
if(s.size() == 0)
return "";
vector<char> stack(100005, 0);
int curidx = 0;
for(int i=0; i<s.size(); i++)
{
char tmpch = s[i];
int tmpnum = 1;
for(int j=curidx-1; j>=0; j--)
{
if(stack[j] == tmpch)
tmpnum++;
else
break;
}
if(tmpnum >= k)
{
curidx -= k-1;
}
else
{
stack[curidx] = tmpch;
curidx++;
}
}
string ret = "";
for(int i=0; i<curidx; i++)
{
ret += stack[i];
}
return ret;
}
};

Minimum Moves to Reach Target with Rotations

我们在一个 n*n 的网格上构建了新的迷宫地图,蛇的长度为 2,也就是说它会占去两个单元格。蛇会从左上角((0, 0) 和 (0, 1))开始移动。我们用 0 表示空单元格,用 1 表示障碍物。蛇需要移动到迷宫的右下角((n-1, n-2) 和 (n-1, n-1))。

每次移动,蛇可以这样走:

  • 如果没有障碍,则向右移动一个单元格。并仍然保持身体的水平/竖直状态。
  • 如果没有障碍,则向下移动一个单元格。并仍然保持身体的水平/竖直状态。
  • 如果它处于水平状态并且其下面的两个单元都是空的,就顺时针旋转 90 度。蛇从((r, c)、(r, c+1))移动到 ((r, c)、(r+1, c))。
  • 如果它处于竖直状态并且其右面的两个单元都是空的,就逆时针旋转 90 度。蛇从((r, c)、(r+1, c))移动到((r, c)、(r, c+1))。

返回蛇抵达目的地所需的最少移动次数。

如果无法到达目的地,请返回 -1。

1
2
3
4
5
6
7
8
9
输入:grid = [[0,0,0,0,0,1],
[1,1,0,0,1,0],
[0,0,0,0,1,1],
[0,0,1,0,1,0],
[0,1,1,0,0,0],
[0,1,1,0,0,0]]
输出:11
解释:
一种可能的解决方案是 [右, 右, 顺时针旋转, 右, 下, 下, 下, 下, 逆时针旋转, 右, 下]。
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
class Solution {
public:
bool canRotate(vector<vector<int>>& g, int i, int j) {
return i < g.size() - 1 && j < g[i].size() - 1 && (g[i + 1][j] & 1) == 0 && (g[i][j + 1] & 1) == 0 && (g[i + 1][j + 1] & 1) == 0;
}
bool canGoDown(vector<vector<int>>& g, int i, int j, bool vertical) {
if (vertical) return i < g.size() - 2 && (g[i + 2][j] & 1) == 0;
return i < g.size() - 1 && (g[i + 1][j] & 1) == 0 && (g[i + 1][j + 1] & 1) == 0;
}
bool canGoRight(vector<vector<int>>& g, int i, int j, bool vertical) {
if (!vertical) return j < g[i].size() - 2 && (g[i][j + 2] & 1) == 0;
return j < g[i].size() - 1 && (g[i][j + 1] & 1) == 0 && (g[i + 1][j + 1] & 1) == 0;
}
int minimumMoves(vector<vector<int>>& grid, int steps = 0) {
queue<array<int, 3>> q1, q2;
q1.push({ 0, 0, false }); // not vertical.
while (!q1.empty()) {
while (!q1.empty()) {
auto& a = q1.front();
if (a[0] == grid.size() - 1 && a[1] == grid[a[0]].size() - 2) return steps;
if ((grid[a[0]][a[1]] & (a[2] ? 2 : 4)) == 0) {
grid[a[0]][a[1]] = grid[a[0]][a[1]] | (a[2] ? 2 : 4);
if (canGoDown(grid, a[0], a[1], a[2])) q2.push({ a[0] + 1, a[1], a[2] });
if (canGoRight(grid, a[0], a[1], a[2])) q2.push({ a[0], a[1] + 1, a[2] });
if (canRotate(grid, a[0], a[1])) q2.push({ a[0], a[1], a[2] ? false : true });
}
q1.pop();
}
++steps;
swap(q1, q2);
}
return -1;
}
};

Google Test

Posted on 2019-09-29 | In 初级实训 |
Words count in article: 3k | Reading time ≈ 12

单元测试

针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作

程序模块可以是函数式编程中的函数和过程,也可以是面向对象编程中类的方法。通常每当我们完成了一个模块,我们都会对它进行测试以确保模块功能的完好。比如,后台完成了一个插入数据库的api,那么当我们使用这个api进行数据插入时,我们希望数据能够被正确的插入进数据库,于是我们使用这个api先尝试着插入几条数据,然后再在数据库中查看是否正确插入,这便是单元测试的工作原理。

单元测试一个非常重要的特点是,各个模块是独立的,A模块功能的正确不应该依赖于B模块(因为我们只对A模块进行单元测试)。比如插入数据库的api模块不应该与制造数据的模块同时在同一单元测试中测试,出现依赖关系的时候应该考虑是否应该重新划分模块,将A,B模块集成为一个模块,如将产生数据和插入数据库集成为一个模块(对于这种情况不建议合并),或者通过模拟(mock)来模拟B的功能(在数据库插入的api测试中mock接口,并产生假数据)。

xUnit

xUnit是一系列单元测试框架的集合(x代表语言,如JUnit中J代表Java),xUnit测试框架是面向对象的,并且他们有一套共同的架构

xUnit的架构

  • Test runner
    执行所有测试的可执行程序

  • Test case
    基本类,所有的测试都继承于test case,对于同一个单元可以有多个test case来进行测试,每个测试可以覆盖不同的方面。比如在测试插入数据库api时,一个case对应插入正确数据,另一个case对应插入错误数据等

  • Test fixtures
    可以理解为测试所需环境。在每个测试前为测试而搭建的环境(preconditions)。例如在在测试插入数据库api时,fixture可以为连接数据库

  • Test suites
    共享同一fixture(precondition)的一系列测试,test suit里面的测试顺序不重要(每个测试都是独立的,互不依赖),例如在上述测试中的两个test case就应该放入同一test suit下

  • Test execution
    对于每一个test case,都以以下步骤执行

    • setup() // 搭建测试环境
    • test() // 执行测试
    • teardown() // 恢复测试环境,以免影响到下一个测试
  • Test result formatter
    输出测试结果,可以是plain text也可以是xml或其他格式

  • Assertions
    Assertion(断言)是用来测试函数返回结果是否为期望值的函数或宏,遇到非期望值时,一般会抛出异常,终止当前测试

Google Test

Google Test,全称为Google’s C++ test framework,是一个基于xUnit的C++测试框架

Google Test程序框架

1

Quick Look

假设我们实现了一个除法函数,其定义和实现如下:

1
2
3
4
5
6
7
8
9
10
#ifndef DIVISION_H
#define DIVISION_H

#include <stdexcept>

namespace myns {
float divide(float dividend, float divisor) throw(std::domain_error);
}

#endif /* DIVISION_H */
1
2
3
4
5
6
7
8
9
10
11
12
13
// division.cpp
#include <stdexcept>
#include <iostream>
#include "division.h"

using namespace std;

float myns::divide(float dividend, float divisor) throw(domain_error) {
if (divisor == 0) {
throw domain_error("Divisor cannot be 0");
}
return dividend / divisor;
}

测试文件:

1
2
3
4
5
6
7
8
9
10
11
12
// test.cpp
#include <gtest/gtest.h>
#include <stdexcept>
#include "division.h"

TEST(divisionTest, divisorNotZero) {
EXPECT_EQ(2, myns::divide(8, 4));
}

TEST(divisionTest, divisorZero) {
EXPECT_THROW(myns::divide(1, 0), std::domain_error);
}

编译命令:

g++ division.cpp test.cpp -o test -std=c++11 -lgtest -lgtest_main -lpthread

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
Running main() from gtest_main.cc
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from divisionTest
[ RUN ] divisionTest.divisorNotZero
[ OK ] divisionTest.divisorNotZero (0 ms)
[ RUN ] divisionTest.divisorZero
[ OK ] divisionTest.divisorZero (0 ms)
[----------] 2 tests from divisionTest (0 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran. (0 ms total)
[ PASSED ] 2 tests.

声明:

  • TEST()

在Google Test中,每一个Test case都由 TEST() 宏来实现,其接收两个参数,第一个为test casename,第二个为test name(测试名字都是自己定义的,但是要按照要求定义)。正如QuickLook中的两个测试, TEST(divisionTest, divisorNotZero) , TEST(divisionTest,divisorZero) ,第一个参数表明是对divide这个函数进行测试,第二个表示测试的测试的方面。这些名字会反应在生成的报告上。

传入的名字不能带 _ 字符,因为这是一个宏定义,用户传入的名字会插入在Google Test内部定义的变量中,而这个变量是用 _ 连接的。

  • Assertions

Google Test的断言非常丰富,在Quick Look中使用了两个断言: EXPECT_EQ() 和EXPECT_THROW() 。其中 EXPECT_EQ() 会判断传入的两个值是否相等,若相等,则这次断言通过,不等,则失败。通常,第一个参数为期望值,第二个为实际测试的值,这个结果会最终反映在report中,用Expected和Actual表示。 EXPECT_THROW() 同理,第一个参数为一个表达式,第二个为一个异常,并期望这个表达式会抛出这个异常。

Google Test中断言分为两类,一类是 EXPECT_ ,一类是 ASSERT_ 。 EXPECT_ 在失败时会继续这个 TEST() ,而 ASSERT_ 在失败时会终止该 TEST() 。一般情况下, ASSERT_* 用于致命错误,或者是这次断言失败 TEST() 中剩下的测试都没有意义的情况。

  • 编译命令

写好的测试代码在编译时需要加上 -std=c++11 -lpthread -lgtest -lgtest_main 参数,Google Test安装时会安装 libgtest.a 和 libgtest_main.a (默认的main函数,这样就不用写main函数了)静态库。同时,Google Test是一个多线程程序,需要使用线程库。(实际测试的时候Google Test并不会开多线程让测试同时进行,因为这涉及到线程安全和死亡测试等问题,同时,也不要在非pthread库支持的平台下多线程测试 )

重要的特性

main()

上面的例子使用了Goolge Test自带的main静态库,当然我们也可以自己写main函数,下面是Google Test自带的main函数:

1
2
3
4
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

Google Test首先接收外部传参进行初始化(具体参数可以在运行测试程序时传入 –help 参数查看), RUN_ALL_TESTS() 会收集所有的 TEST 并执行,因此实现的 TEST 不用手动注册了(后面讲到全局 SetUp 和 TearDown 时需要手动注册)

Fixture

Fixture的目的是减少代码冗余,如果你的一系列测试中都需要在相同的环境运行,或者需要相同的测试数据,那么你不用在每次 TEST 的开头手动搭建环境和创建数据,而可以将这些配置都写在fixture里面。

在Google Test中,创建一个fixture首先需要定义一个类,类名即为test case的名字,并从 ::testing::Test 继承,其中的所有成员都需要声明为 public 或 protected ,其中你可以定义以下内容:

  • virtual void SetUp() (注意是大写的U)
  • virtual void TearDown()
  • static void SetUpTestCase()
  • static void TearDownTestCase()
  • 以及在测试中需要用到的变量

回顾xUnit中Test execution的执行顺序,每次执行 TEST() 的都会先执行 SetUp() ,每次 TEST() 执行完后都会执行 TearDown() ,继承 ::testing::Test 后重写这两个函数可以自定义其功能,对于开头数据库的例子,连接数据库的步骤就可以写在 SetUp() 中。

SetUpTestCase() 和 TearDownTestCase() 同理,不过这个函数是作用在Test Case上的。它们分别会在Test Case中第一个 TEST() 的 SetUp() 之前和最后一个 TEST() 的 TearDown() 之后执行,作为整个Test Case的环境维护。这两个函数可以用于构造开销较大,在每个 TEST() 中都要用到的数据,以达到数据在 TEST() 之间共享的目的。要注意的是,数据共享并不意味着两个 TEST() 之间能够相互影响,原则上 TEST() 之间是应该互相独立的,其执行顺序不同也不应该影响测试结果,这意味着,当你在一个 TEST() 中修改了数据,你应该在测试结束时将其恢复。

在每一个 TEST() 中都需要用到的变量可以直接声明在fixture中,在测试中可以直接使用 SetUpTestCase() 和 TearDownTestCase() 中操作的变量需要声明为 static 。所以,执行顺序为: SetUpTestCase()->SetUp()->TEST()->TearDown()->…->SetUp()->TEST()->TearDown()->TearDownTestCase() ,若有多个Test Case,则继续下一个Case。

最后,要使用这个fixture,你需要使用 TEST_F() 宏,并将fixture类的名字传入第一个参数。

现在修改上面 test.cpp 的代码,添加了 setup 和 teardown ,并添加了一些数据

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
// test.cpp
#include <gtest/gtest.h>
#include <stdexcept>
#include <iostream>
#include "division.h"

class divisionTest: public ::testing::Test {
protected:
static void SetUpTestCase() {
std::cout << "SetUpTestCase\n";
}
static void TearDownTestCase() {
std::cout << "TearDownTestCase\n";
}
virtual void SetUp() {
data1 = 8;
data2 = 4;
data3 = 0;
std::cout << "SetUp\n";
}
virtual void TearDown() {
std::cout << "TearDown\n";
}
int data1, data2, data3;
};

TEST_F(divisionTest, divisorNotZero) {
std::cout << "Test: divisorNotZero\n";
EXPECT_EQ(2, myns::divide(data1, data2));
}

TEST_F(divisionTest, divisorZero) {
std::cout << "Test: divisorZero\n";
EXPECT_THROW(myns::divide(1, data3), std::domain_error);
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Running main() from gtest_main.cc
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from divisionTest
SetUpTestCase
[ RUN ] divisionTest.divisorNotZero
SetUp
Test: divisorNotZero
TearDown
[ OK ] divisionTest.divisorNotZero (0 ms)
[ RUN ] divisionTest.divisorZero
SetUp
Test: divisorZero
TearDown
[ OK ] divisionTest.divisorZero (0 ms)
TearDownTestCase
[----------] 2 tests from divisionTest (0 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran. (0 ms total)
[ PASSED ] 2 tests.

从上面的运行结果可以很直观的看出执行顺序。

既然Test Case也有 setup 和 teardown ,那么对于整个程序是否也有类似的方法呢?有的,在AdvancedGuide中有介绍。和创建fixture类似,首先你需要继承 ::testing::Environment 这个类,并重写 virtual void SetUp() 和 virtual void TearDown() 这两个方法。但是与fixture不同的是,你需要注册这个environment,在 RUN_ALL_TESTS() 之前调用 Environment*AddGlobalTestEnvironment(Environment* env) 来注册。当然,你可以注册多个Environment,他们会按照注册顺序依次执行。若你的设置了重复次数,那么每次重复时都会执行。

重写main函数:

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
// main.cpp
#include <gtest/gtest.h>
#include <iostream>
#include "division.h"
using namespace std;

class myEnv_1: public ::testing::Environment {
public:
virtual void SetUp() {
cout << "Global setup 1\n";
}
virtual void TearDown() {
cout << "Global teardown 1\n";
}
};

class myEnv_2: public ::testing::Environment {
public:
virtual void SetUp() {
cout << "Global setup 2\n";
}
virtual void TearDown() {
cout << "Global teardown 2\n";
}
};

int main(int argc, char **argv) {
AddGlobalTestEnvironment(new myEnv_1);
AddGlobalTestEnvironment(new myEnv_2);
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
Global setup 1
Global setup 2
[----------] 2 tests from divisionTest
SetUpTestCase
[ RUN ] divisionTest.divisorNotZero
SetUp
Test: divisorNotZero
TearDown
[ OK ] divisionTest.divisorNotZero (0 ms)
[ RUN ] divisionTest.divisorZero
SetUp
Test: divisorZero
TearDown
[ OK ] divisionTest.divisorZero (0 ms)
TearDownTestCase
[----------] 2 tests from divisionTest (0 ms total)
[----------] Global test environment tear-down
Global teardown 2
Global teardown 1
[==========] 2 tests from 1 test case ran. (0 ms total)
[ PASSED ] 2 tests.

自定义报错信息

如果在上面fixture例子中没有实现抛出异常的代码,那么错误报告会是这样:

1
2
3
test.cpp:37: Failure
Expected: myns::divide(1, data3) throws an exception of type std::domain_error.
Actual: it throws nothing.

你会发现报错信息中出现了 data3 这个变量但是并没有显示出它的值,这非常不友好,也不容易让我们定位到错误。Google Test的所有断言都可以接收字符串流并显示在报告中。对此,你可以自定义报错信息来查看 data3 变量。下面是一个例子:

1
2
3
4
5
TEST_F(divisionTest, divisorZero) {
std::cout << "Test: divisorZero\n";
ASSERT_THROW(myns::divide(1, data3), std::domain_error)
<< "With data3 = " << data3;
}

报错信息:

1
2
3
Expected: myns::divide(1, data3) throws an exception of type std::domain_error.
Actual: it throws nothing.
With data3 = 0

Assertion的详细介绍(包括自定义断言等)
测试程序的传参(可以设置测试行为,如:生成xml报告,设置测试次数,随机测试顺序等)

linux gdb

Posted on 2019-09-29 | In 初级实训 |
Words count in article: 934 | Reading time ≈ 3

安装gdb

sudo apt-get install gdb

编译test

  1. 在编译指令里记得加上 -g 参数,这个会使得编译出来的程序可以使用gdb进行调试
    $ g++ -g test.cpp -o test
  2. gdb载入可执行文件,这里有两个方法

直接运行 gdb test

或:

gdb + file test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ gdb
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.h
tml>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copyin
g"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) gdb tes
Undefined command: "gdb". Try "help".
(gdb) gdb file test
Undefined command: "gdb". Try "help".
(gdb) file test
Reading symbols from test...done.
(gdb)

基本命令:

  • l : 输出代码,输出当前上下各10行的代码
  • b 行数 : 在那一行上设置断点
  • r : 运行整个程序,直至遇到断点
  • s : 单步继续执行(进入函数)
  • n : 单步继续进行 (不进入函数,直接把子函数一并运行完)
  • q : 退出gdb
  • d num : num为断点编号, 删除该断点
  • c : continue , 继续运行直至下一个断点
  • p exp : 查看变量exp的内容
  • k :kill掉当前运行的程序(然后再用r来重新调试)

breakpoint进阶

  • b 行数 if i > 9 :增加条件, 当 i > 9的时候,程序运行到num行会停下来。
  • info break : 查看断点信息,包括所有断点号,禁用情况等
  • 删除breakpoint:
    • clear 行数 :删除该行上的所以断点
    • d :删除所有的断点
    • d 1-6 :删除编号是1~6的断点
  • 禁用断点相关:
    • disable num :禁用某个断点
    • disable :全部禁用
    • enable num :启用某个断点
    • enable :全部启用
  • 在其他文件里面设置断点
    • b 文件名 : 函数名/行数 :for example:
      (gdb) b AgendaService.cpp : AgendaService::startAgenda()

设置观察点 (数据断点):

watch 变量/表达式

  • 当观察点的内容有变化的时候,即立刻停止
  • 用delete来删除观察点
  • info watchpoints 来查看所有观察点的信息

p 命令

  • p 后的变量必须是全局变量或者是当前可见的局部变量,如果两者同名,则优先查看局部变
    量

  • p 还可以显示其他数据结构,比如说,在调试agenda的时候,可以直接 p startdate ,这
    样就可以直接看startdate这个Date类里面的全部内容了

ptype命令

通过 ptype var 可以查看var的数据类型

bt命令

bt 查看程序crash堆栈信息

当程序被停住了,你需要做的第一件事就是查看程序是在哪里住的。当你的程序调用了一个函数,函数的地址,函数参数,数内的局部变量都会被压入“栈”(Stack)中。你可以用GDB令来查看当前的栈中的信息。

1
2
3
4
5
(gdb) bt
#0 func (n=250) at tst.c:6
#1 0x08048524 in
main (argc=1, argv=0xbffff674) at tst.c:30
#2 0x40040Arrayed in __libc_start_main () from /lib/libc.so.6
  1. 程序崩溃的时候,这个时候查看最近的堆栈消息,可以非常快地查看到是在哪里出错的。
  2. 当你确认了一个函数有bug,这个函数被其他很多函数调用,这个时候,程序在这个函数停
    下来的时候使用bt命令,就可以知道是哪个函数调用了有问题的函数。

display命令

display + var 来使用自动显示var的值

set命令

set tol=100 这样可以在程序运行时把tol设置为100

Makefile

Posted on 2019-09-29 | In 初级实训 |
Words count in article: 1.5k | Reading time ≈ 6

Makefile简介

Makefile是一个定义项目的编译规则的文件,以便于整个项目的编译。

Makefile中就可以定义好各个文件的依赖关系,在之后再需要编译时,只需要执行 make命令就可以自动编译了。

在一次 make之后,一般 会生成很多 目标文件(*.o) 和一个可执行文件,当这些文件和源代码都没有被修改时,再次执行 make会提示 make: ‘bin/your_program’ is up to date.,而当你只修改了一个源代码文件再执行 make时,它也不会重复编译已经最新的文件,而只编译依赖了你的源代码的文件,这对提高编译效率是非常重要的。

基本语法

1
2
3
4
target ... : prerequisites ...
command
...
...
  • target 是下面的命令的 目标 ,即下面命令是为了target而生的。这个 目标 可以是*.o文件,也可以是可执行文件

  • prerequisites 则是生成该目标所 依赖 的文件,如果没有依赖的文件,下面的命令就不会执行且会中断 make

  • command 就是生成目标文件的命令,一般就是编译命令了,如 g++ main.cpp等等(注
    意:命令前面必须有 Tab (‘\t’) 真正 的 Tab,这样make才会认为它是指令)

1

1
2
3
4
5
6
7
8
9
10
11
12
13
main: main.o support1.o
g++ main.o support1.o -o main

main.o: main.cpp main.hpp support1.hpp
g++ main.cpp main.hpp support1.hpp -c

support1.o: support1.cpp support1.hpp
g++ support1.cpp support1.hpp -c

clean:
@rm -f main
@rm -f *.o
@rm -f *.gch

1


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Makefile version 3
CC := g++
FLAGS := ­std=c++11 -­w

main: support1.o main.o
$(CC) $(FLAGS) main.o support1.o ­-o $@

support1.o: support1.hpp support1.cpp
$(CC) $(FLAGS) ­-c support1.cpp

main.o: main.cpp
$(CC) $(FLAGS) -­c main.cpp

clean:
@rm ­f *.o
@rm ­f *.gch
@rm ­f main
  • 变量
  • 在Makefile中可以使用” := ”声明变量,并在此后使用$()可以使用,类似C/C++中的宏,它是字符串的直接替换。如 $(CC) $(FLAGS) ­csupport1.cpp会变成 g++ ­std=c++11 ­-w -­c support1.cpp。
  • 前缀是 $符号,后面带个括号 ()的都是变量,如 $@也是变量,称为 自动化变量,它是目标文件的名字。如目标为 main的这一段的命令会变成 g++ ­std=c++ -­w main.o support1.o ­-o main
  • 伪目标
  • clean一段中,target后面没有依赖文件,这称为 伪目标 ,作用是写了之后就可以使用 make clean来执行它的命令集了
  • 这里的命令集可以使用许多shell命令,但似乎并不是所有都能用
  • 命令前面的 @表示 不显示这条命令 ,和Windows下的.bat类似(下面分别是不使
    用 @和使用 @的对比)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在Makefile中加入
foo:
echo "Testing"
# 同样地,这是伪目标,可以使用make foo来执行其命令集

user@computer:~/learnmake$ make foo
echo "Testing"
Testing

Makefile中改为
foo:
@echo "Testing"

user@computer:~/learnmake$ make foo
Testing

复杂一点的情况

有时候一个项目的目录并没有如此简单,例如:

1
2
3
4
5
6
7
8
.
├── bin
├── build
├── include
├── lib
│ ├── mysql
│ └── mysql++
└── src
  • bin是编译生成的可执行文件
  • build是编译的中间文件如*.o文件
  • include是各种.h或.hpp文件
  • lib是一些必要的库文件
  • src是.cpp文件

这个时候,事情并不简单。因为现在的代码“身首异处”,所以要把它们的路径告诉编译器才行,我们使用 相对路径 来达到效果。

.表示当前目录, ..表示上一级目录,用类似这样的相对路径来找到需要的文件,当然 ./经常可以省略

以下面的目录结构为目标(假设原本不存在 bin目录和 build目录):

1
2
3
4
5
6
7
8
9
10
11
12
.
├── bin
│ └── main
├── build
│ ├── main.o
│ └── support1.o
├── include
│ └── support1.hpp
├── Makefile
└── src
├── main.cpp
└── support1.cpp

因为最后的目标 main在 bin目录中,所以target也应该是 bin/main,而其依赖的两个文件也要写清楚目录了 build/main.o build/support1.o。

而在编译前,应该确认 bin和 build都是存在的目录,因此需要在编译前多一条命令 mkdir ­-p bin。因为要指明文件目录,于是编译命令变成 g++ ­std=c++11 -­w build/main.o build/support1.o ­-o bin/main

由于在support1.cpp中我们写了 #include “support1.hpp”,按以前的经验,这样写是只能找到同一个文件夹下的文件的,所以需要加一个编译器的参数 ­I./include,让它去其他地方找头文件。这样编译命令就完成了,生成.o文件的也类似。那么Makefile就可以写出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CC := g++
FLAGS := ­std=c++11 -­w

bin/main: build/support1.o build/main.o
@mkdir ­-p bin
$(CC) $(FLAGS) ­I./include build/support1.o build/main.o ­-o $@

build/%.o: src/%.cpp
@mkdir ­-p build
$(CC) $(FLAGS) ­I./include ­-c ­-o $@ src/support1.cpp

build/main.o: src/main.cpp
@mkdir ­-p build
$(CC) $(FLAGS) ­I./include ­-c ­-o $@ src/main.cpp

clean:
@rm ­rf build
@rm ­rf bin

这样就好了吗?不,不可以。将来要是文件结构有了偏差,写这个Makefile的人是要负责的。如果目录不同了,那么改各个路径要每个都改,而最好的做法应该是只改一处就影响全局。所以应该下面这样会更好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CC := g++
FLAGS := ­std=c++11 ­-w
INC_DIR := include
SRC_DIR := src
BUILD_DIR := build
BIN_DIR := bin
INCLUDE := ­I./$(INC_DIR)

$(BIN_DIR)/main: $(BUILD_DIR)/support1.o $(BUILD_DIR)/main.o
@mkdir ­-p $(BIN_DIR)
$(CC) $(FLAGS) $(INCLUDE) $(BUILD_DIR)/support1.o $(BUILD_DIR)/main.o ­-o $@

$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp
@mkdir -­p $(BUILD_DIR)
$(CC) $(FLAGS) $(INCLUDE) ­-c -­o $@ $(SRC_DIR)/support1.cpp

$(BUILD_DIR)/main.o: $(SRC_DIR)/main.cpp
@mkdir -­p $(BUILD_DIR)
$(CC) $(FLAGS) $(INCLUDE) -­c -­o $@ $(SRC_DIR)/main.cpp

clean:
@rm ­rf $(BUILD_DIR)
@rm ­rf $(BIN_DIR)

然而这个Makefile还可以更加简短并更加完善

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CC := g++
FLAGS := ­std=c++11 ­-w
INC_DIR := include
SRC_DIR := src
BUILD_DIR := build
BIN_DIR := bin
INCLUDE := ­I./$(INC_DIR)

$(BIN_DIR)/main: $(BUILD_DIR)/support1.o $(BUILD_DIR)/main.o
@mkdir ­-p $(BIN_DIR)
$(CC) $(FLAGS) $(INCLUDE) $^ ­-o $@

$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp
@mkdir -­p $(BUILD_DIR)
$(CC) $(FLAGS) $(INCLUDE) -­c ­-o $@ $<

clean:
@rm ­rf $(BUILD_DIR)
@rm ­rf $(BIN_DIR)
  • 两个自动化变量
    • $^依赖文件的集合,用空格分隔
    • $<第一个依赖文件
    • 其实在 %.o部分,也可以换成 $^
    • 还有上文已经提过的 $@目标文件
  • 通配符 %
    • 作用是就是 *的作用了通配

这里的好处就是当 src目录下再多cpp文件时,生成%.o文件的这一部分不用更改,生成可执行文件那里只要加一个依赖就好了

实际上还有方法让生成可执行文件的规则也自动

c++ 11特性

Posted on 2019-09-29 | In 初级实训 |
Words count in article: 1.6k | Reading time ≈ 7

c++ 11特性:

Type Inference 类型推导

  1. auto
  2. decltype:
1
2
3
4
int a;
std::string b;
decltype(a) i;
decltype(b) s;

这个语法可以应用于复用匿名的struct或union

1
2
3
4
5
6
7
8
9
10
struct { // 匿名
int x, y;
} temp_position;

union { // 匿名
int *foo;
} temp_union;

decltype(temp_position) pos;
decltype(temp_union) uni;

需要注意,auto和decltype都是编译时推导变量的类型,而不能在运行时推导变量类型,而且其推导有一定的限制。例如

1
2
3
4
auto i; // Compile error
int foo(auto a, auto b); // Compile error, but it is valid in C++14
int bar(auto a = 1, auto b = 1); // 虽然我们可能觉得编译器可以推导
// 但这也是不合法的表达

Return Value Tracking(追踪返回类型)

追踪返回类型的函数和普通函数的声明最大区别在于返回类型的后置。如

1
2
int func(char* a, int b);
auto func(char* a, int b) -> int;

而追踪返回类型还可以让我们在写返回类型时,不写明作用域

1
2
3
4
5
6
class Foo {
struct InnerType { int i; }
InnerType GetInner();
InnerType it;
}
auto OuterType::GetInner() -> InnerType { return it; }

上面体现的效果并不是特别的明显,那么下面的例子呢?

1
2
3
4
5
// 有的时候,你会发现这是面试题
// ——《Understanding C++11 Analysis
// and Application of New Features》
int (*(*pf()) ()) () { return nullptr; }
auto pf1() -> auto (*) () -> int (*) () { return nullptr; }

上述两个函数的作用是完全一样的,但是可读性却完全不同
这其实是一个返回函数指针的函数,而这个指针指向着返回int*的函数

auto的使用细则:auto*, auto&, const auto, volatile auto…

Rvalue Reference 右值引用

通常,在C++中,我们称可以使用取地址运算符(&)的值为左值,而不是左值的值就是右值

1
2
3
4
5
6
7
8
9
#include <iostream>
int main() {
s int a = 1, b = 3, c = 0;
c = a + b;
std::cout << &c << std::endl; // Valid, c is an Lvalue
// std::cout << &(a + b) << std::endl; // Invalid, (a + b) is an Rvalue
std::cout << &a << " " << &b << std::endl; // Valid, a and b are Lvalue
return 0;
}

右值引用,就是使右值也拥有一个名字,如果它是一个临时变量(如函数的返回值),右值引用还能给它“续命”。

基本语法 value_type && val = temp_val;

1
2
3
4
5
6
7
8
int foo() {
return 1;
}

int main() {
int && a = foo(); // foo()的返回值不会立即销毁
return 0;
}

所谓移动语义,就是从将要销毁的临时变量那里把资源移动过来。这样就节省了分配空间和复制值的开销。这就是移动语义的核心。

Lambda 匿名函数

Lambda Functional Programming

基本语法:

1
2
3
4
5
6
7
[capture] (parameters) mutable -> return_type { statement }
[capture]: 捕捉列表。能够捕捉上下文的变量供lambda函数使用。
(parameters): 参数列表。
mutable: 默认情况下,lambda函数总是一个const函数,mutable可以取
消其常量性。
->return_type: 返回类型。
statement: 函数体。可以使用捕获的变量
1
2
3
4
5
6
7
8
9
10
11
12
// example
int main ( ) {
int a = 2 ;
// catch a
auto pow = [ a ] ( int times ) - > int {
int ret = 1 ;
while ( times -- ) ret * = a ; // use a
return ret ;
} ;
std :: cout << pow ( 3 ) << std :: endl ; // 8
return 0 ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::list<int> li = { 1,2,3,4,5,6,7,8 };
auto printList = [](std::list<int>& li) {
for(auto it = li.begin(); it != li.end(); it++) {
std::cout << *it << " ";
}
std::cout << std::endl;
};
printList(li);
// remove all even numbers from the list
li.remove_if([&](int x) -> bool {
if(x % 2 == 0) {
return true;
}
return false;
});
printList(li);
1
2
3
4
5
 std :: array < int , 10 > s = { 5 , 7 , 4 , 2 , 8 , 6 , 1 , 9 , 0 , 3 } ;
// sort using a lambda expression
std :: sort ( s. begin ( ) , s. end ( ) , [ ] ( int a, int b ) {
return b < a ;
} ) ;

Closure 函数闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fromto = [ ] ( auto start, auto finish ) {
return [ = ] ( ) mutable {
if ( start < finish )
return start ++ ;
else
throw std :: runtime_error ( "Complete" ) ;
} ;
} ;
auto range = fromto ( 0 , 10 ) ;

std :: cout << range ( ) << std :: endl ;
std :: cout << range ( ) << std :: endl ;
std :: cout << range ( ) << std :: endl ;
std :: cout << range ( ) << std :: endl ;

Initializer List

1
2
3
4
5
6
7
8
9
10
11
struct s {
int x;
std::string std;
std::vector<int> vec;
};

s func (){
return {1, "abcde", {2,4,6,8,10}};
}

s s1{1, "abcde", {2,4,6,8,10}};

Smart Pointer 智能指针

auto_ptr(C++98):

auto_ptr以对象的方式管理堆分配的内存,并在适当的时间(比如auto_ptr析构时),释放获得的堆内存。这种管理方式只要程序员将new返回的指针作为auto_ptr的初始值即可,不需要显式调用delete。比如:

auto_ptr(new int);

不过auto_ptr有一些缺点,如拷贝时返回一个左值,不能调用delete[]等,在C++11中被废弃。

unique_ptr

unique_ptr如其命名,表示它管理一个只属于它的内存,它不能被复制,可以被移动。

1
2
3
4
5
6
7
8
9
10
11
using namespace std;
unique_ptr<int> up1(new int(11));
unique_ptr<int> up2 = up1; // 编译错误,复制
unique_ptr<int> up3 = move(up1) // std::move会使up1清空,
// 这句的语义是将up1移动给up3
cout << *up1 << endl; // 错误,up1已经不拥有该内存
cout << *up3 << endl; // 11
up3.reset(); // reset使指针置空并释放内存
up1.reset(); // 不会导致内存错误
cout << *up3 << endl; // 错误,内存已释放
// 即使不调用reset,unique_ptr对象在析构时也会释放拥有的内存

shared_ptr

shared_ptr也如其命名,表示多个智能指针可以拥有同一个堆内存。在实现上使用了引用计数,当一个shared_ptr失效,内存也不会被释放,而是引用计数减小。当引用计数归零的时候,shared_ptr才会真正释放堆内存的空间。

weak_ptr

weak_ptr用于指向一个shared_ptr,但它实际上不拥有这个内存。其lock()方法可返回一个shared_ptr,而当所指对象已经失效时,可以返回nullptr。用这个可以验证shared_ptr的有效性。

1
2
3
4
5
6
7
8
9
10
11
12
using namespace std;
shared_ptr<int> sp1(new int(22));
shared_ptr<int> sp2 = sp1; // 和sp1共享一个堆内存
weak_ptr<int> wp = sp1; // 指向sp1所指对象
cout << *sp1 << endl; // 22
cout << *sp2 << endl; // 22
sp1.reset(); // 引用计数-1,但不释放内存
if (wp.lock() != nullptr) cout << "valid" << endl; //valid
shared_ptr<int> sp3 = wp.lock(); // sp3指向与sp1相同的对象
sp2.reset();
sp3.reset(); // 所指对象被释放
if (wp.lock() == nullptr) cout << "invalid" << endl; // invalid

Range Base Loop

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <vector>
int main() {
vector<int> a {1,2,3,4,5,6,7,8};
for(auto i : a) {
std::cout << i << " ";
}
std::cout << endl;
return 0;
}

linux 指令入门

Posted on 2019-09-29 | In 初级实训 |
Words count in article: 1.9k | Reading time ≈ 7

linux 指令入门

cd

打开terminal,进入的是你的主目录路径,通过cd命令,可以进入其他目录。格式为 cd[dir] ,dir是目录的路径,可以是绝对路径也可以是相对路径,其中 ./ 代表当前文件夹, ../ 代表上一目录,也就是父文件夹。如果没有参数,回到主目录(也就是 ~/ )。

ls

键入 ls [option] [dir] ,你会得到整个dir目录下的未隐藏文件(夹)

option是参数选项,如使用 ls -a ./ 显示当前目录所有文件, -a 显示所有文件, -l 以详细列表显示,-R 以递归的方式列出指定目录下的所有文件

man

如果想知道一个命令更多的信息怎么办,一般又三种方法:

  1. 首先是试一下命令有没有自带的help参数,一般是 –help 、 -h ;

  2. 其次就是 man ,使用命令 man [command] 来打开一个命令的文档(如果有),通过回
    车或者方向上下就可以读到整个文档了,退出的话按 q ;

1

mkdir、touch和cat

mkdir dir 用来生成一个文件夹,文件夹所在的路径应当存在。

touch 命令一般用来生成一个文件, touch path 会生成一个路径为path的空白文件。

cat 命令会以文本方式读取一个文件,然后把文件中的内容输出到标准输出中,使用cat可以快捷的查看一个文件的内容。

1

sudo

Linux下有些命令或者执行某些动作是需要权限的,这个有点类似于Windows下的管理员权限。如果你得到一个Permission denied,那么试试 sudo 命令。

sudo [command] 会让你输入用户密码,然后以超级用户的权限执行command命令(superuser do)。

注意: 这里输入密码时,是不会有任何回显的,不要认为没有成功输入

mv cp rm rmdir mv

1
2
3
cp hello.cpp ../
cd ../
rm hello.cpp

1

mv 移动文件与目录或重命名

1
2
mv hello.cpp helloworld.cpp
mv helloworld.cpp ../

1

Linux rmdir命令删除空的目录。
rmdir [-p] dirName

-p 是当子目录被删除后使它也成为空目录的话,则顺便一并删除。

1
2
3
4
将工作目录下,名为 AAA 的子目录删除 :
rmdir AAA
在工作目录下的 BBB 目录中,删除名为 Test 的子目录。若 Test 删除后,BBB 目录成为空目录,则 BBB 亦予删除。
rmdir -p BBB/Test

执行可执行文件

当前目录下的可执行文件前面加上./

gcc、g++、gdb

gcc:

  1. -c 只做编译,不做链接
  2. -o 生成目标,后面的路径会生成目标文件
  3. -std=c[++]**|gnu** 规定ISO标准,如-std=c99就是使用c99标准编译文件,默认的话C语言是c89,C++是c++98/03
  4. -g 插入调试信息,请配合gdb食用
  5. -W** 警告某种规则,常见是-Wall,打开所有警告,可用值可以使用可以百度或者查看官方文档
  6. -Werror 把警告视为错误
  7. -Wno-** 关闭某种警告,可用值与上面相同
  8. -I 将路径添加到预处理默认路径
  9. -L 将路径添加到链接库默认路径
  10. -l 链接的时候链接库默认路径下的库文件,如-lgtest会链接/usr/lib或者/usr/local/lib下的libgtest.a文件或许libgtest.so文件(静态库或者动态库)
  11. -pthread 多线程代码使用

gdb:

如果你编译代码的时候有加-g参数,那么你就可以使用gdb调试你的代码了

使用gdb <program>可以对可执行文件进行调试。

linux c++ gdb教程

grep、locate、find 查找

  • 如果想查找文档中的某些信息,使用grep绝对是个好选择grep -i regex file 会将file中匹配了正则表达式的行输出,请注意这里是行为单位的

  • find可以在特定文件夹中搜索匹配规则的文件,规则比较复杂,常用的是 -name 参数,可以查找名字匹配正则表达式的文件(包括文件夹,毕竟Unix一切皆文件嘛,文件夹也不例外)

  • locate命令可能需要通过 sudo apt install locate 安装,通过 locate name 可以定位文件位置,也就是查找文件啦

chmod

Linux下的文件是有权限控制的,使用 ls -l 可以看到每行最前面有-rwxrwxr-x之类的,这是描述用于文件的属性与权限。第一个字符代表文件是否为目录,如果是则为d,否为-;接下来九个字符每三个为一组,分别代表用户,组,和其他人的权限,三个字符分别代表读写执行的权限,即r、w和x。一个二进制文件必须要有可执行权限才能运行。为了改变文件的属性,可以使用命令 chmod [option] mode ... file ... 来更改文件权限。mode是指文件的权限更改模式,主要有以下两种类型:

  1. 一个三位的八进制数。这个八进制数从高位到低位分别代表用户、组和其他人的权限模式。每个位是1、2、4或者它们的和,1代表执行,2代表写,4代表读。如7 = 1 + 2 + 4,所以7代表有读写执行的权限,而5= 1 + 4,所以5代表有读和执行,但是没有写的权限。如果将这个三位八进制数写成二进制,每一位0正好对应前文说道的后九位的-,而每一位1对应字符。如数字0775写成二进制是111111 101,对应的就是rwx rwx r-x。正所谓一言不合777, chmod 777 path 将文件的所有权限给了所有人,是很不安全的行为。

  2. [a|u|g|o][+|=|-][rwx] 的格式。其中a代表全部,u代表用户,g代表用户组,o代表其他,+是加上权限,=是权限改为,-是除去权限。如 chmod u+x a+r a.out 是使得用户可执行,全部可读文件a.out

Linux管道与(真)grep

一个由标准输入输出链接起来的进程集合,所以每一个进程的输出(stdout)被直接作为下一个进程的输入(stdin)

1

使用管道操作符 | 将多个命令连接起来,将前一个命令的输出作为当前命令的输入,就像管子一样字符串在命令之间流动。

1
2
3
apt list | grep ${package}\ \\[installed\\] #快速查找软件包是否安装
cat file | grep regx #查找文件中匹配正则表达式的行,快,但是缺点是只能匹配一行,其实可以使用sed
ll -R dir | grep regx #确定目录下(包括所有子目录)是否含有匹配正则表达式的文件,个人觉得比find稍快,但是不能定位路径

使用管道+grep,可以极大减少无用信息的输出,极大地提高你的工作效率
当然,还有 less 命令可以使得长长的输出逐页输出,翻页随心所欲。

bash的初始化脚本

严格意义上说这是题外话,但是我觉得跟大家分享一下还是有必要的一般来说有两个文件是在bash启动的过程中比较重要的,一个是/etc/bash.bashrc,另一个是~/.bashrc,主要都是bash的配置命令。文件/etc/bash.bashrc会在系统启动时执行,而~/.bashrc会在用户打开bash的时候执行。如果你在linux或者Windows中装过JVM或者JDK,一定忘不了要设置环境变量,而在Linux下,一般是在写在/etc/bash.bashrc中,这样开机的时候就会把
JDK或者JVM的目录添加到$PATH中.

CSV

Posted on 2019-09-29 | In 初级实训 |
Words count in article: 305 | Reading time ≈ 1

CSV

csv是用逗号将数据分开存放的一种存储格式。

在实际操作中,只需要把需要记录的会议信息以csv规定的以下格式写入到csv文件,并在关比Agenda之后还可以从csv文件中读入历史记录即可,不是一定要用csvtool等表格程序打开,用文本文档打开也可以,只是为了方便看数据和格式的正确性。

  • 每个记录放在独立的一行,用换行符隔开;记录中不同的项用逗号隔开,注意每个记录的项的数目都是相同的,比如下面的例子第一二个记录都有三个项;最后一行可以没有换行(也可以有)。

  • 可以有首行作为对应每项的名字。

  • 记录中的每一项可以被双引号包含或者不包含。

  • 如果记录的项中出现了逗号(,)、双引号(”)、和换行,则必须用双引号将记录的项包含起来,而且项之中的双引号要写成两个双引号,即(””)

Linux下可以使用csvtool或者Ubuntu的LibreOffice来查看csv文件

csvtool:

1
2
3
sudo apt-get install csvtool

csvtool readable filename|view -

Ubuntu:
直接点开

1

1

1

git

Posted on 2019-09-27 | In 初级实训 |
Words count in article: 2.5k | Reading time ≈ 9

git-简明指南

创建新仓库

git init

检出仓库

执行如下命令以创建一个本地仓库的克隆版

git clone /path/to/repository

如果是远端服务器上的仓库,你的命令会是这个样子:

git clone username$host:/path/to/repository

工作流

你的本地仓库由git维护的三棵树组成,第一个是你的工作目录,它持有实际文件;第二个是暂存区,它像个缓冲区域,临时保存你的改动;最后是HEAD,它指向你最后一次提交的结果。

working-dir----add---->index(stage)-----commit--->HEAD

添加和提交

git add <filename>
or
git add *
or
git add .

then

git commit -m "message"

推动改动

git push origin master
可以把master换成你想要推送的任何分支

如果你还没有克隆现有仓库,并欲将你的仓库连接到某个远程服务器,你可以使用如下命令添加:
git remote add origin <server>

如此你就能够将你的改动推送到所添加的服务器上去了

分支

分支是用来将特性开发绝缘开来的。在你创建仓库的时候,master是默认的分支,在其他分支上进行开发,完成后再将他们合并到主分支上。

创建一个叫“feature_x”的分支,并切换过去:

git checkout -b feature_x

切换回主分支:

git checkout master

再把新建的分支删掉:

git branch -d feature_x

除非你将分支推送到远端仓库,不然该分支就是不为他人所见的:

git push origin <branch>

更新与合并

更新你的仓库至最新改动:git pull

以在你的工作目录中获取(fetch)并合并(merge)远端的改动。

要合并其他分支到你的当前分支(例如master),执行:

git merge <branch>

在这两种情况下,git都会尝试去自动合并改动。遗憾的是,这可能并非每次都成功,并可能出现冲突(conflicts),这时候就需要你修改这些文件来手动合并这些冲突(conflicts),改动之后,你需要执行如下命令以将他们标记为合并成功。

git add <filename>

在合并改动之前,你可以使用如下命令预览差异:

git diff <source_branch> <target_branch>

标签

为软件发布创建标签是推荐的

git tag 1.0.0 1b2e1d63ff

1b2e1d63ff是你想要标记的提交ID的前10位字符,可以使用下列命令获取提交ID:

git log

你也可以使用少一点的提交ID前几位,只要它的指向具有唯一性

替换本地改动

假如你操作十五,你可以使用如下命令替换掉本地改动:

git checkout -- <filename>

此命令会使HEAD中的最新内容替换掉你的工作目录中的文件,已添加到暂存区的改动以及新文件都不会受到影响

假如你想丢弃你在本地的所有改动和提交,可以到服务器上获取最新的版本历史,并将你本地主分支指向它:

git fetch origin
git reset --hard origin/master

实用小贴士

内键的图形化git
gitk

彩色的git输出:
git config color.ui true

显示历史记录时,每个提交的信息只显示一行:

git config format.pretty oneline

交互式添加文件到暂存区:

git add -i

图解Git

基本用法:

1

上面的四条命令在工作目录、暂存目录(也叫做索引)和仓库之间复制文件

  • git add files 把文件放入暂存区域
  • git commit 给暂存区域生成快照并提交
  • git reset – files 用来撤销最后一次git add files, 你也可以用git reset撤销所有暂存区域文件
  • git checkout – fils 把文件从暂存区域复制到工作目录中,用来丢弃本地修改

你可以用git reset -p, git checkout -p, or git add -p进入交互模式

也可以跳过暂存区域直接从仓库取出文件或者直接提交代码。

1

  • git commit -a 相当于运行git add 把所有当前目录下的文件加入暂存区域再运行git commit
  • git commit files 进行一次包含最后一次提交加上工作目录中文件快照的提交,并且文件被添加到暂存区域
  • git checkout HEAD –files 回滚到复制最后一次提交

1

绿色的5位字符表示提交的ID,分别指向父节点。分支用橘色显示,分别指向特定的提交。当前分支由附在其上的HEAD标识。 这张图片里显示最后5次提交,ed489是最新提交。 master分支指向此次提交,另一个maint分支指向祖父提交节点。

Diff

有许多种方法查看两次提交之间的变动。

1

Commit

提交时,git用暂存区域的文件创建一个新的提交,并把此时的节点设为父节点。然后把当前分支指向新的提交节点。下图中,当前分支是master。 在运行命令之前,master指向ed489,提交后,master指向新的节点f0cec并以ed489作为父节点。

1

即便当前分支是某次提交的祖父节点,git会同样操作。下图中,在master分支的祖父节点maint分支进行一次提交,生成了1800b。 这样,maint分支就不再是master分支的祖父节点。此时,合并 (或者 衍合) 是必须的。

1

1

Checkout

checkout命令用于从历史提交(或者暂存区域)中拷贝文件到工作目录,也可用于切换分支。

当给定某个文件名(或者打开-p选项,或者文件名和-p选项同时打开)时,git会从指定的提交中拷贝文件到暂存区域和工作目录。比如,git checkout HEAD~ foo.c会将提交节点HEAD~(即当前提交节点的父节点)中的foo.c复制到工作目录并且加到暂存区域中。(如果命令中没有指定提交节点,则会从暂存区域中拷贝内容。)注意当前分支不会发生变化。

1

当不指定文件名,而是给出一个(本地)分支时,那么HEAD标识会移动到那个分支(也就是说,我们“切换”到那个分支了),然后暂存区域和工作目录中的内容会和HEAD对应的提交节点一致。新提交节点(下图中的a47c3)中的所有文件都会被复制(到暂存区域和工作目录中);只存在于老的提交节点(ed489)中的文件会被删除;不属于上述两者的文件会被忽略,不受影响。

1

如果既没有指定文件名,也没有指定分支名,而是一个标签、远程分支、SHA-1值或者是像master~3类似的东西,就得到一个匿名分支,称作detached HEAD(被分离的HEAD标识)。这样可以很方便地在历史版本之间互相切换。比如说你想要编译1.6.6.1版本的git,你可以运行git checkout v1.6.6.1(这是一个标签,而非分支名),编译,安装,然后切换回另一个分支,比如说git checkout master。然而,当提交操作涉及到“分离的HEAD”时,其行为会略有不同,详情见在下面。

1

HEAD标识处于分离状态时的提交操作

当HEAD处于分离状态(不依附于任一分支)时,提交操作可以正常进行,但是不会更新任何已命名的分支。(你可以认为这是在更新一个匿名分支。)

1

一旦此后你切换到别的分支,比如说master,那么这个提交节点(可能)再也不会被引用到,然后就会被丢弃掉了。注意这个命令之后就不会有东西引用2eecb。

1

但是,如果你想保存这个状态,可以用命令git checkout -b name来创建一个新的分支。

1

Reset

reset命令把当前分支指向另一个位置,并且有选择的变动工作目录和索引。也用来在从历史仓库中复制文件到索引,而不动工作目录。

1

如果不给选项,那么当前分支指向到那个提交。如果用–hard选项,那么工作目录也更新,如果用–soft选项,那么都不变。

1

如果没有给出提交点的版本号,那么默认用HEAD。这样,分支指向不变,但是索引会回滚到最后一次提交,如果用–hard选项,工作目录也同样。

1

如果给了文件名(或者 -p选项), 那么工作效果和带文件名的checkout差不多,除了索引被更新。

Merge

merge 命令把不同分支合并起来。合并前,索引必须和当前提交相同。如果另一个分支是当前提交的祖父节点,那么合并命令将什么也不做。 另一种情况是如果当前提交是另一个分支的祖父节点,就导致fast-forward合并。指向只是简单的移动,并生成一个新的提交。

1

否则就是一次真正的合并。默认把当前提交(ed489 如下所示)和另一个提交(33104)以及他们的共同祖父节点(b325c)进行一次三方合并。结果是先保存当前目录和索引,然后和父节点33104一起做一次新提交。

1

Cherry Pick

cherry-pick命令”复制”一个提交节点并在当前分支做一次完全一样的新提交。

1

Rebase

衍合是合并命令的另一种选择。合并把两个父分支合并进行一次提交,提交历史不是线性的。衍合在当前分支上重演另一个分支的历史,提交历史是线性的。 本质上,这是线性化的自动的 cherry-pick

1

1…8910…38
zzm99

zzm99

372 posts
40 categories
3 tags
GitHub
0%
© 2020 zzm99 | Site words total count: 409.1k
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4