笔试准备
目录- 笔试准备
- 1. C语言基础
- 1.1 new和malloc的区别?
- 1.2 有1G内存的计算机能否malloc(1.2G)?为什么?
- 1.3 extern“C”的作用是什么?
- 1.4 strcat、strncat、strcmp、strcpy哪些函数会导致内存溢出?如何改进?
- 1.5 static的用法(定义和用途)(必考)
- 1.6 const的用法(定义和用途)(必考)
- 1.7 volatile作用和用法
- 1.8 const常量和#define的区别(编译阶段、安全性、内存占用等)
- 1.9 变量的作用域(全局变量和局部变量)
- 1.10 sizeof和strlen(字符串,数组)
- 1.11 sizeof(struct)和sizeof(union)内存对齐
- 1.11 inline关键字(内联函数)
- 1.12 堆和栈的区别
- 1.13 内存四区,什么变量分别存储在什么区域,堆上还是栈上?
- 1.14 指针常量和常量指针
- 1.15 数组名和指针的区别
- 1.16 内存泄露
- 1.17 #define 和 typedef 的区别
- 1.18 使用#include<>和使用#include""有什么区别
- 1.19 写一个宏,返回参数比较小的那一个
- 1.20 数组和链表的区别
- 1.21 野指针
- 1.22 数组指针和指针数组的区别
- 1.23 c语言内存分配方式有几种
- 1.24 什么是函数指针,什么是指针函数
- 1.25 struct和class在C++中的区别
- 1.26 C++中的类有几个访问权限
- 1.27 用c语言实现strcpy函数
- 1.28 程序可分为几个段
- 1.29 队列和栈的区别
- 1.30 一个.c文件怎么转化为可执行程序
- 1.31 什么是交叉编译
- 1.32 使用32位编译情况下,给出判断所使用机器大小端的方法
- 1.33
- 1.34 用a变量给出下面的定义
- 1.35 与或非,异或,运算符优先级
- 1.36 explicit关键字、mutable关键字
- 1.37 环形缓冲区
- 1.38
- 2.操作系统
- 3.数据机构
- 4.计算机网络
- 5. 单片机
- 6. FreeTROS
- 7. 题库
- 8.公司相关
- 1. C语言基础
1. C语言基础
1.1 new和malloc的区别?
malloc和free是c++和c的库函数,对应头文件stdlib.h;new和delete是c++关键字,不用头文件,需要编译器支持;
malloc分配内存需要指定大小;new不用指定大小,编译器会根据数据类型计算分配;
malloc分配成功的返回值是void*类型指针,需要通过强制类型转换将void指针转成我们要的类型。new分配成功时返回的是对象类型的指针,与对象相匹配,不用强转,所以new是符合类型安全性的操作符;
malloc分配失败返回NULL;new分配失败会显示bad_allloc异常;
new会调用构造函数,malloc不会;
补充:
free 是 C 语言 API ,主要用来释放由 malloc 和 calloc 分配的内存
free 也可以释放 由 new 分配的内存,但是 不推荐使用
delete 是 C++ 关键字,主要用来释放由 new 分配的内存
delete 也可以用来释放由 malloc 和 calloc 分配的内存
在 C++ 中虽然可以使用 delete 替代 free 但是,还是建议搭配使用为好
不要使用 free 去释放由 new 分配的内存,因为它 不会去触发析构函数
1.2 有1G内存的计算机能否malloc(1.2G)?为什么?
可以申请1.2G内存。malloc函数是向程序虚拟空间申请虚拟地址空间,与物理地址没有直接关系。程序运行后,物理地址由操作系统完成。
1.3 extern“C”的作用是什么?
在C++中使用C编译好的函数模块,需要用到extern"C"。也就是extern”C“都是在c++文件里面添加。
extern在链接阶段起作用(四大阶段:预处理--编译--汇编--链接)
1.4 strcat、strncat、strcmp、strcpy哪些函数会导致内存溢出?如何改进?
strcpy(char *strDest,const char *strSrc)函数会导致内存溢出。strcpy不安全,拷贝时不会判断拷贝大小,也不会判断目的地址内存是否够用;
strncpy函数会计算复制的大小,但不会检查目标地址大小;
strncpy_s是安全的;
strcmp(str1,str2)比较函数,str1=str2返回零,小于返回负数,大于返回正数(比较字符串);
strncat()主要功能是在字符串结尾追加n个字符;
strcat()函数主要用来将两个char类型链接。strcat(a字符串,b字符串),结果a字符串变为接有b字符串;
延伸:
memcpy函数可以拷贝任意类型的数据,strcpy只能拷贝字符串类型数据;
memcpy函数用于把资源内存(src指向的内存)拷贝到目标内存(dest指向的内存);
1.5 static的用法(定义和用途)(必考)
static修饰局部变量、全局变量、函数
- static修饰局部变量,让变量变成静态存储方式,函数执行完毕后,内存不被释放,继续保留,即只会被初始化一次;
- static修饰全局变量,让变量只在这个文件内部有用,其他文件不能调用;
- static修饰函数,让函数只在文件内有用,其他文件不可见。又称为静态函数,避免了多文件函数重名出现干扰;
1.6 const的用法(定义和用途)(必考)
const修饰变量、函数形参和类成员函数
- const修饰常量,定义时就初始化,后续不能再改变;
- const修饰形参,func(const int a){};该形参在函数里不能改变;
- const修饰类成员函数,函数对成员变量只能读,不能修改成员变量的数值;
const的好处,被const修饰受强制保护,预防意外变动,提高程序健壮性。
补充:
int const * a const; int不可修改,*不可修改。const管控const优先管控位于前面的类型
1.7 volatile作用和用法
定义为volatile的变量说明其可能会被意想不到的改变。编译器要使用这个变量时,必须从该变量的地址中获取值,而不是使用寄存器中的备份;
使用情况:
- 并行设备的硬件寄存器(如:状态寄存器)
- 一个中断服务子程序中会访问到的非自动变量
- 多线程应用中被几个任务共享的变量
补充:
- 一个参数可以既是const还是volatile。volatile说明其可能会意想不到的改变,const说明其不应该被试图改变;
- 一个指针也可以是volatile,因为指针和普通变量一样会随程序变化有不可控性。比如,子中断服务子程序修改一个指向buffer的指针时,必须使用volatile来修饰指针。
1.8 const常量和#define的区别(编译阶段、安全性、内存占用等)
- 用define max 100;定义的变量时没有类型的(不进行类型安全检查,可能会产生意想不到的错误),给出的是单纯立即数,编译器只是把定义常量值与常量名关联起来,define所定义的宏变量在预处理阶段时进行替换,在程序中使用该变量的地方也进行拷贝替换;
- const int max = 255;定义的常量有类型(编译时会进行类型检查)名字,变量存放在静态区域中,在编译时确定实际值。在程序运行过程中,const变量只有一个拷贝。#define定义的宏变量有多个拷贝,因此宏定义在程序运行过程中所消耗的内存比const变量大很多。
1.9 变量的作用域(全局变量和局部变量)
- 全局变量,在所有函数体外部定义,程序所在的部分(甚至其他文件中的代码)都可以使用,全局变量不受作用域的影响,即全局变量的生命周期直到程序运行结束,存储在全局数据区中;
- 局部变量:出现在一个函数作用域内,局限于一个函数。局部变量常称为自动变量,进入作用域时自动生成,离开作用域自动消失。关键字auto可以显式的说明这个问题,但局部变量默认为auto类型,没必要特殊声明,存储在栈里面。
补充:局部变量和全局变量可以重名,在局部变量作用域范围内局部变量生效。
1.10 sizeof和strlen(字符串,数组)
本质区别sizeof是关键字,strlen是函数
-
举例,对于数组:
-
对于指针,sizeof只会检测到是指针的类型,指针都是占用4个字节的空间(32位机) ;例如
-
sizeof()中的数据或指针基本不会做运算,例如sizeof(1+2.0),直接检测到其中类型是double,即是sizeof(double) = 8 ;
-
strlen函数用法:
#include <stdio.h> #include <string.h> int main () { char str[50]; int len; strcpy(str, "This is runoob.com"); len = strlen(str); printf("|%s| 的长度是 |%d|\n", str, len); return(0); }
结果
|This is runoob.com| 的长度是 |18|
1.11 sizeof(struct)和sizeof(union)内存对齐
sizeof 是运算符,关键字,用来计算数据类型、变量所占空间存储的大小,包括句为\0,编译期;strlen计算字符串长度,不包括句尾\0,运行期。包含在string.h内
内存对齐是在存储数据时,将数据按照一定的规则放置在内存中的过程。
struct和union的区别
- union 联合体的成员共享一块地址。共同体大小 = 成员中占内存最大成员的大小。
- struct结构体不同成员放在不同的地址中。结构体大小 = 所有成员大小之和(内存对齐)。
内存对齐作用:
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台只能在某些地址出取某些特定类型的数据,否则抛出硬件异常(比如MAC);
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因是为了访问未对齐的内存,处理器需要做两次内存访问;而对齐内存仅需要一次访问;
结构体struct内存对齐三大原则:
- 对于结构体内部各成员,第一个成员的偏移量是0,排列在后面的成员其当前偏移量必须是当前成员类型的整数倍;
- 结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,保证整个结构体占用的内存大小是结构体内最大数据的成员的最小整数倍;
- 如程序中有#progma pack(n) 预编译指令,则所有成员对齐以n字节为准(即偏移量是n的整数倍),不再考虑当前类型以及最大结构体内类型;
struct test
{
char x1;
short x2;
float x3;
char x4;
};
结构的第一个成员x1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其起始地址必须2字节 对界,因此,编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然对界地址上,在它们前面不需要额外的填充字 节。在test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最大对界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面 填充了3个空字节。整个结构所占据空间为12字节。
#pragma pack(8)
struct s1{
short a;
long b;
};
struct s2{
char c;
s1 d;
long long e;
};
#pragma pack()
问
1.sizeof(s2) = ?
2.s2的c后面空了几个字节接着是d?
结果如下:
sizeof(S2)结果为24.
成员对齐有一个重要的条件,即每个成员分别对齐.即每个成员按自己的方式对齐.
也就是说上面虽然指定了按8字节对齐,但并不是所有的成员都是以8字节对齐.其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里是8字节)中较小的一个对齐.并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节.
S1中,成员a是1字节默认按1字节对齐,指定对齐参数为8,这两个值中取1,a按1字节对齐;成员b是4个字节,默认是按4字节对齐,这时就按4字节对齐,所以sizeof(S1)应该为8;
S2 中,c和S1中的a一样,按1字节对齐,而d 是个结构,它是8个字节,它按什么对齐呢?对于结构来说,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个,S1的就是4.所以,成员d就是 按4字节对齐.成员e是8个字节,它是默认按8字节对齐,和指定的一样,所以它对到8字节的边界上,这时,已经使用了12个字节了,所以又添加了4个字节 的空,从第16个字节开始放置成员e.这时,长度为24,已经可以被8(成员e按8字节对齐)整除.这样,一共使用了24个字节.
a b
S1的内存布局:11**,1111,
c S1.a S1.b d
S2的内存布局:1***,11**,1111,****11111111
联合体union内存对齐两大规则:
- 找到占用字节最多的成员;
- union的字节数必须是占用字节最多成员的字节的倍数,而且能够容纳其他成员;
1.11 inline关键字(内联函数)
c语言中,如果函数被频繁调用,不断有函数入栈,即函数栈,会导致栈空间或栈内存的大量消耗。为解决这个问题,引入了inline修饰符,表示为内联函数;
函数调用要做很多工作:1.调用前保存寄存器,并在返回时恢复,复制实参,程序还需要变到新位置执行C++支持内联函数,目的时提高函数执行效率。关键字inline放在函数定义(定义而非声明)前面即可将函数指定为内联函数。内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。
#include <stdio.h>
inline const char *num_check(int v)
{
return (v % 2 > 0) ? "奇" : "偶";
}
int main(void)
{
int i;
for (i = 0; i < 100; i++)
printf("%02d %s\n", i, num_check(i));
return 0;
}
上面的例子就是标准的内联函数的用法,使用 inline 修饰带来的好处我们表面看不出来,其实,在内部的工作就是在每个 for 循环的内部任何调用 *dbtest(i)* 的地方都换成了 *(i%2>0)?"奇":"偶"*,这样就避免了频繁调用函数对栈内存重复开辟所带来的消耗。
1.12 堆和栈的区别
- 创建方式不同,栈是系统自动创建(栈主要用于保存局部变量),当函数执行完成,栈被销毁;堆是程序员手动进行创建和释放的,malloc进行创建,free进行释放;
- 空间大小不同,栈的空间通常比较小,堆的空间比较大;
- 访问速度,栈的访问速度比堆要快;
- 栈使用完成后会自动销毁,堆需要考程序员手动销毁。
1.13 内存四区,什么变量分别存储在什么区域,堆上还是栈上?
-
四区:栈区、堆区、全局区静态区(BSS未初始化数据,初始化数据和文字常量)、代码区
1.14 指针常量和常量指针
-
常量指针指的是指向一个常量的指针,这个指针无法修改所指向的数据;
int a = 5 ; const int* p = &a; *p = 6 ;会提示报错
int b = 6 ; p = &b; 可行 -
指针常量指的是指针是一个常量,指针指向的地址是固定的;
int* const p1 = &a;p1 = &b; 会提示报错
*p1 = 5 ; 可行
-
1.15 数组名和指针的区别
- 数组名就是数组首元素的地址,可以看作一个常量指针,这个指针不能修改指向,内存访问为4个字节;
- 使用指针访问数组的时候需要使用到解引用*,使用指针访问数组是间接访问,使用数组名访问数组是直接访问;
- 使用sizeof对指针和数组名计算的大小不同。指针的大小和编译器的位数有关(32位指针4字节,64位8字节),使用sizeof计算数组名是整个数组大小;
1.16 内存泄露
内存泄露是指程序在运行的时候,动态分配的空间没有被回收或正确的释放,导致了这个内存空仍然占据着系统资源;
int * p = malloc(20);后续空间未被释放便会泄露,使用free后delete释放。
1.17 #define 和 typedef 的区别
- define是一个预处理指令,typedef是关键字(用于类型重命名);
- define不会做正确性检查,直接替换,typedef会做正确性检查;
- define无作用域限制,typedef有作用域现在;
- 对指针的操作不同,常使用typedef;
1.18 使用#include<>和使用#include""有什么区别
- 使用#include<>编译器会从标准库的路径里面去搜索,对于搜索标准库的文件速度比较快;
- 使用#inclde""编译器会从用户的工作路径去搜索,对于自己定义的文件使用""速度较快。
1.19 写一个宏,返回参数比较小的那一个
#define MIN((a),(b)) ((a)<= (b) ? (a) : (b))
1.20 数组和链表的区别
- 数组的地址空间是连续的,链表的地址空间是不连续的;
- 数组的访问速度访问速度快,直接通过下标访问,访问链表需要通过遍历;
- 链表增删改查的速度比数组快
1.21 野指针
野指针说的是指向不可用内存的指针
- 当指针被创建的时候没有赋值,这个指针就是野指针; char *p;
- 当指针被free或delete后,如果没有把指针赋值为NULL,也是野指针; char *p1 = malloc(10); free(p1); 要加上 p1 = NULL;
- 当指针越界也是野指针。
1.22 数组指针和指针数组的区别
- 数组指针是指向数组的指针,本质是指针;指针数组本质是数组,数组里面每个元素都是指针;
- 举例:
int ( * p)[10] ; // 数组指针的声明
int * a, a1, a2;
int * p1[10] = { a, a1, a2}; // 指针数组
1.23 c语言内存分配方式有几种
- 静态存储区分配;例如全局变量,静态变量
- 栈上分配;例如函数中定义出的局部变量
- 堆上分配;malloc,new
1.24 什么是函数指针,什么是指针函数
- 函数指针,指向函数的指针;int (*fun)(int x,int y);
- 指针函数,函数的返回值是指针;int *fun(int x,int y);
1.25 struct和class在C++中的区别
- struct默认成员是公有的,class默认成员是私有的;
- 继承方面,struct默认公有继承,class默认私有继承;
- struct一般用于简单数据结构,class一般用于封装和继承。
1.26 C++中的类有几个访问权限
-
在c++中有三个访问权限:公有的、私有的和受保护的;
-
public,当成员声明为public时就可以在类的外部进行访问;例子
class fun { public: int a; fun(void) { } } int main(void) { fun b; b.a; }
-
private,当成员声明为private时只能在类的内部进行访问。例子
class fun { public: int a; fun(void) { a = 3; } } int main(void) { fun b; }
-
protected,当成员声明为protected时,只能在类内部或子类中进行访问。
1.27 用c语言实现strcpy函数
char * mystrcpy( char * dest, char * src)
{
char * temp = dest;
while (( * dest++ = * src++));
return temp;
}
1.28 程序可分为几个段
- 代码段:用于存储程序的可执行命令,一般时只读,防止被修改;
- 数据段(data):用于存储已经初始化的全局变量和静态变量;
- BSS段:存储没有初始化的全局变量和静态变量;
- 堆:mallo和free进行管理;
- 栈:用于存储局部变量,申请和释放由操作系统定义;
1.29 队列和栈的区别
- 访问方式:栈先进后出,队列时先进先出。
- 栈:只能在栈顶操作;队列只能在队尾进行插入,队首进行删除怕;
- 应用场景不同:栈主要用于函数调用和表达式求职;队列用于任务调度,广度优先搜索;
1.30 一个.c文件怎么转化为可执行程序
- 预处理:将头文件和宏定义进行展开,生成没有注释的源代码; .i 文件
- 编译:将预处理得到的源代码,转化为汇编代码; .s文件
- 汇编:将汇编的代码转化为机器码,生成对应的目标文件; .o 文件
- 链接: 将全部.o文件链接成一个可执行程序;
1.31 什么是交叉编译
交叉编译指的是在一个平台上编译出另一个平台的可执行程序
举例: ARM开发板 .c 文件无法使用
就在ubuntu中编译,使用交叉编译链转换成 可执行程序。 GUN的gcc工具
1.32 使用32位编译情况下,给出判断所使用机器大小端的方法
联合体方法判断方法:利用union结构体的从低地址开始存,且同一时间内只有一个成员占有内存的特性。大端储存符合阅读习惯。联合体占用内存是最大的那个,和结构体不一样。a和c公用同一片内存区域,所以更改c,必然会影响a的数据
#include <stdio.h>
int main(void)
{
union w
{
int a;
char b;
} c;
c.a = 1;
if(c.b == 1)
printf("小端存储\n");
else
printf("大端存储\n");
return 0;
}
指针方法,通过将int强制类型转换成char单字节,p指向a的起始字节(低字节)
#include <stdio.h>
int main(void)
{
int a = 1;
char *p = (char *)&a;
if(*p == 1)
{
printf("小端存储\n");
}
else
printf("大端存储\n");
return 0;
}
1.33
1.34 用a变量给出下面的定义
a) 一个整型数;
b)一个指向整型数的指针;
c)一个指向指针的指针,它指向的指针是指向一个整型数;
d)一个有10个整型的数组;
e)一个有10个指针的数组,该指针是指向一个整型数;
f)一个指向有10个整型数数组的指针;
g)一个指向函数的指针,该函数有一个整型参数并返回一个整型数;
h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数
int a;
int *a;
int **a;
int a[10];
int *a[10];
int ( * a)[10];
int (*a)(int)
int (*a[10])(int)
1.35 与或非,异或,运算符优先级
sum=a&b<<c+a^c;
其中a=3,b=5,c=4(先加再移位再&再异或)答案4
1.36 explicit关键字、mutable关键字
1.37 环形缓冲区
使用环形缓冲其,经常被用于串口通信,socket,使用环形缓冲区可以将数据保存下来,使用R,W指针来进行数据的操作
1.38
2.操作系统
2.1 Memory、Cache、Buffer的区别
- Memory指内存处理器,用于暂时存放CPU中的运算数据,以及作为与硬盘等外部存储器交换数据的临时存储器;
- Cache指数据交换的缓存区,位于CPU和主内存间的快速、小容量临时存储器。因为CPU速度远大于主内存,cpu直接调取数据需要等待 ,于是就把常用的数据放在Cache中,减少CPU等待时间,提高系统效率;要保证CPU在Cache中的高命中率,缓存Cache中通常使用“最近最少使用算法,LRU算法”进行数据更换淘汰。核心是:每行数据设置一个计数器,每次被调用时,该行计数器归零,其他行计数器+1,最后计数最大的数据淘汰出Cache。Read Cache的数据随机的。Cache分为L1 Cache,L2 Cache,L3 Cache三种,理论上,读取Cache命中的概率为80%,那么在缓存中命中数据的概率为:P(L1)+P(非L1)*P(L2) = 0.96;
- Buffer指缓冲区,用于存储速度不同步的设备或优先级不同的设备之间的数据传输。通过Buffer可以减少进程间通信需要的等待时间,当存储速度慢的设备和存储速度快的设备进行通信时,存储速度慢的设备先将数据存放到Buffer中,每当Buffer满或者主动flush buffer时主动触发数据读取,对于少量数据,当Buffer满时才read一次;当数据量大时,可控制每次read的数据量,因此,无论数据量的大小每次read buffer的数据量都是按照buffer尺寸的数据量,因此,read buffer的数据是顺序访问的。Buffer相当于慢存储速度设备的中介。
2.2 进程和线程的区别
- 定义上,进程是资源分配的基本单位;线程是进程中的执行单元,CPU调度和执行的基本单位 进程 > 线程
- 资源占用上,每个进程都有自己独立的地址空间;线程共享进程的地址空间;
- 容错性上,当一个进程出现问题不会影响其他进程的执行;但当线程出现问题可能会导致整个程序崩溃,进而影响其他线程;
- 调度和切换上,进程是一个独立的单位,需要恢复的上下文消耗的资源比较多;线程调度和切换消耗的资源比较少。
2.3 进程间通信有几种方式,哪几种需要借助内核
- 管道;命名管道;共享内存(mmap);信号量;消息队列;套接字(socket);信号 共七种;
- 管道、共享内存(在内核空间创建的共享内存)、信号量、消息队列、套接字 这五种需要借助内核。
2.4 进程有哪几种状态
- 创建状态:当调用fork函数就会进入创建状态;
- 就绪状态:进程已经准备好,等待CPU运行
- 运行状态:进程已经获得系统的资源并运行
- 阻塞状态:等待信号量或者互斥量等事件的时候会进入该状态
- 终止状态:进程结束
状态关系图如下:
2.5 什么是僵尸进程,孤儿进程,守护进程?
-
僵尸进程:使用fork创建子进程后,如果子进程退出,而父进程没有调用wait或waitpid函数获取子进程的退出状态,那么这个子进程的信息还保存在系统中,这个时候这个子进程称为僵尸进程;
-
孤儿进程:当父进程异常结束时,子进程就变成了孤儿进程,会被init 1号收养;
-
守护进程daemon:在父进程创建出子进程后故意把父进程结束这个进程就称作守护进程;
补充
3.数据机构
3.1 排序算法
3.1.1 插入排序
void InsertSort(int* arr,int n)
{
for(int i = 0; i < n - 1; ++i)
{
// 记录有序序列最后一个元素的下标
int end = i;
// 待插入的元素
int tem = arr[end + 1];
// 单趟排
while (end >= 0)
{
// 比插入的数大就往后移
if(tem < arr[end])
{
arr[end + 1] = arrr[end];
end--;
}
// 比插入的数小,跳出循环
else
{
break;
}
}
// tem放到比插入的数小的数后面
arr[end + 1] = tem;
}
}
时间复杂度:最坏情况下为O(N*N),此时待排序列为逆序,或者说接近逆序
最好情况下为O(N),此时待排序列为升序,或者说接近升序。
空间复杂度:O(1)
3.1.2 冒泡排序
void BubbleSort(int* arr,int n)
{
int end = n;
while (end)
{
int flag = 0;
for (int i = 1; i<end; ++i)
{
if (arr[i - 1] > arr[i])
{
int tem = arr[i];
arr[i] = arr[i-1];
arr[i - 1] = tem;
flag = 1;
}
}
if (flag == 0)
{
break;
}
--end;
}
}
时间复杂度:最坏情况:O(N^2)
最好情况:O(N)
空间复杂度:O(1)
3.1.3 归并排序
https://blog.csdn.net/weixin_46726346/article/details/105913203
int merge(int r[],int s[],int left,int mid,int right)
{
int i,j,k;
i=left;
j=mid+1;
k=left;
while((i<=mid)&&(j<=right))
if(r[i]<=r[j])
{
s[k] = r[i];
i++;
k++;
}
else
{
s[k]=r[j];
j++;
k++;
}
while(i<=mid)
s[k++]=r[i++];
while(j<=right)
s[k++]=r[j++];
return 0;
}
int merge_sort(int r[],int s[],int left,int right)
{
int mid;
int t[20];
if(left==right)
s[left]=r[right];
else
{
mid=(left+right)/2;
merge_sort(r,t,left,mid);
merge_sort(r,t,mid+1,right);
merge(t,s,left,mid,right);
}
return 0;
}
int main()
{
int a[10];
int i;
for(i=0;i<10;i++)
scanf("%d",&a[i]);
merge_sort(a,a,0,9);
for(i=0;i<10;i++)
printf("%d ",a[i]);
return 0;
}
3.1.4 选择排序
void swap(int* a, int* b)
{
int tem = *a;
*a = *b;
*b = tem;
}
void SelectSort(int* arr,int n)
{
// 保存参与单趟排序的第一个数和最后一个数的下标
int begin = 0,end = n - 1;
while(begin < end)
{
// 保存最大值的下标
int maxi = begin;
// 保存最小值的下标
int mini = begin;
// 找出最大值和最小值的下标
for (int i = begin; i <= end; ++i)
{
if (arr[i] < arr[mini])
{
mini = i;
}
if (arr[i] > arr[maxi])
{
maxi = i;
}
}
// 把最小值放在序列开头
swap(&arr[mini], &arr[begin])
// 防止最大的数在begin位置被换走
if (begin == maxi)
{
maxi = mini;
}
// 把最大值放在序列结尾
swap(&arr[maxi], &arr[end]);
++begin;
--end;
}
}
时间复杂度:最坏情况:O(N^2)
最好情况:O(N^2)
空间复杂度:O(1)
3.1.5 堆排序
3.1.6 快速排序
思路:
1、选出一个key,一般是最左边或是最右边的。
2、起始时,prev指针指向序列开头,cur指针指向prev+1。
3、若cur指向的内容小于key,则prev先向后移动一位,然后交换prev和cur指针指向的内容,然后cur指针++;若cur指向的内容大于key,则cur指针直接++。如此进行下去,直到cur到达end位置,此时将key和++prev指针指向的内容交换即可。
经过一次单趟排序,最终也能使得key左边的数据全部都小于key,key右边的数据全部都大于key。
然后也还是将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作
void QuickSort(int *arr,int begin,int end)
{
if (begin >= end)
return;
int cur = begin, prev = begin - 1;
int keyi = end;
while (cur != keyi)
{
if (arr[cur] < arr[keyi] && ++prev != cur)
{
swap(&arr[cur], &arr[prev])
}
++cur;
}
swap(&arr[++prev], &arr[keyi])
keyi = prev;
QuickSort(arr, begin, keyi - 1);
QuickSort(arr, keyi + 1, end);
}
3.2 查找算法
3.2.1 二分查找
int binary_search(int array[], int value, int size)
{
int low = 0;
int high = size -1;
int mid;
while(low <= high)
{
mid = (low + high) / 2; // 二分
if(array[mid] == value) // 中间数据是目标数据
return mid;
else if(array[mid] < value) // 中间数据比目标数据小
low = mid + 1;
else // 中间数据比目标数据大
high = mid - 1;
}
return -1;
}
3.2.2
4.计算机网络
4.1 TCP和UDP的区别
- 数据的可靠性:TCP提供的时可靠的数据传输,三次握手;UDP是无连接的,所以是不可靠的数据通信;
- 通信方式:TCP是面向连接的通信方式 ;UDP是不需要进行连接;
- 数据传输的效率:UDP的通信速率比TCP,但UDP丢包的概率比较大;
- 应用场景:TCP一般用于邮件、文件传输(FTP) ;UDP用于视频,在线游戏,直播
补充:
5. 单片机
5.1 SPI和IIC寻址的区别
- SPI一共有四根线(MISO、MOSI、SCLK和CS),通过CS片选引脚,选择对应设备进行通信;
- IIC一共四根线(VCC、GND、SCL和SDA),通过从机地址来进行寻找(7位和10位)
5.2 UART,IIC、SPI的区别
- 通信方式:UART是异步通信,没有时钟线;IIC和SPI都是同步通信分别有SCL和SCLK
- 接线的区别:UART(TX、RX);SPI(SCLK时钟线,CS片选引脚,MOSI主机输出从机输入,MISO主机输入从机输出);IIC(SDA数据线,CSL时钟线)
- 设备数量:UART一对一通信;IIC支持多主机和多从机间的通信;SPI一主机多从机之间的通信
- 传输速度:UART(波特率决定,常见有115200和9600);IIC(标准模式100kbps,快速模式400kbps,高速模式3.4Mbps);SPI速度可配置
- 工作模式:UART(全双工),IIC(半双工),SPI(全双工)
5.3 SPI可以去除哪几根线工作?
SCLK时钟线,用于同步数据传输时的时序控制;MOSI主设备输出,从设备输入线;MISO主设备输入,从设备输出线;CS片选线,用于选择对应的从机设备
不需要双向通信的话,MOSI和MISO可以去除其中一根;
只进行一对一通信时,可以把CS片选线去除;
5.4 什么是DMA?
DMA是一种无需CPU参与就可以让外设和系统之间进行双向传递的方式。直接内存访问;相当于CPU的助理,RAM将数据给到DMA,DMA把数据传到外设;
DMA可以减轻CPU的负担,提高运行效率;
5.5 异步通信和同步通信
异步通信: 数据的发送和接收时独立进行的,发送方和接收方不需要同时进行操作,发送方可以在发送完数据后继续区执行其他任务,接收方是在数据到达后进行处理;特点:非阻塞,无需等待响应,适用于延迟较大的场景;
同步通信:发送方在发送数据后会等待接收方的响应,才能继续执行,发送方和接收方必须在相同的时间节点进行操作。如IIC,发送数据后需要等待ACK回复。特点:阻塞,需要等待等待响应,适合用于延迟较小的场景。
6. FreeTROS
6.1 FreeRTOS的调度算法
- 强制式调度:高优先级的任务可以打断低优先级的任务执行;
- 时间片轮转:相同优先级的任务具有相同大小的时间片;
- 协作式调度:使用vtaskdelay释放CPU的资源让其他的任务来运行,信号量和互斥量来实现。
6.2 RTOS中任务同步的方式
- 队列,还可以进行数据的传递;
- 信号量,在FreeRTOS中分为两种,二进制信号量(实现共享资源的访问),计数型信号量(实现生产者和消费者模型);
- 互斥量,实现共享资源的访问;
- 事件组:可以等待多个任务的通知;
- 任务通知:是一种轻量级的任务同步方式,TCB,任务通知不需要创建就可以使用(用于一对一的通信);
6.3 RTOS中时间片的大小由什么决定?
在 FreeRTOSConfig.h 文件中
#define configTICK_RATE_HZ ((TickType_t)1000) // 时间片的频率 1000HZ = 1ms
6.4 FreeRTOS中任务的状态
- 就绪态Ready,当任务被创建的时候就会进入到就绪态;
- 运行态Running,当任务的代码正在被执行的时候就是运行态;
- 阻塞态Blocked,当任务在等待信号量或者时互斥量等的时候就会进入到阻塞态;
- 挂起态Suspend, 任务使用vTaskSuspend会进入挂起状态。Resume后又会返回就绪态;
7. 题库
https://blog.csdn.net/Cowena/article/details/47165849?ops_request_misc=&request_id=&biz_id=102&utm_term=嵌入式工程师笔试题&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-3-47165849.142v100pc_search_result_base5&spm=1018.2226.3001.4187
https://blog.csdn.net/qq_38410730/article/details/80927935?ops_request_misc=%257B%2522request%255Fid%2522%253A%25223b62a5b89da27f1468635494485a43f9%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=3b62a5b89da27f1468635494485a43f9&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-2-80927935-null-null.142v100pc_search_result_base5&utm_term=%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%B7%A5%E7%A8%8B%E5%B8%88%E7%AC%94%E8%AF%95%E9%A2%98&spm=1018.2226.3001.4187
https://blog.csdn.net/m0_74055118/article/details/138201909?spm=1001.2014.3001.5502
https://blog.csdn.net/qq_38410730/article/details/80951443?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522159678426719725211952883%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=159678426719725211952883&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_v1~rank_blog_v1-1-80951443.pc_v1_rank_blog_v1&utm_term=%E5%A4%A7%E7%96%86%E5%B5%8C%E5%85%A5%E5%BC%8F%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B%E5%B8%88&spm=1018.2118.3001.4187
7.1 C语言刷题总结(一)
-
预处理-编译-汇编-链接
预处理阶段:头文件的包含#include,宏替换#define,条件编译,去除注释,添加行号
-
#define MI((A),(B)) ((A)<=(B) ? (A) : (B)) 定义标准宏MIN
-
知道数组table,用宏求宿主元素个数
#define Count(table) (sizeof(table)/sizeof(table(0))) -
带参宏和函数的区别
a.带参宏在预编译阶段进行简单的字符替换;函数是在运行是调用和返回
b.宏替换不占用运行时间,占用编译时间;函数占用运行时间(分配单元、保留现场、值传递和返回) -
内联函数inline的优缺点
优点:和宏定义类似直接替换展开,省去了调度开销,还能检查类型
缺点:膨胀代码,消耗更多内存空间
适用场景:函数体内没有循环(执行时间短)且代码简短(占用时间小),双小,空间小,耗时小 -
关键volatile的作用和三个举例
volatile告诉编译器这个变量可以会被意想不到的改变,所以每次使用这个变量都要去地址重新读,而不是使用寄存器的值;
如:并行设备的硬件寄存器(状态寄存器);一个中断服务子进程中会访问到的非自动变量;多线程应用中被几个线程共享的变量(防止死锁) -
C语言实现读写寄存器变量
#define rBANCON0 (*(volatile unsigned long *)0x48000004)
rBANCON0 = 0x12;
-
常量不能自增、自减
-
预处理命令都是#开头的,反之也对
-
#error的作用是什么?
编译程序时,只要遇到 #error 就会跳出一个编译错误统头文件中指定的,当你不太确定当前是否定义了 XXX 时,可写如下预处理代码:
#ifdef XXX
#error "XXX has been defined"
#else
…
#endif
这样,如果编译时出现错误,输出了 XXX has been defined ,表明宏 XXX 已经被定义
了。 -
用预处理指令#define声明一个常数,用以表明1年中有多少秒
#define SECONDS_PER_YEAR (60*60*24*365)UL
UL ,告诉编译器这个常数是的无符号长整型数 -
关键字static的作用
局部变量,变量从栈区转变到data静态区,生命周期从函数变成了整个程序,只被初始化一次
全局变量,让变量只能在这个文件使用,不能被其他文件使用
函数,让函数只在文件内有用,其他文件不可见。又称为静态函数,避免了多文件函数重名出现干扰; -
const的使用
const int a; // a是一个整形常量 int const a; // a是一个整形常量 const int *a; // a是一个指向整形常量的指针变量 int * const a; // a是一个指向整形变量的指针常量 int const * const a = &b; // a是一个指向整形常量的指针常量 char * strcpy(char *strDest,const char *strSrc); // 参数在函数内部不会被修改 const int strcmp(char *source,char *dest); // 函数的返回值不能被修改
const 放在 * 前是修饰指向的对象,放在 * 后则是修饰指针本身
-
const和volatile可以同时使用,由于 *ptr 的值可能被意想不到地该变,因此 a 和 b 可能是不同的。结果,这段代码可能
返不是你所期望的平方值!正确的代码如下:long square( volatile int *ptr) { int a; a = *ptr; return a * a; }
-
extern用于跨文件引用全局变量
-
C++代码中 使用extern “c” 可调用c函数,
7.2 C语言刷题总结(二)
-
变量a定义
int a; int *a; int **a; int a[10]; int *a[10]; int (*a)[10]; int (*a)(int); int (*a[10])(int)
-
有符号数和无符号数时,有符号数隐式转换成了无符号数(即底层的补码不变,但是此数从有符号数变成了无符号数)。
-
float x与“零值”比较的if语句
if(x<0.000001 && x>-0.000001);
-
由gcc编译的C语言程序占用的内存分为哪几个部分?
栈区(stack):存放函数的参数,局部变量
堆区(heap):程序员申请的动态空间malloc,free
全局区(静态区static):全局变量和静态变量不为0,const常量 位于data段,未初始化的全局变量和静态变量位于BSS段;
程序代码区:存放函数体二进制代码和字符串常量 -
判断大小端存储
方法一:利用union结构体#include <stdio.h> int main(void) { union w { int a; char b; } c; c.a = 1; if(c.b == 1) printf("小端存储"); else printf("大段存储"); return 0; }
方法二:利用int*类型强转char*
#include <stdio.h> int main(void) { int a = 1; char *p = (char *)&a; if(*p == 1) printf("小端存储"); else printf("大端存储"); return 0; }
-
位翻转实现
unsigned char bit_reverse(unsigned char input) { unsigned char result = 0; int bit = 8; while(bit--) { result |= ((input & 0x01 ) << bit); input >>= 1; } return result; }
-
字符串倒序
#include <string.h> void iverted_order(char *p) { char *s1,*s2,tem; s1 = p; s2 = s1 + strlen(p) - 1; while(s1<s2) { tem = *s1; *s1 = *s2; *s2 = tem; s1++; s2--; } }
-
找出一个字符串中一个最长的连续数字,并标注出位置和长度
char *find(char *a,int *size) { char *in = a,*temp,*pos; int count = 0, max = 0; while(*in != '\0') { if(*in >= '0' && *in <= '9') // 寻找数字 { temp = in; while(*in >= 0 && *in <= '9') // 判断数字 { count += 1; in ++; } if(count > max) //记录最长长度和记录当前数字地址 { pos = temp; max = count; count = 0; } } in++; } *size = max; return pos; }
-
写一个函数,判断输入参数是不是质数(素数)
int IsPrime(unsigned int p) { unsigned int i; if(p <= 1) { printf("请输入大于1的自然数。\n") return -1; } for(i = 2; i <= sqrt(p); i++) { printf("该数不是质数。\n"); return 0; } printf("该数是质数。\n"); return 0; }
-
大小端转化:对一个输入的整型数进行大小端存储模式转化
int endian_convert(int input) { int result = 0; int size = size(input); while(size--) { result |= ((input & 0xFF) << (size * 8)); input >>= 8; } return result; }
-
验证回文串:给定一个字符串,验证它是否是回文串,只考虑字符和数字字符,可以忽略字母的大小写。(回文串即左右对称的字符串,如"A man, a plan, a canal: Panama")
bool isPalindrome(char * s)
{
char *left = s, *right = s + strlen(s) - 1;
if(strlen(s) == 0) return true;
while(left < right)
{
while(!((*left >= 'a' && *left <= 'z') || (*left >= 'A' && *left <= 'Z') || (*left >= '0' && *left <= '9')) && left < right) // 找到字母或数字
left++;
while(!((*right >= 'a' && *right <= 'z') || (*right >= 'A' && *right <= 'Z') || (*right >= '0' &&*right <= '9')) && right > left) // 找到字母或数字
right--;
if(left < right)
{
if((*left >= 'A' && *left <= 'Z')) // 若为大写,则转为小写
*left = *left + 32;
if((*right >= 'A' && *right <= 'Z')) // 若为大写,则转为小写
*right = *right + 32;
if(*left == *right) // 比较
{
left++;
right--;
}
else
return false;
}
else
return true;
}
return true;
}
7.3 C语言刷题总结(三)
-
写一个程序, 要功能:求出用1,2,5这三个数不同个数组合的和为100的组合个数。 如:100个1是一个组合,5个1加19个5是一个组合。
void count(void) { int x, y, z, number; number = 0; for (x = 0; x <= 100 / 1; x++) { for (y = 0; y <= 100 / 2; y++) { if (100 - x - y * 2 >= 0 && (100 - x - y * 2) % 5 == 0) // 判断能否5整除 number++; } } printf("%d\t", number); }
-
数组a[N],存放了数字1 ~ N-1,其中某个数重复一次。写一个函数,找出被重复的数字,时间复杂度必须为O(N)。
int do_dup(int a[], int N) { int temp; // a[0]为监视哨 while (a[0] != a[a[0]]) // 若两者相等,则说明该数字是重复数字 { temp = a[0]; // 若不相等,则将该数放到以该数为下标的位置上 a[0] = a[temp]; // 该位置原来的数则放到0 为下标的位置上 a[temp] = temp; } return a[0]; // 返回重复数字 }
-
手写:字符串排序问题,时间复杂度O(n),任意字符串的排序并统计重复的个数,例如输入:$-%-#aeartvDEtGD%!% ,输出:!1#1$1%3-2D2E1G1a2e1r1t2v
#include <stdio.h> #include <string.h> void sort(char a[], int len) { int i; char b[128] = {0}, key; for(i = 0; i < len; i++) { key = a[i]; // 将字符对应的ASCII 码作为下标 b[key]++; // 对应元素+1 } for(i = 0; i < 128; i++) { if(b[i] != 0) printf("%c%d", i, b[i]); // 按顺序打印出字符跟个数 } printf("\n"); }
8.公司相关
此部分内容不对外展示,避免影响该公司
标签:const,函数,int,笔试,char,内存,准备,指针 From: https://www.cnblogs.com/yangyang13/p/18651946