C++面试常见题

1.什么是虚函数?什么是纯虚函数?

虚函数是容许被其子类从新定义的成员函数。html

虚函数的声明:virtual returntype func(parameter);引入虚函数的目的是为了动态绑定;ios

纯虚函数声明:virtual returntype func(parameter)=0;引入纯虚函数是为了派生接口。(使派生类仅仅只是继承函数的接口)c++

2.基类为何须要虚析构函数?

防止内存泄漏。想去借助父类指针去销毁子类对象的时候,不能去销毁子类对象。假如没有虚析构函数,释放一个由基类指针指向的派生类对象时,不会触发动态绑定,则只会调用基类的析构函数,不会调用派生类的。派生类中申请的空间则得不到释放致使内存泄漏。程序员

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

几乎同样。i++返回的是i的值,++i返回的是i+1的值,即++i是一个肯定的值,是一个能够修改的左值。算法

4.vector的reserve和capacity的区别?

reserve()用于让容器预留空间,避免再次分配内存;capacity()返回在从新进行分配之前所能容纳的元素数量。编程

5.如何初始化const和static数据成员?

一般在类外申明static成员,可是static const的整型(bool,char,int,long)能够在类中声明且初始化,static const的其余类型必须在类外初始化(包括整型数组)。数组

6.static 和const分别怎么用,类里面static和const能够同时修饰成员函数吗?

static的做用:安全

对变量:数据结构

1.局部变量:socket

在局部变量以前加上关键字static,局部变量就被定义成为一个局部静态变量。

  1)内存中的位置:静态存储区

  2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)

  3)做用域:做用域仍为局部做用域,当定义它的函数或者语句块结束的时候,做用域随之结束。

 注:当static用来修饰局部变量的时候,它就改变了局部变量的存储位置(从原来的栈中存放改成静态存储区)及其生命周期(局部静态变量在离开做用域以后,并无被销毁,而是仍然驻留在内存当中,直到程序结束,只不过咱们不能再对他进行访问),但未改变其做用域。

2.全局变量

在全局变量以前加上关键字static,全局变量就被定义成为一个全局静态变量。

 1)内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在)

 2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)

 3)做用域:全局静态变量在声明他的文件以外是不可见的。准确地讲从定义之处开始到文件结尾。

注:static修饰全局变量,并未改变其存储位置及生命周期,而是改变了其做用域,使当前文件外的源文件没法访问该变量,好处以下:(1)不会被其余文件所访问,修改(2)其余文件中可使用相同名字的变量,不会发生冲突。对全局函数也是有隐藏做用。而普通全局变量只要定义了,任何地方都能使用,使用前须要声明全部的.c文件,只能定义一次普通全局变量,可是能够声明屡次(外部连接)。注意:全局变量的做用域是全局范围,可是在某个文件中使用时,必须先声明。

 对类中的:

 1.成员变量

   用static修饰类的数据成员实际使其成为类的全局变量,会被类的全部对象共享,包括派生类的对象。所以,static成员必须在类外进行初始化(初始化格式: int base::var=10;),而不能在构造函数内进行初始化,不过也能够用const修饰static数据成员在类内初始化 。由于静态成员属于整个类,而不属于某个对象,若是在类内初始化,会致使每一个对象都包含该静态成员,这是矛盾的。

        特色:

         1.不要试图在头文件中定义(初始化)静态数据成员。在大多数的状况下,这样作会引发重复定义这样的错误。即便加上#ifndef #define #endif或者#pragma once也不行。 

         2.静态数据成员能够成为成员函数的可选参数,而普通数据成员则不能够。

         3.静态数据成员的类型能够是所属类的类型,而普通数据成员则不能够。普通数据成员的只能声明为 所属类类型的指针或引用。

    2.成员函数

  1. 用static修饰成员函数,使这个类只存在这一份函数,全部对象共享该函数,不含this指针。
  2. 静态成员是能够独立访问的,也就是说,无须建立任何对象实例就能够访问。base::func(5,3);当static成员函数在类外定义时不须要加static修饰符。
  3. 在静态成员函数的实现中不能直接引用类中说明的非静态成员,能够引用类中说明的静态成员。由于静态成员函数不含this指针。 

不能够同时用const和static修饰成员函数。

C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时const的用法和static是冲突的。

咱们也能够这样理解:二者的语意是矛盾的。static的做用是表示该函数只做用在类型的静态变量上,与类的实例没有关系;而const的做用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。所以不能同时用它们。

const的做用:

1.限定变量为不可修改。

2.限定成员函数不能够修改任何数据成员。

3.const与指针:

const char *p 表示 指向的内容不能改变。

char * const p,就是将P声明为常指针,它的地址不能改变,是固定的,可是它的内容能够改变。

7.指针和引用的区别

本质上的区别是,指针是一个新的变量,只是这个变量存储的是另外一个变量的地址,咱们经过访问这个地址来修改变量。

而引用只是一个别名,仍是变量自己。对引用进行的任何操做就是对变量自己进行操做,所以以达到修改变量的目的。

注:

