C++(2)- 静态成员函数

在 C++ 中,静态成员函数(static member function)是与类相关联而不是与类的任何特定对象相关联的函数。静态成员函数有以下几个特点:

1. 声明和定义

静态成员函数使用 static 关键字声明。它们可以访问类的静态成员,但不能直接访问非静态成员(因为非静态成员属于具体的对象,而静态成员函数没有对象实例)。

声明

在类声明中使用 static 关键字声明静态成员函数:

1
2
3
4
class MyClass {
public:
static void myStaticFunction();
};

定义

在类外部定义静态成员函数时,不需要再次使用 static 关键字:

1
2
3
void MyClass::myStaticFunction() {
// 函数体
}

2. 访问静态成员函数

静态成员函数可以通过类名直接调用,也可以通过对象调用,但推荐使用类名调用以明确其静态特性。

1
2
3
MyClass::myStaticFunction();  // 通过类名调用
MyClass obj;
obj.myStaticFunction(); // 通过对象调用

3. 静态成员函数的特点

  • 无隐式 this 指针:静态成员函数没有隐式的 this 指针,因此不能直接访问非静态成员变量和非静态成员函数。
  • 共享数据:静态成员函数可以访问类的静态成员变量,这些变量在所有对象之间共享。
  • 全局作用域:静态成员函数在类的作用域内,但可以看作是全局函数,因为它们不依赖于任何特定的对象实例。

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
#include <iostream>
#include <string>

class MyClass {
public:
static int objectCount; // 静态成员变量

MyClass() {
++objectCount; // 每创建一个对象,计数加1
}

~MyClass() {
--objectCount; // 每销毁一个对象,计数减1
}

static void printObjectCount() {
std::cout << "Number of objects: " << objectCount << std::endl;
}
};

// 初始化静态成员变量
int MyClass::objectCount = 0;

int main() {
MyClass::printObjectCount(); // 输出: Number of objects: 0

{
MyClass obj1;
MyClass obj2;
MyClass::printObjectCount(); // 输出: Number of objects: 2
}

MyClass::printObjectCount(); // 输出: Number of objects: 0

return 0;
}

解释

  1. 静态成员变量objectCount 是一个静态成员变量,它在所有 MyClass 对象之间共享。
  2. 构造函数和析构函数:在构造函数中增加 objectCount,在析构函数中减少 objectCount
  3. 静态成员函数printObjectCount 是一个静态成员函数,它可以访问 objectCount 并打印当前的对象数量。
  4. 调用静态成员函数:通过类名 MyClass::printObjectCount() 调用静态成员函数,也可以通过对象调用,但推荐使用类名调用。

5. 静态成员函数的用途

  • 工具函数:静态成员函数可以用于实现与类相关的工具函数,这些函数不依赖于具体的对象实例。
  • 工厂方法:静态成员函数可以用作工厂方法,用于创建类的实例。
  • 全局状态管理:静态成员函数可以用于管理类的全局状态,例如计数器、配置选项等。

C++(1)- 虚函数

在 C++ 中,虚函数(virtual function)是实现多态的一种机制。通过虚函数,可以在派生类中重写(override)基类中的函数,从而在运行时根据对象的实际类型调用相应的函数。以下是关于 C++ 中虚函数的详细介绍和示例。

1. 基本概念

  • 虚函数:在基类中声明为 virtual 的成员函数。虚函数允许派生类重写该函数,从而实现多态。
  • 纯虚函数:在基类中声明为 virtual 并且没有实现的函数,形式为 virtual 返回类型 函数名() = 0;。含有纯虚函数的类称为抽象类,不能实例化。
  • 多态:通过基类指针或引用调用虚函数时,实际调用的是派生类中重写的函数,而不是基类中的函数。

2. 虚函数的声明和使用

声明虚函数

在基类中声明虚函数:

1
2
3
4
5
6
class Base {
public:
virtual void display() {
std::cout << "Base class display function" << std::endl;
}
};

派生类中重写虚函数

在派生类中重写基类的虚函数:

1
2
3
4
5
6
class Derived : public Base {
public:
void display() override {
std::cout << "Derived class display function" << std::endl;
}
};

