0%

Asp.Net Core是微软的一个跨平台高性能开源框架,用于生成连接到Internet的新式应用程序,使用core框架可以创建基于.net core或.NET Framework的Web应用程序和服务。

新建一个asp.net core项目

可以通过多种方式建立.net core项目,直接下载sdk并使用命令行建立新项目、使用visual studio或者visual studio code新建项目或者使用JetBrains的Rider新建项目。这里使用vs2017参考杨旭的课程新建项目。
首先需要在vs2017中配置aps.net core的开发环境,vs installer中简单操作就可以。然后新建一个项目,选择 Visual C# -> Web -> ASP.NET Core Web应用程序,更改位置、名称以及解决方案,点击确定。
新建项目的解决方案中包含Program.cs(程序入口)、Startup.cs(配置)、appsettings.json(配置信息)、launchSettings.json(启动信息)还有一些自动加载的依赖项。其中列出来的是在建立Web应用过程中非常有用的几个文件。
Program.cs文件
Program.cs中包含程序入口Main方法,在控制台启动时可以通过string[] args传入参数,Main方法中只含有一条语句,CreateWebHostBuilder(args).Build().Run(),该语句通过lambda传递args参数建立Web应用并启动该应用。


Startupcs文件
上图为Startup文件,Startup类中包含两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void ConfigureServices(IServiceCollection services) //注册接口
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, //配置管道和中间件或者MVC模型
IHostingEnvironment env,
IConfiguration configuration)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}

自定义的接口必须在ConfigureServices方法中注册后才能正常使用,args可以从json文件、环境变量、控制台运行参数等位置输入。

总结

因为我也是初学,这篇就是自己的学习笔记,逻辑还比较混乱,以后再改吧(捂脸)。

异步方法介绍

大多数编程语言都支持多进程、多线程编程,在Win窗体应用开发中,使用多线程开发可以大大提高程序性能,避免出现卡顿,C#中定义多线程略微有一些繁琐。在某个情况下,只是为某个简单方法定义一个线程有些浪费,这时就可以使用C#中的异步方法,让程序运行到这里时,自动定义一个线程去执行异步方法,程序员不需要为此多花费精力。异步方法在完成其工作之前即返回到调用该方法的地方,主进程可以继续执行,不需要等待异步方法执行完成。

定义一个异步方法

在C#中自定义异步方法,最简单的情况就是使用Task.Run()配合lambda表达式。定义异步方法时,其方法头需要用async标识出,并且在方法体中包含一个或多个await表达式,async告诉编译器该方法是异步方法,await表达式告诉编译器该行为异步完成的任务,异步方法的返回值必为三种返回类型:void、Task和Task,异步方法的参数可以是除out和ref外的任意类型,同时按约定,异步方法名末尾应该以Async结尾。

1
2
3
4
5
6
7
8
9
10
11
async void PrintDelayAsync(string str)
{
await Task.Run(() => PrintDelay(str));
}

private void PrintDelay(string str)
{
Thread.Sleep(3000);
Console.WriteLine("{0} in async method after 3 seconds.", str);
}
}

上述代码定义了名为PrintDelayAsync(string str)的方法,方法体中包含await表达式,调用了Task.Run()方法,并使用lambda表达式作为Task.Run()的参数,调用该异步方法,线程会停止3秒,然后在控制台输出一段文字。


1
2
3
4
5
6
7
8
9
10
11
12
static void Main(string[] args)
{
Program pp = new Program();
pp.PrintDelayAsync("Hello");
int n = 1;
while (n <= 10)
{
Console.WriteLine("{0} Hello in main thread.", n);
n++;
Thread.Sleep(500);
}
}

上述主程序将pp对象声明为Program类并初始化,进而可以通过调用Program中的异步方法,不会阻塞主程序执行。执行结果如图所示:
执行结果

总结

