【转】C++基础知识归纳(1)-大厂必备八股文篇

本文整理自CSDN
C++基础知识归纳(1)-大厂必备八股文篇

const作用

常引用

常对象

常成员函数(调用限制:常对象->常成员函数->常成员函数)

extern和static(作用域限制)

运算符重载

单目运算符重载为?

双目重载为?

=、[]只能重载成员函数,<<、>>只能重载为友元函数。

(主要区别在于是否访问参数对象的private成员)

内存分配方式

栈、

堆、

全局静态区、

常量区、

代码区

全局变量和全局静态变量区别

new delete 与malloc free

同:申请堆空间,成对使用(否则内存泄漏/二次释放)

异:主要是new和delete除了分配空间、释放空间外,还分别调用类的构造、析构函数,但malloc free不会。

其次malloc 还可以通过realloc重新分配内存,new不能;new是运算符,malloc是函数。

explicit

避免函数参数被隐式类型转换, explicit关键字只对有一个参数的类构造函数有效

mutable

和const相反,mutable变量永远可变,高于const优先级,即使在const函数中。(使用mutable修饰的数据成员可以被const成员函数修改)

const修饰函数的返回值

该返回值只能被赋给加const修饰的同类型指针

1
2
3
4
5
const char * GetString(void);
// 如下语句将出现编译错误:
char*str = GetString();
// 正确的用法是
const char *str =GetString();

宏、const和enum

对于#define 定义的:

1.常量:使用enum和const代替;

2.宏函数,使用内联函数代替。

stack的生存期

static对象寿命?

local static对象、non-local static对象是什么?两者生命周期?

(local static对象包括作用域内、函数内对象)

STL相关

***************学习***************

STL是C++通用库,由**,____,____,____,**构成。

顺序容器:vector、deque、list、forward_list、array、string、

关联容器:map、multimap、set、multiset

容器适配器:priority_queue(基于vector)、queue(基于deque)、stack(基于deque) 其他:bitset

  • vector和deque如果没有预先指定大小,是不能用下标法插入元素的!
  • 序列式容器才可以在容器初始化的时候制定大小,关联式容器不行;
  • 关联容器的迭代器不支持it+n操作,仅支持it++操作。

算法相关头文件<algorithm>,<numeric>

迭代器几种?输入型迭代器(只读、自增),输出型迭代器(只写、自增),前向迭代器(前两者结合、自增),双向迭代器(读写、自增自减),随机存取迭代器(还支持比较、+-+=-=、迭代器减法、下标乱序访问)

https://blog.csdn.net/sim_szm/article/details/8980879

仿函数是什么?仅实现一个()运算符重载函数的空类,实现函数效果

适配器几种?容器适配器、迭代器适配器、函数适配器(基础容器并不是固定的,可以选择)

空间配置器分几级?

程序编译

预处理 -> 编译 -> 汇编 -> 链接

https://blog.csdn.net/daaikuaichuan/article/details/89060957

  1. 预处理:对宏定义进行展开,不进行语法检查(常量建议用const或enum,函数用内联函数)
  2. 编译:源文件翻译成二进制目标代码生成汇编代码.s文件,这个过程检查语法错误
  3. 汇编:根据汇编代码生成目标文件.o或.obj文件,这个过程把汇编语言代码翻译成目标机器指令
  4. 连接:将目标文件和其他文件分别进行编译生成的目标程序模块、系统提供的标准库函数链接在一起,生成可执行文件.out或.exe文件。分为静态连接和动态链接

const 赋值类型不一致时,生成新的常量

?

单例模式示例

位运算

左移右移补什么?(左移补0,右移,有符号补符号位,无符号补0)

负数存储什么形式?(计算机存储补码,正数原码反码补码相同,负数补码为反码+1)

C++二维数组作为形参传递参数(三种方式)

数组:形参是数组,给出第二维;void fun1(int array[][10])

引用:形参是二维数组的引用,必须加括号,给定二个维度;void fun2(int (&array)[10][10])

这两种都是传递实参名字。fun1(array);

指针:形参是二维指针、第二维。void fun3(int **a, int n);

int *a[n];

for(int i=0;i<n; i++) a[i] = new int [n];

实参fun3(a,n);

全局变量和static变量的区别

同:

异:

都是静态存储、都存在全局静态区,区别在文件作用域。

全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用, 因此可以避免在其它源文件中引起错误。

为什么栈要比堆速度要快

  1. 定位 (栈FILO,找到数据更快)

  2. 指令 (栈有CPU提供指令支持,操作系统支持的堆数据)

  3. 缓存 (栈一级缓存、堆在二级缓存,硬件性能上有差别)

  4. 优化 (各语言对栈的优化支持要优于对堆的支持,)

c++ 析构函数调用时间

3条,需要注意的是:

对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用

定义了对象的函数调用结束、局部对象释放、全局对象释放、new创建的对象释放、对象析构时,其成员函数也调用析构

c++中关于cout与printf的简单区别

3条区别

原理:cout使用流,存在缓冲区,只有当缓冲区满或者遇到endl、调用flush才输出

