0%

echo

1
echo [-neE] [ARGUMENTS]
  • 当-n 选项,则取消尾随换行符

  • 如果-e 选项,则将解释以下反斜杠转义字符:

    • \ 显示反斜杠字符

    • \a 警报(BEL)

    • \b 显示退格字符

    • \c 禁止任何进一步的输出

    • \e 显示转义字符

    • \f 显示窗体提要字符

    • \n 显示新行

    • \r 显示回车

    • \t 显示水平标签

    • \v 显示垂直标签

    • 这个-E 项禁用转义字符的解释。这是默认值

dig

查询域名的DNS记录,看域名解析到哪里

1
dig [@dns服务器] <域名> [查询类型]

grep

选项 作用
-i 忽略大小写
-n 显示匹配行的行号
-v 反向匹配(显示不匹配的行)
-r / -R 递归搜索目录下所有文件
-l 只显示文件名,不显示匹配内容
-c 只显示匹配的行数
-o 只输出匹配的字符串,而不是整行
-E 使用扩展正则(等价于 egrep
-w 只匹配整个单词,而不是部分匹配
--color 高亮匹配的关键字

例如:

1
grep "error" /var/log/nginx/error.log

这条命令会输出所有包含error的行

tail

显示文件的最后几行

1
tail /var/log/messages
选项 作用
-n <行数> 显示最后 N 行(默认 10 行)
-c <字节数> 显示最后 N 个字节
-f 持续输出文件新增内容(实时跟踪)
-F 类似 -f,但文件被重命名或替换后会重新打开(更适合看日志)
--pid=<PID> -f 一起用,指定进程退出后自动停止跟踪
-q 静默模式,多文件时不显示文件名
-v 强制显示文件名(多文件时更清晰)

chown

修改文件或目录的所有者和所属组

1
chown [选项] [新属主][:[新属组]] 文件...

例如

1
chown alice:developers myfile.txt

修改所有者为alice,组为developers

1
chown alice myfile.txt

修改所有者

1
chown :developers myfile.txt

所有者不变,只修改组

dd

dd if=/dev/zero of=test.txt bs=1M count=10

参数解释:

  • if=/dev/zero:表示输入来源是一个全是 0 的虚拟设备(会输出无限个 0 字节)
  • of=test.txt:输出文件名为 test.txt
  • bs=1M:每次写入 1MB
  • count=10:一共写入 10 次(总计 10MB)

1 view c++ as a federation of languages

c

pass by value比pass by reference更高效,c中的引用实质上还是指针

object-oriented c++

有构造函数和析构函数存在,传值会调用拷贝构造函数和析构函数

template c++

pass by reference 更高效,在使用模版时,并不知道是什么类型,使用reference不用拷贝

pass by reference-to-const

STL

2 prefer const enum inline to define

1
2
#define PI 3.14
const pi =3.142

const 比define更好

1
2
3
4
5
class foo{
private:
static const int num = 5;
int score[num];
}

num是类中的专属常量,如果在类外取它的地址的话,需要在类外给出定义式

1
const int foo::num;
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

class foo {
public:
static const int num;
};
const int foo::num = 5;
int main() {
const int* p = &foo::num;
std::cout << *p << std::endl;
}

如果不在类外给出定义式,在类外访问num的地址会出现错误,原因是并没有给num分配内存

1
2
3
4
class foo {
public:
static const int num = 9;
};

也没分配地址,所以要在外面给出定义式,在类中仅仅只是声明式

1
2
3
4
5
6
7
8
class foo {
public:
static const int num = 5;
};

int main() {
std::cout << foo::num << std::endl;
}

像这样,虽然能够输出num的值,但是并没有给num分配内存,仅仅只是替换成5

也可以这样写

1
2
3
4
5
class foo{
private:
enum {num = 5};
int score[num];
}

使用inline替换’#define’

3 use const whenever possible

4 初始化后再使用

1
2
3
4
5
6
7
8
9
class ABEntry {
public:
ABEntry(const std::string& name, const std::string& address, const std::list<std::string> phones);
private:
std::string theName;
std::string theAddress;
std::list<std::string> thePhones;
int numTimesConsulted;
};

对于下面的构造函数

1
2
3
4
5
6
ABEtry::ABEntry(const std::string& name, const std::string* address, const std::list<std::string> phones) {
theName = name;
theAddress = address;
thePhones = phones;
numTimesConsulted = 0;
}

参数是在进入构造函数前就初始化了,上面的操作是赋值,不是初始化

正确的做法是使用member initialization list

1
2
3
ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list<std::string> phones)
:theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0)
{ }

赋值版本的构造函数,首先会调用default构造函数为参数设置初值,然后立刻赋予新值

const

const位于星号左侧,const用于修饰指针指向的变量;const位于星号右侧,const修饰指针本身

1
2
3
4
5
int b = 500;
const int* a = &b; [1]
int const *a = &b; [2]
int* const a = &b; [3]
const int* const a = &b; [4]

1和2是等价的,都表示指向常量的指针,3表示指针本身不可变,但是指向的内容可变

1
2
3
4
5
int a = 10;
int* const p = &a;
*p = 20; //合法
int b = 20;
p = &b; //不合法,Cannot assign to readonly type int * const

const int 必须用const int * p来指向

int* const p必须初始化

顶层const(top-level const)表示指针本身是一个常量,底层const(low-level const)表示指针所指的对象是一个常量,更一般的,顶层const可以表示任意的对象是常量

constexpr修饰指针,constexpr仅对指针有效

函数体外定义的变量存放在固定地址,函数体外的变量则不是,constexpr指针和引用只能用于函数体外的变量

constexpr

常量表达式(const expression)是指值不会改变并且在编译阶段就会得到计算结果的表达式

将变量声明为constexpr类型,由编译器来验证变量的值是否为常量表达式

自定义类、IO库、string类型不能被定义为constexpr

1
2
3
constexpr std::string str = "abc";
std::cout << str << std::endl;
// error: constexpr variable cannot have non-literal type 'const std::string' (aka 'const basic_string<char>')

constexpr指针初值必须是nullptr或0

1
2
3
4
int num = 20;
constexpr int* p = &num;
std::cout << *p << std::endl;
// error: constexpr variable 'p' must be initialized by a constant expression

static

被static修饰的变量只能在当前文件访问,函数同理

