Marco Nie - 2020年11月
type: channel
link: https://blog.niekun.net/2020/11/
description: you are the company you keep...
items: c++ 中 string 字符串的含义
items: c++ 中数组类型数据作为 function 参数时的注意事项
items: C++ 入门教程之八 -- Templates - Exceptions - Files
items: C++ 入门教程之七 -- 继承和多态
items: C++ 入门教程之六 -- Classes 实践/头文件/操作符复用
items: C++ 入门教程之五 -- Classes and Objects
items: C++ 入门教程之四 -- Functions
items: C++ 入门教程之三 -- 数据类型，数组及指针
items: Speedtest CLI 命令行测速工具
items: 两个子网间网络互访的原理

c++ 中 string 字符串的含义
type: item
date: 2020-11-25T03:52:00+00:00
description: 有如下代码：int main() { char *s; s = "hello"; cout << *s << endl; cout << s << endl; return 0; } //OUTPUT: //h //hello首先定义了一个指针，然后给指针赋值为一个字符串。看起来以上写法有错误，s 指针是一个地址，为什么给它赋值一个字符串？因为在 c++ 中字符串类似于我们提到的数组，使用数组名就表示第一个数组元素的地址。例如：int main() { int arr[] = {1, 3}; int *p = arr; cout << *p << endl; cout << *(p+1) << endl; return 0; } //OUTPUT: //1 //3同样在字符串中，s = "hello" 表示将首字母 h 的地址赋给指针。

c++ 中数组类型数据作为 function 参数时的注意事项
type: item
date: 2020-11-25T03:16:00+00:00
description: 关于 function 的基础教程参考：C++ 入门教程之四 -- Functions当 function 的传入参数为数组时，如果我们想要得到数组元素个数可能会这样写：void testFunc(int arr[]) { int count = sizeof(arr)/sizeof(arr[0]); } int main() { int arr[] = {1, 3, 6, 8}; cout << testFunc(arr) << endl; return 0; }sizeof function 可以返回数据占用内存空间的大小。所以用整个 arr 大小除以第一个元素大小可以得到数组元素个数。但是测试会发现以上得到的 count 值并不是 arr 数组元素个数 4 而是 2。这是因为当 function 传入数组数据时，会自动将其转换为 pointer 指针类型。这时候使用 sizeof() 得到的不是 arr 数据所在地址的内存大小，而是这个指向 arr 数组的指针所占用内存大小。我们做个实验：int main() { int *arrP = new int[4]; cout << sizeof(arrP) << endl; cout << sizeof(arrP[0]) << endl; return 0; } //output: //8 //4以上建立了一个指针指向 4 个元素的数组，然后分别查看其整个指针和某个元素指针的内存大小，得到的结果是 8 和 4。所以他们两个相除结果为 2。因为指针指向的是数组第一个元素，如果我们要通过指针给数组元素赋值，可以使用 *(pointer + n) 的方式完成，例如：int *arrP = new int[4]; *arrP = 1; *(arrP+1) = 2;*(arrP+1) 就是第 2 个数组元素。可以用指针查询数组中某个元素的内存大小，数组名 arr 表示数组所在地址(不需要加地址转换符&)：int main() { int arr[] = {1, 3, 6, 8}; int *arrP = arr; cout << sizeof(*arrP) << endl; cout << sizeof(*(arrP+1)) << endl; return 0; } //output: //4 //4由于 arr 数组名表示此数组第一个元素所在地址的指针，所以我们可以直接使用此名称来用指针的方式地区元素数据：int main() { int arr[] = {1, 3, 6, 8}; cout << *arr << endl; cout << *(arr+1) << endl; return 0; } //output: //1 //3那么如何在 function 使用数组作为传入数据时如何能够直接使用它的数据而不是指针类型呢？可以使用以下方法：template <size_t N> int testFunc(int (&arr)[N]) { int count = sizeof(arr) / sizeof(arr[0]); return count; } int main() { int arr[] = {1, 3, 6, 8}; int count = testFunc(arr); cout << count << endl; return 0; } //OUTPUT: //4创建一个 function template，size_t 类型表示任意数据类型的元素占用内存大小，此处将其作为 generic data type 用来定义实际传入的数组所占用内存容量的 template，然后以传入数据的实际地址的模式将数组的元素都顺序都传入 function。这样传入数据就是实际数据地址而不是指针。关于 function 的传入数据实际地址是使用指针形式 * 还是 & 形式，在前面我介绍过，形式如下：void test1(int *a) { *a = 3; } void test2(int &a) { a = 2; } int main() { int a = 1; int *p = &a; test1(p); cout << a << endl; test2(a); cout << a << endl; return 0; } //OUTPUT: //3 //2function 中使用在指针类型的传入数据时需要传入一个指针，当定义为 & 时可以直接传入数据名称。两种方法都是 by reference 会影响到对应内存地址的数据内容。可参考教程：C++ 入门教程之四 -- Functions参考链接：https://stackoverflow.com/questions/4839626/…