printf无缓冲输出。

效率:原理上流操作效率更高,但是____,总体效率更低

class与struct区别

默认继承权限

成员的默认访问权限

“class”这个关键字还用于定义模板参数,就像“typename”。但关建字“struct”不用于定义模板参数

静态绑定 动态绑定 (也叫动态连编,静态连编)

多态只能在运行时确定调用哪个方法

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

类型 指针有类型是变量、有内存;引用无类型不是变量、无内存。

大小 指针大小4或8字节;引用是被引用对象的大小。

初始化 指针初始化可选(一般赋值或置NULL),引用必须初始化赋值。

const 有const指针,没有const引用

可变 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用

多级 指针可以有多级指针(**p),而引用只有一级

为什么要有引用?运算符重载

为什么要有指针?历史遗留的兼容性问题

它们的联系?引用底层使用指针实现

c++里面的四种智能指针以及代码实现

为什么要有智能指针?

auto_ptr(c++98的方案,cpp11已经抛弃) 特点、被废弃原因 (所有权)

unique_ptr(替换auto_ptr) 特点、与auto_ptr异同 (独占所有权)

shared_ptr 为什么要有? 特点、与unique_ptr异同、机制 (共享所有权)

weak_ptr 为什么要有?特点、和引用计数的关系 (仅指向shared_ptr、不增加引用计数)

虚函数和多态

静态多态(重载)和动态多态

父类加了virtual,子类重写还用加吗?

类的最开始存放虚函数表指针,占4/8字节,继承时也会继承父类虚函数表。

虚函数表中存放父类的虚函数地址,若子类重写,则会替换对应函数地址。

extern “C”的主要作用简单解释

加上extern “C”后,会指示编译器这部分代码按C语言(而不是C++)的方式进行编译

区别:编译时一个只有函数名、一个还包括参数类型。

请你说说C语言是怎么进行函数调用的

压栈内容的顺序?

C语言函数参数压栈顺序?

C++中拷贝赋值函数的形参能否进行值传递?

提示:值传递在实参传递时拷贝

include头文件的顺序以及双引号””和尖括号<>的区别

当前头文件目录
编译器设置的头文件路径(编译器可使用-I显式指定搜索路径)
系统变量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的头文件路径

使用尖括号包含的头文件不会查找”当前头文件目录”

面试题—>#include尖括号和双引号的区别?

  • #include<> ,从标准库中寻找头文件。

  • #include””,从当前目录开始寻找头文件。

STL中迭代器有什么作用作用,有指针为何还要迭代器

聚合对象的模式

一个C++源文件从文本到可执行文件经历的过程

预处理 -> 编译 -> 汇编 -> 链接

https://blog.csdn.net/daaikuaichuan/article/details/89060957

  1. 预处理:对宏定义进行展开,不进行语法检查(常量建议用const或enum,函数用内联函数)

  2. 编译:源文件翻译成二进制目标代码生成汇编代码.s文件,这个过程检查语法错误

  3. 汇编:根据汇编代码生成目标文件.o或.obj文件,这个过程把汇编语言代码翻译成目标机器指令

  4. 连接:将目标文件和其他文件分别进行编译生成的目标程序模块、系统提供的标准库函数链接在一起,生成可执行文件.out或.exe文件。分为静态连接和动态链接

内存泄漏原因和判断方法

原因:

方法:linux下命令valgrid、编码习惯、链表形式管理指针、智能指针、插件(Dmalloc等)

分类:

new和malloc的区别

分配依据(数据类型/指定大小)

返回类型(对象指针、void*)

构造函数(new除了分配内存,还会调用构造函数)

类型(new是运算符、可重载;malloc是函数)

扩容(malloc申请到的内存可realloc扩容)

失败返回值(分配失败,new抛出bad_malloc的异常,malloc返回NULL)

申请数组(new[] delete[];malloc申请连续内存)

段错误

1.段错误是什么

访问的内存超出了系统给这个程序所设定的内存空间

2.段错误原因——4个

不存在的、系统保护的、只读的、栈溢出

C++重载实现原理

依据什么区分函数?()

重载时函数可以哪些不同?

C++ 函数调用过程

哪三步

sizeof求类型大小

