C++:三角函数、随机数、数论线性同余(待研究)、math库函数、配置C++20、C++20的number、GCC版本和C++版本区别、M_PI、条件编译、头文件和命名空间和库函数区别、控制台/终端/命令行/cmd/bash
菜鸟教程 数字
三角函数 和 控制输出位数
C++中:sin(π)表示的是弧度制,对应角度制是sin(180°)
因为180°=π弧度,那么1°=π/180弧度。所以想输出sin(90°)应该cout << sin(90*π/180) << endl;
,只不过没有C++库里定义π (豆包误人子弟,md后来在知乎直答里知道,cmath
或者math.c
库函数里有“派π”这玩意M_PI
)(M_PI不是C++标准的一部分,是编译器or操作系统的扩展定义)
cos90°,横坐标x=r*cosθ,所以cos90°是0,cos180°是-1,可以用acos输出圆周率精确数值,代码如下,acos0是90°,计算机里三角函数都是用弧度制表示
#include <iostream> #include <cmath> #include<iomanip> using namespace std; int main() { // 使用 acos 函数计算 pi const double pi = 2 * acos(0.0); // const double pi = acos(-1);两个都可以,这个更直接,只不过一开始没想到acos-1直接就是π cout<<setprecision(19)<< pi << endl; }
cout
输出double
类型,默认保留6位有效数字,有效数字:从第一个非0的数开始算第一位。3.123456789,cout默认输出3.12346,四舍五入。
关于控制位数:
#include <iostream> #include <iomanip> using namespace std; int main() { double num = 3.14159265358979323846; // 设置输出精度为 4 位小数 cout << setprecision(4) <<"#" <<num << endl; cout << fixed << setprecision(4) <<"@"<< num << endl; // 取消 fixed 格式 cout.unsetf(ios::fixed); cout.precision(4); cout << num << endl; }
再次舍弃豆包,上一篇解释的lambda全是错的,现在解释cout输出double的时候保留6位数字,解释的自相矛盾前后不搭,驴唇不对马嘴
发现文心GPT也回答的是错的,受不了了,找靠谱的AI去了
bing搜这个,出现这个回答,本来想找他说的GPT-4 Turbo来着,结果直接跳到了知乎直答,试了下感觉全方位的完美,但是好卡
豆包更适合查缺补漏,但如果主力用它会误人子弟南辕北辙,解答有错误
文心适合知乎直答和豆包都解释不明白的时候用,因为UI太差劲,解答也不一定对
随机数 —— 感叹知乎直答太开门了,真的牛逼
之前弄对拍的时候,用过随机数,现在好好学一学
如果使用相同的种子,同一个伪随机数生成器将产生完全相同的序列。因此,为了获得不同的随机结果,每次生成随机数时通常会使用不同的种子。
种子是啥?就是一个起始值,也是随机数生成器的起点,
伪随机数生成器是啥?一般来说就是某种特殊的算法,用于产生一系列数字
所以相同的种子,同一个伪随机数生成器,会产生完全相同的随机数序列
简单来说,伪随机数是由种子(seed)和伪随机数生成器(PRNG, Pseudo-Random Number Generator)的算法共同产生的。生成器的公式根据所使用的算法而不同,但一个经典例子是线性同余生成器(LCG, Linear Congruential Generator),其公式大致如下:
其中:
- 𝑋𝑛 是当前的随机数,
- 𝑋𝑛+1 是下一个随机数,
- 𝑎、𝑐 和 𝑚 是生成器的参数,需要根据特定的生成器来设定,
- 种子 𝑋0 是序列的起始值。
也就是说,其实根据种子和生成器,人们可以自己算出计算机即将给出的伪随机数。而种子的产生一般都是用时间戳,生成器(同余)是计算机设定好的,所以只需要弄个种子就行
即生成10个随机数,如果是时间戳的形式,则这10个随机数满足上述同余公式,只不过a、c、m都是计算机设定的(编程语言的标准库会使用预定义的设置或内部算法来初始化随机数生成器),如果自己设定可以这么写

