C++常考基础概念

一.常考C++基础概念

1.C++三大特性(封装、继承、多态)

封装:

隐藏类的属性和实现细节,仅仅对外提供接口,
封装性实际上是由编译器去识别关键字public、private和protected来实现的,
体现在类的成员可以有公有成员(public),私有成员(private),保护成员(protected)。
私有成员是在封装体内被隐藏的部分,只有类体内说明的函数(类的成员函数)才可以访问私有成员,
而在类体外的函数时不能访问的,公有成员是封装体与外界的一个接口,
类体外的函数可以访问公有成员,保护成员是只有该类的成员函数和该类的派生类才可以访问的。

 优点:隔离变化;便于使用;提高重用性;提高安全性
 缺点:如果封装太多,影响效率;使用者不能知道代码具体实现。

继承:

被继承的是父类(基类),继承出来的类是子类(派生类),子类拥有父类的所有的特性。
 继承方式有公有继承、私有继承,保护继承。默认是私有继承

    *公有继承中父类的公有和保护成员在子类中不变,私有的在子类中不可访问。
    *私有继承中父类的公有和保护成员在子类中变为私有,但私有的在子类中不可访问。
    *保护继承中父类的公有和保护成员在子类中变为保护,但私有的在子类中不可访问。
     c++语言允许单继承和多继承,
     
    优点:继承减少了重复的代码、继承是多态的前提、继承增加了类的耦合性;
    缺点:继承在编译时刻就定义了,无法在运行时刻改变父类继承的实现;
父类通常至少定义了子类的部分行为,父类的改变都可能影响子类的行为;
如果继承下来的子类不适合解决新问题,父类必须重写或替换,那么这种依赖关系就限制了灵活性,
最终限制了复用性。

虚继承:为了解决多重继承中的二义性问题,它维护了一张虚基类表。

多态:

 C++中有两种多态,称为 动多态(运行期多态)和静多态(编译期多态),
而静多态主要通过模板来实现,宏也是实现静多态的一种途径 。
动多态在C++中是通过虚函数实现的 ,即在基类中存在一些接口(一般为纯虚函数),
子类必须重载这些接口。这样通过使用基类的指针或者引用指向子类的对象,
就可以实现调用子类对应的函数的功能。
动多态的函数调用机制是执行期才能进行确定,所以它是动态的。
接口的多种不同实现方式即为多态。可以举个例子加深记忆,
比如电脑的USB接口,既可以插优盘,又可以插鼠标,USB接口就类似类的接口。
优点:
1.大大提高了代码的可复用性;
2.提高了了代码的可维护性,可扩充性;
缺点:
 1)易读性比较不好,调试比较困难
 2)模板只能定义在.h文件中,当工程大了之后,编译时间十分的变态

 多态的四种表现形式

https://blog.csdn.net/weixin_42678507/article/details/97111466?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164515036016780269828775%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164515036016780269828775&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-97111466.pc_search_result_cache&utm_term=%E5%A4%9A%E6%80%81%E7%9A%84%E8%A1%A8%E7%8E%B0%E5%BD%A2%E5%BC%8F&spm=1018.2226.3001.4187icon-default.png?t=M1FBhttps://blog.csdn.net/weixin_42678507/article/details/97111466?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164515036016780269828775%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164515036016780269828775&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-97111466.pc_search_result_cache&utm_term=%E5%A4%9A%E6%80%81%E7%9A%84%E8%A1%A8%E7%8E%B0%E5%BD%A2%E5%BC%8F&spm=1018.2226.3001.4187

2.数组和链表的区别

数组和链表是两种不同的数据存储方式

数组的定义

数组是一组具有相同数据类型的变量的集合,这些变量称之为集合的元素

每个元素都有一个编号,称之为下标,可以通过下标来区别并访问数组元素,数组元素的个数叫做数据的长度

链表的定义

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的

    链表的特性是在中间任意位置插入和删除元素都非常快,不需要移动其它元素

    对于单向链表而言,链表中的每一个元素都要保存一个指向下一个元素的指针

    对于双向链表而言,链表中的每个元素既要保存指向下一个元素的指针,又要保存指向上       一个元素的指针

    对于双向循环链表而言,链表中的最后一个元素保存一个指向第一个元素的指针

数组和链表的区别主要表现在以下几个方面

比较数组链表
逻辑结构1)数组在内存中连续; (2)使用数组之前,必须事先固定数组长度,不支持动态改变数组大小;(3) 数组元素增加时,有可能会数组越界;(4) 数组元素减少时,会造成内存浪费;(5)数组增删时需要移动其它元素1)链表采用动态内存分配的方式,在内存中不连续 (2)支持动态增加或者删除元素 (3)需要时可以使用malloc或者new来申请内存,不用时使用free或者delete来释放内存
内存结构数组从栈上分配内存,使用方便,但是自由度小链表从堆上分配内存,自由度大,但是要注意内存泄漏
访问效率数组在内存中顺序存储,可通过下标访问,访问效率高链表访问效率低,如果想要访问某个元素,需要从头遍历
越界问题数组的大小是固定的,所以存在访问越界的风险只要可以申请得到链表空间,链表就无越界风险

 数组和链表的使用场景

比较数组使用场景链表使用场景
空间数组的存储空间是栈上分配的,存储密度大,当要求存储的大小变化不大时,且可以事先确定大小,宜采用数组存储数据链表的存储空间是堆上动态申请的,当要求存储的长度变化较大时,且事先无法估量数据规模,宜采用链表存储
时间数组访问效率高。当线性表的操作主要是进行查找,很少插入和删除时,宜采用数组结构链表插入、删除效率高,当线性表要求频繁插入和删除时,宜采用链表结构

智能指针

我们知道除了静态内存和栈内存外,每个程序还有一个内存池,这部分内存被称为自由空间或者堆。程序用堆来存储动态分配的对象即那些在程序运行时分配的对象,当动态对象不再使用时,我们的代码必须显式的销毁它们。

在C++中,动态内存的管理是用一对运算符完成的:new和delete,new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针,delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。

动态内存管理经常会出现两种问题:一种是忘记释放内存,会造成内存泄漏;一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。

为了更加容易(更加安全)的使用动态内存,引入了智能指针的概念。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。标准库提供的两种智能指针的区别在于管理底层指针的方法不同,shared_ptr允许多个指针指向同一个对象,unique_ptr则“独占”所指向的对象。标准库还定义了一种名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象,这三种智能指针都定义在memory头文件中。

1 智能指针的作用

      智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象生命周期结束时,自动调用析构函数释放资源

2 智能指针的种类

        shared_ptr、unique_ptr、weak_ptr、auto_ptr 

