语言基础(C++)

  • 1.指针和引用的区别

    • 指针是对象,引用不是对象,而是为已经存在的对象所起的别名,不占据内存空间
    • 指针对应的是对象的地址,引用对应的是对象
    • 指针可以指向空,引用不可以,引用必须初始化
    • 指针可以改变它指的对象,引用一旦绑定就不可更改
    • 引用和指针都实现了对其他对象的间接访问
  • 2.堆和栈的区别

    • 生长方式不同

      • 堆是向上生长的,栈是向下生长的
    • 空间大小不同

      • 堆的空间可以达到4g,栈的空间有限
    • 管理方式不同

      • 堆是由程序员自己控制的的,栈是由编译器自动分配,无需程序员控制
    • 分配效率不同

      • 栈是机器系统提供的数据结构,计算机底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令。
      • 堆则是由C/C++函数库提供,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
    • 是否会碎片化

      • 堆比较容易产生碎片,多次的new/delete会造成内存的不连续,从而产生大量的碎片。栈不会产生碎片,因为栈是种先进后出的队列。
    • 分配方式不同

      • 堆是动态分配的。栈可以是静态分配和动态分配两种,它的动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
  • 3.new和delete是如何实现的,new 与 malloc的异同处

    • 当我们使用new表达式的时候,实际执行了三步操作:
      • 第一步new表达式调用operator new(或者operator new[])标准库函数。该函数分配一个足够大的,原始的,未命名的内存空间以便存储特定类型的对象。
      • 第二步编译器调用相应的构造函数以构造这些对象,并为其传入初始值。
      • 第三步对象被分配了空间,并完成构造,返回一个指向该对象的指针。
    • 当我们使用delete表达式删除一个动态分配的对象时,实际上执行了两步操作:
      • 第一步对要删除的对象中的元素执行对应的析构函数。
      • 第二步编译器调用名为operator delete(或者operator delete[])的标准库函数释放内存空间。
    • new和malloc的异同处
      • 1.申请的内存所在位置
        • new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配。
        • 那么自由存储区是否能够是堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。特别的,new甚至可以不为对象分配内存!
      • 2.返回类型安全性
        • new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回(void * ) ,需要通过强制类型转换将void *指针转换成我们需要的类型。
      • 3.内存分配失败时的返回值
        • new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。
      • 4.是否需要指定内存大小
        • 使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存的尺寸。
      • 5.是否调用构造函数/析构函数
        • new/delete会调用对象的构造函数/析构函数以完成对象的构造/析构。而malloc则不会
      • 6.对数组的处理
        • new对数组的支持体现在它会分别调用构造函数函数初始化每一个数组元素,释放对象时为每个对象调用析构函数。注意delete[]要与new[]配套使用,不然会找出数组对象部分释放的现象,造成内存泄漏。
        • 至于malloc,它并知道你在这块内存上要放的数组还是啥别的东西,反正它就给你一块原始的内存,在给你个内存的地址就完事。
      • 7.new与malloc是否可以相互调用
        • operator new /operator delete的实现可以基于malloc,而malloc的实现不可以去调用new。
      • 8.是否可以被重载
        • 我们有足够的自由去重载operator new /operator delete ,以决定我们的new与delete如何为对象分配内存,如何回收对象。而malloc/free并不允许重载。
      • 9.能够直观地重新分配内存
        • 使用malloc分配的内存后,如果在使用过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;如果空间不够,先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来的内存区域。new没有这样直观的配套设施来扩充内存。
  • 4.c语言和c++有什么区别

    • C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制)。
    • C++,首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。 所以C与C++ 的最大区别在于它们的用于解决问题的思想方法不一样。之所以说C++ 比C更先进,是因为“ 设计这个概念已经被融入到C++ 之中 ”。
  • 5.struct和class的区别

    • 成员的默认访问权限不同.struct默认是public,而class默认是private.
    • 默认的继承保护级别不同.struct默认是public继承,而class默认是private继承.
    • class可用作定义模板参数的关键字,类似typename,而struct不行.
  • 6.#define和const的区别

    • 就定义常量说的话:
      • const 定义的常数是变量 带类型, #define 定义的只是个常数 不带类型。
    • 就作用的阶段而言
      • define是在预处理阶段起作用,而const是在 编译、运行的时候起作用。
    • 就起作用的方式而言:
      • define只是简单的字符串替换,没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。 正因为define只是简单的字符串替换会导致边界效应
    • 就空间占用而言:
      • define预处理后占用代码段空间。const占用数据段空间
    • define可以用来防止头文件重复引用,而const不能
    • 从是否可以再定义的角度而言:
      • const不足的地方,是与生俱来的,const不能重定义,而#define可以通过#undef取消某个符号的定义,再重新定义。
  • 7.在C++中const的用法(定义,用途)

    • 在C语言里面,const修饰的不是常量,而是只读变量。在C++里面const修饰的是常量。
    • 常量位于常量区,对这个区域的修改是未定义的,由具体编译器决定结果,const修饰的只读变量位于静态区
    • const修饰的只读变量并不是说变量对应的内存中的值不可更改,而是说这个值对于const修饰的这个符号来说是只读的,不能通过const修饰的这个符号来更改这个变量的值,如果用其他的方式能够访问到这块内存,这个值仍可被更改
    • 用const修饰类对象时,这个类对象只可以访问const成员函数
    • 当函数后面加const的时候,则在这个函数里面不可以修改变量的值
  • 8.在C++中static的用法(定义,用途)

    • 在C语言中static的作用如下

      • 第一、在修饰变量的时候,static修饰的静态局部变量只执行一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。 Static修饰的局部变量存放在全局数据区的静态变量区。初始化的时候自动初始化为0;
      • 第二、static修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是extern外部声明也不可以。
      • 第三、static修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。不像C++里面的静态成员函数那样,C里面static函数,是可以调用非静态成员的
        • (1)不想被释放的时候,可以使用static修饰。比如修饰函数中存放在栈空间的数组。如果不想让这个数组在函数调用结束释放可以使用static修饰
        • (2)考虑到数据安全性(当程序想要使用全局变量的时候应该先考虑使用static)
    • 在C++中static关键字除了具有C中的作用还有在类中的使用,在类中,static可以用来修饰静态数据成员和静态成员函数

      • 静态数据成员

        • (1)静态数据成员可以实现多个对象之间的数据共享,它是类的所有对象的共享成员,它在内存中只占一份空间,如果改变它的值,则各对象中这个数据成员的值都被改变。
        • (2)静态数据成员是在程序开始运行时被分配空间,到程序结束之后才释放,只要类中指定了静态数据成员,即使不定义对象,也会为静态数据成员分配空间。
        • (3)静态数据成员可以被初始化,但是只能在类体外进行初始化,若为对静态数据成员赋初值,则编译器会自动为其初始化为0
        • (4)静态数据成员既可以通过对象名引用,也可以通过类名引用。
      • 静态成员函数

        • (1)静态成员函数和静态数据成员一样,他们都属于类的静态成员,而不是对象成员。
        • (2)非静态成员函数有this指针,而静态成员函数没有this指针。
        • (3)静态成员函数主要用来访问静态数据成员而不能访问非静态成员。
  • 9.C++中的const类成员函数(用法和意义)

    • 在成员函数的参数列表后面出现了一个const。这个const指明了这个函数不会修改该类的任何成员数据的值,称为常量成员函数。对于const函数的外部定义,是一定要书写const限定符,而对于static函数而言,外部定义时是不能书写static限定符的。因为static是变量的存储状态,而const是变量 类型。
    • 如果在const成员函数的定义中出现了任何修改对象成员数据的现象,都会在编译时被检查出来这个错误
    • const成员函数存在的意义在于它能被const常对象调用。我们都知道,在定义一个对象或者一个变量时,如果在类型前加一个const,如const int x;,则表示定义的量为一个常量,它的值不能被修改。但是创建的对象却可以调用成员函数,调用的成员函数很有可能改变对象的值
    • 但是,如果不允许const对象调用任何成员函数又是非常不合理的。于是,我们把那些肯定不会修改对象的各个属性值的成员函数加上const说明符,这样,在编译时,编译器将对这些const成员函数进行检查,如果确实没有修改对象值的行为,则检验通过。以后,如果一个const常对象调用这些const成员函数的时候,编译器将会允许。
    • 有些时候,我们却必须要让const函数具有修改某个成员数据值的能力。比如一些内部的状态量,对外部用户无所谓,但是对整个对象的运行却大有用处,如支持缓存的技术。遇到这种问题,我们可以把一个成员数据定义为mutable(多变的),它表示这个成员变量可以被const成员函数修改却不违法。
    • 但需要注意的时,不可滥用mutabe描述符,如果在某个类中只有少数一部分是被允许const常量函数修改的,使用mutable是再合适不过的。如果大部分数据都定义为mutable,那么最好将这些需要修改的数据放入另一个独立的对象里,并间接地访问它。
  • 10.计算下面几个类的大小:

    • 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;
    1. C++的STL介绍(这个系列也很重要,建议侯捷老师的这方面的书籍与视频),其中包括内存管理allocator,函数,实现机理,多线程实现等
    •  
  • 12.STL中vector的实现

  • 13.vector使用的注意点及其原因,频繁对vector调用push_back()对性能的影响和原因。

    • 常用操作push_back()函数在每次插入元素时会检测预留空间是否够用,push_back()时预留空间不够用:要重新分配内存,并且拷贝当前已有的所有元素到新的内存区域。如果已有元素很多,这个操作将变的非常昂贵,所以应该尽量避免vector重新分配内存。
    • 如何避免vector自动重新分配内存?可以预先估计元素的个数,用reserve函数进行预留空间的分配。所以当我们用push_back时,请慎重考虑是否需要reserve。
  • 14.C++中的重载、重写和重定义的区别:

    • 1).函数重载(overload)函数重载是指在一个类中声明多个名称相同但参数列表不同的函数,这些的参数可能个数或顺序,类型不同,但是不能靠返回类型来判断。特征是:

      • 相同的范围(在同一个作用域中);
      • 函数名字相同;
      • 参数不同;
      • virtual 关键字可有可无(注:函数重载与有无virtual修饰无关);
      • 返回值可以不同;
    • 2).函数重写(也称为覆盖 override)函数重写是指子类重新定义基类的虚函数。特征是:

      • 不在同一个作用域(分别位于派生类与基类);
      • 函数名字相同;
      • 参数相同;
      • 基类函数必须有 virtual 关键字,不能有 static 。
      • 返回值相同,否则报错;
      • 重写函数的访问修饰符可以不同;
    • 3).重定义(也称隐藏)

      • 不在同一个作用域(分别位于派生类与基类) ;
      • 函数名字相同;
      • 返回值可以不同;
      • 参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏(注意别与重载以及覆盖混淆);
      • 参数相同,但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆);
  • 15.C ++内存管理(热门问题)

  • 16.介绍面向对象的三大特性,并且举例说明每一个。

    • 封装性是基础,继承性是关键,多态性是补充,并且多态性存在于继承的环境中。
    • 封装
      • C++语言中支持数据封装,类是支持数据封装的工具,对象是数据封装的实现。在封装中,还提供一种对数据访问的控制机制,使得一些数据被隐藏在封装体内,因此具有隐藏性。封装体与外界进行信息交换是通过操作接口进行的。这种访问控制机制体现在类的成员可以有公有成员(public),私有成员(private),保护成员(protected)。

      • 私有成员是在封装体内被隐藏的部分,只有类体内说明的函数(类的成员函数)才可以访问私有成员,而在类体外的函数时不能访问的,公有成员是封装体与外界的一个接口,类体外的函数可以访问公有成员,保护成员是只有该类的成员函数和该类的派生类才可以访问的。

      • 类是一种复杂的数据类型它是将不同类型的数据和与这些数据相关的操作封装在一起的集合体。因此,类具有对数据的抽象性,隐藏性和封装性。

    • 继承
      • C++语言允许单继承和多继承。继承是面向对象语言的重要特性。一个类可以根据需要生成它的派生类,派生类还可以再生成派生类。派生类继承基类的成员,另外,还可以定义自己的成员。继承是实现抽象和共享的一种机制。
      • C++ 语言中的继承机制可以克服传统的面向过程程序设计的缺点,因为传统编程方式不能重复使用程序而造成资源的浪费,而c++语言提供了无限重复利用程序资源的一种新途径。
    • 多态(看下条)
  • 17.多态的实现,虚函数的实现原理,虚函数表和虚函数指针(重要)

    • 多态:
      • 多态性是指对不同类的对象发出相同的消息将会有不同的实现。多态性也可以理解为,在一般类中定义的属性或服务被特殊类继承后,可以具有不同的数据类型或不同的实现。可见,多态性与继承性相关联。简单的说,多态性是指发出同样的消息被不同的数据类型的对象接收后导致不同的行为。
      • C++语言支持多态性表现在:
        • 1,C++语言允许函数重载和运算符重载。
        • 2,C++语言通过定义虚函数来支持动态联编,动态联编是多态性的一个重要的特征。
      • 多态特性的工作依赖虚函数的定义,在需要解决多态问题的重载成员函数前,加上virtual关键字,那么该成员函数就变成了虚函数
    • 每一个含有虚函数(无论是其本身的,还是继承而来的)的类都至少有一个与之对应的虚函数表,其中存放着该类所有的虚函数对应的函数指针。
    • 虚函数替换过程出现在编译阶段,基类的虚函数表构造比较简单,派生类的虚函数表构造较复杂:
      • 拷贝基类的虚函数表
      • 替换已经重写的虚函数指针
      • 追加派生类自己的虚函数指针
  • 18.构造函数不是虚函数的原因是

    • C++之父的回答是:虚函数调用是在部分信息下完成工作的机制,允许我们只知道接口而不知道对象的确切类型。 要创建一个对象,你需要知道对象的完整信息。 特别是,你需要知道你想要创建的确切类型。 因此,构造函数不应该被定义为虚函数。
    • 换句话就是虚函数的作用在于通过父类的指针或引用来调用子类的那个成员函数。而构造函数是在创建对象时自己主动调用的,不可能通过父类的指针或者引用去调用。
  • 19.为什么在含有虚函数的情况下析构函数要是虚函数

    • 假如析构函数不是虚函数,那么当基类指针指向派生类时,调用析构函数,那么只会析构掉派生类的基类部分,剩下的派生类部分就不会被删掉,会出现内存泄漏
    1. 构造函数或者析构函数中调用虚函数会怎样?
    • 从语法上讲,没有问题
    • 从效果上讲,完全达不到预期效果
    • 当在构造基类部分时,派生类还没被完全创建,从某种意义上讲此时它只是个基类对象。所以在构造函数里面这个虚函数只会是实现基类的那个。
    • 而在析构基类部分时,派生类已经被析构掉,从某种意义上讲此时它只是一个基类对象。
    • 所以:基类部分在派生类部分之前被构造,当基类构造函数执行时派生类中的数据成员还没被初始化。如果基类构造函数中的虚函数调用被解析成调用派生类的虚函数,而派生类的虚函数中又访问到未初始化的派生类数据,将导致程序出现一些未定义行为和bug。
  • 21.纯虚函数

    • 基类中不能对虚函数给出有意义的实现,而把它们说明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
    • 为了方便实用多态,编程者常常需要在基类中定义虚拟函数。在很多情况下,积累本身生成对象是不合情理的。例如,动物作为一个基类可以派生出猴子、犀牛等子类,但动物本身生成对象明显不合理。
    • 为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数,则编译器要求在派生类中必须予以重载以实现多态性。同时含有纯虚函数的类称为抽象类,不能生成对象
  • 22.静态绑定和动态绑定的介绍

    • 静态绑定(statically bound):又名前期绑定(eraly binding),绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期;
    • 动态绑定(dynamically bound):又名后期绑定(late binding),绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期;
    • virtual函数是动态绑定,non-virtual函数是静态绑定,缺省参数值也是静态绑定。
    • 绝对不要重新定义一个继承而来的virtual函数的缺省参数值,因为缺省参数值都是静态绑定(为了执行效率),而virtual函数却是动态绑定,修改了后就不会出现动态了。
  • 23.引用是否能实现动态绑定,为什么引用可以实现?

    • 因为对象的类型是确定,在编译期就确定了。指针或引用是在运行期根据他们绑定的具体对象确定
    • C++中,通过基类的引用或指针调用虚函数时,发生动态绑定。引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的。
    • C++中动态绑定是通过虚函数实现的。而虚函数是通过一张虚函数表(virtual table)实现的。这个表中记录了虚函数的地址,解决继承、覆盖的问题,保证动态绑定时能够根据对象的实际类型调用正确的函数。
    • 在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
    1. 深拷贝和浅拷贝的区别(举例说明深拷贝的安全性)
    • 浅拷贝只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做“(浅复制)浅拷贝”,换句话说,浅复制仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。
    • 深拷贝在计算机中开辟了一块新的内存地址用于存放复制的对象。
    • 深复制和浅复制最根本的区别在于是否是真正获取了一个对象的复制实体,而不是引用。
    • 当类成员里面存在指针变量时,如果是浅拷贝的话,如果生成变量A a,然后A b(a),那么只会调用一次构造函数,而调用两次析构函数
    • 一般情况下,当类中成员有指针变量、类中有动态内存分配时常常需要用户自己定义拷贝构造函数,以避免浅拷贝。
  • 25.对象复用的了解,零拷贝的了解

    • 。。。。。。。
  • 26.什么情况下会调用拷贝构造函数(三种情况)

    • (1)用类的一个对象去初始化另一个对象时
    • (2)当函数的形参是类的对象时(也就是值传递时),如果是引用传递则不会调用
    • (3)当函数的返回值是类的对象或引用时,因为函数体内生成的对象是临时的,离开这个函数就会消失,所以需要调用拷贝构造函数复制一份。
  • 27.结构体内存对齐方式和为什么要进行内存对齐?

    • 原则1:数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
    • 原则2:结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
    • 原则3:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。
    • 需要字节对齐的根本原因在于CPU访问数据的效率问题。当然字节对齐的作用不仅是便于cpu快速访问,同时合理的利用字节对齐可以有效地节省存储空间。
    • #pragma pack(n)可以更改对齐规则
  • 28.内存泄露的定义,如何检测与避免?

    • 定义:简单地说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。(只new不delete)
    • 良好的编码习惯,尽量在涉及内存的程序段,检测出内存泄露。当程式稳定之后,在来检测内存泄露时,无疑增加了排除的困难和复杂度。使用了内存分配的函数,一旦使用完毕,要记得要使用其相应的函数释放掉。
    • 可以使用智能指针。
  • 29.模板的用法与适用场景

    • template <typename identifier> function_declaration;
    • 使用模板是为了实现泛型,可以减轻编程的工作量,增强函数的重用性。
  • 30.成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)?

    • 在类构造函数中,不在函数体内对变量赋值,而在参数列表后,跟一个冒号和初始化列表。
    • 在成员初始化列表里面是执行初始化操作,在构造函数体内则是赋值操作。
    • 《C++ Primer》中提到在以下三种情况下需要使用初始化成员列表:
      • 情况一、需要初始化的数据成员是对象的情况(这里包含了继承情况下,通过显示调用父类的构造函数对父类数据成员进行初始化);
      • 情况二、需要初始化const修饰的类成员或初始化引用成员数据;
      • 情况三、子类初始化父类的私有成员;
    • 类对象的构造顺序显示,进入构造函数体后,进行的是计算,是对成员变量的赋值操作,显然,赋值和初始化是不同的,这样就体现出了效率差异,如果不用成员初始化类表,那么类对自己的类成员分别进行的是一次隐式的默认构造函数的调用,和一次赋值操作符的调用,如果是类对象,这样做效率就得不到保障。
    • 构造函数需要初始化的数据成员,不论是否显示的出现在构造函数的成员初始化列表中,都会在该处完成初始化,并且初始化的顺序和其在类中声明时的顺序是一致的,与列表的先后顺序无关,所以要特别注意,保证两者顺序一致才能真正保证其效率和准确性。
  • 31.用过C11吗,知道C11新特性吗?(有面试官建议熟悉C11)

    • 。。。。
    1. C的调用惯例(简单一点C函数调用的压栈过程)
    • ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶
    • EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部
    • 函数栈帧:ESP和EBP之间的内存空间为当前栈帧,EBP标识了当前栈帧的底部,ESP标识了当前栈帧的顶部。
    • 函数调用大致包括以下几个步骤:
      • 参数入栈:将参数从右向左依次压入系统栈中
      • 返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行
      • 代码区跳转:处理器从当前代码区跳转到被调用函数的入口处
      • 栈帧调整:具体包括保存当前栈帧状态值,已备后面恢复本栈帧时使用(EBP入栈)将当前栈帧切换到新栈帧。(将ESP值装入EBP,更新栈帧底部)给新栈帧分配空间。(把ESP减去所需空间的大小,抬高栈顶)
  • 33.C++的四种强制转换

    • (1).static_cast
      • a.用于类层次结构中基类和派生类之间指针或引用的转换
        • 上行转换(派生类---->基类)是安全的;
        • 下行转换(基类---->派生类)由于没有动态类型检查,所以是不安全的。
      • b.用于基本数据类型之间的转换,如把int转换为char,这种带来安全性问题由程序员来保证
      • 把空指针转换成目标类型的空指针
      • 把任何类型的表达式转为void类型
      • 使用特点
        • a、主要执行非多态的转换操作,用于代替C中通常的转换操作
        • b、隐式转换都建议使用static_cast进行标明和替换
    • (2)const_cast
      • 使用场景
        • a、常量指针转换为非常量指针,并且仍然指向原来的对象
        • b、常量引用被转换为非常量引用,并且仍然指向原来的对象
      • 使用特点:
        • a、cosnt_cast是四种类型转换符中唯一可以对常量进行操作的转换符
        • b、去除常量性是一个危险的动作,尽量避免使用。一个特定的场景是:类通过const提供重载时,一般都是非常量函数调用const_ cast<const T>将参数转换为常量,然后调用常量函数,然后得到结果再调用const_cast <T>去除常量性。
      • (顶层const可以表示任意对象是常量,这一点对任何数据类型都适用。底层const表示指针所指的对象是一个常量,则与指针和引用这种复合类型有关)
    • (3)dynamic_cast
      • dynamic_cast<type-id> expression
      • 只有在派生类之间转换时才使用dynamic_cast,type-id必须是类指针,类引用或者void*。
      • 3.使用特点:
        • a、基类必须要有虚函数,因为dynamic_cast是运行时类型检查,需要运行时类型信息,而这个信息是存储在类的虚函数表中,只有一个类定义了虚函数,才会有虚函数表(如果一个类没有虚函数,那么一般意义上,这个类的设计者也不想它成为一个基类)。
        • b、对于下行转换,dynamic_ cast是安全的(当类型不一致时,转换过来的是空指针),而static_cast是不安全的(当类型不一致时,转换过来的是错误意义的指针,可能造成踩内存,非法访问等各种问题)
        • c、dynamic_cast还可以进行交叉转换
    • (4)reinterpret_cast
      • 不到万不得已,不用使用这个转换符,高危操作
      • a、reinterpret_cast是从底层对数据进行重新解释,依赖具体的平台,可移植性差
      • b、reinterpret_cast可以将整型转换为指针,也可以把指针转换为数组
      • c、reinterpret_cast可以在指针和引用里进行肆无忌惮的转换
  • 在C++中内存共分为五个区域

    • 栈区
    • 堆区
    • 自由存储区(这个和堆的区别要注意啊)
    • 全局/静态存储区
    • 常量存储区
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值