计算机基础知识

 

C++基础

类中构造函数调用的顺序

1. 调用基类构造函数

2. 对象成员的构造函数

3. 派生类的构造函数

注:基类的构造函数与声明顺序有关,与初始化列表顺序无关

 

类赋值规则

派生类的对象继承了基类所有的特性,因此派生类对象可以直接给基类对象赋值。

 Base b;

 Deived d;

 b=d;

 

 Base *b;

 Deived d;

 b=&d;//指向派生类的基类指针

 

C++的多态性

- 编译时的多态性 (重载,函数与运算符)

- 运行时的多态性(虚函数)

 

虚函数的使用

- 到运行时才确定所要调用的函数

- 在基类中关键字virtual必须写,在派生类中可写可不写

- 通过指向派生类的基类指针实现

- 构造函数不能是虚函数,析构函数可以是虚函数

 

虚析构函数

在主函数中指向派生类的基类指针new一个派生类对象的时候,delete直接调用基类的析构函数,不会调用派生类的构造函数。

将基类析构函数声明为虚析构函数即可解决。(基类析构函数为虚函数,派生类函数为析构函数)

 

纯虚函数

纯虚函数不具备函数的功能,不能被调用,仅用来继承。

形式:virtual void show()=0;

 

抽象类

定义:包含了纯虚函数的类。

1.只能作为基类使用,不能建立对象

2.一般用来声明指向派生类的指针和引用

 

.h文件与.cpp关系

如果这两个文件没有inlcude,那这个文件就没有任何关系,包含也没有用

 

switch用法

- 如果case之后不加break;程序就会一直向后执行到default;

- case成立说明程序从那个case开始执行,但结束则根据break;

 

C++中string类的用法

数字变字符串

   #include <sstream>

  #include <string>

   int i=100;

  stringstream ss;

  ss << i;

  string result=ss.str();

 

字符串变数字

    string s

  int num;

  stringstream ss(s);

  ss>>num;

注:

1.<<,>>都是对数字进行的操作。

2.利用stringstream作为中介。

 

"?:"用法

前面是判断条件,如果成立则返回后面第一个数,如果不成立就返回后面第二个数

例如:(a=)(2>1)?(3):(4)

 

C++11 中for的用法

for(auto p:num) 可以自动遍历数组,vector等所有元素。

 

vector<>最大值与最小值极其索引

std::vector<double> v{1.0, 2.0, 3.0,4.0, 5.0, 1.0, 2.0, 3.0, 4.0, 5.0};  

std::vector<double>::iterator biggest= std::max_element(std::begin(v), std::end(v));  

std::cout << "Max element is" << *biggest<< " at position " <<std::distance(std::begin(v), biggest) << std::endl;  

auto smallest =std::min_element(std::begin(v), std::end(v));  

std::cout << "min element is" << *smallest<< " at position " <<std::distance(std::begin(v), smallest) << std::endl;

注:max_element()/min_element()返回最大/小值的迭代器;

    distance()返回两个元素地址(迭代器)之间的差;

 

命名空间的作用

#include <iostream>

namespace aaa

{

    void display();

}

namespace bbb

{

    void display();

}

void aaa::display()

{

    std::cout<<"Hello"<<std::endl;

}

void bbb::display()

{

    std::cout<<"sorry"<<std::endl;

}

int main()

{

    aaa::display();

    bbb::display();//不同的空间下虽然名字相同调用的却不是同一个函数

    return 0;

}

 

aaa空间里的display()函数可以和bbb空间的display()函数互不干扰,而std空间里的函数是C++自己库函数的命名空间,专业的术语就是指标识符的各种可见范围,

由于人类的单词有限,现在的大型程序开发,尤其是各种库之间,不可能没有重名的,而且大型程序不可能一个人完成,难免会有名字重复的变量或函数,这时就需要命名空间来区分。

 

输出当前的cpp文件中的行号

__LINE__: 在源代码中插入当前源代码行号;

__FILE__: 在源文件中插入当前源文件名;

__DATE__: 在源文件中插入当前的编译日期

__TIME__: 在源文件中插入当前编译时间;

 

命名空间namespace

- 如果程序中没有使用关键字namespace,则程序被认为位于一个无名的命名空间中。(大部分程序示例就是这样)

- 命名空间的声明可以是不连续的(也可以在不同文件中),命名空间的实际内容为所有声明的总和。

使用方法:

  1. 命名空间名称::成员名称

     例如:NS::PI

  2. 使用指令usingnamespace xx;声明,声明之后直接使用(不需要"name::")

 

C++中迭代器与指针

1. 迭代器也可以理解为指针,主要有自增,吸取,相等,不等这四种运算,比指针的功能更强大一些。

2. 指针是C语言里面就有的东西,而迭代器是C++里面才有的,指针用起来灵活,效率高。迭代器功能更丰富一些(不见得是强大一些),c++的stl里面很多算法都是基于迭代器的,一部分算法的参数可以传递指针作为迭代器使用。

C++运算符重载

运算符重载是创建运算符重载函数来实现的,运算符重载函数定义了重载的运算符将要进行的操作。

例:

    A operator+(A o1,Ao2)

    {

      ...............

      return temp;

    }

 

调用的两种方法:

    1.o3=operator+(o1,o2);//即完全当成普通函数使用,函数名为"operator+"

    2. o3=o1+o2;

    注:实际上编译系统将方法2中的程序解释为方法1的形式。

typedef与#define区别

1) #define是预处理指令,在编译预处理时进行简单的替换,不作正确性检查,不关含义是否正确照样带入,只有在编译已被展开的源程序时才会发现可能的错误并报错。

例如:

#define PI 3.1415926

程序中的:area=PI*r*r 会替换为3.1415926*r*r

如果你把#define语句中的数字9 写成字母g 预处理也照样带入。

 

2)typedef是在编译时处理的。它在自己的作用域内给一个已经存在的类型一个别名,但是You cannot use the typedef specifier inside a function definition。

循环体内工作量最小化

应仔细考虑循环体内的语句是否可以放在循环体之外,使循环体内工作量最小,从而提高程序的时间效率。

for (ind = 0; ind < MAX_ADD_NUMBER; ind++) 

sum += ind; 

back_sum = sum; /* backup sum */ 

语句“back_sum = sum;”完全可以放在for语句之后,如下。

for (ind = 0; ind < MAX_ADD_NUMBER; ind++) 

sum += ind; 

back_sum = sum; /* backup sum */ 

 

在多重循环中,应将最忙的循环放在最内层

减少 CPU 切入循环层的次数。 

示例:如下代码效率不高。 

for (row = 0; row < 100; row++) 

for (col = 0; col < 5; col++) 

sum += a[row][col]; 

}

可以改为如下方式,以提高效率。 

for (col = 0; col < 5; col++) 

for (row = 0; row < 100; row++) 

sum += a[row][col]; 

 

用乘法或其它方法代替除法

浮点运算除法要占用较多CPU资源。

示例:如下表达式运算可能要占较多CPU资源。

#define PAI 3.1416 

radius = circle_length / (2 * PAI); 

应如下把浮点除法改为浮点乘法。

#define PAI_RECIPROCAL (1 / 3.1416 ) // 编译器编译时,将生成具体浮点数

radius = circle_length * PAI_RECIPROCAL / 2; 

 

vector容器

 

 

List容器

-在任何位置插入/删除时间都是常数。

-不支持随机访问

-只支持双向迭代器,不能使用比较,移动运算符

 

 

deque容器

双向队列,可以理解为vector的扩充,deque是在功能上合并了vector和list。

优点:

(1) 随机访问方便,即支持[ ]操作符和vector.at()

(2) 在内部方便的进行插入和删除操作

(3) 可在两端进行push、pop

缺点:

(1) 占用内存多

 

三个容器使用区别:

1 如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector

2 如果你需要大量的插入和删除,而不关心随即存取,则应使用list

3 如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque

 

普通static函数

在函数的返回类型前加上关键字static,函数就被定义成为静态函数。

函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。

好处

<1> 其他文件中可以定义相同名字的函数,不会发生冲突

<2> 静态函数不能被其他文件所用。

 

容器适配器

Stack:

-后进先出(可以用vector,list,deque实现)

-只能插入、删除、访问栈顶元素

-三个主要成员函数

n  void push()

n  void pop()

n  T &top()//返回栈顶元素的引用

Queue:

-先进先出

-list,deque实现(默认使用dequeue)

-push发生在队尾;pop(),top()发生在队头

priority_queue:

-vector与deque(默认情况使用vector实现)

-通过堆排序,保证最大元素总在最前面

-pop()删除最大的元素;top返回最大元素的引用

 

C++11特性

1.统一的初始化方法:

例如:

vector<int>i{1,2,3};

string str{“dadasd”};

2.成员变量可以有默认的初始值: (从JAVA里面学习的)

class B

{

public:

int m=3;

}

3.auto关键字

注意:必须初始化,编译器自动推倒类型

4.decltype关键字:

作用:反推类型

int i;

decltype(i) x;//此时x就是整形变量

5.智能指针(shared_ptr):

- 头文件<memory>

- 是一个类模板

- 不能指向动态分配内存的数组

当没有指针指向new出来的对象的时候,就自动delete掉

例如:

对比1:

shared_ptr<A>sp1(new A(2));

shared_ptr<A>sp2(sp1);//此时由sp1与sp2才能同时托管

对比2:

A *p=new A();

shared_ptr<A>sp1(p);

shared_ptr<A>sp2(p);

//不会同时托管,或者说不会增加托管计数。

tips:new运算符在内存中开辟一块空间的同时,会返回一个地址

6.空指针nullptr:

可以自动转换成bool类型

bool x=nullptr;//x=false

7.基于范围的for循环

例如:

int arry[]={1,2,3,4};

for(int  e:arry) …;

for(int & e:arry) …;

vector<A>st(5);

for(auto& it:st);

7.右值引用和move语义

右值:不能取地址的表达式

左值:能取地址的

A& r=A();//错误

A&& r=A();//不需要深拷贝

move()//把左边值变为右值

8.无序容器(哈希表)

#include<unorderd_map>//必须包含此头文件

unorderd_map<string,int>tu;

tu.insert(make_pair(“Scot”,1972));

注:插入和查询的时间复杂度是常数。

 

9.正则表达式

#include<regex>//必须包含这个

 

刷新输出缓冲区

cout<<”HI”<<endl;//输出换行符,刷新缓冲区

cout<<”HI”<<ends;//输出空格,刷新缓冲区

cout<<”HI”<<flush;//刷新缓冲区(不添加任何字符)

  

获取当前的时间

使用示例:

time_t timer;

struct tm *lt;

lt = localtime(&timer);

cout<<"当前时间:"<<lt->tm_hour << "时 " << lt->tm_min << "分 "<< lt->tm_sec << "秒"<<lt->tm_year+1900<<"年"<<lt->tm_mon+1<<"月 "<<lt->tm_mday<<"日";

 

&与*的多重含义

上面两个符号需要结合上下文理解

-    在变量的声明中

&:表示声明引用

