当前位置:   article > 正文

【C++面试知识】知识点大汇总_c++面试知识点总结

c++面试知识点总结

1.static关键字的作用:

第一个作用是隐藏:变量或者函数被static关键字修饰后只能在本程序使用(非static的可以通过extern关键字声明后在其他文件中定义,此文件可以调用)

第二个作用是指定变量存储位置:static变量存储位置位于全局数据区

第三个作用是默认初始化为0,被static修饰的变量默认初始化为0.

第四个作用是定义类中的静态成员变量,使得其属于类但不属于某个实例,类中声明,类外定义,不分配内存

第五个作用是定义类中的静态成员函数,使得其只能调用本类的静态成员变量/函数,因为非static成员函数和变量在类成员函数中调用时,由形参中隐含一个指向当前实例对象的this指针调用,静态成员函数没有this形参。

2.五种内存区域

  • 栈区:由编译器分配释放,存放函数参数和局部变量
  • 堆区:由程序员手动通过new、delete关键字分配和释放
  • 全局/静态区:存放全局变量和静态变量,程序结束后由操作系统回收
  • 文字常量区:存放常量,不可修改,程序结束后由操作系统回收
  • 代码区:存放函数体的二进制代码

3.extern关键字

主要作用:用于标识该函数或者变量在别的文件中定义,提示编译器遇到此变量或者函数时就去别的模块寻找。可以用于链接指定。

  • extern接‘C’:如extern “C” void fun(int a, int b);告诉编译器fun这个函数名用C的方式去翻译,而不是C++规则,因为C++支持重载
  • 接其他变量或者函数:它的作用就是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块活其他模块中使用,记住它是一个声明不是定义!

4.C和C++的区别

最主要的区别是C是面向过程:分析解决问题的步骤,用函数实现步骤;C++是面向对象:把构成问题的事务分解成各个对象,建立对象,描述某个事务在解决整个问题步骤中的行为

C主要用于嵌入式领域、驱动开发等与硬件打交道,C++主要用于应用层开发,用户界面开发等与操作系统。

C++继承了C的底层操作特性

5.C++4种cast类型转换

  • const_cast:用于修改const、volatile属性,强制消除对象常量性
  • dynamic_cast:用于将派生类指针转化成基类指针(运行时处理,其他三种编译时完成)
  • static_cast:只能转化可隐式转化类型(double->int, 派生类->基类)
  • reinterpret_cast:根据内存中值转化成另一种类型,实现基本类型、指针,基类型到指针。

6.C++11特性 https://blog.csdn.net/a15920804211/article/details/90691525

      map<string, int> m{{"a", 1}, {"b", 2}, {"c", 3}};
      for (auto p : m){
           cout<<p.first<<" : "<<p.second<<endl;
      }

  • for_each:

      vector<int> vec;
      for(int i=0;i<10;++i)
      {
           vec.push_back(i);
      }
      for_each(vec.begin(),vec.end() ,[](int i)->void{ cout << i <<" "; }); // lambda表达式

  • lambda表达式:用于定义并创建匿名函数对象 ([函数对象参数] (操作符重载函数参数) mutable 或 exception 声明 -> 返回值类型 {函数体})
  • 变长参数的模板:引入tuple数据类型,N元组,可以传入多个数据类型
  • 更优雅的初始化方法:
  1. int arr[3]{1, 2, 3};
  2. vector<int> iv{1, 2, 3};
  3. map<int, string>{{1, "a"}, {2, "b"}};
  4. string str{"Hello World"};

7.指针与引用的区别

  • 引用是别名,指针是地址(本质)
  • 指针在运行时可以改变所指向的值,引用一旦与某个对象绑定后不再改变值
  • 指针变量有内存分配,引用不被分配内存,声明时必初始化
  • 引用不能指向空值,指针可以
  • 指针可是多级指针,引用只能一级