当某个方法存在阻塞程序执行的可能时,可以新定义一个线程或者使用异步方法;由于线程的创建和终值会消耗许多系统资源,因此在处理简单的耗时任务时,可以使用异步方法代替;异步方法体执行完成前就可以返回至调用方法,当然两者各有各的优势,应分情况使用。

泛型

一般使用的类声明中用到的类型都是特定类型,比如程序员自定义的类型,或者C#定义的类型。有些时候对于某个类型,我们希望这个类型能保存int类型的数据,同时也希望它能保存string类型的数据,如果没有泛型的概念,我们就需要定义两个非常相似的类,一个用来保存int,另一个用来保存string,这样可以满足我们的需求,但是会包含很多重复的工作:复制粘贴一遍int的类,然后将关键字int全部修改为string,这样很容易出错,而且在编译的时候,这两个类型都会进行编译,如果这样的类型较多,会使得生成的程序冗长。
C#中的泛型可以解决这个问题,我们可以使用泛型建立一个满足需求的模板,只需要在使用前声明其数据类型是int还是string,就可以正常使用。

定义一个泛型

定义泛型的时候,与自定义类很相似,只是需要加上泛型占位符,”T”可以是任意的。下面来定义一个简单泛型:

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
class Stack<T>
{
private T[] StackArray;
public int StackPointer = 0;
private const int MaxStack = 10;
public Stack()
{
StackArray = new T[MaxStack];
}
private bool IsFull
{
get { return (StackPointer >= MaxStack); }
}
private bool IsEmpty { get { return StackPointer <= 0; } }
public void push(T x)
{
if (!IsFull)
{
StackArray[StackPointer++] = x;
}
}
public T pop()
{
return (!IsEmpty)
? StackArray[StackPointer--]
: StackArray[0];
}
public void Print()
{
for (int i = StackPointer - 1; i >= 0; i--)
{
Console.WriteLine("Value: {0}",StackArray[i]);
}
}
}

上面的代码自定义了栈泛型,可以实现int、string、double等类型数据入栈出栈。
字段StackArray是T类型的数组,用来保存T类型数据,StackPointer是栈的指针,为当前数据的序数。StackArray类里面有两个返回bool值的方法,用来判断栈是否为空或满,以及入栈方法push()和出栈方法pop(),还有一个打印栈内所有数据的方法Print()。
接下来我们使用该泛型建立int和string的数据栈,将一系列数据压入,并打印栈中全部数据,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void Main(string[] args)
{
Stack<int> StackInt = new Stack<int>();
Stack<string> StackString = new Stack<string>();
StackInt.push(3);
StackInt.push(11);
StackInt.push(1);
StackInt.push(42);
StackInt.push(23);
StackInt.push(15);
StackInt.push(77);
StackInt.Print();
StackString.push("I'm Godric.");
StackString.push("And stupid!");
StackString.Print();
var intData = new PieceOfData<int>(10);
var stringData = new PieceOfData<string>("Godric");
Console.WriteLine("Value: {0}",intData.Data);
Console.WriteLine("Value: {0}",stringData.Data);
}

上述代码通过Stack泛型创建了StackInt实例和StackString实例,通过实例的push()方法将一系列数压入StackInt栈中,并将“I’m Godric。”和“And stupid!”压入StackString栈中,最后通过实例的Print()方法将栈中所有元素按与入栈相反的顺序打印到屏幕,运行结果如图:
运行结果

总结

C#的泛型可以将已经抽象过的面向对象类进行进一步的抽象,使得程序设计更加灵活,减少重复代码,尤其适用于类结构相似、字段类型不同的情况,可以大大提高编程速度并降低错误率。

简介

基于C#的Winform开发时,经常会涉及窗体间传值,比如配置某个参数,通常的方式是点击按钮弹出对话框,然后在TextBox中输入参数,点击确认后,关闭弹出对话框并将TextBox中参数传递给主界面,这里就涉及到两个窗体间传值

基于事件的窗体传值