四种指针详情:https://blog.csdn.net/weixin_41969690/article/details/107912842?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164499296416780274187959%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164499296416780274187959&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-5-107912842.pc_search_result_cache&utm_term=%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88&spm=1018.2226.3001.4187https://blog.csdn.net/weixin_41969690/article/details/107912842?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164499296416780274187959%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164499296416780274187959&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-5-107912842.pc_search_result_cache&utm_term=%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88&spm=1018.2226.3001.4187

3 智能指针的实现原理

智能指针的实现原理就是在一个类的内部封装了类对象的指针,然后在析构函数里对我们的类对象指针进行释放,因为类的析构是在类对象生命期结束时自动调用的,这样我们就省去了手动释放内存的操作,避免忘记手动释放导致的内存泄漏。

4.重载、重写、重定义

1)重载(overload):
指函数名相同,但是它的参数表列个数或顺序,类型不同。但是不能靠返回类型来判断。
   a 相同的范围(在同一个类中)
   b 函数名字相同、 参数不同
   c virtual关键字可有可无
   d 返回值可以不同;

2) 重写(覆盖override)是指派生类函数覆盖基类函数,特征是:
   a 不同的范围,分别位于基类和派生类中
   b 函数的名字相同、 参数相同
   c 基类函数必须有virtual关键字,不能有static
   d 返回值相同(或者协变),否则报错;
   e 重写函数的访问修饰符可以不同。尽管virtual是private的,派生类中重写改写为public, protected也是可以的

3) 重定义(隐藏redefine)是指派生类的函数屏蔽了与其同名的基类函数,特征是:
   a 不在同一个作用域(分别位于派生类与基类)
   b 函数名字相同
   c 返回值可以不同
   d 规则:

如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏;

如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏。

ps: 多态性可以分为静态多态性(方法的重载,一个类)和动态多态性(方法的覆盖,有继承关系的类之间的行为)。进而多态性可以由重载和覆盖来实现。

5.static与const区别和作用

static

    1.static局部变量将一个变量声明为函数的局部变量,那么这个局部变量在函数执行完不会释放,而是继续保留在内存中;
    2.static全局变量表示一个变量在当前文件的全局可以访问;
    3.static函数表示一个函数只能在当前文件中被访问;
    4.static类成员变量表示这个成员为全类所共有;
    5.static类成员函数表示这个函数为全类所有,且只能访问成员变量。
    6.全局变量在整个工程文件内有效;静态全局变量只在定义它的文件中有效;
    7.静态局部变量只在定义它的函数内有效,且程序只分配一次内存,函数返回时不会释放,下次调用时不会重新赋值,还保留上次结果值;局部变量在函数返回时就释放掉;
    8.全局变量和静态变量编译器会默认初始化为0;局部变量的默认值未知;
    9.局部静态变量与全局变量共享全局数据,但是静态局部变量值在定义该变量的函数内部可见。
    10.静态成员(静态成员函数)与配静态成员(成员函数)的区别在于有无this指针;静态成员是静态存储,必须进行初始化;
    11.静态成员函数访问非静态成员报错:静态成员在类加载时就已经分配内存,而此时非静态成员尚未分配内存,访问不存在的内存自然会报错;

const

    1.const常量 定义时必须初始化,以后不能修改;
    2.const形参 该形参在函数里不能被修改;
    3.const修饰类成员函数 该函数对成员变量只能进行读操作;

static关键字作用

    1.函数体内static变量的作用范围为该函数体,该变量的内存只被分配一次,因此该值在下次调用时还维持上一次的值;
    2.在模块内的static函数和变量可以被可以被模块内的函数访问,不能被模块外的函数访问;
    3.在类内的static成员变量为整个类所有,类的所有对象只有一份拷贝;
    4.在类内的static成员函数为整个类所有,这个函数不接收this指针,因此只能访问类的static成员变量;

const关键字

    1.阻止一个变量被改变;
    2.声明常量指针和指针常量;
    3.const修饰形参,表示为输入参数,在函数体内不能修改该参数的值;
    4.const修饰成员函数,表明为一个常函数,不能修改成员变量的值;
    5.类的成员函数,有时必须返回const类型的值,使得返回值不能为左值。

const修饰指针有三种情况
1. const修饰指针 --- 常量指针
//const修饰的是指针,指针指向可以改,指针指向的值不可以更改

const int * p1 = &a;
p1 = &b; //正确
//*p1 = 100; 报错

2. const修饰常量 --- 指针常量

//const修饰的是常量,指针指向不可以改,指针指向的值可以更改
int * const p2 = &a;
//p2 = &b; //错误
*p2 = 100; //正确

3. const即修饰指针,又修饰常量

//const既修饰指针又修饰常量,都不可以改
const int * const p3 = &a;
//p3 = &b; //错误
//*p3 = 100; //错误

技巧:看const右侧紧跟着的是指针还是常量, 是指针就是常指针,是常量就是指针常量