哪些数据之和(非静态成员数据的类型大小之和

class大小怎么计算?类似struct

字节对齐原则:

在系统默认的对齐方式下,每个成员相对于这个结构体变量地址的偏移量正好是该成员类型所占字节的整数倍,且最终占用字节数为成员类型中最大占用字节数的整数倍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct A {
char y;
char z;
long long x;
}; 16字节
struct A {
char y;
char z;
int x;
}; 8字节

struct A {
char y;
char* z;
int x;
};12字节
struct A {
char y;
}; 1字节

void Fun(char str[100]){
sizeof(str) = ? //数组的变量名str是什么类型
//调用Fun(str)函数的main函数中,sizeof(str)是100
//数组名作函数形参时,沦为普通指针
}
void *p = malloc(100);
sizeof(p) = ? //p是什么类型


//上面两个都是4

总结:

  1. 最终大小一定是最大数据类型的整数倍;
  2. 静态变量不占空间
  3. 每种类型的偏移量为自身的n倍;

如何调试c++ 多线程程序?

https://blog.csdn.net/zhangye3017/article/details/80382496

info threads

thread ID(1,2,3…)

面向对象和面向过程的区别

面向对象:三大特性五大原则。划分成各个____

面向过程:自__向__顺序、____形结构。划分成各个____

(过程)优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗源;比如嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。缺点:没有面向对象易维护、易复用、易扩展。
(对象)优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统。缺点:性能比面向过程低。

关于引用赋值的多态

通过父类的____指向子类对象来实现多态

1
2
3
4
5
6
7
Class B;
Class D : public B;

B& b;
D& d;
B& b1 = d ; //父类可以作为子类的引用,此时b1表现和指针形式一致(会调用B的非虚函数)
D& d1 = b; //错误,不能将子类作为父类的引用

模板的声明和实现不能分开的原因

?????????????

C++类中引用成员和常量成员的初始化(初始化列表)

哪些变量初始化必须要初始化列表

(注意区分赋值和初始化)

枚举变量和宏的区别(3种常量区别)

  • 内存

const常量拷贝数量?

#define常量拷贝数? 有多少展开多少,不分配内存

  • 效率

const常量在符号表,而不是存储空间中,不用读写内存,效率高;

  • 编译器处理方式
  • 类型和安全检查
  • 存储方式

枚举和宏

  • 是否是常量

    ​ 枚举常量属于常量,宏不是

  • 是否有类型

    ​ 枚举有类型,宏没有类型

  • 是否有检查,易错性

    ​ 宏替换没有检查,

  • 定义常量的数量

    ​ 枚举可以一次定义多个,宏智能一个

  • 枚举和宏作用时间和存储形式不同

宏不分配内存

memset为int型数组初始化问题

头文件:#include <string.h>

char数组可以设置任意值

int数组只能设置0和-1,因为其实是把value转换成的16进制数字写入内存。

void * memset( void * ptr, int value, size_t num );

直接向int数组赋值1,底层存储的数据为0x01010101,对应十进制数并不是1

bzero区别?

C++中volatile的作用

为什么要有?使用场景?

建议编译器不要对该变量进行优化

你希望这个值被正确的处理,每次从内存中去读这个值,而不是因编译器优化从缓存的地方读取(比如读取缓存在寄存器中的数值),,从而保证volatile变量被正确的读取。

编译器对 inline 函数的处理步骤

步骤?(复制函数体、局部变量、参数映射、返回值)

优缺点

虚函数(virtual)可以是内联函数(inline)吗?

可以,但在多态时不行。

内联函数在____阶段将函数展开,而虚函数在运行期才能确定具体调用哪个。

const * 和 *const区别

const 在左边表示修饰数据类型,在右边表示修饰指针。

自我赋值可能带来的危害有哪些?

“自我赋值安全性”问题

“异常安全性”问题

右值引用

一般的引用是左值,但新标准引入了新的引用类型——右值引用&&

除了右值引用&&,const左值引用也可以绑定右值

1
2
3
int i = 42;
const int &r3 = i*42;//正确,const引用绑定右值
int &&r2 = i*42;//正确,右值引用绑定右值

左值和右值

左值引用

右值引用

常量左值:使用 const T&, 既可以绑定左值又可以绑定右值

已命名的右值引用,编译器会认为是个左值

  • str6 = std::move(str2),虽然将str2的资源给了str6,但是str2并没有立刻析构,只有在str2离开了自己的作用域的时候才会析构,所以,如果继续使用str2的m_data变量,可能会发生意想不到的错误
  • 没有提供移动构造函数,只提供了拷贝构造函数
  • move对含有资源的对象更有意义。(why)

universal references(通用引用)

T&&是左值还是右值引用?

完美转发

所谓转发,就是通过一个函数将参数继续转交给另一个函数进行处理,原参数可能是右值,可能是左值,如果还能继续保持参数的原有特征,那么它就是完美的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;

void RunCode(int &&m) {
cout << "rvalue ref" << endl;
}
void RunCode(int &m) {
cout << "lvalue ref" << endl;
}
void RunCode(const int &&m) {
cout << "const rvalue ref" << endl;
}
void RunCode(const int &m) {
cout << "const lvalue ref" << endl;
}
// 这里利用了universal references,如果写T&,就不支持传入右值,而写T&&,既能支持左值,又能支持右值
template<typename T>
void perfectForward(T && t) {
RunCode(forward<T> (t));
}

template<typename T>
void notPerfectForward(T && t) {
RunCode(t);
}

int main()
{
int a = 0;
int b = 0;
const int c = 0;
const int d = 0;

notPerfectForward(a); // lvalue ref
notPerfectForward(move(b)); // lvalue ref
notPerfectForward(c); // const lvalue ref
notPerfectForward(move(d)); // const lvalue ref

cout << endl;
perfectForward(a); // lvalue ref
perfectForward(move(b)); // rvalue ref
perfectForward(c); // const lvalue ref
perfectForward(move(d)); // const rvalue ref
}

总结

  • 三种引用类型,左值引用、右值引用和通用引用。通用引用由初始化时绑定的值的类型确定
  • 引用折叠规则:所有的右值引用叠加到右值引用上仍然是一个右值引用,其他引用折叠都为左值引用。当T&&为模板参数时,输入左值,它将变成左值引用,输入右值则变成具名的右值应用。
  • 移动语义可以减少无谓的内存拷贝,要想实现移动语义,需要实现移动构造函数和移动赋值函数。
  • std::move()将一个左值转换成一个右值,强制使用移动拷贝和赋值函数,这个函数本身并没有对这个左值什么特殊操作。
  • std::forward()和universal references通用引用共同实现完美转发。

补充:

动态库、静态库区别:

后缀:静态库.lib,动态库.dll。(静态库.a,动态库.so)

one is library archive and other is shared object

包含方式:静态库中代码被放入可执行文件中,会使文件大小更大;而动态库文件是单独的文件。动态库可执行程序文件更小

//加载时机:无论静态库中代码是否用到,都会在程序运行时被加载;动态库文件只有在需要时才被加载。动态库更节省空间

加载数量:运行多个程序,内存中会存在多份静态库文件的拷贝,分别存在每个程序的空间。如果是动态库,那么永远只会有一份拷贝。动态库更节省空间

依赖:只要发布者的lib文件正确,用户打开静态链接的可执行文件即可用,没有依赖;动态链接生成的文件需要依赖.dll文件才能运行,并且dll要和可执行程序兼容,否则也不会运行。

链接分为两种:静态链接动态链接

1)静态链接