这里展示一个简单的例子,新建一个名为PassArgs的窗体应用,默认窗体Form1作为主窗体,在其中拖入一个TextBox框和一个Button按钮,分别作为显示参数框和配置按钮,将TextBox的Multiline设为true。
主界面
创建一个新窗口作为弹出的配置窗口,右键PassArgs工程名,选择添加->新建项->Windows窗体,命名为conf.cs,点击确认
配置窗体
点击“配置”按钮后弹出conf窗体的设置非常简单,双击“配置”按钮,
在“private void button1_Click(object sender, EventArgs e)”内输入以下代码:

1
2
conf conf1 = new conf();
conf1.ShowDialog();

现在弹窗配置完成。


打算通过conf对话框中的两个TextBox向Form1传入两个参数,比如“Godric”和“Stupid”,然后显示在Form1的TextBox中
因为传递的是两个参数,但是EventHandler类只能传递一个参数,因此在Form1.cs中定义一个Args类,用来保存“Godric”和“Stupid”。Args类定义如下:

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
public class Args : EventArgs
{
private string name;
private string state;
public Args()
{

}
public Args(string theName, string theState)
{
name = theName;
state = theState;
}
public string getName()
{
return name;
}
public string getState()
{
return state;
}
public static void PassArgs(object o, Args e)
{
args1.name = e.name;
args1.state = e.state;
}
}

Args类中有两个字段name、state,两个构造函数Args()、Args(string theName, string theState),以及两个方法getName()、getState()和一个静态方法PassArgs(object o, Args e),PassArgs作为事件的注册函数,注册到接下来将要定义的事件中,PassArgs的定义中包含args1对象,使得PassArgs的扩展性变差,但是没有办法,事件传值只能有(object o, Args e)两个参数。
接下来定义conf.cs中的“确认”按钮,双击“确认”输入一下代码:

1
2
3
4
5
6
7
private void button1_Click(object sender, EventArgs e)
{
Form1.Args args1 = new Form1.Args(textBox1.Text,textBox2.Text);
confPass += Form1.Args.PassArgs;
confPass(this, args1);
Close();
}

其中confPass为事件名,需要添加在conf.cs的conf类最开始位置:

1
public event EventHandler<Form1.Args> confPass;

Form1.cs中的代码


conf.cs中的代码
下面是运行结果:


基于DialogResult传值

基于DialogResult传值方法比较简单,当弹出窗口conf的DialogResult属性设置为OK时,可以在主窗口中获得参数值。
和之前一样,仍旧定义Args类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Args
{
public Args() { }
public Args(string theName, string theState)
{
name = theName;
state = theState;
}
private string name;
private string state;

public string getName()
{
return name;
}
public string getState()
{
return state;
}
}

“配置”按钮的点击代码如下:

1
2
args = new Args(textBox1.Text,textBox2.Text);
DialogResult = DialogResult.OK;

在conf类中声明一个名为args的Args类,上面的代码第一句是将args实例化并通过Args类的第二个构造函数将TextBox.Text和textBox2.Text赋值给args,第二行代码将conf1的DialogResult属性更改为OK状态,然后关闭对话框。
在conf1.cs中需要定义属性argsPass:

1
2
3
4
5
public Args argsPass
{
set {}
get { return args; }
}

C#中属性的使用和对象中的方法非常类似,属性看起来非常像写入或读取一个字段,语法相同,属性和字段不同的是,字段会为数据分配内存地址,属性不会为数据分配内存地址。上面的代码定义了名为argsPass的Args类型的属性,set访问器为空,get访问器返回预先声明的Args类型的args对象。
form1中的“配置”按钮代码如下:

1
2
3
4
5
6
7
conf conf1 = new conf();
conf1.ShowDialog();
if (conf1.DialogResult == DialogResult.OK)
{
listBox1.Items.Add(conf1.argsPass.getName());
listBox1.Items.Add(conf1.argsPass.getState());
}

其中if判断conf1的DialogResult属性是否为OK,如果是,将conf1的argsPass属性传出的Args对象在listBox1中分行显示。

总结

