【c++面试集】年度整理

系列文章目录


文章目录

  • 系列文章目录
  • 前言
  • 一、C++基础(必备)
    • 三目运算符表达式
    • 原码、反码和补码
    • 常量定义
    • 变量定义
    • 变量持久性
    • lambda 表达式默认捕获变量
    • const、virtual、static和noexcept关键字的用法
    • 自增自减在while中使用
    • 模板使用
    • 类和结构体区别
    • 标准库strcpy使用
    • sizeof 运算符计算字节数
    • #include默认路径搜索
    • 容器vector运算符重载
    • 运算符类型转换
  • 二、C++面向对象
    • 面向对象语言特性
    • 多态性
    • 初始化列表的初始化顺序
    • 派生类构造函数和析构函数的执行顺序
    • 类构造函数次数
    • 虚函数实现原理
    • 重载、重写和隐藏
  • 三、C++内存管理
    • 指针数组和运算符优先级别: [] > * > ++
    • 二维指针使用
    • 堆与栈
  • 四、C++11 及现代 C++特性
    • 智能指针和移动语义
    • nullptr 和 NULL的区别
  • 五、进程通信
    • 进程间通信
    • 协程和线程
  • 六、算法
    • 链表插入
  • 七、推荐书籍
  • 八、推荐网站


前言

每一年有的企业会对相关专业做一次语言考试,作为个人也应对自己的主要编程语言做一次年度考试。

一、C++基础(必备)

三目运算符表达式

条件表达式 “a?b:c”,当a为真时,取b值,否则取c值。C/C++遵循的规则是“非零即为真”,所有不是零的数,都可以认为是“true”,而仅把零当做false,即“为真”即“不等于零”,“为假”即“等于零”,所以题目中w等价的表达式是w非零。

若有表达式“(w) ? (–x) : (++y)”,则其中与w等价的表达式是: w!=0

原码、反码和补码

正数的原码、反码和补码都相同;负数原码和反码的相互转换的方式是符号位不变,数值位按位取反;负数原码和补码的相互转换的方式是符号位不变,数值位按位取反,末位再加1;0的补码表示只有一种。

下列关于原码、反码和补码的描述,正确的是: ABCD

A、负整数的符号位为1
B、0的补码是唯一的
C、正整数的原码、反码和补码都一样
D、十进制数-122的原码为11111010,反码为10000101,补码为10000110

常量定义

下列哪些写法是对的,而且是常量? ABD

A、125
B、1.25E+20
C、‘AB’
D、-0.456

变量定义

下列代码的main函数中哪个标志符是变量名?

class Base
{
public:
   
   Base() {}
   
   Base(int i)
       : counter(i)
   {
   }
   
private:
   
   int counter = 0;
};

int main()
{  
   Base a(); //  函数声明的优先级最高,所以标志符a是函数名
   
   Base b(1); // 变量名
   
   Base c = 1;// 变量名
   
   Base d{};// 变量名
   
  return 0;
}

变量持久性

全局变量持久性跟进程相同;静态全局变量改变的链接性,持久性跟进程相同;静态局部变量的作用域是局部的,但是持久性跟进程相同;静态成员变量的持久性跟进程相同。

以下哪种类型的变量的持久性跟进程是一样的? BCEF

A、非静态成员变量
B、静态全局变量
C、静态局部变量
D、非静态局部变量
E、静态成员变量
F、全局变量

lambda 表达式默认捕获变量

lambda只能捕获非静态局部变量,不能捕获全局变量和静态局部变量;this是一种特殊的局部变量。

下面的代码,lambda表达式捕获了哪几个变量? BD

int g_counter = 0;
class Base
{
public:
   
   void testLambda()
   {
       int index = 0;
       static int counter = 1;
       auto callback = [=]() // =代表可以捕获所有,但lambda只能捕获非静态局部变量,不能捕获全局变量和静态局部变量; this是一种特殊的局部变量
       {
       };
   }
};

A、counter
B、this
C、g_counter
D、index

const、virtual、static和noexcept关键字的用法

  1. const关键字可以用来修饰变量和函数
  2. virtual关键字只能用来修饰函数和类继承
  3. static关键字可以用来修饰变量和函数,用来定义静态成员变量和静态成员函数;还可以用来改变全局变量的链接行以及局部变量的持久性;也可以改变全局函数的链接性
  4. noexcept关键字用来表明该函数不会产生异常,允许编译器尽量优化异常相关的代码

以下哪几个关键字即可用来修饰变量又可以用来修饰函数? AD

A、const
B、virtual
C、noexcept
D、static

自增自减在while中使用

