C++ 面经以及答案

第一部分 c++ 基础

1C和C++的区别

  1. C是面向过程的语言是一个结构化的语言考虑如何通过一个过程对输入进行处理得到输出C++是面向对象的语言主要特征是封装继承和多态封装隐藏了实现细节使得代码模块化派生类可以继承父类的数据和方法扩展了已经存在的模块实现了代码重用多态则是一个接口多种实现通过派生类重写父类的虚函数实现了接口的重用
  2. C和C++动态管理内存的方法不一样C是使用malloc/free而C++除此之外还有new/delete关键字
  3. C++中有引用C中不存在引用的概念

2C++中指针和引用的区别

  1. 指针有自己的一块空间而引用只是一个别名
  2. 使用 sizeof 看一个指针的大小为4字节32位如果要是64位的话指针为8字节而引用则是被引用对象的大小
  3. 指针可以被初始化为 NULL而引用必须被初始化且必须是一个已有对象的引用
  4. 作为参数传递时指针需要被解引用才可以对对象进行操作而直接对引用的修改都会改变引用所指向的对象
  5. 指针在使用中可以指向其他对象但是引用只能是一个对象的引用不能被改变
  6. 指针可以是多级而引用没有分级
  7. 如果返回动态分配内存的对象或者内存必须使用指针引用可能引起内存泄漏

引用占用内存空间吗 对引用取地址其实是取的引用所对应的内存空间的地址这个现象让人觉得引用好像并非一个实体但是引用是占用内存空间的而且其占用的内存和指针一样因为引用的内部实现就是通过指针来完成的

3结构体struct和共同体union联合的区别

结构体将不同类型的数据组合成一个整体是自定义类型 共同体不同类型的几个变量共同占用一段内存

  1. 结构体中的每个成员都有自己独立的地址它们是同时存在的 共同体中的所有成员占用同一段内存它们不能同时存在
  2. sizeof(struct)是内存对齐后所有成员长度的总和sizeof(union)是内存对齐后最长数据成员的长度

结构体为什么要内存对齐呢

  1. 平台原因移植原因不是所有的硬件平台都能访问任意地址上的任意数据某些硬件平台只能在某些地址处取某些特定类型的数据否则抛出硬件异常
  2. 硬件原因经过内存对齐之后CPU的内存访问速度大大提升

4struct 和 class 的区别

  1. 首先说一下C中的结构体和C++中的结构体的异同 在这里插入图片描述在这里插入图片描述
  2. C++中 struct 与 class 的区别 内部成员变量及成员函数的默认访问属性struct 默认防控属性是 public 的而 class 默认的访问属性是private的 继承关系中默认访问属性的区别在继承关系struct 默认是 public 的而 class 是 private class这个关键字还可用于定义模板参数就等同于 typename而strcut不用与定义模板参数

5#define和const的区别

  1. #define定义的常量没有类型所给出的是一个立即数const定义的常量有类型名字存放在静态区域
  2. 处理阶段不同#define定义的宏变量在预处理时进行替换可能有多个拷贝const所定义的变量在编译时确定其值只有一个拷贝
  3. #define定义的常量是不可以用指针去指向const定义的常量可以用指针去指向该常量的地址
  4. #define可以定义简单的函数const不可以定义函数

6重载overload覆盖重写override隐藏重定义overwrite这三者之间的区别

  1. overload将语义相近的几个函数用同一个名字表示但是参数列表参数的类型个数顺序不同不同这就是函数重载返回值类型可以不同 特征相同范围同一个类中函数名字相同参数不同virtual关键字可有可无
  2. override派生类覆盖基类的虚函数实现接口的重用返回值类型必须相同 特征不同范围基类和派生类函数名字相同参数相同基类中必须有virtual关键字必须是虚函数
  3. overwrite派生类屏蔽了其同名的基类函数返回值类型可以不同 特征不同范围基类和派生类函数名字相同参数不同或者参数相同且无virtual关键字

7new 和 delete 是如何实现的与 malloc 和 free有什么异同

new操作针对数据类型的处理分为两种情况

  1. 简单数据类型包括基本数据类型和不需要构造函数的类型 简单类型直接调用 operator new 分配内存 可以通过new_handler 来处理 new 失败的情况 new 分配失败的时候不像 malloc 那样返回 NULL它直接抛出异常bad_alloc要判断是否 分配成功应该用异常捕获的机制
  2. 复杂数据类型需要由构造函数初始化对象 new 复杂数据类型的时候先调用operator new然后在分配的内存上调用构造函数

delete也分为两种情况

  1. 简单数据类型包括基本数据类型和不需要析构函数的类型 delete简单数据类型默认只是调用free函数
  2. 复杂数据类型需要由析构函数销毁对象 delete复杂数据类型先调用析构函数再调用operator delete

与 malloc 和 free 的区别

  1. 属性上new / delete 是c++关键字需要编译器支持 malloc/free是库函数需要c的头文件支持
  2. 参数使用new操作符申请内存分配时无须制定内存块的大小编译器会根据类型信息自行计算而mallco则需要显式地指出所需内存的尺寸
  3. 返回类型new操作符内存分配成功时返回的是对象类型的指针类型严格与对象匹配故new是符合类型安全性的操作符而malloc内存成功分配返回的是void *需要通过类型转换将其转换为我们需要的类型
  4. 分配失败时new内存分配失败时抛出bad_alloc异常malloc分配内存失败时返回 NULL
  5. 自定义类型new会先调用operator new函数申请足够的内存通常底层使用malloc实现然后调用类型的构造函数初始化成员变量最后返回自定义类型指针delete先调用析构函数然后调用operator delete函数释放内存通常底层使用free实现 malloc/free是库函数只能动态的申请和释放内存无法强制要求其做自定义类型对象构造和析构工作
  6. 重载C++允许重载 new/delete 操作符而malloc为库函数不允许重载
  7. 内存区域new操作符从自由存储区free store上为对象动态分配内存空间而malloc函数从堆上动态分配内存其中自由存储区为C++基于new操作符的一个抽象概念凡是通过new操作符进行内存申请该内存即为自由存储区而堆是操作系统中的术语是操作系统所维护的一块特殊内存用于程序的内存动态分配C语言使用malloc从堆上分配内存使用free释放已分配的对应内存自由存储区不等于堆如上所述布局new就可以不位于堆中

既然有了malloc/freeC++中为什么还需要new/delete呢 运算符是语言自身的特性有固定的语义编译器知道意味着什么由编译器解释语义生成相应的代码库函数是依赖于库的一定程度上独立于语言的编译器不关心库函数的作用只保证编译调用函数参数和返回值符合语法生成call函数的代码 对于非内部数据类型而言光用malloc/free无法满足动态对象都要求new/delete是运算符编译器保证调用构造和析构函数对对象进行初始化/析构但是库函数malloc/free是库函数不会执行构造/析构

8delete和delete[]的区别

delete只会调用一次析构函数而delete[]会调用每个成员的析构函数 用new分配的内存用delete释放用new[]分配的内存用delete[]释放

9const知道吗解释一下其作用

const修饰类的成员变量表示常量不可能被修改 const修饰类的成员函数表示该函数不会修改类中的数据成员不会调用其他非const的成员函数 const函数只能调用const函数非const函数可以调用const函数

10关键字static的作用

  1. 函数体内 static 修饰的局部变量作用范围为该函数体不同于auto变量其内存只被分配一次因此其值在下次调用的时候维持了上次的值
  2. 模块内static修饰全局变量或全局函数可以被模块内的所有函数访问但是不能被模块外的其他函数访问使用范围限制在声明它的模块内
  3. 类中修饰成员变量表示该变量属于整个类所有对类的所有对象只有一份拷贝
  4. 类中修饰成员函数表示该函数属于整个类所有不接受this指针只能访问类中的static成员变量 注意和const的区别const强调值不能被修改而static强调唯一的拷贝对所有类的对象

11堆和栈的区别

  1. 堆栈空间分配区别操作系统由操作系统自动分配释放 存放函数的参数值局部变量的值等其操作方式类似于数据结构中的栈操作系统 一般由程序员分配释放 若程序员不释放程序结束时可能由OS回收分配方式倒是类似于链表
  2. 堆栈的缓存方式区别 栈是内存中存储值类型的大小为2Mwindowlinux下默认为8M可以更改超出则会报错内存溢出内存中存储的是引用数据类型引用数据类型无法确定大小堆实际上是一个在内存中使用到内存中零散空间的链表结构的存储空间堆的大小由引用类型的大小直接决定引用类型的大小的变化直接影响到堆的变化
  3. 堆栈数据结构上的区别 堆数据结构堆可以被看成是一棵树堆排序数据结构一种先进后出的数据结构

C++内存区域中堆和栈的区别 管理方式不同栈是由编译器自动管理无需我们手工控制对于堆来说释放由程序员完成容易产生内存泄漏 空间大小不同一般来讲在32为系统下面堆内存可达到4G的空间从这个角度来看堆内存几乎是没有什么限制的但是对于栈来讲一般都是有一定空间大小的例如在vc6下面默认的栈大小好像是1M当然也可以自己修改打开工程 project–>setting–>link在category中选中output然后再reserve中设定堆栈的最大值和 commit 能否产生碎片对于堆来讲频繁的new/delete势必会造成内存空间的不连续从而造成大量的碎片使程序效率降低对于栈来讲则不会存在这个问题 生长方向不同对于堆来讲生长方向是向上的也就是向着内存地址增加的方向对于栈来讲它的生长方式是向下的是向着内存地址减小的方向增长 分配方式不同堆都是动态分配的栈静态分配由编译器完成比如局部变量的分配 分配效率不同栈是机器系统提供的数据结构计算机会在底层对栈提供支持分配专门的寄存器存放栈的地址压栈出栈都有专门的指令执行这就决定了栈的效率比较高堆则是c/c++库函数提供的机制很复杂库函数会按照一定的算法进行分配显然堆的效率比栈要低得多 进程内存中的映像主要有代码区动态存储区new/delete的动态数据静态存储区

堆和自由存储区的区别 总的来说堆是C语言和操作系统的术语是操作系统维护的一块动态分配内存自由存储是C++中通过new与delete动态分配和释放对象的抽象概念他们并不是完全一样 从技术上来说heap是C语言和操作系统的术语堆是操作系统所维护的一块特殊内存它提供了动态分配的功能当运行程序调用malloc()时就会从中分配稍后调用free可把内存交还而自由存储是C++中通过new和delete动态分配和释放对象的抽象概念通过new来申请的内存区域可称为自由存储区基本上所有的C++编译器默认使用堆来实现自由存储也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现这时藉由new运算符分配的对象说它在堆上也对说它在自由存储区上也正确 扩展: 堆和栈的区别转过无数次的文章

12#include<file.h> #include “file.h” 的区别

前者是从标准库路径寻找 后者是从当前工作路径

13什么是内存泄漏面对内存泄漏和指针越界你有哪些方法

动态分配内存所开辟的空间在使用完毕后未手动释放导致一直占据该内存即为内存泄漏 方法malloc/free要配套对指针赋值的时候应该注意被赋值的指针是否需要释放使用的时候记得指针的长度防止越界

14定义和声明的区别

为变量分配地址和存储空间的称为定义不分配地址的称为声明一个变量可以在多个地方声明但是只在一个地方定义 加入 extern 修饰的是变量的声明说明此变量将在文件以外或在文件后面部分定义说明很多时候一个变量只是声明不分配内存空间直到具体使用时才初始化分配内存空间如外部变量

15C++文件编译与执行的四个阶段

  1. 预处理根据文件中的预处理指令来修改源文件的内容
  2. 编译编译成汇编代码
  3. 汇编把汇编代码翻译成目标机器指令
  4. 链接链接目标代码生成可执行程序

16C++的内存管理

在C++中内存被分成五个区自由存储区静态存储区常量区 存放函数的参数和局部变量编译器自动分配和释放 new关键字动态分配的内存由程序员手动进行释放否则程序结束后由操作系统自动进行回收 自由存储区new所申请的内存则是在自由存储区上使用delete来释放

  • 堆是操作系统维护的一块内存而自由存储是C++中通过new与delete动态分配和释放对象的抽象概念堆与自由存储区并不等价

全局/静态存储区存放全局变量和静态变量 常量区存放常量不允许被修改

17 C++的四种强制转换

类型转化机制可以分为隐式类型转换和显示类型转化强制类型转换 (new-type) expression new-type (expression) 隐式类型转换比较常见在混合类型表达式中经常发生四种强制类型转换操作符 static_castdynamic_castconst_castreinterpret_cast

  1. static_cast 编译时期的静态类型检查static_cast静态转换相当于C语言中的强制转换但不能实现普通指针数据空指针除外的强制转换一般用于父类和子类指针引用间的相互转换没有运行时类型检查来保证转换的安全性

①用于类层次结构中基类父类和派生类子类之间指针或引用的转换不管是否发生多态父子之间互转时编译器都不会报错

  • 进行上行转换把派生类的指针或引用转换成基类表示是安全的
  • 进行下行转换把基类指针或引用转换成派生类表示由于没有动态类型检查所以是不安全的但是编译器不会报错

②用于基本数据类型之间的转换如把int转换成char把int转换成enum这种转换的安全性也要开发人员来保证 ③把空指针转换成目标类型的空指针 ④把任何指针类型转换成空指针类型 ⑤可以对普通数据的const和non_const进行转换但不能对普通数据取地址后的指针进行const添加和消去 ⑥无继承关系的自定义类型不可转换不支持类间交叉转换 另外static_cast不能转换掉原有类型的constvolatile或者 __unaligned属性(前两种可以使用const_cast 来去除)

  1. dynamic_cast运行时的检查 动态转换的类型和操作数必须是完整类类型或空指针空引用说人话就是说只能用于类间转换支持类间交叉转换不能操作普通数据 主要用于类层次结构中基类父类和派生类子类之间指针或引用的转换 ①进行上行转换把派生类的指针或引用转换成基类表示是安全的允许转换 ②进行下行转换把基类指针或引用转换成派生类表示由于没有动态类型检查所以是不安全的不允许转化编译器会报错 ③发生多态时允许互相转换 ④无继承关系的类之间也可以相互转换类之间的交叉转换 ⑤如果dynamic_cast语句的转换目标是指针类型并且失败了则结果为0如果转换目标是引用类型并且失败了则dynamic_cast运算符将抛出一个std::bad_cast异常
  2. const_cast const_cast用于强制去掉不能被修改的常数特性其去除常量性的对象一般为指针或引用
  3. reinterpret_cast 在C++语言中reinterpret_cast主要有几种强制转换用途将指针或引用转换为一个足够长度的整型将整型转换为指针或引用类型 更详细见 C++的四种强制转换
  4. 为什么不使用C的强制转换 C的强制转换表面上看起来功能强大什么都能转但是转化不够明确不能进行错误检查容易出错

18externC作用

extern “C”的主要作用就是为了能够正确实现C++代码调用其他C语言代码加上extern “C”后会指示编译器这部分代码按C语言的进行编译而不是C++的

19typdef和define区别

#define是预处理命令在预处理是执行简单的替换不做正确性的检查 typedef是在编译时处理的它是在自己的作用域内给已经存在的类型一个别名 typedef (int*) pINT; #define pINT2 int* 效果相同实则不同实践中见差别pINT a,b;的效果同int *a; int *b;表示定义了两个整型指针变量而pINT2 a,b;的效果同int *a, b;表示定义了一个整型指针变量a和整型变量b