*:表示指针变量

-    在函数中

&:表示取其地址

*:表示取地址对象

 

const 类型在其他cpp文件中引用

声明时:extern const int a=3;

在其他cpp文件中引用时: extern const int a;

 

指针与引用的区别

指针是一个独立的对象,存放的某一个对象的地址。

引用相当于取别名,并没有生成新的对象,是原对象的另一个标签。

 

typedef、using、#define三者区别

typedef与using都只是在局部作用域

#define是预编译命令,整个cpp文件

使用区别:

#define 自定义名 原始名字

using 自定义名 原始名字

typedef 原始名字 自定义名字 //就你最特殊

typedef使用方法:把原来为变量名的地方替换为自定义的类型名

typedef unsigned int * a;--->typedefunsigned int * UN_p;

 

const与类型别名混合使用的区别

typedef char* pchar;

const pchar p;//不可以直接将别名原型直接带入

上句指的是指向char类型的常量指针(指针内容(地址)不可以变,地址存放的内容可以变)

区别于:

const char *p;

上句指的是指向const char(常量字符)的指针,(指针地址可以变,但不能通过访问地址改变其所指向的内容)

 

#if等预编译宏指令

判断内容只能用#define宏定义,不能使用int a=1;,因为这是预编译,在编译过程中就结束了。

注:预处理变量无视c++中关于作用域的规则

 

定义与声明的区别

声明是告诉编译器存在这么一个标识符。

定义则是为程序申请一块内存。

int x; //这是一个定义

extern int x = 10; //这也是一个定义 

extern int x; //这是声明(用extern且不赋值才是声明)  

因为定义只能一次,而声明可以有很多次,不能把定义放在.h文件中,多次包含会出问题。

以上说的是不能在头文件中定义变量。但是有三个例外。头文件可以定义类、值在编译时就知道的const对象和inline函数。

 

编译流程

 

预处理:预处理相当于根据预处理命令组装成新的C程序,不过常以i为扩展名。

编译:将得到的i文件翻译成汇编代码.s文件。

汇编:将汇编文件翻译成机器指令,并打包成可重定位目标程序的o文件。该文件是二进制文件,字节编码是机器指令。

链接:将引用的其他O文件并入到我们程序所在的o文件中,处理得到最终的可执行文件。

c(源文件) → I(经过预处理) → S(经过编译得到汇编代码) → o(经过汇编得到机器指令) → e(经过链接后得到可执行程序)

 

string对象

是标准库的一部分,在头文件#include<string>中

包含很多成员函数:判断大小写,判断字符,空格,拼接字符串

 

迭代器