#include 
void main()
{
int num=2,i=6;
do {
i--;
num++;
} while(--i);
cout<

模板使用

模板是类型无关的,具有很高的可复用性;模板是平台无关的,可移植性;模板是编译时检查数据类型而不是运行时,保证了类型安全;模板可用来创建动态增长和减少的数据结构。

C++中使用模板类的原因,下列描述正确的是? ABD

A、可用于基本数据类型
B、可用来创建动态增长和减小的数据结构
C、它是类型相关的,因此具有很高的可复用性。
D、它是平台无关的,可移植性
E、它在运行时而不是编译时检查数据类型,保证了类型安全

类和结构体区别

class 可以使用模板,而 struct 不能。

关于C++中类class和C语言中struct的区别描述,说法正确的是? ACD

A、class中的成员默认是private 属性的,struct的成员默认是public 属性的
B、class 和struct都可以使用模板类型
C、class有继承、多态的特性,struct没有
D、class可以定义成员函数,struct只能定义成员变量

标准库strcpy使用

// 请问运行Test 函数会有什么样的结果?
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL)
{
 strcpy(str, “world”); // world
 printf(str);
}
}

sizeof 运算符计算字节数

sizeof计算结构体字节数

// 假设sizeof(int)等于4,则sizeof(struct Derived)的值是什么?

// 编译器默认会在结构体成员变量之间插入补齐字节,确保每一个成员变量都是自然对齐的。
struct Base
{
   char a; // 偏移量为0, 0 + 1 = 1
   int b; // 偏移量为4,4 +4 = 8
   char c; // 偏移量为8, 8 + 1 = 9, 但要自然对齐规则的满足为int(4) 的整数倍,因此添加3个pad, 为8 + 1 + 3 = 12
}; // 12,结构体整体大小为最大字节数为int(4)的整数倍

// 如果子类中所有成员的自然对齐要求小于父类的话,则子类需要按照父类的自然要求进行对齐,也可以理解成嵌套结构体的字节数
// 嵌套结构体字节数需要将嵌套结构体展开计算偏移量(为接下来要放置变量的整数倍),而不是整个计算。
struct Derived : public Base
{
   char d;
};  

sizeof 计算数组的字节数

// 某32位系统下,C++程序,请计算sizeof 的值,按程序执行顺序,应该输出什么

// 数组名作函数的参数,主调函数和被调函数共用一段存储单元
void Foo ( char str[100] )
{
printf("sizeof(str)=%d \n", sizeof(str) );
}

void main()
{
char str[] = "www.ibegroup.com";
char *p1 = str ;
int n = 10;
void *p2 = malloc( 100 );
printf("sizeof(str)=%d \n", sizeof(str) ); // 17
printf("sizeof(p1)=%d \n", sizeof(p1) ); // 4
printf("sizeof(n)=%d \n", sizeof(n) ); // 4
printf("sizeof(p2)=%d \n", sizeof(p2) ); // 4
Foo(str); // 4
}

sizeof 计算类的字节数

1.假设sizeof(long)等于4
2. 给定一个类C,该类不继承任何其他类
3. 该类的内存布局采用编译器默认逻辑
4. 该类中包含一个int类型非静态成员和两个char类型的非静态成员变量
则,sizeof(C)表达式可能的求值结果是多少? ABD

A、16 // char int char 和考虑定义虚函数;如果该类定义了虚函数,则需要增加一个虚函数表指针成员变量,其大小为sizeof(long)
B、8 // char char int ,不考虑虚函数
C、10
D、12 // char int char,不考虑虚函数

#include默认路径搜索

#include 和 #include “filename.h” 有什么区别? D

A、对于#include ,编译器从用户的工作路径开始搜索filename.h;对于#include “filename.h” ,编译器从用户的工作路径开始搜索filename.h
B、对于#include ,编译器从用户的工作路径开始搜索filename.h;对于#include “filename.h” ,编译器从标准库路径开始搜索filename.h
C、对于#include ,编译器从标准库路径开始搜索filename.h;对于#include “filename.h” ,编译器从标准库路径开始搜索filename.h
D、对于#include ,编译器从标准库路径开始搜索filename.h;对于#include “filename.h” ,编译器从用户的工作路径开始搜索filename.h

容器vector运算符重载

vector::iterator重载了下面哪些运算符? ABD

A、++
B、==
C、>>
D、*(前置)

运算符类型转换

C++运算法匿名转换向高类型转换。

已知:char a ; float b ; double c ; 则执行语句:c = a + b + c; 后变量c的类型为:double

以下两条输出语句,按照顺序,输出什么

float a = 1.0f;
cout << (int)a << endl;
cout << (int&)a << endl;
cout << boolalpha << ( (int)a == (int&)a ) << endl; // false
float b = 0.0f;
cout << (int)b << endl;
cout << (int&)b << endl;
cout << boolalpha << ( (int)b == (int&)b ) << endl; // true

二、C++面向对象

面向对象语言特性

