【C++】面试题整理

参考别人的面试题,自己搜集汇总、整理了答案。

1.C++ extern关键字的作用

C++语言支持分离式编译机制,该机制允许将程序分割为若干个文件,每个文件可以被单独编译,多个文件之间可能需要共享代码,即一个文件的代码可能需要另一个文件中定义的变量。

C++的单定义规则: 所谓的单定义规则(One Definition Rule,ODR)是指变量只能有一次定义。为了满足这种需求,c++提供了两种变量声明。
一种是定义声明(defining declaration)简称定义,它给变量分配内存空间;
另外一种是引用声明(referencing declaration)简称为声明,它不给变量分配空间,因为它引用已有变量。

变量只能被定义一次,但可以被声明多次。

在全局变量中,引用声明使用extern关键字且不进行初始化。否则声明为定义,导致分配空间:

int x; // definition x is 0
extern int y; //y defined in elsewhere
extern char c = ‘g’; // definition ,c is initialized

如果要在多个文件中使用外部变量,只需在一个文件中包含该变量的定义(ORD),但在使用改变量的其他所有文件中,都必须使用extern声明这个变量。

当编译器遇到extern模板声明时,它不会在本文件中生成实例化代码,将一个实例化声明为extern就表示承诺在程序的其他位置有该实例化的一个非extern定义。对于一个给定的实例化版本,可能有多个extern声明,但必须只有一个定义。

对于extern声明的函数,表明其在其他源文件中具有定义。
由于以上讨论可知:extern一般是使用在多文件之间需要共享某些代码时。

2.C++的this指针

C++ this指针详解
C++this指针
this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。

this 作为隐式形参,本质上是成员函数的局部变量,所以只能用在成员函数的内部,并且只有在通过对象调用成员函数时才给 this 赋值。

成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是 this,它是成员函数和成员变量关联的桥梁。

3.C++ static静态成员函数详解

C++ static静态成员函数详解
在类中,被static关键字修饰的函数为静态成员函数。
编译器在编译普通成员函数时,会隐式地增加一个形参this,并把当前对象的地址赋给this。也正因此,普通成员函数只能在创建对象之后通过对象来调用,因为它需要当前对象的地址。
而静态成员函数没有this指针,不知道指向哪个对象,无法访问对象的成员变量,因此,静态成员函数不能访问普通成员变量,只能访问静态成员变量。

静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。

4.友元

一个类中可以有 public、protected、private 三种属性的成员,通过对象可以访问 public 成员,只有本类中的函数可以访问本类的 private 成员。
借助友元(friend),可以使得其他类中的成员函数以及全局范围内的函数访问当前类的 private 成员。
C++ friend友元函数和友元类
友元函数
(1)将非成员函数声明为友元函数
(2)将其他类的成员函数声明为友元函数

注意,友元函数不同于类的成员函数,在友元函数中不能直接访问类的成员,必须要借助对象。
成员函数在调用时会隐式地增加 this 指针,指向调用它的对象,从而使用该对象的成员;而 show() 是非成员函数,没有 this 指针,编译器不知道使用哪个对象的成员,要想明确这一点,就必须通过参数传递对象(可以直接传递对象,也可以传递对象指针或对象引用),并在访问成员时指明对象。
友元类

需要说明:

友元的关系是单向的而不是双向的。如果声明了类 B 是类 A 的友元类,不等于类 A 是类 B 的友元类,类 A 中的成员函数不能访问类 B 中的 private 成员。
友元的关系不能传递。如果类 B 是类 A 的友元类,类 C 是类 B 的友元类,不等于类 C 是类 A 的友元类。

除非有必要,一般不建议把整个类声明为友元类,而只将某些成员函数声明为友元函数,这样更安全一些。

5.main函数中两个参数的用法总结

main函数中两个参数的用法总结
int main(int argc,char* argv[])详解

