C++杂记(有感而发)

2023.2.18

内联函数

有时候,我们有一些很短小的函数,相比于函数体,压栈要执行的时间反而更多,但是宏定义又满足不了,比如以下函数:

int max(int a,int b)
{
    return a>b?a:b;
}
/*
使用宏定义:#define max(a,b) a>b?a:b
考虑以下函数调用:
int a=10,b=100;
max(++a,++b);
cout << a << " " << b << endl << "预期:11 101" << endl;
输出结果与预期不符!
*/

这时候,我们就可以用内联函数解决:

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

内联函数用关键字inline声明/定义,这样的话,可执行文件中就会有多个函数的副本,比如这样(假设已经定义了内联的max函数):

void invoke_max()
{
    int sum=0;
    sum+=max(100,10);
    sum+=max(332,39);
    sum+=max(477,92);
    sum+=max(100,max(104,38));
    cout << sum << endl;
}

就会被转换为类似代码(只是类似):

void invoke_max()
{
    int sum=0;
    {
        int return_value,a=100,b=10;
        return_value=a>b?a:b;
        sum+=return_value;
    }
    {
        int return_value,a=332,b=19;
        return_value=a>b?a:b;
        sum+=return_value;
    }
    {
        int return_value,a=477,b=92;
        return_value=a>b?a:b;
        sum+=return_value;
    }
    {
        int return_value,a=100,b;
        {
            int return_value,a=104,b=38;
            return_value=a>b?a:b;
            b=return_value;
        }
        return_value=a>b?a:b;
        sum+=return_value;
    }
    cout << sum << endl;
}

看到没,其实里面根本没有函数调用,也就不会有压栈,出栈的耗时了。

注意!内联函数不允许递归!(至于为什么,留给聪明的你自己想吧)

2023.3.19.

论如何兼容Windows于Linux

大家写头文件时,有没有考虑过一个问题:如何让头文件在Linux下可以编译,在Windows下也可以呢?例如Windows下有Windows.h、WinSock.h、WinSock2.h等等头文件,Linux下却没有或者名字不同。

有些人可能说:“编写两个头文件不就行了吗?”,但是,实现的是同一个效果,绝大部分都是一样的,要重新编写的话很麻烦,又很消耗磁盘空间。

所以,我们可以用条件编译指令结合Windows特有的宏来判断:

#ifdef Win32
//Windows代码
#else
//Linux代码
#endif

比如这样(输出红色字体,Windows和Linux方法不一样):

#define <cstdio>
#ifdef Win32
#include <Windows.h>
#define set_red_letter SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),12)
#else
#define set_red_letter printf("\033[31m")
#endif

int main()
{
    set_red_letter;
    printf("This is the red letter.\n");
    return 0;
}

看吧,编译并运行这段代码,在Linux编译,和在Windows编译,输出结果是一样的!(当然,如果编译器和系统不匹配,肯定是不行的)

再说一下:如果是C++,那么要加一层“包装”,但C语言不用。比如C语言不支持命名空间,但是我们的头文件是这样的:要是C语言,则直接把所有东西放在全局。如果是C++,则放在my_namespace命名空间里,就可以用C++的__cplusplus宏:

#ifdef __cplusplus
namespace my_namespace
{
    //一些东西
}
#else
//一些东西
#endif

也可以看看官方的用法:

#ifdef __cplusplus
extern "C" {
#endif
//头文件代码...
#ifdef __cplusplus
}
#endif

有些人可能就要问了,这样做有什么用呢?首先,这些头文件C语言C++通用。

大家知道,有些函数的代码是在dll中的,导出函数使用C风格命名规则,所以C++要使用extern "C"来使用C风格命名规则。C++支持extern "C"但C不支持,但是都支持#ifdef ... ...#endif预处理,所以这样就可以完美解决问题啦!

今天的内容就到此结束,谢谢观看!以后会继续不定期更新的!

2023.5.1

不用临时变量交换变量值

通常,我们交换两个变量的值都是用

int temp = a;
a = b;
b = temp;

来实现的,但是如果不用临时变量,该怎么实现呢?

来看:

a^=b^=a^=b;

看到他的时候是不是一脸茫然呢?把他“翻译”成与上面含义相同的代码:

a^=b;
b^=a;
a^=b;

什么?你还是不懂?那我们就列一张表!

假设原本是a1,b1,有两个变量a2,b2开始时和a1,b1值相同,但是交换的是a2和b2,列出一张表:

代码a2=b2=
a2^=b2a1^b1b1
b2^=a2a1^b1b1^a1^b1(=a1)
a2^=b2a1^a1^b1(=b1)a1

你看,最终a2=b1,b2=a1,这样就实现了变量的交换:

最后,布置一道练习题(?):

证明

a=a+b;
b=a-b;
a=a-b;

也可以实现变量的交换。答案发在评论区。

2023.6.18

CRT源码

我发现了一个好玩的东西:

C:\Program Files (x86)\Windows Kits\10\Source\...\ucrt\

里面有啥?可能是一部分的CRT源码!

比如stdlib/rand.cpp中就有srand和rand函数:

// Seeds the random number generator with the provided integer.
extern "C" void __cdecl srand(unsigned int const seed)
{
    __acrt_getptd()->_rand_state = seed;
}



// Returns a pseudorandom number in the range [0,32767].
extern "C" int __cdecl rand()
{
    __acrt_ptd* const ptd = __acrt_getptd();

    ptd->_rand_state = ptd->_rand_state * 214013 + 2531011;
    return (ptd->_rand_state >> 16) & RAND_MAX;
}

所以rand函数计算流程就是(seed代表种子):

seed = seed*214013+2531011;

返回(seed>>16)&32767

但是为什么不设置种子多次使用rand会得到同一个值呢?rand函数中明明更改了seed啊!

但是你想想,有时候经过这个计算还是同一个值!

解方程:

214013x + 2531011 ≡ x(mod 32768)

但我,小学生,不会解!!!

那就!写个程序,暴力枚举!

懒得编译,写了个Python程序:

while True:
    if (i * 214013 + 2531001)&0x7fff == i&0x7fff:
        print(i)
        break

结果一直没解出来,所以他没有解!

这是不是就是说,就算开始时不srand,多次得到的随机数也不相同?

设置问题:是不是就算开始时不srand,多次得到的随机数也不相同?如果相同,那么初始时种子是多少?

我们再看看system函数吧

我其实没怎么搞懂

感觉关键函数就是traits::tspawnve函数

你们可以自己来解释一下:

template <typename Character>
static int __cdecl common_system(Character const* const command) throw()
{
    typedef __crt_char_traits<Character> traits;

    static Character const comspec_name[] = { 'C', 'O', 'M', 'S', 'P', 'E', 'C', '\0' };
    static Character const cmd_exe[]      = { 'c', 'm', 'd', '.', 'e', 'x', 'e', '\0' };
    static Character const slash_c[]      = { '/', 'c', '\0' };

    __crt_unique_heap_ptr<Character> comspec_value;
    _ERRCHECK_EINVAL(traits::tdupenv_s_crt(comspec_value.get_address_of(), nullptr, comspec_name));

    // If the command is null, return TRUE only if %COMSPEC% is set and the file
    // to which it points exists.
    if (!command)
    {
        if (!comspec_value)
            return 0;

        return traits::taccess_s(comspec_value.get(), 0) == 0;
    }

    _ASSERTE(command[0] != '\0');

    Character const* arguments[4] =
    {
        comspec_value.get(),
        slash_c,
        command,
        nullptr
    };

    if (comspec_value)
    {
        errno_t const saved_errno = errno;
        errno = 0;

        int const result = static_cast<int>(traits::tspawnve(_P_WAIT, arguments[0], arguments, nullptr));
        if (result != -1)
        {
            errno = saved_errno;
            return result;
        }

        if (errno != ENOENT && errno != EACCES)
        {
            return result;
        }

        // If the error wasn't one of those two errors, try again with cmd.exe...
        errno = saved_errno;
    }

   arguments[0] = cmd_exe;
   return static_cast<int>(traits::tspawnvpe(_P_WAIT, arguments[0], arguments, nullptr));
}

//注:system函数是直接调用的他(这一行是我自己加的)

感觉官方都喜欢用int const而不是const int...

以后我可能会专门发一个博客讲解这个神奇的目录,敬请期待!
本日内容到此结束,喜欢的就点个赞可否?

2023.9.7

atexit

二话不说,先上代码:

1.

#include <cstdlib>
#include <cstdio>

void a()
{
    atexit(a);
}

int main()
{
    atexit(a);
    return 0;
}

运行结果是什么呢?

2.

#include <cstdlib>
#include <cstdio>

void a()
{
    atexit(a);
    atexit(a);
}

int main()
{
    atexit(a);
    return 0;
}

运行结果有是什么呢?

3.

#include <cstdlib>
#include <cstdio>

void a()
{
    atexit(a);
    exit(0);
}

int main()
{
    atexit(a);
    return 0;
}

这个呢?

答案及解释:

1. 答案:TLE。毋庸置疑。

2. 答案:MLE。atexit注册函数过多,列表过长。

3. 答案:MLE。exit函数递归层数过多。

怎么样?懂了吗?不懂的在评论区吼一声~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值