(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。如:
int a=1;int *p=&a;
int a=1;int &b=a;
上面定义了一个整形变量和一个指针变量p,该指针变量指向a的存储单元,即p的值是a存储单元的地址。

而下面2句定义了一个整形变量a和这个整形a的引用b,事实上a和b是同一个东西,在内存占有同一个存储单元。

(2)能够有const指针,可是没有const引用(const引用可读不可改,与绑定对象是否为const无关)

注:引用能够指向常量,也能够指向变量。例如int &a=b,使引用a指向变量b。而为了让引用指向常量,必须使用常量引用,如const int &a=1; 它表明的是引用a指向一个const int型,这个int型的值不能被改变,而不是引用a的指向不能被改变,由于引用的指向原本就是不可变的,无需加const声明。即指针存在常量指针int const *p和指针常量int *const p,而引用只存在常量引用int const &a,不存在引用常量int& const a。

(3)指针能够有多级,可是引用只能是一级(int **p;合法 而 int &&a是不合法的)

(4)指针的值能够为空,可是引用的值不能为NULL,而且引用在定义的时候必须初始化;

(5)指针的值在初始化后能够改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。

(6)"sizeof引用"获得的是所指向的变量(对象)的大小,而"sizeof指针"获得的是指针自己的大小;

(7)指针和引用的自增(++)运算意义不同;

(8)指针使用时须要解引用(*),引用则不须要;

8.什么是多态?多态有什么用途?

C++ 多态有两种:静态多态(早绑定)、动态多态(晚绑定)。静态多态是经过函数重载实现的;动态多态是经过虚函数实现的。

1.定义:“一个接口,多种方法”,程序在运行时才决定要调用的函数。

2.实现:C++多态性主要是经过虚函数实现的,虚函数容许子类重写override(注意和overload的区别,overload是重载,是容许同名函数的表现,这些函数参数列表/类型不一样)。

注:多态与非多态的实质区别就是函数地址是静态绑定仍是动态绑定。若是函数的调用在编译器编译期间就能够肯定函数的调用地址,并产生代码,说明地址是静态绑定的;若是函数调用的地址是 须要在运行期间才肯定,属于动态绑定。

3.目的:接口重用。封装可使得代码模块化,继承能够扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。

4.用法:声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,能够根据指向的子类的不一样而实现不一样的方法。

用一句话归纳:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。若是对象类型是派生类,就调用派生类的函数;若是对象类型是基类,就调用基类的函数。

关于重载、重写、隐藏的区别

Overload(重载):在C++程序中,能够将语义、功能类似的几个函数用同一个名字表示,但参数或返回值不一样(包括类型、顺序不一样),即函数重载。
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不一样;
(4virtual 关键字无关紧要。

Override(覆盖或重写):是指派生类函数覆盖基类函数,特征是:
(1)不一样的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
注:重写基类虚函数的时候,会自动转换这个函数为virtual函数,无论有没有加virtual,所以重写的时候不加virtual也是能够的,不过为了易读性,仍是加上比较好。

Overwrite(重写):隐藏,是指派生类的函数屏蔽了与其同名的基类函数,规则以下:
(1)若是派生类的函数与基类的函数同名,可是参数不一样。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)若是派生类的函数与基类的函数同名,而且参数也相同,可是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

虚函数表:

详细解释能够参考博客:https://www.cnblogs.com/jin521/p/5602190.html

多态是由虚函数实现的,而虚函数主要是经过虚函数表(V-Table)来实现的。

若是一个类中包含虚函数(virtual修饰的函数),那么这个类就会包含一张虚函数表,虚函数表存储的每一项是一个虚函数的地址。以下图:

这个类的每个对象都会包含一个虚指针(虚指针存在于对象实例地址的最前面,保证虚函数表有最高的性能),这个虚指针指向虚函数表。

注:对象不包含虚函数表,只有虚指针,类才包含虚函数表,派生类会生成一个兼容基类的虚函数表。

 

  • 原始基类的虚函数表

          下图是原始基类的对象,能够看到虚指针在地址的最前面,指向基类的虚函数表(假设基类定义了3个虚函数)

           

  • 单继承时的虚函数(无重写基类虚函数)

     假设如今派生类继承基类,而且从新定义了3个虚函数,派生类会本身产生一个兼容基类虚函数表的属于本身的虚函数表

     

    Derive Class继承了Base Class中的3个虚函数,准确说是该函数的实体地址被拷贝到Derive Class的虚函数列表中,派生新增的虚函数置于虚函数列表后面,并按声明顺序摆放。

  • 单继承时的虚函数(重写基类虚函数)

     如今派生类重写基类的x函数,能够看到这个派生类构建本身的虚函数表的时候,修改了base::x()这一项,指向了本身的虚函数。

      

  • 多重继承时的虚函数(class Derived :public Base1,public Base2)

     这个派生类多重继承了两个基类base1,base2,所以它有两个虚函数表。

     

    它的对象会有多个虚指针(听说和编译器相关),指向不一样的虚函数表。

          注:有关以上虚函数表等详见c++对象模型。连接地址:http://www.javashuo.com/article/p-wkwpgphp-gb.html

 

纯虚函数:

  定义: 在不少状况下,基类自己生成对象是不合情理的。为了解决这个问题,方便使用类的多态性,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;)纯虚函数不能再在基类中实现,编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。称带有纯虚函数的类为抽象类。

  特色:

  1,当想在基类中抽象出一个方法,且该基类只作能被继承,而不能被实例化;(避免类被实例化且在编译时候被发现,能够采用此方法)

  2,这个方法必须在派生类(derived class)中被实现;

  目的:使派生类仅仅只是继承函数的接口。

9.vector中size()和capacity()的区别。

size()指容器当前拥有的元素个数(对应的resize(size_type)会在容器尾添加或删除一些元素,来调整容器中实际的内容,使容器达到指定的大小。);capacity()指容器在必须分配存储空间以前能够存储的元素总数。

size表示的这个vector里容纳了多少个元素,capacity表示vector可以容纳多少元素,它们的不一样是在于vector的size是2倍增加的。若是vector的大小不够了,好比如今的capacity是4,插入到第五个元素的时候,发现不够了,此时会给他从新分配8个空间,把原来的数据及新的数据复制到这个新分配的空间里。(会有迭代器失效的问题)

10.new和malloc的区别。

       详细参考:连接

  • new是运算符,malloc()是一个库函数
  • new会调用构造函数,malloc不会;
  • new返回指定类型指针,malloc返回void*指针,须要强制类型转换;
  • new会自动计算需分配的空间,malloc不行;
  • new能够被重载,malloc不能。

11.C++的内存分区

  • 栈区(stack):主要存放函数参数以及局部变量,由系统自动分配释放。
  • 堆区(heap):由用户经过 malloc/new 手动申请,手动释放。注意它与数据结构中的堆是两回事,分配方式却是相似于链表。
  • 全局/静态区:存放全局变量、静态变量;程序结束后由系统释放。
  • 字符串常量区:字符串常量就放在这里,程序结束后由系统释放。
  • 代码区:存放程序的二进制代码。

C++内存管理详细连接

12.vector、map、multimap、unordered_map、unordered_multimap的底层数据结构,以及几种map容器如何选择?

底层数据结构

  • vector基于数组,map、multimap基于红黑树,unordered_map、unordered_multimap基于哈希表

根据应用场景进行选择:

  • map/unordered_map 不容许重复元素
  • multimap/unordered_multimap 容许重复元素
  • map/multimap 底层基于红黑树,元素自动有序,且插入、删除效率高
  • unordered_map/unordered_multimap 底层基于哈希表,故元素无序,查找效率高。

13.内存泄漏怎么产生的?如何避免?

  • 内存泄漏通常是指堆内存的泄漏,也就是程序在运行过程当中动态申请的内存空间再也不使用后没有及时释放,致使那块内存不能被再次使用。
  • 更广义的内存泄漏还包括未对系统资源的及时释放,好比句柄、socket等没有使用相应的函数释放掉,致使系统资源的浪费。

VS下检测内存泄漏方法:

#define CRTDBG_MAP_ALLOC    
#include <stdlib.h>    
#include <crtdbg.h>    
//在入口函数中包含 _CrtDumpMemoryLeaks();    
//便可检测到内存泄露  

//以以下测试函数为例:  
int main()
{
    char* pChars = new char[10];
    //delete[]pChars;
    _CrtDumpMemoryLeaks();
    system("pause");
    return 0;
}