8.volatile关键字

  • 易变的:用volatile修饰的变量,每次使用时重新从内存中取得变量内容,并不会从上次使用过的寄存器中取
  • 不可优化的:告诉编译器不要对变量进行各种激进优化,甚至清除
  • 顺序性:保证volatile变量间的顺序性,编译器不会进行乱序优化

9.野指针

  • 指针变量定义未被初始化
  • 指针指向内存被释放,指针本身未被置NULL
  • 指针超过了变量作用范围

10.虚函数

多态作为面向对象的三大特性‘封装、继承、多态’之一,由虚函数机制实现,多态指相同的接口,不同的实现方式,特指父类指针调用函数时,实际调用的是指针指向的实际类型的成员函数。

虚函数由虚函数表实现

构造函数不能是虚函数:因为虚函数是由虚函数表实现,虚函数表需要一块内存,构造函数未构造没办法做到。

析构函数需要是虚函数:因为如果析构函数不是虚函数,在释放子类对象内存时,通过基类指针释放时,会调用基类对象的析构函数,达不到子类对象释放内存的目的。

11.函数指针

函数指针与指针函数:int (*f)(int a, int b)、int *f(int a, int b)

12.智能指针

智能指针的出现是为了更好地管理动态内存,防止忘记释放动态内存或者尚有指针引用内存的情况下释放内存。

  • unique_ptr: 同时只能有一个unique_ptr指向特定的对象,所以不支持普通的拷贝和赋值操作,但是可以通过release和reset将所有权转移给其他unique_ptr。
  • shared_ptr: 允许多个指针指向同一个对象,一般使用make_shared函数在动态内存中分配一个对象并初始化它,shared_ptr<int> p3 = make_shared<int>(42);https://blog.csdn.net/a15920804211/article/details/90268728
  • weak_ptr: 为了配合shared_ptr而出现的一种观测智能指针,从一个shared_ptr或者另一个weak_ptr对象中构造出来,不会增加shared_ptr引用计数,可以获取引用计数值。

13.野指针

  • 指针变量未被初始化
  • 指针所指向的内存被释放,指针本身未被置NULL
  • 指针超过了变量作用范围

14.引用与指针

  • 引用定义时必须初始化,指针不用,所以指针可以为空,引用不可以
  • 指针指向的对象可以随时改变,引用初始化后不可更改
  • 指针可以有多级,引用只能有一级
  • 引用只是变量的一个别名,与变量同享一个内存空间,指针独占内存空间

15.fork函数

https://www.cnblogs.com/hellogiser/p/fork.html

通过系统调用创建一个与原来进程完全一致的进程,两个进程可以做完全一样的事,但是如果初始参数不一样或者传入变量不同,可以做不同的事;一个进程调用fork后,系统先给新进程分配资源。例如存储数据和代码空间,然后把原来进程所有值都复制到新进程中,少量数值不同,相当于克隆。

fork仅仅被调用一次,却能够返回两次(一次是子进程返回,另一次是父进程返回),有可能有三种不同的返回值

  • 父进程中,fork返回子进程的进程ID
  • 子进程中,fork返回0
  • 若出现错误,fork返回一个负值

fork出错的原因:进程数达到系统规定上限、系统内存不足

16.静态函数和虚函数

静态函数不能够作为虚函数被调用,因为首先,静态成员函数不属于任何类对象和类实例,所以加上virtual关键字没有意义;

然后静态成员函数没有this指针,而虚函数是靠虚函数表和指向表的指针实现的,必须考this指针访问,所以静态成员函数不能是虚函数。

虚函数的调用关系:this -> vptr -> vtable ->virtual function。

17.重载、覆盖、隐藏

  • 重载:函数参数类型、个数、顺序不同的同名函数
  • 覆盖(重写):虚函数,在派生类中重新定义的函数,只有函数体内部实现不同
  • 隐藏:子类函数与父类函数名相同,但是参数列表不同;或者函数名和参数列表都相同,但是没有virtual关键字修饰,则父类函数被隐藏。