20引用作为函数参数以及返回值的好处

对比值传递引用传参的好处

  1. 在函数内部可以对此参数进行修改
  2. 提高函数调用和运行的效率所以没有了传值和生成副本的时间和空间消耗 值传递 形参是实参的拷贝改变形参的值并不会影响外部实参的值从被调用函数的角度来说值传递是单向的实参->形参参数的值只能传入不能传出当函数内部需要修改参数并且不希望这个改变影响调用者时采用值传递 指针传递 形参为指向实参地址的指针当对形参的指向操作时就相当于对实参本身进行的操作 引用传递 形参相当于是实参的别名对形参的操作其实就是对实参的操作在引用传递过程中被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间但是这时存放的是由主调函数放进来的实参变量的地址被调函数对形参的任何操作都被处理成间接寻址即通过栈中存放的地址访问主调函数中的实参变量正因为如此被调函数对形参做的任何操作都影响了主调函数中的实参变量 用引用作为返回值最大的好处就是在内存中不产生被返回值的副本

但是有以下的限制

  1. 不能返回局部变量的引用因为函数返回以后局部变量就会被销毁
  2. 不能返回函数内部new分配的内存的引用虽然不存在局部变量的被动销毁问题可对于这种情况返回函数内部new分配内存的引用又面临其它尴尬局面例如被函数返回的引用只是作为一 个临时变量出现而没有被赋予一个实际的变量那么这个引用所指向的空间由new分配就无法释放造成memory leak
  3. 可以返回类成员的引用但是最好是const因为如果其他对象可以获得该属性的非常量的引用那么对该属性的单纯赋值就会破坏业务规则的完整性

21什么是野指针

野指针不是NULL指针是未初始化或者未清零的指针它指向的内存地址不是程序员所期望的可能指向了受限的内存 成因

  1. 指针变量没有被初始化
  2. 指针指向的内存被释放了但是指针没有置NULL
  3. 指针超过了变量了的作用范围比如b[10]指针b+11

22C++中内存泄漏的几种情况

内存泄漏是指动态分配的堆内存由于某种原因程序未释放或无法释放造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果

  1. 类的构造函数和析构函数中new和delete没有配套
  2. 在释放对象数组时没有使用delete[]使用了delete
  3. 没有将基类的析构函数定义为虚函数当基类指针指向子类对象时如果基类的析构函数不是virtual那么子类的析构函数将不会被调用子类的资源没有正确释放因此造成内存泄露
  4. 没有正确的清楚嵌套的对象指针

23栈溢出的原因以及解决方法

栈溢出是指函数中的局部变量造成的溢出函数中形参和函数中的局部变量存放在栈上 栈的大小通常是1M-2M,所以栈溢出包含两种情况一是分配的的大小超过栈的最大值二是分配的大小没有超过最大值但是接收的buf比原buf小

  1. 函数调用层次过深,每调用一次,函数的参数局部变量等信息就压一次栈
  2. 局部变量体积太大

解决办法大致说来也有两种

  1. 增加栈内存的数目如果是不超过栈大小但是分配值小的就增大分配的大小
  2. 使用堆内存具体实现由很多种方法可以直接把数组定义改成指针,然后动态申请内存;也可以把局部变量变成全局变量,一个偷懒的办法是直接在定义前边加个static,呵呵,直接变成静态变量(实质就是全局变量)

24左值和右值

不是很严谨的来说左值指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式)右值指的则是只能出现在等号右边的变量(或表达式)举例来说我们定义的变量 a 就是一个左值而malloc返回的就是一个右值或者左值就是在程序中能够寻址的东西右值就是一个具体的真实的值或者对象没法取到它的地址的东西(不完全准确)因此没法对右值进行赋值但是右值并非是不可修改的比如自己定义的class, 可以通过它的成员函数来修改右值 归纳一下就是 可以取地址的有名字的非临时的就是左值 不能取地址的没有名字的临时的通常生命周期就在某个表达式之内的就是右值 但是到了 C++11 之后概念变的略微复杂引入了 lvalue, glvalue, rvalue, xvalue 和 prvalue

25左值引用与右值引用

左值引用就是我们通常所说的引用如下所示左值引用通常可以看作是变量的别名

type-id & cast-expression 

// demo
int a = 10
int &b = a

int &c = 10	// 错误<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>无法对一个立即数做引用

const int &d = 10	// 正确<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span> 常引用引用常数量是ok的<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>其等价于 const int temp = 10; const int &d = temp	

右值引用是 C++11 新增的特性其形式如下所示右值引用用来绑定到右值绑定到右值以后本来会被销毁的右值的生存期会延长至与绑定到它的右值引用的生存期

  • 右值引用支持移动语义的实现可以减少拷贝提升程序的执行效率
  • 右值引用可以使重载函数变得更加简洁右值引用可以适用 const T& 和 T& 形式的参数
type-id && cast-expression  

// demo
int &&var = 10;	// ok

int a = 10
int &&b = a	// 错误<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span> a 为左值

int &&c = var	// 错误<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>var 为左值

int &&d = move(a)	// ok, 通过move得到左值的右值引用

在汇编层面右值引用做的事情和常引用是相同的即产生临时量来存储常量但是唯一 一点的区别是右值引用可以进行读写操作而常引用只能进行读操作

26头文件中的 ifndef/define/endif 是干什么用的? 该用法和 program once 的区别

相同点 它们的作用是防止头文件被重复包含 不同点

  1. ifndef 由语言本身提供支持但是 program once 一般由编译器提供支持也就是说有可能出现编译器不支持的情况(主要是比较老的编译器)
  2. 通常运行速度上 ifndef 一般慢于 program once特别是在大型项目上 区别会比较明显所以越来越多的编译器开始支持 program once
  3. ifndef 作用于某一段被包含define 和 endif 之间的代码 而 program once 则是针对包含该语句的文件 这也是为什么 program once 速度更快的原因
  4. 如果用 ifndef 包含某一段宏定义当这个宏名字出现撞车可能会出现这个宏在程序中提示宏未定义的情况在编写大型程序时特别需要注意因为有很多程序员在同时写代码相反由于program once 针对整个文件 因此它不存在宏名字撞车的情况 但是如果某个头文件被多次拷贝program once 无法保证不被多次包含因为program once 是从物理上判断是不是同一个头文件而不是从内容上

27指针数组和数组指针的区别

数组指针是指向数组的指针而指针数组则是指该数组的元素均为指针

  1. 数组指针是指向数组的指针其本质为指针形式如下如 int (*p)[n]p即为指向数组的指针()优先级高首先说明p是一个指针指向一个整型的一维数组这个一维数组的长度是n也可以说是p的步长也就是说执行p+1时p要跨过n个整型数据的长度数组指针是指向数组首元素的地址的指针其本质为指针可以看成是二级指针
类型名 (*数组标识符)[数组长度]
  1. 指针数组在C语言和C++中数组元素全为指针的数组称为指针数组其中一维指针数组的定义形式如下指针数组中每一个元素均为指针其本质为数组如 int _p[n] []优先级高先与p结合成为一个数组再由int_说明这是一个整型指针数组它有n个指针类型的数组元素这里执行p+1时则p指向下一个数组元素这样赋值是错误的p=a因为p是个不可知的表示只存在p[0]p[1]p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址但可以这样 _p=a; 这里_p表示指针数组第一个元素的值a的首地址的值
类型名 *数组标识符[数组长度]

28C++是不是类型安全的

不是两个不同类型的指针之间可以强制转换

29main函数执行之前还会执行什么代码

全局对象的构造函数会在main函数之前执行

30全局变量和局部变量有什么区别是怎么实现的操作系统和编译器是怎么知道的

生命周期不同 全局变量随主程序创建和创建随主程序销毁而销毁局部变量在局部函数内部甚至局部循环体等内部存在退出就不存在 使用方式不同 通过声明后全局变量程序的各个部分都可以用到局部变量只能在局部使用分配在栈区 内存分配位置不同 全局变量分配在全局数据段并且在程序开始运行的时候被加载局部变量则分配在堆栈里面

31关于sizeof小结的

sizeof计算的是在栈中分配的内存大小

  1. sizeof不计算static变量占得内存
  2. 32位系统的指针的大小是4个字节64位系统的指针是8字节而不用管指针类型
  3. char型占1个字节int占4个字节short int占2个字节 long int占4个字节float占4字节double占8字节string占4字节 一个空类占1个字节单一继承的空类占1个字节虚继承涉及到虚指针所以占4个字节
  4. 数组的长度 若指定了数组长度则不看元素个数总字节数=数组长度*sizeof元素类型 若没有指定长度则按实际元素个数类确定 Ps若是字符数组则应考虑末尾的空字符
  5. 结构体对象的长度 在默认情况下为方便对结构体内元素的访问和管理当结构体内元素长度小于处理器位数的时候便以结构体内最长的数据元素的长度为对齐单位即为其整数倍若结构体内元素长度大于处理器位数则以处理器位数为单位对齐
  6. unsigned影响的只是最高位的意义数据长度不会改变所以sizeofunsigned int=4
  7. 自定义类型的sizeof取值等于它的类型原型取sizeof
  8. 对函数使用sizeof在编译阶段会被函数的返回值的类型代替
  9. sizeof后如果是类型名则必须加括号如果是变量名可以不加括号这是因为sizeof是运算符
  10. 当使用结构类型或者变量时sizeof返回实际的大小当使用静态数组时返回数组的全部大小sizeof不能返回动态数组或者外部数组的尺寸

32sizeof 和 strlen 的区别

  1. sizeof 是一个操作符 strlen 是库函数
  2. sizeof 的参数可以是数据的类型 也可以是变量 而 strlen 只能以结尾为 ‘\0’ 的字符串做参数
  3. 数组做 siezeof 的参数不退化传递给 strlen 就退化为指针了
  4. 编译器在编译时就计算出了 sizeof 的结果 而 strlen 函数必须在运行时才能计算出来 而且 sizeof 计算的是数据类型占内存的大小 而 strlen 计算的是字符串实际的长度

33说说内联函数

函数调用是有时间和空间开销的程序在执行一个函数之前需要做一些准备工作要将实参局部变量返回地址以及若干寄存器都压入栈中然后才能执行函数体中的代码函数体中的代码执行完毕后还要清理现场将之前压入栈中的数据都出栈才能接着执行函数调用位置以后的代码如果函数体代码比较多需要较长的执行时间那么函数调用机制占用的时间可以忽略如果函数只有一两条语句那么大部分的时间都会花费在函数调用机制上这种时间开销就就不容忽视 为了消除函数调用的时空开销C++ 提供一种提高效率的方法即在编译时将函数调用处用函数体替换类似于C语言中的宏展开这种在函数调用处直接嵌入函数体的函数称为内联函数Inline Function又称内嵌函数或者内置函数 详见 C++ inline内联函数详解

34C/C++的存储期

C对象有4种存储期静态存储期线程存储期自动存储期动态分配存储期 详见 C/C++的存储期

35流操作符重载为什么返回引用

在程序中流操作符>>和<<经常连续使用因此这两个操作符的返回值应该是一个仍旧支持这两个操作符的流引用其他的数据类型都无法做到这一点 注意除了在赋值操作符和流操作符之外的其他的一些操作符中如+-*/等却千万不能返回引用因为这四个操作符的对象都是右值因此它们必须构造一个对象作为返回值

36全局变量和局部变量有什么区别实怎么实现的操作系统和编译器是怎么知道的

  1. 生命周期不同 全局变量随主程序创建和创建随主程序销毁而销毁 局部变量在局部函数内部甚至局部循环体等内部存在退出就不存在 内存中分配在全局数据区
  2. 使用方式不同通过声明后全局变量程序的各个部分都可以用到局部变量只能在局部使用分配在栈区

操作系统和编译器通过内存分配的位置来知道的全局变量分配在全局数据段并且在程序开始运行的时候被加载局部变量则分配在堆栈里面

第二部分 c++ 类相关

1什么是面向对象OOP面向对象的意义

Object Oriented Programming, 面向对象是一种对现实世界理解和抽象的方法思想通过将需求要素转化为对象进行问题处理的一种思想其核心思想是数据抽象封装继承和动态绑定多态 面向对象的意义在于将日常生活中习惯的思维方式引入程序设计中将需求中的概念直观的映射到解决方案中以模块为中心构建可复用的软件系统提高软件产品的可维护性和可扩展性

2解释下封装继承和多态

  1. 封装 封装是实现面向对象程序设计的第一步封装就是将数据或函数等集合在一个个的单元中我们称之为类 封装的意义在于保护或者防止代码数据被我们无意中破坏 从封装的角度看public private 和 protected 属性的特点如下

    • 不管哪种属性内类都是可以访问的
    • public 是一种暴露的手段比如暴露接口类的对象可以访问
    • private 是一种隐藏的手段类的对象不能访问
    • protected 成员
      • 和 public 一样可以被子类继承
      • 和 private 一样不能在类外被直接调用
      • 特例在衍生类中可以通过衍生类对象访问
  2. 继承 继承主要实现重用代码节省开发时间 子类可以继承父类的一些东西

    • a.公有继承(public) 公有继承的特点是基类的公有成员和保护成员作为派生类的成员时它们都保持原有的状态基类的私有成员仍然是私有的不能被这个派生类的子类所访问
    • b.私有继承(private) 私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员并且不能被这个派生类的子类所访问
    • c.保护继承(protected) 保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员并且只能被它的派生类成员函数或友元访问基类的私有成员仍然是私有的 这里特别提一下虚继承虚继承是解决C++多重继承问题其一浪费存储空间第二存在二义性问题的一种手段比如菱形继承典型的应用就是 iostream, 其继承于 istream 和 ostream而 istream 和 ostream 又继承于 ios
  3. 多态 多态是指通过基类的指针或者引用在运行时动态调用实际绑定对象函数的行为与之相对应的编译时绑定函数称为静态绑定多态是设计模式的基础多态是框架的基础 C++静态多态与动态多态

3构造函数和析构函数的执行顺序

构造函数

  1. 首先调用父类的构造函数
  2. 调用成员变量的构造函数
  3. 调用类自身的构造函数

析构函数 对于栈对象或者全局对象调用顺序与构造函数的调用顺序刚好相反也即后构造的先析构对于堆对象析构顺序与delete的顺序相关

4虚函数是怎么实现的

每一个含有虚函数的类都至少有有一个与之对应的虚函数表其中存放着该类所有虚函数对应的函数指针地址 类的示例对象不包含虚函数表只有虚指针 派生类会生成一个兼容基类的虚函数表

5 构造函数为什么一般不定义为虚函数而析构函数一般写成虚函数的原因

  1. 构造函数不能声明为虚函数 1). 因为创建一个对象时需要确定对象的类型而虚函数是在运行时确定其类型的而在构造一个对象时由于对象还未创建成功编译器无法知道对象的实际类型是类本身还是类的派生类等等 2). 虚函数的调用需要虚函数表指针而该指针存放在对象的内存空间中若构造函数声明为虚函数那么由于对象还未创建还没有内存空间更没有虚函数表地址用来调用虚函数即构造函数了
  2. 析构函数最好声明为虚函数 首先析构函数可以为虚函数当析构一个指向派生类的基类指针时最好将基类的析构函数声明为虚函数否则可以存在内存泄露的问题 如果析构函数不被声明成虚函数则编译器实施静态绑定在删除指向派生类的基类指针时只会调用基类的析构函数而不调用派生类析构函数这样就会造成派生类对象析构不完全 子类析构时要调用父类的析构函数吗 析构函数调用的次序时先派生类后基类的和构造函数的执行顺序相反并且析构函数要是virtual的否则如果用父类的指针指向子类对象的时候析构函数静态绑定不会调用子类的析构 不用显式调用会自动调用

