面试必会知识------C++

C++常见面试题:https://blog.csdn.net/fakine/article/details/51321544

目录

目录

1、new与malloc,delete与free之间的区别:

2、虚函数

1、为什么要用虚函数?虚函数一般在编译器怎么实现多态?

2、纯虚函数

3、虚函数表

4、虚函数表是在什么时期建立的?

3、vector的相关内容

1、vector及new vector的区别:

2、vector源码剖析:

3、vector的构造和内存管理:

 

4、static关键词

5、const关键词

6、volatile

7、define与const之间的区别

10、类型萃取

引用



1、new与malloc,delete与free之间的区别:

1、本质区别

malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符。
对于用户自定义的对象而言,用maloc/free无法满足动态管理对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。

malloc/free不可以被重载,而new/delete可以被重载。

opeartor new /operator delete可以被重载。标准库是定义了operator new函数和operator delete函数的8个重载版本:

//这些版本可能抛出异常
void * operator new(size_t);
void * operator new[](size_t);
void * operator delete (void * )noexcept;
void * operator delete[](void *0)noexcept;
//这些版本承诺不抛出异常
void * operator new(size_t ,nothrow_t&) noexcept;
void * operator new[](size_t, nothrow_t& );
void * operator delete (void *,nothrow_t& )noexcept;
void * operator delete[](void *0,nothrow_t& )noexcept;

2、使用方法

malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。malloc需要提供申请空间的大小,并且要将返回的void*强制转化为所期望的类型。

而new的使用方法比较简单,因为new 内置了sizeof、类型转换和类型安全检查功能对于非内部数据类型的对象而言,new 在创建动态对象的同时完成了初始化工作

3、new/delete会调用构造函数和析构函数,但是malloc/free则不会

使用new操作符来分配对象内存时会经历三个步骤:

  • 第一步:调用operator new 函数(对于数组是operator new[])分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象。
  • 第二步:编译器运行相应的构造函数以构造对象,并为其传入初值。
  • 第三部:对象构造完成后,返回一个指向该对象的指针。

使用delete操作符来释放对象内存时会经历两个步骤:

  • 第一步:调用对象的析构函数。
  • 第二步:编译器调用operator delete(或operator delete[])函数释放内存空间。

4、处理数组时new比较特殊,必须采用new[]与delete[]

注意:C++中的内存分配最终都是采用malloc/free:new中调用malloc函数,allocate和deallocate中调用的new。

 

2、虚函数

https://songlee24.github.io/2014/09/02/cpp-virtual-table/讲的很不错

https://blog.csdn.net/lihao21/article/details/50688337

1、为什么要用虚函数?虚函数一般在编译器怎么实现多态?

2、纯虚函数

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
 virtual void funtion1()=0

含有纯虚函数的类称为抽象类,不能被实例化,子类可以实现也可不实现基类的纯虚函数,但是没有实现的类不能被实例化。

在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数

3、虚函数表

https://blog.csdn.net/yile0000/article/details/68944287

包含虚函数的类拥有一个虚函数表,类的所有对象公用一个虚函数表,但每个对象都有自己的虚函数表指针 vptr(指针位于对象实例的最前面)。虚函数表内存放的事虚函数的地址。

  • 单继承:派生类会自己产生一个兼容基类虚函数表的属于自己的虚函数表
  • 多继承:有多个虚函数表
  • 虚继承:除了每个基类(MyClassA和MyClassB)和公共基类(MyClass)的虚函数表指针需要记录外,每个虚拟继承了MyClass的父类还需要记录一个虚基类表vbtable的指针vbptr

4、虚函数表是在什么时期建立的?

在C++中,virtual functions(可经由其class object被调用)可以在编译时期获知。此外,这一组地址是固定不变的,执行期不可能新增或替换之。由于程序执行时,表格的大小和内容都不会改变,所以其建构和存取皆可以由编译器完全掌控,不需要执行期的任何介入。

 

 

3、vector的相关内容

1、vector及new vector的区别:

vector、map等通过stl的allocator进行内存分配的,所以其内部数据都分配在堆上,而vector本身并不是大。用vector及 new vector并没有太大的影响。前者是用栈中的vector管理堆中的一片内存,而后者是用堆中的vector管理堆中的一片内存而已。

2、vector源码剖析:

vector中的数据只有三个迭代器

protect:
iterator start;//表示目前使用空间的头部
iterator finish;//表示目前使用空间的尾部
iterator end_of_storage;//表示目前可用空间的尾部

运用这三个迭代器便可以轻松实现首位标识、大小、容量、是否为空、[]运算、最前端元素值、最后端元素值等。

gcc下,sizeof(vector<T>)=12。

3、vector的构造和内存管理:

vector初始分配指定大小的内存

vector<int> test(2,9);//size=2;capacity=2;

当元素个数大于内存的空间时,以原来大小的两倍另外分配一块较大的空间:

test.push_back(1);//size=3;capacity=4;
test.push_back(2);//size=4;capacity=4;
test.push_back(3);//size=5;capacity=8;

 

 

4、static关键词

http://www.cnblogs.com/stoneJin/archive/2011/09/21/2183313.html

https://blog.csdn.net/dqjyong/article/details/7976735

  • 1、先来介绍它的第一条也是最重要的一条:隐藏

当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。而static修饰的,就会对其它源文件隐藏。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏.

  • 2、保持变量内容的持久。

存放在静态数据区,在程序的整个运行期间都有效,且仅被初始化一次

全局变量也存放在静态数据区,与static变量的区别就是可访问域的不同,被static修饰的仅在当前问价可见。

  • 3、默认初始化为0(static变量)