从上面两个例子可以看出,相比于DialogResult传值,事件传值过程比较复杂,而且逻辑难于梳理,但是事件作为C#中的一个重要特性,仍然有必要掌握。

回调函数简介

C#回调函数用处非常广泛,最近在做一个图像检测项目,用的是海康威视的相机,C#编写Server/Client,相机SDK中对于实时码流处理都是借助回调函数,因此不好好研究一下回调函数,简直是没法工作(捂脸)。
网上有好多帖子介绍回调函数的,最简单的一种说法是:
回调函数是通过函数指针调用的函数,就是将函数作为参数传递给要执行的函数,作为参数的这个函数就是回调函数


回调函数的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SDK
{
public delegate void CallBack(string RealData);
private string videoStream_1 = "I am the video stream 1 and palying.";
private string videoStream_2 = "I am the video stream 2 and playing.";

public bool RealDataStream(int userID, CallBack callBack)
{
if (userID == 1)
{
callBack(videoStream_1);
return true;
}
else if (userID == 2)
{
callBack(videoStream_2);
return true;
}
else
{
return false;
}
}
}

上面的代码是假设的相机SDK包,这个SDK类里面有两个字段videoStream_1和videoStream_2以及一个回调函数类定义CallBack,SDK类中还包含有视频取流的方法RealDateStream,这个方法需要传入两个参数,一个是用户ID,另一个就是回调函数,如果视频回调成功,会返回true,如果失败,返回false。


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
class Program
{
static void Main(string[] args)
{
int userID_1 = 1;
int userID_2 = 2;
int userID_3 = 3;
SDK sdk = new SDK();
Program pp = new Program();

if (!sdk.RealDataStream(userID_1, pp.callbackFunction))
{
Console.WriteLine("Some error happened.");
}
if (!sdk.RealDataStream(userID_2, pp.callbackFunction))
{
Console.WriteLine("Some error happened.");
}
if (!sdk.RealDataStream(userID_3, pp.callbackFunction))
{
Console.WriteLine("Some error happened.");
}
}

private void callbackFunction(string stream)
{
Console.WriteLine(stream);
}
}

上面的代码是主程序,Program类中定义有回调函数callbackFunction,该回调函数需要按照SDK类中的回调函数public delegate void CallBack(string RealData)来定义,其中对string类型stream的实现非常简单:只是在控制台中打印stream(模拟处理实时视频流)。
Main函数中定义了三个字段userID-1,2,3,对于ID=1,2,SDK类中会输出相应的“视频流”,但是对于ID=3时,没有对应的“视频流”,因此会报错。代码的运行结果如下:
运行结果


总结

实际二次开发时,SDK类会封装起来,二次开发时无法看到SDK类的实现方式,只会告诉二次开发的程序员SDK类的接口,二次开发的程序员可以通过自己编写回调函数,来实现自己想要达到的目的。

委托

可以认为委托是拥有一个或多个方法的对象,这里的对象和面向对象编程中的对象略有区别,正常情况下是不能“执行”一个对象,但是委托与典型的对象不同。可以“执行”委托,此时委托会“执行”它包含的所有方法。

声明委托

声明委托就像是在头文件中声明一个函数头,只不过需要在返回类型前面加上关键词”delegate”

1
2
delegate void MyDel(int a, int b);
MyDel myDelegate;

上面就是声明了一个委托类型MyDel,该委托类型需要传入两个整型参数a、b,并且会返回void,第二行代码创建了myDelegate实例。


接下来做一个简单演示,定义一个委托和一个myClass类,其中包含sumTwoNum方法,将该方法赋给委托,通过委托来进行计算两个数的和;

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
using System;

namespace MyDelegate
{
delegate int MyDel();

class Program
{
static void Main(string[] args)
{
myClass m1 = new myClass(1,2);
MyDel myDelegate = new MyDel(m1.sumTwoNum);
Console.WriteLine(myDelegate());

}

}
public class myClass
{
private int num1;
private int num2;
public int sumTwoNum()
{
return num1+num2;
}
public myClass(int a, int b)
{
num1 = a;
num2 = b;
}
}

}