6.const与宏定义(#define)区别和作用

const 定义的是变量不是常量,只是这个变量的值不允许改变是常变量!带有类型。编译运行的时候起作用存在类型检查。

define 定义的是不带类型的常数,只进行简单的字符替换。在预编译的时候起作用,不存在类型检查。

1、两者的区别
(1) 编译器处理方式不同
#define 宏是在预处理阶段展开。
const 常量是编译运行阶段使用。

(2) 类型和安全检查不同
#define 宏没有类型,不做任何类型检查,仅仅是展开。
const 常量有具体的类型,在编译阶段会执行类型检查。

(3) 存储方式不同
#define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。(宏定义不分配内存,变量定义分配内存。)
const常量会在内存中分配(可以是堆中也可以是栈中)。

(4) const 可以节省空间,避免不必要的内存分配。 例如:
const 定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象 #define 一样给出的是立即数,所以,const 定义的常量在程序运行过程中只有一份拷贝(因为是全局的只读变量,存在静态区),而 #define 定义的常量在内存中有若干个拷贝。

(5) 提高了效率。 编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

(6) 宏替换只作替换,不做计算,不做表达式求解;宏预编译时就替换了,程序运行时,并不分配内存。计算时注意边缘效应

7.虚函数和纯虚函数区别

1.虚函数和纯虚函数可以定义在同一个类中,含有纯虚函数的类被称为抽象类,而只含有虚函数的类不能被称为抽象类。
2.虚函数可以被直接使用,也可以被子类重载以后,以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用,因为纯虚函数在基类有声明而没有定义。
3.虚函数和纯虚函数都可以在子类中被重载,以多态的形式被调用。
4.虚函数和纯虚函数通常存在于抽象基类之中,被继承的子类重载,目的是提供一个统一的接口。
5.虚函数的定义形式:virtual{};纯虚函数的定义形式:virtual  { } = 0;在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时要求前期绑定,然而虚函数却是动态绑定,而且被两者修饰的函数生命周期也不一样。
6.虚函数充分体现了面向对象思想中的继承和多态性这两大特性,在C++语言里应用极广。比如在微软的MFC类库中,你会发现很多函数都有virtual关键字,也就是说,它们都是虚函数。难怪有人甚至称虚函数是C++语言的精髓
7.定义纯虚函数就是为了让基类不可实例化,因为实例化这样的抽象数据结构本身并没有意义或者给出实现也没有意义。
纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。

虚函数在子类里面也可以不重载的;但纯虚必须在子类去实现,这就像Java的接口一样。通常我们把很多函数加上virtual,是一个好的习惯,虽然牺牲了一些性能,但是增加了面向对象的多态性,因为你很难预料到父类里面的这个函数不在子类里面不去修改它的实现

8. 指针和引用的区别

1.指针和引用的定义和性质区别:

(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来

的变量实质上是同一个东西,只不过是原变量的一个别名而已。如:

int a=1;int *p=&a;

int a=1;int &b=a;

上面定义了一个整形变量和一个指针变量p,该指针变量指向a的存储单元,即p的值是a存储单元的地址。

而下面2句定义了一个整形变量a和这个整形a的引用b,事实上a和b是同一个东西,在内存占有同一个存储单元。

(2)引用不可以为空,当被创建的时候,必须初始化,而指针可以是空值,可以在任何时候被初始化。

(3)可以有const指针,但是没有const引用;

(4)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)

(5)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;

(6)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。

(7)”sizeof引用”得到的是所指向的变量(对象)的大小,而”sizeof指针”得到的是指针本身的大小;

(8)指针和引用的自增(++)运算意义不一样;

(9)如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄漏;

9 . 结构体赋值

https://blog.csdn.net/datase/article/details/78988320icon-default.png?t=M1FBhttps://blog.csdn.net/datase/article/details/78988320

10. C和C++区别

https://blog.csdn.net/czc1997/article/details/81254971?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164506866516780255247665%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164506866516780255247665&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-81254971.pc_search_result_cache&utm_term=C%E5%92%8CC%2B%2B%E5%8C%BA%E5%88%AB&spm=1018.2226.3001.4187icon-default.png?t=M1FBhttps://blog.csdn.net/czc1997/article/details/81254971?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164506866516780255247665%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164506866516780255247665&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-81254971.pc_search_result_cache&utm_term=C%E5%92%8CC%2B%2B%E5%8C%BA%E5%88%AB&spm=1018.2226.3001.4187

11. C和C++传参方式区别

C语言不支持引用传参,如果想要改变传入参数的值,只能用传入指针的方式。

12. 深拷贝和浅拷贝区别

https://blog.csdn.net/Situo/article/details/110225143?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164506982716781685316908%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164506982716781685316908&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-7-110225143.pc_search_result_cache&utm_term=c%2B%2B%E6%B7%B1%E6%8B%B7%E8%B4%9D%E5%92%8C%E6%B5%85%E6%8B%B7%E8%B4%9D%E7%9A%84%E5%8C%BA%E5%88%AB&spm=1018.2226.3001.4187icon-default.png?t=M1FBhttps://blog.csdn.net/Situo/article/details/110225143?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164506982716781685316908%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164506982716781685316908&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-7-110225143.pc_search_result_cache&utm_term=c%2B%2B%E6%B7%B1%E6%8B%B7%E8%B4%9D%E5%92%8C%E6%B5%85%E6%8B%B7%E8%B4%9D%E7%9A%84%E5%8C%BA%E5%88%AB&spm=1018.2226.3001.418713. 避免头文件重复包含以及宏定义重定义

#ifndef LWIP_TCP_KEEPALIVE
#define LWIP_TCP_KEEPALIVE      
#endif

14. 你怎么理解虚拟类?虚拟类可以实例化一个对象吗?为什么?它的作用和其他类的区别
答案:虚拟类可以派生对象,纯虚类不可以实例化对象。因为纯虚类存在未定义的函数,只是个概念,不可真实存在。虚拟类用做多态,纯虚类做接口。
15. 内联函数怎么实现的,什么时期处理的,优缺点
答案:在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来进行替换。
优点:不会产生函数调用的开销
缺点:增加目标程序的代码量,即增加空间开销

16 .位运算(按位与、按位或、异或)

按位与运算符(&)

参加运算的两个数,按二进制位进行“与”运算。

运算规则:只有两个数的二进制同时为1,结果才为1,否则为0。(负数按补码形式参加按位与运算)

即 0 & 0= 0 ,0 & 1= 0,1 & 0= 0, 1 & 1= 1。

例:3 &5  即 00000011 & 00000101 = 00000001 ,所以 3 & 5的值为1。

按位或运算符(|)

参加运算的两个数,按二进制位进行“或”运算。

运算规则:参加运算的两个数只要两个数中的一个为1,结果就为1。

即  0 | 0= 0 ,  1 | 0= 1  , 0 | 1= 1  ,  1 | 1= 1 。

例:2 | 4 即 00000010 | 00000100 = 00000110 ,所以2 | 4的值为 6 。
异或运算符(^)

参加运算的两个数,按二进制位进行“异或”运算。

运算规则:参加运算的两个数,如果两个相应位为“异”(值不同),则该位结果为1,否则为0。

即 0 ^ 0=0  , 0 ^ 1= 1  , 1 ^ 0= 1  , 1 ^ 1= 0 。

例: 2 ^ 4 即 00000010 ^ 00000100 =00000110 ,所以 2 ^ 4 的值为6 。

18. 原码、反码、补码

原码:是最简单的机器数表示法。用最高位表示符号位,‘1’表示负号,‘0’表示正号。其他位存放该数的二进制的绝对值。

反码:正数的反码还是等于原码

负数的反码就是他的原码除符号位外,按位取反。

补码:正数的补码等于他的原码
负数的补码等于反码+1。 

19 . 堆和栈

https://blog.csdn.net/qq_45856289/article/details/106473750?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164507989016780265461539%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164507989016780265461539&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-6-106473750.pc_search_result_cache&utm_term=%E5%A0%86%E5%86%85%E5%AD%98%E5%92%8C%E6%A0%88%E5%86%85%E5%AD%98%E5%8C%BA%E5%88%ABc%2B%2B&spm=1018.2226.3001.4187icon-default.png?t=M1FBhttps://blog.csdn.net/qq_45856289/article/details/106473750?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164507989016780265461539%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164507989016780265461539&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-6-106473750.pc_search_result_cache&utm_term=%E5%A0%86%E5%86%85%E5%AD%98%E5%92%8C%E6%A0%88%E5%86%85%E5%AD%98%E5%8C%BA%E5%88%ABc%2B%2B&spm=1018.2226.3001.418720. 类和对象

面向对象(Object Oriented,OO)。

起初,“面向对象”是指在程序设计中采用封装、继承、多态等设计方法。现在,面向对象的思想已经涉及到软件开发的各个方面。如,面向对象的分析(OOA,ObjectOriented Analysis),面向对象的设计(OOD,Object Oriented Design)、以及面向对象的编程实现(OOP,Object Oriented Programming)。
对象和类解释:

1)对象:对象是人们要进行研究的任何事物,它不仅能表示具体的事物,还能表示抽象的规则、计划或事件。对象具有状态,一个对象用数据值来描述它的状态。对象还有操作,用于改变对象的状态,对象及其操作就是对象的行为。对象实现了数据和操作的结合,使数据和操作封装于对象的统一体中。