指向某种容器元素的指针(本质上是类的静态成员

vector<int>::iterator it;

string::iterator it2;

注意:

1.所有的标准库容器都支持迭代器

2.使用迭代器访问类函数(*it).empty();,如果不加这个,就变成(*(it.empty()))

3.循环体中使用迭代器,就不要向容器中添加元素。

4.v.end()//尾后迭代器,指向的一个容器根本就不存在的尾后

5.如果容器为空,begin和end返回的都是尾后迭代器。

 

数组默认的索引

不一定是无符号型的整数

int a[10];

int *p=&a[3];

p[-1];//正确,可以正常使用,可以理解程*(p-1);

 

循环内部定义的变量

定义在while循环条件部分与循环体内部的变量每次迭代都经历从创建到销毁的过程。

 

异常处理

try{ //在此1.运行代码并2.抛出错误}

catch{//在此3.处理错误}

例如:

int fun1(int a)

{

if(a==0) throw 1;//可以抛出各种数据类型

}

try{

fun1(0);

catch(int i)

{

if(i==1)cout<<”输入的是0”;

}

 

函数声明的头文件

函数声明的头文件应在

1.函数定义的cpp中

2.使用函数的cpp中

 

参数传递

引用传递:引用形参是实参的别名

值传递:实参的值拷贝给形参,形参和实参是两个独立的对象。

 

访问(修改)函数外部对象

C程序员用指针的值传递;

void fun1(int *p){}

voidm main(){int a=1;fun1(&a); }

C++中推荐用法(引用类型的形参代替指针)

void fun1(int &a){}

voidm main(){int a=1;fun1(a); }

 

数组形参调用

void print(const int *p);

void print(const int p[]);

void print(const int p[10]);//上面三者等价,本质上就是一个指针

 

数组的引用

void print( int (&a)[10]);//括号一定要加

 

main函数的参数传递

当使用argv时,实参从argv[1]开始,argv[0]保存程序名,而非用户输入。

 

C++中强制转换

static_cast<目标类型>

例如:

const char *p;static_cast<string>(p);//char改成string

 

数组指针与指针数组的区别

int *ip[4];//指针数组

int (*ip)[4];//指向四个元素的指针

 

sizeof()

后面可以加类型,也可以加对象

 

 

函数返回值

当返回为值的时候,函数会返回一个临时构建的对象。

当返回为引用的时候,函数不会生成对象。

 

列表初始化返回值

例如:

vector<Point> fun1()

{

   Point p1,p2,p3;

   return{p1,p2,p3};

}

 

assert()调试函数

assert()宏是用于保证满足某个特定条件,用法是:assert(表达式); 如果表达式的值为假,整个程序将退出,并输出一条错误信息。如果表达式的值为真则继续执行后面的语句。

注:这个仅仅在debug模式下可以使用

 

函数指针及其使用

函数指针的声明:

bool (*pf)(int a,int b);//注意括号必须加,否则变为函数的声明了。

使用:

int fun1(int,int);

pf=&fun1;与pf=fun1;//这两者是等价的

pf(1,2);与(*pf)(1,2);//这两者等价

函数指针作为返回值

using pf=int (*)(int ,int);

using f=int (int ,int );//f代表函数

pf f1(int);

f* f1(int);

比较复杂点的用法:

1.int (* f1(int))(int,int);

上面遵循由内向外的顺序

f1有形参列表,所以f1是一个函数

f1前面有*,说明返回的是一个指针

最外面说明函数返回的是一个指向xx函数的函数指针

2.auto f1(int)->int(*)(int,int);

尾置返回类型(便于理解)

3.decltype(fun)* f1(int);

注意,decltype()返回的是函数的类型。要显示加指针

 

类中成员函数的调用过程

class A{};

A a;

a.initial();

A::initial(&a);//伪代码,可以等价认为。

成员函数执行的时候,隐式传入指向当前对象的this指针。

 

const成员函数

在成员函数的形参列表后面加const表示不能修改对象的数据成员(准备说是非静态成员)

普通对象,可以调用非const成员函数const成员函数

const对象,只能调用const成员函数

返回值:如果const函数返回引用,返回的是一个常量引用。

 

struct与class定义类的区别

默认的访问权限不一样

 

友元函数

在类声明中加friend,可以是普通函数,也可以是其他类的成员函数。

 

构造函数初始化必不可少

类中的常量对象,引用必须在构造函数中初始化(而且必须通过构造函数列表)

 

聚合类

条件:

1.所有成员都是public

2.没有构造函数(全部由用户初始化)

3.没有类内的初始值

4.没有基类,也没有virtual函数

例:

struct A{int a;int b};  A a={1,2};

 

隐式类的类型转换

转换构造函数:构造函数值接受一个实参(准确说应该是其他类的实参)

 

带有explicit 的成员函数

不会进行隐式的转换

例如:

class A {A(int n);};

int a=3;char b=3;

A(a)与A(b)都能正常执行,因为实参会隐式转换成实参。

但如果加了explicit,A(b)这种写法就会报错,必须类型完全一样才行。

 

“.h”用法

包含目录下(包括当前目录),不要在项目中添加,直接#include即可。

注:由此可知,很多时候,我们在项目中的头文件中添加,都是没有必要的,因为解决方案管理器中的头文件,只是起到说明提示的作用,没有实际作用

库目录(包含当前目录),直接#program lib(附加依赖项也行)即可。

 

IO库

C++不直接处理输入输出,通过定义在标准库中的类型来处理IO,支持从设备读写数据。设备可以是文件,控制台窗口等。

   

静态成员必须类外初始化

静态成员必须在类外初始化(定义)

1.在类内部声明静态变量,并没有定义。

2.声明只是说明变量的数据类型属性,并没有分配内存

3.静态变量并非成员变量,独立于类的对象。

注:一个例外的情况:const static 成员就可以在类的定义体中进行初始化

 

使用SWAP函数

例如:

vector<string> vec1(10);

vector<string> vec2(20);

swap(vec1,vec2);

元素本身并未交换,只是交换了容器的内部数据结构。

注:除了array外,swap不对任何元素进行拷贝、删除、或插入操作,因此可以保证在常数时间内完成。

 

顺序容器插入元素

向vector、string、或deque插入元素会使所有元素指向容器的迭代器、引用、指针失效。

  

emplace操作(C++11)

对比组:

v1.push_back(Point(1,1));//调用构造函数创建一个局部的临时对象,然后把这个对象被拷贝到容器中。

v2.emplace_back(1,1);//调用构造函数,在容器的内存空间中直接创建对象

 

front和back成员函数

访问元素的成员函数返回的都是引用

front返回容器的首成员

back返回容器的最后一个成员

例:

auto& v=c.back();//如果希望改变值,必须加引用号

auto v2=c.front();

注:不能对空容器进行操作,其行为将是未定义的。

Tips:当函数返回类型为引用时候,也可以直接对对象赋值

 

从容器中删除元素

例:(删除一个元素)

auto it=lst.begin();

lst.erase(it);//删除此元素,返回的是(it+1)位置的迭代器。

例:(删除多个元素)

auto it=slist.erase(elemnt1,element2);

注:这是一个左闭右开的区间,最后一个位置(element2)不删除,返回也也是迭代器element2

 

调整容器大小

list<int> ii(10,42);

ii.resize(15);//容器的大小变为15,将5个0添加到最后

ii.resize(10);//容器的大小变为10,删除最后的五个元素

注:此操作会导致迭代器,指针,引用失效。

 

容器中的循环

每步循环中都必须更新迭代器,这也是成员函数返回都是迭代器的原因。

 

容器容量

capacity    //是当前无需再次扩容的情况下的容量

reserve(n)//人工强行分配至少这么多数目,如果分配<capacity就不操作,如果>当前capacity就再次分配,但分配的结果是>=n,因为n是至少分配这么多。

size()     //表示使用了多少,(<=capacity)

 

string的数值转换

数字转string:

to_string();//可以是浮点,整形等【重点记忆】

string转数字:

stoi();//返回整型【重点记忆】

stol();//……

stof();//……

stod();//……

 

在容器中查找一个元素

例:

int val=42;//要查找的元素

auto result=find(c.cbegin(),c.cend(),val);

//如果能够找到就返回元素的迭代器,如果不能找到就返回最后一个迭代器(c.cend());

同样试用于数组

例如:

int a[]={1,23,5,3,13,2};

int *p=find(begin(a),end(a),1);

注:最后一个地址(迭代器)不访问

 

比较容器中元素是否相等,equal()函数

equal(v1.cbegin(),v1.cend(),v2.cbegin())//第二个序列中的长度≥第一个的。

 

容器的常用算法

accumulate(v.begin(),v.end(),0)// 求和,最后一个元素为初始值。

fill(v.begin(),v.end(),0)填充容器,将每个元素置零

replace(v.begin(),v.end(),0,42)//后两个表示搜索值与替换值。

copy(v.begin(),v.end(),v2.begin)//把v拷贝到v2中

消除重复元素

sort(word.begin(),word.end())  

//按照字典顺序排序,使得相同元素靠在一起。

autoend_unique=unique(word.begin(),word.end());

 //把重复的元素移动到最后面,返回第一个重复位置的迭代器

注:unique的作用是将相邻的重复项消除并返回指向无效位置的迭代器

words.erase(end_unique,word.end()) 

//擦除第一个重复位置至最后一个。实现去除重复元素的功能

 

自定义排序等函数(谓词)

bool fun1(const string &s1,const string&s2)

{

return s1.size()<s2.size()

}

sort(words.start(),words.end(),fun1);

 

string对象输出

string对象没有重载运算符<<,但可以通过c_str();

例:

string word;

cout<<word.c_str();

 

lambda表达式

形式:[capture list](parameter list)->return type{function body}

可以忽略参数列表和返回类型,但必须包含捕获列表函数体

auto f=[]{return 42};

捕获列表为lambda表达式所在函数的局部变量。

例:

int a = 123456;

   auto f = [a]{return a; };

   cout << f() <<endl;

  

for_each()用法

前两个表示范围,第三个为函数指针

例:

vector < string >words ={"aa","bcb","c"};

for_each(words.begin(), words.end(),[](string &s1)

   {cout << s1.c_str()<< endl; });

 

标准库中算法函数命名规范

_if版本(是否使用默认的比较)

find(begin,end,val)//找到val第一次出现的地方

find_if(begin,end,pred)//找到pred第一次为真的地方

_copy 拷贝元素与不拷贝元素版本

reverse(beg,end);//反转容器中的元素

reverse_copy(beg,end,dset);//把容器中的元素反转,拷贝到dset[原容器不变]

 

map容器与set容器

map容器与数组类似,数组的索引为int类型,而map的索引可以为制定类型。

set就是一个集合, map与set都有关键字,map有值,而set没有值,但两者中关键字是不能重复。

map与set混用,找出不在集合中的单次,例:

map<string,size_t>word_count;

set<string> exclude={“a”,”b”,“c”};

string word;

while(cin>>word)

if(set.find(word)==set.end())map[word]++;

 

pair类型

定义在utility中,一个pair保存两个数据成员,第一个为first,第二个为second。pair<T1,T2>p(v1,v2);

 

向关联容器添加元素

例:

向set添加元素

vector<int>ivec={1,2,3,4,5};

set<int>set2;

set2.insert(ivec.cbegin(),ivec.cend());

set2.insert({1,2,3,4,5});

Insert函数有两个版本,分别接受一对迭代器,或是一个初始化列表

 

向map添加元素:(类型必须是pair)

以下是四种方法:

words.insert({word,1});

words.insert(make_pair(word,1));

words.insert(pair<string,size_t>(word,1));

words.insert(map<string,size_t>::value_type(word,1));

 

STL容器区别

我们常用到的STL容器有vector、list、deque、map、multimap、set和multiset。

verctor

vector类似于C语言中的数组,它维护一段连续的内存空间,具有固定的起始地址,因而能非常方便地进行随机存取,即 [] 操作符,但因为它的内存区域是连续的,所以在它中间插入或删除某个元素,需要复制并移动现有的元素。此外,当被插入的内存空间不够时,需要重新申请一块足够大的内存并进行内存拷贝。值得注意的是,vector每次扩容为原来的两倍,对小对象来说执行效率高,但如果遇到大对象,执行效率就低了。

list

list类似于C语言中的双向链表,它通过指针来进行数据的访问,因此维护的内存空间可以不连续,这也非常有利于数据的随机存取,因而它没有提供 [] 操作符重载。

deque

deque类似于C语言中的双向队列,即两端都可以插入或者删除的队列。queue支持 [] 操作符,也就是支持随机存取,而且跟vector的效率相差无几。它支持两端的操作:push_back,push_front,pop_back,pop_front等,并且在两端操作上与list的效率也差不多。或者我们可以这么认为,deque是vector跟list的折中。

map

map类似于数据库中的1:1关系,它是一种关联容器,提供一对一(C++ primer中文版中将第一个译为键,每个键只能在map中出现一次,第二个被译为该键对应的值)的数据处理能力,这种特性了使得map类似于数据结构里的红黑二叉树。

multimap

multimap类似于数据库中的1:N关系,它是一种关联容器,提供一对多的数据处理能力。

set

set类似于数学里面的集合,不过set的集合中不包含重复的元素,这是和vector的第一个区别,第二个区别是set内部用平衡二叉树实现,便于元素查找,而vector是使用连续内存存储,便于随机存取。

multiset

multiset类似于数学里面的集合,集合中可以包含重复的元素。

小结

在实际使用过程中,到底选择这几种容器中的哪一个,应该根据遵循以下原则:

1、如果需要高效的随机存取,不在乎插入和删除的效率,使用vector;

2、如果需要大量的插入和删除元素,不关心随机存取的效率,使用list;

3、如果需要随机存取,并且关心两端数据的插入和删除效率,使用deque;

4、如果打算存储数据字典,并且要求方便地根据key找到value,一对一的情况使用map,一对多的情况使用multimap;

5、如果打算查找一个元素是否存在于某集合中,唯一存在的情况使用set,不唯一存在的情况使用multiset。

 

Insert函数

Insert()的返回值依赖与容器和参数,返回一个pair对象,第一个是指向关键字的迭代器,第二个返回是够插入成功的bool值。(如果key存在就不插入,返回false)

 

Multimap

允许一个关键字多次出现,一个关键字可以对应多个不同的元素

 

Map的下标运算

返回值类型是左值,可以直接改变pair的值。

:对map进行下标访问的时候,如果关键字不存在,则会向map中添加该元素

find()当搜索不到关键字的时候,不会自动添加

 

multimap或者multiset查找元素

方法一:[起点与个数]find()与count()搭配使用

P390,count函数返回元素对应的个数

方法二:[起点与终点]lower_bound()与upper_bound()函数

分别指向第一个与最后一个位置,如果元素不在里面则指向同一个位置

方法三:[起点与终点]equal_range函数,返回一个迭代器pair

pair里面放的实际上是两个迭代器(指针)

第一个迭代器是第一个位置,第二个迭代器是最后一个位置,若没有找到则指向可插入的位置。

 

使用动态生存期的类的资源的类

使用动态内存出于以下三种原因

1.程序不知道自己需要多少对象//此处需要扩充一下

2.程序不知道对象的准确类型

3.程序需要在多个对象之间共享数据

 

shared_ptr和new结合使用

不能将一个内置指针隐式转换为一个智能指针

shared_ptr<int> p1=new int(1024);//错误,必须使用直接初始化的方式

shared_ptr<int> p1(new int(1024));//正确,使用了直接初试化

 

 

智能指针向普通指针的转换

向不能使用智能指针的代码传递一个普通指针,使用.get()函数,返回一个普通指针,但这个指针不能被delete。

 

拷贝构造函数在以下三种情况被自动调用

1.  用一个对象去初始化另外一个对象的时候。

2.  函数值传递中为某个类的对象

3.  函数返回值为某个类的对象(会创建一个临时变量)

:变量创建完成后,立即释放函数的局部变量

 

对象之间的之间赋值(=)

内容通过重载运算符=实现,当对象中包含const成员的时候,默认的重载函数就会失效。

 

dll与lib文件的区别

(1)lib是编译时需要的,dll是运行时需要的。

如果要完成源代码的编译,有lib就够了。

如果也使动态连接的程序运行起来,有dll就够了。

在开发和调试阶段,当然最好都有。

开发和使用dll需注意三种文件    

  1、dll头文件

  它是指dll中说明输出的类或符号原型或数据结构的.h文件。当其它应用程序调用dll时,需要将该文件包含入应用程序的源文件中。    

  2、dll的引入库文件

  它是dll在编译、链接成功后生成的文件。主要作用是当其它应用程序调用dll时,需要将该文件引入应用程序。否则,dll无法引入。

  3、 dll文件(.dll)    

  它是应用程序调用dll运行时,真正的可执行文件。dll应用在编译、链接成功后,.dll文件即存在。开发成功后的应用程序在发布时,只需要有.exe文件和.dll文件,不必有.lib文件和dll头文件。 

  

C++ 写入csv文件

FILE *tempF = fopen("D:\\结果图片\\resruslt.csv","r");

if (NULL == tempF)//如果不存在就增加第一行

{

   FILE* fpt = fopen("D:\\结果图片\\resruslt.csv","a");

   fprintf(fpt,"%-s,%-s,%-s,%-s,%-s,%-s,%-s,%-s,%-s,%-s,%-s,%-s,%-s\n", "图像编号",

   "耗时     ", "得分      ", "位移X       ", "位移Y     ", "旋转角度        ", "ROI1", "ROI2","ROI3", "ROI4", "锚点X", "锚点Y","分辨率");

    fclose(fpt);

}

else fclose(tempF); //检测是否存在

FILE* fpt = fopen("D:\\结果图片\\resruslt.csv","a");

for (auto& p : matchresult)

{

   fprintf(fpt, "%d,%f,%f,%f,%f,%f,%d,%d,%d,%d,%d,%d,%d\n",

        i,

       large.time_sum,

       p.scores,

       p.translationX,

       p.translationY,

       p.rotation,

       large.p_trainResult->ROI_Rect.y,

       large.p_trainResult->ROI_Rect.x,

       large.p_trainResult->ROI_Rect.height,

       large.p_trainResult->ROI_Rect.width,

       large.p_trainResult->anchorPoint.x,

       large.p_trainResult->anchorPoint.y,

       (large.p_trainResult->srcImage.rows>2000 ? 500 : 130)

       );

}

fclose(fpt);

 

对成员进行拷贝、移动、赋值、销毁等操作(拷贝与析构)

拷贝构造函数,移动构造函数

用同类型的对象初始化本对象时

 

拷贝赋值运算符,移动赋值运算符(就是重载=运算符)

将一个对象赋值给同类型另外一个对象。

 

lambda表达式

就是匿名函数,多用于for_each(),sort()等标准库函数

使用方法:

[capture](parameters) mutable->return-type{statement}

1.[capture]:捕捉列表。捕捉列表总是出现在Lambda函数的开始处。实际上,[]是Lambda引出符。编译器根据该引出符判断接下来的代码是否是Lambda函数。捕捉列表能够捕捉上下文中的变量以供Lambda函数使用;

[=]通过值捕捉所有变量

[&]通过引用捕捉所有变量

[value]通过值捕捉value,不捕捉其它变量

[&value]通过引用捕捉value,不捕捉其它变量

[=, &value]默认通过值捕捉,变量value例外,通过引用捕捉

[&, value]默认通过引用捕捉,变量value例外,通过值捕捉

2.(parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号“()”一起省略;

3.mutable:mutable修饰符。默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空);

4.->return-type:返回类型。用追踪返回类型形式声明函数的返回类型。我们可以在不需要返回值的时候也可以连同符号”->”一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导;就是C++11中的后置返回类型

 

template<class T, class U>

auto multiplt(T a,U b)->decltype(a*b)

{

   return a*b

}

5.{statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。

记忆方法:void fun(){}//常规函数 ,把返回类型后置,函数名改成[]。

 

复杂定义的读法

char   (*(*x())[])() //右左法则 
从x开始: 
1.向右看到(),   x是一个函数定义,   没有参数; 
2.向左看到*,   x的返回值是指针; 
3.向右看到[],   指针指向数组; 
4.向左看到*,   数组的元素也是指针; 
5.向右看到(),   指针指向的是函数,   即函数指针,   而且函数没有参数. 
6.向左看到char,   函数的返回值是char. 
所以,   x是一个没有参数的函数,   它的返回值是指向一个数组的指针,   而这个数组的元素是函数指针,   指向的函数没有参数,   且返回char. 

 

来源: http://blog.csdn.net/gxiaob/article/details/8507658

 

虚函数动态绑定

当且仅当通过指针或者引用调用虚函数的时候,才会在运行时解析调用,这种情况下动态类型才有可能与静态类型不同,实际调用是指针所绑定的真实类型。

多态要满足两个条件:

1. 声明为虚函数(JAVA中所有函数都是虚函数)

2. 通过基类指针引用调用。

 

override与final

都是向JAVA学习,

overrideC++11 支持,用于确保覆盖了基类的虚函数。

final是不允许子类覆盖此虚函数。

 

虚函数的默认参数

由静态绑定决定,也就是执行父类的默认形参,却执行子类的函数。

 

回避虚函数机制

有时候为了避免虚函数执行“最新”虚函数版本,可以执行执行其中一个版本。

例如:

base_p->Derive::v_a();

 

模板的使用示例

使用方法:

template <class ****>

template<typename ****> //推荐下面的用法

注:

T::A* obj;       

容易混淆类的静态成员还是类中类(嵌套类)

typename T::A* obj; //这样就不会产生冲突

 参考:WhyC++ Supports both Class and Typename for Type Parameters – Stan Lippman's BLog

 

template<class MyType>

class Stack //自定义的vector

{

public:

   MyType* top=NULL; //指向栈顶

   MyType* base=NULL;//指向栈底

   int maxSize;

   Stack(); //在构造函数中初始化

   void push_back(const MyType& element);

   void pop();

   ~Stack();//析构函数

};

template<class MyType>

Stack<MyType>::Stack()

{

   base = new MyType[10];

   if (base=NULL)

    {

       cout << "初始化失败" << endl;

       exit(0);

    }

   maxSize = 10;

   top = base + 10;

}

 

delete与new

delete与new不是函数,C++中对“运算符”进行重载,是关键字//sizeof()不是函数。

 

new

  1. 申请空间
  2. 调用构造函数初始化(基本类型没有)
  3. 返回地址

 

delete

  1. 调用析构函数(基本类型没有)
  2. 删除空间

 

注:

通过new一个数组的时候,delete能够自动识别数组的个数,是因为 C++ 的做法是在分配数组空间时多分配了 4 个字节的大小,专门保存数组的大小,在 delete [] 时就可以取出这个保存的数,就知道了需要调用析构函数多少次了。

 

避免相等问题引起bug

if('x'==c)比if(c=='x')好,因为如果漏写=,前者编译器会报错

 

函数参数值传递-指针传递

void fun(int *p);

注:

注意这里也只是值传递,把指针重新拷贝了一份,所以不会改变指针变量的地址。

再次强调,这个只能改变指针所指向的对象。并不能改变指针变量的地址。

 

string与char*转换

string  -> char *

string str;

const char*p=str.c_str();

 

char *->string 

char *p="12345";

string str=p;

 

string与数字转换

 字符串 -> 数字

string a;

atoi(a.c_str())

atof(a.c_str())

数字 -> 字符串

string a=to_string("123");

 

字符串的分割

strtok()函数

需要两个调用,第二次输入字符指针改成NULL;

 

char str[]="12345";

char * p;

p = strtok(str, split);

p = strtok(NULL,",");

   

 

 C++内存5个区

1.,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。

2.,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete.如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

3.代码区 存放程序的二进制代码

4.全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。

5.常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改)

 

引用的内部实现

A a;

A &i=a; //内部通过指针常量实现

A *p_i=&a;

 

构造函数

构造函数为创建对象时,自动调用,完成初始化工作。

包括:

构造函数:用其他对象初始化

拷贝构造函数:用本对象初始化

移动构造函数:用本对象初始化(完成后对象资源转移)

 

左值、右值、移动函数

左值:可以取地址的变量。

右值:无法取址的临时变量(基本变量在CPU寄存器中,不在内存中)

move函数:可以将一个左值变成右值引用,也可以通过(T&&)进行类型的强制转化。

移动构造函数与移动赋值函数,相对于拷贝构造函数与拷贝赋值函数,前者调用后资源不存在。

 

注:

在swap对象的时候调用移动构造函数,会避免资源的深拷贝,提高性能,如下所示。

 

template <class T> swap(T& a,T& b)

{

      T tmp(std::move(a)); //move a to tmp

      a = std::move(b);   // move b to a

      b = std::move(tmp); // move tmp to b

 }

 

智能指针

1.智能指针托管的地址都是堆上的地址。

2.不支持自动将指针转换为智能对象,必须显示调用

常见的几种智能指针:

auto_ptr,shared_ptr,unique_ptr,weak_ptr

- auto_ptr与unique_ptr是通过转让所有权,只有一个智能指针可拥有。

- auto_ptr支持赋值运算,已经被舍弃,unique_ptr不支持赋值与拷贝构造函数,但支持转移构造与转移赋值

- shared_ptr赋值时,计数将加1,而指针过期时,计数将减1。当减为0时才调用delete。

 

代码元编程

C++模板中分函数模板(functiontemplate)类模板(class template)

模板参数

- 类型参数(type templateparameter),用 typename 或 class 标记;

- 非类型参数(non-typetemplate parameter)可以是:整数及枚举类型、对象或函数的指针、对象或函数的引用、对象的成员指针,非类型参数是模板实例的常量;

- 模板型参数(templatetemplate parameter),如“template<typename T, template<typename> class A>someclass {};”;

- 模板参数可以有默认值(函数模板参数默认是从C++11 开始支持);

- 函数模板的和函数参数类型有关的模板参数可以自动推导,类模板参数不存在推导机制;

- C++11 引入变长模板参数,请见下文。

 

string 类的用法

注意包含<string> 否则会失败

1. find

找到元素第一次出现的位置则返回元素索引,没有找到则返回 string::npos;

 

string s="0123456";

cout<<s.find("2")<<endl;//输出为2

cout<<s.find("3",2)<<endl;//输出为 3 ,从第2个位置开始搜(包括第2个位置)

cout<<s.find("z")<<endl;//输出为0xffffffff(string::npos),表示没有找到

2. find_first_of

当字符串中有一个元素与另外一个字符串中的任意一个元素匹配则输出

 

   string s = "01223456";

   cout << s.find_first_of("321") << endl; //返回为1

   cout << s.find_first_of("321",2) << endl; //返回为2 ,因为是从第二个字符串开始

注:注意find必须要完全匹配,find_first_of只要匹配其中一个字符就可以。

3. substr

 

string s = "01223456";

cout << s.substr(2) << endl; //输出为223456, 表示从2开始,到最后结束

cout << s.substr(3,2) << endl; //输出为23,表示从3开始,字符串长度为2,包括3本身

4.erase

erase(pos,n); 删除从pos开始的n个字符,比如erase(0,1)就是删除第一个字符
erase(position);删除position处的一个字符(position是个string类型的迭代器)
erase(first,last);删除从first到last之间的字符(first和last都是迭代器)

 

string str("0123456789");

// 第(1)种用法

str.erase(2, 3);

cout << str << endl;       // "015789",从2开始删除连续的三个元素

// 第(2)种用法

str.erase(str.begin() + 2);

cout << str << endl;       // "013456789"

// 第(3)种用法

str.erase(str.begin() + 1, str.end() - 2);//注意这是一个!!!左闭右开!!!的区间

cout << str << endl;       // "09"

注意:

如果参数输入为迭代器,返回则为指向被删除位置的迭代器

如果参数为数字,则返回为字串

 

数组名相关

数组名:为指向第一个元素的指针常量,也就是数组第一个元素的地址。

但在以下两中场合下,数组名并不是用指针常量来表示

1.      sizeof操作符

2.      单目操作符&

注意区分:

1.  int a[3];  

2.  a;  

3.  &a;  //地址相同,但指向类型却不同

-         对于用户没有明确给出&的编码,编译器翻译自动给变量a加上取值符$,其中取a的地址得到的指针类型由数组元素决定。

-         对于用户明确给出&的编码,编译器将会把取a的地址得到的指针类型看作指向数组的指针。

总结:编译器通过用户是否给出&,来决定指针变量的类型,进而翻译为相应的汇编码。或者换句话说,&符只是用来表明变量a取地址后得到的值,被看作什么类型的指针,而不是用来表示对a进行取地址操作。

更多参考:http://blog.csdn.net/daniel_ice/article/details/6857019

 

N维数组可以看成一维数组,其元素是N-1维的数组

 

 

 

 

操作系统

 

静态链接库与动态链接库区别

 

静态

动态

定义

在程序链接过程中,将用到的库函数合并到可执行文件中

链接时记录一些重定位消息,需要执行函数时,操作系统动态加载

 

优点

1. 可移植性强不需要依赖库文件,可以单独执行,

2. 不需要动态加载,不影响性能

1.       文件体积小

2.       多个程序可以共享同一段代码,节省内存

3.       修改库文件后,直接替换使用

对比内容四个方向: 空间占用,性能,移植性,维护

linux中多线程编程

int pthread_create(pthread_t *tid, constpthread_attr_t *attr, void *(*func) (void *), void *arg);

作用:创建一个线程,并有系统调用(一旦调用此函数,线程就开始运行) //默认是不可以分离的

输入:线程对象指针,线程属性,调用函数指针,函数参数。

返回值:返回值为0代表正常,返回负数代表异常

示例:

void *thread(void *ptr)

2

{

3

    for(int i = 0;i < 3;i++) {

4

        sleep(0.1);

5

        cout <<"This is a pthread." << endl;

6

   }

7

    return 0;

8

}

9

10

int main()

11

{

12

    pthread_t id;

13

    int ret = pthread_create(&id,NULL, thread, NULL);

14

}

 

int pthread_join (pthread_t tid, void **status);

作用:用于等待某个线程退出,是当前线程阻塞,等待tid线程结束,能够实现线程的合并

输入:待合并线程, 用户定义的指针,用来存储被等待线程的返回值(注意是指针的指针)

返回值:0代表正确,其他值代表错误

示例:

 pthread_t id;

 pthread_join(id, NULL);

 

线程终止的三种方式

1. 线程只是从启动例程中返回,返回值是线程的退出码;

2. 线程调用了pthread_exit函数;

3. 线程可以被同一进程中的其他线程取消。

 

进程

程序段、数据段、PCB(process control block)构成进程的实体

所谓创建进程就是创建进程实体中的PCB,撤销进程就是撤销进程中的PCB

 

进程的三种状态

 

 

用户级线程、核心级线程

 内核级线程(KLT):切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态;可以很好的利用smp,即利用多核cpu。windows线程就是这样的。

 用户级线程(ULT):内核的切换由用户态程序自己控制内核切换,不需要内核干涉,少了进出内核态的消耗,但不能很好的利用多核Cpu,目前Linux pthread大体是这么做的。 

 

临界区


临界资源(critical resource):只允许一个进程使用的资源称

临界区:每个进程中访问临界资源的代码

 

C++11中多线程——Mutex

mutex

std::mutex: 普通互斥量,不支持同一线程多次锁定

std::recursive_mutex: 递归 Mutex 类,支持多次lock()与unlock()

std::time_mutex: 定时 Mutex 类,使用try_lock_for(),try_lock_until()来限定当前线程阻塞时间,如果在规定时间获得锁(加锁成功),那就返回true,否则返回false。

std::recursive_timed_mutex: 定时递归 Mutex 类。

 

管理的 Mutex 对象的两个类模板

lock_guard<recursive_timed_mutex>lock1(m1);

//对象创建的时候,自动加锁,析构的时候,自动解锁。

std::unique_lock<std::mutex>lck(mtx); 

//创建对象的时候不会自动加锁,比lock_guard拥有更多的函数,控制更为灵活。

//unique_lock, 不一定要拥有 mutex,所以可以透过default constructor 建立出一个空的 unique_lock

 

Tips:

try_lock() 函数与lock的区别就是有返回值

try_lock_for() for后面接一段时间

执行lock前,如果当前互斥量未锁,则加锁。

如果已经锁定,则①阻塞等待(其他线程锁定),②崩溃(当前线程锁定)

 

C++11中多线程——condition_variable

一般都需要配合unique_lock使用,std::unique_lock <std::mutex> lck(mtx);//进行加锁操作(获得锁),并执行直到解锁。

wait函数 :★★★★着重理解

①当前线程调用 wait() 后将被阻塞,该函数会自动调用 lck.unlock() 释放锁 ,在线程被阻塞时,使得其他被阻塞在锁竞争上的线程得以继续执行。

②一旦当前线程获得通知(notified,通常是另外某个线程调用notify_* 唤醒了当前线程),wait() 函数也是自动调用 lck.lock(),使得 lck 的状态和 wait 函数被调用时相同。

wait需要while内部:

因为可能存在虚假唤醒的情况

 

#include <iostream>               // std::cout

#include <thread>               // std::thread

#include <mutex>               // std::mutex, std::unique_lock

#include <condition_variable>   // std::condition_variable

std::mutex mtx; // 全局互斥锁.

std::condition_variable cv; // 全局条件变量.

bool ready = false; // 全局标志位.

void do_print_id(int id)

{

    std::unique_lock<std::mutex> lck(mtx);

    while (!ready) // 如果标志位不为 true,则等待...

        cv.wait(lck); // 当前线程被阻塞, 当全局标志位变为 true之后,

    // 线程被唤醒, 继续往下执行打印线程编号id.

    std::cout << "thread" << id << '\n';

}

void go()

{

    std::unique_lock<std::mutex> lck(mtx);

    ready = true; // 设置全局标志位为true.

    cv.notify_all(); // 唤醒所有线程.

}

 

int main()

{

    std::thread threads[10];

    // spawn 10 threads:

    for (int i = 0; i < 10; ++i)

        threads[i] =std::thread(do_print_id, i);

    std::cout << "10threads ready to race...\n";

    go(); // go!

  for (auto & th:threads)

        th.join();

    return 0;

}

 

进程控制

 

 

 

 

 

虚拟地址与物理地址

 

MMU将虚拟地址映射到物理地址是以页(Page)为单位的,对于32位CPU通常一页为4K(4096)。例如,虚拟地址0xb7001000~0xb700 1fff是一个页,可能被MMU映射到物理地址0x2000~0x2fff,物理内存中的一个物理页面也称为一个页框(Page Frame)。

现代操作系统都提供了一种内存管理的抽像,即虚拟内存(virtual memory)。进程使用虚拟内存中的地址,由操作系统协助相关硬件,把它“转换”成真正的物理地址。这个“转换”,是所有问题讨论的关键。有了这样的抽像,一个程序,就可以使用比真实物理地址大得多的地址空间。(拆东墙,补西墙,银行也是这样子做的),甚至多个进程可以使用相同的地址。不奇怪,因为转换后的物理地址并非相同的。

 

CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步:首先将给定一个逻辑地址(其实是段内偏移量,这个一定要理解!!!),CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址(=段基址加逻辑地址),再利用其页式内存管理单元,转换为最终物理地址。

线程地址=段基址+逻辑地址

线性地址-->物理地址(页式存储管理单元,最简单的为页映射表,如果不存在分页则线性地址为物理地址)

参考链接: http://blog.csdn.net/hguisu/article/details/5713164

 

页式管理地址变换

 

在页式系统中,指令所给出的地址分为两部分:逻辑页号和页内地址。

原理:CPU中的内存管理单元(MMU)按逻辑页号通过查进程页表得到物理页框号,将物理页框号与页内地址相加形成物理地址(见图4-4)。

逻辑页号,页内偏移地址->查进程页表,得物理页号->物理地址:

 

 

段式管理的地址变换

 

在段式管理系统中,整个进程的地址空间是二维的,即其逻辑地址由段号和段内地址两部分组成。为了完成进程逻辑地址到物理地址的映射,处理器会查找内存中的段表,由段号得到段的首地址,加上段内地址,得到实际的物理地址(见图4—5)。这个过程也是由处理器的硬件直接完成的,操作系统只需在进程切换时,将进程段表的首地址装入处理器的特定寄存器当中。这个寄存器一般被称作段表地址寄存器。

 

页式和段式管理的区别

页式和段式系统有许多相似之处。比如,两者都采用离散分配方式,且都通过地址映射机构来实现地址变换。但概念上两者也有很多区别,主要表现在:

1)、需求:是信息的物理单位,分页是为了实现离散分配方式,以减少内存的碎片,提高内存的利用率。或者说,分页仅仅是由于系统管理的需要,而不是用户的需要。段是信息的逻辑单位,它含有一组其意义相对完整的信息。分段的目的是为了更好地满足用户的需要。

    一条指令或一个操作数可能会跨越两个页的分界处,而不会跨越两个段的分界处。

2)、大小:页大小固定且由系统决定,把逻辑地址划分为页号和页内地址两部分,是由机器硬件实现的。段的长度不固定,且决定于用户所编写的程序,通常由编译系统在对源程序进行编译时根据信息的性质来划分。

3)、逻辑地址表示:页式系统地址空间是一维的,即单一的线性地址空间,程序员只需利用一个标识符,即可表示一个地址。分段的作业地址空间是二维的,程序员在标识一个地址时,既需给出段名,又需给出段内地址。

4)、比页大,因而段表比页表短,可以缩短查找时间,提高访问速度。

 

 

 