1
2
3
4
5
6
7
8
9
10
11
12
// a.cpp 文件
static int a = 10; // static 修饰全局变量
int main() {
a++; // 合法,可以在当前文件中访问 a
return 0;
}

// b.cpp 文件
extern int a; // 声明 a
void foo() {
a++; // 非法,会报链接错误,其他文件无法访问 a
}

修饰局部变量

1
2
3
4
5
6
7
8
9
10
11
12
void foo() {
static int count = 0; // static 修饰局部变量
count++;
cout << count << endl;
}

int main() {
foo(); // 输出 1
foo(); // 输出 2
foo(); // 输出 3
return 0;
}

extern

用于声明外部变量

File1.c

1
2
3
4
5
6
7
8
#include <stdio.h>

int globalVar = 42; // 定义全局变量

void printVar() {
printf("globalVar = %d\n", globalVar);
}

file2.c

1
2
3
4
5
6
7
8
#include <stdio.h>

extern int globalVar; // 声明外部变量

void modifyVar() {
globalVar = 100; // 修改外部变量
}

函数同理,extern还可以链接c和c++,用extern声明一个函数是c语言,则该函数可以在c++文件中使用

c

1
2
3
4
5
6
#include <stdio.h>

void hello() {
printf("Hello from C!\n");
}

c++

1
2
3
4
5
6
7
8
9
#include <iostream>

extern "C" void hello(); // 告诉编译器这个函数是 C 语言的

int main() {
hello(); // 调用 C 代码中的 hello() 函数
return 0;
}

vector

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

// 声明
vector<int> vec1; //声明空的vector
vector<int> vec2(5); // 声明大小为5的vector
vector<int> vec3(5, 10); //声明大小为5且值都为10的vector
vector<int> vec4{1, 2, 3, 4, 5}; //初始化
vector<int> vec5(vec4); // 用另一个vecotr初始化

// 用法
vec.push_back(x); //在末尾添加元素
vec.emplace_back(x); //在末尾添加元素,比push_back更快,push_back需要先构造,再复制,emplace_back直接在容器内构造,不需要复制
vec.pop_back(); //删除最后一个元素
int num = vec.back(); //获取最后一个元素
vec.erase(vec.begin() + 1); //删除指定位置的元素
vec.erase(vec.begin() + 1, vec.edn() - 1); // 删除指定范围的元素
vec.clear(); // 清空vector
int n = vec.size(); // 获取vec的大小
vec.resize(x); //动态调整大小,x比n大补0, x比n小,超出的部分移除
sort(vec.begin(), vec.end()); // 升序
sort(vec.begin(), vec.end(), greater<int>()); // 降序
int it = find(vec.begin(), vec.end(), 3); //查找值为3的元素
vec.remove(vec.begin(), vec.end(), 5); // 将值为5的元素移动到容器末尾
if (!vec.empty()); //判空
int sum = accumulate(vec.begin(), vec.end(), 0) //求和,初始值为0
int max_num = *max_element(vec.begin(), vec.end())//数组中的最大值
vec.insert(vec.end(), vec.begin(), vec.end()) //在vec的后面添加vec例如123->123123z
//在一个vector的后面添加另一个vector
vector<int> vec1 = {1, 2, 3};
vector<int> vec2 = {4, 5, 6};
vec1.insert(vec1.end(), vec2.begin(), vec2.end());
// 二维数组
int row = 3;
int col = 4;
vector<vector<int>> nums(row, vector<int>(4));

atoi stoi

1
2
3
4
5
6
7
8
9
10
11
12
const char* str = "abc";

// atoi
int result = atoi(str); //将字符串转换为数字

// stoi
try {
std::string str = "abc";
int result = std::stoi(str);
} catch (const std::invalid_argument& e) {
std::cout << "Invalid argument:" << e.what() << std::endl;
}

stoi提供了更安全的方式

tolower, toupper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <cctype>
string str = "Hello World";
string res;
for (char ch : str) {
res += tolower(ch); //转为小写
}
for (char ch : str) {
res += toupper(ch); //转为大写
}

// 更简单的方法
for (char ch : str) {
ch ^= 32;
}
// 'A'(65) ⊕ 32 = 'a'(97)
// 'a'(97) ⊕ 32 = 'A'(65)

unordered_map

哈希表

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
// 声明
unordered_map<char ch, int n> my_map;
my_map.insert({'a', 2}); // 插入元素
my_map[b] = 1; // 如果键不存在会创建并初始化
// 遍历
for (const auto& pair: my_map) {
cout << pair.first << pait.second << endl;
}
// find的用法
auto it = umap.find("a");
if (it != umap.end()) {
std::cout << "Found: " << it->first << " -> " << it->second << std::endl;
} else {
std::cout << "Not found!" << std::endl;
}
// count,返回键是否存在(0 或 1)
if (umap.count("a") > 0) {
std::cout << "Key exists!" << std::endl;
}
// 清除特定元素
umap.erase(target);
// 在滑动窗口题目中可以这样使用
if (--cnt[nums[i - k + 1]] == 0) {
cnt.erase(nums[i - k + 1]);
}

sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//基础用法
vector<int> nums = {2, 3, 1, 0, 4};
sort(nums.begin(), nums.end()); // 默认升序
sort(nums.begin(), nums.end(), std::greater()); //降序排列
// 自定义排序
struct Student {
std::string name;
int score;
};

// 自定义排序规则
bool compareStudents(const Student& a, const Student& b) {
if (a.score == b.score) // 分数相同,按姓名排序
return a.name < b.name;
return a.score > b.score; // 分数降序
}

引用和指针

1
2
3
4
int a = 10;
int &b = a;
b = 20;
cout << a << endl; // 20

b是a的引用,就是给a起个别名,对b进行操作实际上就是对a进行操作

1
2
int* a = null;
int& b; //error

指针可以指向空,但是引用不能为空

1
2
3
4
int a = 10;
int &b = a;
int c = 30;
int &b = c; //error

指针可以随意改变(除const修饰外),引用一旦绑定就不可以再改变

静态链接和动态链接

.cpp文件经过预处理成为.i文件,.i文件经过编译后成为.s文件,.s文件经过汇编后成为目标文件,即.o,静态链接将该.o文件和其他目标文件以及库文件链接起来,这个过程称为静态链接。

而动态链接将这个过程推迟到了运行时,由操作系统装载程序加载库