C++ 入门教程之八 -- Templates - Exceptions - Files
type: item
date: 2020-11-23T13:41:00+00:00
description: function template使用 function 和 class 使我们编程更加简单及易操作。但是它们仍然受限于 c++ 编程规范，在定义它们的参数时必须指定参数的类型。例如：int sum(int a, int b) { return a+b; } int main () { int x=7, y=15; cout << sum(x, y) << endl; }以上示例中，需要我们定义sum function 需要传入两个 int 型数据，返回其相加结果。在 main 中调用 sum function。sum() 可以正确的执行指令，但它的限制是必须传入 int 类型的数据。如果需要实现两个 double 类型数据相加，需要再次定义新 funtion：double sum(double a, double b) { return a+b; }使用 function template 能够让我们只定义一个 sum() 来适用于所有类型的数据。使用关键词 template 来定义一个 function template，在尖括号<>内定义通用数据类型：template <class T> 关键词 class 表示一个申明要定义 generic 通用数据类型，注意不要和我之前学习的 class 混淆。也可以使用关键词 typename：template <typename T> T 表示我们的 generic 通用数据类型名称，在后续中可以使用此名称代表数据类型，可以是任意自定义字符。修改我们的示例：#include <iostream> using namespace std; template <class T> T sum(T a, T b) { return a + b; } int main() { int x=7, y=15; cout << sum(x, y) << endl; double a=7.15, b=15.54; cout << sum(a, b) << endl; return 0; }我们在 function 中建立了一个 generic data type 通用数据类型 T，返回值和传入参数类型都为 T。在 mian 中调用时，会根据实际传入数据类型来动态确定 T 的实际类型。function template 可以节省编程中很多时间，因为只需要一次定义就可以兼容不同的数据类型。我们也可以同时定义多个 generic data type，使用逗号, 分隔多个定义类型：template <class T, class U> T smaller(T a, U b) { return (a < b ? a : b); } int main () { int x=72; double y=15.34; cout << smaller(x, y) << endl; }以上示例定义了两个通用数据类型，返回值类型为第一种数据类型。(a < b ? a : b) 表达式的意思是：判断 a 是否小于 b，如果结果为 true 则返回 a 的值，如果结果为 flase 返回 b 的值，两个参数可以是不同数据类型，如：int 和 double。在 mian 中调用 smaller function，返回结果为两个参数中较大的那个的值，由于返回值类型为第一个定义数据类型 T，而我们调用时传入的第一个参数类型为 int，所以返回结果也为 int 型。需要注意的是在一旦定义了 function template，在 function 中就一定要使用定义的那些通用数据类型，否则编译器会报错。class template同样也可以定义 class template，允许 class 的元素类型为 generic data type。语法如下：template <class T> class MyClass { };类似于 function template，多个 generic data type 使用逗号, 分隔：template <class T,class U> 下面举例说明使用方法：template <class T> class Pair { public: Pair (T a, T b): first(a), second(b) { } private: T first, second; };以上建立了一个 class template，类型为 T，通过构造器对 private 里的两个 T 类型的参数初始化数据。在 class template 外定义 function，如 class 大括号外或一个单独文件，需要在 class 后标记 class template 内定义过的 generic type。例如…