下列对C++语言特点描述正确的选项是?ABD

A、可以担负起以模版为特征的泛型化编程
B、在C语言的基础上进行扩充和完善,使C++兼容了C语言的面向过程特点,又成为了一种面向对象的程序设计语言
C、可以使用抽象数据类型进行面向过程的编程
D、可以使用多继承、多态进行面向对象的编程

多态性

关于C++的多态性,下列描述正确的是?ABCD

A、存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的
B、在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数
C、多态用虚函数来实现,结合动态绑定
D、多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性
E、用virtual关键字申明的函数叫做虚函数,虚函数不一定是类的成员函数

初始化列表的初始化顺序

初始化列表的初始化顺序:为了避免两次构造,推荐使用类构造函数初始化列表。初始化列表不是按照列表的顺序进行的;是按照内存模型中的成员变量的顺序(也即类声明的定义顺序)进行的;为了避免出现依赖的问题,应当让参数列表和在类内的成员变量声明保持一致。

class A
{
public:
    A():n2(0), n1(n2+0){}
    void print() { cout << "n1: " << n1 << ", n2: " << n2 << endl; }
private:
    int n1;
    int n2;
};

int main(int, char**)
{
    A a;

	// 初始化的顺序是由类内定义的顺序决定的,先初始化 n1,而 n1 由未被赋值的 n2 初始化
    a.print(); // n1: -858993458, n2: 0
    return 0;
}

派生类构造函数和析构函数的执行顺序

派生类构造函数必须对这三类成员进行初始化,其执行顺序:调用基类构造函数;调用子对象的构造函数;派生类的构造函数体。

析构函数在执行过程中也要对基类和成员对象进行操作,但它的执行过程与构造函数正好相反,即对派生类新增普通成员进行清理;调用成员对象析构函数,对派生类新增的成员对象进行清理;调用基类析构函数,对基类进行清理。

有程序如下:
#include
using namespace std;
class A {
public:
A() { cout << "A"; }
};
class B { public:B() { cout << "B"; } };
class C : public A {
   B b;
public:
   C() { cout << "C"; }
};

int main() { 
C obj;  // "ABC" 
return 0; 
}

类构造函数次数

假定AB为一个类,则执行“AB a(2), b[3],*p[4];”语句时调用该类构造函数的次数为: 4 。

a(2)调用1次带参数的构造函数,b[3]调用3次无参数的构造函数,*p[4]指针没有给它分配空间,没有调用构造函数。所以共调用构造函数的次数为4

虚函数实现原理

在下面的代码中,函数调用语句base->print();会产生什么行为?

#include 

class Base
{
public:
   
   virtual void print()
   {
       printf("%d\n", counter++);
   }
             
private:
 
  static int counter;
};

// static
int Base::counter = 0;

int main()
{  
  Base* base = nullptr; // this 指针为空指针
   
   base->print(); // 运行崩溃,虚函数使用虚函数表实现;带有虚函数的类都会有一个由编译器插入的成员变量:虚函数表指针
           
  return 0;
}

给定下面的代码,使用main函数的中的局部变量b分别调用Func1、Func2、Func3和Func4,哪个函数调用会产生崩溃?

class Base {
public:
 Base() : b(1) {}

 void Func1() { printf("Func1 b = %d\n", b); } // 崩溃,this指针为空指针,使用空指针访问成员变量会引发崩溃
 virtual void Func2() { printf("Func2\n"); } // 崩溃,函数是虚函数,需要首先使用空指针获取虚函数表指针成员变量,引发崩溃
 void Func3() { printf("Func3\n"); } // 不崩溃,this指针为空指针,但是,没有访问成员变量,不崩溃
 static void Func4() { printf("Func4\n"); } // 不崩溃,函数是静态成员函数,函数内部不能使用this,不崩溃

private:
 int b;
};

int main() {
 Base* b = nullptr; // this 指针为空指针

 printf("done!\n");

 return 0;
}

重载、重写和隐藏

#include 
在下面的代码中,打印结果是什么?
class Base
{
public:
   Base(int c1)
   {
       counter = c1;
   }
   
   virtual void printCounter()
   {
       printf("%d\n", counter++);
   }
   
protected:
 
  int counter = 1;
};


class Derived : public Base
{
public:
   
   Derived(int c1)
       : Base(c1)
   {
       
   }
   
   void printCounter() override // 子类继承父类并重写了父类的虚函数
   {
       printf("%d\n", counter);
   }
};


int main()
{  
  Derived d(2); // 使用子类实例可以初始化父类对象,并且默认采用拷贝构造

   Derived* pd = &d;
   
   Base& rb = *pd; // 父类引用和指针都可以指向子类实例
   rb.printCounter(); // 2
   
   Base b = d;
   b.printCounter(); // 2
   
  return 0;
}