1、定义
C语言规定main函数的参数只能有两个,习惯上这两个参数写为argc和argv。因此,main函数的函数头可写为: main (argc,argv)
C语言还规定argc(第一个形参)必须是整型变量,argv( 第二个形参)必须是指向字符串的指针数组。加上形参说明后,main函数的函数头应写为:
int main (int argc,char *argv[]){…}或者
int main (int argc,char **argv){…}
其中第一个表示参数的个数;第二个参数中argv[0]为自身运行目录路径和程序名,argv[1]指向第一个参数、argv[2]指向第二个参数……

2、如何执行
由于main函数不能被其它函数调用, 因此不可能在程序内部取得实际值。那么,在何处把实参值赋予main函数的形参呢**? 实际上,main函数的参数值是从操作系统命令行上获得的**。当我们要运行一个可执行文件时,在DOS提示符下键入文件名,再输入实际参数即可把这些实参传 送到main的形参中去,具体操作如下:
首先在C++中完成编译之后,然后在dos命令窗口下切换到.exe执行文件所在的文件夹,再在该路径下输入带参数的命令:XXX.exe a b c …即可得到输出。

6.移动构造

C++11移动构造函数详解

在C++11标准之前,如果想用其他对象初试化一个同类的新对象,只能借助类的复制(拷贝)构造函数。‘
另外,当类中拥有指针类型的成员变量时,拷贝构造函数中需要以深拷贝的方式复制该指针成员。
C++深拷贝和浅拷贝(深复制和浅复制)完全攻略

深拷贝除了会将原有对象的所有成员变量拷贝给新对象,还会为新对象再分配一块内存,并将原有对象持有的内存也拷贝过来。这样做的结果是,原有对象和新对象所持有的动态内存是相互独立的。更改一个对象的数据不会影响另外一个对象。
深拷贝即拷贝指针成员本身的同时,还要拷贝指针指向的内存资源,否则一旦多个对象的指针成员指向同一块堆空间,这些对象析构时就会对该空间释放多次,这是不允许的。

C++移动构造函数(移动语义的具体实现)

移动构造函数(c++11)
C++中对象发送拷贝的场景可以分为两种:
被拷贝的对象还要继续使用;
被拷贝的对象不再继续使用,第二种一般可以认为是对右值的拷贝。

所谓移动语义,指的就是以移动而非深拷贝的方式初始化含有指针成员的类对象。
简单的理解,移动语义指的就是将其他对象(通常是临时对象)拥有的内存资源“移为已用”。
对象发生拷贝时不需要重新分配空间而是使用被拷贝对象的内存,从而提高代码运行效率。

通常情况下:
用户使用右值初始化类对象时,会调用移动构造函数;
用户使用左值初始化类对象时,会调用拷贝构造函数。
C++11 标准中为了满足用户使用左值初始化同类对象时也通过移动构造函数完成的需求,新引入了 std::move() 函数,它可以将左值强制转换成对应的右值,由此便可以使用移动构造函数。

调用拷贝构造函数的情形

返回参数为对象

7.左值 右值

左值右值将亡值
基础篇:lvalue,rvalue和move
右值:程序执行结果中产生的临时对象(函数返回值、lambda表达式)既无名称也无法获取其存储地址,所以属于右值。

将左值强制转换为右值
std::move() 能把左值强制转换为右值。
move()函数
移动构造函数
从4行代码看右值引用

8.强制类型转换

c++ 强制类型转换
static_cast、dynamic_cast、const_cast、reinterpret_cast
强制类型转换的一般形式为:
(类型名)(表达式)
或 类型名(表达式)

static_cast
 a、用于类层次结构中基类和派生类之间指针或引用的转换;
  上行转换(派生类---->基类)是安全的。
  下行转换(基类---->派生类)由于没有动态类型检查,所以是不安全的。
b、用于基本数据类型之间的转换,如把int转换为char,安全性问题由开发者来保证;
c、把空指针转换成目标类型的空指针;
d、把任何类型的表达式转为void类型;

reinterpret_cast
用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时,执行的是逐个比特的复制操作。
1.使用场景:
不到万不得已,不用使用这个转换符,高危操作;
2.使用特点:  
  a、reinterpret_cast是从底层对数据进行重新解释,依赖具体的平台,可移植性差;
  b、reinterpret_cast可以将整型转换为指针,也可以把指针转换为数组;
  c、reinterpret_cast可以在指针和引用里进行肆无忌惮的转换;
 
const_cast
const_cast 运算符仅用于进行去除 const 属性的转换,它也是四个强制类型转换运算符中唯一能够去除 const 属性的运算符。
1.使用场景:
  a、常量指针转换为非常量指针,并且仍然指向原来的对象
  b、常量引用被转换为非常量引用,并且仍然指向原来的对象
2.使用特点:
  a、cosnt_cast是四种类型转换符中唯一可以对常量进行操作的转换符
  b、去除常量性是一个危险的动作,尽量避免使用。
  
dynamic_cast
dynamic_cast专门用于将多态基类的指针或引用强制转换为派生类的指针或引用,而且能够检查转换的安全性。对于不安全的指针转换,转换结果返回 NULL 指针。

dynamic_cast 是通过“运行时类型检查”来保证安全性的。不能用于将非多态基类的指针或引用强制转换为派生类的指针或引用——这种转换没法保证安全性,只好用 reinterpret_cast 来完成。

1.使用场景:
只有在派生类之间转换时才使用dynamic_cast。
2.使用特点:
  a、基类必须要有虚函数,因为dynamic_cast是运行时类型检查,需要运行时类型信息,而这个信息是存储在类的虚函数表中,只有一个类定义了虚函数,才会有虚函数表(如果一个类没有虚函数,那么一般意义上,这个类的设计者也不想它成为一个基类)。
  b、对于下行转换,dynamic_cast是安全的(当类型不一致时,转换过来的是空指针),而static_cast是不安全的(当类型不一致时,转换过来的是错误意义的指针,可能造成踩内存,非法访问等各种问题)
  c、dynamic_cast还可以进行交叉转换

9.智能指针

RAII思想

RAII
RAII(Resource Acquisition Is Initialization)资源获取即初始化。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终有效。最后在对象析构时释放资源。借此,实际上我们把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

  • 不需要显式地释放资源;
  • 对象所需的资源在其生命周期内始终保持有效。

智能指针

智能指针1
智能指针2
智能指针3

每个程序的内存除了有静态内存和栈内存外,还有一个内存池,这部分内存被称为自由空间或堆。
程序用堆来存储动态分配的对象,当动态对象不再使用时,我们的代码必须显式的销毁他们。
在C++,动态内存的管理用一对运算符完成:new和delete。
new在自由空间中为对象分配一块空间并返回一个指向该对象的指针;
delete销毁指针指向的对象,释放与之关联的内存。

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

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

C++11 引入了 3 个智能指针类型:
std::unique_ptr< T > :独占资源所有权的指针。
std::shared_ptr< T > :共享资源所有权的指针。
std::weak_ptr< T > :共享资源的观察者,需要和 std::shared_ptr 一起使用,不影响资源的生命周期。

unique_ptr原理:把将拷贝构造/赋值函数设为delete,即将这两个函数定义成已删除的函数,任何试图调用它的行为将产生编译期错误,将这两个函数设为私有,且只声明不实现,这是C++11标准的内容。

shared_ptr原理:shared_ptr既不会直接剥夺原对象对内存的控制权,也允许进行拷贝构造和赋值,因为引入了一个新的标志—引用计数。
shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
循环引用问题:
在这里插入图片描述
我的理解:有两个对象互相指向对方,因此在两个对象同时析构时,需要先把对方给析构掉,这就导致了死循环。

weak_ptr

std::weak_ptr is a smart pointer that holds a non-owning (“weak”) reference to an object that is managed by std::shared_ptr. It must be converted to std::shared_ptr in order to access the referenced object.

std::weak_ptr models temporary ownership: when an object needs to be accessed only if it exists, and it may be deleted at any time by someone else, std::weak_ptr is used to track the object, and it is converted to std::shared_ptr to assume temporary ownership. If the original std::shared_ptr is destroyed at this time, the object’s lifetime is extended until the temporary std::shared_ptr is destroyed as well.
Another use for std::weak_ptr is to break reference cycles formed by objects managed by std::shared_ptr. If such cycle is orphaned (i.e., there are no outside shared pointers into the cycle), the shared_ptr reference counts cannot reach zero and the memory is leaked. To prevent this, one of the pointers in the cycle can be made weak.

可以用于辅助shared_ptr来解决引用计数的问题。
当shared_ptr内部要监视其他的shared_ptr对象时,类型就采用weak_ptr。这种weak_ptr在指向被监视的shared_ptr后,并不会使被监视的引用计数增加,且当被监视的对象析构后就自动失效。

lambda表达式

lambda
提供了一个类似匿名函数的特性。即在需要一个函数又不想费力去命名的情况下使用。

基本语法:

[caputrue](params)opt->ret{body;};

capture是捕获列表
params是参数表;选填
opt:函数选项;可以填mutable,exception,attribute(选填)。mutable:说明lambda表达式内的代码可以修改被捕获的变量,并且可以访问被捕获的non-const方法;exception:说明lambda表达式是否抛出异常以及何种异常;attribute:用来声明属性。
ret:返回值类型(拖尾返回类型)。(选填)
body:是函数体。

lambda表达式的捕获列表捕获住的任何外部变量,最终会变成闭包类型的成员变量。按照C++11标准,lambda表达式的operator()默认是const的,一个const成员函数无法修改成员变量。而mutable的作用就是修改operator的const的。

lambda实现原理:
每当定义一个lambda表达式之后,编译器会自动生成一个匿名类(这个类重载了()运算符),我们称为闭包类型(closure type)。运行时lambda表达式就会返回一个匿名的闭包实例,是一个右值。所以lambda表达式的结果就是一个个闭包。
对于复制传值捕获方式,类中会添加非静态成员变量,运行时,会用复制的值初始化这些非静态成员变量,从而生成闭包。
对于引用捕获方式,无论是否标记为mutable,都可以修改被捕获变量的值。

function,bind

在STL定义的< functional>头文件提供了一个多态的函数对象封装std::function,其类似函数指针。它可以绑定函数对象,只要参数和返回类型相同。

参考链接:
C++ extern
C++中extern关键字用法小结

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于面试题文档下载的需求,我可以提供以下答案。 首先,在网上寻找相关的面试题文档下载网站是一种常见的方法。可以通过搜索引擎输入相关关键词,如“面试题文档下载”、“职位面试题集合”等,会有很多网站提供免费或付费的面试题文档下载服务。用户可以根据自己的需求选择合适的网站,浏览并下载自己需要的面试题文档。 另外,与具体面试相关的行业或领域网站也会提供相关的面试题文档下载。例如,在招聘网站或行业专业网站中,可以找到与特定职位或行业相关的面试题文档。这些文档通常会包含常见的面试题目和答案,帮助求职者更好地准备面试。 此外,还可以通过与同行或朋友交流来获取面试题文档。有时候,一些人可能会有自己精心整理和收集的面试题库,他们乐意与别人分享。可以向他们索取面试题文档,并根据需要进行下载和使用。 最后,自己整理和记录面试题文档也是一个很好的方法。在整个求职和面试过程中,我们可能会遇到各种各样的面试题目。可以将这些问题和答案整理成一个个主题,形成自己的面试题文档。随着时间的推移,这个文档会不断完善和积累,成为自己的宝贵资源。 总之,寻找面试题文档下载的方法很多,可以利用网上资源、行业专业网站、交流与自身整理等途径。希望以上回答对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值