上面的代码中,定义了一个myClass类,其中包含两个私有整型变量num1、num2,及一个sunTwoNum的求和方法;
将myDelegate实例化,并且将m1.sumTwoNum赋给myDelegate委托,这样执行myDelegate()后,就可在控制台输出1+2的结果。

1
2
3
myDelegate += class.method2;
myDelegate += class.method3;
...

上面的代码展示了给myDelegate增加更多的实例方法,通过”+=”赋值,也可以将静态方法赋给委托对象,这样在执行委托时,就会执行其中“拥有”的全部方法。


事件

程序运行时常常会有这样的需求,就是在某个特定事件发生时,程序的其他部分可以得到该事件已经发生的通知,事件就可以满足这种需求。
发布者类定义了一些程序其他类可能感兴趣的事件,其他类就可以注册这些事件,当发布者类的定义事件发生时,已经注册的类就可以获得通知,事件触发后执行的方法称为回调方法,也可以称为事件处理程序。
为什么要将委托和事件一起来介绍,就是因为事件就像是专门用于特殊途径的简单委托。

通过事件传递参数的例子

这个例子中包含一个Args类,该类是用来保存需要从发布者传递到订阅者的信息;当然还需要一个publisher类,publisher中有RunInPub方法,从方法名中就可以看出,这个方法是在publisher的实例中的方法,当publisher的实例运行该方法,就会将Args类的实例信息发送出去;最后还需要一个subscriber类,subscriber类中包含EventForPublisher方法,从该方法的名字可以看出,这个方法是订阅者subscriber在publisher注册的方法。
以下为Args类的定义

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
public class Args : EventArgs
{
private string name;
private int age;
private bool isStupid;

public Args(string theName, int theAge, bool stupidState)
{
name = theName;
age = theAge;
isStupid = stupidState;
}
private string boolToString(bool state)
{
if (state)
{
return "stupid";
}
else
{
return "not stupid";
}
}
public string[] GetArgs()
{
string[] argsArray = new string[3];
argsArray[0] = name;
argsArray[1] = age.ToString();
argsArray[2] = boolToString(isStupid);
return argsArray;
}
}

可以看出Args类是继承于EventArgs类,EventArgs类是专门用来在事件的发布者与订阅者之间传递参数的类,Args类中包含三个字段name、age和isStupid,为了数据安全,三个字段均设为private;一个Args的构造函数,用于初始化Args的实例;一个boolToString方法,用于将bool类型isStupid转化为字符串类型;一个GetArgs()函数,返回包含私有字段的字符转数组。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class publisher
{
public event EventHandler<Args> Event;
Args godricArgs = new Args("godric",30,true);
public publisher(subscriber sub)
{
this.Event += sub.EventForPublisher;
}

public void RunInPub()
{
Event(this, godricArgs);
}
}

以上为publisher类,该类中通过:

1
public event EventHandler<Args> Event;

语句,声明了Event事件,该事件是通过泛型委托EventHandler自定义的类注册的;在publisher的构造函数中,将sub实例的EventForPublisher方法“赋值”给Event事件,意思就是Event事件中现在有了一个方法,这个方法就是EventForPublisher;在RunInPub方法中,通过执行Eevnt(this, godricArgs),将godricArgs中的信息发布出去


1
2
3
4
5
6
7
8
9
public class subscriber
{
public void EventForPublisher(object o, Args args)
{

Console.WriteLine("The name is "+args.GetArgs()[0]+
", the age is "+args.GetArgs()[1]+" and he is "+args.GetArgs()[2]+".");
}
}

