编译时的多态(静态) 主要体现在函数重载上和函数模板上。
运行时的多态(动态)主要通过虚函数来实现,体现在子类重写父类所定义的 virtual 函数。
重载、重写、重定义。
在类继承中,重定义,重载。重定义函数名相同,返回类型必须相同,参数列表可以相同,可以不同;
重载,函数名相同,返回类型和参数列表至少一个不同。只要子类出现与父类同名的函数,子类就不会继承父类同名函数(隐藏)。当该同名函数在父类声明为虚函数时(virtual),称为重写(覆盖),非虚函数时,称为重定义
当编译器发现类中有虚函数的时候,编译器会创建一张虚函数表,把虚函数的函数入口地址放到虚函数表中,并且在类中增加一个指针:vpointer,这个指针是指向对象的虚函数表。在多态调用的时候,根据vpointer指针,找到虚函数表来实现动态绑定。
基类指针可以指向派生类对象,但是无法使用只存在于派生类而不存在于基类当中的元素。(所以我们需要定义虚函数 和 纯虚函数 来实现一个接口,多种实现)
C语言中强制类型转换可以直接通过在变量前面添加类型来实现。但是在C++里这样是不太合适的。强制转换非常容易被过度使用,成为C++程序犯错的错误根源。
为什么C++ 不延续 C语言的强转而去使用下面这些关键字实现强制类型转换?
1.编译期检查,使用关键字能更好的定位于错误,早发现早解决
2.安全性,代码中强制类型转换容易出错误,其中 dynamic_cast 关键字能够运行检查错误
3.后期的维护,修改相对好维护一下。
所以在C++中,提供了四个与类型转换相关的关键字:static_cast、const_cast、reinterpret_cast、dynamic_cast。
1.static_cast
int i;
float f;
f=(float)i;//c
f=static_cast<float>(i);//c++
2.const_cast
const int *fun(int x,int y){}
int *ptr=const_cast<int *>(fun(2.3))
3.dynamic_cast; 该类型操作符用于运行时检查该转换是否符合类型安全。但是只在多态类型是合法,且该类至少有一个虚拟方法。在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。如:
class C
{
//…C没有虚拟函数
};
class T{
//…
}
int main()
{
dynamic_cast<T*> (new C);//错误
}
此时如改为以下则是合法的:
class C
{
public:
virtual void m() {};// C现在是 多态
}
4.reinterpret_cast:interpret是解释的意思,reinterpret即为重新解释,此标识符的意思即为数据的二进制形式重新解释,但是不改变其值。如:
int i;
char *ptr="hello freind!";
i=reinterpret_cast<int>(ptr);
这个转换方式很少使用。
操作符重载一般在类里面实现,运算符重载模型为 friend 友元函数。如下:
#include
using namespace std;
class A
{
public:
A& operator = (const A& rhs)
{
data = rhs.data;
return *this;
}
friend A operator + (const A& lhs,const A& rhs);//运算符重载原型
private:
double data;
};
A operator + (const A& lhs,const A& rhs)
{
A res(0);
res.data = lhs.data + rhs.data;
return res;
}
内存对齐的底层原理是因为内存的物理存储读取方式,为了更好的有效率的读取内存数据,内存对齐是必要的。
内存对齐的三大原则主要针对于结构体或联合体成员;
默认对齐值:
Linux 默认#pragma pack(4)
window 默认#pragma pack(8)
原则A:struct或者union的成员,第一个成员在偏移0的位置,之后的每个成员的起始位置必须是当前成员大小的整数倍;
原则B:如果结构体A含有结构体成员B,那么B的起始位置必须是B中最大元素大小整数倍地址;
原则C:结构体的总大小,必须是内部最大成员的整数倍;
typedef struct bb
{
int id; //[0]....[3]
double weight; //[8].....[15] 原则1
float height; //[16]..[19],总长要为8的整数倍,补齐[20]...[23] 原则3
}BB;
typedef struct aa
{
char name[2]; //[0],[1]
int id; //[4]...[7] 原则1
double score; //[8]....[15]
short grade; //[16],[17]
BB b; //[24]......[47] 原则2
}AA;
int main()
{
AA a;
cout<<sizeof(a)<<" "<<sizeof(BB)<<endl;
return 0;
模板是泛型编程的基础,模板是创建类或函数的公式,
template <typename T>//typename 非限定类型名
T find_max(T data[],int len)
{
T max = data[0];
for (int i = 0; i < len; i++)
{
if (data[i] > max)
max = data[i];
}
return max;
}
指针常量:int * const p; p 地址作为一个常量不能被改变,但是 *p 可以被改变
常量指针:const int *p ( 等价于 int const *p); *p 作为一个常量不能被改变,但是 p 可以改变
指针常量、内容常量: const int * const p; 指针本身和指向的内容都是常量,不能改变。
指针和引用:
1.指针可以为空、引用必须初始化
2.指针可以改变指向、引用不可以
程序编译的时候,将指针和引用添加到符号表时,指针放入的是“指针变量的地址”,而引用放入的是“所引用的对象的地址”,所以 指针可以改变,而引用不可以更改。
虚函数:当编译器发现基类当中存在虚函数,那么就会为每个虚函数创建一个虚函数表,以及一个指向虚函数的虚指针 *vptr ,
纯虚函数:只有定义没有实现。基类中实现的方法是在纯虚函数后面加上0。即:virtual void fun() = 0;
纯虚函数在基类中没有定义,他会将定义交给他的派生类去做,仅仅是多了一个接口的作用。
在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()=
0;),则编译器要求在派生类中必须予以重载以实现多态性。
同时含有纯虚拟函数的类称为抽象类,它不能生成对象。
这样就很好地解决了上述两个问题。
析构函数为什么要虚函数?
当一个类没有子类的时候,它没必要将析构函数设置为虚函数,虚函数会有虚函数表来维护虚函数,会造成空间的浪费。当有继承发生时,为了将整个类都析构完全,还是设为虚函数有一定的好处。
当基类指针操作派生类对象时,如果基类的析构函数为虚函数,那么在子类在析构的时候就会调用基类的虚函数,从而将整个类一起析构。
内联函数 和普通函数的区别主要在于调用时的区别。
普通函数调用时,系统会跳到该函数的入口地址,然后是函数体,最后会返回到函数调用的地方,每一次的调用都是这么一个过程。所以普通函数的调用会让系统跳来跳去,这是一笔很大的开销。
内联函数解决了这个问题,当内联函数被调用的时候,内联函数会自动展开,系统不会发生跳转,这样就节省了开销。
最后问题: 普通函数虽然会造成跳转,但是至始至终它只有一次拷贝,它的地址不会发生改变。
内联函数得到拷贝次数和他被调用的次数一样多,所以需要正确的使用,同时内联函数补鞥呢包含复杂的控制语句,如果内联函数的函数体过大,编译器会自动的吧这个内联函数变成普通函数。
内联函数和宏定义的区别:
1.内联函数只在编译的时候展开,宏在预编译的时候展开。
2.内联函数可以进行类型安全、语句是否正确的编译功能,宏不具有这样的功能。
3.实质上,宏定义只是简单地文本替换,是在预编译阶段完成的,而内联函数就是用来取消这种宏定义的。
1.如何才能使用内联函数?
a. 在函数声明前加上关键字 inline
b. 在函数定义前加上关键字 inline
typrdef 常用来定义一个标识符即关键字的别名,特别是结构体。如果没有这个关键字,那么结构体不能有其他的别名
const:
1. 修饰的量为常量,不能被改变。同时需要初始化常量。
2. 修饰指针,这就涉及到指针常量,常量指针。如上:
3. 修饰类成员函数,在该函数体类不能改变数据成员,返回值也不能被改变。
4. 修饰类。当一个对象被声明或使用const关键字创建时,它的数据成员在对象的生命周期中永远不会被改变。
extern "C"的主要作用是能够让 C++ 代码调用其他 C语言的代码,编译器遇到 extern “C” 之后,编译器会按照C 语言来编译当前代码。
由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。
extern放在变量或者函数之前,表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
1.extern 变量;全局变量作用域的拓宽。你在 .c 文件中声明了一个全局变量,这个全局变量需要被其他的文件的使用,只需要将该全局变量 放在 .h头文件中,并使用extern 声明。
2.extern 函数; 如果函数的声明中带有关键字extern,仅仅是暗示这个函数可能在别的源文件里定义,没有其它作用。此外:extern 修饰函数还可以取代 include “xxxx.h” 头文件,但是不推荐使用。
static 作用范围是内部连接的关系,和extern有点相反.它和对象本身是分开存储的,extern也是分开存储的,但是extern可以被其他的对象用extern 引用,而static 不可以,只允许对象本身用它.
具体差别首先,static与extern是一对“水火不容”的家伙,也就是说extern和static不能同时修饰一个变量;其次,static修饰的全局变量声明与定义同时进行,也就是说当你在头文件中使用static声明了全局变量后,它也同时被定义了;最后,static修饰全局变量的作用域只能是本身的编译单元,也就是说它的“全局”只对本编译单元有效,其他编译单元则看不到它
C++ 的异常处理机制:
使用的是 try throw catch 等语句。主要是try 模块中定义表达式,并 throw 出可能会产生的异常,紧接着的catch语句会根据自己的异常类型捕获异常并进行相应的处理。
try
{
throw 表达式;
}
catch(参数类型)
{
异常处理;
}
static_cast
dynamic_cast
const_cast
reinterpret_cast
1.向上转型:向上转型是隐式转换,将子类看作父类对象;
解释:平行四边形是四边形的一种,那么就可以将平行四边形对象看作是一个四边形对象。
2.向下转型: 将抽象类转换为较具体的类,这种转型一般会出现问题,不能说四边形是平行四边形的一种,所有的鸟都是鸽子等。
基类指针可以指向派生类,而派生类指针不可以指向基类。
基类中的成员在派生类中全都有,但是反之就不一定了,所以容易出问题。
基类指针操作派生类,虚函数的作用就显现出来,为了实现一个接口,多种实现,定义虚函数,或者纯虚函数接口来让派生类具体实现,就可以达到多态性的目的。
虚函数会生成一个虚函数指针,他是一个指针占据4个字节,它指向的是虚函数数组。每一个含有虚函数的类都会创建一张虚函数类,子类继承自父类,它不仅继承了父类的虚函数表,它还会自己创建一张虚函数表。
动态分配类对象:使用 new 运算符 来创建一个类的对象,在堆上分配内存
静态定义类对象:就是 A a;由 编译器创建类对象,在栈上分配内存。
如何实现只能动态分配类对象,不能定义类对象?
把 类的构造函数 和析构函数 设为protect 属性,类对象不能访问,但是派生类可以继承,可以访问。
同时创建 create 和 destory 函数,用于创建类对象。
*(create函数设为static,原因是,创建对象的时候A p=A::create().只有静态成员函数才能有类名直接访问)
class A
{
protected:
A() {}
~A() {}
public:
static A* create()
{
return new A();
}
void destroy()
{
delete this;
}
};
static:
1 修饰局部变量. 延长生命周期,函数结束时不被回收依然保留最后一次的值,下一次调用直接使用。
2.修饰全局变量,定义该全局变量的作用域,只能在该原文件中被调用。
3.修饰类成员变量,类中共享,只能在类外初始化。静态变量先于对象存在,只能在类外初始化。
4.修饰类成员函数,该静态函数只属于类中,而不属于任何对象,所以没有 this 指针,也就不能访问非静态成员或者非静态函数。
vector : 数组,连续的存储结构,下标查找,查找效率及高,但是不利于增加删除节点。删除节点之后,迭代器失效。
set :集合 和map 很像,只是存储结构不一样,map 是键值对, set 只有一个值,且是排好序的。红黑树底层机构,树结构,增删效率高,但是查找效率也很高,相对于数组来说还是稍微逊色一点。
黑根黑叶路同黑,红黑二色红生黑。
非严格平衡二叉树,拥有查找搜索效率综合性最强的特性。
红黑树靠变色和左旋右旋来保持红黑树的五条性质。相对于AVL树的严格平衡来说开销小很多,但是效率并不会非常低,所以折中的选择,STL map、set 、epoll等结构都采用了红黑树底层存储结构。
1.修饰变量,定义为只读变量,不能被改变。即使是重新赋同样的值也不可以。
2.修饰指针变量,就有指针常量,常量指针的区别了。
3.修饰成员函数,函数定义为只读函数,函数内部的数据成员不能被改变。
静态成员函数:只属于类,不属于任何对象,所以不存在this 指针。只能访问静态成员或者静态函数
静态成员变量:只能在类中声明,类外定义。静态数据成员存储于静态村存储区,先于对象存在,所以需要类外定义,分配内存。
C++中经常为了避免重复的编码而需要使用到模板,这是C++泛型编程不可或缺的利器。然而通常又有一些特殊的情况,不能直接使用泛型模板展开实现,这时就需要针对某个特殊的类型或者是某一类特殊的类型,而实现一个特例模板————即模板特化。
防止类构造函数的隐式转换。
#include
using namespace std;
class A
{
public:
//explicit A (int i = 5)
A(int i = 5)
{
m_a = i;
}
private:
int m_a;
};
int main()
{
A s;
//我们会发现,我们没有重载'='运算符,但是去可以把内置的int类型赋值给了对象A.
s = 10;
//实际上,10被隐式转换成了下面的形式,所以才能这样.
//s = A temp(10);
system("pause");
return 0;
}
返回 char * 类型是为了支持链式表达
char* my_strcpy(char *deststr, const char *str)//char*类型是为了方便后续的函数调用 链式连接
{
assert(str != NULL);
assert(deststr != NULL);//判断输入参数的合法性,如果条件为假,即终止程序。
char *res = deststr;
int length = strlen(str);
for (size_t i = 0; i <= length; i++)
{
deststr[i] = str[i];
}
//while ((*deststr++ = *str++)!='\0')
return res;
}
数组越界、指针越界,内存不足、内存过载、
c/c++ 语言本身属于不安全的语言,它不会对数组,类型等进行检查,另一方面它又直接在内存上操作,造成问题的概率远大于其他语言。
new 是 C++ 里申请内存的运算符,malloc 是 C 语言中的库函数,原型是 void *malloc(size_t size); new 对 malloc 进行了封装,它带有构造函数和析构函数,能够自动的对new 对象进行初始化操作,而 malloc 只能进行内存的申请,而不能进行对象的初始化。
为了后期的维护,重构。查找错误也好找,规范一些。
C++ 的异常处理主要有两种方式:
1.最常见的就是 return 方式,直接将错误返回调用函数的主程序,直接报错
2.异常抛出机制:
采用的格式是:try catch 机制。在 try 语句中 throw 抛出异常错误,然后由紧接着的 catch 语句通过相应的错误类型捕获相应的错误,然后相应的处理。
try
{
包含可能抛出异常的语句;
throw &变量;
}
catch(类型名 [形参名]) // 捕获特定类型的异常
{
//处理1
}
catch(类型名 [形参名]) // 捕获特定类型的异常
{
//处理2
}
catch(...) // 三个点则表示捕获所有类型的异常
{
}
STL 中 分为关联容器 和 非关联容器。
其中关联容器有: string、vector、deque等,删除当前迭代器会使得后面元素的迭代器失效。
void VectorTest()
{
vector<int> vec;
for (int i = 0; i < 5; i++)
{
vec.push_back(i);
}
vector<int>::iterator it;
cout << sizeof(it) << endl;
for (it = vec.begin(); it != vec.end(); )
{
if (*it==3)
{
it = vec.erase(it++);//erase 操作可以返回下一个有效的迭代器。
}
// it++;
}
for (it = vec.begin(); it != vec.end(); it++)
cout << *it << " ";
cout << endl;
}
非关联容器有:list、map、set、multimap、multiset,删除当前的iterator,仅仅会使当前的iterator失效,只要在erase时,递增当前的iterator即可。这是因为map之类的容器,使用了红黑树来实现,插入,删除一个结点不会对其他结点造成影响。
常量成员、引用类型、对象成员
1.常量成员,const 修饰的成员变量,因为常量只能初始化不能赋值,所以要必须初始化列表里面。
2.引用类型,引用必须在定义的时候初始化,并且不能重新赋值。
3.没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数来初始化。
1.private成员只能被本类成员(类内)和友元访问,不能被派生类访问;
2.protected成员可以被派生类访问。
3.public 成员可以被类外对象访问。
有public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。
1.public继承:基类public成员,protected成员,private成员的访问属性在派生类中分别变成:public, protected, private
2.protected继承:基类public成员,protected成员,private成员的访问属性在派生类中分别变成:protected,
protected, private
3.private继承:基类public成员,protected成员,private成员的访问属性在派生类中分别变成:private, private, private
智能指针:
unique_ptr:转移语义,没有拷贝语义,不能共享。
shared_ptr: 可共享,采用内部计数,每增加一个对象,计数+1,反之-1,所以线程安全。
weak_ptr:弱指针,和shared_ptr 一起使用,协助者的角色,像旁观者那样观测资源的使用情况。
C++ 采用new/delete 关键字或者叫运算符来进行内存的动态分配,
C 语言 采用的是malloc/free来分配内存;
new/delete 可以看做事带有构造函数和析构函数的 malloc / free。
new 一个对象是经过初始化的,会自动计算分配内存的大小,delete 也是自动析构。
malloc 返回类型是一个 void* 类型,它没有经过初始化,申请内存的大小也需要自己设定,同时后期进行初始化操作。
注意的一点:在对指针进行操作的时候, delete /free 只是释放了内存空间,没有删除指针,一定需要放置野指针的问题。
智能指针:
unique_ptr:转移语义,没有拷贝语义,不能共享。
shared_ptr: 可共享,采用内部计数,每增加一个对象,计数+1,反之-1,所以线程安全。
weak_ptr:弱指针,和shared_ptr 一起使用,协助者的角色,像旁观者那样观测资源的使用情况。
C/S 模型就是常用的 Client / Sever模型。它的整个流程如下:
服务端启动后,创建一个 Socket 套接字并初始化,并调用bind函数绑定端口号,开始listen 监听。 客户端同样准备好Socket 套接字后就采用connect 函数来申请与服务端连接,此时就会有I/O模式的选择,通常会有同步阻塞、同步非阻塞以及I/O多路复用等模式,假如采用I/O多路复用模式,select 模式。当客户端与服务端通过TCP三次握手建立好链接之后,select 就将客户端的请求发送到服务端,服务端就调用accept 函数接受请求,然后服务器采用多进程或者多线程技术处理具体请求,同时主进程或者主线程返回重新处理新的I/O 请求,最后客户端请求结束连接,经过TCP四次分手结束连接。回收子进程或子线程。
select / epoll 都是属于I/O多路复用。相比之下,epoll 的使用场景更加广泛。区别如下:
select:主要由一个事件链表构成,Linux 内核采用的是无差别轮询的方式去访问事件数组,每一次的处理都需要对事件数组进行询问,这很浪费资源,同时基于限制,select 最大能存储 1024 个事件同时。
epoll:主要由一个红黑树 和事件双向链表构成,通过红黑树来存储事件,并通过回调函数机制来将具体发生的事件注册到事件链表之中,然后内核就遍历该事件链表处理具体事件,那么这样就很好的避免了大范围的轮询造成的开销过大的问题。至于 回调函数机制,则是通过struct epoll_event 结构体中嵌套的 *call_back()函数来进行事件响应的回调。
struct epoll_event
{
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
} __attribute__ ((__packed__));
typedef union epoll_data
{
void *ptr;//指向的是 回调函数 结构体
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
修饰局部变量:延长生命周期
修饰全局变量:定义作用域
修饰类成员变量:类里共享,类外初始化,优先于对象存在
修饰类成员函数:只属于类,不属于对象,,没有 this 指针。
将最后一个节点的next指针指向头结点
#pragma once 与 #ifndef 都是防止头文件被重复调用,区别:
#ifndef 受到 C/C++语言标椎的支持,不受编译器的限制。
#pragma once 由编译器提供保证,一些老的编译器版本可能不支持这种方法。
一般来说,大部分程序员还是会选择 #ifndef ,牺牲一点编译性能从而保障代码健壮性。
构造函数、析构函数、拷贝构造函数、以及拷贝赋值函数。
通常的原则是:
①对于凡是包含动态分配成员或包含指针成员的类都应该提供拷贝构造函数;
②在提供拷贝构造函数的同时,还应该考虑重载"="赋值操作符号。
冒泡、插入、选择、希尔、归并、快排、堆、计数、桶排等等,
算法时间复杂度:如上:
string/list/vector/map/set/
for(list<int>::iterator it = list.begin(); it != list.end(); ++it)
for(map<int,int>::iterator it = mp.begin(); it != mp.end(); ++it)
{mp->first; mp->second; }
单例模式、工厂模式。
class CSingleton
{
private:
CSingleton(){};
static CSingleton *p;
public:
static CSingleton *GetInsatnce()
{
if(p == NULL)
{
p = new CSingleton;
}
}
}
CSingleton *CSingleton::p = NULL;//类外初始化//懒汉式
//CSingleton *CSingleton::p = new CSingleton;//饿汉式
HTTP 协议 、 FTP协议、DNS 协议、SMTP(简单邮件传输协议)
dif 、
top、
lsof -p -pid
makefile 是用在Linux 操作系统之下的联合编译工具,通过提前写入的脚本语言,将大型项目的各个文件,以及所需要的资源库,链接库等文件,按照所想要的方式生成可执行或者其他编译文件,大大提高了开发的效率。
一、从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如static 静态变量、全局变量。
二、在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
三、从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
多线程调试,用在线程互斥调试中,将 schuduler-locking 设置为线程同时运行状态,观察主线程的状态。
静态存储区创建
栈有系统自动申请以及释放
堆由程序员申请以及释放
Linux 才有虚拟内存和物理内存映射的机制来管理内存,
每个进程都有自己独立的4G虚拟内存,每一次的访问你都会将虚拟内存地址翻译为实际物理内存,而所有的进程都共享同一块物理内存,通过映射的机制来实现。
问题:
计算机明明没有那么多内存(n个进程的话就需要n*4G)内存
建立一个进程,就要把磁盘上的程序文件拷贝到进程对应的内存中去,对于一个程序对应的多个进程这种情况,浪费内存!
内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
1.对于C和C++这种没有垃圾回收机制的语言来讲,我们主要关注两种类型的内存泄漏:
(1)堆内存泄漏 (Heap leak)。对内存指的是程序运行中根据需要分配通过malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.
(2)系统资源泄露(Resource Leak)。主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。
2.使用C/C++语言开发的软件在运行时,出现内存泄漏。可以使用以下两种方式,进行检查排除:
⑴ 使用工具软件BoundsChecker,BoundsChecker是一个运行时错误检测工具,它主要定位程序运行时期发生的各种错误。
⑵ 调试运行DEBUG版程序,运用以下技术:CRT(C run-time libraries)、运行时函数调用堆栈、内存泄漏时提示的内存分配序号(集成开发环境OUTPUT窗口),综合分析内存泄漏的原因,排除内存泄漏。
3.解决内存泄漏最有效的办法就是使用智能指针(Smart Pointer)。
使用智能指针就不用担心这个问题了,因为智能指针可以自动删除分配的内存。智能指针和普通指针类似,只是不需要手动释放指针,而是通过智能指针自己管理内存的释放,这样就不用担心内存泄漏的问题了。
【计算机基础】对称加密和非对称加密。http请求中,get和post区别。http请求中,服务器识别不同客户端的方式(通过cookie)。
【数据结构】数据结构是面试中问到最多的,也是最能体现一个人的代码能力的,这一部分被问到的问题比较多,我这里大概总结一下。数组、链表、栈、队列、二叉树、二叉查找树等,STL里面的 list、queue、stack、vector、map等,dfs、wfs,dp,冒泡排序、选择排序、归并排序、快速排序、推排序、希尔排序等排序方式。题目如:判断链表是否有环。判断两个链表是否有重叠。用队列模拟栈。用栈模拟队列。二叉树的前中后序遍历、层次遍历(重在具体问题具体应用)。queue、stack、vector等实现的函数、各自区别,尤其vector的reverse函数和capacity函数。深搜、广搜和动态规划是属于图里面的,是属于难度比较大的。深搜类似二叉树里面的先序遍历,广搜类似层次遍历。
【实现函数库中的函数】实现函数库中的一些函数,如:strcpy、memcpy、memmove,move等。
【网络】tcp协议的建立、断开详细过程。OSI网络7层协议。tcp、udp区别。ARP协议。DNS。上层协议用到tcp或udp的有哪些。
【数据库】我的数据库基本是属于不会的,面试官问我,我都是说不会数据库(⊙o⊙)…,有被问到过数据库的简单增删改查操作。
【操作系统】进程和线程的区别,进程间(线程间)通信方式、调度方式、状态转移等。
)。
Linux系统的掌握情况,这个就看过私房菜,现在都忘的差不多 了,就记得一些最简单的命令了(如一些常用的命令:cat、tac、top、chmod、ls、mv、cp、rm等)。