6细看拷贝构造函数

对于 class A它的拷贝构造函数如下

A::A(const A &a){}
  1. 为什么必须是当前类的引用呢 循环调用如果拷贝构造函数的参数不是当前类的引用而是当前类的对象那么在调用拷贝构造函数时会将另外一个对象直接传递给形参这本身就是一次拷贝会再次调用拷贝构造函数然后又将一个对象直接传递给了形参将继续调用拷贝构造函数……这个过程会一直持续下去没有尽头陷入死循环

只有当参数是当前类的引用时才不会导致再次调用拷贝构造函数这不仅是逻辑上的要求也是 C++ 语法的要求

  1. 为什么是 const 引用呢 拷贝构造函数的目的是用其它对象的数据来初始化当前对象并没有期望更改其它对象的数据添加 const 限制后这个含义更加明确了

另外一个原因是添加 const 限制后可以将 const 对象和非 const 对象传递给形参了因为非 const 类型可以转换为 const 类型如果没有 const 限制就不能将 const 对象传递给形参因为 const 类型不能直接转换为非 const 类型这就意味着不能使用 const 对象来初始化当前对象了

7静态绑定和动态绑定的介绍

静态绑定和动态绑定是C++多态性的一种特性

  1. 对象的静态类型和动态类型 由于继承导致对象的指针和引用具有两种不同的类型静态类型和动态类型 静态类型对象在声明时采用的类型在编译时确定 动态类型当前对象所指的类型在运行期决定对象的动态类型可变静态类型无法更改
  2. 静态绑定和动态绑定 静态绑定绑定的是对象的静态类型函数依赖于对象的静态类型在编译期确定 动态绑定绑定的是对象的动态类型函数依赖于对象的动态类型在运行期确定 只有虚函数才使用的是动态绑定其他的全部是静态绑定
  3. 引用是否能实现动态绑定为什么引用可以实现 可以因为引用或指针既可以指向基类对象也可以指向派生类对象这一事实是动态绑定的关键用引用或指针调用的虚函数在运行时确定被调用的函数是引用或指针所指的对象的实际类型所定义的

8深拷贝和浅拷贝的区别

深拷贝和浅拷贝可以简单的理解为如果一个类拥有资源当这个类的对象发生复制过程的时候如果资源重新分配了就是深拷贝反之没有重新分配资源就是浅拷贝

9 什么情况下会调用拷贝构造函数三种情况

系统自动生成的构造函数普通构造函数和拷贝构造函数 在没有定义对应的构造函数的时候 生成一个实例化的对象会调用一次普通构造函数而用一个对象去实例化一个新的对象所调用的就是拷贝构造函数 调用拷贝构造函数的情形

  1. 用类的一个对象去初始化另一个对象的时候
  2. 当函数的参数是类的对象时就是值传递的时候如果是引用传递则不会调用
  3. 当函数的返回值是类的对象或者引用的时候

10类对象的大小受哪些因素影响

  1. 类的非静态成员变量大小静态成员不占据类的空间成员函数也不占据类的空间大小
  2. 内存对齐另外分配的空间大小类内的数据也是需要进行内存对齐操作的
  3. 虚函数的话会在类对象插入vptr指针加上指针大小
  4. 当该该类是某类的派生类那么派生类继承的基类部分的数据成员也会存在在派生类中的空间中也会对派生类进行扩展

11拷贝构造函数和赋值运算符的理解

  1. 拷贝构造函数生成新的类对象而赋值运算符不能
  2. 由于拷贝构造函数是直接构造一个新的类对象所以在初始化这个对象之前不用检验源对象 是否和新建对象相同而赋值运算符则需要这个操作另外赋值运算中如果原来的对象中有内存分配要先把内存释放掉

当类中有指针类型的成员变量时一定要重写拷贝构造函数和赋值运算符 不要使用默认的

12C++空类默认有哪些成员函数

默认构造函数析构函数复制构造函数赋值函数

13如果虚函数是有效的那为什么不把所有函数设为虚函数

不行首先虚函数是有代价的由于每个虚函数的对象都要维护一个虚函数表因此在使用虚函数的时候都会产生一定的系统开销这是没有必要的

14构造函数和虚构函数可以是内联函数

构造函数析构函数虚函数可以声明为内联函数这在语法上是正确的 编译器并不真正对声明为inline的构造和析构函数内联因为编译器会在构造和析构函数中添加额外的操作申请/释放内存构造/析构对象调用基类成员对象构造函数等致使构造函数/析构函数并不像看上去的那么精简

15虚函数声明为inline

inline是编译期决定的而虚函数是运行期决定的即在不知道将要调用哪个函数的情况下如何将函数内联

16C++的空类有哪些成员函数

  1. 缺省构造函数
  2. 缺省拷贝构造函数
  3. 缺省析构函数
  4. 缺省赋值运算符
  5. 缺省取址运算符
  6. 缺省取址运算符 const

注意有些书上只是简单的介绍了前四个函数没有提及后面这两个函数但后面这两个函数也是空类的默认函数另外需要注意的是只有当实际使用这些函数的时候编译器才会去定义它们

17C++中的五种构造函数

  1. 默认构造函数
  2. 普通构造函数
  3. 拷贝构造函数
  4. 转换构造函数
  5. 移动构造函数 详见 C++中的五种构造函数

第三部分 c++11/c++14/c++17

1C++11 中有哪些智能指针shared_ptr 的引用计数是如何实现的unique_ptr 的unique 是如何实现的make_shared 和 make_unique 的作用智能指针使用注意事项

C++ 11 中的智能指针有shared_ptr, unique_ptr 和 weak_ptr shared_ptr 的引用计数是存放在堆上的多个 shared_ptr 的对象的引用计数都指向同一个堆地址 unique_ptr 中拷贝构造函数和赋值操作符都声明为delete或private 优先使用 make_shared 和 make_unique 的原因是为了避免内存泄露参考 C++11 中的 Smart Pointershared_ptr/weak_ptr/unique_ptr 总结 智能指针使用注意事项 不使用相同的内置指针值初始化或reset多个智能指针 不delete get()返回的指针 不使用get()初始化或reset另一个智能指针 get()返回的智能指针可能变成dangling pointer 如果智能指针管理的内存不是new出来的需要提供删除器 拓展问题 shared_ptr 是否线程安全 侵入式智能指针 从源码理解智能指针—— shared_ptrweak_ptr 在这里插入图片描述在这里插入图片描述

2智能指针weak_ptr 能够破坏环型引用的原理引用计数的原理

weak_ptr只是提供了对管理对象的一个访问手段weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放它是对对象的一种弱引用不会增加对象的引用计数和shared_ptr之间可以相互转化shared_ptr可以直接赋值给它它可以通过调用lock函数来获得shared_ptr 引用计数原理 shared_ptr的实现是这样的: shared_ptr模板类有一个__shared_count类型的成员_M_refcount来处理引用计数的问题__shared_count也是一个模板类它的内部有一个指向Sp_counted_base_impl类型的指针_M_pi所有引用同一个对象的shared_ptr都共用一个_M_pi指针 当我们谈论shared_ptr的线程安全性时我们在谈论什么

3C++ 的闭包

闭包与函数A调用函数B相比较闭包中函数A调用函数B可以不通过函数A给函数B传递函数参数而使函数B可以访问函数A的上下文环境才可见(函数A可直接访问到)的变量 c++11提供的闭包方式lambdafunctionbind

引用百度上对闭包的定义闭包是指可以包含自由未绑定到特定对象变量的代码块这些变量不是在这个代码块内或者任何全局上下文中定义的而是在定义代码块的环境中定义局部变量闭包 一词来源于以下两者的结合要执行的代码块由于自由变量被包含在代码块中这些自由变量以及它们引用的对象没有被释放和为自由变量提供绑定的计算环境作用域

详见 c++11的闭包(lambdafunctionbind)

4lambda 表达式怎么捕获外部变量

  1. 值捕获: 值捕获和参数传递中的值传递类似被捕获的变量的值在Lambda表达式创建时通过值拷贝的方式传入因此随后对该变量的修改不会影响影响Lambda表达式中的值
  2. 引用捕获: 使用引用捕获一个外部变量只需要在捕获列表变量前面加上一个引用说明符&
  3. 隐式捕获: 还可以让编译器根据函数体中的代码来推断需要捕获哪些变量这种方式称之为隐式捕获
  4. 混合方式捕获: 即同时使用显式捕获和隐式捕获混合捕获时捕获列表中的第一个元素必须是 = 或 &此符号指定了默认捕获的方式是值捕获或引用捕获

C++11捕获外部变量总结

捕获形式 说明
[] 不捕获任何外部变量
[变量名, …] 默认以值得形式捕获指定的多个外部变量用逗号分隔如果引用捕获需要显示声明使用&说明符
[this] 以值的形式捕获this指针
[=] 以值的形式捕获所有外部变量
[&] 以引用形式捕获所有外部变量
[=, &x] 变量x以引用形式捕获其余变量以传值形式捕获
[&, x] 变量x以值的形式捕获其余变量以引用形式捕获

5C++ 的垃圾回收机制

很多语言都有对内存自动管理即所谓的垃圾回收机制所谓垃圾指的是那些不再使用或者没有任何指针指向的内存空间回收则指的是将这些垃圾收集起来以便再次利用C++采用 unique_ptrshared_ptr 以及 weak_ptr 这 3 个智能指针来实现堆内存的自动回收来实现自动释放分配的内存 C++之智能指针C++的垃圾回收机制

6vector的clear()的时间复杂度是多少

C++标准明确指出不管是序列容器⽐如vector还是关联容器⽐如unordered_map)其 clear()成员函数都是线性时间复杂度O(n)的因为只要执⾏了clear()就需要对其存储的元素调⽤析构函数 这个析构操作显然是逐个析构的因⽽时间复杂度是O(n) 当然在实践中也有个例⽐如当vector存储基本数据类型或POD类型⽐如基本数据类型构成的struct的时候由于其元素类型没有析构函数也不需要析构函数加之vector内部连续存储的特性编译器的实现是可以在常量时间完成clear()的 仅限于vector存储基本数据类型和POD类型的时候编译器可能有此优化如果vector存储的 是其他类型的对象或者是其他容器⽐如listmapunordered_map都是没办法做这个优化的 详见 C++和STL中有哪些副作用或者稍不注意会产生性能开销的地方

7C++11为什么引入enum class

  1. C++98 的 enum 是非域化的而 C++11 的 enum class 是域化的限制了枚举成员只在域内可见
  2. enum class 的缺省潜在类型 (underlying type) 是 int 型而 enum 没有缺省潜在类型
  3. enum class 一般总是前置声明而 enum 只有在指定了潜在类型时才可以是前置声明

详见 C++11 之 enum class

8std::thread使用lambda做回调有没有什么注意事项

  1. 使用到外部引用要小心谨慎避免悬空引用 若需要用到的外部局部变量需以值传递的方式捕获而非引用捕获若是外部指针变量则需深拷贝
  2. 谨慎使用或者不用外部指针 如果你用值捕获了个指针你在lambda创建的闭包中持有这个指针的拷贝但你不能阻止lambda外面的代码删除指针指向的内容从而导致你拷贝的指针空悬
  3. 注意引用捕获陷阱引用捕获[&]不要使用外部局部变量
  4. 注意this陷阱lambda里避免有全局变量或静态变量或者比当前类生命周期更长的变量Effective Modern C++ 条款31 对于lambda表达式避免使用默认捕获模式
  5. 避免使用默认捕获模式([=][&],它可能导致你看不出悬空引用问题) 默认值捕获就意外地捕获了this指针而不是你以为的外部变量

例子参考 c++的lambda使用注意事项,可能导致的崩溃问题分析

9C++11的thread_local有没有使用过