以上为subscriber类,该类中只包含有一个EventForPublisher方法,从名字也可以看出,这个方法就是subscriber的实例注册到事件Event中的方法,该方法执行后,会将args传来的数组中的信息显示出来。
以上的代码组合起来,就实现了从publisher类中将godricArgs信息传递出去,并且传递的方法是在subscriber中定义的。
事件运行结果
最后,我努力来打个比方,事件的发布和订阅就好比是,发布者好比是西餐厅,订阅者就好比顾客,西餐厅可以提供各种服务,但是具体是什么服务,需要顾客来”订阅(this.Event += sub.EventForPublisher)”,比如顾客说要一份牛排并且三分熟(void EventForPublisher(object o, Args args)),餐厅就会按要求(void EventForPublisher(object o, Args args))把牛排做好(执行Event(this, godricArgs)),我现在也就只能理解到这个程度了,以后如果有新的想法,在添加吧。

C++动态链接库介绍

Dynamic Link Library(DLL)是微软在Windows系统中实现共享函数库概念的一种方式,常见的后缀为“.dll”,其优点是不会包含在程序编译文件中,而是在运行过程中调用,并且可以被多个程序公用,不需要在每个程序中都附加上相同的代码。


生成C++动态链接库

我的运行环境是Win10+VS2017,打开vs2017后,新建空的C++项目
假设我要创建一个名为math1.dll(因为C++本身就有一个叫math.h的数学方法库,不能与其重名,所以加了数字1)的动态链接库,并且包含在GodricDll的命名空间中,编写一个math1的类,其中包含一个构造函数math1,可以初始化该类的实例,包含一个求两个数和的方法sum,以及两个私有变量a和b。
首先在资源管理器的源文件处新建名为math1.cpp的空白源文件,在头文件处添加名为math1.h的空白头文件。
资源管理器
头文件的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#endif
#ifndef MATH1_H
#define MATH1_H
namespace godricdll
{
class MATH1_API math1
{
public:
math1(int theA, int theB);
int sum();
private:
int a;
int b;
};
}
#endif

头文件math.h中从#ifndef MATH1_H到最后的#endif,是为了防止头文件被重复加载,#ifndef MATH1_API是为了防止宏重复定义,可以看到代码中间部分包括在godricdll命名空间中,定义了math1类。
源文件包含类的实现,其内容如下:

1
2
3
4
5
6
7
8
9
10
11
#include "math1.h"//包含math.h头文件
#define MATH1_API _declspec(dllexport)
using namespace godricdll;//使用godricdll命名空间
MATH1_API math1::math1(int theA, int theB):a(theA), b(theB)//构造函数的实现
{

}
MATH1_API int math1::sum()
{
return a + b;
}

头文件和源文件已经编写完成,设置好debug或者release,以及32或64位,还需要将生成类型由.exe转为.dll,右键点击myDll项目,打开属性,配置属性->常规->项目默认值->配置类型,将配置类型更改为”动态库(.dll)”。
这样就可以生成解决方案了,生成完成后可以在debug或release中找到以下文件:
生成的文件

然后新建一个项目叫importDll,可以是控制台程序,在源文件出添加importDll.cpp文件,其源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <math1.h>
using namespace godricdll;
using namespace std;

int main()
{
math1 m1(4,5);
cout << m1.sum()<<endl;
int x;
cin>> x;
return 0;
}

当然还需做一些配置将math1.h、math1.lib及math1.dll放到正确的文件夹,来调用math1.dll。
右键importDll,选择”属性”
配置属性->VC++目录->包含目录(包含放置math1.h文件的文件夹)和库目录(包含math1.lib文件的文件夹);
配置属性->链接器->输入->附加依赖项(包含math1.lib)
将math1.dll文件放入importDll的debug或者release文件夹中
至此配置结束,可以运行importDll了
importDll源码及运行结果
上面就是importDll源码及运行结果。

引言

很早以前就想搭建个人网站,但都没有付诸行动,现在终于开始着手做了,本来想一步到位搭建一个动态网站,但是发现自己建网站知识太欠缺,退而求其次搭建了静态网站,本文介绍用hexo+nginx来搭建个人博客。

hexo

在hexo官网上的介绍如下:


Hexo was originally created and maintained by Tommy Chen in 2012. Since then, it has helped thousands of people to build their dream website/blog.