1 #include <iostream> 2 3 // 自定义线性同余法生成伪随机数 4 unsigned long long lcg_rand(unsigned long long previous, unsigned long long a, unsigned long long c, unsigned long long m) { 5 return (previous * a + c) % m; 6 } 7 8 int main() { 9 // 自定义参数 10 unsigned long long seed = 1; // 初始种子值 11 unsigned long long a = 1103515245; // 乘数 12 unsigned long long c = 12345; // 增量 13 unsigned long long m = (1ull << 31) - 1; // 模数,通常使用2的31次方减1 14 15 std::cout << "Random number sequence using custom LCG:\n"; 16 17 // 生成并打印前5个随机数 18 for(int i = 0; i < 5; ++i) { 19 seed = lcg_rand(seed, a, c, m); 20 std::cout << seed << std::endl; 21 } 22 23 return 0; 24 }
进一说线性同余,这个是初等数论里的知识,
这个定理探讨的是形如 ax ≡ b (mod m) 的方程,其中 a, b, 和 m 是整数,m 不为零。
线性同余定理关注的是这样的方程有多少解,以及如何找到这些解。
在算法和计算机科学中,线性同余算法利用了线性同余的概念来生成伪随机数序列
之前听过,回顾一下,bing,知乎回答(没看这个),OI_Wiki(没看这个),看到这个 PKU内部讲解(里面只看了第3页,仅仅是弄懂了这个PPT,ACM数论的玩意先搁置吧)
“≡”数论里表示同余,这里进一步解释下上述PPT里:
3𝑘 ≡ 4 (𝑚𝑜𝑑 5)
它有唯一解𝑘 ≡ 3 (𝑚𝑜𝑑 5).
知乎直答:
先说下逆元:
逆元是指在特定运算下,与一个元素相结合能产生单位元的元素。在乘法运算中,一个数a的逆元是b,满足a乘以b等于1。在模运算环境下,如果考虑模n的乘法,数a的逆元x需要满足ax ≡ 1 (mod n)。但只有当a和n互质(即最大公约数为1)时,逆元才存在且唯一。逆元的概念广泛应用于数论、密码学以及计算机科学中的算法设计。
3的乘法逆元是2(在模5条件下)这句话:在模5的运算中,寻找一个数,使得这个数与3相乘后,结果除以5的余数是1。这里所说的“3的乘法逆元是2”,是因为当我们将3乘以2时,得到6,而6除以5的余数是1(即6 ≡ 1 (mod 5))。这意味着在模5的世界里,2就是3的逆元,因为3乘以2的结果模5等于1,满足乘法逆元的定义。开始解释:
如果3k模5等于4,我们可以通过等式来找到k模5的值,即k除以5的余数。
为了找到k模5的值,我们可以将等式两边同时除以3(假设k和m都是整数,且这样的操作在模运算的语境下意味着寻找一个等价的k值,使得该值除以5的余数不变),但是直接除以3在模运算中不直接适用,我们可以直接考虑3的逆元在模5下的情况,3的乘法逆元是2,将原方程两边同时乘以3的逆元2,得到: 2∗(3∗k)≡2∗4(mod5),已经说过在mod5的情况下,2*3是1,这转换为: k≡8(mod5)
简化后,k模5等于3,因为8除以5的余数是3。所以,k模5是3
继续解释:
15𝑟 + 11 ≡ 6 (𝑚𝑜𝑑 7),即15𝑟 ≡ 2 (𝑚𝑜𝑑 7)。
知乎直答
在模7的环境下,-5等价于2,因为-5加上7等于2
继续解释:
它有唯一解𝑟≡ 2 (𝑚𝑜𝑑 7).
知乎直答:
这个问题涉及模线性同余方程的解法。给定的同余方程是 15r≡2mod7。要找到这个方程的解,我们可以通过简化方程来开始,利用模运算的性质。
首先,观察到15和7的最大公约数是1,这意味着方程有唯一解。根据模运算的性质,我们可以将15除以7的余数作为新的系数,因为15mod7=1。因此,方程可以简化为 r≡2mod7。
简化后的方程直接告诉我们,r除以7的余数必须是2,这意味着r的值可以表示为7k+2的形式,其中k是任意整数。但是,当我们特别询问是否有唯一解时,答案是指在模7意义下的特定解,即r本身在模7下的等价类就是2,这表明在0到6(因为是模7)的范围内,解是唯一的,即r=2。
更广泛地,根据同余方程的理论,如果一个线性同余方程形如ax≡b mod m,且a与m互质(即gcd(a,m)=1),那么方程一定有且只有一个解,这个解可以通过扩展欧几里得算法或者直接的简化方法找到。在这个特定例子中,由于15和7互质,且通过简化直接得到了解,所以我们可以确定r≡2mod7是唯一的解。
当系数和模数互质时,根据同余方程的性质,方程ax ≡ b mod m 有唯一解。这是因为乘法逆元的存在保证了可以通过乘以某个数来“取消”a的影响,找到使得等式成立的唯一x值。
那对于这里a的逆元,也就是15在mod7下的逆元:
在模7的意义下,要找到15的逆元,即找到一个数x,使得15x≡1mod7。根据扩展欧几里得算法,我们知道15和7互质(gcd(15,7)=1),所以这样的x存在且唯一。
通过计算或直接观察,我们可以发现15≡1mod7,因为15除以7的余数是1,但这不是我们要找的逆元的形式。正确的做法是寻找满足条件的x。实际上,我们寻找的是一个数,使得15x与1同余于模7下。
直接计算或使用扩展欧几里得算法,我们可以得到15的逆元。由于15等价于1(在模7下),我们可以简化问题为求解x使得x≡15-1mod7。直观地,因为15≡1mod7,其逆元就是1本身。因此,在模7下,15的逆元是1。
互质的重要性:在方程ax ≡ b mod m 中,如果a和m互质(即它们的最大公约数为1),根据贝祖定理,存在整数x和y使得ax+my=1。这意味着a在模m下有乘法逆元,可以找到一个数让等式两边同时乘以它来解出x。
数论好有趣好费时间啊 (韩信点兵问题最早出现在南北朝的《孙子算经》中,这个问题因此也被称为“孙子问题” 此类问题在现代数学中叫做“一次同余问题”,其解法称为“中国剩余定理”或“孙子定理”。 宋代数学家秦九韶在《数书九章》中对这个问题作出了完整系统的解答,称为“大衍求一术”。可不是那个九章算法啊哈哈)。
进一步研究一下好神奇啊(这博主也是个死妈玩意,讲的根本就是在糊弄傻子,跟窜稀一样完全不想评价),发现tm是小学五年级题目,给我干自闭了,数论这个先搁置吧(文中的“三人同行七十稀”咋来的,问知乎直答,解释的还行,但没时间去钻研了,先搁置)(md小学就开始有中国剩余定理了吗我靠)(找了个视频给我都整的不好意思在图书馆看这玩意,小学数学~~~~(>_<)~~~~)(记得之前C语言心魔有过这玩意,穷举法笑死我了)(全网满片子像这种的狗逼博主)
至此全网没有找到讲解正确的,全都是糊弄傻子的讲解方法,直接搬出答案来自圆其说验证人家是正确的。
另外好神奇的博客里,
先找到一个除以7余2,除以11余4的数(可以在4上不断加11尝试,4除以7不余2,15除以7不余2,26除以7不余2,37除以7余2),是37。但是37除以13不余5,那就只能在37上加7和11的最小公倍数77了。
37÷13=2……11
77÷13=2……12
根据余数的加法定理,要加6个才行(11+12×6=83,83除以13余5)。
问了知乎直答,解释的不错但真没心情没时间去研究这玩意了,如果不是在学C++,而是在刷题,一定会钻研清楚,先搁置。
备注:
如果看到“b|a”,这通常意味着“b能整除a”,即a是b的倍数。即存在整数c,使得a = bc。
找到最小的值后,每次增加他们的最小公倍数,余数不变
说完了生成器,接着说上面的随机数算法,我一直听到的说法是rand()是伪随机数发生器,由,初始值决定,我一直以为加了时间戳就是真随机数了,但其实不是,我们计算机里学到的关于生成随机数的东西,全叫“伪”,啥是真?自然界不可预测的
真正的随机数不是通过算法生成的,而是基于物理过程,如放射性衰变、大气噪声等不可预测的自然现象。这些数字序列是不可预测的,因为它们的生成过程不依赖于任何确定性的计算。在需要高度安全性或不可预测性的应用中,如加密,真随机数是必要的
显式地设置种子(即调用 srand(seed) 来设定起始点)
如果没显示设置种子,就会将相同的固定值作为默认种子(默认种子值取决于各个编译器的具体实现,或者计算机的预设值)。这就导致每次运行程序时,如果没有改变种子,那么产生的序列都是完全一样的。即
#include <iostream> #include <ctime> #include <cstdlib> using namespace std; int main() { int i; for(int j=0;j<10;j++){ i= rand(); cout<<i<<endl; } }
未指定种子的情况:

未指定种子的情况: 如果你不调用srand(seed),rand()函数仍然可以正常工作,但它会使用某个默认值进行初始化。 ①在早期的标准(如C++98/03) 有些编译器确实会在这种情况下使用固定的默认种子(例如1),这会导致每次运行程序时生成相同的随机数序列。 ②现代标准 (C++11及以后): 根据C++11及其后续版本的规定,如果rand()在调用前没有被srand(seed)初始化,其行为仍然是未定义的。也就是说,编译器可以选择任何方式处理这种情况,包括但不限于使用一个固定的默认种子或使用其他机制。 实际上,许多现代编译器和实现倾向于在这种情况下使用当前时间或其他动态值作为种子,以便于获得更好的随机性。
rand()和srand()是C语言中用于生成伪随机数的一对函数,它们有不同的用途和功能。
rand()
-
作用:生成一个伪随机数。
-
返回值:返回一个介于0到RAND_MAX(32767)之间的整数(包括0,不包括RAND_MAX)
关于3276732767是一个在计算机科学和编程中具有特定意义的数字,它代表了16位(两个字节)有符号整数的最大值。在二进制表示中,32767是15个1后面跟着一个0(即01111111 11111111),转换为十进制就是32767。这个数值常用于表示特定的数据类型限制,比如在C语言和其他许多编程语言中,基本的短整型(short int或signed short)的范围通常是-32768到32767。 此外,32767在不同的上下文中也有特别的意义,例如在游戏《我的世界》中,附魔等级的最大值就是32767,因为游戏内部使用了16位的整数来存储这个信息。在PLC(可编程逻辑控制器)编程中,32767可能作为某些数据溢出的标志或最大计数值。 在计算机领域,这个数值是由于二进制补码系统决定的,其中最高位用作符号位,0表示正数,1表示负数,因此16位整数的正数范围是从0到32767,而负数范围是从-32768到-1。当一个正数达到32767再加1时,会因为溢出而得到一个负数,通常是-32768,这体现了有符号整数的算术溢出现象。
srand()
-
作用:设置随机数生成器的种子。
- 参数:接受一个无符号整数作为种子值
srand((unsigned int) time(NULL));
srand((unsigned)time( NULL ));: 这行代码使用当前时间作为随机数生成器的种子,确保每次程序运行时都能得到不同的随机序列。这是因为time(NULL)返回自纪元(1970年1月1日00:00:00 UTC)以来的秒数,确保了种子的唯一性。
Q:哪怕有了实时的秒数也不是伪随机是吗?
A:使用实时秒数作为种子可以增加伪随机数生成的不可预测性,因为每次调用时种子值会随时间变化。但这并不改变生成的数是伪随机的本质,因为尽管种子随时间变化看似随机,但只要生成器算法是确定性的,从同一秒数开始,用相同的生成器,得到的随机数序列将是一样的。所以,即使是基于实时秒数,产生的仍然是伪随机数,而非真随机数。
Q:与long int有啥区别?
A:
time_t和long int的主要区别在于它们的用途和标准定义的灵活性:
-
time_t:专门用于存储时间值,即自纪元(通常是1970年1月1日)以来的秒数。它的具体数据类型(如long int、long long int)取决于编译器和操作系统,以确保能够表示足够长的时间跨度。在32位系统中,它可能与long int相似,但在64位系统中,它可能是long long int,以解决2038年问题。
-
long int:是一个更通用的整数类型,其大小和范围由编译器和目标平台决定,但不特定于时间存储。它可以用于任何需要大整数的场合,并不局限于时间表示。
总结来说,尽管在某些系统上time_t可能实际上被实现为long int,但它的语义和使用场景专注于时间处理,而long int则是一个更通用的数值类型。time_t通常是long int型
-
不写NULL行吗?只time() 可以的,直接写time()是等价于time(NULL)的。在C语言中,函数time()默认期待一个可选的指针参数,用来存放结果。如果不提供(即写为time()),编译器会默认认为你传递了NULL,函数会直接返回当前时间的time_t值而不是存储到某个变量。(更新:C编译器可以不写,即直接写
time()
是等价于time(NULL)
的,因为 C 语言允许函数调用时参数缺失,并将缺失的参数视为NULL
。而C++ 对类型安全有更严格的要求,不允许像 C 语言那样自动将缺失的参数视为NULL
。当你调用time()
函数时,必须显式提供一个time_t*
类型的参数,以满足函数的参数要求。这是 C 和 C++ 在函数调用和类型安全方面的一个重要区别。我codeblock是GNU GCC complier,大多数扩展名.c和.cpp会给编译器一个暗示,分别使用C和C++编译器,但我弄完.c文件发现还是报错,查到说可以强制C编译器,输入你想要使用的 C 语言标准,如-std=c11
或-std=c99
或-std=c17
。但我输入完又提示我怕把其他设置给我自动搞乱了,就不管了,不继续测试time()了,以后带参数就是了
-
时间戳 时间戳是一个表示时间的长整数,它代表自1970年1月1日(UTC/GMT的午夜)以来的秒数,不考虑闰秒。这个系统广泛应用于计算机科学中,用于记录和计算时间,被称为Unix时间戳。
-
time_t是啥? time_t是C语言标准库中定义的一种数据类型,用于存储时间值。它通常是一个可以表示日期和时间的整数类型,具体大小依赖于实现,但目的是能够表示从某个固定时间点(通常是1970年1月1日)以来的秒数。使用time_t,程序员可以方便地处理时间,包括获取当前时间、进行时间运算等。
-
NULL 在C和C++等编程语言中,是一个特殊的值,通常用于表示空指针或不存在的链接。在time(NULL)这个函数调用中,NULL是传递给函数的一个参数,意味着“没有特定的时间指针”。函数会忽略这个参数,并直接返回当前时间(从1970年1月1日00:00:00 UTC开始的秒数),这样设计是为了方便,你不需要预先创建一个time_t变量来接收结果。
-
输出time值 的示例代码(以C语言为例):
#include <stdio.h> #include <time.h> int main() { // 获取当前时间戳 time_t currentTime = time(NULL); // 输出时间戳 printf("当前时间戳(秒): %ld\n", (long)currentTime); return 0; }
Q:去掉unsigned
A:
srand((unsigned int) time(NULL));
中的 unsigned int 可以去掉,因为 time() 返回的是一个长整型 (long) 值,并且可以直接传递给 srand() 函数。虽然去掉了 unsigned int 强制转换,效果通常也是相同的。
然而,进行这种类型的显式转换是有益的,因为它明确地表明您希望将 time_t 类型的结果转换为 unsigned int 类型,这有助于提高代码的清晰性和可读性。此外,在某些平台上,time_t 和 unsigned int 的大小可能不同,因此为了确保兼容性和避免潜在的问题,建议保留该转换。
以下是两种方式的效果对比:
// 方式一:包含 unsigned int 转换 srand((unsigned int) time(NULL)); // 方式二:不包含 unsigned int 转换 srand(time(NULL));
这两种方法都可以用来初始化随机数生成器并获得不同的随机数序列。不过推荐第一种做法,以便更好地表达意图和提升代码质量。
---------------------------------------------------------------------------------------------------------------
math.c是C的:默认接受double
cmath是C++的
Q:数学函数都在std里,为何还要cmath
A:<cmath> 是 C++ 标准库的一部分,它提供了许多数学运算所需的函数和常量。尽管 <cmath> 中的所有实体都在 std 命名空间内,仍然需要包含这个头文件的原因在于,预处理器指令 #include <cmath> 明确地指示编译器去查找并加载 <cmath> 提供的功能。
调用原理:
当你在代码中写下:
#include <cmath>
这意味着你要求编译器读取 <cmath> 头文件的内容,并将其提供的所有符号(如 sin, cos, sqrt 等)放入当前翻译单元的作用域中,但这些符号位于 std 命名空间内部。
为了访问其中的任何函数或常量,你需要显式地指明其所在的命名空间,除非你在作用域级别上打开了整个 std 命名空间,但这通常被认为是不良实践,因为它可能导致命名冲突和其他潜在问题。
Q:简单来说加cmath的必要性就是指定所需要的区域,较少时间呗
A:理解得差不多了,但稍微调整一下会更加准确。以下是简单的解释:
加载 <cmath> 的必要性
-
明确功能来源:
- 包含 <cmath> 头文件是为了告诉编译器你需要使用标准库中的数学函数(如 sin, cos, sqrt 等)。这类似于声明:“我要用这里的工具。”
-
避免命名冲突:
- 尽管所有这些函数都在 std 命名空间中,如果不包含 <cmath>,编译器就不认识这些函数,从而导致错误。
-
提高可维护性和清晰度:
- 明确指出使用的头文件可以让其他开发者更容易理解和维护代码。他们一眼就能看到哪些部分依赖于数学库。
-
性能优化:
- 虽然包含头文件本身不会直接影响运行时性能,但它确实帮助编译器更好地生成高效的机器码。这是因为编译器可以在编译阶段了解所需的具体函数及其实现细节。
总结起来,添加 <cmath> 主要是为了让编译器识别和正确处理你想要使用的数学函数,而不是直接节省运行时间。不过,正确的头文件管理可以帮助编写高效且易于维护的代码。
命名空间主要用于解决命名冲突和组织代码结构。
头文件主要用于共享声明和减少代码冗余。
关于未定义行为:
是指程序执行过程中出现的一种状态,在标准规范中没有明确规定该状态下会发生什么情况。这意味着在这种情况下,程序的行为可能是不可预测的,可能会产生以下几种后果之一:
- 崩溃:程序突然终止运行。
- 异常行为:程序表现出意外的结果或行为。
- 看似正常的输出:有时程序似乎能够正常工作,但这只是偶然现象,未来可能会出现问题。
- 安全漏洞:攻击者可以利用未定义行为来注入恶意代码或其他有害活动。
#include <stdio.h>
#include <math.h>
int main(){
printf("%d\n", sqrt(4)); // 使用%d来打印double类型的结果
return 0;
}
- sqrt(4) 返回的是一个 double 类型的值 2.0。
- printf("%d\n", sqrt(4)) 中,%d 是用来打印 int 类型的值。
- 这种类型不匹配导致了未定义行为,因为在底层,printf 内部会假设接收到的是一个 int 类型的值,并按相应的方式来读取和解释内存中的数据。
结果表现
- 在某些系统上,上述代码可能会输出 0,这是因为 printf 不正确地解码了存储在堆栈上的 double 数据。
当您在 printf 中使用不匹配的格式说明符(例如使用 %d 打印 double 类型的值)时,这种做法确实是未定义行为。
为了避免未定义行为,应确保使用的格式说明符与实际传递的参数类型相匹配。对于 double 类型的值,应当使用 %f 或 %lf 格式说明符。修改如下:
#include <stdio.h>
#include <math.h>
int main(){
printf("%.2f\n", sqrt(4)); // 使用%f来打印double类型的结果
return 0;
}
继续说cmath和math.c:
1、关于头文件的区别:
-
-
<cmath> 是 C++ 标准库的一部分,它包含了所有的数学函数并且支持函数重载。这意味着你可以传递不同类型的数据给同一个函数名称(如 float 或者 double),而不必担心类型转换的问题。
#include <cmath> #include<iostream> using namespace std; int main(){ cout << sqrt(4)<<endl; cout << sin(3.1415926/2); }
-
而 <math.h> 则是 C 语言的标准库,不支持函数重载。所有函数默认只接受并返回双精度浮点数 (double) 参数。如果你试图向这些函数传递其他类型的参数,则可能需要显式地进行类型转换以确保正确的行为。(printf头文件是stdio,但iostream也可能隐式的包涵了printf的头文件stdio)
#include <stdio.h> #include <math.h> int main(){ printf("%lf\n", sqrt((double)4)); // 必须将整数转成double才能正常工作 printf("%lf\n", sin(M_PI / 2)); }
-
关于这一点我自己的总结是,其实没啥差别,如果这样写也可以啊,主要是printf的事并不是math.h还是cmath的事,不过根据上面提到的未定义那个事,math.c默认double,虽说用cout可以完美解决,但编译器底层逻 辑还是一个“只默认接收double”的这个限制,项目大起来就会有其他问题了,先这样吧
#include <math.h>
#include<iostream>
using namespace std;
int main(){
cout << sqrt(4)<<endl;
}
2、math.h: 则将它的所有标识符同时放在全局命名空间和 std 命名空间中,可能导致潜在的名字冲突问题。
了解完这个开始看教程里的number(关于π,C++20之后可以用命名空间里的number),这玩意又折腾老半天,但感觉和深刻,自己追问GPT_AI比看视频掌握的透彻
小知识总结:
不是所有IDE都有编译器,比如codeblock就没有

IDE 与编译器的关系: 一般来说,集成开发环境(IDE)是一个包含了多个工具的软件套件,旨在帮助开发者更方便地进行软件开发,但并不一定总是包含编译器。 有些 IDE 包含编译器: 例如,Visual Studio 包含了 Microsoft Visual C++(MSVC)编译器,它是专门为 Windows 平台开发的,并且这个编译器与 IDE 高度集成,为开发 Windows 应用程序提供了完整的开发环境,包括代码编辑、编译、调试、性能分析、图形化界面设计等功能。IDE 内部的编译器通常是为了方便开发者使用,而且通常与 IDE 紧密结合,可能具有对 IDE 自身的某些特性的支持,比如对 IDE 项目文件的解析和编译优化。 有些 IDE 不包含编译器: Code::Blocks 就是这样的一个例子,它本身是一个开源的 IDE,为用户提供了代码编辑、项目管理、调试等功能,但并不包含编译器。它需要用户在系统上安装编译器(如 GCC 或 MinGW 等),并在 Code::Blocks 中配置相应的编译器路径和选项,以完成代码编译。这样做的好处是可以让用户灵活选择不同的编译器,适用于不同的操作系统和开发场景,如在 Linux 上使用系统自带的 GCC,在 Windows 上使用 MinGW 或其他编译器。 还有些 IDE 可能支持多种编译器: Eclipse 是另一个常见的 IDE,它主要用于 Java 开发,但也可以用于 C/C++ 开发。对于 C/C++ 开发,Eclipse 本身不包含编译器,而是通过 CDT(C/C++ Development Tools)插件来支持多种编译器,用户可以配置使用 GCC、Clang 或其他编译器。这体现了 IDE 的灵活性,它可以与不同的编译器进行集成,以满足不同用户的需求和开发环境。 IDE 的主要目的是为开发者提供一个集成的开发环境,而编译器只是其中的一部分,它负责将源代码转换成可执行程序。有些 IDE 为了方便用户或针对特定的开发场景,会将编译器包含在其软件包中,而另一些 IDE 则为了保持灵活性和可扩展性,允许用户选择不同的编译器。这样可以让用户根据自己的开发需求、操作系统、项目的特殊要求等选择最适合的编译器,而不是局限于 IDE 自带的编译器。 综上所述,IDE 是否包含编译器取决于 IDE 的设计和目标用户群体。有些 IDE 包含编译器是为了方便和特定平台的开发,而有些则不包含,以提供更大的灵活性。
codeblock不自带编译器,我之前是下载GCC然后在codeblock里选择上GCC所在的路径编译的
想看gcc版本直接cmd里gcc -v
而VS2022是自带编译器,VS里运行可查看
#include <iostream> int main() { std::cout << "MSVC 编译器版本: " << _MSC_VER << std::endl; return 0; }
搁置疑惑:豆包说VS是专门给windows用的,linux用的是其他IDE,但如果是这样为啥菜鸟教程开头说linux貌似也是安装VS???没介绍单独Linux的IDE。而且既然VS是自带MSVC,那还安啥GCC啊??好混乱,不想了越想越头疼,也没个专业解答的。这些教程真的好垃圾
number正题:
先放代码 (codeblock里可能编译器不严格,不需要number也可以)
#include <iostream> #include <numbers> int main() { std::cout << "pi: " << std::numbers::pi << std::endl; return 0; }
看了下教程里说的std::numbers::pi
连他自己的编译器都过不了, std::numbers 库是在 C++20 标准中才引入的,2020年的东西,而菜鸟教程是2017年之前写的(下面笔记最早有2017年的)。
折腾半天以为是codeblock版本低了,我一直用的版本是20年的,但发现现在2025年最新版依旧停留在20.03,故不是codeblock的问题。
豆包说 std::numbers::pi
是C++20 标准中才引入的,从GCC 8
开始有一定程度的支持,并且随着版本的更新不断完善,即只支持C++20的一部分,而到GCC 11
才开始全面支持所有C++20的东西,gcc -v
发现是 8.1.0,所以升级GCC,这里我还纠结大半天,环境变量这些最头疼了,知乎直答说半天误人子弟,要什么TDM_GCC或者MSYS2来搞,烦,直接问豆包说官网直接下(MinGW是windows的开源软件集,除了包括GCC还有其他调试器啥的,各种编译环境)
其实直接就是这个。
装完之后是一个w64devkit的文件夹,里面的bin下有gcc.exe啥的,这个是windows上的开发工具包,包涵了GCC啥的,可以理解为简洁版的MinGW,多了我也懒得研究。开始配置环境变量:
搁置疑惑:cmd都是中文正常显示,但w64devkit一开始在一个“下载”的目录下,有包括用户GerJCS岛的中文,这个“岛”字却乱码了,导致gcc -v
的时候bin目录是包含乱码“岛”字的。也是出错点。但其他中文都正常,猜测可能唯独bin目录不允许有中文吧
更换位置,索性放到codeblock下,即C:\Program Files\CodeBlocks\w64devkit
跟MinGW同级(C:\Program Files\CodeBlocks\MinGW
)。然后环境变量配置里,注意:如果想应用新的目录,需要把新的放到旧的前面,这path路径是按照前后顺序来的,(还好奇查了下,path变量误删怎么办),最后呈现的就是
C:\Program Files\CodeBlocks\w64devkit\bin
C:\Program Files\CodeBlocks
C:\Program Files\CodeBlocks\MinGW\bin
但其实只有第一个有用,后两个可以删掉。
cmd里gcc -v
是14.2.0了。还需要把codeblock的这里改下,然后还要把
Other compiler options
下加一句 -std=c++20
。本来是豆包说在这里有关于C++20ISO勾选的,我没找到,就手动加了 -std=c++20
。
至此codeblock完美支持C++20了,问题解决。
VS里是MSVC,需要右键“解决方案”找属性
改成20,之前默认是14,VS也支持C++20了,完美解决。
cmd
运行的话,g++ -std=c++20 your_source_file.cpp -o your_executable
,cmd
也可以了,至于MSVC咋弄懒得了解了,我也没安装MSVC(VS自带的),外部命令行执行的就用GCC
至此经过自己摸索,终于知道GCC、GCC8/11/、C++11/14/20这些东西都咋回事了
关于number查几个小知识
1、std::numbers::pi 不是 GCC 编译器独有的,它是 C++20 标准的一部分,任何符合 C++20 标准的编译器都应该支持。
2、std::stoi
、std::stol
、std::stoll
等字符串到整数的转换是C++11的
之前博客里用过。从GCC 4.8以后貌似就开始支持stoi了
3、最新版是C++23,GCC15.0.1
GCC 4.8开始完整支持C++11
GCC 5开始支持C++14
GCC 7开始支持C++17
GCC 11开始完整支持C++20
GCC 13开始支持C++23
如今我是GCC14.2.0
收获:
不用边学边记录,这样先卯足劲攻克一个问题,比较快。总怕忘记过程。打牌你们是怎么记住的
这是因为 _USE_MATH_DEFINES 必须在任何标准库头文件被包含之前进行定义。
再看另一个代码(即C++20之前是怎么用π的)
#define _USE_MATH_DEFINES // for Visual Studio to define M_PI #include <iostream> #include <cmath> int main() { #ifdef __GNUC__ const double pi = acos(-1)+1; #else const double pi = M_PI; #endif std::cout << pi << std::endl; // 使用定义的pi变量 }
VS:
1、输出3.14159
2、注释掉cmath也可以,豆包、知乎直答、文心GPT都没有结束清楚为啥VS里去掉cmath也可以,大概是有了#define _USE_MATH_DEFINES
就会自动有cmath
或者math.c
吧。反正用的是M_PI
3、而且要将#define _USE_MATH_DEFINES
移到所有 #include
语句的最前面,否则VS里报错。
codeblock:
1、输出是4.14159
,
2、注释掉#define _USE_MATH_DEFINES
那一行也可以,因为只要用到就是acos
解释代码:以下就叫条件编译
#ifdef __GNUC__
:这里__GNUC__
是一个预定义的宏,当使用 GCC(GNU 编译器集合)编译器时会被定义。#define _USE_MATH_DEFINES
:就可以简单理解为,节省空间,即每次上下班带回家基本物品,而你收到领导说“搬工位”指令的时候,才会带走自己所有物品,
1. #define _USE_MATH_DEFINES 的解释: 在 Visual Studio 中,<cmath> 头文件包含了许多数学函数和常量。然而,出于一些与向后兼容性、性能或实现细节相关的原因,并非所有的常量都会默认被包含。 #define _USE_MATH_DEFINES 是一个预处理器指令。当你使用这个指令时,它会通知 Visual Studio 的预处理器去包含一些通常不被包含的额外的数学定义。其中一个额外的定义就是 M_PI。 M_PI 是表示圆周率 π 的常量(约等于 3.14159265358979323846)。如果不使用 #define _USE_MATH_DEFINES,Visual Studio 的预处理器在编译过程中不会包含 M_PI,如果你在代码中尝试使用 M_PI,编译器会报错,因为它不认识这个符号。这就好比告诉编译器 “嘿,我需要你把这些通常隐藏起来的额外数学定义暴露出来”。 2. 关于 Visual Studio 这种行为的解释: Visual Studio 有它自己的设计和实现选择。它可能选择不默认包含 M_PI 是为了避免命名空间污染。在某些情况下,默认包含太多的常量和函数可能会导致命名冲突,或者使编译过程变慢,尤其是在大型项目中,并非所有这些数学常量都会被使用。 另一个可能的原因可能与历史原因有关。随着时间的推移,C++ 标准和库的实现已经发展,不同的编译器对于如何处理这些常量做出了不同的决策。Visual Studio 可能选择了一种更保守的方法,像 M_PI 这样的某些常量只有在用户通过 #define _USE_MATH_DEFINES 明确请求时才会被包含。
-
#define _USE_MATH_DEFINES
- 这行代码告诉 Microsoft Visual Studio 预处理器在 <cmath> 中定义 M_PI 及其他类似的数学常量。
- 如果不定义 _USE_MATH_DEFINES,Visual Studio 默认不会定义 M_PI,从而会导致未定义标识符的错误。
-
#ifdef __GNUC__
- 检查当前编译器是否是 GCC 或者与其兼容的编译器(如 Clang)。
- 如果是,则进入第一个分支。
-
const double pi = acos(-1);
- 使用 acos(-1) 计算 π 的值。
- acos(x) 函数返回 x 的弧度制余弦值的角度,即 arccos(x)。由于 cos(π) = -1,因此 acos(-1) 返回的就是 π。
-
#else
- 如果当前编译器不是 GCC 或 Clang,则进入第二个分支。
-
const double pi = M_PI;
- 直接使用 M_PI 常量。
- 对于支持 M_PI 的编译器(如 GCC、Clang),这行代码有效。
- 对于像 Visual Studio 这样的编译器,只有在定义了 _USE_MATH_DEFINES 后才会成功。
-
#endif
- 结束条件编译块。
#define _USE_MATH_DEFINES
math
库函数math
库函数前面对于VS用的是自带的MSVC:
#define _USE_MATH_DEFINES
#include
头文件的前面math
库函数
再说一下M_PI不是C++标准的一部分
- 不同的操作系统和编译器可能有自己的扩展定义,如果不将其纳入标准,可以保持更大的灵活性和支持多样性。
- 将 M_PI 放入标准可能导致不必要的限制或冲突。旨在确保不同编译器之间的互操作性和一致性。
关于M_PI和number的总结就是:
1、C++20之后用的是 std::numbers::pi来获得π,C++20之前就是通过#define _USE_MATH_DEFINES和math库函数来获取π
2、命名空间 vs 库函数:
- 命名空间是用来组织代码的一种机制,它可以用来避免不同来源的代码之间的名称冲突。例如,标准库的所有实体都在 std 名称空间内,这样即使你自己有一个叫做 sort 的函数也不会与标准库里的排序算法发生冲突。
- 库函数则是具体的功能单元,它们实现了特定的操作或计算任务。这些函数可能存在于某个具体的命名空间之中(如 std::vector, std::map 等)。简言之,命名空间是对一组相关功能的一个分类和管理工具,而库函数则提供了实际执行某些操作的具体方法。
好吧感觉还是有些奇奇怪怪的问题,先这样吧。
关于gcc -v
和gcc --version
,
程度上有所不同:
-
gcc -v:这个命令在显示版本号的同时,还会提供一个详细的编译器配置和编译链的版本信息。它会列出预处理器、编译器、汇编器以及链接器使用的具体版本,并且在执行时会展示出整个编译过程使用的各种库和工具的详细路径,这对于了解编译器工作环境和排查依赖问题非常有帮助。
-
gcc --version:相比之下,这个命令更加简洁,它仅仅输出GCC编译器的版本号,不包含额外的配置或路径信息。这对于快速确认编译器版本或者在脚本中检查兼容性时非常方便。
简而言之:
单杠 - 通常代表一个简短的选项,它后面跟着一个字符,用于快速调用命令的特定功能,-v 提供了“verbose”(详细)信息,当需要深入了解编译器配置或解决环境问题时,可以使用 -v
双杠 -- 代表一个长选项,后面跟着的是单词,提供更明确的命令参数, --version 则是更直接的“version”查询,初学者使用 --version 来快速了解版本
黄金比例:(植物的叶片排列、花瓣数量、海螺的螺旋线黄金比例)(有说叶子是黄金分割率比例和斐波那契而数列的,不研究这个了)
豆包AI真的烦,问了半天云里雾里说的假大空,太玄乎太啰嗦了
最后我自己总结了下,发给他,问他是不是这个意思,他说我总结的对,如下:
关于控制台/终端/命令行/cmd/bash自己,学的时候发现总有这玩意,混乱不堪,理顺一下
在计算机上,
终端像是你和计算机聊天的窗口,你可以在里面输入指令让计算机做事,比如在 Linux 系统的终端里输入命令来查看文件。
控制台更像计算机的 “总控制室”,主要供管理员对计算机系统进行全面管理和监控,比如 Windows 系统的设备管理器,管理员能在这对硬件设备进行各种设置。
CMD是Windows系统中一个功能相对简单,用于执行基本系统命令的终端。
codeblock的控制台:
之前用codeblock刷题的时候说那个运行弹出来的黑色框框就是控制台,它主要用于程序的输入输出。用户可以在其中输入程序所需的数据,程序运行的结果、提示信息等也会在这个控制台中显示出来,方便用户查看程序运行状态和验证结果是否正确,它是程序与用户进行交互的一个重要界面。
但从更准确的角度来说也可以叫终端。因为它为用户提供了与程序进行交互的接口,用户可以输入信息,程序也会在其中输出结果等,符合终端作为用户与计算机系统进行交互接口的定义。通常人们叫它控制台,可能是强调它在这个特定环境下主要用于显示程序运行的输入输出,起到类似于控制和查看程序运行状态的作用,但从技术角度讲它具有终端的功能和属性。
懒得研究解释器 和 编译器啥区别了
总结发现:
刷算法题真的是太简单了,纯网瘾,学C++对我这个初学者最头疼的是
1、配置各种环境啥的,就比如上面提到的。这些工具真的难用,不像codeblock上刷算法题不用操心这些直接写代码。比如更新GCC
2、还有就是各种头文件宏啥的,刚接触看到一头雾水。#if
备注:
###:开始用知乎直答+编辑器TinyMCE5,真他么极品,发现TinyMCE是一坨狗屎,而TinyMCE5就是一坨人屎,没好哪去。
MCE5为啥折叠代码就总出问题,刷新重新编辑是默认MCE版本,会把MCE5的折叠代码取消。
而MCE版本不知道为啥总会在修改文本的时候,修改后的保留,然后莫名其妙的把修改前的垃圾文本和格式一起搞到文章最后面,唉~~~~(>_<)~~~~异常痛苦这个博客园的编译器。TinyMCE是用阳寿开发的东西吗
只能将就用TinyMCE5,涉及到用代码就用TinyMCE的插入代码
###:数学公式
###:知乎直答没豆包智能,相当鸡肋,回答的极其缓慢,4/5个问题就直接卡死,相当卡了,很无语,且编辑框很烂,不方便点击。但很专业,互动性哪里有问题还是问题豆包吧,界面太好了,如果豆包解释的前后矛盾的时候在用知乎直答。知乎直答比文心GPT回答的好,唯独文心GPT比知乎直答快一点
豆包+知乎直答 (VS的Fitten Code就是个智障直接Pass,文心GPT界面受不了,但解答很棒会有奇效,豆包和知乎直答能解决的情况下绝对不会用)(而一般情况也是用豆包,只有豆包说不清楚不专业的时候才会用知乎直答,知乎直答也不行才会用文心GPT)
###:操Ni妈逼的为啥VS的自动补全跟个傻逼脑残一样啊,跳来跳去的,想删东西都删不了,想写啥反倒自作主张给我写成其他代码去了,逼不得已绝不会用傻逼VS
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix