2021最新C++面试题(附答案)

2021最新C++面试题(附答案)今天分享给大家的是比较全面的 C C 面试题 也都是 C 版本升级之后 重新整理归纳的最新答案 会让 C 面试者少走很多不必要的弯路 同时每个 C 面试题都尽量做到了详尽的面试解析文档 以确保每个阶段的读者都能看得懂 同时这部分 C 面试文档也是可以免费的提供给有需要的同学们学习的 一 计算机基础更多阿里 百度 华为 美团 腾讯 头条 C 面试题可以关注微信公众号 C 和 C 加加 回复 面试题 即可获取相关 C 面试题 1 C C 内存有哪几种类型 C 中 内存分为 5 个区 堆 malloc

2021最新C++面试题(附答案)

今天分享给大家的是比较全面的C/C++面试题,也都是C++版本升级之后,重新整理归纳的最新答案,会让C++面试者少走很多不必要的弯路。同时每个C++面试题都尽量做到了详尽的面试解析文档,以确保每个阶段的读者都能看得懂,同时这部分C++面试文档也是可以免费的提供给有需要的同学们学习的!

博主已将大量C++相关面试题汇总整理成了一个PDF版的C++面试宝典,关注微 信 公 众 号 “C和C加加” 回复“面试题”即可获取!

一、计算机基础

1.C/C++内存有哪几种类型?

2.堆和栈的区别?

3.堆和自由存储区的区别?

4.程序编译的过程?

#include <stdio.h> int main() { printf("happy new year!\n"); return 0; } 

其编译过程如下:

2021最新C++面试题(附答案)

5.计算机内部如何存储负数和浮点数?

2021最新C++面试题(附答案)

而双精度的存储方式如下图:

2021最新C++面试题(附答案)

6.函数调用的过程?

如下结构的代码

int main(void) { ... d = fun(a, b, c); cout<<d<<endl; ... return 0; } 

调用fun()的过程大致如下:

7. 左值和右值

不是很严谨的来说,左值指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式),右值指的则是只能出现在等号右边的变量(或表达式)。举例来说我们定义的变量 a 就是一个左值,而malloc返回的就是一个右值。或者左值就是在程序中能够寻值的东西,右值就是一个具体的真实的值或者对象,没法取到它的地址的东西(不完全准确),因此没法对右值进行赋值,但是右值并非是不可修改的,比如自己定义的class, 可以通过它的成员函数来修改右值。

归纳一下就是:

8. 什么是内存泄漏?面对内存泄漏和指针越界,你有哪些方法?你通常采用哪些方法来避免和减少这类错误?

用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元即为内存泄露。

二、C/C++的比较面试题

1. C和C++的区别?

2. int fun() 和 int fun(void)的区别?

这里考察的是c 中的默认类型机制。

3. const 有什么用途

主要有几点:

class Screen { public: const char cha; //const成员变量 char get() const; //const成员函数 }; const Screen screen; //只读对象 

4. 在C中用const 能定义真正意义上的常量吗?C++中的const呢?

int main(void) { const int a = 8; int *pa = (int *)&a; *pa = 4; printf("*pa = %d, a = %d", *pa, a); return 0; } 

另外值得一说的是,由于c++中const常量的值在编译期就已经决定,下面的做法是OK的,但是c中是编译通不过的。

int main(void) { const int a = 8; const int b = 2; int array[a+b] = {0}; return 0; } 

5. 宏和内联(inline)函数的比较?

6. C++中有了malloc / free , 为什么还需要 new / delete?

int *p = new int(1); 

特别的,在C++中,如下的代码,用new创建一个对象(new 会触发构造函数, delete会触发析构函数),但是malloc仅仅申请了一个空间,所以在C++中引入new和delete来支持面向对象。

#include <cstdlib> class Test { ... } Test* pn = new Test; Test* pm = (Test*)malloc(sizeof(Test)); 

7. C和C++中的强制类型转换?

C中是直接在变量或者表达式前面加上(小括号括起来的)目标类型来进行转换,一招走天下,操作简单,但是由于太过直接,缺少检查,因此容易发生编译检查不到错误,而人工检查又及其难以发现的情况;而C++中引入了下面四种转换:

1)static_cast

  • 用于基本类型间的转换
  • 不能用于基本类型指针间的转换
  • 用于有继承关系类对象间的转换和类指针间的转换

2)dynamic_cast

  • 用于有继承关系的类指针间的转换
  • 用于有交叉关系的类指针间的转换
  • 具有类型检查的功能
  • 需要虚函数的支持