程序运行时,操作系统需要

 

1.在虚拟内存中分配段空间(左边) //要求连续

2.在物理内存中分配相应的页面(右边) //不一定要连续

3.把段拆开,分别映射到不同的页,建立映射表

 

段基址保存在进程的PCB中,但是由于进程里面有很多段(代码段,堆、栈、等),所以一个进程存放多个段基址(进程段表LDT表)

 

 

函数调用过程

基础知识——栈

1. 每一个进程在用户态对应一个调用栈结构(callstack)

2. 程序中每一个未完成运行的函数对应一个栈帧(stackframe),栈帧中保存函数局部变量、传递给被调函数的参数等信息

3. 栈底对应高地址,栈顶对应低地址,栈由内存高地址向低地址生长

 

栈底是最后一个出去的

栈顶是第一个出去的

基础知识——寄存器

ax(accumulator): 可用于存放函数返回值

bp(base pointer): 用于存放执行中的函数对应的栈帧的栈底地址

sp(stack poinger): 用于存放执行中的函数对应的栈帧的栈顶地址

ip(instruction pointer): 指向当前执行指令的下一条指令

 

入栈顺序

---------------------- 以下内容属于调用者栈

1. 被调者参数(被调用参数从右向左依次入栈)

2. 返回地址(指向调用者下一条指令)