18.在main函数之前执行的函数

定义一个类,然后定义构造函数,接着构造一个全局变量为该类的一个实例,那么在运行main函数之前就会调用构造函数。

19.隐式类型转换

https://www.cnblogs.com/QG-whz/p/4472566.html

20.new、malloc

  • 申请内存所处的位置不一样,new申请的内存位于自由储存区,malloc申请的内存位于堆
  • new和delete属于关键字,需要编译器支持,malloc和free属于库函数,需要头文件支持
  • new不需要指定申请内存的大小,编译器自动算出,malloc需要显示指定内存大小
  • new分配内存成功后返回对应对象类型指针,不需要进行类型转换,malloc分配成功后返回void*,需要进行类型转换
  • new分配失败后抛出异常,malloc分配失败后返回NULL
  • new申请内存经历三个步骤:调用operator new函数为对象申请足够的内存、调用构造函数构造对象、返回对象类型指针;malloc没有调用构造函数和析构函数的过程。

21.RTTI

run time type identification运行时类型识别,dynamic_cast和typeid关键字会用到这个机制

22.迭代器失效问题

容器的insert和erase操作可能会使得迭代器失效

vector:insert操作,只有在改变了内存大小的情况下会全部迭代器失效,否则只失效之后的迭代器,erase操作被删除节点后的所有节点的迭代器失效

list:insert操作不会使得迭代器失效,erase操作有且只有被删除节点的迭代器失效

map、set:insert操作不会使得迭代器失效,erase操作有且只有被删除节点的迭代器失效

23.vector扩容和回收

  • 扩容:当插入新元素使得当前最大存储空间不够,进行2倍扩容(一个元素一个元素地扩浪费时间、倍数大了浪费空间);使用reserve、resize函数进行扩容,reserve只改变capacity值,resize改变capacity和size值。
  • 回收:先使用v.clear()清空,再使用vector<T>().swap(v),回收空间,使得size和capacity都为0.

24.set容器

有序、不重复的容器,不能直接修改set里的元素值,因为修改后不会自动排序,若要修改,必须删除后重新插入达到目的

  1. #include <iostream>
  2. #include <set> //使用set须包含此文件
  3. using namespace std;
  4. int main()
  5. {
  6. typedef set<int>::iterator IT;
  7. int a[5] = { 3,4,6,1,2 };
  8. set<int> st(a,a+5); // st里是 1 2 3 4 6
  9. pair< IT,bool> result;
  10. result = st.insert(5); // st变成 1 2 3 4 5 6
  11. if(result.second) //插入成功则输出被插入元素
  12. cout << * result.first << " inserted" << endl; //输出: 5 inserted
  13. if(st.insert(5).second)
  14. cout << * result.first << endl;
  15. else
  16. cout << * result.first << " already exists" << endl;
  17. //输出 5 already exists
  18. pair<IT,IT> bounds = st.equal_range(4);
  19. cout << * bounds.first << "," << * bounds.second ; //输出:4,5
  20. return 0;
  21. }

25.map容器

每个元素包括key和value两部分,按照key排序,所以不能直接修改key值,

  1. #include <iostream>
  2. #include <map> //使用map需要包含此头文件
  3. using namespace std;
  4. template <class T1,class T2>
  5. ostream & operator <<(ostream & o,const pair<T1,T2> & p)
  6. { //将pair对象输出为 (first,second)形式
  7. o << "(" << p.first << "," << p.second << ")";
  8. return o;
  9. }
  10. template<class T>
  11. void Print(T first,T last)
  12. {//打印区间[first,last)
  13. for( ; first != last; ++ first)
  14. cout << * first << " ";
  15. cout << endl;
  16. }
  17. typedef map<int,double,greater<int> > MYMAP; //此容器关键字是整型,元素按关键字从大到小排序
  18. int main()
  19. {
  20. MYMAP mp;
  21. mp.insert(MYMAP::value_type(15,2.7));
  22. pair<MYMAP::iterator,bool> p = mp.insert(make_pair(15,99.3));
  23. if(!p.second)
  24. cout << * (p.first) << " already exists" << endl; //会输出
  25. cout << "1) " << mp.count(15) << endl; //输出 1) 1
  26. mp.insert(make_pair(20,9.3));
  27. cout << "2) " << mp[40] << endl;//如果没有关键字为40的元素,则插入一个
  28. cout << "3) ";Print(mp.begin(),mp.end());//输出:3) (40,0)(20,9.3)(15,2.7)
  29. mp[15] = 6.28; //把关键字为15的元素值改成6.28
  30. mp[17] = 3.14; //插入关键字为17的元素,并将其值设为3.14
  31. cout << "4) ";Print(mp.begin(),mp.end());
  32. return 0;
  33. }