有且只有thread_local关键字修饰的变量具有线程周期(thread duration)这些变量(或者说对象在线程开始的时候被生成(allocated)在线程结束的时候被销毁(deallocated)并且每 一个线程都拥有一个独立的变量实例(Each thread has its own instance of the object)thread_local 可以和static 与 extern关键字联合使用这将影响变量的链接属性(to adjust linkage) 那么哪些变量可以被声明为thread_local以下3类都是ok的

  1. 命名空间下的全局变量
  2. 类的static成员变量
  3. 本地变量

详见 C++11中thread_local的使用

10谈一谈你对zero overhead零开销原则的理解

  1. 不需要为没有使用到的语言特性付出代价
  2. 使用某种语言特性不会带来运行时的代价

详见 跟我学c++中级篇——zero overhead abstraction

11了解移动语义和完美转发吗

移动语义可以理解为转移所有权拷贝是对于别人的资源自己重新分配一块内存存储复制过来的资源而对于移动语义类似于转让或者资源窃取的意思对于那块资源转为自己所拥有别人不再拥有也不会再使用通过C++11新增的移动语义可以省去很多拷贝负担如何利用移动语义主要通过移动构造函数

完美转发指可以写一个接受任意实参的函数模板并转发到其它函数目标函数会收到与转发函数完全相同的实参转发函数实参是左值那目标函数实参也是左值转发函数实参是右值那目标函数也是右值 参考 C++11之右值引用移动语义和完美转发(带你了解移动构造函数纯右值将亡值右值引用std::moveforward等新概念)

12了解列表初始化吗

列表初始化可以直接在变量名后面加上初始化列表来进行对象的初始化 参考 C++11之初始化列表

第四部分 多线程多进程 相关

1线程与进程的区别

线程与进程的比较如下

  1. 进程是资源包括内存打开的文件等分配的单位线程是 CPU 调度的单位
  2. 进程拥有一个完整的资源平台而线程只独享必不可少的资源如寄存器和栈
  3. 线程同样具有就绪阻塞执行三种基本状态同样具有状态之间的转换关系
  4. 线程能减少并发执行的时间和空间开销

对于线程相比进程能减少开销体现在

  1. 线程的创建时间比进程快因为进程在创建的过程中还需要资源管理信息比如内存管理信息文件管理信息而线程在创建的过程中不会涉及这些资源管理信息而是共享它们
  2. 线程的终止时间比进程快因为线程释放的资源相比进程少很多
  3. 同一个进程内的线程切换比进程切换快因为线程具有相同的地址空间虚拟内存共享这意味着同一个进程的线程都具有同一个页表那么在切换的时候不需要切换页表而对于进程之间的切换切换的时候要把页表给切换掉而页表的切换过程开销是比较大的
  4. 由于同一进程的各线程间共享内存和文件资源那么在线程之间数据传递的时候就不需要经过内核了这就使得线程之间的数据交互效率更高了 所以线程比进程不管是时间效率还是空间效率都要高

进程之间私有和共享的资源 私有地址空间全局变量寄存器 共享代码段公共数据进程目录进程 ID 线程之间私有和共享的资源 私有线程栈寄存器程序计数器 共享地址空间全局变量静态变量

2什么是线程不安全?

在随机调度之下,线程执行有多种可能,其中某些可能会导致代码出bug就称为线程不安全.

3造成线程不安全的原因

  1. 操作系统的随机调度/抢占式执行(万恶之源)–>无法改变
  2. 多个线程修改同一个变量(一个字都不能少)—>尽量避免
  3. 有些修改操作,不是原子的!(不可拆分的最小单位,就叫原子 即对应一条机器指令)—>通过加锁操作,把指令打包
  4. 内存可见性问题(内存改了,但是在优化的背景下,读不到,看不见) 如:线程1一直在读取硬盘上的资源再判断,在多次读取硬盘并获得相同的结果后编译器会认为这样的做法过于低效,然后就会省略读取的过程,一直判断,若此时有线程2需要这个判断结果(读取到数据后的判断结果)就会出现问题.
  5. 指令重排序(也是编译器,操作系统等的优化,调整了代码的执行顺序)

最常见的就是对象new的问题 分为三步:1.创建内存空间 2.往内存空间上构造对象 3.把这个内存的引用赋值给你要创建的变量 有时候编译器会认为先执行3或者先执行2最高效,就会出现指令顺序倒转的问题,这时另一个线程尝试读取变量的引用就会出现问题

4C++线程中的几类锁

线程之间的锁有 互斥锁条件锁自旋锁读写锁递归锁一般而言锁的功能与性能成反比详见C++11线程中的几种锁如何理解互斥锁条件锁读写锁以及自旋锁

5什么是死锁解决方式

死锁死锁是指两个或两个以上的进程线程在运行过程中因争夺资源而造成的一种僵局若无外力作用这些进程线程都将无法向前推进 详见 死锁四个必要条件及死锁的预防检测避免解除

6进程之间的通信方式

管道PIPE 1有名管道一种半双工的通信方式它允许无亲缘关系进程间的通信 ①优点可以实现任意关系的进程间的通信 ②缺点a长期存于系统中使用不当容易出错b缓冲区有限 2无名管道一种半双工的通信方式只能在具有亲缘关系的进程间使用父子进程 ①优点简单方便 ②缺点a局限于单向通信b只能创建在它的进程以及其有亲缘关系的进程之间c缓冲区有限

信号量Semaphore一个计数器可以用来控制多个线程对共享资源的访问 ①优点可以同步进程 ②缺点信号量有限

信号Signal一种比较复杂的通信方式用于通知接收进程某个事件已经发生

消息队列Message Queue是消息的链表存放在内核中并由消息队列标识符标识 ①优点可以实现任意进程间的通信并通过系统调用函数来实现消息发送和接收之间的同步无需考虑同步问题方便 ②缺点信息的复制需要额外消耗 CPU 的时间不适宜于信息量大或操作频繁的场合

共享内存Shared Memory映射一段能被其他进程所访问的内存这段共享内存由一个进程创建但多个进程都可以访问 ①优点无须复制快捷信息量大 ②缺点a通信是通过将共享空间缓冲区直接附加到进程的虚拟地址空间中来实现的因此进程间的读写操作的同步问题b利用内存缓冲区直接交换信息内存的实体存在于计算机中只能同一个计算机系统中的诸多进程共享不方便网络通信

套接字Socket可用于不同计算机间的进程通信 ①优点 a传输数据为字节级传输数据可自定义数据量小效率高 b传输数据时间短性能高 c适合于客户端和服务器端之间信息实时交互 d可以加密,数据安全性强 ②缺点需对传输的数据进行解析转化成应用级的数据 更多详见 C++基础语法梳理进程与线程知识点详细梳理

7线程之间的通信方式

锁机制 包括互斥锁/量mutex读写锁reader-writer lock自旋锁spin lock条件变量condition

  1. 互斥锁/量mutex提供了以排他方式防止数据结构被并发修改的方法
  2. 读写锁reader-writer lock允许多个线程同时读共享数据而对写操作是互斥的
  3. 自旋锁spin lock与互斥锁类似都是为了保护共享资源互斥锁是当资源被占用申请者进入睡眠状态而自旋锁则循环检测保持者是否已经释放锁
  4. 条件变量condition可以以原子的方式阻塞进程直到某个特定条件为真为止对条件的测试是在互斥锁的保护下进行的条件变量始终与互斥锁一起使用

信号量机制(Semaphore)

  1. 无名线程信号量
  2. 命名线程信号量 信号机制(Signal)类似进程间的信号处理 屏障barrier屏障允许每个线程等待直到所有的合作线程都达到某一点然后从该点继续执行 线程间的通信目的主要是用于线程同步所以线程没有像进程通信中的用于数据交换的通信机制

第五部分 STL

1STL 六大组件

STL 六大组件容器Container算法Algorithm迭代器Iterator仿函数Function object适配器Adaptor和 空间配置器allocator

2stack 中有 pop() 和 top() 方法为什么不直接用 pop() 实现弹出和取值的功能

把pop()和top()的功能放在一个函数中如果 stack 中存放的是较大是内容时比如 vector 类型取值的时候就会发生拷贝如果拷贝失败要弹出的数据将会丢失就很可能出现下述的bug导致原始数据丢失 假设有一个stackvector是一个动态容器当你拷贝一个vector时标准库会从堆上分配很多内存来完成这次拷贝当这个系统处在重度负荷或有严重的资源限制的情况下这种内存分配就会失败所以vector的拷贝构造函数可能会抛出一个std::bad_alloc异常当vector中存有大量元素时这种情况发生的可能性更大当pop()函数返回弹出值时(也就是从栈中将这个值移除)会有一个潜在的问题这个值被返回到调用函数的时候栈才被改变但当拷贝数据的时候调用函数抛出一个异常会怎么样如果事情真的发生了要弹出的数据将会丢失它的确从栈上移出了但是拷贝失败了std::stack的设计人员将这个操作分为两个部分先获取顶部元素(top())然后从栈中移除元素(pop())这样在不能安全的将元素拷贝出去的情况下栈中的这个数据还依旧存在没有丢失当问题是堆空间不足时应用可能会释放一些内存然后再进行尝试 参考 为什么适配器stack中成员函数top()和pop()需要分离实现

3 STL库用过吗常见的STL容器有哪些算法用过几个

STL包括两部分内容容器和算法 容器即存放数据的地方比如array, vector分为两类序列式容器和关联式容器 序列式容器其中的元素不一定有序但是都可以被排序比如vector,list,queue,stackheap, priority-queue, slist 关联式容器内部结构是一个平衡二叉树每个元素都有一个键值和一个实值比如map, set, hashtable, hash_set 算法有排序复制等以及各个容器特定的算法 迭代器是STL的精髓迭代器提供了一种方法使得它能够按照顺序访问某个容器所含的各个元素但无需暴露该容器的内部结构它将容器和算法分开让二者独立设计 Vector是顺序容器是一个动态数组支持随机存取插入删除查找等操作在内存中是一块连续的空间在原有空间不够情况下自动分配空间增加为原来的两倍vector随机存取效率高但是在vector插入元素需要移动的数目多效率低下 注意vector动态增加大小时并不是在原空间之后持续新空间因为无法保证原空间之后尚有可供配置的空间而是以原大小的两倍另外配置一块较大的空间然后将原内容拷贝过来然后才开始在原内容之后构造新元素并释放原空间因此对vector的任何操作一旦引起空间重新配置指向原vector的所有迭代器就都失效了

4STL中map setmultisetmultimap的底层原理关联式容器

map set multiset multimap 的底层实现都是红黑树 红 黑 树 的 特 性

  1. 每个结点或是红色或是黑色
  2. 根结点是黑色
  3. 每个叶结点是黑的
  4. 如果一个结点是红的则它的两个儿子均是黑色
  5. 每个结点到其子孙结点的所有路径上包含相同数目的黑色结点

5map setmultisetmultimap的特点

  1. set和multiset会根据特定的排序准则自动将元素排序set中元素不允许重复multiset可以重复
  2. map和multimap将key和value组成的pair作为元素根据key的排序准则自动将元素排序因为红黑树也是二叉搜索树所以map默认是按key排序的map中元素的key不允许重复multimap可以重复
  3. map和set的增删改查速度为都是logn是比较高效的

6hash_map与map的区别什么时候用hash_map什么时候用map

构造函数hash_map需要hash function和等于函数而map需要比较函数大于或小于 存储结构hash_map以hashtable为底层而map以RB-TREE为底层 总的说来hash_map查找速度比map快而且查找速度基本和数据量大小无关属于常数级别而map的查找速度是logn级别但不一定常数就比log小而且hash_map还有hash function耗时 如果考虑效率特别当元素达到一定数量级时用hash_map 考虑内存或者元素数量较少时用map

7STL中unordered_map和map的区别

map是STL中的一个关联容器提供键值对的数据管理底层通过红黑树来实现实际上是二叉排序树和非严格意义上的二叉平衡树所以在map内部所有的数据都是有序的且map的查询插入删除操作的时间复杂度都是O(logN) unordered_map和map类似都是存储key-value对可以通过key快速索引到value不同的是unordered_map不会根据key进行排序unordered_map底层是一个防冗余的哈希表存储时根据key的hash值判断元素是否相同即unoredered_map内部是无序的

8STL中的vector的实现是怎么扩容的

vector使用的注意点及其原因频繁对vector调用push_back()对性能的影响和原因 vector就是一个动态增长的数组里面有一个指针指向一片连续的空间当空间装不下的时候会申请一片更大的空间将原来的数据拷贝过去并释放原来的旧空间当删除的时候空间并不会被释放只是清空了里面的数据对比array是静态空间一旦配置了就不能改变大小 vector的动态增加大小的时候并不是在原有的空间上持续新的空间无法保证原空间的后面还有可供配置的空间而是以原大小的两倍另外配置一块较大的空间然后将原内容拷贝过来并释放原空间在VS下是1.5倍扩容在GCC下是2倍扩容

9C++中vector和list的区别

vector和数组类似拥有一段连续的内存空间vector申请的是一段连续的内存当插入新的元素内存不够时通常以2倍重新申请更大的一块内存将原来的元素拷贝过去释放旧空间因为内存空间是连续的所以在进行插入和删除操作时会造成内存块的拷贝时间复杂度为o(n) list是由双向链表实现的因此内存空间是不连续的只能通过指针访问数据所以list的随机存取非常没有效率时间复杂度为o(n); 但由于链表的特点能高效地进行插入和删除 vector拥有一段连续的内存空间能很好的支持随机存取因此vector::iterator支持++=<等操作符 list的内存空间可以是不连续它不支持随机访问因此list::iterator则不支持++=<等 vector::iterator和list::iterator都重载了++运算符 总之如果需要高效的随机存取而不在乎插入和删除的效率使用vector; 如果需要大量的插入和删除而不关心随机存取则应使用list

10STL内存优化

STL内存管理使用二级内存配置器

  1. 第一级配置器 第一级配置器以malloc()free()realloc()等C函数执行实际的内存配置释放重新配置等操作并且能在内存需求不被满足的时候调用一个指定的函数一级空间配置器分配的是大于128字节的空间如果分配不成功调用句柄释放一部分内存如果还不能分配成功抛出异常 第一级配置器只是对malloc函数和free函数的简单封装在allocate内调用malloc在deallocate内调用free同时第一级配置器的oom_malloc函数用来处理malloc失败的情况
  2. 第二级配置器 第一级配置器直接调用malloc和free带来了几个问题 内存分配/释放的效率低 当配置大量的小内存块时会导致内存碎片比较严重 配置内存时需要额外的部分空间存储内存块信息所以配置大量的小内存块时还会导致额外内存负担 如果分配的区块小于128bytes则以内存池管理第二级配置器维护了一个自由链表数组每次需要分配内存时直接从相应的链表上取出一个内存节点就完成工作效率很高 自由链表数组自由链表数组其实就是个指针数组数组中的每个指针元素指向一个链表的起始节点数组大小为16即维护了16个链表链表的每个节点就是实际的内存块相同链表上的内存块大小都相同不同链表的内存块大小不同从8一直到128如下所示obj为链表上的节点free_list就是链表数组 内存分配allocate函数内先判断要分配的内存大小若大于128字节直接调用第一级配置器否则根据要分配的内存大小从16个链表中选出一个链表取出该链表的第一个节点若相应的链表为空则调用refill函数填充该链表默认是取出20个数据块 填充链表 refill若allocate函数内要取出节点的链表为空则会调用refill函数填充该链表refill函数内会先调用chunk_alloc函数从内存池分配一大块内存该内存大小默认为20个链表节点大小当内存池的内存也不足时返回的内存块节点数目会不足20个接着refill的工作就是将这一大块内存分成20份相同大小的内存块并将各内存块连接起来形成一个链表 内存池chunk_alloc函数内管理了一块内存池当refill函数要填充链表时就会调用chunk_alloc函数从内存池取出相应的内存 在chunk_alloc函数内首先判断内存池大小是否足够填充一个有20个节点的链表若内存池足够大则直接返回20个内存节点大小的内存块给refill 若内存池大小无法满足20个内存节点的大小但至少满足1个内存节点则直接返回相应的内存节点大小的内存块给refill 若内存池连1个内存节点大小的内存块都无法提供则chunk_alloc函数会将内存池中那一点点的内存大小分配给其他合适的链表然后去调用malloc函数分配的内存大小为所需的两倍若malloc成功则返回相应的内存大小给refill若malloc失败会先搜寻其他链表的可用的内存块添加到内存池然后递归调用chunk_alloc函数来分配内存若其他链表也无内存块可用则只能调用第一级空间配置器

11正确释放vector的内存(clear(), swap(), shrink_to_fit())

vec.clear()清空内容但是不释放内存 vector().swap(vec)清空内容且释放内存想得到一个全新的vector vec.shrink_to_fit()请求容器降低其capacity和size匹配 vec.clear();vec.shrink_to_fit();清空内容且释放内存

12什么情况下用vector什么情况下用list什么情况下用deque

  1. vector可以随机存储元素即可以通过公式直接计算出元素地址而不需要挨个查找但在非尾部插入删除数据时效率很低适合对象简单对象数量变化不大随机访问频繁除非必要我们尽可能选择使用vector而非deque因为deque的迭代器比vector迭代器复杂很多
  2. list不支持随机存储适用于对象大对象数量变化频繁插入和删除频繁比如写多读少的场景
  3. 需要从首尾两端进行插入或删除操作的时候需要选择deque

13priority_queue的底层原理

priority_queue优先队列其底层是用堆来实现的在优先队列中队首元素一定是当前队列中优先级最高的那一个

14 STL线程不安全的情况

  1. 在对同一个容器进行多线程的读写写操作时
  2. 在每次调用容器的成员函数期间都要锁定该容器
  3. 在每个容器返回的迭代器例如通过调用begin或end的生存期之内都要锁定该容器
  4. 在每个在容器上调用的算法执行期间锁定该容器

推荐阅读 C++ STL容器如何解决线程安全的问题

15vector 中迭代器失效的情况

  1. 当插入push_back一个元素后end操作返回的迭代器肯定失效
  2. 当插入(push_back)一个元素后capacity返回值与没有插入元素之前相比有改变则需要重新加载整个容器此时first和end操作返回的迭代器都会失效
  3. 当进行删除操作erasepop_back指向删除点的迭代器全部失效指向删除点后面的元素的迭代器也将全部失效

16迭代器的几种失效的情况

  1. vectordeque该数据结构分配在连续的内存中当删除一个元素后内存中的数据会发生移动以保证数据的紧凑所以删除一个元素后其他数据的地址发生了变化之前获取的迭代器根据原有信息就访问不到正确的数据 解决方法erase返回下一个有效迭代器的值
  2. mapsetmultisetmapmultimap树形数据结构以红黑树或者平衡二叉树组织数据虽然删除了一个元素整棵树也会调整以符合红黑树或者二叉树的规范但是单个节点在内存中的地址没有变化变化的是各节点之间的指向关系删除一个结点不会对其他结点造成影响 erase迭代器只是被删元素的迭代器失效
  3. list链表型数据结构双向链表使用不连续分配的内存删除运算使指向删除位置的迭代器失效不会使其他迭代器失效

17STL 是复制性还是侵入性

STL实现的容器都是非侵入式容器通过模板类可以放入任何类型的结构与浸入式容器的最大不同是C++的非侵入式容器必须存储用户数据的拷贝 参考 STL容器概述

18vector使用注意事项

  1. 在push_back()resize()insert()后有可能引起重新分配内存此时原始的迭代器失效需要重新生成一次迭代器
  2. 为了防止多次扩容造成性能低可以先使用reserve()预留足够的空间最后在使用 shrink_to_fit()收缩空间
  3. vector不能用来存储bool类型的元素存储机理不同bool类型的元素存储到vector中会转化为1个bit不是1个byte可以用deque或者是bitset存储bool类型的
  4. vector的扩容VS是1.5倍GCC是2倍

第六部分 网络编程

1三次握手和四次挥手

参考 详解 TCP 连接的三次握手四次挥手

2 TCP 和 UDP 的区别

  1. TCP面向连接如打电话要先拨号建立连接;UDP是无连接的即发送数据之前不需要建立连接
  2. TCP提供可靠的服务也就是说通过TCP连接传送的数据无差错不丢失不重复且按序到达;UDP尽最大努力交付即不保 证可靠交付
  3. TCP面向字节流实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的 UDP没有拥塞控制因此网络出现拥塞不会使源主机的发送速率降低对实时应用很有用如IP电话实时视频会议等
  4. 每一条TCP连接只能是点到点的;UDP支持一对一一对多多对一和多对多的交互通信
  5. TCP首部开销20字节;UDP的首部开销小只有8个字节
  6. TCP的逻辑通信信道是全双工的可靠信道UDP则是不可靠信道 详见 TCP和UDP的最完整的区别

3TCP 粘包 拆包

如果应用一次请求发送的数据量比较小没达到缓冲区大小TCP则会将多个请求合并为同一个请求进行发送站在业务上来看这就是粘包; 如果应用一次请求发送的数据量比较大超过了缓冲区大小TCP就会将其拆分为多次发送这就是拆包也就是将一个大的包拆分为多个小包进行发送 常见的解决思路如下

  1. 发送端给每个数据包添加包首部
  2. 发送端将每个数据包封装为固定长度
  3. 可以在数据包之间设置边界 详见 一分钟看懂TCP粘包拆包

4TCP 丢包

服务器给客户端发大量数据Send的频率很高那么就有可能在Send时发生错误原因可能是又多种可能是程序处理逻辑问题多线程同步问题缓冲区溢出问题等等如果没有对Send失败做处理重发数据那么客户端收到的数据就会比理论应该收到的少就会造成丢数据丢包的现象 这种现象其实本质上来说不是丢包也不是丢数据只是因为程序处理有错误导致有些数据没有成功地被socket发送出去 常用的解决方法如下 拆包加包头发送组合包如果客户端服务端掉线常采用心跳测试 详见 TCP通信丢包原因总结

5为什么握手是三次挥手却要四次

TCP建立连接时之所以只需要”三次握手”是因为在第二次”握手”过程中服务器端发送给客户端的TCP报文是以SYN与ACK作为标志位的SYN是请求连接标志表示服务器端同意建立连接ACK是确认报文表示告诉客户端服务器端收到了它的请求报文 即SYN建立连接报文与ACK确认接收报文是在同一次”握手”当中传输的所以”三次握手”不多也不少正好让双方明确彼此信息互通 TCP释放连接时之所以需要四次挥手,是因为FIN释放连接报文与ACK确认接收报文是分别由第二次和第三次”握手”传输的为何建立连接时一起传输释放连接时却要分开传输

  • 建立连接时被动方服务器端结束CLOSED阶段进入握手阶段并不需要任何准备可以直接返回SYN和ACK报文开始建立连接
  • 释放连接时被动方服务器突然收到主动方客户端释放连接的请求时并不能立即释放连接因为还有必要的数据需要处理所以服务器先返回ACK确认收到报文经过CLOSE-WAIT阶段准备好释放连接之后才能返回FIN释放连接报文

所以是三次握手四次挥手

6为什么握手是三次挥手却要四次

TCP建立连接时之所以只需要”三次握手”是因为在第二次”握手”过程中服务器端发送给客户端的TCP报文是以SYN与ACK作为标志位的SYN是请求连接标志表示服务器端同意建立连接ACK是确认报文表示告诉客户端服务器端收到了它的请求报文 即SYN建立连接报文与ACK确认接收报文是在同一次”握手”当中传输的所以”三次握手”不多也不少正好让双方明确彼此信息互通

  • TCP释放连接时之所以需要四次挥手,是因为FIN释放连接报文与ACK确认接收报文是分别由第二次和第三次”握手”传输的为何建立连接时一起传输释放连接时却要分开传输 建立连接时被动方服务器端结束CLOSED阶段进入握手阶段并不需要任何准备可以直接返回SYN和ACK报文开始建立连接
  • 释放连接时被动方服务器突然收到主动方客户端释放连接的请求时并不能立即释放连接因为还有必要的数据需要处理所以服务器先返回ACK确认收到报文经过CLOSE-WAIT阶段准备好释放连接之后才能返回FIN释放连接报文

所以是三次握手四次挥手

7网络的七层模型作用传输单位分别是什么

整个模型分为七层物理层数据链路层网络层传输层会话层表示层应用层

详见 OSI七层模型及各层作用

第七部分 设计模式

1设计模式

  1. 创建型模式 对象实例化的模式创建型模式用于解耦对象的实例化过程
  • 单例模式某个类智能有一个实例提供一个全局的访问点
  • 工厂模式一个工厂类根据传入的参量决定创建出哪一种产品类的实例
  • 抽象工厂模式创建相关或依赖对象的家族而无需明确指定具体类
  • 建造者模式封装一个复杂对象的创建过程并可以按步骤构造
  • 原型模式通过复制现有的实例来创建新的实例
  1. 结构型模式 把类或对象结合在一起形成一个更大的结构
  • 装饰器模式动态的给对象添加新的功能
  • 代理模式为其它对象提供一个代理以便控制这个对象的访问
  • 桥接模式将抽象部分和它的实现部分分离使它们都可以独立的变化
  • 适配器模式将一个类的方法接口转换成客户希望的另一个接口
  • 组合模式将对象组合成树形结构以表示部分-整体的层次结构
  • 外观模式对外提供一个统一的方法来访问子系统中的一群接口
  • 享元模式通过共享技术来有效的支持大量细粒度的对象
  1. 行为型模式 类和对象如何交互及划分责任和算法
  • 策略模式定义一系列算法把他们封装起来并且使它们可以相互替换
  • 模板模式定义一个算法结构而将一些步骤延迟到子类实现
  • 命令模式将命令请求封装为一个对象使得可以用不同的请求来进行参数化
  • 迭代器模式一种遍历访问聚合对象中各个元素的方法不暴露该对象的内部结构
  • 观察者模式对象间的一对多的依赖关系
  • 仲裁者模式用一个中介对象来封装一系列的对象交互
  • 备忘录模式在不破坏封装的前提下保持对象的内部状态
  • 解释器模式给定一个语言定义它的文法的一种表示并定义一个解释器
  • 状态模式允许一个对象在其对象内部状态改变时改变它的行为
  • 责任链模式将请求的发送者和接收者解耦使的多个对象都有处理这个请求的机会
  • 访问者模式不改变数据结构的前提下增加作用于一组对象元素的新功能

2单例模式的线程安全问题

饿汉模式的单例模式本身就是现场安全的懒汉模式的线程安全见 C++单例模式与线程安全 推荐阅读 c++的单例模式为什么不直接全部使用static而是非要实例化一个对象

3工厂方法模式

简单工厂模式 简单工厂模式属于类的创建型模式,又叫做静态工厂方法模式通过专门定义一个类来负责创建其他类的实例被创建的实例通常都具有共同的父类简单工厂模式可以减少客户程序对类创建过程的依赖 优点

  • 帮助封装: 实现组件封装面向接口编程
  • 解耦合:客户端和具体实现类的解耦合

缺点

  • 可能增加客户端的复杂度
  • 不方便扩展子工厂

工厂模式 工厂方法模式同样属于类的创建型模式又被称为多态工厂模式 工厂方法模式的意义是 定义一个创建产品对象的工厂接口将实际创建工作推迟到子类当中 核心工厂类不再负责产品的创建这样核心类成为一个抽象工厂角色仅负责具体工厂子类 必须实现的接口这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品 优点

  • 需求改变时改动最小
  • 具体的创建实例过程与客户端分离

缺点

  • 新增功能时工程量稍大

抽象工厂模式 抽象工厂模式是所有形态的工厂模式中最为抽象和最一般性的抽象工厂模式可以向客户端提供一个接口使得客户端在不必指定产品的具体类型的情况下能够创建多个产品族的产品对象

抽象工厂方法是针对与一个产品族使得易于交换产品系列只需改变具体的工厂就可以使用不同的产品配置当一个族中的产品对象被设计成一起工作且一个应用只适用同一族的对象例如设计系统生成不同风格的UI界面按钮边框等UI元素在一起使用并且只能同属于一种风格这很容易使用抽象工厂实现 优点

  • 抽象工厂封装了变化封装了对象创建的具体细节
  • 增加新的产品族很方便无须修改已有系统
  • 针对接口进行编程而不是针对具体进行编程

缺点

  • 增加新的产品等级结构需对原系统做较大修改(违背开放封闭)

详见 C++工厂模式简单工厂工厂方法抽象工厂 详见 C++设计模式三种工厂模式详解简单工厂工厂模式抽象工厂

第八部分 数据结构

1双链表和单链表的优缺点

单向链表单向链表包含两个域一个是信息域一个是指针域也就是单向链表的节点被分成两部分一部分是保存或显示关于节点的信息第二部分存储下一个节点的地址而最后一个节点则指向一个空值 优点单向链表增加删除节点简单遍历时候不会死循环双向也不会死循环循环链表忘了进行控制的话很容易进入死循环缺点只能从头到尾遍历只能找到后继无法找到前驱也就是只能前进

双向链表每个节点有2个链接一个是指向前一个节点当此链接为第一个链接时指向的是空值或空列表另一个则指向后一个节点当此链接为最后一个链接时指向的是空值或空列表意思就是说双向链表有2个指针一个是指向前一个节点的指针另一个则指向后一个节点的指针 优点可以找到前驱和后继可进可退缺点增加删除节点复杂

2红黑树比AVL的优势为何用红黑树

红黑树的查询性能略微逊色于AVL树因为他比avl树会稍微不平衡最多一层也就是说红黑树的查询性能只比相同内容的avl树最多多一次比较但是红黑树在插入和删除上完爆avl树 avl树每次插入删除会进行大量的平衡度计算而红黑树为了维持红黑性质所做的红黑变换和旋转的开销相较于avl树为了维持平衡的开销要小得多

3红黑树的高度

从任何一个节点到其最远的后代叶子的节点数不超过到最近的后代叶子的节点数的两倍 每个具有 n 个节点的红黑树的高度 <= 2Log 2 (n+1) 详见: 红黑树详解 详见: 红黑树 (Red-Black Tree) – 介绍

4判断链表是不是环形链表

  1. 基于循环次数判断的暴力方法
  • 如果一个链表不成环那么它就是可以穷尽的我们假定你的链表的节点的个数小于10^4个如果比这个还多的话也许你需要用到树型结构了不然你的速度会比乌龟还慢因此我们可以一直循环循环的时候要么是因为到达了指针的尾部NULL要么是因为循环次数太多超越了节点的个数循环结束时判断循环次数就可以了
  1. 借助Hash表
  • 每次访问节点的时候都判断是否在hash表中如果没有就加入如果存在就说明有环如果没有环一个节点只可能被访问一次,这和我们企图修改链表的数据结构去维护访问次数是等价的只是以一种不影响节点内容的方式去完成
  1. 快慢指针
  • 定义两种类型的指针slow慢指针前进步长为1快指针前进步长为2在第一次前进后要判断前进后是否到达了NULL这样如果在循环的过程中快指针追上了慢指针那么说明成了环

5简述队列和栈的异同

队列和栈都是线性存储结构但是两者的插入和删除数据的操作不同队列是先进先出栈是 后进先出

  • 注意区别栈区和堆区堆区的存取是顺序随意而栈区是后进先出栈由编译器自动分配释放 存放函数的参数值局部变量的值等其操作方式类似于数据结构中的栈堆一般由程序员分配释放 若程序员不释放程序结束时可能由 OS 回收分配方式类似于链表 它与本题中的堆和栈是两回事堆栈只是一种数据结构而堆区和栈区是程序的不同内存存储区域

整理的原文

c++面试常见问题汇总—建议收藏

C++常见面试题总结

C/C++ 最常见50道面试题

C++核心知识点汇总(面试)

精选 30 个 C++ 面试题含解析

如果你是一个C++面试官你会问哪些问题

k6k4中C/C++面试题

参考阅读

十大经典排序你真的都会了吗源码详解

漫画排序算法 大总结

内存都没了还能运行程序

多个线程为了同个资源打起架来了该如何让他们安分

Linux 运维故障排查思路有这篇文章就够了

万字长文剑指offer全题解思路汇总

我的天第一次知道操作系统的算法还能这么迷人

24 张图彻底弄懂九大常见数据结构

万字长文系统梳理C++函数指针

万字长文图解七道超高频位运算面试题

一口气搞懂文件系统就靠这 25 张图了

看了齐姐这篇文章再也不怕面试问树了

看完这篇操作系统和面试官扯皮就没问题了

史上最全的数据库面试题面试必刷

70道C语言与C++常见问答题

c++面试

C++面试题1

万字详解我今年经历的腾讯Linux C++ 笔试/面试题及答案

C++开发面试之——C++11新特性20问

腾讯 C++ 笔试/面试题及答案

C++面试——内存管理堆栈指针50问

C语言 / C++基础面试知识大集合

50 家公司的 C++ 面经总结硬核分享

一文让你搞懂设计模式

这里有60篇硬核文章

超硬核 | 2 万字+20 图带你手撕 STL 容器源码

熬夜整理的万字C/C++总结值得收藏

现代 C++ 教程高速上手 C++ 11/14/17/20

c++后端开发 资料整理学习

欢迎指出错误遗漏的题尽量做到面试的时候复习一文就够

作者红尘氵梦
链接https://www.nowcoder.com/discuss/454697528508870656
来源牛客网

江科大STM32学习笔记

江科大STM32

资料资料

STM32 简介





片上资源

系统结构

引脚定义

启动配置

最小系统电路


GPIO

GPIO基本结构

端口位结构






硬件电路的驱动方式

LED闪烁实验

int main(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    while (1)
    {
        GPIO_ResetBits(GPIOA, GPIO_Pin_0);
        Delay_ms(500);
        GPIO_SetBits(GPIOA, GPIO_Pin_0);
        Delay_ms(500);
        
        GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);
        Delay_ms(500);
        GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);
        Delay_ms(500);
        
        GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0);
        Delay_ms(500);
        GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1);
        Delay_ms(500);
    }
}