C++ 入门教程之七 -- 继承和多态
type: item
date: 2020-11-21T14:31:00+00:00
description: inheritance 继承inheritance 继承是面向对象编程最重要的概念之一。继承允许我们基于已有的 class 创建新的 class。这能够极大的方便我们创建应用程序。一个 class 的属性被其他 class 继承，我们称这个 class 叫做 base class。一个从 base class 继承属性的 class 我们称其为 derived class。一个 derived class 获得所有 base class 的功能，且可以有自己独有的功能：继承的概念是 is a 属于是什么 的关系，例如：猫 is a 动物，狗 is a 动物。两者都继承了动物的属性。我们来建立两个 class：Mother 和 Daughter：class Mother { public: Mother() {}; void sayHi() { cout << "Hi"; } }; class Daughter { public: Daughter() {}; };Mother 有一个 sayHi() function。下面我们让 Daughter 继承 Mother 的属性，Daughter class 名称修改如下：class Daughter : public Mother { public: Daughter() {}; };使用一个冒号: 标记 base class。access specifier 访问标记符 public 表示所有 base class 里 public 段下的内容继承给 derived class 里的 public 段下。以上示例中 Daughter 继承了所有 Mother 的 public 下的元素，所以我们可以实例化一个 Daughter 的 object 然后调用 sayHi() function 了：#include <iostream> using namespace std; class Mother { public: Mother() {}; void sayHi() { cout << "Hi" << endl; } }; class Daughter: public Mother { public: Daughter() {}; }; int main(int argc, const char * argv[]) { Daughter d; d.sayHi(); return 0; }关于 access 访问标记符的使用，在下面会详细介绍使用方法。一个 derived class 可以继承所有 base class 的 function 出过一下几种特殊情况类型：constructor and destructor 构造器和销毁器复用操作符 functionfriend function一个 class 可以同时继承多个 base class，使用逗号, 分隔 base class，如：class Daughter: public Mother, public Father。protected 元素对于 class 中的元素的 access 访问表示符，我们前面使用了 public 和 private。public 里的元素可以被外界访问或修改，private 里的元素只能被 class 内部使用，或者通过 friend function 来读取。除了以上两种识别符外，还有一种类型：protected。在 protected 内的元素和 private 段内的很类似。唯一的区别是，protected 内的元素可以被 derived class 访问。例如我们给 Mother 添加一个 protected 元素：class Mother { public: void sayHi() { cout << var; } private: int var=0; protected: int someVar; };someVar 变量可以被 Daughter 访问。继承的类型access 访问标记符能够用来定义继承类型。在上面的示例中我们使用 public 作为访问标记符：class Daughter: public Mother。private 和 protected 同样可以被使用。三者的区别：Public Inheritance: base class 里 public 段内的元素继承到 derived class 里的 public 段内，base class 里 protected 段内的元素继承到 derived class 里 protected 段内，base class 里 private 段内的元素永远不能能够被 derived class 直接访问，但可以通过 …

C++ 入门教程之六 -- Classes 实践/头文件/操作符复用
type: item
date: 2020-11-21T08:42:00+00:00
description: 下面我们通过实际应用来介绍如果在真实环境下应用。我们使用 Code::Blocks IDE 来进行调试。这是一个免费的 c++ 开发环境，能够应用于绝大多数使用场景。官网：http://www.codeblocks.org/在下载界面下载对应系统版本的安装包：http://www.codeblocks.org/downloads/26最好安装带编译器的版本，这样就可以直接使用。新建项目安装好 codeblock 后新建一个项目：选择 console application：根据提示设置项目名称等，完成项目建立。然后添加一个 class：设置 class 名称，取消勾选 has destructor，勾选 headers and implementation file shall be in same folder：完成后项目结构如下：main.cpp 项目主文件MyClass.cpp class source 源文件MyClass.h class header 头文件头文件和源文件头文件 .h 申明了 class 的 function 和 variable 变量等元素，我们新建的 class 头文件内容如下：#ifndef MYCLASS_H #define MYCLASS_H class MyClass { public: MyClass(); protected: private: }; #endif // MYCLASS_H里面只默认包含了一个 constructor 构造器 MyClass()。源文件 .cpp 包含了这个 class 具体 function 和 variable 等元素的实现过程。当前只有一个空的构造器 function：#include "myclass.h" MyClass::MyClass() { //ctor }范围解析符：我们注意到源文件内构造器前的两个冒号::，叫做 scope resolution operator 范围解析符。这个符号是用来表示那些已经在头文件内申明过的 function 要在这里做具体实现了。用来和头文件内定义的元素进行关联的作用。关于::和.的使用场景我的理解是，当在定义一个元素或功能时用范围解析符::，当实例化object后使用其一个元素或功能时用点.，例如：//定义一个元素为class内的一个att1类型： MyClass1::att1 a; //使用class的function： MyClass2 obj; obj.func();要在 main 总使用我们创建的 class，只需要在其中引用头文件名即可:#include <iostream> #include "myclass.h" using namespace std; int main() { MyClass obj; return 0; }总体来说，头文件用来定义 class 提供了哪些功能及元素，源文件用来具体实现这些功能。destructor 销毁器和上一章我们讲到的 constructor 构造器类似，destructor 销毁器也是一个特殊的 function，他在 object 被销毁时自动执行。销毁一个 object 一般是在跳出创建这个 object 的 scope 段或者使用 pointer 指针指向这个 object，使用 delete 指令清除此指针指向的数据。销毁其的写法和构造器类似，使用 class 名作为 function 名字，前面加上一个波浪符~。当然也是没有返回值类型的。例如：class MyClass { public: ~MyClass() { // some code } };使用销毁器能够方便的实现在关闭程序时释放资源，关闭文件，释放内存等功能。在头文件中声明一个销毁器：class MyClass { public: MyClass(); ~MyClass(); };然后再源文件内定义具体实现内容：#include "MyClass.h" #include <iostream> using namespace std; MyClass::MyClass() { cout<<"Constructor"<<endl; } MyClass::~MyClass() { cout<<"Destructor"<<endl; }不同于构造器，销毁器不能使用参数，也不能被重写，也就是一个 class 只能有一个销毁器。销毁器不是必须有的，不需要的话可以不写返回我们的 main，我们已经定义了一个 object MyClass obj，编译运行程序，会在终端输出以下：Constructor Destructor选择符使用选择符 -> 可以访问一个指针指向的 object 的元素。…

