《C++高效编程》笔记(贰)

《C++高效编程》笔记

标签(空格分隔):《C++高效编程》


本博客是看《C++高效编程》的笔记,还在更新中。。。

基本编程语句

if…else if…

我们在写if…else if…语句时,为了减少运行量,书上列举了几个要点:

  • 有多个else if时把运行最快的,最可能发生的,放到最前面。(相当于剪枝)
  • 如果判断条件中要进行实质性操作,把其放到最前面。比如:if(c = getnextchar() && k>10){…}

转移表(Jump Table)

个人理解:转移表相当于一个函数表,它相当于一个函数指针数组。【高级c++内容呀】

#include<cstdio>
#include<iostream>

using namespace std;

int add1(int n)
{
    return n+1;
}
int add2(int n)
{
    return n + 2;
}
int add3(int n)
{
    return n+3;
}

int main()
{
    //转移表定义
    typedef int (*functs)(int c);
    functs JumpTable[] = {add1, add2, add3};

    int res1 = JumpTable[0](3);
    int res2 = JumpTable[1](3);
    int res3 = JumpTable[2](3);

    printf("%d\n", res1);
    printf("%d\n", res2);
    printf("%d\n", res3);
    return 0;
}

现在我们可以使用转移表遍历调用函数,或者根据条件调用函数【我们可以用switch…case…语句来代替转移表,之后会讲到它们的运行速度比较】

转移表的缺点:转移表必须是连续的,也就是无法定义值为NULL,而且无法实现像switch…case…中类似default的功能。

swich…case…

书上列举了几个swich…case…要点,感觉和if…else…差不多,就不讲了。

总结与比较

我们先比较不进行函数调用时if…else…与swich…case…的区别。(转移表只能进行函数调用,因此不比较)

if(x==1)...;
else if(x==2)...;
else if(x==3)...;
。。。

与

swich(x)
{
case:1
    ...;
case:2
    ...;
case:3
    ...;
}

当x==k时,两个选择语句的耗时情况大致如下:(具体真实数据在书上,我懒得抄了,就编了一些)

kif..else…耗时swich…case…耗时
114
224
334
444
554

结论:case的时间不随判断情况在代码块的哪部分而改变,if判断越是靠前的条件越快执行(因此我们要把最可能的情况写前面)。书上提到了一种优化方案,就是:在case语句前使用1~2个if…else…语句,这样可以互补。

有函数调用时三者的区别

直接给结论好了。

kif..else…耗时swich…case…耗时转移表耗时
1142
2242
3342
4442
5542

因此,转移表>case>if

当需要稳定查找时间时,转移表是最佳选择。

数组

虽然转移表查找快,但在某些方面数组才是大爷中的大爷【书上说他快得无与伦比】,因此在做acm题时,我们一般先预处理打一份答案表。

函数

函数调用

一个简短的相加程序解析。

int add(int a, int b, int c)
{
    return a+b+c;
}
void main(void)
{
    int a = 1, b = 2, c = 3;
    int res = add(a, b, c);
}

调用函数add()产生的汇编程序1

mov eax, DWORD PTR _c$[ebp]    ;把数值'c'存入寄存器eax中
push eax                    ;把寄存器eax压入堆栈中
mov ecx, DWORD PTR _b$[ebp] ;把数值'b'存入寄存器ecx中
push ecx                    ;把寄存器ecx压入堆栈中
mov edx, DWORD PTR _a$[ebp] ;把数值'a'存入寄存器edx中,由于ebx有其他特殊用途,我们没有用ebx
push edx                    ;把寄存器edx压入堆栈中
call ?add@@YAHHHH@Z         ;调用add()

可以看到开始,我们把c,b,a通过寄存器放入堆栈中(注意顺序)。

当我们执行最后一行—add()函数时,会产生一个程序计数器,在任何时候程序计数器都会指向处理器检索程序指令的地址。因为有程序计数器,我们可以在函数结束后继续进行调用函数后的程序。

进入函数时add()的操作

push ebp                    ;把基指针放在堆栈中
mov ebp, esp                ;新的基指针值

函数执行时会产生一个基指针,还会用到堆栈。

基指针:该指针告诉处理器在何处可以找到局部变量。(局部变量也会压入栈中)

函数运行时的操作

mov eax, DWORD PTR _a$[ebp]; 寄存器eax中的数值'a'
add eax, DWORD PTR _b$[ebp]; 在eax中添加'b'
add eax, DWORD PTR _c$[ebp]; 在eax中添加'c'

这是三个变量的加法。

函数退出时的操作

pop ebp; ebp恢复为原值
ret 0  ; 从函数中返回

捕获返回结果时的操作

add esp, 12; 3个变量出栈,每个变量4字节
mov DWORD PTR _res$[ebp], eax; eax包含该函数

宏相对于函数,可以避免函数在调用堆栈时的开销。缺点大家当然也知道,难于阅读且易出错。

有人会说,要想宏用得好,只要多加括号就可以了。这里举一个未预料到的宏设计。

#define MAX(a,b) ((a)<(b)?(b):(a))

void main(void)
{
    int a = 10;
    char b = 12;

    short c = MAX(a, b); // a = 10, b = 12, c = 12 (确定)
    c = MAX(a++, b++);   // a = 11, b = 14, c = 13 (不确定)
}

优点

  • 避免函数调用开销。

缺点

  • 难以阅读(在某些方面),很难检查出错

内联函数

内联函数与宏差不多,本质是代码替换。会花费多余内存,但比宏更不容易出错。

关于内存,还有一点就是:我们可以把递归的算法转化为迭代,内存占用少,不容易RE。

参数传递

我们在传递参数时往往有4种写法:

  1. 值传递
  2. 引用传递(实质是指针)
  3. 指针
  4. 全局变量

其中最快的是全局变量,但它吃内存。

值传递无法修改传入变量的值。

虚函数*

你们知道虚函数是如何实现多态的吗?可能绝大多数人只知道虚函数能实现多态,却不知道它是如何实现的!

我们用关键字virtual向编译器表明我们需要虚函数,这时我们使用一种叫后联编技术(late binding),这意味着在编译过程中并不确定程序运行时调用何种函数,而在运行中确定调用何种函数。

上述工作如何完成的呢?

  1. 在编译器中产生的代码中添加虚函数表(Virtual Table ,VT)和虚函数指针(Virtual Table Pointer,VPTR)
  2. 其次,在程序运行时,添加虚函数表和虚函数表指针的代码。

比如有一个这样的代码:

class base
{
public:
    virtual int area(int k)=0;
};
class spere:public base
{
public:
    int area(int l){return l*l;}
};
class circle:public base
{
public:
    int area(int r){return 3.14*r*r;}
}

我们首先会在编译器产生的代码中添加虚函数表,比如在base中添加一个虚函数表

//假想操作
typedef vtable (*vptr)();
vtable vTable[] = {spere:area, circle:area};

然后在运行时添加调用代码

vtable * vptr = p[0]->vptr;
void (*funtionPtr)() = vptr[PrintNameIndex];
functionPtr();
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值