3)reinterpret_cast

  • 用于指针间的类型转换
  • 用于整数和指针间的类型转换

4)const_cast

  • 用于去掉变量的const属性
  • 转换的目标类型必须是指针或者引用

在C++中,普通类型可以通过类型转换构造函数转换为类类型,那么类可以转换为普通类型吗?答案是肯定的。但是在工程应用中一般不用类型转换函数,因为无法抑制隐式的调用类型转换函数(类型转换构造函数可以通过explicit来抑制其被隐式的调用),而隐式调用经常是bug的来源。实际工程中替代的方式是定义一个普通函数,通过显式的调用来达到类型转换的目的。

class test{ int m_value; ... public: operator int() //类型转换函数 { return m_value; } int toInt() //显示调用普通函数来实现类型转换 { return m_value } }; int main() { ... test a(5); int i = a; ... return 0; } 

8. static 有什么用途

  • 静态(局部/全局)变量
  • 静态函数
  • 类的静态数据成员
  • 类的静态成员函数

9. 类的静态成员变量和静态成员函数各有哪些特性?

静态成员变量

  • 静态成员变量需要在类内声明(加static),在类外初始化(不能加static),如下例所示;
  • 静态成员变量在类外单独分配存储空间,位于全局数据区,因此静态成员变量的生命周期不依赖于类的某个对象,而是所有类的对象共享静态成员变量;
  • 可以通过对象名直接访问公有静态成员变量;
  • 可以通过类名直接调用公有静态成员变量,即不需要通过对象,这一点是普通成员变量所不具备的。
class example{ public: static int m_int; //static成员变量 }; int example::m_int = 0; //没有static cout<<example::m_int; //可以直接通过类名调用静态成员变量 

静态成员函数

  •  静态成员函数是类所共享的;
  • 静态成员函数可以访问静态成员变量,但是不能直接访问普通成员变量(需要通过对象来访问);需要注意的是普通成员函数既可以访问普通成员变量,也可以访问静态成员变量;
  • 可以通过对象名直接访问公有静态成员函数;
  • 可以通过类名直接调用公有静态成员函数,即不需要通过对象,这一点是普通成员函数所不具备的。
class example{ private: static int m_int_s; //static成员变量 int m_int; static int getI() //静态成员函数在普通成员函数前加static即可 { return m_int_s; //如果返回m_int则报错,但是可以return d.m_int是合法的 } }; cout<<example::getI(); //可以直接通过类名调用静态成员变量 

10. 在C++程序中调用被C编译器编译后的函数,为什么要加extern“C”?

C++语言支持函数重载,C语言不支持函数重载,函数被C++编译器编译后在库中的名字与C语言的不同,假设某个函数原型为:

void foo(int x, int y); 

该函数被C编译器编译后在库中的名字为 _foo, 而C++编译器则会产生像: _foo_int_int 之类的名字。为了解决此类名字匹配的问题,C++提供了C链接交换指定符号 extern “C”。

11. 头文件中的 ifndef/define/endif 是干什么用的? 该用法和 program once 的区别?

相同点:

它们的作用是防止头文件被重复包含。

不同点