3. 多态的实现

通过基类指针或引用调用虚函数:

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
#include <iostream>

class Base {
public:
virtual void display() {
std::cout << "Base class display function" << std::endl;
}
};

class Derived : public Base {
public:
void display() override {
std::cout << "Derived class display function" << std::endl;
}
};

int main() {
Base baseObj;
Derived derivedObj;

Base* basePtr = &baseObj;
Base* derivedPtr = &derivedObj;

basePtr->display(); // 输出: Base class display function
derivedPtr->display(); // 输出: Derived class display function

return 0;
}

4. 纯虚函数和抽象类

声明纯虚函数

在基类中声明纯虚函数:

1
2
3
4
class Base {
public:
virtual void display() = 0; // 纯虚函数
};

派生类中实现纯虚函数

在派生类中实现基类的纯虚函数:

1
2
3
4
5
6
class Derived : public Base {
public:
void display() override {
std::cout << "Derived class display function" << std::endl;
}
};

5. 构造函数和析构函数中的虚函数

  • 构造函数:虚函数在构造函数中不具有多态性,即在构造函数中调用虚函数时,只会调用当前类的虚函数,不会调用派生类的虚函数。
  • 析构函数:虚函数在析构函数中具有多态性,因此基类的析构函数通常声明为虚函数,以确保派生类的析构函数也能被调用。
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
#include <iostream>

class Base {
public:
virtual ~Base() {} // 虚析构函数
virtual void display() {
std::cout << "Base class display function" << std::endl;
}
};

class Derived : public Base {
public:
~Derived() {} // 派生类的析构函数
void display() override {
std::cout << "Derived class display function" << std::endl;
}
};

int main() {
Base* basePtr = new Derived();
basePtr->display(); // 输出: Derived class display function
delete basePtr; // 调用 Derived 的析构函数

return 0;
}

6. 虚函数表(VTable)

C++ 编译器在内部使用虚函数表(Virtual Table, VTable)来实现虚函数的多态调用。每个包含虚函数的类都有一个虚函数表,表中存放了该类所有虚函数的地址。每个对象都有一个指向其类的虚函数表的指针(vptr),通过 vptr 可以找到对应的虚函数地址。

python(5) subprocess and logging

subprocess

You can use the Python subprocess module to create new processes, connect to their input and output, and retrieve their return codes and/or output of the process.

subprocess run

The subprocess.run() method is a convenient way to run a subprocess and wait for it to complete. Once the subprocess is started, the run() method blocks until the subprocess completes and returns a CompletedProcess object, which contains the return code and output of the subprocess.The check argument is an optional argument of the subprocess.run() function in the Python subprocess module. It is a boolean value that controls whether the function should check the return code of the command being run.When check is set to True, the function will check the return code of the command and raise a CalledProcessError exception if the return code is non-zero. The exception will have the return code, stdout, stderr, and command as attributes.

Note that when there is “&” at the end in command, the run will not wait the process to end. When using eval$command in shell, it’s the same. Don’t use &.

subprocess Popen

subprocess.Popen is a lower-level interface to running subprocesses, while subprocess.run is a higher-level wrapper around Popen that is intended to be more convenient to use. Popen allows you to start a new process and interact with its standard input, output, and error streams. It returns a handle to the running process that can be used to wait for the process to complete, check its return code, or terminate it.
In general, you should use run if you just need to run a command and capture its output and Popen if you need more control over the process, such as interacting with its input and output streams.The Popen class has several methods that allow you to interact with the process, such as communicate(), poll(), wait(), terminate(), and kill().

subprocess call

subprocess.call() is a function in the Python subprocess module that is used to run a command in a separate process and wait for it to complete. It returns the return code of the command, which is zero if the command was successful, and non-zero if it failed.subprocess.call() is useful when you want to run a command and check the return code, but do not need to capture the output.

subprocess check_output

check_output is a function in the subprocess module that is similar to run(), but it only returns the standard output of the command, and raises a CalledProcessError exception if the return code is non-zero.

Subprocess Pipe