解决方法:

  • 养成良好的编码习惯和规范,记得及时释放掉内存或系统资源。
  • 重载new和delete,以链表的形式自动管理分配的内存。
  • 使用智能指针,share_ptr、auto_ptr、weak_ptr。

14.说几个C++11的新特性

  • auto类型推导
  • 范围for循环
  • lambda函数
  • override 和 final 关键字
  • /*若是不使用override,当你手一抖,将foo()写成了f00()会怎么样呢?结果是编译器并不会报错,由于它并不知道你的目的是重写虚函数,而是把它当成了新的函数。若是这个虚函数很重要的话,那就会对整个程序不利。
    
      因此,override的做用就出来了,它指定了子类的这个虚函数是重写的父类的,若是你名字不当心打错了的话,编译器是不会编译经过的:*/
    class A
    {
        virtual void foo();
    }
    class B :public A
    {
        void foo(); //OK
        virtual foo(); // OK
        void foo() override; //OK
    }
    
    class A
    {
        virtual void foo();
    };
    class B :A
    {
        virtual void f00(); //OK
        virtual void f0o()override; //Error 
    };
    /*当不但愿某个类被继承,或不但愿某个虚函数被重写,能够在类名和虚函数后添加final关键字,添加final关键字后被继承或重写,编译器会报错。例子以下:*/
    
    class Base
    {
        virtual void foo();
    };
     
    class A : Base
    {
        void foo() final; // foo 被override而且是最后一个override,在其子类中不能够重写
        void bar() final; // Error: 父类中没有 bar虚函数能够被重写或final
    };
    
    class B final : A // 指明B是不能够被继承的
    {
        void foo() override; // Error: 在A中已经被final了
    };
     
    class C : B // Error: B is final
    {
    };
  • 空指针常量nullptr
  • 线程支持、智能指针等

15.C和C++区别?

C++在C的基础上增添类,C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何经过一个过程,对输入(或环境条件)进行运算处理获得输出(或实现过程(事务)控制),而对于C++,首要考虑的是如何构造一个对象模型,让这个模型可以契合与之对应的问题域,这样就能够经过获取对象的状态信息获得输出或实现过程(事务)控制。

16.const与#define的区别

1.编译器处理方式 
define – 在预处理阶段进行替换 
const – 在编译时肯定其值

2.类型检查 
define – 无类型,不进行类型安全检查,可能会产生意想不到的错误 
const – 有数据类型,编译时会进行类型检查

3.内存空间 
define – 不分配内存,给出的是当即数,有多少次使用就进行多少次替换,在内存中会有多个拷贝,消耗内存大 
const – 在静态存储区中分配空间,在程序运行过程当中内存中只有一个拷贝

4.其余 
在编译时, 编译器一般不为const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操做,使得它的效率也很高。 
宏替换只做替换,不作计算,不作表达式求解。

17.悬空指针与野指针区别

  • 悬空指针:当所指向的对象被释放或者收回,可是没有让指针指向NULL;
{
   char *dp = NULL;
   {
       char c;
       dp = &c;
   } 
  //变量c释放,dp变成空悬指针
}
void func()
{
    char *dp = (char *)malloc(A_CONST);
    free(dp);         //dp变成一个空悬指针
    dp = NULL;        //dp再也不是空悬指针
    /* ... */
}

 

  • 野指针:那些未初始化的指针;
int func()
{
    char *dp;//野指针,没有初始化
    static char *sdp;//非野指针,由于静态变量会默认初始化为0
}

18.struct与class的区别?

本质区别是访问的默认控制:默认的继承访问权限,class是private,struct是public;

19.sizeof和strlen的区别?

功能不一样:

sizeof是操做符,参数为任意类型,主要计算类型占用内存大小。

strlen()是函数,其函数原型为:extern unsigned int strlen(char *s);其参数为char*,strlen只能计算以"\0"结尾字符串的长度,计算结果不包括"\0"。

char* ss="0123456789";  
//s1=4,ss为字符指针在内存中占用4个字节  
int s1=sizeof(ss);  
//s2=10,计算字符串ss的长度  
int s2=strlen(ss);  

参数不一样:

当将字符数组做为sizeof()的参数时,计算字符数组占用内存大小;当将字符数组做为strlen()函数,字符数组转化为char*。由于sizeof的参数为任意类型,而strlen()函数参数只能为char*,当参数不是char*必须转换为char*。

char str[]="abced";  
     //a为6(1*6),字符数组str包含6个元素(a,b,c,d,e,\0),每一个元素占用1个字节  
    int a= sizeof(str);  
    //len为5,不包含"\0",  
    int len=strlen(str);  
    //str[0]是字符元素a,因此b=1  
    int b= sizeof(str[0]);  

20.32位,64位系统中,各类经常使用内置数据类型占用的字节数?

char :1个字节(固定)

*(即指针变量): 4个字节(32位机的寻址空间是4个字节。同理64位编译器)(变化*)

short int : 2个字节(固定)

int: 4个字节(固定)

unsigned int : 4个字节(固定)

float: 4个字节(固定)

double: 8个字节(固定)

long: 4个字节

unsigned long: 4个字节(变化*,其实就是寻址控件的地址长度数值)

long long: 8个字节(固定)


64位操做系统

char :1个字节(固定)

*(即指针变量): 8个字节

short int : 2个字节(固定)

int: 4个字节(固定)

unsigned int : 4个字节(固定)

float: 4个字节(固定)

double: 8个字节(固定)

long: 8个字节

unsigned long: 8个字节(变化*其实就是寻址控件的地址长度数值)

long long: 8个字节(固定)

除*与long 不一样其他均相同。

21.virtual, inline, decltype,volatile,static, const关键字的做用?使用场景?

inline:在c/c++中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数。

#include <stdio.h>
//函数定义为inline即:内联函数
inline char* dbtest(int a) {
    return (i % 2 > 0) ? "" : "";
} 

int main()
{
   int i = 0;
   for (i=1; i < 100; i++) {
       printf("i:%d    奇偶性:%s /n", i, dbtest(i));    
   }
}//在for循环的每一个dbtest(i)的地方替换成了 (i % 2 > 0) ? "奇" : "偶",避免了频繁调用函数,对栈内存的消耗

decltype:从表达式中推断出要定义变量的类型,但却不想用表达式的值去初始化变量。还有多是函数的返回类型为某表达式的的值类型。

volatile:volatile 关键字是一种类型修饰符,用它声明的类型变量表示能够被某些编译器未知的因素更改,好比:操做系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就再也不进行优化,从而能够提供对特殊地址的稳定访问。