---------------------- 以下内容属于被调用者栈

3. ebp

4. 临时变量等

。。。

 

补码

 

 

 

线程同步方式

有四种方式:临界区、互斥量、事件、信号量。

临界区:( 单进程)

当多个线程访问一个独占性共享资源时,可以使用临界区对象。拥有临界区的线程可以访问被保护起来的资源或代码段,其他线程若想访问,则被挂起,直到拥有临界区的线程放弃临界区为止。具体应用方式:

  1、定义临界区对象CcriticalSection g_CriticalSection;

  2、在访问共享资源(代码或变量)之前,先获得临界区对象,g_CriticalSection.Lock();

  3、访问共享资源后,则放弃临界区对象,g_CriticalSection.Unlock();

 

互斥量: 

互斥对象和临界区对象非常相似

1. 只是其允许在进程间使用,而临界区只限制与同一进程的各个线程之间使用

2. 更节省资源,更有效率。

 

信号量:  

允许多个线程同时访问同一资源,我们可以指定允许个数

 

事件:(条件变量)

提供线程之间的一种通知机制,当某一条件满足时,线程A可以通知阻塞在条件变量上的线程B,B所期望的条件已经满足,可以解除在条件变量上的阻塞操作,继续做其他事情。

 

线程与进程

线程有内核与用户切换之分

不共享的资源:

栈  、寄存器、 状态、 程序计数器


 

用户级线程:

用户级线程有yield,由用户控制(切换)

 

核心级线程:

核心级线程shedule,用系统控制(切换)

能够发挥多核CPU的特性,给不同的线程分配CPU核

 

CPU调度

1. SJF:短作业优先

周转时间最短

//适合后台任务

 

2. RR:轮转调度

响应时间

//适合前台任务

 

3. 优先级调度

linux中schdule使用counter作为其优先级与时间片指标

是一种IO约束形,在IO中阻塞越久,则优先级越高

 

对临界区的保护

1. Peterson算法(两个进程之间)

标记加轮转

 

 

2. 面包店算法(多个进程)

 

3. 硬件方法

关中断,但在多CPU会出问题

原子指令法

 

死锁

定义:互相等待对方持有资源,导致都无法执行的情况

 

四个必要条件:

互斥使用:资源的固有属性

不可抢占:资源只能自动放弃