A pipe is a unidirectional communication channel that connects one process’s standard output to another’s standard input. A pipe can connect the output of one command to the input of another, allowing the output of the first command to be used as input to the second command.Pipes can be created using the subprocess module with the Popen class by specifying the stdout or stdin argument as subprocess.PIPE.

logging

Logging provides a set of convenience functions for simple logging usage. These are debug(), info(), warning(), error() and critical().
The default level is WARNING, which means that only events of this level and above will be tracked, unless the logging package is configured to do otherwise.

logging config

logging.basicConfig(format=’%(levelname)s %(asctime)s %(process)d %(message)s’, level=logging.DEBUG)
If not printing after config, note that you should config this before importing other libraries incase the config is overriden.

reference

https://www.datacamp.com/tutorial/python-subprocess
https://docs.python.org/3/howto/logging.html

java(2)- 集合框架

集合框架

java集合框架包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。

Collection

Collection下包括Set、List、Queue,各自又包含了使用不同方式的实现。

Set

Set下包括TreeSet,HashSet,LinkedHashSet。其中TreeSet基于红黑树实现。

python(4)-多进程

协程、线程与进程

协程、线程和进程是计算机编程中常用的并发编程概念。总的来说,协程适合于高并发、I/O 密集型的场景,可以减少线程切换的开销;线程适合于 CPU 密集型和需要实时响应的任务;而进程则适合于独立性强、资源隔离要求高的任务。在实际应用中,通常会根据任务的特点和需求选择合适的并发编程模型。

协程

协程是一种程序组件,类似于线程,但其执行过程是可中断的。
在协程中,执行可以在特定的位置暂停,并在需要时恢复。
协程通常在单个线程中运行,因此不涉及线程切换的开销,可以有效地利用系统资源。

线程

线程是操作系统能够进行运算调度的最小单位。
一个进程中可以包含多个线程,它们共享进程的内存空间和其他资源。
多线程编程允许程序同时执行多个任务,提高了程序的并发性和响应性。
线程之间的切换开销相对较小,但线程间的共享资源需要进行同步,以避免竞态条件和死锁等问题

进程

进程是程序执行时的一个实例,每个进程都有自己独立的内存空间和系统资源。
进程间相互独立,各自拥有独立的地址空间和资源,彼此之间通信需要特殊的机制。
多进程编程可以充分利用多核处理器,但进程间的切换开销相对较大,因为需要切换地址空间和资源上下文。

python如何使用协程、线程与进程

在Python中,可以使用不同的工具来实现协程、线程和进程。
在Python中,协程通常使用 asyncio 库来实现。 线程可以使用内置的 threading 模块来实现。进程可以使用 multiprocessing 模块来实现。
需要注意的是,在Python中,由于全局解释器锁(GIL)的存在,多线程并不能有效利用多核处理器。因此,如果需要充分利用多核处理器,可以考虑使用多进程。而协程则是在单个线程中实现并发的一种方式,适合于I/O密集型任务。

python(3)-matplotlib绘图

安装

1
2
pip install matplotlib

简单例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import matplotlib.pyplot as plt

# 数据
x = [1, 2, 3, 4, 5]
y = [2, 3, 5, 7, 11]

# 绘图
plt.plot(x, y)

# 添加标题和标签
plt.title('Simple Line Plot')
plt.xlabel('X-axis')
plt.ylabel('Y-axis')

# 显示图形
plt.show()

更多例子

多个子图

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
import matplotlib.pyplot as plt
import numpy as np

# 数据
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)

# 创建一个包含两个子图的图形
fig, (ax1, ax2) = plt.subplots(2, 1)

# 在第一个子图中绘制正弦函数
ax1.plot(x, y1, 'r-')
ax1.set_title('Sine Function')
ax1.set_xlabel('X-axis')
ax1.set_ylabel('Y-axis')

# 在第二个子图中绘制余弦函数
ax2.plot(x, y2, 'g--')
ax2.set_title('Cosine Function')
ax2.set_xlabel('X-axis')
ax2.set_ylabel('Y-axis')

# 设置图形的整体标题
plt.suptitle('Sine and Cosine Functions')

# 自定义图形的风格
plt.style.use('ggplot')

# 调整子图的间距
plt.tight_layout()