静态链接:由链接器在链接时将库的内容加入到可执行程序中。

优点:

  • 对运行环境的依赖性较小,具有较好的兼容性

缺点:

  • 生成的程序比较大,需要更多的系统资源,在装入内存时会消耗更多的时间
  • 库函数有了更新,必须重新编译应用程序

2)动态链接

动态链接:连接器在链接时仅仅建立与所需库函数的之间的链接关系,在程序运行时才将所需资源调入可执行程序。

优点:

  • 在需要的时候才会调入对应的资源函数
  • 简化程序的升级;有着较小的程序体积
  • 实现进程之间的资源共享(避免重复拷贝)

缺点:

  • 依赖动态库,不能独立运行
  • 动态库依赖版本问题严重

3)静态、动态编译对比

前面我们编写的应用程序大量用到了标准库函数,系统默认采用动态链接的方式进行编译程序,若想采用静态编译,加入-static参数。

结构体内存对齐、结构体大小

总结:

  1. 最终大小一定是最大数据类型的整数倍;
  2. 静态变量不占空间
  3. 每种类型的偏移量为自身的n倍;

对象大小

没有成员变量、没有虚函数时最小,为1,单纯占位符,无实际意义

有成员变量,考虑基类大小、内存对齐

主要考虑如下:

  • 内存对齐
  • 基类大小
  • 静态成员(不算入大小,而是存在于堆当中)
  • 虚函数表指针
  • 虚基类表指针

const能否修饰static函数和成员、构造函数、析构函数、虚函数

const可以修饰static成员,但不能修饰static函数(static函数属于类,没有this指针,而const阻止修改对象的数据成员)

构造或析构函数不允许const,虚函数允许const

函数指针好处、用途

https://blog.csdn.net/wujiangguizhen/article/details/17153495)

一般的时候用不到,主要还是一个简化结构程序通用性的问题

1.实现回调函数,更加灵活

如果函数作参数不用指针的话,还真不知道怎么解决。参数传递一般就是传值和传址两种方式,作为函数的话,好像都想不出怎么传值。

  • 如libcurl中的函数:
1
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_memory_callback);

一旦收到需要保存的数据,libcurl就会调用此回调函数。 对于大多数传输,此回调被多次调用,每次调用都会传递另一块数据。

  • 如qsort函数中,可以自己写比较函数并设置其函数指针为参数,指定对象谁大谁小、升序还是降序
  • Libevent中设置回调函数evhttp_set_cb();
  1. 实现一种多态性

只需要改改指向的对象,就可以操作不同的函数

int a[5];(&a + 1) 与 (a + 1)的区别、a和&a的区别

https://www.nowcoder.com/questionTerminal/c4e82d13a1684119841b8934ef838847

a和&a数值相同,但a是首元素地址、&a是数组地址,(&a + 1) 与 (a + 1)不同)

指针数组和数组指针

int a[2][3];

a的类型是? (a是指向存放有3个空间数组的指针,等价于int (*p)[3])

1
2
3
4
5
6
7
8
9
10
11
int a[2][3];
int(*p)[3] = a; //成功
//a是指向存有3个元素的数组的指针,也是指向a[2][3]首地址的指针

int *q[3] ;
for (int i = 0; i < 3; i++)
{
q[i] = new int [3];
}
int **b = q; //成功
//q是二维指针

访问二维数组中元素的几种方法?