static:

  1. 隐藏

      在变量和函数名前面若是未加static,则它们是全局可见的。加了static,就会对其它源文件隐藏,利用这一特性能够在不一样的文件中定义同名函数和同名变量,而没必要担忧命名冲  突。static能够用做函数和变量的前缀,对于函数来说,static的做用仅限于隐藏 。

    2.static变量中的记忆功能和全局生存期

      存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是惟一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static能够控制变量的可见范围,说到底static仍是用来隐藏的。PS:若是做为static局部变量在函数内定义,它的生存期为整个源程序,可是其做用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。

#include <stdio.h>

int fun(){
    static int count = 10;       //在第一次进入这个函数的时候,变量a被初始化为10!并接着自减1,之后每次进入该函数,a
    return count--;              //就不会被再次初始化了,仅进行自减1的操做;在static发明前,要达到一样的功能,则只能使用全局变量:    

}

int count = 1;

int main(void)
{
     printf("global\t\tlocal static\n");
     for(; count <= 10; ++count)
               printf("%d\t\t%d\n", count, fun());
     return 0;
}

---基于以上两点能够得出一个结论:把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的做用域, 限制了它的使用范围。所以static 这个说明符在不一样的地方所起的做用是不一样的。

   3.static的第三个做用是默认初始化为0(static变量)

最后对static的三条做用作一句话总结。首先static的最主要功能是隐藏,其次由于static变量存放在静态存储区,因此它具有持久性和默认值0。

  4.static的第四个做用:C++中的类成员声明static(有些地方与以上做用重叠)

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

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

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

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

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

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

(6)静态数据成员在<定义或说明>时前面加关键字static。      

(7)静态数据成员是静态存储的,因此必须对它进行初始化。 (程序员手动初始化,不然编译时通常不会报错,可是在Link时会报错误)     

(8)静态成员初始化与通常数据成员初始化不一样:

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

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

22.深拷贝与浅拷贝的区别?

1.何时用到拷贝函数?

  a.一个对象以值传递的方式传入函数体; 
  b.一个对象以值传递的方式从函数返回;
  c.一个对象须要经过另一个对象进行初始化。

  若是在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝;

2.是否应该自定义拷贝函数?

 自定义拷贝构造函数是一种良好的编程风格,它能够阻止编译器造成默认的拷贝构造函数,提升源码效率。

3.什么叫深拷贝?什么是浅拷贝?二者异同?

  若是一个类拥有资源,当这个类的对象发生复制过程的时候,资源从新分配,这个过程就是深拷贝,反之,没有从新分配资源,就是浅拷贝。

4.深拷贝好仍是浅拷贝好?

若是实行位拷贝,也就是把对象里的值彻底复制给另外一个对象,如A=B。这时,若是B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。

参考博客:https://blog.csdn.net/caoshangpa/article/details/79226270

                  http://www.cnblogs.com/BlueTzar/articles/1223313.html

23.派生类中构造函数,析构函数调用顺序?

构造函数:“先基后派”;析构函数:“先派后基”。

24.C++类中数据成员初始化顺序?

1.成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。

2.若是不使用初始化列表初始化,在构造函数内初始化时,此时与成员变量在构造函数中的位置有关。

3.类中const成员常量必须在构造函数初始化列表中初始化。

4.类中static成员变量,只能在类内外初始化(同一类的全部实例共享静态成员变量)。

初始化顺序:

  • 1) 基类的静态变量或全局变量
  • 2) 派生类的静态变量或全局变量
  • 3) 基类的成员变量
  • 4) 派生类的成员变量

25.结构体内存对齐问题?结构体/类大小的计算?

注:内存对齐是看类型,而不是看总的字节数。好比:

#include<iostream>
using namespace std;

struct AlignData1
{
    int a;
    char b[7];//a后面并不会补上3个字节,而是因为char的类型因此不用补。
    short c;
    char d;
}Node;
struct AlignData2
{
    bool a;
    int b[2];//a后面并不会补上7个字节,而是根据int的类型补3个字节。
    int c;
    int d;
}Node2;
int main(){
    cout << sizeof(Node) << endl;//16
    cout << sizeof(Node2) << endl;//20
    system("pause");
    return 0;
}

连接

补充:

  • 每一个成员相对于这个结构体变量地址的偏移量正好是该成员类型所占字节的整数倍。为了对齐数据,可能必须在上一个数据结束和下一个数据开始的地方插入一些没有用处字节。
  • 最终占用字节数为成员类型中最大占用字节数的整数倍
  • 通常的结构体成员按照默认对齐字节数递增或是递减的顺序排放,会使总的填充字节数最少。
struct AlignData1
{
    char c;
    short b;
    int i;
    char d;
}Node;

这个结构体在编译之后,为了字节对齐,会被整理成这个样子:
struct AlignData1
{
    char c;
    char padding[1];
    short b;
    int i;
    char d;
    char padding[3];
}Node;

 含有虚函数的类的大小:连接

补充:联合体的大小计算:

联合体所占的空间不只取决于最宽成员,还跟全部成员有关系,即其大小必须知足两个条件:1)大小足够容纳最宽的成员;2)大小能被其包含的全部基本数据类型的大小所整除。

union U1  
{  
    int n;  
    char s[11];  
    double d;  
};  //16,char s[11]按照char=1能够整除
  
union U2  
{  
    int n;  
    char s[5];  
    double d;  
};  //8
  

 

26.static_cast, dynamic_cast, const_cast, reinpreter_cast的区别? 

连接

 补充:static_cast与dynamic_cast

  • cast发生的时间不一样,一个是static编译时,一个是runtime运行时
  • static_cast是至关于C的强制类型转换,用起来可能有一点危险,不提供运行时的检查来确保转换的安全性。
  • dynamic_cast用于转换指针和和引用不能用来转换对象 ——主要用于类层次间的上行转换和下行转换,还能够用于类之间的交叉转换。在类层次间进行上行转换时,dynamic_cast和static_cast的效果是同样的;在进行下行转换时,dynamic_cast具备类型检查的功能,比static_cast更安全。在多态类型之间的转换主要使用dynamic_cast,由于类型提供了运行时信息
#include <iostream> 
using namespace std; 
class CBasic 
{ 
public: 
     virtual int test(){return 0;} 
}; 

class CDerived : public CBasic 
{ 
public: 
     virtual int test(){ return 1;} 
}; 
  