LED流水灯

int main(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    while (1)
    {
        GPIO_Write(GPIOA, ~0x0001);	//0000 0000 0000 0001
        Delay_ms(100);
        GPIO_Write(GPIOA, ~0x0002);	//0000 0000 0000 0010
        Delay_ms(100);
        GPIO_Write(GPIOA, ~0x0004);	//0000 0000 0000 0100
        Delay_ms(100);
        GPIO_Write(GPIOA, ~0x0008);	//0000 0000 0000 1000
        Delay_ms(100);
        GPIO_Write(GPIOA, ~0x0010);	//0000 0000 0001 0000
        Delay_ms(100);
        GPIO_Write(GPIOA, ~0x0020);	//0000 0000 0010 0000
        Delay_ms(100);
        GPIO_Write(GPIOA, ~0x0040);	//0000 0000 0100 0000
        Delay_ms(100);
        GPIO_Write(GPIOA, ~0x0080);	//0000 0000 1000 0000
        Delay_ms(100);
    }
}

蜂鸣器

int main(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    while (1)
    {
        GPIO_ResetBits(GPIOB, GPIO_Pin_12);
        Delay_ms(100);
        GPIO_SetBits(GPIOB, GPIO_Pin_12);
        Delay_ms(100);
        GPIO_ResetBits(GPIOB, GPIO_Pin_12);
        Delay_ms(100);
        GPIO_SetBits(GPIOB, GPIO_Pin_12);
        Delay_ms(700);
    }
}