26.类与结构体内存大小

https://blog.csdn.net/changyang208/article/details/78668462

对齐、最后大小为最长变量所占大小的整数倍

27.define和inline(宏定义和内联函数)

宏定义的出现是因为考虑到函数调用花费空间与时间,宏定义只是在预编译阶段将代码段进行替换,不需要额外的空间和时间,但是容易产生二义性。

#define TABLE_MULTI(x) (x*x),在TABLE_MULTI(10+10)的输出结果是10+10*10+10,违背了我们的预期

#define TABLE_MULTI(x) ((x)*(x)),在TABLE_MULTI(a++)的输出结果是(a++)*(a++),即使加上了括号也不合预期输出。

所以引入inline内联函数,去代替宏定义

两者的区别在于:宏定义在预处理阶段展开,内联函数在编译阶段处理;内联函数是函数,但是没有参数压栈,但是有参数检查,宏定义没有参数检查,所以内联函数更加安全;但是内联函数的函数体不能过大,否则编译器会将内联函数看出普通函数来处理。

28.进程、线程、协程

进程是资源分配的基本单位,线程是CPU调度和分派的基本单位,协程是用户态的轻量级线程

进程与线程:

  • 一个进程可以有多个线程,共享进程的地址空间,进程有独立的地址空间,所以多线程的执行效率比较高,不需要频繁地进行上下文切换;
  • 线程不可以独立执行,必须寄存在应用程序中,由应用程序发起执行
  • 线程执行效率高,但是对资源管理和保护没有进程好

线程和协程:

  • 一个线程可以有多个协程
  • 线程是同步机制,协程是异步机制
  • 协程在进行调度切换时,会将寄存器上下文和栈放到其他地方,等到切换回来后再恢复保存的内容。

29.同步异步、并发并行

同步:在发出一个功能调用后,没有得到结果,不会返回,也就是说必须等待上一步执行完毕才会执行下一步,

异步:当一个异步过程调用时,调用不能立即得到结果,可立即执行下一步操作。

并发:几个程序在同一个CPU上执行,同时只能有一个程序执行;

并行:几个程序在几个CPU上执行,同时有多个程序在多个CPU上执行,互不干扰

30.进程间的通信方式

  • 无名管道:半双工的通信方式,数据只能单向流动,而且只能在有亲缘关系的进程间通信,常见的是父子进程间
  • 命名管道FIFO:也是半双工的通信方式,但是不限于有亲缘关系的进程
  • 信号量:是一个计数器,用于控制进程对共享资源的访问。
  • 消息队列:是一种消息的链表,存放在内核中,由消息队列标识符标识
  • 共享内存:是最快的通信方式,由多个进程共享同一块内存,涉及同步问题,需用信号量解决。
  • socket:网络进程通信方式。

31.同步和互斥

同步是一种合作关系,为了完成某种任务而建立的多个进程或者线程的协调调用、次序等待,相互告知资源占用情况

互斥是一种制约关系,一个进程或者多个进程进入临界区会进行加锁操作,此时其他进程无法进入临界区,直到解锁

32.进程、线程间的同步方式