int main() 
{ 
     CBasic        cBasic; 
     CDerived    cDerived; 
     CBasic * pB1 = new CBasic; 
     CBasic * pB2 = new CDerived;
     CBasic * pB3 = new CBasic;
     CBasic * pB4 = new CDerived;
     
     
     //dynamic cast failed, so pD1 is null. 
     CDerived * pD1 = dynamic_cast<CDerived * > (pB1);    
                  
     //dynamic cast succeeded, so pD2 points to  CDerived object                                         
     CDerived * pD2 = dynamic_cast<CDerived * > (pB2);
     //pD3将是一个指向该CBasic类型对象的指针,对它进行CDerive类型的操做将是不安全的
     CDerived * pD3 = static_cast<CDerived * > (pB3);     
     //static_cast成功
     CDerived * pD4 = static_cast<CDerived * > (pB4);      
      
     //dynamci cast failed, so throw an exception.             
//    CDerived & rD1 = dynamic_cast<CDerived &> (*pB1);    
  
//dynamic cast succeeded, so rD2 references to CDerived object. 
     CDerived & rD2 = dynamic_cast<CDerived &> (*pB2);    
  
     return 0; 
} 

注:CBasic要有虚函数,不然会编译出错;static_cast则没有这个限制。

27.智能指针

  • 智能指针是在 <memory> 头文件中的std命名空间中定义的,该指针用于确保程序不存在内存和资源泄漏且是异常安全的。它们对RAII“获取资源即初始化”编程相当重要,RAII的主要原则是为将任何堆分配资源(如动态分配内存或系统对象句柄)的全部权提供给其析构函数包含用于删除或释放资源的代码以及任何相关清理代码的堆栈分配对象。大多数状况下,当初始化原始指针或资源句柄以指向实际资源时,会当即将指针传递给智能指针。
  • 智能指针的设计思想:将基本类型指针封装为类对象指针(这个类确定是个模板,以适应不一样基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。
  • unique_ptr只容许基础指针的一个全部者。unique_ptr小巧高效;大小等同于一个指针且支持右值引用,从而可实现快速插入和对STL集合的检索。
  • shared_ptr采用引用计数的智能指针,主要用于要将一个原始指针分配给多个全部者(例如,从容器返回了指针副本又想保留原始指针时)的状况。当全部的shared_ptr全部者超出了范围或放弃全部权,才会删除原始指针。大小为两个指针;一个用于对象,另外一个用于包含引用计数的共享控制块。最安全的分配和使用动态内存的方法是调用make_shared标准库函数,此函数在动态分配内存中分配一个对象并初始化它,返回对象的shared_ptr。

连接

28.计算类大小例子

class A {};: sizeof(A) = 1;
class A { virtual Fun(){} };: sizeof(A) = 4(32位机器)/8(64位机器);
class A { static int a; };: sizeof(A) = 1;
class A { int a; };: sizeof(A) = 4;
class A { static int a; int b; };: sizeof(A) = 4;

类中用static声明的成员变量不计算入类的大小中,由于static data不是实例的一部分。static的属于全局的,他不会占用类的存储,他有专门的地方存储 (全局变量区)

29.大端与小端的概念?各自的优点是什么?

  • 大端与小端是用来描述多字节数据在内存中的存放顺序,即字节序。大端(Big Endian)指低地址端存放高位字节,小端(Little Endian)是指低地址端存放低位字节。
  • 须要记住计算机是以字节为存储单位
  • 为了方便记忆可把大端和小端称做高尾端和低尾端,eg:若是是高尾端模式一个字符串“11223344”把尾部“44”放在地址的高位,若是是地尾端模式,把“44”放在地址的低位。

各自优点:

  • Big Endian:符号位的断定固定为第一个字节,容易判断正负。
  • Little Endian:长度为1,2,4字节的数,排列方式都是同样的,数据类型转换很是方便。

举一个例子,好比数字0x12 34 56 78在内存中的表示形式为:

  • 1)大端模式:

低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78

  • 2)小端模式:

低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12

30.C++中*和&同时使用是什么意思?

template <class T>
void InsertFront(Node<T>* & head, T item)
上面一个函数的声明,其中第一个参数*和&分别是什么意思?
head是个指针,前面为何加个&

原本“* head”表明的是传指针的,可是只能改变head指向的内容,而“* &head”意思是说head是传进来的指针的同名指针,就能既改变*head指向的内容,又能改变head这个指针。好比:main()有个Node<int>* p,int t;当调用insertFront(p,t)是,若是template <class T> void InsertFront(Node<T>* & head, T item)中有对head进行赋值改变时,main()中的p也会跟着改变,若是没有&这个别名标识时,p则不会随着head的改变而改变。

31.C++vector与list区别

http://www.javashuo.com/article/p-qvvgctsh-eg.html

32.C语言中static关键字做用

C语言中static的做用以下

第1、在修饰变量的时候,static修饰的静态局部变量只执行一次,并且延长了局部变量的生命周期,直到程序运行结束之后才释放。 
第2、static修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即使是extern外部声明也不能够。 
第3、static修饰一个函数,则这个函数的只能在本文件中调用,不能被其余文件调用。Static修饰的局部变量存放在全局数据区的静态变量区。初始化的时候自动初始化为0; 
(1)不想被释放的时候,可使用static修饰。好比修饰函数中存放在栈空间的数组。若是不想让这个数组在函数调用结束释放可使用static修饰 
(2)考虑到数据安全性(当程想要使用全局变量的时候应该先考虑使用static)


在C++中static关键字除了具备C中的做用还有在类中的使用 
在类中,static能够用来修饰静态数据成员和静态成员方法 
静态数据成员 
(1)静态数据成员能够实现多个对象之间的数据共享,它是类的全部对象的共享成员,它在内存中只占一份空间,若是改变它的值,则各对象中这个数据成员的值都被改变。 
(2)静态数据成员是在程序开始运行时被分配空间,到程序结束以后才释放,只要类中指定了静态数据成员,即便不定义对象,也会为静态数据成员分配空间。 
(3)静态数据成员能够被初始化,可是只能在类体外进行初始化,若为对静态数据成员赋初值,则编译器会自动为其初始化为0 
(4)静态数据成员既能够经过对象名引用,也能够经过类名引用。

静态成员函数 
(1)静态成员函数和静态数据成员同样,他们都属于类的静态成员,而不是对象成员。 
(2)非静态成员函数有this指针,而静态成员函数没有this指针。 
(3)静态成员函数主要用来访问静态数据成员而不能访问非静态成员。

33.C/C++中堆和栈的区别

讲解全面的一篇博客:http://www.javashuo.com/article/p-kqoifllz-hp.html

连接1

连接2

34.定义一个空类编译器作了哪些操做?

若是你只是声明一个空类,不作任何事情的话,编译器会自动为你生成一个默认构造函数、一个拷贝默认构造函数、一个默认拷贝赋值操做符和一个默认析构函数。这些函数只有在第一次被调用时,才会被编译器建立。全部这些函数都是inline和public的。

定义一个空类例如:

class Empty
{
}

一个空的class在C++编译器处理事后就再也不为空,编译器会自动地为咱们声明一些member function,通常编译过就至关于:

class Empty
{
public:
Empty(); // 缺省构造函数//
Empty( const Empty& ); // 拷贝构造函数//
~Empty(); // 析构函数//
Empty& operator=( const Empty& ); // 赋值运算符//
};

须要注意的是,只有当你须要用到这些函数的时候,编译器才会去定义它们。

35.友元函数和友元类

连接

36.什么状况下,类的析构函数应该声明为虚函数?为何?

基类指针能够指向派生类的对象(多态性),若是删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象彻底被释放。

若是析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会形成派生类对象析构不彻底。

37.哪些函数不能成为虚函数?

不能被继承的函数和不能被重写的函数。

1)普通函数

普通函数不属于成员函数,是不能被继承的。普通函数只能被重载,不能被重写,所以声明为虚函数没有意义。由于编译器会在编译时绑定函数。

而多态体如今运行时绑定。一般经过基类指针指向子类对象实现多态。

2)友元函数

友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。

3)构造函数

首先说下什么是构造函数,构造函数是用来初始化对象的。假如子类能够继承基类构造函数,那么子类对象的构造将使用基类的构造函数,而基类构造函数并不知道子类的有什么成员,显然是不符合语义的。从另一个角度来说,多态是经过基类指针指向子类对象来实现多态的,在对象构造以前并无对象产生,所以没法使用多态特性,这是矛盾的。所以构造函数不容许继承。

4)内联成员函数

咱们须要知道内联函数就是为了在代码中直接展开,减小函数调用花费的代价。也就是说内联函数是在编译时展开的。而虚函数是为了实现多态,是在运行时绑定的。所以显然内联函数和多态的特性相违背。

5)静态成员函数

首先静态成员函数理论是可继承的。可是静态成员函数是编译时肯定的,没法动态绑定,不支持多态,所以不能被重写,也就不能被声明为虚函数。、

38.编写一个有构造函数,析构函数,赋值函数,和拷贝构造函数的String类

 
 
//.h
class String{
    public:
        String(const char* str);
        String(const String &other);
        ~String();
        String & operate=(const String &other);
    private:
         char* m_data;
};
//.cpp
String::String(const char*str){
    if(str==NULL){
        m_data=new char[1];
        *m_data='\0';
    }
    else{
        int length=strlen(str);
        m_data=new char[length+1];
        strcpy(m_data,str);
    }
}

String::String(const String &other){
    int length=strlen(other.m_data);
    m_data=new char[length+1];
    strcpy(m_data,other.m_data);
}

String::~String(){
    delete [] m_data;
}

String::String& operate=(const String & other){
    if(&other==*this)return *this;//检查自赋值
    delete[]m_data;//释放原有的内存资源
    int length=strlen(other.m_data);
    m_data=new char[length+1];
    strcpy(m_data,other.m_data);
    return *this;//返回本对象的引用
}

注:一个单链表的简单实现:连接 

39.this指针的理解

连接

40.程序加载时的内存分布

  • 在多任务操做系统中,每一个进程都运行在一个属于本身的虚拟内存中,而虚拟内存被分为许多页,并映射到物理内存中,被加载到物理内存中的文件才可以被执行。这里咱们主要关注程序被装载后的内存布局,其可执行文件包含了代码段,数据段,BSS段,堆,栈等部分,其分布以下图所示。

  • 代码段(.text):用来存放可执行文件的机器指令。存放在只读区域,以防止被修改。
  • 只读数据段(.rodata):用来存放常量存放在只读区域,如字符串常量、全局const变量等。
  • 可读写数据段(.data):用来存放可执行文件中已初始化全局变量,即静态分配的变量和全局变量。
  • BSS段(.bss):未初始化的全局变量和局部静态变量以及初始化为0的全局变量通常放在.bss的段里,以节省内存空间。eg:static int a=0;(初始化为0的全局变量(静态变量)放在.bss)。
  • :用来容纳应用程序动态分配的内存区域。当程序使用malloc或new分配内存时,获得的内存来自堆。堆一般位于栈的下方。
  • :用于维护函数调用的上下文。栈一般分配在用户空间的最高地址处分配。
  • 动态连接库映射区:若是程序调用了动态连接库,则会有这一部分。该区域是用于映射装载的动态连接库。
  • 保留区:内存中受到保护而禁止访问的内存区域。

