C++基础知识梳理二

1.隐式类型转换
基本数据类型的转换以低精度到高精度,即保证精度不丢失。如:char 到 int,int 到 long。
自定义对象:子类对象可隐式的转换为父类对象。

发生条件:https://blog.csdn.net/weixin_40627841/article/details/88320470

2.new/delete和malloc/free的区别
new/delete是C++的关键字,而malloc/free是C语言的库函数,后者使用必须指明申请内存空间的大小,对于类类型的对象,后者不会调用构造函数和析构函数

3.RTTI
运行时类型信息,在C++层面主要体现在dynamic_cast和typeid,VS中虚函数表的-1位置存放了指向type_info的指针。对于存在虚函数的类型,typeid和dynamic_cast都会去查询type_info
https://www.cnblogs.com/jameszhan/p/RTTI.html

4.虚函数表具体是怎样实现运行时多态的
子类若重写父类虚函数,虚函数表中,该函数的地址会被替换,对于存在虚函数的类的对象,在VS中,对象的对象模型的头部存放指向虚函数表的指针,通过该机制实现多态。

5.函数调用,压栈原理
可执行文件代码段中包含的二进制级别的机器代码会被装入内存的代码区(.text),处理器将到内存的这个区域一条一条地取出指令和操作数,并送入运算逻辑单元进行运算;如果代码中请求开辟动态内存,则会在内存的堆区分配一块大小合适的区域返回给代码区的代码使用;当函数调用发生时,函数的调用关系等信息会动态地保存在内存的栈区,以供处理器在执行完被调用函数的代码时,返回母函数
函数调用包括以下步骤:
(1)参数入栈:将参数从右向左依次压入系统栈中。
(2)返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行。
(3)代码区跳转:处理器从当前代码区跳转到被调用函数的入口处。
(4)栈帧调整:具体包括:
  <1>保存当前栈帧状态值,已备后面恢复本栈帧时使用(EBP入栈)。
   <2>将当前栈帧切换到新栈帧(将ESP值装入EBP,更新栈帧底部)。
  <3>给新栈帧分配空间(把ESP减去所需空间的大小,抬高栈顶)。
  <4>对于_stdcall调用约定,函数调用时用到的指令序列大致如下:
    push 参数3 ;假设该函数有3个参数,将从右向做依次入栈
    push 参数2
    push 参数1
    call 函数地址 ;call指令将同时完成两项工作:a)向栈中压入当前指令地址的下一个指令地址,即保存返回地址。 b)跳转到所调用函数的入口处。
    push ebp ;保存旧栈帧的底部
    mov ebp,esp ;设置新栈帧的底部 (栈帧切换)
    sub esp,xxx ;设置新栈帧的顶部 (抬高栈顶,为新栈帧开辟空间)
函数返回的步骤如下:
  <1>保存返回值,通常将函数的返回值保存在寄存器EAX中。
  <2>弹出当前帧,恢复上一个栈帧。具体包括:
  (1)在堆栈平衡的基础上,给ESP加上栈帧的大小,降低栈顶,回收当前栈帧的空间。
  (2)将当前栈帧底部保存的前栈帧EBP值弹入EBP寄存器,恢复出上一个栈帧。
  (3)将函数返回地址弹给EIP寄存器。
  <3>跳转:按照函数返回地址跳回母函数中继续执行。
  还是以C语言和Win32平台为例,函数返回时的相关的指令序列如下:
  add esp,xxx ;降低栈顶,回收当前的栈帧
  pop ebp ;将上一个栈帧底部位置恢复到ebp
  retn ;a)弹出当前栈顶元素,即弹出栈帧中的返回地址,至此,栈帧恢复到上一个栈帧工作完成。b)让处理器跳转到弹出的返回地址,恢复调用前代码区
  在这里插入图片描述
函数栈帧:ESP和EBP之间的内存空间为当前栈帧,EBP标识了当前栈帧的底部,ESP标识了当前栈帧的顶部
  
6.C++中拷贝赋值函数的形参能否进行值传递
不能。如果是这种情况下,调用拷贝构造函数的时候,首先要将实参传递给形参,这个传递的时候又要调用拷贝构造函数。。如此循环,无法完成拷贝,栈也会满

7.静态函数和虚函数的区别
静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销

8.C++中struct和class的区别
在C++中,可以用struct和class定义类,都可以继承。区别在于:structural的默认继承权限和默认访问权限是public,而class的默认继承权限和默认访问权限是private。
另外,class还可以定义模板类形参,比如template <class T, int i>。

9.三种继承访问权限
在这里插入图片描述
10.C++的类可以定义定义引用数据成员吗
可以,必须在构造函数初始化列表初始化

11.什么是右值引用,跟左值又有什么区别
左值:能对表达式取地址、或具名对象/变量。一般指表达式结束后依然存在的持久对象。
右值:不能对表达式取地址,或匿名对象。一般指表达式结束就不再存在的临时对象。
右值引用和左值引用的区别:

  1. 左值可以寻址,而右值不可以。
  2. 左值可以被赋值,右值不可以被赋值,可以用来给左值赋值。
  3. 左值可变,右值不可变(仅对基础类型适用,用户自定义类型右值引用可以通过成员函数改变)。

12.C++源文件从文本到可执行文件经历的过程
预处理阶段:对源代码文件中文件包含关系(头文件)、预编译语句(宏定义)进行分析和替换,生成预编译文件。
编译阶段:将经过预处理后的预编译文件转换成特定汇编代码,生成汇编文件
汇编阶段:将编译阶段生成的汇编文件转化成机器码,生成可重定位目标文件
链接阶段:将多个目标文件及所需要的库连接成最终的可执行目标文件

13.include头文件的顺序以及双引号””和尖括号<>的区别
对于使用双引号包含的头文件,查找头文件路径的顺序为:
当前头文件目录
编译器设置的头文件路径(编译器可使用-I显式指定搜索路径)
系统变量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的头文件路径

对于使用尖括号包含的头文件,查找头文件的路径顺序为:
编译器设置的头文件路径(编译器可使用-I显式指定搜索路径)
系统变量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的头文件路径

14.malloc的原理,另外brk系统调用和mmap系统调用的作用分别是什么?
Malloc函数用于动态分配内存。为了减少内存碎片和系统调用的开销,malloc其采用内存池的方式,先申请大块内存作为堆区,然后将堆区分为多个内存块,以块作为内存管理的基本单位。当用户申请内存时,直接从堆区分配一块合适的空闲块。Malloc采用隐式链表结构将堆区分成连续的、大小不一的块,包含已分配块和未分配块;同时malloc采用显示链表结构来管理所有的空闲块,即使用一个双向链表将空闲块连接起来,每一个空闲块记录了一个连续的、未分配的地址。
当进行内存分配时,Malloc会通过隐式链表遍历所有的空闲块,选择满足要求的块进行分配;当进行内存合并时,malloc采用边界标记法,根据每个块的前后块是否已经分配来决定是否进行块合并。

Malloc在申请内存时,一般会通过brk或者mmap系统调用进行申请。其中当申请内存小于128K时,会使用系统函数brk在堆区中分配;而当申请内存大于128K时,会使用系统函数mmap在映射区分配。

15.如何判断内存泄漏?
内存泄漏通常是由于调用了malloc/new等内存申请的操作,但是缺少了对应的free/delete。为了判断内存是否泄露,我们一方面可以使用linux环境下的内存泄漏检查工具Valgrind,另一方面我们在写代码时可以添加内存申请和释放的统计功能,统计当前申请和释放的内存是否一致,以此来判断内存是否泄露

16.如何采用单线程的方式处理高并发
在单线程模型中,可以采用I/O复用来提高单线程处理多个请求的能力,然后再采用事件驱动模型,基于异步回调来处理事件来

17.类的大小

#include<iostream.h>

class a {};
class b{};
class c:public a{
 virtual void fun()=0;
};
class d:public b,public c{};
int main()
{
 cout<<"sizeof(a)"<<sizeof(a)<<endl;
 cout<<"sizeof(b)"<<sizeof(b)<<endl;
 cout<<"sizeof(c)"<<sizeof(c)<<endl;
 cout<<"sizeof(d)"<<sizeof(d)<<endl;
 return  0;}
程序执行的输出结果为:
sizeof(a) =1
sizeof(b)=1
sizeof(c)=4
sizeof(d)=8

每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址.所以a,b的大小为1
类c是由类a派生而来,它里面有一个纯虚函数,由于有虚函数的原因,有一个指向虚函数的指针(vptr),在32位的系统分配给指针的大小为4个字节,所以最后得到c类的大小为4
为了提高实例在内存中的存取效率.类的大小往往被调整到系统的整数倍.并采取就近的法则,所以类d的大小为8个字节.

18.C++类的前向声明
假设有两个类A和B,类A要将类B的对象(或者指正)作为自己的成员使用,并且类B将类A的对象(或者指针)作为自己可以访问的数据,那么这个时候要在a.h中include b.h,同时在b.h 中要include a.h,但是相互包含是不可以的,这个时候就要用到类的前向声明了。
类的前向声明是利用了编译器的特性,编译器在编译的过程中只需要知道各个元素的名称和相应的大小就可以。而在c++中每一个类的大小是固定的,这个时候使用前向声明的类就可以通过编译器。
A.h

# include "B.h" 
class A 
{ 
    A(void); 
    ~A(void);     
    B b_;  //要包含B.h 
};

B.h


class A;
class B 
{ 
    B(void); 
    ~B(void); 
    void fun(A& a)//只能是指针或引用 
    { 
    } 
    //前向声明的类不能实例化对象 ,必须为指针或引用
    A* a_;  // 
};

19.C++防止头文件被重复引入方法
1)使用宏定义避免重复引入

#ifndef _NAME_H
#define _NAME_H
//头文件内容
#endif

需要注意的是,这里设置的宏名_NAME_H必须是独一无二的,不要和项目中其他宏的名称相同
2)使用#pragma once避免重复引入

#pragma once
class Student {
    //......
};

和 ifndef 相比,#pragma once 不涉及宏定义,当编译器遇到它时就会立刻知道当前文件只引入一次,所以效率很高,但值得一提的是,并不是每个版本的编译器都能识别 #pragma once 指令,一些较老版本的编译器就不支持该指令(执行时会发出警告,但编译会继续进行),即 #pragma once 指令的兼容性不是很好

17.STL 的内存优化

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值