请求和保持:进程必须占有资源,再去申请

循环等待:资源分配中形成一个环路

 

 

应对方法:

预防

破坏死锁出现的条件

1. 一次性申请所有资源

2. 资源申请排序

避免

银行家算法 

每次申请资源都要执行银行家算法 O(mn^2)

检测+恢复

发现问题再处理

回滚

死锁忽略

许多通用操作系统系统都采用死锁忽略系统

 

 

 

 

数据结构与算法

算法复杂度衡量标准

以下六种计算算法时间的多项式是最常用的。其关系为:

     O(1) < O(㏒n) < O(n)< O(n㏒n) < O(n2) < O(n3)

 

两种物理存储结构

1.顺序存储结构

三个属性:

- 存储空间起始位置

- 线性表的最大容量 //这是预先分配的

- 当前长度

读取与存储的时间复杂度为O(1),删除或者插入就是O(n)

 

2.链式存储

一个节点中包含一个数据域和指针域

节点只包含一个指针域,称为单链表

头指针指向头节点,如果没有头节点,则指向首节点。(头节点可以没有)

顺序存储与单链表性能对比

时间性能

空间性能

查找第i个元素

插入/删除

顺序存储

O(1)

O(n)

需要预先分配

单链表

O(n)

O(1)

不需要分配存储空间,个数不受限制

 

静态链表:用数组来描述的链表(游标实现法)

之所以成为静态链表,是因为需要预先(静态)分配内存。

元素中游标存储下一个元素的下标(可以认为下标等同于地址)

 

 

使用注意:

第一和最后一个元素特殊处理

1.第一个元素的cur存放 备用链表的第一节点下标

2.最后一个元素cur 存放 第一个元素的下标

 

 

优点:

在插入和删除操作时只需要修改游标,不需要移动元素,改进了顺序存储插入和删除需要大量移动元素的缺点

 

缺点:

没有解决连续存储分配带来表长难以确定的问题。

 

循环链表:

能够从任意一个节点开始,遍历全部节点。

尾节点指向头节点

 

 

链表头插与为插法对比:

  • 头插法要比尾插法算法简单
  • 头产生的链表是逆序的,即第一个输入的节点实际是链表的最后一个节点。
  • 通常用尾插法来创建链表

 

队列

只允许在一头插入,另一端进行删除的线性表,而栈是全面都在一头进行操作。

 

 

队列的顺序存储,入队列很快,出队列为O(n),如下图所示。

 

但顺序存储结构,通过循环队列优化,使得出队列仍然为O(1)。

 

KMP算法

主要是通过分析模式信息,加大步进实现加速。

1.根据前缀和后缀计算出公共元素的最大长度表:

其中:

前缀为除最后一个元素外,包含第一个元素的所有组合。

后缀为除第一个元素外,包含最后一个元素的所有组合。

 

2.失配时:

模式串右移位数=已匹配的字符数-失配字符前一字符对应的最大长度值。

 

树、二叉树、森林的转换

树->二叉树

1.加线 兄弟节点之间

2.去线 除第一个孩子外所有,所有连线去掉

3.调整结构 顺时针旋转

注意:

1.长子仍然为长子

2.老二变成右孩子

 

森林->二叉树

1.每颗树变成二叉树

2.加线 所有根节点变成二叉树

3.调整结构

 

区别:

树到二叉树根节点只有左子树

森林到二叉树根节点既有左子树又有右子树。

因为右子树为其兄弟,森林有好多同级的树。

 

二叉树->森林

1.加线 右孩子与父节点连接

2.去线 去除兄弟之间的连线

3.调整顺序

 

遍历规律:

树,森林前根遍历与二叉树的前序遍历结果相同

树,森林后根遍历与二叉树的中序遍历结果相同

 

赫夫曼树(最优二叉树)

结点路径长度:根节点到该节点的连接数

树的路径长度:所有叶子结点的长度之和

带权路径就要*相应的权值。

WPL:树的带权路径长度,越小越好,当达到最小值时,就是赫夫曼树。

 

定长编码:ASCII编码

变长编码:根据整体频率灵活调节

前缀码:没有任何码字是其他码的前缀

 

逻辑结构与物理结构

逻辑结构

1.集合结构

2.线性结构(一对一)

3.树形结构

4.图形结构

 

物理结构

1.顺序存储

2.链式存储

 

算法的特性

特性:

1.输入

2.输出

3.有穷性

4.确定性

5.可行性

 

设计要求:

1.正确性

2.可读性

3.健壮性

4.高效率

5.低存储量

 

两个概念注意区分

 

算法效率度量

1.事后统计法

2.事前估计法 T(n)=O(f(n));

 

平均情况与最坏情况

默认为最坏情况

 

数据长度与线性表长度

线性表长度(length) ≤ 数据长度(maxSize)

 

邻接表存储

顶点都存储在一个数组中,然后与其相邻的顶点的(下标/地址)存在一个链表中

 

 

有向图需要存储两个领接表(邻接表和逆邻接表),分别表示其出度与入度

 

 

邻接表的优化

十字链表(针对有向表优化) 可以同时知道节点的入度和出度信息

顶点表结构:data | firstin | firstout

//分别是数据,入边表第一个节点,出边表第一个节点

 

边表结构:tailvex | headvex | headlink | taillink

//分别是弧尾节点、弧头节点、弧头节点与当前弧头节点相同的边的指针、省略

 

邻接多重表(针对无向表优化) 删除某条边只需删除一个边表节点

顶点表结构不变,

边表结构:ivex | ilink | jvex | jlink

//边端点i,端点为i的另外一条边的指针,边端点j,端点为j的另外一条边的指针

 

图中基本定义

线性表可以没有数据元素称为空表

树没有结点称为空树

图不允许没有结点

 

()表示无序边,顺序可颠倒,(A,D)

<>表示有向边,顺序不可颠倒,<A,D>有向边称为"弧",分别是弧头,弧尾。

 

完全图:任意两个直接存在连接

无向完全图:边数:n*(n-1)/2

有向完全图:任意两个顶点之间存在互为相反的两条弧,边数:n*(n-1)

 

度:表示某顶点上边的连接数

其中有向图分为入度和出度

 

简单回路:出端点外,节点不重复出现

 

连通图:任意两个顶点可以连接

 

有向树:一个结点的入度为0,其余结点入度为1

生成树: n个顶点、n-1条边、连通图


 

邻接矩阵:

用一个二维矩阵表示图

0表示自己点

1,2,3表示边长度

∞表示不通。

 

DFS(深度优先遍历)

在没有遇到标记点,就用右手/左手原则。

类似树的前序遍历

 

BFS(广度优先遍历)

类似树的广度遍历

 

图中常见算法

遍历方式

DFS(深度优先遍历)

BFS(广度优先遍历)

方法

在没有遇到标记点,就用右手/左手原则。

一层一层遍历

代码实现

递归函数

队列

与树比较

类似树的前序遍历

类似树的层序遍历

 

最小生成树:(极小连通子图)

Prim算法

Kruskals算法

实现原理

有一种小树苗生长的思想:
1.选择离当前树最近的顶点
2.加入到当前树,更新节点离树的最短距离

1.将边长度升序排列
2.不断取最小边加入
注:步骤2中取最小边,要注意检查是否形成回路,可以通过并查集实现

算法复杂度

O(N²)

O(MlogM)

 

最短路径:

Floyd算法

Dijkstra算法

Bellman-Ford算法

队列优化Bellman-Ford算法

算法复杂度

O(N³)

O((M+6N)logN)

O(NM)

最坏O(NM)

实现原理

逐顶点检查每个顶点能否缩短其他两个顶点之间的距离
注:检查一个顶点时,就要两个for循环遍历所有情况,所以最终需要三个for循环


1.将距离最小的顶点作为确定点
2.更新源点到不确定顶点的最小距离
注:此过程中确定顶点数目不断增加,直至全部遍历

1.逐边检查,经过这条边能够使点到终点距离变短
2.遍历N-1次,N为顶点数目

备注

计算出任意两点之间的最小距离

计算出某点到任意点的最小距离

1.计算单源路径
2.以边为输入源

负权边

ok

NG(只有dijstra算法不行)

ok

ok

 

排序方法

实现原理

优点

缺点

算法复杂度

备注    

桶排序、基数排序、箱排序

类似做一个统计直方图

如果输入数据变化有限,此方法效率很高

1.如果数据输入波动很大,则占用空间大
2.不支持浮点排序

O(N)

冒泡排序    

1.逐个遍历左右邻居互相比较,来确定最小值
2.重复上述操作,确定次小值

经典

速度慢

O(N²)

快速排序

类二分法
1.确定基准数的位置(左侧≤基准数≤右侧)
2.对左右区间分别快速排序

最坏O(N²)
平均O(NlogN)

递归实现,海量数据会栈溢出

归并排序

类二分法
1.对两个字序列分别排序
2.对已经排序好的序列进行归并

效率高,稳定

占用内存大

O(NlogN)

递归实现,海量数据会栈溢出

选择排序

每次遍历找到最小的元素,并交换

O(N²)

复杂度与冒泡一样,性能稍优于冒泡

堆排序

每次将根节点值输出(最大/最小)

O(NlogN)

不需要额外空间,适合海量数据

 

 

完全二叉树的性质

最后一个非叶结点为第n/2个节点

 

堆与堆排序(优先队列)

堆是一种特殊的完全二叉树,所有的父节点都要比子节点小/大

1.建立

法一:建立一个树,然后不断插入

法二:(更加高效)算法复杂度为O(n)

1. 将n个节点放入一个完全二叉树

2. 从n/2个节点(非叶子节点)开始逐个向下调整

 

2.删除

只能删除第0个元素,

为了便于操作,将最后一个元素移至第0位,然后执行下沉操作

 

3.插入

从最后一个位置插入,然后向上调整。

 

堆排序:

法一:删除最小堆的最顶元素(最小),然后调整顺序,不断重复,可以生成排序好的数组。

法二:建立最大堆:

1.先按最大堆完全二叉树,

2.建立将顶端(最大)与最后一个元素h[n-1]交换,并n--,//此时n的最终位置已经确定。

3.调整顺序

4.重复上述2,3。最终数组h就是排序号的。

 

应用:寻找第K大的数,建立最小堆;寻找第K小的数,建立最大堆

 

并查集

用于确定有森林中有多少颗树。

实现方法:

1.各自为树

2.合并(1.各自头目比较 2.头目归顺)

3.检查数组中索引号为自身的(头目/根节点),就能知道有多少树。

 

查找

元素之间没有关系,是基于数据的集合,要想获得较高的查找性能,就必须改变数据元素之间的关系。

 

一、顺序查找:

1.简单顺序查找(挨个遍历与元素对比)

2.顺序查找优化(将待查找元素放在数组第0个元素位置,从最后开始搜索)

 

二、有序查找:

1.折半查找(二分查找、binarysearch)

2.插值查找(二分法改进,下一查找位置与元素值有关),线性

3.斐波那契查找(二分法插值位置的改进)

注:有序查找比较如下图所示

 

 