环境搭建

新建工程

工程架构

GPIO输出

GPIO基本结构

GPIO端口位结构

GPIO输入



元器件上拉输入模式在没有输入时默认为低电平

按键控制LED


关键代码

void Key_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

uint8_t Key_GetNum(void)
{
    uint8_t KeyNum = 0;
    if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
    {
        Delay_ms(20);
        while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);
        Delay_ms(20);
        KeyNum = 1;
    }
    if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)
    {
        Delay_ms(20);
        while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);
        Delay_ms(20);
        KeyNum = 2;
    }
    
    return KeyNum;
}

光敏传感器控制蜂鸣器

关键代码

void LightSensor_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

uint8_t LightSensor_Get(void)
{
    return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13);
}

STM32 八种输入输出模式总结

1. GPIO_MODE_AIN 模拟输入

输入信号不经施密特触发器直接接入输入信号为模拟量而非数字量其余输入方式输入数字量

2. GPIO_MODE_IN_FLOATING 浮空输入

输入信号经过施密特触发器接入输入数据存储器当无信号输入时电压不确定因为浮空输入既高阻输入可以认为输入端口阻抗无穷大这样可以检测到微弱的信号相当于电压表测电压如果电压表内阻不够大而外部阻抗比较大则电压表分压会比较小此时输入高电平即高电平输入低电平即低电平但是外界没有输入时输入电平却容易受到外界电磁以及各种玄学干扰的影响如按键采用浮空输入则在按键按下时输入电平为低但是当松开按键时输入端口悬空外界有微弱的干扰都会被端口检测到此时端口可能高也可能低

3. GPIO_MODE_IPD 下拉输入

浮空输入在外界没有输入时状态不确定可能对电路造成干扰为了使得电路更加稳定不出现没有输入时端口的输入数据被干扰 比如手碰一下电压就发生变化这时就需要下拉电阻或上拉电阻,此电阻与端口输入阻抗相比仍然较小有输入信号时端口读取输入信号无输入信号时端口电平被拉到低电平高电平

4. GPIO_MODE_IPU 上拉输入

上拉输入与下拉输入类似只是无输入信号时端口电平被拉到高电平例如按键信号当按下时输入低电平松开时电平被拉到高电平这样就不会出现按键松开时端口电平不确定的情况即不知道时按下还是松开

5. GPIO-MODE_OUT_OD 开漏输出

开漏输出即漏极开路输出这种输出方式指场效应管漏极开路输出需要接上拉电阻才能输出1漏极经上拉电阻接到电源栅极输出0时场效应管截止阻抗无线大电压被分到场效应管上此时输出为1当栅极输出1时场效应管导通输出端口相当于接地此时输出0开漏输出高电平时是由外接电源输出的因此可以实现高于输出端口电压的输出可以实现电平的转换开漏输出可以实现线与功能方法是多个输出共接一个上拉电阻但是漏极开路输出上升沿慢因为上升沿是外接电源对上拉电阻以及外接负载充电当上拉电阻较大或者负载容性较大时时间常数较大充电较慢需要较快反映时可以采用下降沿触发此时没有电阻接入电路的时间常数较小充电较快

6. GPIO_MODE_OUT_PP 推挽输出

推挽输出既可以输出1又可以输出0但是无法调节输出电压因为输出高低电平均为三极管输入端电压此电压在由芯片内部供电无法改变推挽输出任意时刻只有一路工作上图为输出高电平时电路工作状态只有三极管导通电阻无外接电阻因此推挽输出损耗小速度快

7. GPIO_MODE_AF_OD 复用开漏输出

STM32单片机内部有其他的外设比如定时器DAC等复用开漏输出与普通开漏输出区别在于开漏输出输出的是输出数据寄存器中的数据复用开漏输出输出的是来自外设的数据

8. GOIO_MODE_AF_PP 复用推挽输出

复用推挽输出原理与复用开漏输出原理相同

OLED调试驱动

中断系统



NVIC嵌套中断向量控制器

EXTI外部中断


配置外设中断的基本流程:

  1. 配置RCC, 打开涉及的外设时钟
  2. 配置GPIO
  3. 配置AFIO
  4. 配置EXTI
  5. 配置NVIC

AFIO复用IO口

旋转编码器

红外传感器计次

void CountSensor_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
    
    EXTI_InitTypeDef EXTI_InitStructure;
    EXTI_InitStructure.EXTI_Line = EXTI_Line14;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_Init(&EXTI_InitStructure);
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);
}

uint16_t CountSensor_Get(void)
{
    return CountSensor_Count;
}

void EXTI15_10_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line14) == SET)
    {
        /*如果出现数据乱跳的现象<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>可再次判断引脚电平<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>以避免抖动*/
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
        {
            CountSensor_Count ++;
        }
        EXTI_ClearITPendingBit(EXTI_Line14);
    }
}

STM32中 中断函数的名字是固定的, 参见start文件中的定义

旋转编码器计次

void Encoder_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);
    
    EXTI_InitTypeDef EXTI_InitStructure;
    EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_Init(&EXTI_InitStructure);
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    NVIC_Init(&NVIC_InitStructure);
}

int16_t Encoder_Get(void)
{
    int16_t Temp;
    Temp = Encoder_Count;
    Encoder_Count = 0;
    return Temp;
}

void EXTI0_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line0) == SET)
    {
        /*如果出现数据乱跳的现象<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>可再次判断引脚电平<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>以避免抖动*/
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
        {
            if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
            {
                Encoder_Count --;
            }
        }
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}

void EXTI1_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line1) == SET)
    {
        /*如果出现数据乱跳的现象<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>可再次判断引脚电平<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>以避免抖动*/
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
        {
            if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
            {
                Encoder_Count ++;
            }
        }
        EXTI_ClearITPendingBit(EXTI_Line1);
    }
}

STM32中 中断函数的名字是固定的, 参见start文件中的定义

__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     MemManage_Handler          ; MPU Fault Handler
                DCD     BusFault_Handler           ; Bus Fault Handler
                DCD     UsageFault_Handler         ; Usage Fault Handler
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     SVC_Handler                ; SVCall Handler
                DCD     DebugMon_Handler           ; Debug Monitor Handler
                DCD     0                          ; Reserved
                DCD     PendSV_Handler             ; PendSV Handler
                DCD     SysTick_Handler            ; SysTick Handler

                ; External Interrupts
                DCD     WWDG_IRQHandler            ; Window Watchdog
                DCD     PVD_IRQHandler             ; PVD through EXTI Line detect
                DCD     TAMPER_IRQHandler          ; Tamper
                DCD     RTC_IRQHandler             ; RTC
                DCD     FLASH_IRQHandler           ; Flash
                DCD     RCC_IRQHandler             ; RCC
                DCD     EXTI0_IRQHandler           ; EXTI Line 0
                DCD     EXTI1_IRQHandler           ; EXTI Line 1
                DCD     EXTI2_IRQHandler           ; EXTI Line 2
                DCD     EXTI3_IRQHandler           ; EXTI Line 3
                DCD     EXTI4_IRQHandler           ; EXTI Line 4
                DCD     DMA1_Channel1_IRQHandler   ; DMA1 Channel 1
                DCD     DMA1_Channel2_IRQHandler   ; DMA1 Channel 2
                DCD     DMA1_Channel3_IRQHandler   ; DMA1 Channel 3
                DCD     DMA1_Channel4_IRQHandler   ; DMA1 Channel 4
                DCD     DMA1_Channel5_IRQHandler   ; DMA1 Channel 5
                DCD     DMA1_Channel6_IRQHandler   ; DMA1 Channel 6
                DCD     DMA1_Channel7_IRQHandler   ; DMA1 Channel 7
                DCD     ADC1_2_IRQHandler          ; ADC1_2
                DCD     USB_HP_CAN1_TX_IRQHandler  ; USB High Priority or CAN1 TX
                DCD     USB_LP_CAN1_RX0_IRQHandler ; USB Low  Priority or CAN1 RX0
                DCD     CAN1_RX1_IRQHandler        ; CAN1 RX1
                DCD     CAN1_SCE_IRQHandler        ; CAN1 SCE
                DCD     EXTI9_5_IRQHandler         ; EXTI Line 9..5
                DCD     TIM1_BRK_IRQHandler        ; TIM1 Break
                DCD     TIM1_UP_IRQHandler         ; TIM1 Update
                DCD     TIM1_TRG_COM_IRQHandler    ; TIM1 Trigger and Commutation
                DCD     TIM1_CC_IRQHandler         ; TIM1 Capture Compare
                DCD     TIM2_IRQHandler            ; TIM2
                DCD     TIM3_IRQHandler            ; TIM3
                DCD     TIM4_IRQHandler            ; TIM4
                DCD     I2C1_EV_IRQHandler         ; I2C1 Event
                DCD     I2C1_ER_IRQHandler         ; I2C1 Error
                DCD     I2C2_EV_IRQHandler         ; I2C2 Event
                DCD     I2C2_ER_IRQHandler         ; I2C2 Error
                DCD     SPI1_IRQHandler            ; SPI1
                DCD     SPI2_IRQHandler            ; SPI2
                DCD     USART1_IRQHandler          ; USART1
                DCD     USART2_IRQHandler          ; USART2
                DCD     USART3_IRQHandler          ; USART3
                DCD     EXTI15_10_IRQHandler       ; EXTI Line 15..10
                DCD     RTCAlarm_IRQHandler        ; RTC Alarm through EXTI Line
                DCD     USBWakeUp_IRQHandler       ; USB Wakeup from suspend

定时器TIM




定时中断

定时器时序



RCC时钟树

定时器定时中断

void Timer_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    TIM_InternalClockConfig(TIM2);
    
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;
    TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
    
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);
    
    TIM_Cmd(TIM2, ENABLE);
}

/*
void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
    {
        
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}
*/

定时器外部时钟

用IO口模拟外部时钟源

void Timer_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
    
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;
    TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
    
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);
    
    TIM_Cmd(TIM2, ENABLE);
}

uint16_t Timer_GetCounter(void)
{
    return TIM_GetCounter(TIM2);
}

/*
void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
    {
        
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}
*/

TIM输出比较





(相当于把PWM当成通讯协议来用, 舵机里面自带驱动电路)

PWM驱动实验

void PWM_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;		//GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    TIM_InternalClockConfig(TIM2);
    
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;		//ARR
    TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;		//PSC
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
    
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure);
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;		//CCR
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);
    
    TIM_Cmd(TIM2, ENABLE);
}

void PWM_SetCompare1(uint16_t Compare)
{
    TIM_SetCompare1(TIM2, Compare);
}

TIM输入捕获



测频法适合高频, 测周法适合低频, 以中界频率为界




PWMI模式测频率/占空比

void IC_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    TIM_InternalClockConfig(TIM3);
    
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;		//ARR
    TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;		//PSC
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
    
    TIM_ICInitTypeDef TIM_ICInitStructure;
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure.TIM_ICFilter = 0xF;
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);

    TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
    TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
    
    TIM_Cmd(TIM3, ENABLE);
}

uint32_t IC_GetFreq(void)
{
    return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

uint32_t IC_GetDuty(void)
{
    return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);
}

编码器接口




编码器接口测速

void Encoder_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
        
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;		//ARR
    TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;		//PSC
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
    
    TIM_ICInitTypeDef TIM_ICInitStructure;
    TIM_ICStructInit(&TIM_ICInitStructure);
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure.TIM_ICFilter = 0xF;
    TIM_ICInit(TIM3, &TIM_ICInitStructure);
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
    TIM_ICInitStructure.TIM_ICFilter = 0xF;
    TIM_ICInit(TIM3, &TIM_ICInitStructure);
    
    TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
    
    TIM_Cmd(TIM3, ENABLE);
}

int16_t Encoder_Get(void)
{
    int16_t Temp;
    Temp = TIM_GetCounter(TIM3);
    TIM_SetCounter(TIM3, 0);
    return Temp;
}

ADC模数转换



ADC基本结构

输入通道

转换模式

  • 单次转换非扫描模式
  • 连续转换非扫描模式
  • 单次转换扫描模式
  • 连续转换扫描模式

AD单通道

void AD_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
    
    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &ADC_InitStructure);
    
    ADC_Cmd(ADC1, ENABLE);
    
    ADC_ResetCalibration(ADC1);
    while (ADC_GetResetCalibrationStatus(ADC1) == SET);
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1) == SET);
}

uint16_t AD_GetValue(void)
{
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
    return ADC_GetConversionValue(ADC1);
}

DMA




高位补零/高位舍弃

DMA数据转运

ADC扫描模式+DMA

存储器映像


DMA工作的条件

  1. 转运计数器大于0
  2. 触发源有触发信号
  3. DMA使能

DMA数据转运

uint16_t MyDMA_Size;

void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
    MyDMA_Size = Size;
    
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    DMA_InitTypeDef DMA_InitStructure;
    DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
    DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = Size;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    
    DMA_Cmd(DMA1_Channel1, DISABLE);
}

void MyDMA_Transfer(void)
{
    DMA_Cmd(DMA1_Channel1, DISABLE);
    DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);
    DMA_Cmd(DMA1_Channel1, ENABLE);
    
    while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
    DMA_ClearFlag(DMA1_FLAG_TC1);
}

DMA+AD多通道

uint16_t AD_Value[4];