C++ 入门教程之五 -- Classes and Objects
type: item
date: 2020-11-20T03:32:00+00:00
description: object 对象Object Oriented Programming 面向对象编程是为了让编程更加接近于真实世界的理解方式。在程序中，每个 object 是一个独立的 unite 单元，拥有自己的 identify 标识，就像真实世界的某个独立物体一样。例如一个苹果就是一个 object，它的 identify 就是名称 苹果，每个苹果都有自己独立的 attributes 属性，如颜色，大小。一个属性就是这个 object 当前状态的描述。不同 object 的 attributes 属性是不一样的，例如一个苹果是绿色的，另一个是红色的。在正式世界中 object 都有其 behave 行为，例如汽车的 move 移动，手机的 ring 响铃。这种 object 的行为叫做 object 的 type 类型。描述一个 object需要的元素：identify 标识，attributes 属性，behavior 行为在程序中每个 object 是独立的，拥有独立的 identify 用来区分其他的 object：class我们通过创建 class 来表示 object，一个 class 描述了一个 object 的形象，但是它并不是一个真正的 object，他只是一个对某种 object 结构的定义。一个 class 可以用来建立多个 object。例如一套设计图纸可以用来作为蓝图修建多栋楼房。一个 class 包含：identify，attributes，behavior程序中，一个对象的 type 就是 class 的 name 名称；Attributes 属性可以是 properties 或 data 数据；behavior 行为通常是一个 function。例如我们建立一个银行系统程序：name: BankAccountattributes: accountNumber, balance, dateOpenedbehavior: open(), close(), deposit()一个 class 定义了某种 object 需要的属性和行为。但是它并不直接定义具体的属性值是多少，她只是一个框架的描述。当我们写好了一个 class，可以基于这个 class 来创建 objects，这个 object 就是 instance of class，就是 class 的实例。建立 class使用关键字 class 来建立 class，然后定义 class 名称，class 内容写在大括号{} 内。注意每个 class 结尾必须写分号;。例如：class BankAccount { };一个 class 的 attributes 和 behaviors 可以设置 access 访问级别。定义时使用关键词 public 不仅可以在 class 内部使用，也可以在 class 外部访问这个属性。也可以是使用关键词 private 或 protected，下面做详细介绍。建立一个 class：class BankAccount { public: void sayHi() { cout << "Hi" << endl; } };然后 instance 实例化这个 class：int main() { BankAccount test; test.sayHi(); }实例化的 object test 拥有其 class 所有的属性和行为。使用点分割符. 来访问 object 的各种属性和 function。抽象化数据抽象化的理念是给外部环境提供最核心的信息，而不用提供具体的细节。比如我们抽象化一本书，我们不用知道他具体有多少也，多少个字，什么颜色。我们只需要知道它是一本书就行了。抽象化的理念是面向对象编程最基础的模块。可以让我们建立一个 class 模型，然后基于这个模型创建具体的 objects 对象。封装encapsulation 封装意味着将一个整体包围起来，不仅仅是将其内容放在一起，也可以将其保护起来。它的设计原则就是让外部程序只能够访问其开放的元素，其他内容保持隐藏状态。例如我们上面的 BankAccount class，我们不想要外部直接访问修改 balance 余额属性，我们需要其使用 deposit() 或 withdraw() 方法来对其进行操作。所以我们需要将 balance 属性对外隐藏掉，只能通过内部 function 来访问。封装的优势有：控制内部数据的访问和修改代码更加灵活，方便后续根据情况修改修改一个地方，不影响其他地方下面举例说明如何使用封装来控制内部数据的可访问性，使用 public，private，protected 关键词。注意如果没有使用关键词，默认 class 内所有都是 private 类型的…