三、线性索引查找

1.稠密索引 每一个记录对应一个索引项

2.分块索引 块之间有序,块内无序

3.倒排索引 由一个属性进行确定。(结构为次关键码、记录号表)

 

四、二叉排序树

左子树所有节点值小于根节点,右子树所有节点值大于根节点

查找:递归,如果比节点小就左边,大就右边,直到相等结束

增加:先查找是否存在,插入到合适的位置

删除:较为复杂需要分情况

 

平衡二叉排序树(AVL树)

要求:

1. 二叉排序树

2. 所有节点的左右子树高度差(abs(BF))不超过1(BF值=左子树高度-右子树)

 

多路查找树

定义:每个节点孩子数可多于2个

阶:节点最大孩子的数目称为B树的阶。

常见的有以下几种形式

 

2-3树:每个节点具有两个或者三个孩子

2节点:包含一个元素与两个孩子

3节点:包含两个元素与三个孩子

注:

1.要么有两个孩子,要么没有孩子,不能只有一个孩子

2.所有叶子节点都在同一层上

 

2-3-4树:与2-3树类似

 

B树:平衡多路查找树,主要用于内外存的数据交换

一般来说阶数都会很大

 

B+树:解决B树的遍历问题,使其不需要来回切换

1.叶子节点会再次出现分支节点元素

2.每个叶子节点包含指向下一个元素的指针

优点:

1.遍历

2.有范围的查找

 

红黑树