https://www.nowcoder.com/questionTerminal/0a16d61875e144208e581ee68765bd95

运算符优先级:淡云一笔,鞍落三服。

https://www.nowcoder.com/questionTerminal/ea83ecd103ac4928bbf4702df42fa2f3)

MySQL高频面试

https://www.nowcoder.com/discuss/637486?source_id=profile_create_nctrack&channel=-1)

优秀简历模板及计算机网络八股文

https://www.nowcoder.com/discuss/682094?channel=666&source_id=feed_index_nctrack)

MVC是什么

MVC的全名是Model View Controller,是一种使用“模型-视图-控制器”设计创建Web应用程序的模式,同时提供了对HTML、CSS和JavaScript的完全控制,它是一种软件设计典范。

派生类型和访问权限

https://www.cnblogs.com/lomper/p/4104542.html)

**基类成员访问权限(列) ** public public protected protected private private
派生方式(行) 派生类对象 派生类类内 **派生类对象 ** **派生类类内 ** **派生类对象 ** **派生类类内 **
public 可访/可修 权限:**public **可访/可修 不可访/不可修 权限:****protected ****可访/不可修 不****可访/不可修 权限:*private 可访*/不可修
protected 不****可访/不可修 权限:protected 可访/不可修 不****可访/不可修 权限:**private **可访/不可修 不****可访/不可修 权限:**private **不可访/不可修
private 不****可访/不可修 权限:**private **可访/不可修 不****可访/不可修 权限:**private **可访/不可修 不****可访/不可修 权限:*private 可访*/不可修

不能重载的运算符

绝大部分都可以重载,不能重载的只有5个。

  • sizeof运算符
  • 三目运算符?:
  • 类属关系运算符.
  • 作用域运算符::
  • 指向类成员的指针的运算符.*

其他的,包括箭头运算符-> 类型转换运算符() 左移右移运算符<<>> 等都可以被重载

=、[]只能重载成员函数,<<、>>只能重载为友元函数。

线程异常是否会导致进程结束

内存对齐和地址偏移

要注意区分,内存对齐并不影响其中个元素的大小

https://www.nowcoder.com/questionTerminal/8e8b73ee8f3a402ba47876f8e0b2b62d

#pragma pack指令可以用来调整编译器的默认对齐方式,将会按照n个字节进行对齐。

但是需要注意的是,按照n个字节对齐并非是每个数据都必须是n个字节。每个成员还是按照自己的方式对齐,只不过对齐时从对齐参数(通常是该类型的大小)和n中取较小的那个。

https://blog.csdn.net/czc1997/article/details/81090740

继承关系中的生成顺序

C++构造函数按下列顺序被调用:
(1)任何虚拟基类的构造函数按照它们被继承的顺序构造;
(2)任何非虚拟基类的构造函数按照它们被继承的顺序构造;
(3)任何成员对象的构造函数按照它们声明的顺序调用;
(4)类自己的构造函数。

父类对象成员 -> 父类构造 -> 子类对象成员 -> 子类构造

析构完全相反:

子类析构 -> 子类对象成员 -> 父类析构 -> 父类对象成员

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

int main()
{
E *e = new E();

cout<< endl;
cout << "code" << endl;
cout << endl;

delete e;
e = NULL;
return 0;
}

并且,各个成员对象按照声明的顺序依次执行其构造函数(如果类C中对象成员顺序改为B A,构造时的输出也会变成bacde)

bzero和memset

https://blog.csdn.net/weixin_42235488/article/details/80589583

bzero相当于全置0的memset,只有两个参数,适用于char和int

优点:便于记忆《UNIX网络编程 卷一》

但是memset还支持设置其他值

memset怎么设置char数组,怎么设置int数组。有什么区别,为什么

for循环中char

char是一个字节(8位)并且有符号,因此其表示范围为-128~127。下面代码会是死循环

1
2
3
4
for(char i = 0; i < 256; ++i) 
{
printf("%d\n", i);
}

缓存

为什么需要缓存
 cpu运行速率很快,内存的就很慢,所以就需要缓存,缓存分为一级二级三级,越往下优先级越低,成本越低,容量越大
CPU读写速率
 寄存器>一级缓存>二级缓存>三级缓存

栈是在一级缓存里面的,堆是属于二级缓存,所以栈的效率比堆的高(还有cpu支持、查找速度等其他原因)

linux查找

https://www.jb51.net/article/127577.htm

https://www.cnblogs.com/struggle-1216/p/12066969.html

locate

相当于find -name,-r正则,-i不区分大小写,-n #只列举前#个匹配项

  • 速度快
  • 模糊查找
  • 非实时(而是查找数据库)
  • 只搜索用户具备 读取和执行权限 的目录,没有权限,即使数据库有内容,也不显示,为了安全

find

实时查找 -name根据名字查,-iname忽略大小写根据名字查,-inum根据inode查,-regex “PATTERN” 正则查找,-perm [/|-]MODE 根据权限查,-type TYPE根据文件类型查

  • 速度慢
  • 精确
  • 实时查找
  • 只搜索用户具备 读取和执行权限 的目录