静态链接的代码装载速度快,但是文件体积大

动态链接的速度慢,但是文件体积小

c和c++的区别

  1. c只支持基本数据类型,还有结构体、枚举、联合;c++支持类和对象
  2. c++有封装的特性、有构造函数和析构函数、c++支持函数重载,可以定义同名但是参数列表不同的函数;c都做不到
  3. c++有异常处理机制;c没有
  4. c没有引用&

delete

释放new申请的空间,会调用析构函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

class MyClass {
public:
MyClass() {
std::cout << "构造函数" << std::endl;
}
~MyClass() {
std::cout << "析构函数" << std::endl;
}
};

int main() {
MyClass* obj = new MyClass();
delete obj;
return 0;
}

作用域解析操作符

访问全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

int num = 20;

void test() {
int num = 10;
std::cout << "test1:" << num << std::endl;
std::cout << "test2" << ::num << std::endl;
}

int main() {
int num = 30;
test();
std::cout << "test3:" << num << std::endl;
std::cout << "test4:" << ::num << std::endl;
return 0;
}

访问命名空间中的标识符

1
2
3
4
5
6
7
8
9
10
#include <iostream>

namespace MyNamespace {
int val = 20;
}

int main() {
std::cout << MyNamespace::val << std::endl;
return 0;
}

访问修饰符

public:可以在任何函数中访问

protected:只能在类中或者类的子类中访问

private:只能在类中访问

strlen 和sizeof

1
2
3
4
5
char ch[] = "Hello World";
std::string str = "Hello World";
std::cout << sizeof(ch) << std::endl; //12
std::cout << strlen(ch) << std::endl; //11
std::cout << sizeof(str) << std::endl; //24(windows|macos) 32(linux)

对于char ch[]类型,是c风格的字符串,在末尾会自动加\0

sizeof会统计末尾的\0

strlen不统计

string 和char ch[]

string在堆上分配内存,sizeof获取的是string类的大小

char ch[]在栈上分配内存

lambda函数

1
2
[capture list] (parameter list) -> return type { function body }

capture list 表示获取列表用于表示lambda可以访问的外部变量

paramete list表示lambda的参数

return type表示返回类型