红黑树:就是用红链接表示3-结点的2-3树。(将三节点转换成二节点,中间用红链连接

规则1、根节点必须是黑色。

规则2、任意从根到叶子的路径不包含连续的红色节点。

规则3、任意从根到叶子的路径的黑色节点总数相同。

优点:

红黑树是牺牲了严格的高度平衡的优越条件为代价,

红黑树能够以O(logN)的时间复杂度进行搜索、插入、删除操作,与AVL树是一样的,但统计性能优于AVL。

 

 

哈希表

定义:数据之间没有关系,建立了数据(key)索引(index)之间的关系,其算法复杂度达到O(1).

散列技术既是存储也是查找技术:都是通过散列函数计算关键字的地址 

 

散列函数构造方法:

1.直接定址法:f(key)=a*key+b

2.数字分析法、平方取中法、折叠法、求余数法、随机数

 

处理冲突的方法:

1.开放定址法:(f(key)+d)mod m(这个可以线性,二次,随机的)

2.再散列函数法:使用多种散列函数

3.链地址法:一个位置加上一个链表

4.公共区域溢出法:有溢出的就存储到溢出表中

 

#include<iostream>

using namespace std;

#define NULLKEY -9999

struct Hash

{

   int *data;//

   int size;

   Hash(int n):size(n)

    {

       //完成相应的初始化

       data = new int[size];

       for (int i = 0; i < size; i++)

           data[i] = NULLKEY;

    }

   int calcAddress(int key)//输入一个数据,返回地址

    {

       return key%size;

    }

   void insert(int key)

    {

       int toBeInserted = calcAddress(key);

       for (int i = 0; i < size;i++)//遍历一圈找空位置

       {

           if (data[toBeInserted%size] == NULLKEY)

           {

                data[toBeInserted%size] = key;

                break;

           }

            toBeInserted++;

       }

       return;

    }

   int find(int key)

    {

       int toBeFind = calcAddress(key);

       for (int i = 0; i < size; i++)//遍历一圈找空位置

       {

           if (data[toBeFind%size] == key)

           {

                return toBeFind%size;

           }

           toBeFind++;

       }

       return -1;

    }

   ~Hash()

    {

       delete[]data;

    }

};

void main()

{

   Hash hash(5);

   hash.insert(5);

   hash.insert(2);

   hash.insert(189);

   cout << hash.find(189);

 

常见题目

Given an array of integers, find twonumbers such that they add up to a specific target number.

The function twoSum should return indicesof the two numbers such that they add up to the target, where index1 must be lessthan index2. Please note that your returned answers (both index1 and index2)are not zero-based.

You may assume that each input would haveexactly one solution.

Input: numbers={2, 7, 11, 15}, target=9

Output: index1=1, index2=2

 

 

class Solution {

public:

    vector<int>twoSum(vector<int> &numbers, int target)

   {

        vector<int >res;

        map<int,int>numMap;

       map<int,int>::iterator iter;

        intlen=numbers.size();

        for(inti=0;i<len;i++)

        {

           iter=numMap.find(target-numbers[i]);

           if(iter!=numMap.end()) //检查另外一个元素是否存在

            {

               res.push_back(iter->second+1);

               res.push_back(i+1);

               return res;

            }

            else                 

            {

               numMap[numbers[i]]=i;

            }

        }

        return res;

   }

      

    

};

STL中数据结构实现

常用容器

实现方式

注意事项

优点

缺点

备注

deque

双向队列

队列、栈

vector

stack的升级版,支持中间插入(insert)、移除(erase)

list

双向链表

不支持随机访问

插入与删除快

随机删除、插入

map

RB-tree

1.内部有序
2.存入一对值
不包含重复元素

查找较慢

查找(有无[set],具体数据(map)),如果允许key重复出现,就要用multi

set

RB-tree

1.内部有序
不包含重复元素

查找较慢

unordered_map

哈希表(散列函数)    

1.内部无序
2.存入一对值
不包含重复元素

查找较快

建立较慢

unordered_set

哈希表(散列函数)

1.内部无序
不包含重复元素

查找较快

建立较慢

//基础的数据机构,实际中很少用

基础容器

实现方式

注意事项

优点

缺点

array

普通数组,不可以调整大小

queue

单向队列,FIFO

里面有个priority_queue(堆),按照优先级最高的出队列

stack    

普通栈,FILO

forward_list

单向链表

 

原理

举例

相同点

不同点

动态规划    

背包问题
矩阵乘法、
最长公共子序列、
构造最优的二叉树

分解成若干子问题,先求解子问题,在逐渐向上求解

1.子问题之间互相关联
2.保存子问题答案,不需要反复求解

分治    

一般通过迭代实现

二分搜索

合并排序

快速排序

大整数乘法

Strassen矩阵乘法 

棋盘覆盖 

线性时间选择

最接近点对问题

循环赛日程表 

汉诺塔

1.子问题相互独立
2.相同的子问题需要重复求解,复杂度指数级别增长

贪心


依赖于当前已经做出的所有选择,采用自顶向下(每一步根据策略得到当前一个最优解,保证每一步都是选择当前最优的)的解决方法

Prime、Krusal最小生成树,Dijstra最短路径,哈夫曼编码、背包问题

与前两种不同,这采用自顶向下的设计

 

快速排序算法优化

1.随机选取基准值(枢轴),随机选择K个、取中间值(默认k=3)

2.优化交换次数

3.子数组很小的时候不使用快排,使用插入排序

4.优化递归

 

KMP

如果没有重复字符串,那就不必要重复匹配

next[j] = 0, 0的含义就是让主串的这个字符和模式串的首字符前面那个不存在的字符进行比较,换种说法就是前面所讲的让主串移动,让主串的下一个字符和模式串的第一个字符进行匹配。 

next[j] = 1  没有上图所示情况,且匹配不成功的模式串字符不是第一个此时需要让主串没有匹配成功的字符和模式串的首字符进行匹配

 

人脑理解递归

递归= 递+结束条件+归

 

拓扑排序

AOV网(Activity On Vertex):有向顶点图

拓扑排序:把有向顶点图构造成拓扑序列。

实施步骤:(输入为邻接表,顶点表结点中存放其入度) 算法复杂度:O(n)

寻找入度为0的结点,并删除弧尾为该结点的弧,重复此操作(最终得到的序列不唯一)

作用:检查有向图中是否存在环。

 

关键路径

定义:从源点到会点的最大长度

关键活动:关键路径上的活动

注意:当关键路径不唯一的时候,提高关键活动速度并不能解决问题。

 

DP动态规划

解题思路:

1. 写出状态转移方程

2. 由下向上填表

 

经典背包问题:

问题分解:分两种情况拿了第i个物体,以及没有拿第i个物体 //因为这里物品只有一个

状态转移函数:maxValue[i][j]=max{maxValue[i-1][j],maxValue[i-1][j-weight[j]]+value[i]}

其中i表示可以拿物品的种类,j表示包的最大重量。

数据库

事务

数据库中单独的执行单元

4个属性(ACID):

原子性:要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态。

一致性:事务执行前后满足所有指定约束。

隔离性:为了防止事务操作间的混淆,必须串行化或序列化请求,即在同一时间仅有一个请求用于同一数据

持久性:事务所对数据库所作的更改持久的保存在数据库之中。

 

触发器

一特殊类型的存储过程,由事件触发,不是程序调用或者,手动启动。

目的:保证数据的有效性与完整性。

 

根据SQL语句不同,触发器可分为两类:DML和DDL

DML:(数据操作语言事件时执行) 有After(在之后执行)与Instead Of(在之前执行)两种

DDL:(数据定义语言事件时执行)

 

与存储过程的区别

存储过程是一个预编译的SQL语句,优点是允许模块化的设计,就是说只需创建一次,以后在该程序中就可以调用多次。如果某次操作需要执行多次SQL,使用存储过程比单纯SQL语句执行要快。可以用一个命令对象来调用存储过程。

 

触发器与存储过程非常相似,触发器也是SQL语句集,两者唯一的区别是触发器不能用EXECUTE语句调用,而是在用户执行Transact-SQL语句时自动触发(激活)执行

 

 

主键与外键

主键(主码):一个字段或者多个字段、为表中位移的标识符。

注:主键不能为空,且一个表只能有一个主键。

外键(外码):公共关键字在一个关系中为主键时,此关键字为另外一个关系的外键。

 

死锁

含义:

程序并发执行中,不断申请与释放,由于争夺资源处于无限期的等待

原因(3点):

1.系统资源不足

2.进程推进顺序不对

3.资源分配不当

必要条件(4点):

1.互斥

2.请求与保持等待

3.不可剥夺

4.环路等待

如何避免:

防止进入不安全的状态,(代表性算法如银行家算法)

 

共享锁、互斥锁

共享锁(读锁、S锁):用于不更改或不更新的数据操作,如果事务T对数据A加共享锁,其他事务只能再加共享锁,不能加排他锁

互斥锁(排他锁、写锁、X锁):用于数据修改,保证在任意时刻只能有一个线程访问对象。

 

一二三四范式

1NF:表中每一列都具有不可分割的基本数据项,即实体的某个属性不能有多个值,或者不能有重复的属性。

比如:如果一个职工有两个电话,那就要分成办公电话,与移动电话不能合在一个属性里面。

2NF:数据库中的每一个实例(行)必须唯一的区分,通常我们会在为表加上一列用于区分,以存储各个实例的唯一标识

注:第二范式必须先满足第一范式。

3NF:非主属性不依赖于其它主属性。//说明这是独立的,不会造成数据冗余。

BCNF:符合3NF,并且,主属性不依赖于主属性,也就是说,所有属性(包括主属性和非主属性)都不传递依赖于R的任何候选关键字

4NF:要求把同一表内的多对多关系删除

 

超键、候选键(候选码),主键

超键:在关系中能唯一标识元组的属性集

候选键:某个关系变量的一组属性所组成的集合,它需要同时满足下列两个条件:

1.这个属性集合始终能够确保在关系中能唯一标识元组。

2.在这个属性集合中找不出合适的子集能够满足条件。

主键:认为指定某个候选键

注:

候选键,就是把超键里面多于的属性给去除。(候选键是最精简的)

主属性:某一个候选关键字的属性集中的一个属性。

 

 

 

 

设计模式

设计模式分类

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

 

设计模式六大准则

1、开闭原则(Open Close Principle)

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

2、里氏代换原则(Liskov Substitution Principle)

子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:

子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

子类中可以增加自己特有的方法。

当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。

当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

3、依赖倒转原则(Dependence Inversion Principle)

这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。

4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。

5、迪米特法则(最少知道原则)(Demeter Principle)

为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则(Composite Reuse Principle)

原则是尽量使用合成/聚合的方式,而不是使用继承。

 

 

单例模式

三个注意点

1.  class single  

2.  {  

3.    private:  

4.        single(){}; //1. 禁止外部创建对象  

5.    public:  

6.        //2. 返回引用,不会执行拷贝构造函数  

7.        static single& getInstance()  

8.        {  

9.            static single a; //3. 静态变量,只会创建一次,线程安全  

10.           return a;  

11.       }  

12. };  

 

 

 

计算机网络

OSI七层模型(Open SystemInterconnection)

物理层:建立、维护、断开物理连接。

数据链路层:建立逻辑连接、进行硬件地址寻址、差错校验等功能。(由底层网络定义协议)将比特组合成字节进而组合成帧,用MAC地址访问介质,错误发现但不能纠正。

网络层:进行逻辑地址寻址,实现不同网络之间的路径选择。协议有:ICMP IGMP IP(IPV4 IPV6) ARP RARP

传输层:定义传输数据的协议端口号,以及流控和差错校验。协议有:TCP,UDP,数据包一旦离开网卡即进入网络传输层

会话层:建立、管理、终止会话。(在五层模型里面已经合并到了应用层)

表示层:数据的表示、安全、压缩。(在五层模型里面已经合并到了应用层)格式有,JPEG、ASCll、DECOIC、加密格式等

应用层:

网络服务与最终用户的一个接口。

协议有:HTTP FTP TFTP SMTP SNMP DNS TELNET HTTPS POP3 DHCP

 

 

各层上的设备

物理层:网卡,网线,集线器(采用广播的形式来传输信息),调制解调器、中继器(Repeater,也叫放大器)

数据链路层:网桥,交换机

网络层:路由器

网关工作在第四层传输层及其以上

 

TCP/IP五层协议

物理层:主要负责在物理线路上传输原始的二进制数据;

数据链路层:主要负责在通信的实体间建立数据链路连接;

网络层:创建逻辑链路,以及实现数据包的分片和重组,实现拥塞控制、网络互连等功能;

传输层:负责向用户提供端到端的通信服务,实现流量控制以及差错控制;

应用层:为应用程序提供了网络服务

注:

有时候也会说成是四层:网络接口层(物理层+数据链路层)、 IP层(网络层)、传输层、应用层。

 

IP地址

IP地址有32位二进制数组成(4个字节)

A类地址:以0开头,第一个字节范围:0~127;

B类地址:以10开头,第一个字节范围:128~191;

C类地址:以110开头,第一个字节范围:192~223;

D类地址:以1110开头,第一个字节范围为224~239;

 

TCP与UDP的区别

UDP是面向无连接的,不可靠的数据报服务;

TCP是面向连接的,可靠的字Internet

节流服务。

 

TCP的可靠性如何保证?

TCP的可靠性是通过顺序编号和确认(ACK)来实现的。

 

TCP报文格式

 

上图中有几个字段需要重点介绍下:

(1)序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。

(2)确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。

(3)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下:

(A)URG:紧急指针(urgent pointer)有效。

(B)ACK:确认序号有效。

(C)PSH:接收方应该尽快将这个报文交给应用层。

(D)RST:重置连接。

(E)SYN:发起一个新连接。

(F)FIN:释放一个连接。

需要注意的是:

(A)不要将确认序号Ack与标志位中的ACK搞混了。

(B)确认方Ack=发起方Req+1,两端配对。

 

TCP三次握手

第一次握手:

SYN置1

seq=J(随机产生)

Client进入SYN_SENT状态,等待Server确认。

第二次握手:

SYN、ACK都置为1

ack=J+1

seq=K(随机产生)

Server进入SYN_RCVD状态。

第三次握手:

检查ACK是否为1以及ack是否为J+1,

如果正确,ACK置为1,ack=K+1,并将该数据包发送给Server,

Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,

Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

 

 

 

为什么要三次握手

例子:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送ack包。

 

SYN攻击

在三次握手过程中,Server发送SYN-ACK之后,收到Client的ACK之前的TCP连接称为半连接(half-open connect),此时Server处于SYN_RCVD状态,当收到ACK后,Server转入ESTABLISHED状态。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server回复确认包,并等待Client的确认,由于源地址是不存在的,因此,Server需要不断重发直至超时,这些伪造的SYN包将产时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。

SYN攻击检测:

当Server上有大量半连接状态且源IP地址是随机的,则可以断定遭到SYN攻击了

 

四次挥手的全过程

第一次挥手:

Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。

第二次挥手:

Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。

第三次挥手:

Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。

第四次挥手:

Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

 

路由器与交换机的区别

三个方面:

1.工作层次不同:交换机(数据链路层),路由器(网络层)。

2.数据转发对象不同:利用物理地址确定转发数据的目的地址(交换机),路由器根据IP地址确定数据转发地址。

3.传统的交换机只能分割冲突域,不能分割广播域,而路由器可以分割广播域。

4.交换机负责同一网段的通信,路由器负责不同网段的通信。

 

 

Socket

 

 

 

传输层详解

传输层定义:在不同的Host上应用进程提供了一种逻辑通信机制,处理的基本对象是一个个segment (segment)

注:区分网络层,网络层是提供主机之间的逻辑通信机制(网络层只有一个协议 IP协议)

 

多路分用接收端有多个应用进程,传输层依据首部信息把不同的报文交给不同的进程

多路复用发送端有多个应用进程,传输层为每一个数据块封装正确的头部信息,从而产生正确的报文段,再交给网络层。

 

 

TCP (Transmission Control Protocol)

UDP(User Datagram Protocol)

Socket

l  Socket用四元组标识

源IP地址 源端口号 目的IP地址 目的端口号

 

l  一对一,一个客户端进程对应一个服务器进程,假如说服务器只有一个进程,那内部可以穿个多个线程,来维持一对一的服务

 

l  Socket用二元组标识

目的IP地址 目的端口号

 

l  不同源IP地址,如果目标目的端口号相同,将被导向同一个socket

 

 

 

 

优点

可靠数据传输(不错、不丢、不乱)

l  拥塞控制

l  流量控制

l  连接建立

 

l  无需建立连接,延迟少

l  实现简单

l  头部开销少(8字节,TCP20字节)

缺点

一对一不支持一对多

 

Best effort服务,UDP段可能

l  丢失

l  非时序到达

应用

http

流媒体、DNS、ping命令

备注

 

1.       UDP带有简单和校验机制,但即使正确也不一定保证数据正确

2.       UDP上实现数据可靠传输

可以在应用层增加可靠性机制

 

 

网络层详解

l  与传输层的区别:

网络层是主机之间的连接,中间参与的设备要全程参与,

传输层是端到端的传输,只要两端之间记录即可

 

l  核心功能:转发与路由

路由器匹配原则:路由器最长匹配优先原则,尽可能匹配更多前缀

 

l  提供的服务:

n  无连接服务(数据报) //Internet所采用

每个分组携带目的地址,独立选择路径

n  连接服务(虚电路) //ATM网络

传输之前,连接已经建立

 

l  IP协议

寻址规约

数据报(分组)格式

 

协议字段:实现复用与分解,6代表TCP,17代表UDP数据报

首部校验和:每次转发重新计算

填充:保证头部是4字节的倍数

标识:标识一个IP分组(计时器产生)

标志位:是否允许分片(DF),以及是否为最后一片(MF)

片偏移:单位是8个字节

注:当较大的IP分组向较小的MTU(最大传输单元)转发时,可以分片

 

l  IP地址分类

 

 

l  CIDR无类域间路由

 

形式:**.***.***.***/x

提高ip4地址空间效率

 

l  ip地址获取有两种方法

1.      静态分配

2.      动态分配ip

DHCP(此协议是应用层协议)

 

l  NAT(网络地址转换)

实现:

1.      替换(出去)

替换外出IP数据报(源ip,源端口号)

2.      记录

记录内外网ip对应关系

3.      替换(进来)

替换接收IP数据报(源ip,源端口号)

问题:

1.      最多支持60000个并行连接

 

NAT穿透:

1.      静态配置:

2.      UPnP:自动配置

3.      中继

 

l  ICMP,互联网控制报文协议(ip伴随协议)

功能:

差错报告

网络探寻

 

数据链路层

目的:把数据从一个节点通过链路传给另外一个节点。

功能:形成帧(framing),差错检测(error detect),差错控制(error control),介质访问控制

实现:主要在网络接口卡(network interface card,NIC)及其驱动程序上实现。

 

l  滑动窗口协议

解决问题:停等协议由于每次发一个帧都要停下来等待,导致效率很低。

 

回退N协议:如果某一帧没有收到,就会重传窗口内所有已发送的帧。

丢失重传优化方案:

1.      选择性重传:主要是对回退N协议,只是将失败的帧重传。

2.      选择性确认:把已经收到帧的序号告诉发送方

3.      捎到确认:双工

4.      延迟确认:等待一段时间再发送确认确认信号。

 

l  PPP协议:

点对点传输,提供认证,加密、压缩功能

 

 

 

l  透明网桥

将多个局域网组成一个更大的局域网

 

 

 

l  最小生成树算法

防止形成环路,导致广播风暴。

 

l  交换机

交换机(Switch)是一种基于MAC(网卡的硬件地址)识别,能完成封装转发数据包功能的网络设备。交换机可以“学习”MAC地址,并把其存放在内部地址表中,通过在数据帧的始发者和目标接收者之间建立临时的交换路径,使数据帧直接由源地址到达目的地址。

 

l  令牌环网

令牌帧绕环而行,当需要发数据帧的时候,拥有令牌的节点才能发送数据帧。

 

 

 

 

l  网络中各个概念区分:

中继器:信号放大器

集线器:它把一个端口接收的所有信号向所有端口分发出去

网桥:局域网之间的桥梁,根据mac地址转发帧

交换机:高级的网桥,硬件实现(网桥软件实现),性能优

路由器:根据ip地址寻找最佳传输路径

网关:网络的关口,连接两个不同网络的接口,一般运行在应用层。

 

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值