虚表、虚指针

虚表属于类,虚指针属于对象

https://blog.csdn.net/lalu58/article/details/53674461

每个有虚函数的类或者虚继承的子类,编译器都会为它生成一个虚表,表中的每一个元素都指向一个虚函数的地址(虚表是从属于类的)

编译器会为包含虚函数的类加上一个成员变量,是一个指向该虚函数表的指针,虚表指针是从属于对象的,也就是说,如果一个类含有虚表,那么类的每个对象都含有虚表指针

下面介绍了vfptr和vbptr

https://blog.csdn.net/weixin_43891775/article/details/112003915

vfptr:虚函数指针(指向虚函数表vftable),存放指向虚函数的指针。派生类中含有新的(非重载的)虚函数才有vfptr,如果重载基类函数,只会替换掉基类vfptr中的函数。

vbptr:虚基类指针(指向虚基类表vbtable),发生虚继承的派生类才有。

关于虚基类表中的内容:

虚基类表原理与虚函数表类似,不过虚基类表的内容有所不同。表的第一项表示本虚基类的首地址相对于虚基类表指针的偏移a(如果没有vfptr,那么vbptr在最前,就是0;否则偏移就是-4),从第二项开始表示各个基类地址相对于虚基类表指针的偏移b。可以通过该类首地址、a、b计算得到虚基类的地址

对于派生类B的vbptr(第一个vbptr):

该类首地址为0,a为-4,b为24。因此0-(-4)+24 = 28,得到类B的基类A的首地址为28

(例子来源https://blog.csdn.net/weixin_43891775/article/details/112003915)

https://blog.csdn.net/chczy1/article/details/100521615

如果虚继承的子类中没有新的虚函数,那新的虚函数表指针vfptr也将不复存在

常见网络知识面试题(黑马文档)

链路层:设备到设备,包括主机到路由器、路由到路由、路由器到主机

网络层:网络边缘的主机到主机

传输层:端口到端口

应用层:应用到应用

  1. TCP如何建立链接

    三次握手。

    第一次握手:客户端将标志位SYN置为1,随机产生一个值序列号seq=x,并将该数据包发送给服务端,客户端 进入syn_sent状态,等待服务端确认。

    第二次握手:服务端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务端将标志位SYN和 ACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给客户端以确认连接请求,服务端进入syn_rcvd状态。

    第三次握手:客户端收到确认后检查,如果正确则将标志位ACK为1,ack=y+1,并将该数据包发送给服务端,服务端进行检查如果正确则连接建立成功,客户端和服务端进入established状态,完成三次握手,随后客户端和服务端之间可以开始传输 数据了

  2. TCP如何通信

    三次握手之后采用流的方式进行数据传输。

    发送端多次执行写操作时,TCP先把这些数据放入TCP缓冲区,当TCP模块真正开始发送数据时,发送缓冲区中这些等待发送的数据可能被封装成一个或者多个TCP报文被发出。因此,应用程序执行的写操作次数和TCP模块发送的TCP报文段个数没有固定的数量关系。具体TCP的数据被客户端怎样接受,取决于长度

  3. TCP如何关闭链接

    四次挥手。

    一方发送FIN,另一方接受;然后另一方发送FIN,一方接受。最后关闭发起方等待2MSL。 (Maximum Segment Lifetime) 。

  4. 什么是滑动窗口

    滑动窗口是传输层进行流量控制的一种措施,接收方通过通告发 送方自己的窗口大小,从而控制发送方的发送速度,防止发送方发送速度过快而导致自己被淹没。

    接收方回复确认报文时,同时会指明自己的窗口(缓冲区)所剩容量。

  5. 什么是半关闭

    当TCP链接中A发送FIN请求关闭,B端回应ACK后(A端进入FIN_WAIT_2状态),B没有立即发送FIN给A时,A方处在半链接状态,此时A可以接收B发送的数据,但是A已不能再向B发送数据。

    1
    2
    3
    4
    5
    6
    7
    8
    #include <sys/socket.h>
    int shutdown(int sockfd, int how);
    sockfd: 需要关闭的socket的描述符
    how: 允许为shutdown操作选择以下几种方式:
    SHUT_RD(0): 关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。
    该套接字不再接收数据,任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。
    SHUT_WR(1): 关闭sockfd的写功能,此选项将不允许sockfd进行写操作。进程不能在对此套接字发出写操作。
    SHUT_RDWR(2): 关闭sockfd的读写功能。相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR。
  6. 局域网内两台机器如何利用TCP/IP通信

  7. internet上两台主机如何进行通信

  8. 如何在internet上识别唯一一个进程

答:通过“IP地址+端口号”来区分不同的服务

  1. 为什么说TCP是可靠的链接,UDP不可靠

    TCP:面向连接、字节流、可靠传输。可靠传输主要依靠发送应答机制;超时重传、拥塞控制;重排后交付给上层

    UDP:无连接、报文、不可靠。不提供数据确认和超时重传,需要上层协议自己定义。

  2. 路由器和交换机的区别

  3. 点到点,端到端

    https://blog.csdn.net/qq_34940959/article/details/78583993

    数据传输的可靠性是通过数据链路层和网络层的点对点和传输层的端对端保证的。端到端与点到点是针对网络中传输的两端设备间的关系而言的。

http状态码

http状态码由三位数字组成,第一个数字代表响应的类别,有五种分类:

\1. 1xx 指示信息–表示请求已接收,继续处理

\2. 2xx 成功–表示请求已被成功接收、理解、接受

\3. 3xx 重定向–要完成请求必须进行更进一步的操作

\4. 4xx 客户端错误–请求有语法错误或请求无法实现

\5. 5xx 服务器端错误–服务器未能实现合法的请求

常见的状态码如下:

  • 200 OK 客户端请求成功
  • 301 Moved Permanently 永久重定向
  • 302 暂时重定向
  • 400 Bad Request 客户端请求有语法错误,不能被服务器所理解
  • 401 Unauthorized 请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
  • 403 Forbidden 服务器收到请求,但是拒绝提供服务
  • 404 Not Found 请求资源不存在,eg:输入了错误的URL
  • 500 Internal Server Error 服务器发生不可预期的错误
  • 503 Server Unavailable 服务器当前不能处理客户端的请求,一段时间后可能恢复正常

常见错误码: 301:永久重定向 302:临时重定向 304:资源没修改,用之前缓存就行 400:客户端请求的报文有错误 403:表示服务器禁止访问资源 404:表示请求的资源在服务器上不存在或未找到

大端/小端

https://blog.csdn.net/weixin_30786813/article/details/112665201

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

int main()
{
int i = 1;
(*(char*)&i == 1) ? printf("little-endiann") : printf("Big-endiann");
return 0;
}

下面表示整数1在计算机中存储的内容:

0x00 0x00 0x00 0x01

取地址转换为char指针后再取值,判断低位地址的数据

如果是1,说明低地址存放数据低位,即为小端;否则为大端。

进程

五态模型中,进程分为新建态、终止态,运行态,就绪态,阻塞态

命令

1
2
3
4
5
6
7
8
9
10
ps  #ps -aux   
-a 显示终端上的所有进程,包括其他用户的进程
-u 显示进程的详细状态
-x 显示没有控制终端的进程
-w 显示加宽,以便显示更多的信息
-r 只显示正在运行的进程

top
kill [-signal] pid #kill命令杀死指定进程号的进程,需要配合 ps 使用。
killall #通过进程名字杀死进程

获取进程id

1
2
3
4
5
6
#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void); //获取本进程号
pid_t getppid(void); //获取父进程号
pid_t getpgid(pid_t pid); //获取进程组号,参数为0时返回本进程组号

进程相关函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pid_t fork(void);
功能:
用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进程称为父进程。
参数:

返回值:
成功:子进程中返回 0,父进程中返回子进程 ID。pid_t,为整型。
失败:返回-1
失败的两个主要原因是:
1)当前的进程数已经达到了系统规定的上限,这时 errno 的值被设置为 EAGAIN。
2)系统内存不足,这时 errno 的值被设置为 ENOMEM。