C++ 入门教程之四 -- Functions
type: item
date: 2020-11-20T01:38:00+00:00
description: 一个 function 是为了完成某个任务的许多命令的一个集合。使用 function 的优势有：代码复用独立测试某功能修改一个 function 内部代码不影响程序整体结构同一个 function 可使用不同传入参数调用一个有效的 c++ 程序至少要有一个 function：main()。返回类型mian() function 通常结构如下：int main() { // some code return 0; }function 的返回值类型定义在它的名称前，以上示例中，返回类型为 int 型，表明此 function 会返回一个 int 型数据。有些 function 在执行后不需返回数据，则使用 void 来定义。void 是一个基本数据类型，用来定义无值 valueless 申明。结构function 定义结构如下：return_type function_name( parameter list ) { body of the function }return-type: 返回值数据类型function name: function 名称parameters: 传递参数，当此function被调用时，传递数据给 function 内部使用。需要指定类型，名称及个数body of the function: 指令集合parameters 参数是可选的，当不需要传递参数时可以留空。使用我们定义一个无返回值类型的 function：void printSomething() { cout << "Hi there!"; }此 function 功能为输出一个字符串。我们在 main() 中调用此 function：int main() { printSomething(); return 0; }调用某个 function 只需要使用 function 名称及传入参数即可。注意 function 的申明和调用顺序，需要先申明然后调用，不然会报错：#include <iostream> using namespace std; void printSomething() { cout << "Hi there!"; } int main() { printSomething(); return 0; }一个 function 的申明告诉编译器这个 function 的名称及调用方法，其内容可以在后续代码中定义：#include <iostream> using namespace std; //Function declaration void printSomething(); int main() { printSomething(); return 0; } //Function definition void printSomething() { cout << "Hi there!"; }传递参数如果一个 function 需要传递数据，需要在申明定义正式的参数用来接收传递数据值。例如：void printSomething(int x) { cout << x; }以上定义一个 function 接收一个 int 整数然后输出这个整数。function 内的变量在调用此 function 时创建，在结束时清除。当一个带传递参数的 function 被定义后，在调用时需要传入对于数据类型的数据：#include <iostream> using namespace std; void printSomething(int x) { cout << x; } int main() { printSomething(42); } // Outputs 42以上示例将 42 传入 printSometing() 然后输出数据。在 function 内对传入参数数据的改变不会对影响到外部的数据。可以在调用时使用不同的传入数据：int main() { printSomething(42); printSomething(22); printSomething(36); }可以定义多个传递参数，使用逗号, 分隔，如：int addNumbers(int x, int y) { int result = x + y; return result; } int main() { cout << addNumbers(50, 25); // Outputs 75 }以上示例定义了两个传递参数，返回值为两个参数的和。rand() function创建一个随机整数是程序内常用的使用场景，可以调用 rand() function 获取随机整数。需要用到 <cstdlib> 库：#include <iostream> #include …

C++ 入门教程之三 -- 数据类型，数组及指针
type: item
date: 2020-11-19T07:54:00+00:00
description: 介绍数据类型操作系统根据数据类型分配内存空间，同一种数据类型才可以进行计算，如：合法的运算：55+15 // legal C++ expression 非法的运算：55 + "John" // illegal 数字类型的有两种类型：整型，如：7，10。浮点型，如：3.14，5.67。字符串是一个数字、字符、符号的集合。使用双引号来标记，如："hello"。字符使用单引号来标记，如：'A'。Boolean 类型只有两个可能的值：true 和 false。整型整型一般默认 4 个字节的存储空间。使用 int 来定义：int a = 42; 带符号型，可以定义正数和负数，一般不写默认 int 就是带符号型：signed int a; 无符号型，只能定义正数型：unsigned int a; short 型，一般默认空间占用大小，就是 2 个字节：short int a; long 型，2倍默认空间大小：long int a; 浮点型有三种浮点类型：float, double, 和 long double。浮点型都是 signed 带符号型的。一般情况下，float 型 4 个字节，double 型 8 个字节，long double 型 16 个字节。使用 float 来定义：float a = 3.14; 字符串string 字符串类型是一个字符、数字和符号的集合，使用双引号来标记。使用 string 定义变量，需要使用 <string> 库：#include <string> using namespace std; int main() { string test = "hello"; return 0; }注意：<string> 库包含在 <iostream> 库中，所以如果已经引用来 <iostream> 就不需要再引用 <string> 了。char 类型char 类型包含了 1 个字节的整数，但是解释器会将这个整数解释为 ASCII 码对应字符。使用单引号来标记，如：char a = 'a'; 变量定义规则所有变量的定义需要遵守以下规则：变量名首位必须是一个字母或下划线 _首字符之后的可以使用字母或数字，不能使用特殊字符或空格一般有两种常用的定义方法首字母及每个单词的首字符大写，如: BlankWord首字母小写后续的每个单词首字符大写，如：blankWord所有的变量名都是区分大小写的，如：helloword 和 HelloWord 是两个变量。数组数组是一个同一种数据类型变量的集合体，只需要一次就可以定义多个同一种变量类型的变量。如：int a[5] = {1, 2, 5, 7, 8}; 变量名后方括号[]定义数组内变量个数，大括号{}内包含数组内元素实际值，使用逗号,分隔各个元素。注意大括号内的元素个数不能超过数组变量定义的个数。也可以在定义时不说明元素个数：int a[] = {1, 3, 7, 9}; 会自动创建足够存储内部元素的数组。数组内每个元素都有一个 index 索引，从 0 开始，所以数组内第一个元素索引为 0，第二个为 1。使用索引号可以访问对应元素：int b[] = {11, 45, 62, 70, 88}; cout << b[0] << endl; // Outputs 11 cout<< b[3] << endl; // Outputs 70如果需要逐个访问数组内元素，可以使用 loop 循环来实现：int myArr[5]; for(int x=0; x<5; x++) { myArr[x] = 42; }多维数组以上是单维数组的定义，也可以定义多维数组，如定义一个二位数组：int a[3][4]; 想象这个数组为一个矩阵，有三行，每行 4 个元素：定义多维数组定义元素需要每行在一个大括号下单独定义：int x[2][3] = { {2, 3, 4}, // 1st row {8, 9, 10} // 2nd row };也可以在一行内定义：int x[2][3] = {{2, 3, 4}, {8, 9, 10}}; 使用行号和列号来定位一个元素：int x[2][3] = {{2, 3, 4}, {8, 9, 10}}; cout << x[0][2] << endl; //Outputs 4指针每个变量都存储在内存单元内，每个内存空间都有地址。这个内存地址可以使用地址查询符&来查询，如：int score = 5; cout << &score << endl; //Outputs "0x29fee8"返回的地址就是存储变量 score 的值的地址空间。一个 pointer 指针就是一个存储某个变量所在内存地址的变量。指针类型可以使某些任务实现更加方便，某些任务，如动态内存分配则必…

Speedtest CLI 命令行测速工具
type: item
date: 2020-11-16T05:08:00+00:00
description: speedtest 是国外很流行的测速平台，可以直接在其网站上测试本地上行下行带宽，最近看到其提供了本地命令行工具，使用起来更加方便了。官网：https://www.speedtest.net/安装首先需要到官网安装对应平台的 CLI 工具：https://www.speedtest.net/apps/cliLinux 类系统可以直接在使用 apt/brew 等包管理工具方便的安装，Windows 下需要手动下载 exe 可执行文件使用。macOS:brew tap teamookla/speedtest brew update brew install speedtest --forceubuntu:sudo apt-get install gnupg1 apt-transport-https dirmngr export INSTALL_KEY=379CE192D401AB61 sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys $INSTALL_KEY echo "deb https://ookla.bintray.com/debian generic main" | sudo tee /etc/apt/sources.list.d/speedtest.list sudo apt-get update sudo apt-get install speedtestwindows:首先在网站上下载可执行文件，点击下载 1.0.0 版。可执行文件为 speedtest.exe，需要将其目录加入系统 PATH 路径才可以直接在 terminal 调用，加入 PATH 方法参考我的教程：https://blog.niekun.net/archives/413.html使用在终端下使用 speedtest -h 命令查看帮助：Speedtest by Ookla is the official command line client for testing the speed and performance of your internet connection. Version: speedtest 1.0.0.2 Usage: speedtest [<options>] -h, --help Print usage information -V, --version Print version number -L, --servers List nearest servers -s, --server-id=# Specify a server from the server list using its id -I, --interface=ARG Attempt to bind to the specified interface when connecting to servers -i, --ip=ARG Attempt to bind to the specified IP address when connecting to servers -o, --host=ARG Specify a server, from the server list, using its host's fully qualified domain name -p, --progress=yes|no Enable or disable progress bar (Note: only available for 'human-readable' or 'json' and defaults to yes when interactive) -P, --precision=# Number of decimals to use (0-8, default=2) -f, --format=ARG Output format (see below for valid formats) -u, --unit[=ARG] Output unit for displaying speeds (Note: this is only applicable for ‘human-readable’ output format and the default unit is Mbps) -a Shortcut for [-u auto-decimal-bits] -A Shortcut for [-u auto-decimal-bytes] -b Shortcut for [-u auto-binary-bits] -B Shortcut for [-u auto-binary-byt…

两个子网间网络互访的原理
type: item
date: 2020-11-13T03:29:00+00:00
description: 之前我介绍过子网，子网掩码，网关等基本概念解析，可以参考：https://blog.niekun.net/archives/1885.html在不同子网下的设备是不能直接访问的，因为他们在不同的网段内，比如：路由 A 下有子网 192.168.1.0/24路由 B 下有子网 192.168.3.0/24设备 james：192.168.1.10/24 网关：192.168.1.1设备 Johnny：192.168.3.10/24 网关：192.168.3.1两个路由的 wan 网口在同一网段：192.168.2.0/24 下正常情况下路由 A 下的设备无法访问路由 B 下的设备，因为他们在不同的子网网段下。路由 A 有两个网段：192.168.1.0 和 192.168.2.0，所以它可以同时看见这两个网段的设备，但是它并不知道 192.168.3.0 这一网段的存在。同样的道理，路由 B 并不知道 192.168.1.0 这一网段的存在。当你给一个设备添加一个路由表 route table，就可以告诉它有其他网段可以进行访问，同时需要告诉它一个可以访问到这个新网段的网关，这样就可以访问到其他网段了。例如让路由 A 访问到路由 B 的子网 192.168.3.0 可以通过给路由 A 添加新的路由表来实现，使用 ip route 命令：ip route add 192.168.3.0/24 via 192.168.2.2以上命令的意义是添加路由 B 子网的路由表 192.168.3.0/24，网关地址是路由 B 的 wan 地址 192.168.2.2。因为路由 B 可以访问到两个网络 192.168.2.0 和 192.168.3.0，而路由 A 和路由 B 通过 192.168.2.0 互访，所以通过给路由 A 添加以上路由表然后网关地址为路由 B 和外部通信的地址，这样路由 A 就可以访问到路由 B 的子网了。现在路由 A 可以访问到路由 B 的子网了，那么路由 A 下的设备如何访问路由 B 的子网设备呢？原理是相同的，可以通过给设备添加路由 B 子网的路由表，然后由于路由 A 已经可以访问 路由 B 子网了，所以将网关地址设置为路由 A 地址即可：ip route add 192.168.3.0/24 via 192.168.1.1在路由 A 下的 James 执行以上命令后，就可以访问到路由 B 下的 Johnny 了。同样的原理，在路由 B 上设置路由表及网关就可以实现子网互访了。参考链接：understand the 'ip route' command for cisco routers