void AD_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
        
    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;
    ADC_InitStructure.ADC_NbrOfChannel = 4;
    ADC_Init(ADC1, &ADC_InitStructure);
    
    DMA_InitTypeDef DMA_InitStructure;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = 4;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    
    DMA_Cmd(DMA1_Channel1, ENABLE);
    ADC_DMACmd(ADC1, ENABLE);
    ADC_Cmd(ADC1, ENABLE);
    
    ADC_ResetCalibration(ADC1);
    while (ADC_GetResetCalibrationStatus(ADC1) == SET);
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1) == SET);
    
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

串口通信




USART


串口发送+接收

void Serial_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
        
    GPIO_InitTypeDef GPIO_Structure;
    GPIO_Structure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Structure.GPIO_Pin = GPIO_Pin_9;
    GPIO_Structure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_Structure);
    
    GPIO_Structure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Structure.GPIO_Pin = GPIO_Pin_10;
    GPIO_Structure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_Structure);
    
    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate = 9600;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_Init(USART1, &USART_InitStructure);
    
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);
    
    USART_Cmd(USART1, ENABLE);
    
}

void Serial_ByteSend(uint8_t byte)
{
    USART_SendData(USART1, byte);
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); 
    
}

//printf的移植
void Serial_Printf(char *format, ...)
{
    char String[100];
    va_list arg;
    va_start(arg, format);
    vsprintf(String, format, arg);
    va_end(arg);
    Serial_SendString(String);
}

Hex/文本数据包


数据包接收


串口发送文本数据包

状态机核心代码

void USART1_IRQHandler(void)
{
    static uint8_t RxState = 0;
    static uint8_t pRxPacket = 0;
    if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
    {
        
        uint8_t RxData = USART_ReceiveData(USART1);
        if(RxState == 0)
        {
            if(RxData == '@')
            {
                RxState = 1;
                pRxPacket = 0;
            }
        }
        else if(RxState == 1)
        {
            if(RxData == '\r')
            {
                RxState = 2;
            }
            
            else
            {		
                Serial_RxPacket[pRxPacket] = RxData;
                pRxPacket ++;
            }

        }
        else if(RxState == 2)
        {
            if(RxData == '\n')
            {
                RxState = 0;
                Serial_RxPacket[pRxPacket] = '\0';
                Serial_RxFlag = 1;
            }
        }
        
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
}

I2C通信

I2C时序单元




I2C时序



MPU6050


软件I2C读写MPU6050

控制电平


//引脚初始化
void MyI2C_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    GPIO_InitTypeDef GPIO_Structure;
    GPIO_Structure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_Structure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
    GPIO_Structure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_Structure);
    
    GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
}
//控制引脚电平
void MyI2C_W_SCL(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
    Delay_us(10);
}

void MyI2C_W_SDA(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
    Delay_us(10);
}

uint8_t MyI2C_R_SDA(void)
{
    uint8_t BitValue;
    BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
    Delay_us(10);
    return BitValue;
}

I2C基本时序单元

void MyI2C_Start(void)
{
    //释放SDA 和 SCL
    MyI2C_W_SCL(1);
    MyI2C_W_SDA(1);
    
    //i2c起始逻辑
    MyI2C_W_SDA(0);
    MyI2C_W_SCL(0);

}

void MyI2C_Stop(void)
{
    MyI2C_W_SDA(0);
    MyI2C_W_SCL(1);
    MyI2C_W_SDA(1);
}

void MyI2C_SendByte(uint8_t Byte)
{
    uint8_t i = 0;
    for(i=0; i<8; i++)
    {
        MyI2C_W_SDA(Byte & (0x80 >> i));
        MyI2C_W_SCL(1);
        MyI2C_W_SCL(0);
    }
}

uint8_t MyI2C_ReceiveByte(void)
{
    uint8_t i, Byte = 0x00;
    MyI2C_W_SDA(1);
    for(i=0; i<8; i++)
    {
        MyI2C_W_SCL(1);
        if(MyI2C_R_SDA() == 1) {Byte |= (0x80 >> i);}
        MyI2C_W_SCL(0);
    }
    return Byte;
}

void MyI2C_SendAck(uint8_t AckBit)
{
    MyI2C_W_SDA(AckBit);
    MyI2C_W_SCL(1);
    MyI2C_W_SCL(0);
}

uint8_t MyI2C_ReceiveAck(void)
{
    uint8_t AckBit;
    MyI2C_W_SDA(1);
    MyI2C_W_SCL(1);
    AckBit = MyI2C_R_SDA();
    MyI2C_W_SCL(0);
    return AckBit;
}

I2C时序

void MPU6050_WriteReg(uint8_t RegAddr, uint8_t Data)
{
    MyI2C_Start();
    MyI2C_SendByte(MPU6050_ADDR);
    MyI2C_ReceiveAck();
    MyI2C_SendByte(RegAddr);
    MyI2C_ReceiveAck();
    MyI2C_SendByte(Data);
    MyI2C_ReceiveAck();
    MyI2C_Stop();
}

uint8_t MPU6050_ReadReg(uint8_t RegAddr)
{
    uint8_t Data;
    
    MyI2C_Start();
    MyI2C_SendByte(MPU6050_ADDR);
    MyI2C_ReceiveAck();
    MyI2C_SendByte(RegAddr);
    MyI2C_ReceiveAck();
    
    MyI2C_Start();
    MyI2C_SendByte(MPU6050_ADDR | 0x01);
    MyI2C_ReceiveAck();
    Data = MyI2C_ReceiveByte();
    MyI2C_SendAck(1);
    MyI2C_Stop();
    
    return Data;
}

MPU6050

void MPU6050_Init(void)
{
    MyI2C_Init();
    MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
    MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
    MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
    MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
    MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
    MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}

uint8_t MPU6050_GetID(void)
{
    return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}


void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,  \
                     int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
    uint16_t DataH, DataL;
    
    DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
    *AccX  = (DataH << 8) | (DataL);
    
    DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
    *AccY  = (DataH << 8) | (DataL);
    
    DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
    *AccZ  = (DataH << 8) | (DataL);
    
    DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
    *GyroX = (DataH << 8) | (DataL);
    
    DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
    *GyroY = (DataH << 8) | (DataL);
    
    DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
    *GyroZ = (DataH << 8) | (DataL);

}

STM32 I2C外设



传送流程

I2C外设实验

硬件部分核心代码业务逻辑层保持不变

void MPU6050_WriteReg(uint8_t RegAddr, uint8_t Data)
{

    I2C_GenerateSTART(I2C2, ENABLE);
    while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
    
    I2C_Send7bitAddress(I2C2, MPU6050_ADDR, I2C_Direction_Transmitter);
    while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
    
    I2C_SendData(I2C2, RegAddr);
    while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);

    I2C_SendData(I2C2, Data);
    while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);

    I2C_GenerateSTOP(I2C2, ENABLE);
}

uint8_t MPU6050_ReadReg(uint8_t RegAddr)
{
    uint8_t Data;

    I2C_GenerateSTART(I2C2, ENABLE);
    while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);
    
    I2C_Send7bitAddress(I2C2, MPU6050_ADDR, I2C_Direction_Transmitter);
    while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
    
    I2C_SendData(I2C2, RegAddr);
    while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);

    I2C_GenerateSTART(I2C2, ENABLE);
    while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);

    I2C_Send7bitAddress(I2C2, MPU6050_ADDR, I2C_Direction_Receiver);
    while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);
    
    I2C_AcknowledgeConfig(I2C2, DISABLE);
    I2C_GenerateSTOP(I2C2, ENABLE);
    
    while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);
    Data = I2C_ReceiveData(I2C2);
    
    return Data;
}

void MPU6050_Init(void)
{
//	MyI2C_Init();
    
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    GPIO_InitTypeDef GPIO_Structure;
    GPIO_Structure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_Structure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
    GPIO_Structure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_Structure);
    
    GPIO_Init(GPIOB, &GPIO_Structure);
    
    I2C_InitTypeDef I2C_InitStructure;
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_InitStructure.I2C_ClockSpeed = 50000;
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStructure.I2C_OwnAddress1 = 0x00;
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
    I2C_Init(I2C2, &I2C_InitStructure);
    
    I2C_Cmd(I2C2, ENABLE);

    
    MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
    MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
    MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
    MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
    MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
    MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}

SPI通信

基本时序单元




SPI时序

W25Q64简介

Flash操作事项


SPI软件读写实验

对电平操作的封装

void MySPI_W_SS(uint8_t bitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)bitValue);
}

void MySPI_W_SCK(uint8_t bitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)bitValue);
}

void MySPI_W_MOSI(uint8_t bitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)bitValue);
}

uint8_t MySPI_R_MISO(void)
{
    return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}

SPI时序

void MySPI_Start(void)
{
    MySPI_W_SS(0);
}

void MySPI_Stop(void)
{
    MySPI_W_SS(1);
}

uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
    uint8_t ByteRecive=0x00;
    
    uint8_t i;
    for(i=0; i<8; i++)
    {
        MySPI_W_MOSI(ByteSend & (0x80 >> i));
        MySPI_W_SCK(1);
        if(MySPI_R_MISO() == 1)
        {
            ByteRecive |= (0x80 >> i);
        }
        MySPI_W_SCK(0);
    }
    
    return ByteRecive;
}

W25Q64操作时序

void W25Q64_Init(void)
{
    MySPI_Init();
}

void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
    MySPI_Start();
    MySPI_SwapByte(W25Q64_JEDEC_ID);
    *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    *DID <<= 8; 	//*DID = *DID << 8;
    *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    MySPI_Stop();
}

void W25Q64_WriteEnable(void)
{
    MySPI_Start();
    MySPI_SwapByte(W25Q64_WRITE_ENABLE);
    MySPI_Stop();
}

void W25Q64_WaitBusy(void)
{
    MySPI_Start();
    MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
    uint32_t Timeout = 100000;
    while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
    {
        Timeout --;
        if(Timeout==0) break;
    }
    MySPI_Stop();
}

void W25Q64_PageProgram(uint32_t Address ,uint8_t *DataArray, uint16_t count)
{
    W25Q64_WriteEnable();
    
    MySPI_Start();
    MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
    MySPI_SwapByte(Address>>16);
    MySPI_SwapByte(Address>>8);
    MySPI_SwapByte(Address);
    
    uint16_t i=0;
    for(i=0; i<count; i++)
    {
        MySPI_SwapByte(DataArray[i]);
    }
    MySPI_Stop();
    W25Q64_WaitBusy();
    
}

void W25Q64_SectorErase(uint32_t Address)
{
    W25Q64_WriteEnable();
    
    MySPI_Start();
    MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
    MySPI_SwapByte(Address>>16);
    MySPI_SwapByte(Address>>8);
    MySPI_SwapByte(Address);
    MySPI_Stop();
    
    W25Q64_WaitBusy();
    
}

void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
    uint32_t i;
    MySPI_Start();
    MySPI_SwapByte(W25Q64_READ_DATA);
    MySPI_SwapByte(Address>>16);
    MySPI_SwapByte(Address>>8);
    MySPI_SwapByte(Address);
    for(i=0; i<Count; i++)
    {
        DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    }
    MySPI_Stop();
    
}

SPI外设



SPI硬件通信实验