# 显示图形
plt.show()

python(1)-语法进阶

yield

yield可以暂停一个函数的运行,返回值给函数调用者,并使得函数可以从上次离开的地方接着运行。通常我们可以借助yield来实现一个生成器。

生成器

生成器是一个可以产生一个序列的函数,调用生成器函数会产生一个生成器对象,并不会开始启动函数,只有当执行__next__()时函数才会执行。生成器时一个一次性操作,和我们常见的列表、字典等可以迭代的对象不同,列表等是可以无限次迭代的。

装饰器

python中函数是一等对象,所以函数可以像普通变量一样当作参数传给另一个函数的,装饰器可以在不改变另一个函数的情况下用来封装另一个函数以拓展这个被封装函数的功能,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。

装饰器不仅可以是函数,也可以是类。使用类装饰器主要依靠类的__call__方法。我们可以直接定义一个装饰器函数或者装饰器类,但是有个缺点是原函数的元信息不见了,比如函数的docstring__name__都会发生改变,此时我们可以使用functools.wrapswraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器里面的函数中。

java(1)-面向对象

类与对象

类就是用户定义好的原型,可以通过类创建对象,类中定义好了一系列的属性或者是函数。对象是真正的实体。当为一个类创建了对象,也可以说是实例化了这个类,java中有几种创建类的方式。

当只是简单地声明一个类变量时,如Object a,不同于原始变量int、double等声明变量时就分配好了内存,这样的声明方式并没有创建好一个对象,需要通过new关键字来触发类构造器,并为这个对象分配好内存。所有类都至少有一个构造函数,如果没有定义,Java编译器会自动创建一个无参构造函数。这个构造器会调用其父类的无参构造函数。

封装、继承与多态

修饰符

在介绍封装、继承与多态之前,需要先了解java中的修饰符,修饰符有两类:

一类是控制访问权限的,一类是实现其他功能的。控制访问权限的修饰符有

1
2
3
4
5
6
7
8
// 以下为类修饰符
public --任何类均可访问。
default --没有指定修饰符时的默认修饰符,只有同一个包中的类可以访问。
// 以下为属性、方法修饰符
public --任何类均可访问。
private --只有声明的类中可以访问。
protected --只有同一个包中的类和其子类可以访问。
default --没有指定修饰符时的默认修饰符,只有同一个包中的类可以访问。

实现其他功能的修饰符有:

1
2
3
4
5
6
7
8
9
10
// 以下为类修饰符
final --此类不能被其他类继承。
abstract --抽象类,此类不能用来创建对象。
// 以下为属性、方法修饰符
final --属性、方法不能被重载。
static --属性、方法属于类而不是对象。
abstract -- 只能用在抽象类中的方法上。
transient -- 序列化对象时跳过此属性和方法。
synchronized -- 此方法一次只能被一个线程访问。
volatile --此属性的值不是线程内部缓存,而是从主内存中读取。

封装

封装可以将实现细节隐藏起来,其最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。

继承

在继承中,先了解两个概念:父类和子类,父类就是被继承的类,子类就是继承其他类的类,在jva中,继承使用extends关键字或implements关键字。java中类的继承是单一继承,一个子类只能拥有一个父类。所有 Java 的类均是由java.lang.Object 类继承而来的。通过使用关键字 extends ,子类可以继承父类的除 private 属性外所有的属性。Implements 关键字在类继承接口的情况下使用,可以多继承接口。

重写(Override)与重载(Overload)

重写(Override)

重写是子类对父类的允许访问的方法的实现过程进行重新编写!返回值和形参都不能改变。

子类在声明变量时,可以使用父类类型,这是因为在编译阶段,只是检查参数的引用类型,然而在运行时,Java 虚拟机 (JVM) 指定对象的类型并且运行该对象的方法。需要注意的是构造方法不能被重写。需要在子类中调用父类的被重写方法时,要使用 super 关键字。

重载(Overload)

重载 (overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。构造函数是可以重载的。

多态

通过继承、重写、重载可以以多种不同的方式实现某个操作,便可以实现多态。除此之外,java中还有接口和抽象类以实现多态。

接口

接口通常以interface来声明。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。