`function body 表示函数体

值捕获

1
2
3
4
int x = 10;
auto f = [x] (int y) -> int {return x + y;};
x = 20;
cout << f(5) << endl; //15

如果想按值捕获所有外部变量,[=]

引用捕获

1
2
3
4
int x = 10;
auto f = [&x] (int y ) -> int {return x + y;};
x = 20;
cout << f(5) << endl; //25

如果想按引用捕获外部所有变量[&]

递归

lamda函数写递归函数

1
auto dfs = [&](this auto&& dfs, int i, int j) -> void {}

类型别名

typedef

1
2
typedef double wages;	// wages是double的同义词
typedef wages base, *p; //base是double的同义词,p是double*的同义词

using

1
using SI = Sales_item;

auto

c++11中引入了auto,它能让编译器分析表达式所属的类型

1
auto item = val1 + val2;

根据val1与val2相加的值来推断出item的值

explicit

防止隐式类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MyClass {
public:
MyClass(int x){}
}
void doSomething(MyClass obj) {

}
int main() {
dosomething(5); // 合法,但是隐式构造了一个MyClass(5);
}

// 有explicit
class MyClass {
public:
explicit MyClass(int x){}
}
void doSomething(MyClass obj) {

}
int main() {
dosomething(5); // 编译错误
dosomething(MyClass(5)); // 正确
}

string_view

1
2
3
4
5
6
7
8
#include <string_view>
#include <iostream>

// 它是对字符串的一个视图,指向一段连续的字符序列,但不拥有数据。类似于一个只读的“窗口”
void print(std::string_view sv) {
std::cout << "String view: " << sv << '\n';
}

类型转换

static_cast

1
2
3
4
5
6
7
8
9
#include <iostream>

using namespace std;

int main() {
int num = 97;
char ch = static_cast<char>(num);
cout << ch << endl; // a
}

static_cast用法,static_cast<要转换的类型>(变量)

dynamic_cast

  1. 上行转换,从子类转换成父类
  2. 下行转换,从父类转换成子类, 且要求父类中至少有一个虚函数

const_cast

  1. 移除const修饰符
  2. 添加const修饰符

reinterpret_cast

  1. 指针类型的转换
  2. 指针和整数之间的转换
  3. 非相关类型的转换

数组指针 todo,(primer 6.3)

可在c中实现多态

数组不能被拷贝,所以在函数中无法返回数组,但是可以返回数组的指针或引用
在写一个返回数组指针的函数时,可以用别名来声明

1
2
3
4
typedef int arrT[10];   // 为int[10]起一个别名
using arrT = int[10]; // 等价上面
// 有了别名,可以定义返回数组指针的函数了
arrT* function(int n); //接受一个int类型的参数,返回一个指向包含十个int数组的指针

如果不想使用别名来声明函数,需要了解下面三个的区别

1
2
3
int *arr[5] = {1, 2, 3, 4, 5};  // 声明了一个大小为5的数组
int *p1[5]; // 声明了一个包含5个指针的数组,它是一个指针数组,数组元素是指针
int (*p2)[5] = &arr; // 声明了一个指针,它指向arr

deep copy and shallow copy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
private:
int age_;
string name_;
public:
Person(int age, string name) : age_(age), name_(name);
// 拷贝构造函数
Person(const Person& person);

}
Person::Person(const Person& person) {
age_ = person.age_;
name_ = person.name_;
}

对于基本数据类型,浅复制可以实现,但是当类中有引用或者指针时,就需要用到深复制
在写拷贝构造函数的时候,必须将参数写成引用,一是引用可以避免拷贝,二是c++语法要求,否则,在传参的时候就会调用拷贝构造函数,进入无限循环
而且最好写成const类型,这样非const类型也可以传入

rvalue

int a = 5在这句代码中,a表示左值(lvalue),5表示右值(rvalue),左值表示在内存中可以找到它,右值在内存中找不到,
右值是一个立即数(immediate number),

1
2
3
4
int a = 20; // correct, a is a lvalue
20 = 30; // incorrect, 20 is a rvalue
int &lref = a; // 左值引用
int &&rref = 20; //右值引用

在写函数的参数时,最好将参数定义为const引用类型,首先可以保证函数内部不会对参数做出修改,而且引用不会进行拷贝操作;其次,const引用可以传入右值引用,而非const的左值引用只能传入左值。例如,当传入的参数为具体的数字的时候,此时参数为右值,如果函数参数定义为const引用,可以传入,否则会出错

move

1
2
int a = 20;
int b = move(20);

b和a分别有自己的地址,a和b各存一份数据,int没有动态分配,所以只能复制,所以move对于基本类型没有实际作用

1
2
3
4
5
class vector{
int* data;
size_t size;
size_t capacity;
}

这是vector的定义,所以对于下面的代码

1
2
vector<int> vec1 = {1, 2, 3, 4, 5};
vector<int> vec2 = std::move(vec1);

会执行下面的操作

1
2
3
4
vec2.data = vec1.data;
vec1.data = nullptr;
vec1.size = 0;
vec1.capacity = 0;

它会将vec1的内容“偷”过来,避免复制。

多态

多态分为两种,一种是运行时多态,一种是编译时多态

运行时多态

运行时多态就是虚函数+继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

class Base {
public:
virtual void foo() {cout << "base foo" << endl;}
};

class Derived : public Base {
public:
void foo() {cout << "derived foo" << endl;}
};

int main() {
Base* p = new Derived();
p -> foo();
}

在上面的例子中,base中foo定义为虚函数,所以在p调用foo时,实际调用的时derived中的foo

1
2
3
Derived* p = new Derived();
p->derivedOnlyMethod(); // ✅ 直接访问派生类独有成员
delete p;
1
2
3
Base* p = new Base();
p->foo(); // 调用 Base::foo()
delete p; // 调用 Base::~Base()
1
2
3
4
Base* p = new Derived();  // upcast
p->foo(); // 如果 foo 是 virtual,调用 Derived::foo()
delete p; // 如果 ~Base 是 virtual,调用 Derived::~Derived() + Base::~Base()

上面这三种定义指针的方式

​ 第一种限制死了p的类型,只能是derived,如果后续想要更换其他派生类的话,需要该所有Derived

​ 第二种p只能是基类

​ 第三种是多态的标准写法,p的类型是Base*,所以只能通过p访问基类中的接口,但是如果Base中的接口定义为虚函数,调用时会动态绑定到Derived中的实现

在c++中,析构函数可以定义为虚函数,而且当析构函数定义为虚函数之后,delete p会调用派生类中的析构函数,否则,只会调用基类中的析构函数;构造函数不能定义为虚函数,虚函数依赖虚函数表,虚表指针在构造函数中初始化的

编译时多态

模板+函数重载

函数重载

1
2
3
4
5
6
7
8
void print(int x) { std::cout << "int\n"; }
void print(double x) { std::cout << "double\n"; }

int main() {
print(10); // 编译期决定调用 print(int)
print(3.14); // 编译期决定调用 print(double)
}

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template <typename T>
struct Base {
void call() {
static_cast<T*>(this)->impl(); // 静态多态
}
};

struct Derived : Base<Derived> {
void impl() { std::cout << "Derived\n"; }
};

int main() {
Derived d;
d.call(); // 编译期决定调用 Derived::impl()
}

noexcept

用于说明一个函数不会抛出异常,或者用来检测一个表达式是否可能抛异常

1
void foo() noexcept{}
1
2
3
4
5
6
7
8
9
struct A {
A(const A&) { std::cout << "copy\n"; }
A(A&&) noexcept { std::cout << "move\n"; }
};

std::vector<A> v;
v.emplace_back();
v.push_back(A{}); // 如果移动构造是 noexcept,vector 扩容时用 move,否则可能用 copy

1
noexcept(expression)

这是一个常量表达式,结果是true或者false,表示expression在当前上下文是否可能抛异常

1
2
3
4
5
6
7
8
9
#include <iostream>

void might_throw();
void never_throw() noexcept;

int main() {
std::cout << noexcept(might_throw()) << "\n"; // 可能抛异常 => 输出 0
std::cout << noexcept(never_throw()) << "\n"; // 承诺不抛 => 输出 1
}

纯虚函数

纯虚函数是一种特殊的虚函数,在声明时写成=0

1
2
3
struct Base {
virtual void hello() = 0; // 纯虚函数
};

它的实现必须由派生类提供

含有至少一个虚函数的类叫做抽象类,它不能直接实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

struct Base {
virtual void hello() = 0; // 纯虚函数
};

struct Derived : Base {
void hello() override { std::cout << "Derived\n"; }
};

int main() {
// Base b; // ❌ 错误:抽象类不能实例化
Base* b = new Derived();
b->hello(); // ✅ 输出 "Derived"
}

Rule of three

如果类定义了析构函数、拷贝构造函数、或拷贝赋值运算符中的一个,几乎总是需要显式的定义另外两个。

因为当你显式定义了其中一个的时候,意味着类涉及到一些资源管理,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

class MyArray {
public:
MyArray(size_t n) : size(n), data(new int[n]) {}
~MyArray() { delete[] data; }
private:
std::size_t size;
int* data;
};

int main() {
MyArray a(10);
MyArray b = a;

}

在上面这段代码中,如果不写拷贝构造和拷贝赋值的话,编译器会自动写,但是这样,a和b中的data指向的是一个地址,data会被释放两次,导致程序崩溃。

虚函数

​ 对于虚函数的调用在运行时才会被解析,直到运行时才能确定使用哪个版本的虚函数,所以每个虚函数必须有定义。

​ 一个函数被声明成虚函数,则在所有的派生类中,它都是虚函数,但是如果派生类中的虚函数参数列表和基类中函数参数列表不相同,编译器不会报错,所以需要在派生类的虚函数中显示的写出override

​ 虚函数中可以有默认实参,该实参值由本次调用的静态类型决定,如果使用基类的指针调用函数,则使用基类中定义的默认实参。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Base {
public:
virtual void print(int a = 3) {
std::cout << "num = " << a << std::endl;
}
};

class Derived : public Base {
public:
void print(int a = 4) override {
std::cout << "num = " << a << std::endl;
}
};

int main() {
Base* p = new Derived();
p->print(); // 输出3
}

设计模式

LSP(里氏替换)

子类能够替换所有父类出现的地方,而且代码能够正常运行

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
#include <iostream>
using namespace std;

class Bird {
public:
virtual void fly() {
cout << "Bird flies" << endl;
}
};

class Sparrow : public Bird {
public:
void fly() override {
cout << "Sparrow flies quickly" << endl;
}
};

void makeBirdFly(Bird* b) {
b->fly();
}

int main() {
Bird b;
Sparrow s;

makeBirdFly(&b); // 输出 Bird 版本
makeBirdFly(&s); // 输出 Sparrow 版本
return 0;
}

在上述代码中,父类指针接收子类对象,代码仍然可以正常运行,这就是里氏替换

下面是一个不符合里氏替换的例子

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
#include <iostream>
using namespace std;

class Rectangle {
public:
virtual void setWidth(int w) { width = w; }
virtual void setHeight(int h) { height = h; }

int getWidth() const { return width; }
int getHeight() const { return height; }

protected:
int width = 0;
int height = 0;
};

class Square : public Rectangle {
public:
void setWidth(int w) override {
width = w;
height = w; // 强制保持正方形性质
}
void setHeight(int h) override {
width = h;
height = h; // 同上
}
};

void test(Rectangle* r) {
r->setWidth(4);
r->setHeight(5);
cout << r->getWidth() << " " << r->getHeight() << endl;
}

int main() {
Rectangle r;
Square s;

test(&r); // 输出 4 5
test(&s); // 输出 5 5 逻辑被破坏
return 0;
}

在这个例子中,test函数要分别设置矩形的长和宽,但是square保持了正方形的特性,子类不能替代父类,违背里氏替换原则

ISP(接口隔离)

不要让一个类被迫依赖它不需要的功能,大接口要拆成小接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class IWorker {
public:
virtual void work() = 0;
virtual void eat() = 0;
virtual void sleep() = 0;
virtual ~IWorker() = default;
};

class Robot : public IWorker {
public:
void work() override { cout << "Robot working" << endl; }
void eat() override {} // 机器人不会吃
void sleep() override {} // 机器人不会睡
};

正确使用方法

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
class IWork {
public:
virtual void work() = 0;
virtual ~IWork() = default;
};

class IEat {
public:
virtual void eat() = 0;
virtual ~IEat() = default;
};

class ISleep {
public:
virtual void sleep() = 0;
virtual ~ISleep() = default;
};

class Human : public IWork, public IEat, public ISleep {
public:
void work() override { cout << "Human working" << endl; }
void eat() override { cout << "Human eating" << endl; }
void sleep() override { cout << "Human sleeping" << endl; }
};

class Robot : public IWork {
public:
void work() override { cout << "Robot working" << endl; }
};

DIP(依赖倒置)

高层模块不依赖底层模块,大家一起依赖抽象;抽象不依赖细节,细节依赖抽象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MySQL {
public:
void connect() {
cout << "Connect to MySQL" << endl;
}
};

class UserService {
public:
void login() {
db.connect(); // 直接写死数据库类型
}
private:
MySQL db;
};

UserService绑定mysql,之后如果要更换数据库,都需要改UserService

正确方法

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
class IDatabase {
public:
virtual void connect() = 0;
virtual ~IDatabase() = default;
};

class MySQL : public IDatabase {
public:
void connect() override {
cout << "Connect to MySQL" << endl;
}
};

class PostgreSQL : public IDatabase {
public:
void connect() override {
cout << "Connect to PostgreSQL" << endl;
}
};

class UserService {
public:
UserService(IDatabase* db) : db_(db) {}

void login() {
db_->connect();
}
private:
IDatabase* db_;
};
int main() {
MySQL mysql;
PostgreSQL pg;

UserService service1(&mysql);
service1.login();

UserService service2(&pg);
service2.login();
return 0;
}


新加数据库直接添加就可以,不需要修改原本的代码,

埃氏筛

1
2
3
4
5
6
7
8
9
10
vector<bool> is_prime(n, true);
is_prime[0] = false;
is_prime[1] = false;
for (int i = 2; i <= n; i++) {
if (is_prime[i]) {
for (int j = 2 * i; j <= n; j += i) {
is_prime[j] = 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void bubble_sort(std::vector<int>& nums) {
int n = nums.size();
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (nums[j] > nums[j + 1]) {
std::swap(nums[j], nums[j + 1]);
}
}
}
}

void sellection_sort(std::vector<int>& nums) {
int n = nums.size();
for (int i = 0; i < n; i++) {
int min = i;
for (int j = i + 1; j < n; j++) {
if (nums[min] > nums[j]) {
min = j;
}
}
std::swap(nums[i], nums[min]);
}
}

int partition(std::vector<int>& nums, int low, int high) {
int pivot = nums[low];
while (low < high) {
while (low < high && nums[high] > pivot) {
high--;
}
nums[low] = nums[high];
while (low < high && nums[low] < pivot) {
low++;
}
nums[high] = nums[low];
}
nums[low] = pivot;
return low;
}
void quick_sort(std::vector<int>& nums, int low, int high) {
if (low < high) {
int pivot = partition(nums, low, high);
quick_sort(nums, low, pivot - 1);
quick_sort(nums, pivot + 1, high);
}
}

回溯

1
2
3
4
5
6
7
8
9
10
11
12

void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择 : 本层集合中的元素) {
处理节点;
backtracking(路径, 选择列表); // 递归
撤销处理; // 回溯
}
}

二叉树的层序遍历

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
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};

// 使用二维数组保存遍历结果
// 使用队列
vector<vector<int>> level_order(TreeNode* root) {
vector<int> temp;
vector<vector<int>> res;
if (!root) {
return res;
}
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
temp.clear();
int len = q.size();
for (int i = 0; i < len; i++) {
TreeNode* node = q.front();
q.pop();
temp.push_back(node->val);
if (node -> left) {
q.push(node -> left);
}
if (node ->right) {
q.push(node -> right);
}
}
res.push_back(temp);
}
return res;
}

高精度乘法

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
#include <iostream>
#include <string>
#include <vector>
using namespace std;
vector<int> mul(vector<int>&A,int b)
{
vector<int> C;
for(int i=0,t=0;i<A.size()||t;i++)
{
if(i<A.size())
t+=A[i]*b;
C.push_back(t%10);
t/=10;
}
while(C.size()>1&&C.back()==0)
C.pop_back();
return C;
}
int main()
{
vector<int> A;
string a;
int b;
cin >> a >> b;
for(int i=int(a.size())-1;i>=0;i--)
A.push_back(a[i]-'0');
auto C=mul(A,b);
for(int i=int(C.size())-1;i>=0;i--)
cout << C[i];
return 0;
}

高精度加法

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
#include <iostream>
#include <string>
#include <vector>
using namespace std;
vector<int> add(vector<int>&a,vector<int>&b)
{
vector<int> A,B;
if(a.size()<b.size())
{
A=b;
B=a;
}
else
{
A=a;
B=b;
}
vector<int> C;
int t=0;
for(int i=0;i<A.size();i++)
{
t+=A[i];
if(i<B.size())
t+=B[i];
C.push_back(t%10);
t/=10;
}
if(t)
C.push_back(t);
return C;
}
int main()
{
vector<int> A,B;
string a,b;
cin >> a >> b;
for(int i=int(a.size())-1;i>=0;i--)
A.push_back(a[i]-'0');
for(int i=int(b.size())-1;i>=0;i--)
B.push_back(b[i]-'0');
auto C=add(A,B);
for(int i=int(C.size())-1;i>=0;i--)
cout << C[i];
}

位运算

1
2
3
4
5
6
7
8
9
10
11
12
13
// 不使用+-来计算两数之和
int add (int a, int b) {
while (b) {
int carry = (a & b) << 1; //进位
a = a ^ b; // 无进位结果
b = carry;
}
return a;
}
// 奇数的最低位总是1,偶数的最低位总是0
if (num & 1) {
cout << "奇数" << endl;
}

01背包

问题描述:给定 n 个物品,第 i 个物品的重量为 wgt[i−1]、价值为 val[i−1] ,和一个容量为 cap 的背包。每个物品只能选择一次,问在限定背包容量下能放入物品的最大价值。

定义状态:[i, c],物品编号i和背包容量c

对于当前物品,有两种方案

​ 不放入背包:[i, c] = [i - 1, c]

​ 放入背包:[i, c] = [i - 1, c - weight[i - 1]],价值增加val[i];

dp[i,c]=max(dp[i−1,c],dp[i−1,c−wgt[i−1]]+val[i−1]

滑动数组

定长滑动数组

给你字符串 s 和整数 k

请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。

例如s = "abciiidef", k = 3

从左到右遍历 s。
首先统计前 k−1=2 个字母的元音个数,这有 1 个。
s[2]=c 进入窗口,此时找到了第一个长为 k 的子串 abc,现在元音个数有 1 个,更新答案最大值。然后 s[0]=a 离开窗口,现在元音个数有 0 个。
s[3]=i 进入窗口,此时找到了第二个长为 k 的子串 bci,现在元音个数有 1 个,更新答案最大值。然后 s[1]=b 离开窗口,现在元音个数有 1 个。
s[4]=i 进入窗口,此时找到了第三个长为 k 的子串 cii,现在元音个数有 2 个,更新答案最大值。然后 s[2]=c 离开窗口,现在元音个数有 2 个。
s[5]=i 进入窗口,此时找到了第四个长为 k 的子串 iii,现在元音个数有 3 个,更新答案最大值。然后 s[3]=i 离开窗口,现在元音个数有 2 个。
s[6]=d 进入窗口,此时找到了第五个长为 k 的子串 iid,现在元音个数有 2 个,更新答案最大值。然后 s[4]=i 离开窗口,现在元音个数有 1 个。
s[7]=e 进入窗口,此时找到了第六个长为 k 的子串 ide,现在元音个数有 2 个,更新答案最大值。然后 s[5]=i 离开窗口,现在元音个数有 1 个。
s[8]=f 进入窗口,此时找到了第七个长为 k 的子串 def,现在元音个数有 1 个,更新答案最大值。遍历结束。

步骤:

入:下标为 i 的元素进入窗口,更新相关统计量。如果 i<k−1 则重复第一步。
更新:更新答案。一般是更新最大值/最小值。
出:下标为 i−k+1 的元素离开窗口,更新相关统计量。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int maxVowels(string s, int k) {
unordered_set<char> vowles = {'a', 'e', 'i', 'o', 'u'};
int ans = 0;
int cur = 0;
for (int i = 0; i < s.size(); i++) {
if (vowles.count(s[i]) > 0) {
cur++;
}
if (i < k - 1) {
continue;
}
ans = max(ans, cur);
if (vowles.count(s[i - k + 1]) > 0) {
cur--;
}
}
return ans;
}
};

不定长滑动数组

给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int lengthOfLongestSubstring(string s) {
int res = 0;
int left = 0;
unordered_map<char, int> cnt;
for (int right = 0; right < s.size(); right++) {
cnt[s[right]]++;
while (cnt[s[right]] > 1) {
cnt[s[left]]--;
left++;
}
res = max(res, right - left + 1);
}
return res;
}

越长越合法

给你一个字符串 s ,它只包含三种字符 a, b 和 c 。

请你返回 a,b 和 c 都 至少 出现过一次的子字符串数目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int numberOfSubstrings(string s) {
int left = 0;
unordered_map<char, int> cnt;
int ans = 0;
for (int right = 0; right < s.size(); right++) {
cnt[s[right]]++;
while (cnt['a'] && cnt['b'] && cnt['c']) {
cnt[s[left]]--;
left++;
}
ans += left;
}
return ans;
}

现在的已经满足了,更长的子串就满足,+left

越短越合法

给你一个整数数组 nums 和一个整数 k ,请你返回子数组内所有元素的乘积严格小于 k 的连续子数组的数目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int numSubarrayProductLessThanK(vector<int>& nums, int k) {
int cur = 1;
int res = 0;
int left = 0;
if (k <= 1) {
return 0;
}
for (int right = 0; right < nums.size(); right++) {
cur *= nums[right];
while (cur >= k) {
cur /= nums[left];
left++;
}
res += right - left + 1;
}
return res;
}

长的合法,长的所有子串都合法,+ right - left + 1

恰好

例如,要计算有多少个元素和恰好等于 k 的子数组,可以把问题变成:

计算有多少个元素和 ≥k 的子数组。
计算有多少个元素和 >k,也就是 ≥k+1 的子数组。
答案就是元素和 ≥k 的子数组个数,减去元素和 ≥k+1 的子数组个数。这里把 > 转换成 ≥,从而可以把滑窗逻辑封装成一个函数 f,然后用 f(k) - f(k + 1) 计算,无需编写两份滑窗代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int my_fun(vector<int>& nums, int goal) {
int left = 0;
int res = 0;
int cur = 0;
for (int right = 0; right < nums.size(); right++) {
cur += nums[right];
while (cur >= goal && left <= right) {
cur -= nums[left];
left++;
}
res += left;
}
return res;
}
int numSubarraysWithSum(vector<int>& nums, int goal) {
return my_fun(nums, goal) - my_fun(nums, goal + 1);
}

二分

1
mid = left + (right - left) / 2; //避免溢出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// lower_bound,指向第一个 ≥ value 的元素的迭代器。
int binarySearch(vector<int>& nums,int target) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
}
1
auto it = lower_bound(nums, target);	//二分,返回的是迭代器,值为*it
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// upper_bound,指向第一个 > value 的元素的迭代器。
int binarySearch(vector<int>& nums,int target) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
}

1ll

1
1ll * nums[mid] * num >= target

target是long long类型,nums和num都是int类型,当需要类型转换的时候,可以在前面加一个1ll,表示转换成long long类型

向上取整

1
2
3
int a = 7;
int b = 3;
int res = (a + b - 1) / b; // res = 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
class Solution {
public:
// 计算满足 check(x) == true 的最小整数 x
int binarySearchMin(vector<int>& nums) {
// 二分猜答案:判断 mid 是否满足题目要求
auto check = [&](int mid) -> bool {

};

int left = ; // 循环不变量:check(left) 恒为 false
int right = ; // 循环不变量:check(right) 恒为 true
while (left + 1 < right) { // 开区间不为空
int mid = left + (right - left) / 2;
if (check(mid)) { // 说明 check(>= mid 的数) 均为 true
right = mid; // 接下来在 (left, mid) 中二分答案
} else { // 说明 check(<= mid 的数) 均为 false
left = mid; // 接下来在 (mid, right) 中二分答案
}
}
// 循环结束后 left+1 = right
// 此时 check(left) == false 而 check(left+1) == check(right) == true
// 所以 right 就是最小的满足 check 的值
return right;
}
};
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
class Solution {
public:
// 计算满足 check(x) == true 的最大整数 x
int binarySearchMax(vector<int>& nums) {
// 二分猜答案:判断 mid 是否满足题目要求
auto check = [&](int mid) -> bool {

};

int left = ; // 循环不变量:check(left) 恒为 true
int right = ; // 循环不变量:check(right) 恒为 false
while (left + 1 < right) {
int mid = left + (right - left) / 2;
if (check(mid)) {
left = mid; // 注意这里更新的是 left,和上面的模板反过来
} else {
right = mid;
}
}
// 循环结束后 left+1 = right
// 此时 check(left) == true 而 check(left+1) == check(right) == false
// 所以 left 就是最大的满足 check 的值
return left; // check 更新的是谁,最终就返回谁
}
};

minmax

返回两个参数,第一个参数为最小值,第二个参数为最大值

1
auto [a, b] = minmax(num1, num2);

进入insert模式

1
2
3
4
i: 在光标的位置插入
a: 在光标后的位置插入
o: 在当前的下一行插入
O:在当前的上一行插入

在insert模式下

1
2
3
ctrl + h: 删除光标前面的字符 
ctrl + w: 删除光标前面的单词
ctrl + u: 删除光标前面的本行所有内容

在normal模式下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
w: 移动到下一个单词的开头
e: 移动到下一个单词的结尾
b: 移动到上一个单词的开头
f{char}: 移动到char上
0: 移动到行首
$: 移动到行尾
gg: 移动到文件开头
G: 移动到文件结尾
ctrl + o: 快速返回
ngg: 跳转到第n行
x: 删除光标后的第一个字符
daw: 删除光标所在的单词
dw: 删除光标后面的单词
diw: 删除光标所在的单词
dt{char}: 删除从光标到char的所有内容
1
2
3
gt:在vim不同标签之间切换
ctrl + shift + t:新建终端标签页
alt + 1/2/3/4:切换标签页

dw :删除一个单词,光标直接到下一个单词

de: 单纯删除一个单词

d$: 删除到一行的末尾

dnw :删除n个单词

nw :移动到第n个单词

ne:移动到第n个单词的结尾

u: 撤销上一步操作

x: 删除当前单词

p: 会将上次删除或者复制的东西粘贴

rx: 会将当前字符替换为x

/str: 会找到str所在的位置,输入n找下一个

%: 在[{()}] 上面输入会自动跳转到对应的位置上

:s/new/old/g,用new代替old,g表示全局

!command: 可以在vim中输入shell命令

Table:

1
以按行按列形式组织及展现的数据

数据

1
数据是数据库中存储的基本对象。

Database:

1
相互之间有关联关系的Table的集合

DB:

1
Database

DBMS:

1
数据库管理系统

DBAP:

1
数据库应用

DBA:

1
数据库管理员

DDL:

1
数据库定义语言

DML:

1
数据库操纵语言

DCL:

1
数据库控制语言

模式:

1
2
对数据库中数据所进行的一种结构性的描述
所观察到的数据的结构信息

视图:

1
某一种表现形式下表现出来的数据库中的数据

三级模式:

1
2
3
External Schema(外模式): 某一用户能够看到与处理的数据的结构描述
Conceptual Schema(概念模式):从全局角度理解\管理的结构描述,含相应的关联约束
Internal Schema(内模式 ):存储在介质上的数据的结构描述,含存储路径、存储方式、索引方式 等

两层映像:

1
2
E-C Mapping: 将外模式映射为概念模式,从而支持实现数据概念视图向外部视图的转换
C-I Mapping:将概念模式映射为内模式,从而支持实现数据概念视图向内部视图的转换

两个独立性:

1
2
逻辑数据独立性:当概念模式变化时,可以不改变外部模式(只需改变E-C Mapping),从而无需改变应用程序
物理数据独立性:当外部模式变化时,可以不改变概念模式(只需改变C-I Mapping),从而不改变外部模式

数据模型:

1
2
规定模式统一描述方式的模式,包括:数据结构、操作和约束
数据模型是对模式本身结构的抽象,模式是对数据本身结构形式的抽象

关系模型:

1
2
3
描述DB各种数据的基本结构形式(Table/Relation)
描述Table与Table之间所可能发生的各种操作(关系运算)
描述这些操作所应遵循的约束 条件(完整性约束)

域(Domain)

1
2
一组值的集合,这组值具有相同的数据类型
集合中元素的个数 成为域的基数

关系模式 :

1
2
3
同一关系模式下,可有很多的关系
关系模式时关系的结构,关系是关系模式在某一时刻的数据
关系模式是稳定的;而关系是 某一时刻的值,是随时间可能变化的

关系的特性:

1
属性不可再分特性

候选码:

1
关系中的一个属性组,其值能唯一标识一个元组,若从该属性组中去掉任何一个属性,它就不具有这一性质 了,这样的属性组称作候选码

主码:

1
当有多个候选码时,可以选定一个作为主码

主属性与非主属性:

1
包含在任何一个候选码的属性被称作主属性,而其他属性被称作 非主属性

外码:

1
关系R中的一个属性组,它不是R的候选码,但它与另一个关系 S的候选码相对应,则成这个 属性组为R的外码或外键

实体完整性:

1
关系的主码中的属性值不能为空值

参照完整性:

1
如果关系R1的外码FK与关系R2的主码PK相对应,则R1中的每一个元组 的FK值 或者等于 R2中的某个元组的PK值,或者为空值

用户自定义完整性:

1
用户针对具体的应用环境定义的完整性约束条件

数据模型的概念

1
数据模型是一种模型,它是对现实世界数据特征的抽象

数据模型的作用

1
数据模型是用来描述数据、组织数据和对数据进行操作的

数据模型的三个要素

1
一是能比较真实地模拟现实世界,二是为人所理解,三是便于在计算机上实现

关系

1
一个关系对应通常来说的一张表

属性

1
表中的一列即为一个属性

1
域是一组具有相同数据类型的值的集合

元组

1
表中的一行即为一个元组

1
也称码键。表中的某个属性组,它可以唯一确定一个元组 

分量

1
元组中的一个属性值

关系模式

1
2
对关系的描述,一般表示为
关系名(属性1、属性2、···,属性n)

逻辑独立性

1
当模式改变时,由数据库 管理员对各个外模式/模式的映像作相应改变,可以使外模式保持不变。应用程序是依据数据的外模式编写的 ,从而应用程序不必改变,保证的数据与程序的逻辑 独立性,简称数据的逻辑独立性

物理独立性

1
当数据库的存储结构改变时,由数据库管理员对模式 /内模式 映像作相应 改变,可以使模式保持不变,从而应用程序也不必改变。保证了数据与程序的物理独立性

什么是操作系统

1
操作系统是配置在计算机硬件上的第一层软件,是对硬件系统的首次扩充

操作系统的作用

  1. OS作为用户与计算机硬件系统之间的接口
  2. OS作为计算机系统资源的管理者
  3. OS实现了对计算机资源的抽象

单批道处理系统

1
解决人机矛盾和CPU与I/O设备速度不匹配矛盾,旨在提高系统资源的利用率和系统吞吐量

单批道处理系统的缺点

1
系统中的资源得不到充分的利用。内存中仅有一道程序,每当程序在运行中发出I/O请求,CPU处于等待状态

多批道处理系统

1
用户提交的作业先存放在外存上,排成一个队列,称为“后备队列”。然后由作业调度程序按一定的算法,从后备队列中选择若干个作业调入内存 ,使他们共享CPU和系统中的各种资源

1
2
print("2" + 8)
-- 输出结果为10, 类型为number,lua中只有number这一种数字类型
1
2
3
-- 字符串连接
str = 123 .. 456
-- 输出结果为123456,类型为string
1
2
3
-- 获取字符串长度
str = "this is a test."
print(type(str))
1
2
local x = 10	-- 局部变量
x = 10 -- 全局变量
1
2
3
4
5
6
7
8
9
10
11
-- lua中的函数可以当作变量
function factorial1(n)
if n == 0 then
return 0
else
return n * factorial1(n - 1)
end
end
print(factorial1(10))
factorial2 = factorial1;
print(factorial2(10))
1
2
-- if ip is true return ip, if ip is nil return "unknow"
return ip or unknow

lua的八种数据类型

nil:空类型
boolean
number:整数和浮点数都用number表示
string:可以有三种表示方式

1
2
3
4
5
6
7
8
str1 = 'this is test1'
str2 = "this ia test2"
-- 第三种方式可以换行
str3 = [[
this
is
test3
]]

function:function表示lua中的函数,可以接受多个参数,也可以用...表示可变数目的参数,function可以返回多个参数
userdata:用户自定义数据
thread – todo
table 相当于数组,下标从1开始,里面的元素可以是各种类型的,函数也可以作为元素

1
2
3
4
5
tb = { 
function() return '123' end,
fucntion() print("abc) end,
function(a, b) return a + b end,
}

table还可以当作哈希表

1
2
3
4
5
6
7
8
tb = {
apple = 3,
banana = 4,
["tt"] = 5,
}
print(tb["apple"])
print(tb.banana)
print(tb.["tt"])

查看当前分支

git branch -- show-current

回退到上一个版本

1
git reset --hard HEAD~1

删除文件

1
git rm filename

删除分支

1
git branch -d branch_name

创建新分支

1
2
git checkout -b branch_name
git switch -c branch_name

回退到clone后的版本

1
git restore .

打开文件

1
with open('file_name','r',encoding = 'utf-8') as f:

with的作用相当于调用close(),文件会自动关闭

读写文件模式

模式 描述
t 文本模式 (默认)。
x 写模式,新建一个文件,如果该文件已存在则会报错。
b 二进制模式。
+ 打开一个文件进行更新(可读可写)。
U 通用换行模式(不推荐)。
r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
rb 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。
r+ 打开一个文件用于读写。文件指针将会放在文件的开头。
rb+ 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。
w 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
w+ 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb+ 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
a 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
ab 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+ 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+ 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于

常用的文件操作方式

1
2
3
4
5
6
7
8
9
10
print(f.readline()) #打印第一行
print(f.readline(6)) #打印前六个字符
print(f.readlines()) #读取所有内容,按行返回list
print(f.tell()) #打印当前指针位置
print(f.read()) #读取文件所有内容
print(f.encoding) #打印当前使用的字符编码
print(f.name) #打印文件名
print(f.flush()) #刷新
f.truncate() #清空文件
f.close() #关闭文件