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^=b2 | a1^b1 | b1 |
b2^=a2 | a1^b1 | b1^a1^b1(=a1) |
a2^=b2 | a1^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函数递归层数过多。
怎么样?懂了吗?不懂的在评论区吼一声~