2)类:具有相同特性(数据元素)和行为(功能)的对象的抽象就是类。因此,对象的抽象是类,类的具体化就是对象,也可以说类的实例是对象,类实际上就是一种数据类型。类具有属性,它是对象的状态的抽象,用数据结构来描述类的属性。类具有操作,它是对象的行为的抽象,用操作名和实现该操作的方法来描述。
对象和类的关系:

类与对象的关系就如模具和铸件的关系,类的实力化的结果就是对象,而对对象的抽象就是类,类描述了一组有相同特性(属性)和相同行为的对象。

21 . new和malloc区别

0.属性
new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。

1.参数
使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
2.返回类型
new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。

3.分配失败
new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。

4.自定义类型
new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。
malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。

5.重载
C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许重载。

6.内存区域
new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。

22. 内核链表与双向循环链表

https://blog.csdn.net/liebao_han/article/details/53956609?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1icon-default.png?t=M1FBhttps://blog.csdn.net/liebao_han/article/details/53956609?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-123. 结构体和类的区别

1.结构体是一种值类型,而类是引用类型。值类型用于存储数据的值,引用类型用于存储对实际数据的引用。
那么结构体就是当成值来使用的,类则通过引用来对实际数据操作。

2. 结构体使用栈存储(Stack Allocation),而类使用堆存储(Heap Allocation)
栈的空间相对较小.但是存储在栈中的数据访问效率相对较高.
堆的空间相对较大.但是存储在堆中的数据的访问效率相对较低.

3.类是反映现实事物的一种抽象,而结构体的作用只是一种包含了具体不同类别数据的一种包装,结构体不具备类的继承多态特性

4.结构体赋值是 直接赋值的值. 而对象的指针 赋值的是对象的地址

5.Struct变量使用完之后就自动解除内存分配,Class实例有垃圾回收机制来保证内存的回收处理。

6.结构体的构造函数中,必须为结构体所有字段赋值,类的构造函数无此限制

首先,关于隐式构造函数.我们知道,在1个类中如果我们没有为类写任意的构造函数,那么C++编译器在编译的时候会自动的为这个类生成1个无参数的构造函数.我们将这个构造函数称之为隐式构造函数 但是一旦我们为这个类写了任意的1个构造函数的时候,这个隐式的构造函数就不会自动生成了.在结构体中,就不是这样了,在结构体中隐式的构造函数无论如何都存在。所以程序员不能手动的为结构添加1个无参数的构造函数。

7.结构体中声明的字段无法赋予初值,类可以:

如何选择结构体还是类

1. 堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些
2. 结构表示如点、矩形和颜色这样的轻量对象,例如,如果声明一个含有 1000 个点对象的数组,则将为引用每个对象分配附加的内存。在此情况下,结构的成本较低。
3. 在表现抽象和多级别的对象层次时,类是最好的选择
4. 大多数情况下该类型只是一些数据时,结构时最佳的选择

24. 结构体和联合体区别

两者最大的区别在于内存利用

一、结构体struct

    各成员各自拥有自己的内存,各自使用互不干涉,同时存在的,遵循内存对齐原则。一个struct变量的总长度等于所有成员的长度之和。

二、联合体union

    各成员共用一块内存空间,并且同时只有一个成员可以得到这块内存的使用权(对该内存的读写),各变量共用一个内存首地址。因而,联合体比结构体更节约内存。一个union变量的总长度至少能容纳最大的成员变量,而且要满足是所有成员变量类型大小的整数倍。

25. 结构体和枚举

一、结构体

结构体:很像面向对象中的对象,但是结构体没有方法只有属性,一个结构体由不同类型的元素组成,而相较于数组来说,数组只能存储相同类型的元素。结构体占用的空间等于内部各元素占用空间的和,并且元素在内存中的地址(按照元素定义的顺序)是连续的。

注意:结构体不能像面向对象中那样递归调用,自己包含自己,但是可以包含其他类型的结构体。

 二、枚举

枚举:和面向对象中一样,枚举都是用来定义一些固定取值的常量,但是C中的枚举中的值是整数,默认按照0递增,也可以在定义枚举的时候赋值,那么后面的元素的值就会以这个元素为第一个元素递增

26 . 数组和指针的区别与联系

https://blog.csdn.net/cherrydreamsover/article/details/81741459?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164508836616781683977487%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164508836616781683977487&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-81741459.pc_search_result_cache&utm_term=%E6%95%B0%E7%BB%84%E5%92%8C%E6%8C%87%E9%92%88%E7%9A%84%E5%8C%BA%E5%88%AB&spm=1018.2226.3001.4187icon-default.png?t=M1FBhttps://blog.csdn.net/cherrydreamsover/article/details/81741459?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164508836616781683977487%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164508836616781683977487&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-81741459.pc_search_result_cache&utm_term=%E6%95%B0%E7%BB%84%E5%92%8C%E6%8C%87%E9%92%88%E7%9A%84%E5%8C%BA%E5%88%AB&spm=1018.2226.3001.418728 . 函数指针&指针函数

C语言基础知识:函数指针&指针函数(定义格式、作用及用法说明)_baidu_37973494的博客-CSDN博客_函数指针定义版权声明:本文为博主原创文章,未经博主允许不得转载。https://mp.csdn.net/postedit/83150266一、函数指针的实质(还是指针变量)1、函数指针定义格式:类型名 (*函数名)(函数参数列表);int (*pfun)(int, int);2、函数指针的定义、赋值、调用void func1(void) //定义一个函数,以方便下面定义函...https://blog.csdn.net/baidu_37973494/article/details/83150266?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164514881516780255281026%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164514881516780255281026&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-2-83150266.pc_search_result_cache&utm_term=%E4%BB%80%E4%B9%88%E6%98%AF%E6%8C%87%E9%92%88%E5%87%BD%E6%95%B0&spm=1018.2226.3001.418729 . const放在函数前后的区别

