C++和C的区别
C++中struct和class的区别
main 函数执行以前,还会执行什么代码
main 函数执行之前主要是初始化相关资源
写个函数在main函数执行前先运行
__attribute((constructor))void before()
{
printf("before main\n");
}
double能够表示的最大值,以及能够表示的最大的安全值
double各个位置的运用:
最大值(最大有限正数):(2 - 2^(-52)) × 2^1023 ≈ 1.7976931348623157 × 10^308
浮点数在表示整数时存在精度限制。安全整数是指能被精确表示且不会因舍入误差导致精度丢失的整数范围。
double 的最大安全整数为:2^53 - 1 = 9,007,199,254,740,991
。double 的尾数部分有 52 位,加上隐含的最高位 1,实际可表示的二进制位数为 53 位
main之后运行
全局对象的析构函数会在main函数之后执行;
可以用_onexit 注册一个函数,它会在main 之后执行;
如果需要加入一段在main退出后执行的代码,可以使用atexit()函数,注册一个函数。
语法:
#include <stdlib.h>
#include <stdio.h>
int atexit(void (*)(void));
void fn1( void ), fn2( void ), fn3( void );
void _tmain(int argc, TCHAR *argv[])
{
atexit(fn1);
atexit( fn2 );
printf( "This is executed first." );
}
void fn1()
{
printf( " This is" );
}
void fn2()
{
printf( " executed next." );
system("pause");
}
结果:
This is executed first.
This is executed next.
extern "C"
整数、指针、布尔变量、浮点数值如何与0比较大小?
if (x >= -0.00001 && x <= 0.00001)
GDB调试、条件断点
gdb exefile corefile
gdb exefile <pid>
set args val1 val2
r
运行c
继续s
单步跟踪进入n
执行一行b 代码行
设置断点bt
列出调用栈f <栈帧>
到指定栈帧p 变量
打印变量info threads
查看线程情况thread ID
切换到对应线程一个C++源文件从文本到可执行文件经历的过程?
预处理
编译产生汇编文件 .s文件
汇编产生目标文件 .o文件
链接产生可执行文件 .out .exe
include头文件的顺序以及双引号""
和尖括号<>
的区别?
#include
头文件其实在编译预处理阶段会将整个文件内容导入进来,是一个递归处理,include顺序就是导入后的替换位置<>
搜索路径是系统路径, ""
先搜索用户路径,然后搜索c++安装路径,最后系统路径<>
, 其他的用 ""
就好RTTI
字节对齐是什么?为什么要进行字节对齐?什么因素会影响字节对齐呢?可以让字节以1对齐么?
便于CPU快速访问,如果不对齐,可能需要访问多次拼接才能获取到一个值
合理设计struct结构,关闭字节对齐,可以节约空间,适用于网络传输等
代码强制转换的时候需要考虑字节对齐的隐患
跨平台通信:由于不同平台对齐方式可能不同,如此一来,同样的结构在不同的平台其大小可能不同,在无意识的情况下,互相发送的数据可能出现错乱,甚至引发严重的问题。因此,为了不同处理器之间能够正确的处理消息,我们有两种可选的处理方法。
1字节对齐: 能够保证跨平台的结构大小一致,同时还节省了空间,但不幸的是,降低了效率。
#pragma pack(1) /*1字节对齐*/
struct test
{
int a;
char b;
int c;
short d;
};
#pragma pack()/*还原默认对齐*/
自己对结构进行字节填充: 访问效率高,但并不节省空间,同时扩展性不是很好,例如,当字节对齐有变化时,需要填充的字节数可能就会发生变化。
struct test
{
int a;
char b;
char reserve[3];
int c;
short d;
char reserve1[2];
};
运行时多态和编译时多态
字节序、字节序如何转化
int的数据范围
-0x7fffffff ~ 0x7fffffff
, -2^31 ~ 2^31-1
2147483647的原码为0111 1111 1111 1111 1111 1111 1111 1111
-2147483647的原码为1111 1111 1111 1111 1111 1111 1111 1111,补码为1000 0000 0000 0000 0000 0000 0000 0001
(原码是我们正常理解的表示,计算机只能进行加法运算,所以负数用补码存储)-2147483648的补码表示为1000 0000 0000 0000 0000 0000 0000 0000,在32位没有原码。
&和&&的区别?
&
是位运算&&
是逻辑运算,且是短路运算,前边的是 false 就不会再执行后边的代码了设计模式分类
Iterator.next()
函数面向对象的特征有哪些方面
面向对象的设计原则
const修饰的变量和#define有什么区别?
#define
宏定义,在预编译的时候进行替换
const修饰变量:会占据实际内存, const修饰的变量是告知编译器该变量不可修改,编译器会根据需要进行编译优化,替换可以替换的值。该变量虽然也可以通过系列操作进行修改,但是不建议,因为有编译器优化的参与,可能出现意外的结果
const修饰函数返回值:
const修饰类成员函数: 防止成员函数修改成员变量,被mutable修饰的成员变量不受影响
#include<iostream>
using namespace std;
class Test
{
public:
Test(int _m,int _t):_cm(_m),_ct(_t){}
void Kf()const
{
++_cm; // 错误
++_ct; // 正确
}
private:
int _cm;
mutable int _ct;
};
int main(void)
{
Test t(8,7);
return 0;
}
如果同时定义了两个函数,一个带const,一个不带,会有问题吗?
static有什么作用?如何改变变量的生命周期和作用域?
全局(静态)存储区:分为 DATA 段和 BSS 段。DATA 段(全局初始化区)存放初始化的全局变量和静态变量;BSS 段(全局未初始化区)存放未初始化的全局变量和静态变量。程序运行结束时自动释放。其中BBS段在程序执行之前会被系统自动清0,所以未初始化的全局变量和静态变量在程序执行之前已经为0。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。
在修饰变量的时候,static 修饰的静态局部变量只执行初始化一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。
static 修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是 extern 外部声明也不可以。
static 修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。static 修饰的变量存放在全局数据区的静态变量区,包括全局静态变量和局部静态变量,都在全局数据区分配内存。初始化的时候自动初始化为 0。
局部静态变量在C++11后也是线程安全的, 且只在调用的时候初始化一次,此后不再初始化
类或者结构体中的 static 成员变量不会影响sizeof大小,且不会影响响字节对齐。只是用了个命名空间而已。
volitale什么作用?
thread_local
cast 转换
constexpr
c++17 支持,可以在编译阶段,根据模板参数的值编译相应的段落。
vector<int> *ptr1 = new vector<int>{1, 2, 3};
vector<int> *ptr2 = new vector<int>{4, 5, 6};
template<bool flag>
inline void fun(vector<int> *tmp)
{
if constexpr (flag)
swap(tmp, ptr1);
else
swap(tmp, ptr2);
}
vector<int> *a, *b;
fun<true>(a);
fun<false>(b);
以下四行代码的区别是什么? const char * arr = "123"; char * brr = "123"; const char crr[] = "123"; char drr[] = "123"
;
函数中 char arr[20]
, char *p = new char[20]
, char *p = new char[20]()
的区别?初始化和未初始化的情况?放在内存的那个位置?
char arr[20]
内存分配在栈上,没有初始化char *p = new char[20]
内存分配在堆,没有初始化char *p = new char[20]()
内存分配在堆,初始化为0. new 对象带括号的才会有自动初始化,否则没有初始化,对class也一样C++内存分配有哪几种方式? 画出C++内存布局图?
内存分配:全局数据区data区,栈内存分配,堆内存分配。堆内存分配可以使用c语言的方式,需要手动free. 使用new 需要手动delete.
内存布局
注意:
不存在virutal函数的类内存与c的struct区别不大,函数会变为全局函数
存在virtual函数的类,会多出虚函数表,存在虚函数的类,具体内存结构键虚函数篇章
一个类,里面有static,virtual,之类的,这个类的内存分布
C++里是怎么定义常量的?常量存放在内存的哪个位置?
C语言是怎么进行函数调用的?
C语言参数压栈顺序?
C++函数栈空间的最大值
C++如何处理返回值?
C++中拷贝赋值函数的形参能否进行值传递?
new/delete与malloc/free的区别是什么
什么是memory leak,也就是内存泄漏, 如何判断内存泄漏,C++如何处理内存泄漏?
什么时候会发生段错误
静态变量什么时候初始化
堆溢出,和栈溢出?解释下堆和栈的区别?
将数组定义在函数内部和外部有什么区别?分配的内存在哪里?
malloc和 new的区别,失败会返回什么
free()函数入参是一个void*指针,它是如何知道被指向的大小的?
malloc的原理,另外brk系统调用和mmap系统调用的作用分别是什么?
指针和引用的区别?
野指针是什么?
"引用"与多态的关系?
int main(int argc, char const* argv[]) {
Parent* p = new Child(1);
p->hello(); // 调用子类方法Parent a = Child(2); // Child会存在释放
a.hello(); // 调用的是父类方法
Child c = Child(3);
Parent& b = c;
b.hello(); // 调用子类方法
delete p;
return 0;
}
在什么时候需要使用"常引用"?
智能指针
shared_ptr
auto pointer = std::make_shared<int>(10);
初始化unique_ptr
weak_ptr
auto_ptr 废弃
智能指针有没有内存泄露的情况, 智能指针的内存泄漏如何解决
右值引用
右值引用有办法指向左值吗?
有办法,std::move: int a = 5; int &&ref_a_right = std::move(a);
; std::move移动不了什么,唯一的功能是把左值强制转化为右值, 转移所有权
std::move
cpp
class Array {
public:
Array(Array&& temp_array) {
data_ = temp_array.data_;
size_ = temp_array.size_;
// 为防止temp_array析构时delete data,提前置空其data_
temp_array.data_ = nullptr; // 原来的放弃数据所有权
}
public:
int *data_;
int size_;
};
C++是如何实现多态的
为什么析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数
知道上边几个规则后,就可以理解:
如果不是虚函数会导致父类类型指针无法清除调用到子类的析构,如下边的例子
class Parent{
public:
~Parent(){
println("release parent");
}
};
class Child: public Parent{
public:
~Child(){
println("release child");
}
};
Child* c = new Child();
delete c; // 先release child, 然后 release parent
Parent* p = new Child();
delete p; // 只 release parent
虚析构函数的情况:
class Parent{
public:
virtual ~Parent(){
println("release parent");
}
};
class Child: public Parent{
public:
virtual ~Child(){
println("release child");
}
};
Parent* p = new Child();
delete p; // 都是 release child, 然后 release parent
为什么构造函数不能是虚函数
构造函数需要用来创建对象,在实例化时需要明确知道构造函数是那个
函数指针
C++中析构函数的作用
重载和覆盖
虚函数表具体是怎样实现运行时多态的?
什么是虚函数表,为什么需要虚函数表, 存在虚函数表的对象的内存结构
虚函数表其实就是函数指针表,用于实现多态
单继承的虚函数表结构
class A {
public:
virtual void a() {
}
virtual void b() {
}
};
class B : public A {
public:
virtual void a() {
}
virtual void c() {
}
virtual void d(){
}
};
class C: public B{
public:
virtual void a(){
}
virtual void c(){
}
};
int main(int argc, char const* argv[]) {
C* p = new C();
delete p;
return 0;
}
gdb查看内存结构如下
(gdb) p *p
$1 = {<B> = {<A> = {_vptr.A = 0x4e9c80 <vtable for C+16>}, <No data fields>}, <No data fields>}
(gdb) p /a *(void**)0x4e9c80@7
$2 = {0x41cb00 <C::a()>, 0x41ca70 <A::b()>, 0x41cb10 <C::c()>, 0x41cac0 <B::d()>, 0x0,
0x4e5610 <_ZTIN10__cxxabiv115__forced_unwindE>, 0x0}
多继承下虚表的结构
class A {
public:
int a1;
virtual void a() {
}
virtual void b() {
}
};
class B {
public:
int b1;
virtual void a() {
}
virtual void c() {
}
virtual void d(){
}
};
class C: public A, public B{
public:
int a1;
int c1;
virtual void a(){
}
virtual void c(){
}
};
int main(int argc, char const* argv[]) {
C* c = new C();
A* a = c;
B* b = c;
delete c;
return 0;
}
gdb查看虚表信息
(gdb) p *c
$1 = {<A> = {_vptr.A = 0x4e9ca0 <vtable for C+16>, a1 = 0}, <B> = {_vptr.B = 0x4e9cc8 <vtable for C+56>, b1 = 0}, a1 = 0,
c1 = 0}
(gdb) p *a
$2 = {_vptr.A = 0x4e9ca0 <vtable for C+16>, a1 = 0}
(gdb) p *b
$3 = {_vptr.B = 0x4e9cc8 <vtable for C+56>, b1 = 0}
(gdb) p /a *(void**)0x4e9ca0@10
$4 = {0x41cb40 <C::a()>, 0x41cac0 <A::b()>, 0x41cb50 <C::c()>, 0xfffffffffffffff0, 0x4e55e0 <_ZTI1C>,
0x4d8100 <_ZThn16_N1C1aEv>, 0x4d8110 <_ZThn16_N1C1cEv>, 0x41cb10 <B::d()>, 0x0,
0x4e5620 <_ZTIN10__cxxabiv115__forced_unwindE>}
(gdb) p /a *(void**)0x4e9cc8@10
$5 = {0x4d8100 <_ZThn16_N1C1aEv>, 0x4d8110 <_ZThn16_N1C1cEv>, 0x41cb10 <B::d()>, 0x0,
0x4e5620 <_ZTIN10__cxxabiv115__forced_unwindE>, 0x0, 0x0, 0x4da220 <__cxa_pure_virtual>, 0x0, 0x0}
(gdb) p _ZThn16_N1C1aEv
$7 = {<text variable, no debug info>} 0x4d8100 <non-virtual thunk to C::a()>
(gdb) p _ZThn16_N1C1cEv
$8 = {<text variable, no debug info>} 0x4d8110 <non-virtual thunk to C::c()>
菱形继承
class A {
public:
int a1;
virtual void a() {
}
virtual void b() {
}
};
class B : public A {
public:
int b1;
virtual void a() {
}
virtual void c() {
}
virtual void d() {
}
};
class C : public A {
public:
int a1;
int c1;
virtual void a() {
}
virtual void c() {
}
};
class D : public B, public C {
public:
int a1;
int c1;
virtual void d() {
}
};
int main(int argc, char const* argv[]) {
D* d = new D();
// A* a = d; // 报编译错误
A* a1 = (B*)d;
A* a2 = (C*)d;
B* b = d;
C* c = d;
delete d;
return 0;
}
(gdb) p *d
$1 = {<B> = {<A> = {_vptr.A = 0x4e9d10 <vtable for D+16>, a1 = 0}, b1 = 0}, <C> = {<A> = {
_vptr.A = 0x4e9d40 <vtable for D+64>, a1 = 0}, a1 = 0, c1 = 0}, a1 = 0, c1 = 0}
(gdb) p *a1
$2 = {_vptr.A = 0x4e9d10 <vtable for D+16>, a1 = 0}
(gdb) p *a2
$3 = {_vptr.A = 0x4e9d40 <vtable for D+64>, a1 = 0}
(gdb) p *b
$4 = {<A> = {_vptr.A = 0x4e9d10 <vtable for D+16>, a1 = 0}, b1 = 0}
(gdb) p *c
$5 = {<A> = {_vptr.A = 0x4e9d40 <vtable for D+64>, a1 = 0}, a1 = 0, c1 = 0}
继承的底层原理
看上边的例子,就可以基本理解继承的原理,以及虚表的原理
介绍下内联函数
虚函数可以是内联的吗
// 此处的虚函数who(),是通过类的具体对象来调用的,编译期间就能确定了,所以它可以是内联的。
Base b;
b.who();
// 此处的虚函数是通过指针调用的,需要在运行时期间才能确定,所以不能为内联。
Base* ptr = new Derived();
菱形继承的数据存储结构
class A{
public:
int a1;
int a2;
};
class B: public A{
public:
int b1;
};
class C: public A{
public:
int c1;
};
class D: public B, public C{
public:
int d1;
};
int main(int argc, char const* argv[]) {
D d;
return 0;
}
(gdb) p d
$1 = {<B> = {<A> = {a1 = 8, a2 = 0}, b1 = 52}, <C> = {<A> = {a1 = 0, a2 = 11801856}, c1 = 0}, d1 = 16}
(gdb) p sizeof(d)
$2 = 28
相当于A类的数据会有两份
为什么GCC下中的虚函数表存在两个虚析构函数
https://www.zhihu.com/question/29257977 : 一个叫complete object destructor, 另一个叫deleting destructor,区别在于前者只执行析构函数不执行delete(),后面的在析构之后执行deleting操作。
拷贝,赋值函数
class data {
private:
char *m_str;
public:
// 构造函数
data(const char *str) {
this->m_str = new char[strlen(str)];
}
// 拷贝
data(const data &str) {
this->m_str = new char[strlen(str.m_str)];
cout << "copy" << endl;
}
// 赋值
data &operator=(const data &str) {
cout << "=" << endl;
delete m_str;
this->m_str = new char[strlen(str.m_str)];
return *this;
}
// 析构
~data() {
cout << "delete" << endl;
delete this->m_str;
}
};
data test() {
cout << "== start test ==" << endl;
data a = data("a");
data b = data("b");
cout << "==1==" << endl;
data c = a; // copy
cout << "==2==" << endl;
b = a; // =
cout << "== end test ==" << endl;
return b; // RVO优化, 不会调用拷贝构造函数, 将RVO优化关闭,可以对g++增加选项-fno-elide-constructors
}
输出
== start test==
==1==
copy
==2==
=
== end test==
delete
delete
== end main ==
delete
单核机器上写多线程程序,是否需要考虑加锁,为什么?
线程需要保存哪些上下文,SP、PC、EAX这些寄存器是干嘛用的
多线程和多进程的不同
场景
A是B的父进程,A挂了,B的父进程是谁?
进程状态转换图,动态就绪,静态就绪,动态阻塞,静态阻塞
项目中单进程模型,怎样做到的高并发
linux进程通信的方式
无名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
有名管道 (namedpipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。
信号量( semophore ) :信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
消息队列( messagequeue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
信号 ( sinal ) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
共享内存( sharedmemory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
进程通信方式,哪种最快
共享内存是最快的 IPC 方式
多线程,线程同步的几种方式
互斥锁(mutex)机制,以及互斥锁和读写锁的区别
产生死锁的原因
死锁是指两个或以上进程因竞争临界资源而造成的一种僵局,即一个进程等待一个已经被占用且永不释放的资源。 若无外力作用,这些进程都无法向前推进。 产生死锁的根本原因是系统能够提供的资源个数比要求该资源的进程数要少。
死锁发生的条件以及如何解决死锁
数据库中是否会出现死锁?
如果数据库中的确发生了死锁,应该怎么解决?
c++ 线程锁
互斥锁 std::mutex
#include <mutex>
std::mutex mtx;
mtx.lock();
mtx.unlock();
std::lock_guard<std::mutex> guard(mtx);
std::lock_guard<std::mutex> guard(mtx);
std::lock_guard其实就是简单的RAII封装
std::uniquelock的功能相比std::lockguard来说,就强大多了,是std::lockguard的功能超集, 封装了各种加锁操作,阻塞的,非阻塞的,还可以结合条件变量一起使用,基本上对锁的各种操作都封装了,当然了,功能丰富是有代价的,那就是性能和内存开销都比std::lockguard大得多,所以,需要有选择地使用。
std::uniquelock也会在析构的时候自动解锁,所以说,是std::lockguard的功能超集。
条件锁/条件变量
std::mutex mut;
std::condition_variable data_cond;
std::unique_lock<std::mutex> lk(mut);
// data_cond.wait(lk,[]{return !data_queue.empty();});
data_cond.wait(lk);
...
lk.unlock();
std::unique_lock<std::mutex> lk(mut);
data_cond.notify_one();
lk.unlock();
自旋锁
//使用std::atomic_flag的自旋锁互斥实现
class spinlock_mutex {
std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
void lock() {
while(flag.test_and_set(std::memory_order_acquire));
}
void unlock() {
flag.clear(std::memory_order_release);
}
}
读写锁
std::shared_mutex share_mtx;
std::shared_lock<std::shared_mutex> lock(share_mtx); // 读数据时这样加锁
std::unique_lock<std::shared_mutex> lock(share_mtx); // 改数据时这样加锁
递归锁 recursive_mutex (可重入锁)
std::recursivemutex 允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,std::recursivemutex 释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),可理解为 lock() 次数和 unlock() 次数相同,除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。
#include <mutex>
class X {
std::recursive_mutex m;
std::string shared;
public:
void fun1() {
std::lock_guard<std::recursive_mutex> lk(m);
shared = "fun1";
std::cout << "in fun1, shared variable is now " << shared << '\n';
}
void fun2() {
std::lock_guard<std::recursive_mutex> lk(m);
shared = "fun2";
std::cout << "in fun2, shared variable is now " << shared << '\n';
fun1(); // 递归锁在此处变得有用
std::cout << "back in fun2, shared variable is " << shared << '\n';
};
在java中锁通过 AbstractQueueSynchronizer, 提供的一套用于实现基于 FIFO 等待队列的阻塞锁和和相关同步器的同步框架实现。其基于 CAS(compare and swap 比较并交换,实现的一种乐观锁) 。当一个线程拿到锁之后,整个线程都拥有该锁,所以java都是可重入锁。
线程池的优点:省时、省资源、更好管理线程
孤儿进程、僵尸进程
进程间通讯
线程通讯
产生死锁的四个必要条件
注意:这四个条件是死锁的必然条件,只要系统发生死锁,这些条件必然成立。只要有上述条件有一条不满足,就不会发生死锁。
死锁的预防
我们可以通过破坏产生死锁的四个必要条件来预防死锁,由于资源互斥是固有特性无法改变的。
破坏“请求与保持”条件
方法一:静态分配,每个进程在开始执行时就申请他所需要的全部资源。
方法二:动态分配,每个进程在申请所需要的资源时他本身不占用系统资源。
破坏“不可剥夺”条件: 一个进程不可获得其所需要的全部资源便处于等待状态,等待期间他占用的资源将被隐式的释放重新加入到系统的资源列表中,可以被其他进程使用,而等待的进程只有重新获得自己原有的资源以及新申请的资源才可以重新启动,执行。
破坏“循环等待”条件: 采用资源有序分配的基本思想。将系统中的资源顺序进行编号,将紧缺的、稀少的资源采用较大的编号,申请资源时必须按照编号的顺序执行,一个进程只有较小编号的进程才能申请较大编号的进程
递归锁 recursive_mutex
recursive_mutex 类是同步原语,能用于保护共享数据免受从个多线程同时访问。
recursive_mutex 提供排他性递归所有权语义:
若 recursivemutex 在仍为某线程占有时被销毁,则程序行为未定义。 recursivemutex 类满足互斥 (Mutex) 和标准布局类型 (StandardLayoutType) 的所有要求。
STL有什么基本组成:六大组件
STL迭代器删除元素
STL里resize和reserve的区别
STL的allocator
vector<int, DIYAllocator<int>> list;
STL中迭代器的作用,有指针为何还要迭代器
vector和list的区别,应用,越详细越好
STL中map与unordered_map
map和set有什么区别,分别又是怎么实现的?
hash表的实现,包括STL中的哈希桶长度常数
哈希表的桶个数为什么是质数,合数有何不妥?
hash表如何rehash,以及怎么处理其中保存的资源
解决hash冲突的方法
C++ STL 的内存优化
为什么有 list 还有 stack queue 等容器的提供
[]
, 可在两端进行push、popmap vector删除数据
// vector 不能使用这种非方式
for(auto it=mymap.begin(); it!=mymap.end();) {
if (it->first == target) {
mymap.erase(it++); //here is the key
} else {
it++;
}
}
或者
// vector 可以使用这种非方式
for(auto it=mymap.begin(); it!=mymap.end();) {
if (it->first == target) {
it = mymap.erase(it);
} else {
it++;
}
}
STL中排序算法sort的实现是什么
tcp 为什么要三次握手,两次不行吗?为什么?。 为什么要四次挥手来进行关闭?详细介绍小TCP协议
TCP 如果解决粘包问题
TCP闪断
断网时有数据传输:断网时如果有数据发送,由于收不到 ACK,所以会重试,但并不会无限重试下去,达到一定的重发次数之后,如果仍然没有任何确认应答返回,就会判断为网络或者对端主机发生了异常,强制关闭连接。此时的关闭是直接关闭,而没有挥手
断网时没有数据传输: 还得看 TCP 连接的 KeepAlive 是否打开。
断电/断网后 server 重启再恢复:如果 server 重启后,client 还是不发数据,那这条连接看起来还是可用的,因为他们根本不知道对方是个什么情况,但如果此时 client 发送一点数据给 server,会发现 server 会发送一个 RST 给client,然后 client 就断开连接了
通信双方A/B非直连链路断:断开时间很短暂,小于TCP连接超时时间: TCP连接丝毫不受影响
本地物理连接断开,比如网线拔出,会导致本机IP释放,则协议栈会释放所有连接
TCP 如何保证可靠传输
详细说:
ARQ协议
自动重传请求(Automatic Repeat-reQuest,ARQ)是OSI模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认帧,它通常会重新发送。ARQ包括停止等待ARQ协议和连续ARQ协议。
停止等待ARQ协议
优点: 简单
缺点: 信道利用率低,等待时间长
1) 无差错情况:
发送方发送分组,接收方在规定时间内收到,并且回复确认.发送方再次发送。
2) 出现差错情况(超时重传):
停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重传时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为 自动重传请求 ARQ 。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。连续 ARQ 协议 可提高信道利用率。发送维持一个发送窗口,凡位于发送窗口内的分组可连续发送出去,而不需要等待对方确认。接收方一般采用累积确认,对按序到达的最后一个分组发送确认,表明到这个分组位置的所有分组都已经正确收到了。
3) 确认丢失和确认迟到
连续ARQ协议
连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。
优点: 信道利用率高,容易实现,即使确认丢失,也不必重传。
缺点: 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5条 消息,中间第三条丢失(3号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N(回退 N),表示需要退回来重传已经发送过的 N 个消息。
滑动窗口和流量控制
TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。
拥塞控制
在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
为了进行拥塞控制,TCP 发送方要维持一个 拥塞窗口(cwnd) 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。
TCP的拥塞控制采用了四种算法,即 慢开始 、 拥塞避免 、快重传 和 快恢复。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。
TCP: SYN ACK FIN RST PSH URG 详解
TCP的三次握手是怎么进行的了:发送端发送一个SYN=1,ACK=0标志的数据包给接收端,请求进行连接, 这是第一次握手;接收端收到请求并且允许连接的话,就会发送一个SYN=1,ACK=1标志的数据包给发送端,告诉它,可以通讯了,并且让发送端发送一个 确认数据包,这是第二次握手;最后,发送端发送一个SYN=0,ACK=1的数据包给接收端,告诉它连接已被确认,这就是第三次握手。之后,一个TCP连 接建立,开始通讯。
SYN:同步标志
同步序列编号(Synchronize Sequence Numbers)栏有效。该标志仅在三次握手建立TCP连接时有效。它提示TCP连接的服务端检查序列编号,该序列编号为TCP连接初始端(一般是客户 端)的初始序列编号。在这里,可以把TCP序列编号看作是一个范围从0到4,294,967,295的32位计数器。通过TCP连接交换的数据中每一个字 节都经过序列编号。在TCP报头中的序列编号栏包括了TCP分段中第一个字节的序列编号。
ACK:确认标志
确认编号(Acknowledgement Number)栏有效。大多数情况下该标志位是置位的。TCP报头内的确认编号栏内包含的确认编号(w+1,Figure-1)为下一个预期的序列编号,同时提示远端系统已经成功接收所有数据。
RST:复位标志
复位标志有效。用于复位相应的TCP连接。
URG:紧急标志
紧急(The urgent pointer) 标志有效。紧急标志置位,
PSH:推标志
该标志置位时,接收端不将该数据进行队列处理,而是尽可能快将数据转由应用处理。在处理 telnet 或 rlogin 等交互模式的连接时,该标志总是置位的。
FIN:结束标志
带有该标志置位的数据包用来结束一个TCP回话,但对应端口仍处于开放状态,准备接收后续数据。
TCP报文到达确认(ACK)机制
原文: http://blog.csdn.net/wjtxt/article/details/6606022
TCP数据包中的序列号(Sequence Number)不是以报文段来进行编号的,而是将连接生存周期内传输的所有数据当作一个字节流,序列号就是整个字节 流中每个字节的编号。一个TCP数据包中包含多个字节流的数据(即数据段),而且每个TCP数据包中的数据大小不一定相同。在建立TCP连接的三次握手 过程中,通信双方各自已确定了初始的序号x和y,TCP每次传送的报文段中的序号字段值表示所要传送本报文中的第一个字节的序号。
TCP的报文到达确认(ACK),是对接收到的数据的最高序列号的确认,并向发送端返回一个下次接收时期望的TCP数据包的序列号(Ack Number)。例如, 主机A发送的当前数据序号是400,数据长度是100,则接收端收到后会返回一个确认号是501的确认号给主机A。
TCP提供的确认机制,可以在通信过程中可以不对每一个TCP数据包发出单独的确认包(Delayed ACK机制),而是在传送数据时,顺便把确认信息传出, 这样可以大大提高网络的利用率和传输效率。同时,TCP的确认机制,也可以一次确认多个数据报,例如,接收方收到了201,301,401的数据报,则只 需要对401的数据包进行确认即可,对401的数据包的确认也意味着401之前的所有数据包都已经确认,这样也可以提高系统的效率。
若发送方在规定时间内没有收到接收方的确认信息,就要将未被确认的数据包重新发送。接收方如果收到一个有差错的报文,则丢弃此报文,并不向发送方 发送确认信息。因此,TCP报文的重传机制是由设置的超时定时器来决定的,在定时的时间内没有收到确认信息,则进行重传。这个定时的时间值的设定非 常重要,太大会使包重传的延时比较大,太小则可能没有来得及收到对方的确认包发送方就再次重传,会使网络陷入无休止的重传过程中。接收方如果收到 了重复的报文,将会丢弃重复的报文,但是必须发回确认信息,否则对方会再次发送。
TCP协议应当保证数据报按序到达接收方。如果接收方收到的数据报文没有错误,只是未按序号,这种现象如何处理呢?TCP协议本身没有规定,而是由TCP 协议的实现者自己去确定。通常有两种方法进行处理:一是对没有按序号到达的报文直接丢弃,二是将未按序号到达的数据包先放于缓冲区内,等待它前面 的序号包到达后,再将它交给应用进程。后一种方法将会提高系统的效率。例如,发送方连续发送了每个报文中100个字节的TCP数据报,其序号分别是1, 101,201,…,701。假如其它7个数据报都收到了,而201这个数据报没有收到,则接收端应当对1和101这两个数据报进行确认,并将数据递交给相关的应用 进程,301至701这5个数据报则应当放于缓冲区,等到201这个数据报到达后,然后按序将201至701这些数据报递交给相关应用进程,并对701数据报进行 确认,确保了应用进程级的TCP数据的按序到达。
ICMP (网间控制消息协议Internet Control Message Protocol)
如同名字一样, ICMP用来在主机/路由器之间传递控制信息的协议。 ICMP包可以包含诊断信息(ping, traceroute - 注意目前unix系统中的traceroute用UDP包而不是ICMP),错误信息(网络/主机/端口 不可达 network/host/port unreachable), 信息(时间戳timestamp, 地址掩码address mask request, etc.),或控制信息 (source quench, redirect, etc.) 。
网络相关知识概念了解
select、poll、epoll之间的区别
epoll原理
epoll的两种模式
epoll为什么要有EPOLLET触发模式?
epoll的优点
http 返回码知道哪些,简单介绍一下
forward 和 redirect 的区别
Http header中用于控制缓存的字段
ipv4与ipv6的区别
局域网可用IP网段:
A类:10段,后三位自由分配,也就是 10.0.0.0 - 10.255.255.255;
B类:172.16段,后两位自由分配,也就是 172.16.0.0 - 172.31.255.255;
C类:192.168段,后两位自由分配,也就是 192.168.0.0 - 192.168.255.255;
特殊的
127.0.0.1为本地回路测试地址
255.255.255.255代表广播地址
0.0.0.0代表任何网络
gcc编译相关
# 预处理
gcc -E hello.c -o hello.i
# 编译
gcc -S hello.i -o hello.s
# 生成目标代码 *.o ;有两种方式:使用 gcc 直接从源代码生成目标代码 gcc -c *.s -o *.o 以及使用汇编器从汇编代码生成目标代码 as *.s -o *.o
gcc -c hello.s -o hello.o
# 或者
as hello.s -o hello.o
# 也可以直接使用as *.s, 将执行汇编、链接过程生成可执行文件a.out, 可以像上面使用-o 选项指定输出文件的格式。
# 生成可执行文件;可以生成的可执行文件格式有: a.out/*/,当然可能还有其它格式。
gcc hello.o -o hello #生成可执行文件 hello
#生成静态库
ar -crv libhello.a hello.o
gcc -MM main.cpp #获取文件导入的文件
#结果
main.o: main.cpp main.h base.h
gdb调试
bash:
ps -aux // 获取进程信息
gdb attach <PID> // 进入进程
gdb:
info inferiors // 查看进程
info threads // 查看线程
info b // 查看断点
bt // 查看线程栈结构
thread <n> // 切换到线程
break 行号/函数名 // 设置断点