hexo是基于node.js的一个静态网站框架,直接在搜索引擎中搜索Hexo即可找到官网以及中文官网,介绍安装方法的博客非常多,

nginx

搭建hexo+nginx环境

起初我是跟随B站up主CodeSheep的视频搭建的,有兴趣的可以直接去B站搜索,但是这个视频介绍的方法是,在本地写好博客内容后,同步至github,CodeSheep的初衷是非常好的,github免费、简单。唯一的问题就是,github服务器在国外,网速不确定,时好时坏,访问起来非常蛋疼,有时候2分钟都刷不出网页来,因此我就决定购买华为云主机,并注册域名来部署访问我的博客。


购买好华为云之后,通过ssh远程连接系统,首先需要安装git工具(ubuntu下)

1
apt-get install git

然后安装nginx服务

1
apt-get install nginx

创建新用户

1
adduser git

创建git仓库,并初始化myblog.git为空目录

1
2
3
mkdir /var/www
cd /var/www
git init --bare myblog.git

创建静态网站目录

1
mkdir /var/www/hexo

配置git hooks的post-receive文件

1
vim /var/www/myblog.git/hooks/post-receive

添加一下内容:

1
2
#!/bin/sh
git --work-tree=/var/www/hexo --git-dir=/var/www/myblog.git checkout -f

保存并退出后,修改设置权限:

1
chmod +x /var/www/myblog.git/hooks/post-receive

并修改myblog.git目录的所有者:

1
chown -R git:git myblog.git

将git仓库与之前创建的静态网站目录链接,并配置权限:

1
2
chown -R git:git /var/www/hexo
chmod -R 775 /var/www/hexo

打开本地的hexo目录,编辑_config.yml文件deploy部分

1
2
3
4
depoly:
type: git
repository: git@ip或域名:/var/www/myblog.git
branch: master

nginx配置
让nginx将端口或域名指向hexo静态文件目录

1
vim /etc/nginx/sites-available/default

并修改一下内容

1
2
3
4
5
6
7
8
9
10
11
server {
listen 80;
listen [::]:80;
root /var/www/hexo; # 修改的地方
server_name godricguo.top www.godricguo.top; #如果需要改域名访问,修改server_name 为域名便可
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
}

这样重新启动nginx服务,就可以在浏览器中访问自己的博客了。

推荐一本书–C++入门经典

C++已经流行多年,问别人该学什么编程语言,基本没有推荐C++的;现在刚入门机器视觉,也在一开始听着网上老司机学Python,Python学得一知半解,如果说只是写个算法来达到某种目标,Python学Python,Python学得一知半解,如果说只是写个算法来达到某种目标,Python+OpenCV肯定是能行的,但是不同项目可能有这样或那样的要求,如果只会一点点Python,确实有点缺乏说服力,所以就转头开始学习C++。
学习C++的时候,找到一本大牛写的书–C++入门经典by Walter Savitch,通俗易懂,而且讲得都是重要的知识点,不会在某些晦涩深奥的地方浪费半点笔墨。而且这本在前言中有各章节的依赖关系,如果想针对某个方面学习,可以按照依赖图跳着学,这样效率非常高。
C++入门经典


C++中的基础链表

不知道其他语言是怎么样的,C++的链表是靠指针来实现的,链表是由结构或类的节点链接起来构成,在结构或类中,定义所需要的数据变量以及指向这个节点的指针变量,初始化指针后,通过指针进行访问当前节点的数据变量,或者通过指针链接到下一个节点,继而访问下一个节点的数据变量。

1
2
3
4
5
6
7
struct ListNode
{
string item;
int count;
ListNode *link;
}
typedef ListNode *ListNodePtr

ListNode结构类型中定义了字符串变量item、整型变量count以及指针类型link,可以看出link指针是循环定义的,C++中支持这种循环定义。

1
2
3
ListNodePtr head;
head = new ListNode;
(*head).count = 12;