进程间同步和互斥方式:信号量、管程(将共享变量及操作集中在一个模块)、

线程间同步方式:信号量、互斥量、事件

33.linux进程内存分布

https://blog.csdn.net/cl_linux/article/details/80328608

34.死锁

定义:多个进程因为资源竞争造成的相互等待

原因:系统资源不足、进程间的推进顺序不对

四个条件:

  • 互斥:资源不能同时被多个进程使用
  • 占用并等待:一个进程必须占用至少一个资源,并等待另一个资源,且该资源被其他进程占用
  • 非抢占:资源不能被抢占
  • 循环等待:进程{p1,p2,p3...pn},p1等待的资源被p2占用,p2等待的资源被p3占用...pn-1等待的资源被pn占用。

死锁处理:

  • 预防死锁:破坏四个必要条件中的一个或者多个即可
  • 避免死锁:在资源分配中,防止系统进入不安全状态
  • 检测死锁:
  • 解除死锁:撤销进程或者剥夺资源等。

35.OSI七层模型

  • 物理层:定义了物理设备的标准,数据单位为比特,主要设备:中继器、集线器、适配器,协议:RS-232,RS-449,FDDI,IEEE802.3,IEEE802.4,IEEE802.5
  • 数据链路层:定义如何让格式化数据进行传输,控制对物理介质的访问,数据单位为帧,主要设备:二层交换机、网桥,协议:PPP点对点协议、CSMA/CD协议
  • 网络层:选择合适的网间路由和交换结点,确保数据及时传送,数据单位为数据包,主要设备:路由器,协议:IP、ARP地址解析协议、RARP、RIP内部网关协议、OSPF内部网关协议、ICMP网际控制报文协议、BGP外部网关协议、IGMP网际组管理协议
  • 传输层:负责获取全部信息,为上层提供端到端的透明的可靠的数据传输服务,协议:TCP、UDP
  • 会话层:不参与具体的传输,提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制
  • 表示层:主要解决用户信息的语法表示
  • 应用层:为操作系统或者网络应用程序提供访问网络服务的接口,协议:超文本传输协议HTTP、TFTP简单文件传输协议、简单网络管理协议SNMP、FTP、简单邮件传输协议SMTP、DNS、Telnet远程终端协议

36.堆排序

https://blog.csdn.net/lzuacm/article/details/52853194

37.HTTP状态码

  • 200:一切正常
  • 500(Bad Request):客户端问题
  • 500(Internal Server error):服务器问题
  • 301:当客户端触发的动作引起资源URL变化
  • 404:客户端请求URL不对应人和资源
  • 409:客户端试图执行一个或者多个资源处于不一致状态操作

38.resize、reverse

vector<int> myVec;  
  
  
myVec.reserve( 100 );     // 新元素还没有构造  
  
                          // 此时不能用[]访问元素  
  
for (int i = 0; i < 100; i++ )  
{  
    myVec.push_back( i ); //新元素这时才构造  
}  
  
  
myVec.resize( 102 );      // 用元素的默认构造函数构造了两个新的元素  
  
myVec[100] = 1;           //直接操作新元素  
  
myVec[101] = 2;  

reverse是容器预留空间,调用之后,并没有创建新对象,在加入元素时,需要调用push_back或者insert;

resize是改变容器大小,调用之后,已经创建了新对象,可以直接用[]访问。

39.防止头文件重复引用

  • #pragma once,受编译器支持,保证不会引用两个相同的文件,指的是物理上的同一文件,不是内容上的,所以如果有同一份文件的多份拷贝是不能保证不被重复引用
  • #ifndef ***

       #define ***

       ...

       #endif

       受C语言标准,依赖于宏名字不能重复,所以不仅保证同一文件不会被包含多次,而且内容一样的文件也不会被包含,但是         如果不同文件的宏名一样,就会出现编译器说找不到头文件声明的情况

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/181165
推荐阅读
相关标签
  

闽ICP备14008679号