  • ifndef 由语言本身提供支持,但是 program once 一般由编译器提供支持,也就是说,有可能出现编译器不支持的情况(主要是比较老的编译器)。
  • 通常运行速度上 ifndef 一般慢于 program once,特别是在大型项目上, 区别会比较明显,所以越来越多的编译器开始支持 program once。
  • ifndef 作用于某一段被包含(define 和 endif 之间)的代码, 而 program once 则是针对包含该语句的文件, 这也是为什么 program once 速度更快的原因。
  • 如果用 ifndef 包含某一段宏定义,当这个宏名字出现“撞车”时,可能会出现这个宏在程序中提示宏未定义的情况(在编写大型程序时特性需要注意,因为有很多程序员在同时写代码)。相反由于program once 针对整个文件, 因此它不存在宏名字“撞车”的情况, 但是如果某个头文件被多次拷贝,program once 无法保证不被多次包含,因为program once 是从物理上判断是不是同一个头文件,而不是从内容上。

12. 当i是一个整数的时候++i和i++那个更快一点?i++和++i的区别是什么?

答:理论上++i更快,实际与编译器优化有关,通常几乎无差别。

//i++实现代码为: int operator++(int) { int temp = *this; ++*this; return temp; }//返回一个int型的对象本身 // ++i实现代码为: int& operator++() { *this += 1; return *this; }//返回一个int型的对象引用 

i++和++i的考点比较多,简单来说,就是i++返回的是i的值,而++i返回的是i+1的值。也就是++i是一个确定的值,是一个可修改的左值,如下使用:

cout << ++(++(++i)) << endl; cout << ++ ++i << endl; 
int main() { int i = 1; printf("%d,%d\n", ++i, ++i); //3,3 printf("%d,%d\n", ++i, i++); //5,3 printf("%d,%d\n", i++, i++); //6,5 printf("%d,%d\n", i++, ++i); //8,9 system("pause"); return 0; } 

三、数组、指针和C++特性

1.指针数组和数组指针的区别

数组指针,是指向数组的指针,而指针数组则是指该数组的元素均为指针。

数组指针,是指向数组的指针,其本质为指针,形式如下。如 int (*p)[10],p即为指向数组的指针,()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。数组指针是指向数组首元素的地址的指针,其本质为指针,可以看成是二级指针。

类型名 (*数组标识符)[数组长度]

指针数组,在C语言和C++中,数组元素全为指针的数组称为指针数组,其中一维指针数组的定义形式如下。指针数组中每一个元素均为指针,其本质为数组。如 int *p[n], []优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。

类型名 *数组标识符[数组长度]

2.左值引用与右值引用

左值引用就是我们通常所说的引用,如下所示。左值引用通常可以看作是变量的别名。

type-id & cast-expression // demo int a = 10 int &b = a int &c = 10 // 错误,无所对一个立即数做引用 const int &d = 10 // 正确, 常引用引用常数量是ok的,其等价于 const int temp = 10; const int &d = temp 

右值引用是 C++11 新增的特性,其形式如下所示。右值引用用来绑定到右值,绑定到右值以后本来会被销毁的右值的生存期会延长至与绑定到它的右值引用的生存期。

type-id && cast-expression // demo int &&var = 10; // ok int a = 10 int &&b = a // 错误, a 为左值 int &&c = var // 错误,var 为左值 int &&d = move(a) // ok, 通过move得到左值的右值引用 

在汇编层面右值引用做的事情和常引用是相同的,即产生临时量来存储常量。但是,唯一 一点的区别是,右值引用可以进行读写操作,而常引用只能进行读操作。

3.右值引用的意义

  • 右值引用支持移动语义的实现,可以减少拷贝,提升程序的执行效率。

    下面的代码时没有采用右值引用时的实现。

class Stack { public: // 构造 Stack(int size = 1000) :msize(size), mtop(0) { cout << "Stack(int)" << endl; mpstack = new int[size]; } // 析构 ~Stack() { cout << "~Stack()" << endl; delete[]mpstack; mpstack = nullptr; } // 拷贝构造 Stack(const Stack &src) :msize(src.msize), mtop(src.mtop) { cout << "Stack(const Stack&)" << endl; mpstack = new int[src.msize]; for (int i = 0; i < mtop; ++i) { mpstack[i] = src.mpstack[i]; } } // 赋值重载 Stack& operator=(const Stack &src) { cout << "operator=" << endl; if (this == &src) return *this; delete[]mpstack; msize = src.msize; mtop = src.mtop; mpstack = new int[src.msize]; for (int i = 0; i < mtop; ++i) { mpstack[i] = src.mpstack[i]; } return *this; } int getSize() { return msize; } private: int *mpstack; int mtop; int msize; }; Stack GetStack(Stack &stack) { Stack tmp(stack.getSize()); return tmp; } int main() { Stack s; s = GetStack(s); return 0; } 

运行结果如下

Stack(int) // 构造s Stack(int) // 构造tmp Stack(const Stack&) // tmp拷贝构造main函数栈帧上的临时对象 ~Stack() // tmp析构 operator= // 临时对象赋值给s ~Stack() // 临时对象析构 ~Stack() // s析构 

执行代码的过程中调用拷贝构造,将内存中的内容逐个拷贝,在 C++ 11 中可以借助右值引用实现移动拷贝构造和移动赋值来解决这个问题。

Stack(Stack &&src) :msize(src.msize), mtop(src.mtop) { cout << "Stack(Stack&&)" << endl; /*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/ mpstack = src.mpstack; src.mpstack = nullptr; } // 带右值引用参数的赋值运算符重载函数 Stack& operator=(Stack &&src) { cout << "operator=(Stack&&)" << endl; if(this == &src) return *this; delete[]mpstack; msize = src.msize; mtop = src.mtop; /*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/ mpstack = src.mpstack; src.mpstack = nullptr; return *this; } 

执行结果如下。可以看到,在有拷贝构造和移动拷贝构造函数的时候,优先调用了移动拷贝构造和移动赋值。在移动拷贝构造和移动赋值中直接把资源所有权进行了转移,而非拷贝,这就大大提高了执行效率。

Stack(int) // 构造s Stack(int) // 构造tmp Stack(Stack&&) // 调用带右值引用的拷贝构造函数,直接将tmp的资源给临时对象 ~Stack() // tmp析构 operator=(Stack&&) // 调用带右值引用的赋值运算符重载函数,直接将临时对象资源给s ~Stack() // 临时对象析构 ~Stack() // s析构 

右值引用在可以使重载函数变得更加简洁。右值引用可以适用 const T& 和 T& 形式的参数。

struct W { W(int&, int&) {} }; struct X { X(const int&, int&) {} }; struct Y { Y(int&, const int&) {} }; struct Z { Z(const int&, const int&) {} }; template <typename T, typename A1, typename A2> T* factory(A1& a1, A2& a2) { return new T(a1, a2); } template <typename T, typename A1, typename A2> T* factory_new(A1&& a1, A2&& a2) { return new T(std::forward<A1>(a1), std::forward<A2>(a2)); } // demo int a = 2; int b = 2; W* c = factory<w>(a, b); // ok Z* d = factory<Z>(2, 2); // 错误,2 是右值 W* pw = factory_new<W>(a, b); // ok X* px = factory_new<X>(2, b); // ok Y* py = factory_new<Y>(a, 2); // ok Z* e = factory_new<Z>(2, 2); // ok W* f = factory_new<W>(2, 2); // 错误, 

4.什么时候生成默认构造函数(无参构造函数)?什么时候生成默认拷贝构造函数?什么是深拷贝?什么是浅拷贝?默认拷贝构造函数是哪种拷贝?什么时候用深拷贝?

  • 没有任何构造函数时,编译器会自动生成默认构造函数,也就是无参构造函数;当类没有拷贝构造函数时,会生成默认拷贝构造函数
  • 深拷贝是指拷贝后对象的逻辑状态相同,而浅拷贝是指拷贝后对象的物理状态相同;默认拷贝构造函数属于浅拷贝。
  • 当系统中有成员指代了系统中的资源时,需要深拷贝。比如指向了动态内存空间,打开了外存中的文件或者使用了系统中的网络接口等。如果不进行深拷贝,比如动态内存空间,可能会出现多次被释放的问题。是否需要定义拷贝构造函数的原则是,是类是否有成员调用了系统资源,如果定义拷贝构造函数,一定是定义深拷贝,否则没有意义。
    更多可以参考下面的代码,比较容易混淆的是赋值操作符,其实区分很简单,在出现等号的时候,如果有构造新的对象时调用的就是构造,不然就是赋值操作符。
class A { public: A() { m = new int[4]{ 1,2,3,4 }; std::cout << "constructor" << std::endl; } ~A() { if (m != nullptr) { delete[] m; } } A(const A& a) { this->m = new int[4]; memcpy(a.m, this->m, this->len * sizeof(int)); std::cout << "copy constructor" << std::endl; } // 移动构造 A(A&& a) : m(a.m) { a.m = nullptr; std::cout << "move constructor" << std::endl; } // 赋值操作符重载 A& operator= (const A& a) { memcpy(a.m, this->m, this->len * sizeof(int)); std::cout << "operator" << std::endl; return *this; } ​ private: int len = 4; int* m = nullptr; }; ​ A getA(A a) { return a; } ​ int main(void) { A a; // construct A b = a; // copy construct A c(a); // copy construct A d; // construct d = a; // operate ​ A e = getA(a); // construct, move construct ​ return 0; } 

5.虚析构函数的作用?

基类采用虚析构函数可以防止内存泄漏。比如下面的代码中,如果基类 A 中不是虚析构函数,则 B 的析构函数不会被调用,因此会造成内存泄漏。

class A{ public: A(){} //~A(){} virtual ~A(){} // 虚析构 }; class B : public A{ public: B(){ // new memory } ~B(){ // delete memory } }; int main(int argc, char *argv) { A *p = new B; // some operations // ... delete p; // 由于基类中是虚析构,这里会先调用B的析构函数,然后调用A的析构函数 return 0; } 

但并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

6.解释下封装、继承和多态?

1)封装:
封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元中(我们称之为类)

封装的意义在于保护或者防止代码(数据)被我们无意中破坏。

从封装的角度看,public, private 和 protected 属性的特点如下

不管那种属性,内类都是可以访问的

public 是一种暴露的手段,比如暴露接口,类的对象可以访问

private 是一种隐藏的手段,类的对象不能访问

protected 成员:

和 public 一样可以被子类继承

和 private 一样不能在类外被直接调用

特例:在衍生类中可以通过衍生类对象访问,如下代码所示

class Base { public: Base(){}; virtual ~Base(){}; protected: int int_pro; }; class A : public Base { public: A(){}; A(int da){int_pro = da;} // 通过 obj 对象直接访问 protected 成员 void Set(A &obj){obj.int_pro = 24;} void PrintPro(){cout << "The proteted data is " << int_pro <<endl;} }; 

2). 继承:

继承主要实现重用代码,节省开发时间。

子类可以继承父类的一些东西。

a.公有继承(public)公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态(基类的私有成员仍然是私有的,不能被这个派生类的子类所访问)。

b.私有继承(private)私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员(并且不能被这个派生类的子类所访问)。

c.保护继承(protected)保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员(并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的)。

这里特别提一下虚继承。虚继承是解决C++多重继承问题(其一,浪费存储空间;第二,存在二义性问题)的一种手段。比如菱形继承,典型的应用就是 iostream, 其继承于 istream 和 ostream,而 istream 和 ostream 又继承于 ios。

3).多态:

多态是指通过基类的指针或者引用,在运行时动态调用实际绑定对象函数的行为。与之相对应的编译时绑定函数称为静态绑定。多态是设计模式的基础,多态是框架的基础。

博主已将大量C++相关面试题汇总整理成了一个PDF版的C++面试宝典,关注微 信 公 众 号 “C和C加加” 回复“面试题”即可获取!

2021最新C++面试题(附答案)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/232342.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • c语言stat函数返回值,stat函数

    c语言stat函数返回值,stat函数一.概述:表头文件:#include#include定义函数:intstat(constchar*file_name,structstat*buf);函数说明:通过文件名filename获取文件信息,并保存在buf所指的结构体stat中返回值:执行成功则返回0,失败返回-1,错误代码存于errno错误代码:ENOENT参数fil…

    2022年8月21日
    13
  • plsqldeveloper_电脑病毒勒索比特币

    plsqldeveloper_电脑病毒勒索比特币plsql dev引起的数据库被黑勒索比特币实现原理分析和解决方案—-惜分飞

    2022年4月20日
    141
  • msfconsole爆破ftp_check point防火墙

    msfconsole爆破ftp_check point防火墙Msfconsole爆破ssh一:什么是sshSsh为linux系统下的远程登录命令。好比windows下的远程桌面。登录ssh后可以对服务器进行相关的操作!二:端口扫描目标发现1:用namp或者netcat扫描网络,发现我的路由器是开启了ssh服务2:用netcap扫描三:msfconsole激活成功教程密码1:msfconsole运行…

    2025年9月5日
    8
  • pycharm怎么打包成exe文件_pycharm打包python程序

    pycharm怎么打包成exe文件_pycharm打包python程序如何将python中的文件打包成exe文件:首先,在cmd中输入pip3Installpyinstaller他就会开始下载pyinstaller下载成功后就点进去你想去打包的代码中,我使用的是pycharm,然后点击terminal,输入Pyinstaller-FXXXX.Py其中XXXX指的是你要打包的文件名,弄好之后你就成功将其打包成exe文件了,下载后里面会显示你的exe文件的路径,去找就好了,发送给别人的话我是压缩后发送的以下是pyinstaller功能的一些注..

    2022年8月28日
    25
  • 第一次面试实习生经历

    第一次面试实习生经历

    2022年2月3日
    38
  • (教程)手把手教你如何申请软件著作权

    (教程)手把手教你如何申请软件著作权「软件」通常指含有图形化界面的程序,但日常「科研程序」很少用得到图形界面,所以本文主要叙述科研程序(代码)的申请方式。专利保护的是思想,而软件著作权保护的是具体实现方式,因而软著的申请难度和价值都比专利要低不少。如果你是独立实现了已有的算法、独立复现了别人的程序,同样可以用来申请软件著作权。版权保护中心官网如下,申请软著均在此网站内完成:中国版权保护中心如果想省事的也可以考虑第三方服务申请我推荐使用:阿里云软件著作权申请​使用阿里云软件著作权申请有很多好处,阿里云为了拓展业务,费用和自己申请都是

    2022年9月22日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号