三、C++内存管理

指针数组和运算符优先级别: [] > * > ++

指针数组:数组元素都是相同类型的指针,相同类型的指针是说指针所指向的对象类型是相同的。

运算符优先级别: [] > * > ++

int *p[5]; 定义了一个指针数组,在指针数组的定义中有两个运算符:*和[ ],运算符[ ]的优先级高于*,所以*p[5]等价于*(p[5]),p[5]表示一个数组,而*表示后面的对象为指针变量,合在一起*p[5]表示一个指针数组。该数组包含5个元素,每个元素都是指向int型的指针。

二维指针使用

执行以下程序段后,m的值为 ( )。
int a[2][3]={{1,2,3},{4,5,6}};
int m,*p=&a[0][0];
m=(*p)*(*(p+2))*(*(p+4)); // 1 * 3 * 5 = 15

堆与栈

C++中什么数据分配在栈中? ACDE
A、函数调用参数
B、静态局部变量
C、局部变量
D、函数返回值
E、函数返回地址

Heap与stack的差别描述正确的是? BCD

A、Heap空间有限,Stack是很大的自由存储区
B、C中的malloc函数分配的内存空间即在堆上,C++中对应的是new操作符。
C、程序在编译期对变量和函数分配内存都在栈上进行,且程序运行过程中函数调用时参数的传递也在栈上进行
D、Stack的空间由操作系统自动分配/释放,Heap上的空间手动分配/释放。

四、C++11 及现代 C++特性

智能指针和移动语义

智能指针类std::unique_ptr支持移动构造和移动赋值,在变量定义时使用等号进行初始化会调用构造函数而不是赋值函数。

在下面的代码中,初始化abc2变量时调用的是什么函数?

  std::unique_ptr abc(new std::string("abc"));
 
  std::unique_ptr abc2 = std::move(abc); // 移动构造

nullptr 和 NULL的区别

在 C++ 中,NULL 的定义实际上是一个整数值 0,而不是一个真正的指针类型。

在函数重载和模板编程中这可能会导致一些问题和歧义。

为了解决这个问题,C++11 引入了一个新的关键字 nullptr,用于表示空指针。

nullptr 是一种特殊类型的字面值,类型为 std::nullptr_t,定义为: typedef decltype(nullptr) nullptr_t,可以隐式转换为任何指针类型。

与 NULL 不同,nullptr 是一个真正的指针类型,因此可以避免一些由于 NULL 是整数类型而引起的问题。

五、进程通信

进程间通信

关于几种进程间通信方式描述,下列选项正确的是? ABCD

A、共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信
B、管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系
C、消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点
D、信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段

协程和线程

关于协程描述,下列正确的是ABCE

A、支持跨平台、跨体系架构、无需线程上下文切换的开销、无需原子操作锁定及同步的开销、方便切换控制流,简化编程模型
B、进行阻塞(Blocking)操作,如IO时,会阻塞掉整个程序,可以使用异步IO操作来解决
C、协程是协作式,线程是抢占式
D、协程没有自身的寄存器上下文和栈,使用系统的
E、协程是一种用户态的轻量级线程。

程序什么时候应该使用线程,什么时候单线程效率高,描述正确的是? CD

A、多CPU系统中,使用线程会降低CPU利用率
B、耗时的操作不应使用线程,会降低应用程序响应时间
C、并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求
D、一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改,其他情况都使用单线程

同一进程下的线程可以共享以下? CD

A、thread ID
B、register set
C、stack
D、data section

六、算法

链表插入

在一个单链表结构中,指针p指向链表的倒数第二个结点,指针s指向新结点,则能将s所指的结点插入到链表末尾的语句组是? BCD

A、p=p->next; s->next=p; p->next=s
B、p=(*p).next; (*s).next=(*p).next; (*p).next=s
C、p=p->next; s->next=p->next; p->next=s
D、s->next=NULL; p=p->next; p->next=s

七、推荐书籍

学习新东西的时候,注重知识的体系性和框架的建立,然后集中时间,快速抓住领域的主线,突出重点去学习。对于细枝末节的零散内容,可以留到实践的时候,用到了再去查看。

书名 推荐情况 预估时间
《C++ Primer》 推荐全看,按照目录索引查漏补缺 2个月
《Effective C++》 推荐全看,掌握 C++ 编码规范 3周
《深度探索 C++ 对象模型》 推荐前3章,掌握内存模型整体概念 2周
《STL 源码剖析》 推荐掌握vector, map, quene STL 源码和底层机制 3周

八、推荐网站

C++ 日常开发一定要记得以下几个网站,可以随时查阅一些语法的用法和标准库:

  • https://en.cppreference.com/w/
  • https://cplusplus.com
  • https://isocpp.org

你可能感兴趣的:(c++面试,c++,开发语言)