1、int GetY() const;
2、const int * GetPosition();

对于1
该函数为只读函数,不允许修改其中的数据成员的值。

 对于2
修饰的是返回值,表示返回的是指针所指向值是常量

30 . goto语句

goto语句也称为无条件转移语句,其一般格式如下: goto 语句标号; 其中语句标号是按标识符规定书写的符号, 放在某一语句行的前面,标号后加冒号(:)。语句标号起标识语句的作用,与goto 语句配合使用。举个例子:

goto lable;
cout<<"This is the first c++ program!"<<"\n";

lable: cout<<"Hello World!"<<"\n"; //goto 将会直接跳到这个语句

 31 . extern关键字

1、extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。extern声明不是定义,即不分配存储空间。也就是说,在一个文件中定义了变量和函数, 在其他文件中要使用它们, 可以有两种方式:使用头文件,然后声明它们,然后其他文件去包含头文件;在其他文件中直接extern。

2、extern C作用

链接指示符extern C
    如果程序员希望调用其他程序设计语言尤其是C 写的函数,那么调用函数时必须告诉编译器使用不同的要求,例如当这样的函数被调用时函数名或参数排列的顺序可能不同,无论是C++函数调用它还是用其他语言写的函数调用它,程序员用链接指示符告诉编译器该函数是用其他的程序设计语言编写的,链接指示符有两种形式既可以是单一语句形式也可以是复合语句形式。
// 单一语句形式的链接指示符
extern "C" void exit(int);
// 复合语句形式的链接指示符
extern "C" {
int printf( const char* ... );
int scanf( const char* ... );
}
// 复合语句形式的链接指示符
extern "C" {
#include <cmath>
}
    链接指示符的第一种形式由关键字extern 后跟一个字符串常量以及一个普通的函数,声明构成虽然函数是用另外一种语言编写的但调用它仍然需要类型检查例如编译器会检查传递给函数exit()的实参的类型是否是int 或者能够隐式地转换成int 型,多个函数声明可以用花括号包含在链接指示符复合语句中,这是链接指示符的第二种形式花扩号被用作分割符表示链接指示符应用在哪些声明上在其他意义上该花括号被忽略,所以在花括号中声明的函数名对外是可见的就好像函数是在复合语句外声明的一样,例如在前面的例子中复合语句extern "C"表示函数printf()和scanf()是在C 语言中写的,函数因此这个声明的意义就如同printf()和scanf()是在extern "C"复合语句外面声明的一样,当复合语句链接指示符的括号中含有#include 时,在头文件中的函数声明都被假定是用链接指示符的程序设计语言所写的,在前面的例子中在头文件<cmath>中声明的函数都是C函数链接指示符不能出现在函数体中下列代码段将会导致编译错误。
int main()
{
// 错误: 链接指示符不能出现在函数内
extern "C" double sqrt( double );
305 第七章函数
double getValue(); //ok
double result = sqrt ( getValue() );
//...
return 0;
}
如果把链接指示符移到函数体外程序编译将无错误
extern "C" double sqrt( double );
int main()
{
double getValue(); //ok
double result = sqrt ( getValue() );
//...
return 0;
}
    但是把链接指示符放在头文件中更合适,在那里函数声明描述了函数的接口所属,如果我们希望C++函数能够为C 程序所用又该怎么办呢我们也可以使用extern "C"链接指示符来使C++函数为C 程序可用例如。
// 函数calc() 可以被C 程序调用
extern "C" double calc( double dparm ) { /* ... */ }
    如果一个函数在同一文件中不只被声明一次则链接指示符可以出现在每个声明中它,也可以只出现在函数的第一次声明中,在这种情况下第二个及以后的声明都接受第一个声明中链接指示符指定的链接规则例如