pid_t wait(int *status);
功能:
等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收该子进程的资源。
参数:
status : 进程退出时的状态信息。
返回值:
成功:已经结束子进程的进程号
失败: -1

父子进程各自的地址空间是独立的

父子进程关系

读时共享、写时拷贝

gdb调试多进程

1
2
3
#一定要在fork函数调用前设置。
set follow-fork-mode child 设置gdb在fork之后跟踪子进程。
set follow-fork-mode parent 设置跟踪父进程(默认)。

父进程运行结束,但子进程还在运行(未运行结束)的子进程就称为孤儿进程(Orphan Process)。

孤儿进程没有什么危害,会被系统0号进程(init 进程)收养。

进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。

危害:资源被占用、进程号无法再次被使用。

进程之间私有和共享的资源

  • 私有:地址空间、堆、全局变量、栈、寄存器

  • 共享:代码段,公共数据,进程目录,进程 ID

进程间通信

https://www.cnblogs.com/xuanyuan/p/15078280.html

进程间通信主要是为了数据交换

  • 管道(半双工)

    • 无名管道pipe

      流式(约定大小)、具有公共祖先的进程之间才能使用、存在内存中、缓冲区有限

    • 有名管道FIFO

      以FIFO格式存在于文件系统中,但内容在内存、有名、非亲缘关系进程可以通信、严格FIFO不支持lseek等定位、缓冲区有限

  • 信号

    比如按下ctrl+c结束进程,就是通过SIGINT信号终止程序。

    程序中可以通过使用signal函数捕捉信号。只能作为通知使用,没办法传输数据。

  • 信号量

    可以用来控制多个线程对共享资源的访问,但信号量有限

    PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。

    信号量主要用于进程或线程间的同步和互斥这两种典型情况。

    信号量数据类型为:sem_t。

  • 共享内存

    使一个磁盘文件与存储空间中的一个缓冲区相映射,于是当从缓冲区中取/存数据,就相当于读/写文件中的相应字节。这种方式效率高,尤其是用于共享大量数据时。无须复制,方便快捷

  • 套接字

    主要用于不同主机之间的网络通信,全双工。一台主机两个进程间也可以通过本地回环通信,此时不走网卡,不走物理设备,但是走虚拟设备。https://blog.csdn.net/weiyuefei/article/details/78796781

  • 消息队列

    内核中的一个消息链表,按照消息块组织,而不是管道中的二进制数据流,并通过系统调用函数来实现消息发送和接收之间的同步。还可以指定数据类型。

    缺点:信息的复制需要额外消耗 CPU 的时间,不适宜于信息量大或操作频繁的场合