41.智能指针

  • 智能指针是在 <memory> 头文件中的std命名空间中定义的,该指针用于确保程序不存在内存和资源泄漏且是异常安全的。它们对RAII“获取资源即初始化”编程相当重要,RAII的主要原则是为将任何堆分配资源(如动态分配内存或系统对象句柄)的全部权提供给其析构函数包含用于删除或释放资源的代码以及任何相关清理代码的堆栈分配对象。大多数状况下,当初始化原始指针或资源句柄以指向实际资源时,会当即将指针传递给智能指针。
  • 智能指针的设计思想:将基本类型指针封装为类对象指针(这个类确定是个模板,以适应不一样基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。
  • unique_ptr只容许基础指针的一个全部者。unique_ptr小巧高效;大小等同于一个指针且支持右值引用,从而可实现快速插入和对STL集合的检索。
  • shared_ptr采用引用计数的智能指针,主要用于要将一个原始指针分配给多个全部者(例如,从容器返回了指针副本又想保留原始指针时)的状况。当全部的shared_ptr全部者超出了范围或放弃全部权,才会删除原始指针。大小为两个指针;一个用于对象,另外一个用于包含引用计数的共享控制块。最安全的分配和使用动态内存的方法是调用make_shared标准库函数,此函数在动态分配内存中分配一个对象并初始化它,返回对象的shared_ptr。

参考:http://www.javashuo.com/article/p-mgvopjua-hu.html

1.引用计数问题

  • 每一个shared_ptr所指向的对象都有一个引用计数,它记录了有多少个shared_ptr指向本身
  • shared_ptr的析构函数:递减它所指向的对象的引用计数,若是引用计数变为0,就会销毁对象并释放相应的内存
  • 引用计数的变化:决定权在shared_ptr,而与对象自己无关

参考:http://www.javashuo.com/article/p-xixpwpyp-ck.html

  2.智能指针支持的操做

  • 使用重载的->和*运算符访问对象。
  • 使用get成员函数获取原始指针,提供对原始指针的直接访问。你可使用智能指针管理你本身的代码中的内存,还能将原始指针传递给不支持智能指针的代码。
  • 使用删除器定义本身的释放操做。
  • 使用release成员函数的做用是放弃智能指针对指针的控制权,将智能指针置空,并返回原始指针。(只支持unique_ptr)
  • 使用reset释放智能指针对对象的全部权。
#include <iostream>
#include <string>
#include <memory>
using namespace std;

class base
{
public:
    base(int _a): a(_a)    {cout<<"构造函数"<<endl;}
    ~base()    {cout<<"析构函数"<<endl;}
    int a;
};

int main()
{
    unique_ptr<base> up1(new base(2));
    // unique_ptr<base> up2 = up1;   //编译器提示未定义
    unique_ptr<base> up2 = move(up1);  //转移对象的全部权 
    // cout<<up1->a<<endl; //运行时错误 
    cout<<up2->a<<endl; //经过解引用运算符获取封装的原始指针 
    up2.reset(); // 显式释放内存 

    shared_ptr<base> sp1(new base(3));
    shared_ptr<base> sp2 = sp1;  //增长引用计数 
    cout<<"共享智能指针的数量:"<<sp2.use_count()<<endl;  //2
    sp1.reset();  //
    cout<<"共享智能指针的数量:"<<sp2.use_count()<<endl;  //1
    cout<<sp2->a<<endl; 
    auto sp3 = make_shared<base>(4);//利用make_shared函数动态分配内存 
}

   3.智能指针的陷阱(循环引用等问题)

        连接

class B;
class A
{
public:
  shared_ptr<B> m_b;
};
class B
{
public:
  shared_ptr<A> m_a;
};

int main()
{
  {
    shared_ptr<A> a(new A);  //new出来的A的引用计数此时为1
    shared_ptr<B> b(new B);  //new出来的B的引用计数此时为1
    a->m_b = b;              //B的引用计数增长为2
    b->m_a = a;              //A的引用计数增长为2
  }
  //b先出做用域,B的引用计数减小为1,不为0;
  //因此堆上的B空间没有被释放,且B持有的A也没有机会被析构,A的引用计数也彻底没减小

  //a后出做用域,同理A的引用计数减小为1,不为0,因此堆上A的空间也没有被释放
}

 循环引用”简单来讲就是:两个对象互相使用一个shared_ptr成员变量指向对方会形成循环引用。

    即A内部有指向B,B内部有指向A,这样对于A,B一定是在A析构后B才析构,对于B,A一定是在B析构后才析构A,这就是循环引用问题,违反常规,致使内存泄露。

   解决循环引用方法:

   1. 当只剩下最后一个引用的时候须要手动打破循环引用释放对象。
   2. 当A的生存期超过B的生存期的时候,B改成使用一个普通指针指向A。
   3. 使用weak_ptr打破这种循环引用,由于weak_ptr不会修改计数器的大小,因此就不会产生两个对象互相使用一个shared_ptr成员变量指向对方的问题,从而不会引发引用循环。

42.vector扩容原理说明

  • 新增元素:Vector经过一个连续的数组存放元素,若是集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放以前的内存,在插入新增的元素;
  • 对vector的任何操做,一旦引发空间从新配置,指向原vector的全部迭代器就都失效了 ;
  • 初始时刻vector的capacity为0,塞入第一个元素后capacity增长为1;
  • 不一样的编译器实现的扩容方式不同,VS2015中以1.5倍扩容,GCC以2倍扩容。
  1. vector在push_back以成倍增加能够在均摊后达到O(1)的事件复杂度,相对于增加指定大小的O(n)时间复杂度更好。
  2. 为了防止申请内存的浪费,如今使用较多的有2倍与1.5倍的增加方式,而1.5倍的增加方式能够更好的实现对内存的重复利用,由于更好。

参考连接:https://blog.csdn.net/yangshiziping/article/details/52550291

43.内联函数和宏定义的区别

1.宏定义不是函数,可是使用起来像函数。预处理器用复制宏代码的方式代替函数的调用,省去了函数压栈退栈过程,提升了效率。

   内联函数本质上是一个函数,内联函数通常用于函数体的代码比较简单的函数,不能包含复杂的控制语句,while、switch,而且内联函数自己不能直接调用自身。若是内联函数的函数体过大,编译器会自动      的把这个内联函数变成普通函数。

 

2. 宏定义是在预处理的时候把全部的宏名用宏体来替换,简单的说就是字符串替换

    内联函数则是在编译的时候进行代码插入,编译器会在每处调用内联函数的地方直接把内联函数的内容展开,这样能够省去函数的调用的开销,提升效率

 

3. 宏定义是没有类型检查的,不管对仍是错都是直接替换

    内联函数在编译的时候会进行类型的检查,内联函数知足函数的性质,好比有返回值、参数列表等

 

4. 宏定义和内联函数使用的时候都是进行代码展开。不一样的是宏定义是在预编译的时候把全部的宏名替换,内联函数则是在编译阶段把全部调用内联函数的地方把内联函数插入。这样能够省去函数压栈退栈,提升了效率

44.内联函数与普通函数的区别

1. 内联函数和普通函数的参数传递机制相同,可是编译器会在每处调用内联函数的地方将内联函数内容展开,这样既避免了函数调用的开销又没有宏机制的缺陷。

2. 普通函数在被调用的时候,系统首先要到函数的入口地址去执行函数体,执行完成以后再回到函数调用的地方继续执行,函数始终只有一个复制。

    内联函数不须要寻址,当执行到内联函数的时候,将此函数展开,若是程序中有N次调用了内联函数则会有N次展开函数代码。

3. 内联函数有必定的限制,内联函数体要求代码简单,不能包含复杂的结构控制语句。若是内联函数函数体过于复杂,编译器将自动把内联函数当成普通函数来执行。

45.C++中成员函数可以同时用static和const进行修饰?

不能。

C++编译器在实现const的成员函数(const加在函数右边)的时候为了确保该函数不能修改类的中参数的值,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时const的用法和static是冲突的。

即:static修饰的函数表示该函数是属于类的,而不是属于某一个对象的,没有this指针。const修饰的函数表示该函数不能改变this中的内容,会有一个隐含的const this指针。二者是矛盾的。

46.溢出,越界,泄漏

1.溢出

要求分配的内存超出了系统能给你的,系统不能知足需求,因而产生溢出。

1)栈溢出

a.栈溢出是指函数中的局部变量形成的溢出(注:函数中形参和函数中的局部变量存放在栈上)

栈的大小一般是1M-2M,因此栈溢出包含两种状况,一是分配的的大小超过栈的最大值,二是分配的大小没有超过最大值,可是接收的buff比新buff小(buff:缓冲区, 它本质上就是一段存储数据的内存)

例子1:(分配的的大小超过栈的最大值)

void
{
    char a[99999999999999999];
}

例子2:(接收的buff比新buff小)

void
{
   char a[10] = {0};
   strcpy(a, "abjjijjlljiojohihiihiiiiiiiiiiiiiiiiiiiiiiiiii");
}

注意:调试时栈溢出的异常要在函数调用结束后才会检测到,由于栈是在函数结束时才会开始进行出栈操做

如:

int main(int argc, char* argv[])