这块代码先是将head声明为ListNodePtr指针类型,然后初始化,再对head指向的节点中的count变量赋值为12。

1
head->count = 12;

*加.的赋值方式难免有些难懂,c++中支持箭头操作符”->”,这样就非常清晰了,将head指向的节点中count变量赋值为12。
接下来给ListNode中的item变量赋值:

1
head->item = "bagels"

这样第一个节点就完成了,如果用图片表示,就是下图这样:
ListNode第一个节点
link之所以指向”?”,是因为还没有给link指定下一个节点,通过以下语句:

1
head->link = anotherNode;

就可以将第一个节点head链接到第二个节点anotherNode(这里假设还有一个节点为anotherNode)
当然也可以写一个插入节点的函数:

1
2
3
4
5
6
7
8
void insert(ListNodePtr head, int theNumber, string theString)
{
listNodePtr tmpNode = new ListNode;
tmpNode->count = theNumber;
tmpNode->item = theString;
tmpNode->link = head;
head = tmpNode;
}

insert函数需要传入的参数有:需要添加节点的链表头名称head、赋给count变量的整型值以及赋给item变量的字符串,通过调用insert函数,可以在现有的head表头前添加一个节点,并将新节点的指针赋值给head。如图所示:
两个节点
重复下去,就可以获得包含若干个节点的链表。
图中那个”?”号仍旧非常扎眼,实际上可以将链表的最末尾的link赋值为NULL或nullptr,这就是告诉c++,之后再没有节点了。

C++中其他形式的链表

除了上面介绍的链表以外,通过指针和struct或类数据类型相结合,还可以设计出更为复杂的链表,比如说双向链表或者二叉树

双向链表

1
2
3
4
5
6
struct Node
{
int data;
Node *forwardLink;
Node *backLink;
}

上面一段代码定义了一个双向链表Node,其中有一个整型变量data,和两个Node型指针变量,这样就可以通过forwardLink和backLink来进行节点的前后移动,达到双向的目的。

二叉树

1
2
3
4
5
6
struct TreeNode
{
int data;
TreeNode *leftLink;
TreeNode *rightLink;
}

上面的代码同样定义了二叉树链表TreeNode,仍旧有一个整型变量data和两个指针型变量,但是指针型变量leftLink和rightLink指向左右方向,这样就构建了树形链表结构。

类构成的链表

除了用结构类型来定义链表外,还可以用类类型来定义链表。

1
2
3
4
5
6
7
8
9
10
11
class Node
{
public:
Node();//类的构造函数
Node(int value, Node *next);//类的构造函数
int getData();//类的成员函数,获取类的值并返回
void setData(int value);//类的成员函数,将value赋给类的成员变量
void setLink(Node *next);//类的成员函数,更改对下一个节点的引用
private:
int data;//类的成员变量
Node *link;//类的指针,指向下一个节点

上面的Node类即为链表的一个节点,多个节点相连就能组成一个链表。


总结

C++的指针优点十分明显,指针可以直接访问内存中的数据,将指针和结构或者类相结合,可以实现各种强大的数据结构,这是其他编程语言不能比拟的;但是缺点也很明显,C++中指针使用非常复杂,通过指针创建的动态数据,需要在使用过后及时销毁,此过程必须程序员手动进行,否则会出现内存溢出问题,如果两个指针指向同一个内存地址,将其中一个指针销毁后,如果再次调用另一个指针,程序同样会崩溃,因此内存管理非常复杂,正印了那句话,成也指针,败也指针!

Markdown基本语法


一、标题

标题是在标题文字前加若干个”#”符合来生成的,几个”#”就是几级标题;


二、字体

加粗是用两个”*”将需要加粗的文字包围,加粗
斜体是用一个”*”将需要倾斜的文字包围,倾斜
倾斜加粗是三个”*”,倾斜加粗
删除线是用两个”“,
~
删除线~~


三、引用

一个”>”,两个”>>”,或者无限多个”>>>>…>>>>”

https://www.jianshu.com/p/191d1e21f7ed