小老弟研发之路面筋大汇总——关于C/C++(二)

目录

C++ extern关键字用法(wy游戏)

引用和指针的区别(wy游戏)

函数的压栈过程 详细说明(wy游戏)

内存的不同用途(扩充知识点)

内联函数的相关概念(扩充知识点)

内联函数和常规函数的区别(扩充知识点)

内联函数的优缺点(wy游戏)

虚函数的底层机制(wy游戏)


C++ extern关键字用法(wy游戏)

基本解释:可以置于变量或函数前,表明该变量或函数的定义在别的文件(模块)中,提示编译器遇到此变量或函数时在其他模块(文件)中寻找其定义。此外extern 也可以用来进行链接指定。(注意:只有当一个变量(函数)是一个全局变量时,extern变量才会起作用)

作用:

  1. 当它与"C"一起连用时,如: extern "C" void fun(int a, int b);这就告诉编译器在编译fun这个函数名时按着C的规则去翻译相应的函数名而不是C++的规则。
  2. 当extern不与"C"在一起修饰变量或函数时,如在头文件中: extern int g_Int; 这时候它的作用就是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块中使用,记住这是一个声明不是定义。(extern只需要指明类型和变量名即可,不能再重新赋值,初始化要在原文件进行,原文件如果不初始化,全局变量会被编译器自动初始化为0)

用法举例:

对于变量:

//文件一

#include<stdio.h>

void main()
{
     //给extern的变量赋值的正确写法
     extern int num;   
     num=1;

     //给extern的变量赋值的错误写法
     //extern int num = 1; //这是一个声明,不应该用来定义。


     printf("%d",num);
     return 0;
}

//文件二

#include<stdio.h>

int num = 5;

void func()
{
    print("fun in a.c")
}

对于函数:

//文件一
#include<stdio.h>

int main()
{
    extern int func(x);
    func(x)
    return 0;
}

//文件二
#include<stdio.h>

const int num=5;
void func(int x)
{
    x=x+1
    return x;
}

  • 引用和指针的区别(wy游戏)

本质区别:引用是别名,指针是地址。

表现区别:

  1. 指针在运行时可以改变其所指向的地址(指向另一个不同的对象),而引用一旦和某个对象绑定后就不再改变。
  2. 从内存分配上看,程序为指针变量分配内存区域,而不为引用分配内存区域。因为引用声明必须初始化,从而指向一个已经存在的对象。引用不能指向空值。
  3. 从编译上看,程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量名对应地址。指针变量在符号表上对应的地址值为指针变量自身的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再修改了,因此指针可以改变指向的对象(就是改变指针变量指向的地址),而引用对象不能改。这也是使用指针不安全,使用引用安全的主要原因。
  4. 使用引用的代码效率比使用指针的高,因为不存在指向空值的引用这个事实。在使用引用之前不需要测试它的合法性,而指针则应该在使用前测试,防止其为空。
  5. 理论上,对于指针的级别没有限制,而引用只能是一级。如下:
int** p1 //合法,指向指针的指针
int*& p2 //合法,指向指针的引用
int&* p3 //非法,指向引用的指针是非法的
int&& p4 //非法,指向引用的引用是非法的

  • 函数的压栈过程 详细说明(wy游戏)

  1. 参数入栈:将参数从右往左依次压入系统栈中。
  2. 返回地址入栈:当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行。
  3. 代码区跳转:处理器从当前代码区跳转到被调用函数的入口处,
  4. 栈帧调整,具体包括:

         ①保存当前栈帧状态值,已备后面恢复本栈帧时使用(EBP入栈)

         ②将当前栈帧切换到新栈帧(将当前ESP值装入EBP,更新栈帧底部)

         ③给新栈帧分配空间(把ESP减去所需要空间的大小,抬高栈顶)

         ④对于_stdcall调用约定,函数调用时用到的指定序列大致如下(背下面的本题基本就ok了):

push 参数3 ;假设该函数有3个参数,将参数从右往左依次入栈

push 参数2

push 参数1

call 函数地址;call指令将同时完成两项工作:a)向栈中压入当前指令地址的下一个指令地址,即保存返回地址。b)跳转到所调用函                                     数的入口处。    

push ebp ;保存旧栈帧的底部

mov ebp,esp ;设置新栈帧底部(栈帧切换)

sub esp,xxx ;设置新栈帧的顶部(抬高栈顶,为新栈帧开辟空间)

EBP:基址指针寄存器,其内存放着一个指针,该指针永远指向系统栈最上面的一个栈帧的底部。

ESP:栈指针寄存器,其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。

函数返回看这个地址:https://blog.csdn.net/u011555996/article/details/70211315


  • 内存的不同用途(扩充知识点)

根据不同的操作系统,一个进程可能被分配到不同的内存区域去执行。但是不管什么样的操作系统,什么样的计算机架构,进程使用的内存都可以按照功能大致分为以下四个部分:

  1. 代码区:这个区域存储着被装入执行的二进制机器代码,处理器会到这个区域取指并执行
  2. 数据区:用于存储全局变量等。
  3. 堆区:进程可以在堆区动态地请求一定大小的内存,并在使用完之后归还给堆区。动态分配和回收是堆区的特点。
  4. 栈区:用于动态地存储函数之间的关系,以保证被调用函数在返回时恢复到母函数中继续执行。

  • 内联函数的相关概念(扩充知识点)

内联函数(inline)定义: 当函数被声明为内联函数之后, 编译器会将其内联展开, 而不是按通常的函数调用机制进行调用。内联函数是C++为提高程序运行速度进行的改进,是一种以空间换时间的做饭。

使用条件:在使用内联函数时,如果调用函数的时间比处理函数的时间长,节省的时间占大头。但如果调用时间小于处理函数的时间短,则没有必要使用内联函数。经验之谈,尽量不要内联超过 10 行的函数。

用法举例:

int max(int a, int b)
{
 return a > b ? a : b;
}

为这么一个小的操作定义一个函数的好处有:

① 阅读和理解函数 max 的调用,要比读一条等价的条件表达式并解释它的含义要容易得多

② 如果需要做任何修改,修改函数要比找出并修改每一处等价表达式容易得多

③ 使用函数可以确保统一的行为,每个测试都保证以相同的方式实现

④ 函数可以重用,不必为其他应用程序重写代码

虽然有这么多好处,但是写成函数有一个潜在的缺点:调用函数比求解等价表达式要慢得多。在大多数的机器上,调用函数都要做很多工作:调用前要先保存寄存器,并在返回时恢复,复制实参,程序还必须转向一个新位置执行

C++中支持内联函数,其目的是为了提高函数的执行效率,用关键字 inline 放在函数定义(注意是定义而非声明,下文继续讲到)的前面即可将函数指定为内联函数,内联函数通常就是将它在程序中的每个调用点上“内联地”展开,假设我们将 max 定义为内联函数:

inline int max(int a, int b)
{
 return a > b ? a : b;
}

则调用: cout<<max(a, b)<<endl;

在编译时展开为: cout<<(a > b ? a : b)<<endl;

从而消除了把 max写成函数的额外执行开销


  • 内联函数和常规函数的区别(扩充知识点)

常规函数:运行程序时,常规函数调用会使程序跳到另一个地址(函数地址),并在函数结束时返回。

详细过程:执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址(或该指令的下一条指令的内存地址,具体是哪个内存地址可以自己运行试一下),并将函数参数压入堆栈(为此保存的内存块),跳到标记函数起点的内存单元,执行函数代码(也许还需要将返回值放入寄存器中),然后跳回到地址被保存的指令处。来回跳跃并记录跳跃位置意味着调用函数时,需要一定的开销。

内联函数:内联函数的编译代码与其他程序代码“内联”起来了。也就是说,编译器将使用相同的函数代码替换函数调用。

对于内联函数,程序无需跳转到代码区另一个位置处执行代码,再跳回来。因此,内联函数的运行速度比常规函数快,但代价是需要占用更多的内存。(在使用内联函数时,如果调用函数的时间比处理函数的时间长,则节省的时间占大头。但如果调用时间小于处理函数的时间短,则没必要使用内联函数)

区别:

  1. 常规函数调用会使程序跳到另一个地址,而内联函数调用程序不会。
  2. 内联函数比常规函数运行速度快,但占用内存更多。
  3. 内联函数使用和常规函数定义不一样(内联函数需要在定义函数的函数名前加 “inline“)。

  • 内联函数的优缺点(wy游戏)

优点: 当函数体比较小的时候, 内联该函数可以令目标代码更加高效(对于存取函数以及其它函数体比较短, 性能关键的函数, 鼓励使用内联)。
缺点: 滥用内联将导致程序变慢,毕竟内联是用空间换时间的。 内联可能使目标代码量或增或减, 这取决于内联函数的大小。内联非常短小的存取函数通常会减少代码大小, 但内联一个相当大的函数将戏剧性的增加代码大小(现代处理器由于更好的利用了指令缓存, 小巧的代码往往执行更快)。


  • 虚函数的底层机制(wy游戏)

首先要说明,多态的实现是基于虚函数的,而虚函数是通过虚函数表来实现C++多态。

底层机制:

  1. 每一个含有虚函数的对象中,都含有一个指针VPTR,指向虚函数表。
  2. 虚函数表里存放都是指针(指向虚函数地址的指针)。(虚函数表可以看成是存放指针的数组)
  3. 对于派生类对象覆盖的虚函数,对象的虚函数表里的指针指向的是这个覆盖的虚函数的地址。而对于派生类对象没有覆盖的虚函数,这个对象的虚函数表中,其指针指向的是基类虚函数的地址。

举例:

一般继承:

#include<iostream>
using namespace std;
 
class Base
{
public:
virtual void fun1 () {cout<<" printf base fun1!" <<endl;}
virtual void fun2 () {cout<<" printf base fun2!" <<endl;}
private:
int m_data1;
} ;
 
class Derive: public Base
{
public:
void fun1 () {cout<<" printf derive fun1!" <<endl;}
void fun3 () {cout<<" printf derive fun3" <<endl;}
private:
int m_data2;
} ;
 
int main ()
{
Base *pBase=new Derive;
Derive a;
pBase->fun1 () ;
pBase->fun2 () ;
a.fun3 () ;
return 0;
}

在每个含有虚函数的类对象中,都有一个VPTR,指向虚函数。

派生类也会继承基类的虚函数,如果在派生类中改写虚函数,虚函数表就会受到影响;表中元素所指向的地址不是基类的地址,而是派生类的函数地址。

当执行语句pBase->fun1()时,由于PBase指向的是派生类对象,于是就调用的Deriver::fun1()。

多重继承:

#include<iostream_h>
class base1
{
    public:
    virtual void vn(){}
    private:
    int i ;
);
class base2
{
    public:
    virtual void vf2(){}
    private:
    int j;
);
class derived:public base 1,public base2
{
    public:
    virtual void vf3(){}
    private:
    int k:
);
void main()
{
    derived d:
    base1 pl;
    base2 p2;
    pl=&d;p2 &d:
    pl->vfl();
    p2->vf2();
}

如果一个类具有多个包含虚函数的父类,编译器会为它创建多个VIrtual table,每个virtual table中各个虚函数的顺序与相应的父类一样。

(其实这就可以说理解为,继承两个父类的子类,有两个指向虚函数表的指针VPTR,故子类的大小会加上两个指针的大小(2*8字节=16字节))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值