线程

线程之间私有和共享的资源

  • 私有:线程栈,寄存器,程序计数器
  • 共享:堆,地址空间,全局变量,静态变量

进程和线程关系:

  • 线程是轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone
  • 从内核里看进程和线程是一样的,都有各自不同的PCB。在Linux环境下线程的本质仍是进程。
  • 进程可以蜕变成线程
  • 在linux下,线程最是小的执行单位;进程是最小的分配资源单位
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <pthread.h>
//编译时要加上参数-lpthread

pthread_t pthread_self(void);
功能:
获取线程号。

int pthread_equal(pthread_t t1, pthread_t t2);
功能:
判断线程号 t1 和 t2 是否相等。为了方便移植,尽量使用函数来比较线程 ID。
参数:
t1,t2:待判断的线程号。
返回值:
相等: 非 0
不相等:0


int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg );
功能:
创建一个线程。
参数:
thread:线程标识符地址。
attr:线程属性结构体地址,通常设置为 NULL
start_routine:线程函数的入口地址。
arg:传给线程函数的参数。
返回值:
成功:0
失败:非 0


int pthread_join(pthread_t thread, void **retval);
功能:
等待线程结束(此函数会阻塞),并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。
参数:
thread:被等待的线程号。
retval:用来存储线程退出状态的指针的地址。
返回值:
成功:0
失败:非 0


int pthread_detach(pthread_t thread);
功能:
使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,线程分离的目的是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。所以,此函数不会阻塞。
参数:
thread:线程号。
返回值:
成功:0
失败:非0

void pthread_exit(void *retval);
功能:
退出调用线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
参数:
retval:存储线程退出状态的指针。
返回值:无

gdb调试多线程

1
2
thread info
thread thr_id

线程间通信/同步

线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制

  • 互斥锁 Mutex

  • 读写锁 pthread_rwlock_init/destroy/rdlock/wrlock/unlock

  • 自旋锁

  • 条件变量 pthread_cond_init/destroy/wait/signal

    可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

  • 信号量 sem_init/destroy/wait/post

    可以用来控制多个线程对共享资源的访问,但信号量有限

    PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。

    信号量主要用于进程或线程间的同步和互斥这两种典型情况。

    信号量数据类型为:sem_t。

单线程、多线程比较

项目 单线程 多线程
编程、调试 编程简单,调试简单 编程复杂,调试复杂
可靠性 取决于这一个线程 一个线程挂掉将导致整个进程挂掉
内存、CPU

生产者消费者模型-条件变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// 节点结构
typedef struct node
{
int data;
struct node* next;
}Node;

// 永远指向链表头部的指针
Node* head = NULL;

// 线程同步 - 互斥锁
pthread_mutex_t mutex;
// 阻塞线程 - 条件变量类型的变量
pthread_cond_t cond;

// 生产者
void* producer(void* arg)
{
while (1)
{
// 创建一个链表的节点
Node* pnew = (Node*)malloc(sizeof(Node));
// 节点的初始化
pnew->data = rand() % 1000; // 0-999

// 使用互斥锁保护共享数据
pthread_mutex_lock(&mutex);
// 指针域
pnew->next = head;
head = pnew;
printf("====== produce: %lu, %d\n", pthread_self(), pnew->data);
pthread_mutex_unlock(&mutex);

// 通知阻塞的消费者线程,解除阻塞
pthread_cond_signal(&cond);

sleep(rand() % 3);
}
return NULL;
}

void* customer(void* arg)
{
while (1)
{
pthread_mutex_lock(&mutex);
// 判断链表是否为空
if (head == NULL)
{
// 线程阻塞
// 该函数会对互斥锁解锁
pthread_cond_wait(&cond, &mutex);
// 解除阻塞之后,对互斥锁做加锁操作
}
// 链表不为空 - 删掉一个节点 - 删除头结点
Node* pdel = head;
head = head->next;
printf("------ customer: %lu, %d\n", pthread_self(), pdel->data);
free(pdel);
pthread_mutex_unlock(&mutex);
}
return NULL;
}

int main(int argc, const char* argv[])
{
pthread_t p1, p2;
// init
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);

// 创建生产者线程
pthread_create(&p1, NULL, producer, NULL);
// 创建消费者线程
pthread_create(&p2, NULL, customer, NULL);

// 阻塞回收子线程
pthread_join(p1, NULL);
pthread_join(p2, NULL);

pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);

return 0;
}