// ---- myMath.h ----
extern "C" double calc( double );
// ---- myMath.C ----
// 在Math.h 中的calc() 的声明
#include "myMath.h"
// 定义了extern "C" calc() 函数
// calc() 可以从C 程序中被调用
double calc( double dparm ) { // ...
    在本节中我们只看到为C 语言提供的链接指示extern "C",extern "C"是惟一被保证由所有C++实现都支持的,每个编译器实现都可以为其环境下常用的语言提供其他链接指示例如extern "Ada"可以用来声明是用Ada 语言写的函数,extern "FORTRAN"用来声明是用FORTRAN 语言写的函数,等等因为其他的链接指示随着具体实现的不同而不同所以建议读者查看编译器的用户指南以获得其他链接指示符的进一步信息。

总结 extern “C”
extern “C” 不但具有传统的声明外部变量的功能,还具有告知C++链接器使用C函数规范来链接的功能。 还具有告知C++编译器使用C规范来命名的功能。

32 . 动态内存管理

https://blog.csdn.net/zgege/article/details/82054076?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164516635716781683919433%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164516635716781683919433&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-2-82054076.pc_search_result_cache&utm_term=%E5%8A%A8%E6%80%81%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86&spm=1018.2226.3001.4187icon-default.png?t=M1FBhttps://blog.csdn.net/zgege/article/details/82054076?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164516635716781683919433%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164516635716781683919433&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-2-82054076.pc_search_result_cache&utm_term=%E5%8A%A8%E6%80%81%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86&spm=1018.2226.3001.418733 .数组、链表、哈希、队列、栈数据结构特点,各自优点和缺点

数组(Array):
优点:查询快,通过索引直接查找;有序添加,添加速度快,允许重复;
缺点:在中间部位添加、删除比较复杂,大小固定,只能存储一种类型的数据;
如果应用需要快速访问数据,很少插入和删除元素,就应该用数组。

链表(LinkedList):
优点:有序添加、增删改速度快,对于链表数据结构,增加和删除只要修改元素中的指针就可以了;
缺点:查询慢,如果要访问链表中一个元素,就需要从第一个元素开始查找;
如果应用需要经常插入和删除元素,就应该用链表。

栈(Stack)
优点:提供后进先出的存储方式,添加速度快,允许重复;
缺点:只能在一头操作数据,存取其他项很慢;

队列(Queue):
优点:提供先进先出的存储方式,添加速度快,允许重复;
缺点:只能在一头添加,另一头获取,存取其他项很慢;

哈希(Hash):
特点:散列表,不允许重复;
优点:如果关键字已知则存取速度极快;
缺点:如果不知道关键字则存取很慢,对存储空间使用不充分;

34 . 友元函数

  • 引入友元函数的原因

    • 类具有封装、继承、多态、信息隐藏的特性,只有类的成员函数才可以访问类的私有成员,非成员函数只能访问类的公有成员。为了使类的非成员函数访问类的成员,唯一的做法就是将成员定义为public,但这样做会破坏信息隐藏的特性。基于以上原因,引入友元函数解决。

https://blog.csdn.net/qq_26337701/article/details/53996104?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164516757916780274151927%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164516757916780274151927&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-53996104.pc_search_result_cache&utm_term=%E5%8F%8B%E5%85%83%E5%87%BD%E6%95%B0&spm=1018.2226.3001.4187icon-default.png?t=M1FBhttps://blog.csdn.net/qq_26337701/article/details/53996104?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164516757916780274151927%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164516757916780274151927&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-53996104.pc_search_result_cache&utm_term=%E5%8F%8B%E5%85%83%E5%87%BD%E6%95%B0&spm=1018.2226.3001.418735 .设计模式之单例模式、工厂模式、发布订阅模式以及观察者模式

 https://blog.csdn.net/m0_37322399/article/details/108515158?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164516870116780269827537%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164516870116780269827537&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-5-108515158.pc_search_result_cache&utm_term=%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%2C%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F%2C%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4187icon-default.png?t=M1FBhttps://blog.csdn.net/m0_37322399/article/details/108515158?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164516870116780269827537%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164516870116780269827537&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-5-108515158.pc_search_result_cache&utm_term=%E5%A6%82%E4%BD%95%E7%90%86%E8%A7%A3%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%2C%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F%2C%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.418736 .构造函数和析构函数区别

构造函数:

什么是构造函数?通俗的讲,在类中,函数名和类名相同的函数称为构造函数。它的作用是在建立一个对象时,作某些初始化的工作(例如对数据赋予初值)。C++允许同名函数,也就允许在一个类中有多个构造函数。如果一个都没有,编译器将为该类产生一个默认的构造函数。

构造函数上惟一的语法限制是它不能指定返回类型,甚至void 也不行。

不带参数的构造函数:一般形式为 类名 对象名(){函数体}

带参数的构造函数:不带参数的构造函数,只能以固定不变的值初始化对象。带参数构造函数的初始化要灵活的多,通过传递给构造函数的参数,可以赋予对象不同的初始值。一般形式为:构造函数名(形参表);

创建对象使用时:类名 对象名(实参表);

构造函数参数的初始值:构造函数的参数可以有缺省值。当定义对象时,如果不给出参数,就自动把相应的缺省参数值赋给对象。一般形式为:
构造函数名(参数=缺省值,参数=缺省值,……);
析构函数:

当一个类的对象离开作用域时,析构函数将被调用(系统自动调用)。析构函数的名字和类名一样,不过要在前面加上 ~ 。对一个类来说,只能允许一个析构函数,析构函数不能有参数,并且也没有返回值。析构函数的作用是完成一个清理工作,如释放从堆中分配的内存。

一个类中可以有多个构造函数,但析构函数只能有一个。对象被析构的顺序,与其建立时的顺序相反,即后构造的对象先析构。
1、概念不同:

析构函数:对象所在的函数已调用完毕时,系统自动执行析构函数。

构造函数:是一种特殊的方法。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载。
2、作用不同

析构函数:析构函数被调用。

构造函数:为对象成员变量赋初始值
3、目的不同:

析构函数:”清理善后” 的工作

构造函数:主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。

37 .C++模板

https://blog.csdn.net/zhaizhaizhaiaaa/article/details/104091658?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164517194916781685316952%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164517194916781685316952&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-104091658.pc_search_result_cache&utm_term=c%2B%2B%E7%B1%BB%E6%A8%A1%E6%9D%BF&spm=1018.2226.3001.4187icon-default.png?t=M1FBhttps://blog.csdn.net/zhaizhaizhaiaaa/article/details/104091658?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164517194916781685316952%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164517194916781685316952&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-104091658.pc_search_result_cache&utm_term=c%2B%2B%E7%B1%BB%E6%A8%A1%E6%9D%BF&spm=1018.2226.3001.418738 .c++ STL

https://www.cnblogs.com/shiyangxt/archive/2008/09/11/1289493.htmlicon-default.png?t=M1FBhttps://www.cnblogs.com/shiyangxt/archive/2008/09/11/1289493.html

二. 常考数据结构知识

1.如何判断环形链表有环

方法一、穷举遍历

方法一:首先从头节点开始,依次遍历单链表的每一个节点。每遍历到一个新节点,就从头节点重新遍历新节点之前的所有节点,用新节点ID和此节点之前所有节点ID依次作比较。如果发现新节点之前的所有节点当中存在相同节点ID,则说明该节点被遍历过两次,链表有环;如果之前的所有节点当中不存在相同的节点,就继续遍历下一个新节点,继续重复刚才的操作。

例如这样的链表:A->B->C->D->B->C->D, 当遍历到节点D的时候,我们需要比较的是之前的节点A、B、C,不存在相同节点。这时候要遍历的下一个新节点是B,B之前的节点A、B、C、D中恰好也存在B,因此B出现了两次,判断出链表有环。

假设从链表头节点到入环点的距离是D,链表的环长是S。那么算法的时间复杂度是0+1+2+3+…+(D+S-1) = (D+S-1)*(D+S)/2 , 可以简单地理解成 O(N*N)。而此算法没有创建额外存储空间,空间复杂度可以简单地理解成为O(1)。

方法二、快慢指针

首先创建两个指针ptr1和ptr2,同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针ptr1每次向下移动一个节点,让指针ptr2每次向下移动两个节点,然后比较两个指针指向的节点是否相同。如果相同,则判断出链表有环,如果不同,则继续下一次循环。

例如链表A->B->C->D->B->C->D,两个指针最初都指向节点A,进入第一轮循环,指针ptr1移动到了节点B,指针ptr2移动到了C。第二轮循环,指针ptr1移动到了节点C,指针ptr2移动到了节点B。第三轮循环,指针ptr1移动到了节点D,指针ptr2移动到了节点D,此时两指针指向同一节点,判断出链表有环。

此方法也可以用一个更生动的例子来形容:在一个环形跑道上,两个运动员在同一地点起跑,一个运动员速度快,一个运动员速度慢。当两人跑了一段时间,速度快的运动员必然会从速度慢的运动员身后再次追上并超过,原因很简单,因为跑道是环形的。

假设从链表头节点到入环点的距离是D,链表的环长是S。那么循环会进行S次(为什么是S次,有心的同学可以自己揣摩下),可以简单理解为O(N)。除了两个指针以外,没有使用任何额外存储空间,所以空间复杂度是O(1)。

2. 浮点数比较

 用"=="来比较两个double应该相等的类型,返回真值完全是不确定的。计算机对浮点数的进行计算的原理是只保证必要精度内正确即可。

我们在判断浮点数相等时,推荐用范围来确定,若x在某一范围内,我们就认为相等,至于范围怎么定义,要看实际情况而已了,float,和double 各有不同
    所以const float EPSINON = 0.00001;
    if((x >= - EPSINON) && (x <= EPSINON) 这样判断是可取的至于为什么取0.00001,可以自己按实际情况定义。

    也可以 abs(x) <= EPSINON

比如要判断浮点数floatA和B是否相等,我们先令float  x = A –B ;

并设const float EPSINON = 0.00001;  则

if ((x >= - EPSINON)&& (x <= EPSINON);//或者if(abs(x) <= EPSINON)

cout<<”A 与B相等<<endl;

else

cout<<”不相等”<<endl;

根据上面分析建议在系统开发过程中设计到字符转换建议采用double类型,精度设置为%.8lf即可,在比较浮点数十建议EPSINON= 0.00000001

3. 常见排序算法

冒泡排序

原理:从左到右相邻元素进行比较,每比较一轮,找出序列中最大(或最小)的一个,这个数会从右边冒出来。

第一轮比较:所有元素中,最大数从最右边冒出来

第二轮比较:所有元素中,第二大数从最右边倒数第二的位置冒出来

n个元素要比较n-1轮,除第一轮需要比较全部元素外,其他轮依次减少比较次数

时间复杂度:O(N^2)空间复杂度:O(1)

4 . 数组反转

方法一:

思路是:第一项和最后一项互换;第二项与倒数第二项互换;第三项与倒数第三项互换;以此类推,直到换到中间。

时间复杂度是O(n),因为对于有n个元素的数组a,需要交换n/2次。

空间复杂度是O(1),因为只开辟了2个int的空间。

方法二:

思路是:新建一个数组b,从后往前遍历数组a,把遍历到的元素加到数组b的后面。

时间复杂度是O(n),需要遍历n次。

空间复杂度是O(n),因为要开辟与输入的数组一样大的空间。

方法三:

这是一个神奇的思路,用的是C++11标准中,vector新的初始化方式。

直接返回一个新的vector,初始化的内容就是数组a从右到左的值。

方法四:

这个是使用C++STL库里的reverse函数,需要包含<algorithm>头文件。

5  .两个无序链表组合为一个有序链表

方法:先分别将两个链表进行排序,然后定义一个新的头节点newHead,

如果head1->data < head2->data

newHead = head1

否则newHead = head2

三. 常考Linux基础知识

1.进程间通信方式

管道

 详情:
https://blog.csdn.net/qq_52621551/article/details/120312238

FIFO(有名管道)

 详情:https://mp.csdn.net/mp_blog/creation/editor/120328113

消息队列

详情:https://blog.csdn.net/qq_52621551/article/details/120354661

共享内存

详情:https://blog.csdn.net/qq_52621551/article/details/120366732

Linux信号(signal)

详情:https://blog.csdn.net/qq_52621551/article/details/120380075

信号量

详情:https://blog.csdn.net/qq_52621551/article/details/120417478
 

 2.进程与线程的关系

典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。  

  进程是程序执行时的一个实例,是担当分配系统资源(CPU时间、内存等)的基本单位。在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。

  线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程包含了表示进程内执行环境必须的信息,其中包括进程中表示线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno常量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。在Unix和类Unix操作系统中线程也被称为轻量级进程(lightweight processes),但轻量级进程更多指的是内核线程(kernel thread),而把用户线程(user thread)称为线程。

"进程——资源分配的最小单位,线程——程序执行的最小单位"

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

从函数调用上来说,进程创建使用fork()操作;线程创建使用clone()操作。

使用线程的理由

(本部分摘自Linux多线程编程(不限Linux)

  从上面我们知道了进程与线程的区别,其实这些区别也就是我们使用线程的理由。总的来说就是:进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)。

  使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

  使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

  除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:

  • 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
  • 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
  • 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

3.多进程与多线程的区别

多线程比多进程性能高?误导!

  应该说,多线程比多进程成本低,但性能更低。

  在UNIX环境,多进程调度开销比多线程调度开销,没有显著区别,就是说,UNIX进程调度效率是很高的。内存消耗方面,二者只差全局数据区,现在内存都很便宜,服务器内存动辄若干G,根本不是问题。

  多进程是立体交通系统,虽然造价高,上坡下坡多耗点油,但是不堵车。

  多线程是平面交通系统,造价低,但红绿灯太多,老堵车。

  我们现在都开跑车,油(主频)有的是,不怕上坡下坡,就怕堵车。

  高性能交易服务器中间件,如TUXEDO,都是主张多进程的。实际测试表明,TUXEDO性能和并发效率是非常高的。TUXEDO是贝尔实验室的,与UNIX同宗,应该是对UNIX理解最为深刻的,他们的意见应该具有很大的参考意义。

多进程:

        多进程优点:

  1、每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;

  2、通过增加CPU,就可以容易扩充性能;

  3、可以尽量减少线程加锁/解锁的影响,极大提高性能,就算是线程运行的模块算法效率低也没关系;

  4、每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大。

        多进程缺点:

  1、逻辑控制复杂,需要和主程序交互;

  2、需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算 多进程调度开销比较大;

  3、最好是多进程和多线程结合,即根据实际的需要,每个CPU开启一个子进程,这个子进程开启多线程可以为若干同类型的数据进行处理。当然你也可以利用多线程+多CPU+轮询方式来解决问题……

  4、方法和手段是多样的,关键是自己看起来实现方便有能够满足要求,代价也合适。

多线程:

        多线程的优点:

  1、无需跨进程边界;

  2、程序逻辑和控制方式简单;

  3、所有线程可以直接共享内存和变量等;

  4、线程方式消耗的总资源比进程方式好。

  多线程缺点:

  1、每个线程与主程序共用地址空间,受限于2GB地址空间;

  2、线程之间的同步和加锁控制比较麻烦;

  3、一个线程的崩溃可能影响到整个程序的稳定性;

  4、到达一定的线程数程度后,即使再增加CPU也无法提高性能,例如Windows Server 2003,大约是1500个左右的线程数就快到极限了(线程堆栈设定为1M),如果设定线程堆栈为2M,还达不到1500个线程总数;

  5、线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也是一个麻烦事儿,需要消耗较多的CPU。

同步互斥

https://blog.csdn.net/goodluckwhh/article/details/8535775?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164507058016781685346680%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164507058016781685346680&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-8535775.pc_search_result_cache&utm_term=%E5%90%8C%E6%AD%A5%E5%92%8C%E4%BA%92%E6%96%A5&spm=1018.2226.3001.4187icon-default.png?t=M1FBhttps://blog.csdn.net/goodluckwhh/article/details/8535775?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164507058016781685346680%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164507058016781685346680&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-8535775.pc_search_result_cache&utm_term=%E5%90%8C%E6%AD%A5%E5%92%8C%E4%BA%92%E6%96%A5&spm=1018.2226.3001.4187线程安全

https://blog.csdn.net/weixin_43464372/article/details/108233648?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164508140916780357215410%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164508140916780357215410&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-108233648.pc_search_result_cache&utm_term=%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98&spm=1018.2226.3001.4187icon-default.png?t=M1FBhttps://blog.csdn.net/weixin_43464372/article/details/108233648?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164508140916780357215410%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164508140916780357215410&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-108233648.pc_search_result_cache&utm_term=%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98&spm=1018.2226.3001.4187经典多线程面试相关

https://blog.csdn.net/jjj19891128/article/details/24393661?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164515522816780357256944%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164515522816780357256944&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-15-24393661.pc_search_result_cache&utm_term=%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B8%B4%E7%95%8C%E5%8C%BA%E6%98%AF%E4%BB%80%E4%B9%88&spm=1018.2226.3001.4187icon-default.png?t=M1FBhttps://blog.csdn.net/jjj19891128/article/details/24393661?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164515522816780357256944%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164515522816780357256944&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-15-24393661.pc_search_result_cache&utm_term=%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B8%B4%E7%95%8C%E5%8C%BA%E6%98%AF%E4%BB%80%E4%B9%88&spm=1018.2226.3001.4187

4. Linux常用命令

终端下参考实例:

使用find查看/etc目录下面所有的.c结尾的配置文件:

find -name /etc "*.c"

使用grep查看某个文件中的关键字

grep -n "关键字" filename
-n显示关键字所在行
例如:
grep -n "configure" log.txt

递归搜索某个目录以及子目录下的所有文件

grep –nr "被查找的字符串" 文件目录
-r递归搜索
例如:
grep -nr "int" /home

查找某个特定进程

ps -aux | grep "进程名"
ps -ef | grep "进程名"
例如:
ps -aux | grep a.out
ps -ef | grep  a.out

显示内存使用情况

free -h
以GB显示

查看磁盘分区情况

sudo fdisk -l |grep Disk

 vim编辑器下

三种模式切换

命令模式下:

向上翻屏:Ctrl+B

向下翻屏:Ctrl+F

末行模式下:

搜索/查找:“:/关键词”

git命令

<1>需要远程下载代码用到gitclone命令:

例如:$gitclone-bmaster2../server.表示克隆名为master2的这个分支,如果省略-b<name>表示克隆master分支

<2>下载完代码后更新代码

$git  pull

3>查看状态

$git  status

5. 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则是不可靠信道

6. 什么是Makefile
https://blog.csdn.net/z961968549/article/details/80092282?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164508228916780265435809%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164508228916780265435809&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-3-80092282.pc_search_result_cache&utm_term=makefile%E6%98%AF%E5%B9%B2%E5%98%9B%E7%9A%84&spm=1018.2226.3001.4187icon-default.png?t=M1FBhttps://blog.csdn.net/z961968549/article/details/80092282?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164508228916780265435809%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164508228916780265435809&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-3-80092282.pc_search_result_cache&utm_term=makefile%E6%98%AF%E5%B9%B2%E5%98%9B%E7%9A%84&spm=1018.2226.3001.41877. 静态库和动态库区别

Linux库概念及相关编程_牵猫散步的鱼儿的博客-CSDN博客Linux库概念及相关编程(面试重点)1.分文件编程案例 好处: 分模块的编程思想 网络 a 超声波b 电机c a.功能责任划分 b.方便调试 c.主程序简洁...https://blog.csdn.net/qq_52621551/article/details/1191090078 .文件操作相关内容

https://mp.csdn.net/mp_blog/manage/article?spm=1011.2124.3001.5298icon-default.png?t=M1FBhttps://mp.csdn.net/mp_blog/manage/article?spm=1011.2124.3001.5298https://blog.csdn.net/qq_52621551/article/details/119355856icon-default.png?t=M1FBhttps://blog.csdn.net/qq_52621551/article/details/119355856

9. 字符设备与块设备

 Linux中I/O设备分为两类:字符设备和块设备。两种设备本身没有严格限制,但是,基于不同的功能进行了分类。
(1)字符设备:提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类设备支持按字节/字符来读写数据。举例来说,键盘、串口、调制解调器都是典型的字符设备。
(2)块设备:应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。硬盘、软盘、CD-ROM驱动器和闪存都是典型的块设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块(通常是512B)的倍数进行。与字符设备不同,块设备并不支持基于字符的寻址。
总结一下,这两种类型的设备的根本区别在于它们是否可以被随机访问。字符设备只能顺序读取,块设备可以随机读取。
10 . 什么是临界资源和临界区

1.临界资源

  临界资源是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源称作临界资源。属于临界资源的硬件有,打印机,磁带机等;软件有消息队列,变量,数组,缓冲区等。诸进程间采取互斥方式,实现对这种资源的共享。

2.临界区:

  每个进程中访问临界资源的那段代码称为临界区(criticalsection),每次只允许一个进程进入临界区,进入后,不允许其他进程进入。不论是硬件临界资源还是软件临界资源,多个进程必须互斥的对它进行访问。多个进程涉及到同一个临界资源的的临界区称为相关临界区。使用临界区时,一般不允许其运行时间过长,只要运行在临界区的线程还没有离开,其他所有进入此临界区的线程都会被挂起而进入等待状态,并在一定程度上影响程序的运行性能。

11 . Linux内核下32位主机寻址空间

4G

 12 .socket网络编程

https://blog.csdn.net/m0_37925202/article/details/80286946?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164516810016780269853007%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164516810016780269853007&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-6-80286946.pc_search_result_cache&utm_term=socket%E5%A5%97%E6%8E%A5%E5%AD%97&spm=1018.2226.3001.4187icon-default.png?t=M1FBhttps://blog.csdn.net/m0_37925202/article/details/80286946?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164516810016780269853007%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164516810016780269853007&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-6-80286946.pc_search_result_cache&utm_term=socket%E5%A5%97%E6%8E%A5%E5%AD%97&spm=1018.2226.3001.4187

  • 10
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牵猫散步的鱼儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值