通信逻辑方面不变, 改变的是底层通信的代码, 用硬件电路实现
改变了2个函数MySPI_Init uint8_t MySPI_SwapByte(uint8_t ByteSend) `

void MySPI_W_SS(uint8_t bitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)bitValue);
}

void MySPI_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
    
    GPIO_InitTypeDef GPIO_Structure;
    GPIO_Structure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Structure.GPIO_Pin = GPIO_Pin_4;
    GPIO_Structure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_Structure);
    
    GPIO_Structure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Structure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_Structure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_Structure);

    GPIO_Structure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Structure.GPIO_Pin = GPIO_Pin_6;
    GPIO_Structure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_Structure);
    
    SPI_InitTypeDef SPI_InitStructure;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_Init(SPI1, &SPI_InitStructure);
    
    SPI_Cmd(SPI1, ENABLE);
    
    MySPI_W_SS(1);
}

void MySPI_Start(void)
{
    MySPI_W_SS(0);
}

void MySPI_Stop(void)
{
    MySPI_W_SS(1);
}

uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
    SPI_I2S_SendData(SPI1, ByteSend);
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
    return SPI_I2S_ReceiveData(SPI1);
}

23Spring_建模复习

UML中九种图的建模元素以及九种模型图的作用画法 (用例图 时序图 状态图为主)

UMLUnified Modeling Language是一种通用的建模语言用于描述和设计软件系统在UML中有九种不同类型的图分别用于不同的建模目的下面分别介绍它们的建模元素和作用

  1. 用例图Use Case Diagram用于描述系统的功能需求和用户角色主要包含用例Use Case参与者Actor和关系Relationship三个建模元素用例图的作用是帮助开发人员和客户之间共同理解系统的功能需求

  2. 活动图Activity Diagram用于描述系统的业务流程和操作流程主要包含活动Activity控制流Control Flow和对象流Object Flow三个建模元素活动图的作用是帮助开发人员和用户之间共同理解系统的业务流程

  3. 类图Class Diagram用于描述系统的类和类之间的关系主要包含类Class接口Interface关系Relationship等建模元素类图的作用是帮助开发人员理解系统的静态结构和对象之间的关系

  4. 对象图Object Diagram用于描述系统中的对象及其之间的关系主要包含对象Object关系Relationship等建模元素对象图的作用是帮助开发人员理解系统中对象的实例化和关系

  5. 时序图Sequence Diagram用于描述系统中对象之间的时间顺序关系主要包含对象Object生命线Lifeline消息Message等建模元素时序图的作用是帮助开发人员理解系统中对象之间的时间顺序关系

  6. 协作图Collaboration Diagram用于描述系统中对象之间的协作关系主要包含对象Object消息Message等建模元素协作图的作用是帮助开发人员理解系统中对象之间的协作关系

  7. 状态图Statechart Diagram用于描述系统中对象的状态和状态之间的转换主要包含状态State转换Transition等建模元素状态图的作用是帮助开发人员理解系统中对象的状态和状态之间的转换

  8. 部署图Deployment Diagram用于描述系统中组件和节点之间的部署关系主要包含节点Node组件Component等建模元素部署图的作用是帮助开发人员理解系统中组件和节点之间的部署关系

  9. 包图Package Diagram用于描述系统的包和包之间的关系主要包含包Package依赖关系Dependency等建模元素包图的作用是帮助开发人员理解系统包之间的关系

用例图类图时序图(画用例图其中某个功能的时序图)

在UML中不同类型的图有不同的画法和符号其中用例图时序图和状态图是比较常用的三种图下面分别介绍它们的画法

  1. 用例图用例图的画法比较简单用椭圆形表示用例用人形图标表示参与者用线条表示关系用例图通常从左侧开始用例向右侧展开参与者位于用例图的左侧或上方
    录音机用例图录音机用例图

  2. 时序图时序图的画法比较复杂需要画出对象的生命线和消息的流向通常从上往下画生命线用竖线表示消息用箭头表示可以用实线虚线等不同类型的线条表示不同类型的消息
    录音机顺序图录音机顺序图

  3. 状态图状态图的画法比较简单用圆角矩形表示状态用箭头表示状态之间的转换可以用实心空心等不同类型的箭头表示不同类型的转换状态图通常从上往下画从初始状态开始沿着状态之间的转换路径画出状态图

  4. 类图: UML类图是一种用于表示面向对象程序设计中的类接口关系等元素的图形化表示法 添加关系使用箭头表示类或对象之间的关系例如使用实线箭头表示继承关系使用虚线箭头表示关联关系添加多重性可以在关系箭头上添加多重性符号以表示类或对象之间的关系的多重性例如* 表示多个1 表示一个

什么是用例, 那些图用来详细描述用例

在软件开发中用例Use Case是一种描述系统功能和行为的技术用例描述了系统的各种功能以及这些功能是如何与系统的用户角色和其他系统进行交互的用例通常用于捕捉系统需求和用户需求以便开发人员和系统分析师能够更好地理解系统的功能和行为并在开发过程中进行测试和验证

在UML中用例可以通过用例图Use Case Diagram和详细用例规范Detailed Use Case Specification来表示和描述用例图是一种UML图形表示方法用于描述系统的用例和角色之间的关系用例图通常包括用例角色和系统边界等元素用例规范则是一种文档用于详细描述每个用例的场景前置条件后置条件流程和其他相关信息

除了用例图和用例规范还有其他UML图形表示方法可以用来描述用例例如

  1. 活动图Activity Diagram用于描述用例场景中的活动和流程

  2. 时序图Sequence Diagram用于描述用例场景中的交互和消息传递

  3. 协作图Collaboration Diagram用于描述用例场景中的对象和它们之间的交互

画出录音机的用例图, 顺序图
CPS系统

CPS系统指的是嵌入式计算机系统和物理系统之间的集成它们共同工作以控制监视和优化物理过程如工业自动化智能交通系统智能制造和智能医疗系统等CPS系统通常需要高度可靠性实时性和安全性因此需要采用特殊的设计方法和技术

实时系统

实时系统指的是需要在严格的时间限制内完成任务的计算机系统这些任务需要在特定的时间内产生正确的结果否则可能会对系统的正常运行产生严重影响实时系统可以分为硬实时系统和软实时系统硬实时系统必须在规定的时间内完成任务而软实时系统可以在某些情况下稍微超过规定的时间限制

名词解释
时间约束Timing constraint实时系统的任务具有一定的时间约束截止时间根据截止时间实时系统的实时性分为硬实时软实时硬实时是指应用的时间需求必须得到完全满足否则就造成重大安全事故
发布时间Release Time作业变成为可执行的时间如果所有的工作在系统开始执行时被释放那么就认为没有发布时间
截止时间Deadline工作被要求执行完成的时间如果截止时间是无限的那么工作就没有最后期限绝对截止时间等于发布时间加上相对截止时间
响应时间Response time作业从发布到执行完成的时间长度

及时性timeless:实时系统中动作必须在事件到达或者预期时间到达时开始执行作出响应在动作开始后的某个时刻必须完成,否则就会造成重大安全事故
并发性concurrency是指多个动作顺序链的同时执行
可预测性predictability系统的可预测性就是系统的响应特性所达到的可预知程度
正确性和鲁棒性correctness and robustness正确性表明一个系统总是运行正确鲁棒性表明系统即使在遇到新的情况不在计划中下也是可靠的因此必须警惕死锁竞争以及其他异常情况
实时操作系统特点 及时性可靠性工业控制武器发射等领域

MDA模型驱动开发 (CIV-PIM-PSID概念为主

MDA模型驱动开发Model Driven Architecture是一种软件开发方法它将模型作为软件系统的核心MDA的核心思想是将系统的设计和实现从平台特定的细节中分离出来以便更容易地实现跨平台的开发在MDA中开发人员首先定义系统的抽象模型然后使用模型转换工具将其转换为特定平台的代码这种方法可以提高开发效率和系统可维护性并提高软件的质量和可重用性

MDA 定义了三种模型

  • 计算独立模型Computation-Independent ModelCIM
    描述系统的需求和将在其中使用系统的业务上下文此模型通常描述系统将用于做什么而不描述如何实现系统CIM 通常用业务语言或领域特定语言来表示 用例图
  • 平台独立模型Platform-Independent ModelPIM
    描述如何构造系统而不涉及到用于实现模型的技术此模型不描述用于为特定平台构建解决方案的机制PIM 在由特定平台实现时可能是适当的或者可能适合于多种平台上的实现 类图顺序图
  • 平台特定模型Platform-Specific ModelPSM
    从特定平台的角度描述解决方案其中包括如何实现 CIM 和如何在特定平台上完成该实现的细节 如SpringJSP模型

RM EDF调度机制

ROPES开发流程(需求分析系统设计系统实现)

Rapid Object-Oriented Process Embedded System 用于嵌入式系统的快速面向对象过程

以下过程都建立在系统的模型之上这些过程可以理解为同一模型的不同视图要明白分析设计阶段用到的UML图

  1. 分析(analysis)阶段对所有可能的正确方案的本质特征进行识别

    • 需求分析(requirements analysis)是从客户获取需求及把这些需求组织为容易理解的形式的过程
    • 系统分析(system analysis)要构造定义更为严格的模型并且以这些需求为基础将系统的行为划分为机械组件电子组件和软件组件
    • 对象分析(Object Analysis)要给出重要的对象和类以及它们的主要属性前面的子阶段定义了系统要求具备的行为这些需求由本阶段给出的对象结构予以满足它还包括两个子阶段
      • 结构对象分析(structural object analysis)以类和对象的形式标识对象分解的结构单元同时建立对象分解的组织单元节点和组件以及这些元素之间的内在关系
      • 行为对象分析(behavioral object analysis)为已识别的类定义必要的动态行为模型
  2. 设计(design)则在分析的结果中添加了一些元素这些元素根据对某些判定准则的优化定义了一份特定的解决方案

    • 架构设计(architectural design)能够给出可部署软件系统的大尺度组织分解
    • 机制设计(Mechanistic Design)对对象间的协作具体化
    • 详细设计(detailed design)定义类的结构和并对各个类的内部进行组织
  3. 转化(translation)阶段为设计创建一个可执行的可部署的实现如将UML模型转化成源代码

  4. 测试(testing)要检查转化的结果是否与设计等价并验证具体实现是否满足了分析阶段建立的正确性准则

自动机的事件迁移等内容理解结合实验



MARTE语言的建模思想;

MARTEModeling and Analysis of Real-Time and Embedded systems是UML的一个扩展语言它特别针对实时和嵌入式系统的建模和分析进行了扩展MARTE提供了一组新的建模元素和工具用于描述实时和嵌入式系统的特性和行为如并发时间资源能耗等方面MARTE还为UML提供了一些新的分析方法和工具用于对实时和嵌入式系统进行性能分析可靠性分析功耗分析等

MARTE语言的建模思想主要包括以下几个方面

  1. 面向模型的建模思想

MARTE语言采用基于模型的方法进行系统建模将系统的各个方面都抽象成模型包括功能架构行为时间和资源等每个模型都有其特定的目的和意义并且可以在系统开发的不同阶段使用和分析

  1. 面向实时和嵌入式系统的建模思想

MARTE语言的建模思想是面向实时和嵌入式系统的因此它包括对实时性可靠性安全性资源约束等方面的建模和分析这些方面在实时和嵌入式系统中都是至关重要的因此MARTE语言的建模思想能够帮助开发人员更好地理解和分析系统的实时性能和约束条件

  1. 面向多视图和多层次的建模思想

MARTE语言的建模思想是面向多视图和多层次的它允许开发人员从不同的视角和层次来描述和分析系统这些视图和层次包括功能架构行为时间和资源等可以帮助开发人员更好地理解系统的不同方面并且可以在不同的开发阶段进行使用和分析

  1. 面向分析和仿真的建模思想

MARTE语言的建模思想是面向分析和仿真的它提供了一系列的分析和仿真工具可以对系统的实时性能和资源利用率等方面进行分析和评估这些工具可以帮助开发人员在系统设计的早期阶段发现和解决问题并且可以提高系统的可靠性和效率

总之MARTE语言的建模思想是面向模型实时和嵌入式系统多视图和多层次分析和仿真等方面的它提供了一种统一的框架可以帮助开发人员更好地进行系统级别的建模和分析

MARTE中什么是refinement

在MARTE中Refinement细化指的是从高层次的抽象模型到低层次的更具体的模型的转换过程在这个过程中高层次的抽象模型被逐步细化成更具体更详细的模型以便更好地描述系统的细节和行为

MARTE中的Refinement可以分为两个方面模型细化和执行细化模型细化是指将高层次的模型转换为低层次的模型以描述系统的结构和行为执行细化是指将高层次的执行模型转换为低层次的执行模型以支持系统的实时性和可靠性

在MARTE中Refinement是一个重要的概念它使得开发人员可以在不同的抽象层次上进行建模和分析从而更好地理解和描述系统的行为和性能通过Refinement开发人员可以逐步深入系统的细节从而更好地理解和优化系统的性能和可靠性

SysML与UML语言的区别从语义语法和所面向建模场景方面进行区分

SysML和UML都是基于图形化建模的语言但它们在语义语法和所面向建模场景方面存在一些区别

  1. 语义方面的区别

SysML是一种专门用于系统工程的建模语言它强调对系统的结构行为和性能等方面进行建模SysML包括对系统的需求结构行为和资源等方面的建模而且可以支持多个视图和层次的建模

UML则是一种通用的建模语言主要用于软件系统的建模和设计UML包括对软件系统的需求结构行为交互和状态等方面的建模而且也可以支持多个视图和层次的建模

  1. 语法方面的区别

SysML是基于UML2.0的扩展它保留了UML的核心概念和语法同时还添加了一些新的元素和语法规则SysML中包括了UML的类图活动图状态图序列图等多种图形化表示法并增加了需求图块定义图行为图等特定于系统工程的图

  1. 面向建模场景方面的区别

SysML主要面向系统工程领域支持对系统需求结构行为资源等多个方面进行建模以及进行系统级别的建模和分析SysML常用于建模和设计复杂的物理系统航空航天系统汽车系统医疗设备等

UML则主要面向软件工程领域支持对软件系统的需求结构行为交互和状态等多个方面进行建模以及进行软件级别的建模和分析UML常用于建模和设计软件系统网络应用系统数据库系统等

总之SysML和UML在语义语法和所面向建模场景方面存在一些区别SysML主要面向系统工程领域强调对系统的结构行为和性能等方面进行建模而UML则主要面向软件工程领域强调对软件系统的需求结构行为交互和状态等方面进行建模

描述一下什么是模型驱动开发(MDD)

模型驱动开发Model-Driven DevelopmentMDD是一种软件开发方法它将建模作为主要活动并通过自动生成代码的方式来实现软件开发MDD是基于模型的软件开发方法之一它提供了一种利用模型来描述软件系统的方法从而可以帮助开发人员更好地理解和实现软件系统

在MDD中模型是软件系统的基础它是对系统的结构行为和功能等方面的抽象描述模型通常使用图形化表示法如UMLSysML等来描述包括用例图类图活动图状态图等这些图形化表示法可以帮助开发人员更好地理解和描述软件系统的各个方面

在MDD中模型不仅仅是一个文档或图形而且它是软件开发的中心所有的代码都是通过模型自动生成的开发人员在模型中定义系统的结构行为和功能等方面的信息然后使用模型转换器将模型转换为代码这些代码可以是各种编程语言如JavaC++Python等的代码也可以是配置文件数据库脚本等

MDD的优点包括

  1. 提高开发效率通过模型自动生成代码可以减少手工编写代码的时间和精力从而提高开发效率

  2. 提高代码质量由于代码是通过模型自动生成的因此可以减少代码错误和缺陷从而提高代码质量

  3. 改善软件维护性由于模型是对系统的抽象描述因此可以更好地理解和维护软件系统

总之模型驱动开发是一种基于模型的软件开发方法它将模型作为软件开发的中心通过自动生成代码的方式来实现软件开发从而提高开发效率代码质量和软件维护性

自动机的事件状态迁移等内容理解

自动机是一种用于描述计算过程或系统行为的数学模型它由一组状态和一组转移函数组成每个状态代表系统或计算过程的一种状态每个转移函数描述了从一种状态到另一种状态的转移过程

自动机可以分为两种类型有限自动机和无限自动机有限自动机是指状态有限的自动机而无限自动机则是状态无限的自动机有限自动机通常用于描述具有固定数量的输入和输出的系统而无限自动机通常用于描述具有无限数量输入和输出的系统例如程序或操作系统

在自动机中事件是指输入到系统的信号或数据状态迁移是指自动机从一种状态到另一种状态的转移过程当自动机接收到一个事件时它会根据当前状态和转移函数计算出下一个状态并根据该状态执行相应的操作或输出

在实际应用中自动机常用于模拟计算机程序控制系统通信协议等它们也可以用于验证系统的正确性和安全性以及用于构建人工智能系统和机器学习模型

顺序图的偏序

顺序图是一种UML图用于描述对象之间的交互关系和消息传递顺序在顺序图中对象之间的交互被表示为一系列的消息传递每个消息传递都有一个发送者和一个接收者

顺序图中的偏序是指消息传递之间的先后顺序如果两个消息传递之间存在偏序关系意味着其中一个消息传递必须在另一个消息传递之前发生这种偏序关系可以通过在顺序图中使用垂直的虚线来表示

例如假设有两个对象A和B之间的交互其中A先发送了一条消息给B然后B再发送一条消息给A在顺序图中可以使用两个垂直的虚线表示这两个消息传递之间的偏序关系具体来说第一个虚线表示A发送消息的时间第二个虚线表示B发送消息的时间这种偏序关系可以帮助开发人员更好地理解和描述对象之间的交互顺序从而更好地设计和实现系统

状态迁移时, 动作执行的顺序

在状态迁移时动作的执行顺序可以分为两种情况

  1. 动作在状态迁移之前执行这种情况下动作会在状态迁移之前执行然后才进行状态的迁移这种情况通常称为”动作优先”它意味着动作的执行对状态的迁移是有影响的例如在状态迁移之前可能需要对一些变量进行计算或更新这些计算或更新的结果可能会影响状态的迁移

  2. 动作在状态迁移之后执行这种情况下动作会在状态迁移之后执行这种情况通常称为”状态优先”它意味着状态的迁移对动作的执行是有影响的例如在状态迁移之后可能需要执行一些操作来处理新状态的变化这些操作可能会影响系统的行为或输出

UML profile 用处, 和UML区别

UML Profile是一种UML扩展机制它允许用户自定义UML元素和图形符号以满足特定领域的需求或特定的开发过程UML Profile提供了一种机制使得用户可以在UML中创建新的元素关系图形符号限制和约束等从而扩展UML的能力和适应性UML Profile可以用于各种领域如嵌入式系统Web应用企业架构等

与UML的区别在于UML是一种通用的建模语言它提供了一套标准化的符号和图形表示方法用于描述软件系统的结构和行为UML包括一些基本元素如类对象接口关系活动用例等以及一些图形符号如类图活动图时序图等UML的目标是提供一种通用的建模语言以便开发人员和系统工程师能够更好地理解和描述软件系统的结构和行为

UML Profile则是在UML基础上进行扩展它允许用户自定义UML元素和图形符号以扩展UML的能力和适应性UML Profile提供了一种机制使得用户可以在UML中创建新的元素关系图形符号限制和约束等从而满足特定领域的需求或特定的开发过程因此UML Profile可以看作是UML的一个扩展它提供了更多的建模元素和自定义能力以适应不同的建模需求

描述系统的执行过程 各次实验的理解(三选二)考察查询语言
实验ATM中死锁, 危害