其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00

  • 4、static的第四个作用:C++中的类成员声明static

在类中声明static变量或者函数时,初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员,这样就出现以下作用:

(1)类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致 了它仅能访问类的静态数据和静态成员函数

(2)不能将静态成员函数定义为虚函数。  

(3)静态数据成员是静态存储的,所以必须对它进行初始化。 (程序员手动初始化,否则编译时一般不会报错,但是在Link时会报错误)     

(4)静态成员初始化与一般数据成员初始化不同:

初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆;
初始化时不加该成员的访问权限控制符private,public等;        
初始化时使用作用域运算符来标明它所属类;
           所以我们得出静态数据成员初始化的格式:
<数据类型><类名>::<静态数据成员名>=<值> 

(5)由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊 ,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember函数指针”。

(6)由于静态成员函数没有this指针,所以就差不多等同于nonmember函数,结果就 产生了一个意想不到的好处:成为一个callback函数,使得我们得以将C++和C-based X W indow系统结合,同时也成功的应用于线程函数身上。 (这条没遇见过)  

(7)static并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问 时间,节省了子类的内存空间。            

(8)为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志

总之:

与全局变量比,全局变量也存放在静态数据区,与static变量的区别就是可访问域的不同,被static修饰的仅在当前问价可见。

修饰局部变量,改变了它的存储方式即改变了它的生存期

修饰函数,限制其只能在其文件下可见。

 

5、const关键词

修饰常量:以*/&为界,在左边则为修饰所指向的变量,在右边则修饰指针或是引用;

  1. 用于定义常量,在定义该const关键字时,通常要对它进行初始化,因为以后再也没有机会去改变它;(相比#define有很多的优点)
  2. const修饰函数形参:当函数参数进行指针传递或引用传递时,加const修饰可防止意外修对应内存单元,起到保护作用(对值传递的参数添加const没多大用处)。{const只能用于修饰输入参数。(因为输出形参若加上const则不能被修改,从而使函数失去了输出的功能)}
  3. const修饰函数的返回类型:返回值不能被直接修改,且该返回值只能被赋值给加const修饰的同类型指针
  4. 修饰指针、指针指向的对象等(引用):对于指针来说,可以指定指针本省为const,也可以指定指针所指向的数据为const,或者二者同时指定为const。
  5. const修饰类的成员函数:则表明其实一个常函数,不能修改类的成员变量
  6. 修饰类成员函数的返回值:对于类的成员函数,有时候必须制定其返回值为const,以使得其返回值不能为左值

 

6、volatile

https://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777432.html

 volatile 关键字和 const 对应,用来修饰变量,通常用于建立语言级别的 memory barrier

 volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。例如:

volatile int i=10;
int a = i;
...
// 其他代码,并未明确告诉编译器,对 i 进行过操作
int b = i;

volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。

在多线程时,有些变量是用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值

 

7、define与const之间的区别

  1. const定义常量是有数据类型的,而#define却没有:这样const定义的常量编译器可以对其进行数据静态类型安全检查,而#define宏定义的常量却只是进行简单的字符替换,没有类型安全检查,且有时还会产生边际效应
  2. 有些调试程序可对const进行调试,但不对#define进行调试。
  3. 当定义局部变量时,const作用域仅限于定义局部变量的函数体内。但用#define时其作用域不仅限于定义局部变量的函数体内,而是从定义点到整个程序的结束点。但也可以用#undef取消其定义从而限定其作用域范围。只用const定义常量,并不能起到其强大的作用。const还可修饰函数形式参数、返回值和类的成员函数等。从而提高函数的健壮性。因为const修饰的东西能受到c/c++的静态类型安全检查机制的强制保护,防止意外的修改。

 

8、define与inline之间的区别

9、C/C++程序编译链接的过程

 

 

 

 

10、类型萃取

https://www.cnblogs.com/youxin/p/3304394.html侯捷类型萃取

https://blog.csdn.net/Dawn_sf/article/details/70038126

类型萃取使用模板技术来萃取类型(包含自定义类型和内置类型)的某些特性,用以判断该类型是否含有某些特性,从而在泛型算法中来对该类型进行特殊的处理用来提高效率或者其他。类型萃取依靠的就是 模板的特化 。模板特化分为两种: 全特化和偏特化 。(对于某些类型进行不同的处理,比如template <T> f(T),对于T可以为int,double等类型使用,但是对于某种类型,如指针该函数不适用,需要进行特殊的处理,此时就可以用模板的特化功能来实现)。

// 类模板
template <class T1, class T2>
class A{
    T1 data1;
    T2 data2;
};

// 函数模板
template <class T>
T max(const T lhs, const T rhs){   
    return lhs > rhs ? lhs : rhs;
}
  • 全特化: 模板参数全部赋值为指定类型
// 全特化类模板
template <>
class A<int, double>{
    int data1;
    double data2;
};

// 函数模板
template <>
int max(const int lhs, const int rhs){   
    return lhs > rhs ? lhs : rhs;
}
  • 偏特化:模板参数只有部分赋值为指定类型,注意函数只有全特化,没有偏特化,但是函数允许重载,重载另一个函数模板就相当于进行了偏特化
template <class T2>
class A<int, T2>{
    ...
};
  • 注意函数偏特化时的起义问题:
template <class T>
void f(){ T d; }

template <>
void f(){ int d; }

此时编译器不知道f()是从f<T>()特化来的,编译时会有错误,这时我们便需要显式指定”模板实参”:

template <class T>
void f(){ T d; }

template <>
void f<int>(){ int d; }

 

 

 

 

智能指针

构造函数和析构函数的调用顺序

引用


 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值