{
char a[10] = {0};

strcpy(a, "abjjijjlljiojohihiihiiiiiiiiiiiiiiiiiiiiiiiiii");

exit(0);

return 0;

}

上面状况是检测不到栈溢出的,由于函数还没执行完就退出了

void fun()
{
 char a[10] = {0};
 strcpy(a, "abjjijjlljiojohihiihiiiiiiiiiiiiiiiiiiiiiiiiii");
}
int main(int argc, char* argv[])
{
  fun();
  exit(0);
  return 0;
}

这种状况调用完fun函数就会检测到异常了

b.栈溢出的解决办法

若是是超过栈的大小时,那就直接换成用堆;若是是不超过栈大小可是分配值小的,就增大分配的大小

2)内存溢出

使用malloc和new分配的内存,在拷贝时接收buff小于新buff时形成的现象

解决:增长分配的大小

2.越界

越界一般指的是数组越界,如

char a[9]={0};

cout << a[9] << endl;

3.泄漏

这里泄漏一般是指堆内存泄漏,是指使用malloc和new分配的内存没有释放形成的

47.C/C++中分配内存的方法

1) malloc 函数: void *malloc(unsigned int size)

     在内存的动态分配区域中分配一个长度为size的连续空间,若是分配成功,则返回所分配内存空间的首地址,不然返回NULL,申请的内存不会进行初始化。

2)calloc 函数: void *calloc(unsigned int num, unsigned int size)

     按照所给的数据个数和数据类型所占字节数,分配一个 num * size 连续的空间。

    calloc申请内存空间后,会自动初始化内存空间为 0,可是malloc不会进行初始化,其内存空间存储的是一些随机数据。 
3)realloc 函数: void *realloc(void *ptr, unsigned int size)

    动态分配一个长度为size的内存空间,并把内存空间的首地址赋值给ptr,把ptr内存空间调整为size。

    申请的内存空间不会进行初始化。
4)new是动态分配内存的运算符,自动计算须要分配的空间,在分配类类型的内存空间时,同时调用类的构造函数,对内存空间进行初始化,即完成类的初始化工做。动态分配内置类型是否自动初始化取决于变量定义的位置,在函数体外定义的变量都初始化为0,在函数体内定义的内置类型变量都不进行初始化。
 

48.构造函数初始化列表

构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每一个数据成员后面跟一个放在括号中的初始化式。例如:

class CExample {
public:
    int a;
    float b;
    //构造函数初始化列表
    CExample(): a(0),b(8.8)
    {}
    //构造函数内部赋值
    CExample()
    {
        a=0;
        b=8.8;
    }
};

上面的例子中两个构造函数的结果是同样的。上面的构造函数(使用初始化列表的构造函数)显式的初始化类的成员;而没使用初始化列表的构造函数是对类的成员赋值,并无进行显式的初始化。

初始化和赋值对内置类型的成员没有什么大的区别,像上面的任一个构造函数均可以。对非内置类型成员变量,为了不两次构造,推荐使用类构造函数初始化列表但有的时候必须用带有初始化列表的构造函数:
1.成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
2.
const成员或引用类型的成员。由于const对象或引用类型只能初始化,不能对他们赋值。 

初始化数据成员与对数据成员赋值的含义是什么?有什么区别?
首先把数据成员按类型分类并分状况说明:
1.内置数据类型,复合类型(指针,引用)
    在成员初始化列表和构造函数体内进行,在性能和结果上都是同样的
2.用户定义类型(类类型)
    
结果上相同,可是性能上存在很大的差异。由于类类型的数据成员对象在进入函数体前已经构造完成(先进行了一次隐式的默认构造函数调用),也就是说在成员初始化列表处进行构造对象的工做,调用构造函数,在进入函数体以后,进行的是对已经构造好的类对象的赋值,又调用了拷贝赋值操做符才能完成(若是并未提供,则使用编译器提供的默认按成员赋值行为)。

49.vector中v[i]与v.at(i)的区别

void f(vector<int> &v)
{
  v[5];    // A
  v.at[5];  // B
}

若是v非空,A行和B行没有任何区别。若是v为空,B行会抛出std::out_of_range异常,A行的行为未定义。

c++标准不要求vector<T>::operator[]进行下标越界检查,缘由是为了效率,老是强制下标越界检查会增长程序的性能开销。设计vector是用来代替内置数组的,因此效率问题也应该考虑。不过使用operator[]就要本身承担越界风险了。

若是须要下标越界检查,请使用at。可是请注意,这时候的性能也是响应的会受影响,由于越界检查增长了性能的开销。

50.指向函数的指针--函数指针

连接

51.C++中调用C的函数

extern "C"

连接

52.指针常量与常量指针

常量指针(被指向的对象是常量)

定义:又叫常指针,能够理解为常量的指针,指向的是个常量

关键点:

  1. 常量指针指向的对象不能经过这个指针来修改,但是仍然能够经过原来的声明修改;
  2. 常量指针能够被赋值为变量的地址,之因此叫常量指针,是限制了经过这个指针修改变量的值;
  3. 指针还能够指向别处,由于指针自己只是个变量,能够指向任意地址;
const int *p或int const *p

(记忆技巧:const读做常量,*读做指针)

#include <stdio.h>
// 常量指针(被指向的对象是常量)
int main() {
    int i = 10;
    int i2 = 11;
    const int *p = &i;
    printf("%d\n", *p);//10
    i = 9; //OK,仍然能够经过原来的声明修改值,
    //Error,*p是const int的,不可修改,即常量指针不可修改其指向地址
    //*p = 11;  //error: assignment of read-only location ‘*p’
    p = &i2;//OK,指针还能够指向别处,由于指针只是个变量,能够随意指向;
    printf("%d\n", *p);//11
    return 0;
}

指针常量(指针自己是常量)

定义:

本质是一个常量,而用指针修饰它。指针常量的值是指针,这个值由于是常量,因此不能被赋值。

关键点

  1. 它是个常量!
  2. 指针所保存的地址能够改变,然而指针所指向的值却不能够改变;
  3. 指针自己是常量,指向的地址不能够变化,可是指向的地址所对应的内容能够变化;
int* const p;
//指针常量(指针自己是常量)
#include <stdio.h>

int main() {
    int i = 10;
    int *const p = &i;
    printf("%d\n", *p);//10
    //Error,由于p是const 指针,所以不能改变p指向的内容
    //p++;//error: increment of read-only variable ‘p’
    (*p)++;    //OK,指针是常量,指向的地址不能够变化,可是指向的地址所对应的内容能够变化
    printf("%d\n", *p);//11
    i = 9;//OK,仍然能够经过原来的声明修改值,
    return 0;
}

 53.防止头文件被重复包含

连接

54.详解拷贝构造函数相关知识

很是好的一篇博客:连接

相关文章
相关标签/搜索