@@ -763,73 +763,73 @@ p = &j; // 错误:p 本身也不可变,不能改变指向
763
763
大家都知道,函数的返回类型可以声明为 ` auto ` ,让其自动推导。
764
764
765
765
``` cpp
766
- auto func () { // int func ();
766
+ auto square () { // int square ();
767
767
return 1;
768
768
}
769
769
```
770
770
771
771
但你知道从 C++20 开始,参数也可以声明为 auto 了吗?
772
772
773
773
``` cpp
774
- auto func (auto x) { // T func (T x);
774
+ auto square (auto x) { // T square (T x);
775
775
return x * x;
776
776
}
777
777
778
- func (1); // func (int)
779
- func (3.14); // func (double)
778
+ square (1); // square (int)
779
+ square (3.14); // square (double)
780
780
```
781
781
782
782
等价于以下“模板函数”的传统写法:
783
783
784
784
```cpp
785
785
template <typename T>
786
- T func (T x) {
786
+ T square (T x) {
787
787
return x * x;
788
788
}
789
789
790
- func (1); // func <int>(int)
791
- func (3.14); // func <double>(double)
790
+ square (1); // square <int>(int)
791
+ square (3.14); // square <double>(double)
792
792
```
793
793
794
794
因为是模板函数,所以也很难分离声明和定义,只适用于头文件中就地定义函数的情况。
795
795
796
796
` auto ` 参数还可以带有引用:
797
797
798
798
``` cpp
799
- auto func (auto const &x) { // T func (T const &x);
799
+ auto square (auto const &x) { // T square (T const &x);
800
800
return x * x;
801
801
}
802
802
803
- func (1); // func (int const &)
804
- func (3.14); // func (double const &)
803
+ square (1); // square (int const &)
804
+ square (3.14); // square (double const &)
805
805
```
806
806
807
807
等价于:
808
808
809
809
```cpp
810
810
template <typename T>
811
- T func (T const &x) {
811
+ T square (T const &x) {
812
812
return x * x;
813
813
}
814
814
```
815
815
816
816
` auto ` 参数最好的配合莫过于是与同样 C++20 引入的 concept:
817
817
818
818
``` cpp
819
- auto func (std::integral auto x) { // T func (T x) requires std::integral<T >
819
+ auto square (std::integral auto x) { // T square (T x) requires std::integral<T >
820
820
return x * x;
821
821
}
822
822
823
- func (1); // func (int)
824
- func (3.14); // 错误:double 不是整数类型
823
+ square (1); // square (int)
824
+ square (3.14); // 错误:double 不是整数类型
825
825
```
826
826
827
827
等价于:
828
828
829
829
```cpp
830
830
template <typename T>
831
831
requires std::integral<T>
832
- T func (T x) {
832
+ T square (T x) {
833
833
return x * x;
834
834
}
835
835
```
@@ -838,7 +838,7 @@ T func(T x) {
838
838
839
839
``` cpp
840
840
template <std::integral T>
841
- T func (T x) {
841
+ T square (T x) {
842
842
return x * x;
843
843
}
844
844
```
@@ -881,12 +881,19 @@ cout.flush();
881
881
882
882
如果还用 endl 的话,就相当于刷新了两次,浪费性能。
883
883
884
- 所以,我们只需要输出 ` '\n' ` 就可以了,每次换行时 cout 都会自动刷新,endl 是一个典型的以讹传讹错误写法。
884
+ 可见,endl 是一个被很多无脑教材错误宣传,实际上根本多此一举的东西。
885
+
886
+ 我们只需要输出 ` '\n' ` 就可以了,每次换行时 cout 都会自动刷新。
885
887
886
888
``` cpp
887
889
cout << " Hello, World!" << ' \n ' ;
888
890
```
889
891
892
+ endl 是一个典型的以讹传讹错误写法,只有当你的输出是指向另一个进程的管道时,其附带的刷新功能才有作用。
893
+
894
+ - 当输出是管道时,` cout ` 需要 ` endl ` 才能刷新。
895
+ - 当输出是普通控制台时,` cout ` 需要 ` endl ` 才能刷新。
896
+
890
897
## 多线程中 cout 出现乱序?
891
898
892
899
同学:小彭老师,我在多线程环境中使用:
@@ -943,6 +950,66 @@ cout << std::format("the answer is {}\n", 42);
943
950
std::println ("the answer is {}", 42);
944
951
```
945
952
953
+ ## cerr 与 cout 的抉择
954
+
955
+ 如果你的目的是调试和报错,可以考虑用 `cerr`!
956
+
957
+ 他会在每次 `<<` 时刷新,`cerr` 才是最适合打印错误和调试信息的流。
958
+
959
+ `cout` 的优点是不需要时刻刷新,有更好的性能。
960
+
961
+ ```cpp
962
+ cout << "hello\n";
963
+ cout << "the answer is ";
964
+ cout << 42;
965
+ *(int *)1 = 1; // 崩溃!
966
+ cout << "!\n"; // 因为还没有抵达 \n 产生刷新就崩溃,导致之前尚未刷新的 the answer is 42 丢失
967
+ ```
968
+
969
+ 可能的输出:
970
+
971
+ ```
972
+ hello[换行]
973
+ ```
974
+
975
+ ``` cpp
976
+ cerr << " hello\n " ;
977
+ cerr << " the answer is " ;
978
+ cerr << 42 ;
979
+ *(int *)1 = 1 ; // 崩溃!
980
+ cerr << " !\n " ;
981
+ ```
982
+
983
+ 输出:
984
+
985
+ ```
986
+ hello[换行]
987
+ the answer is 42
988
+ ```
989
+
990
+ 还有一个特点:` cout ` 输出到“标准输出流”,可以被输出重定向到文件管道。而 ` cerr ` 输出到“标准错误流”,通常不会被重定向到文件或管道。
991
+
992
+ 例如,可以把程序预订的计算结果写到 ` cout ` ,把调试和报错信息写到 ` cerr ` ,这样用户就可以通过 ` > ` 重定向计算结果,而调试和报错信息则正常输出到屏幕上,不受重定向影响。
993
+
994
+ ``` cpp
995
+ cout << " 1 3 5 7\n " ;
996
+ cerr << " ERROR: this is an error message!\n " ;
997
+ cout << " 11 13 17 19\n " ;
998
+ ```
999
+
1000
+ ```
1001
+ $ g++ prime.cpp -o prime
1002
+ $ ./prime
1003
+ 1 3 5 7
1004
+ ERROR: this is an error message!
1005
+ 11 13 17 19
1006
+ $ ./prime > output.txt
1007
+ ERROR: this is an error message!
1008
+ $ cat output.txt
1009
+ 1 3 5 7
1010
+ 11 13 17 19
1011
+ ```
1012
+
946
1013
## 智能指针防止大对象移动
947
1014
948
1015
我们说一个类型大,有两种情况。
@@ -1556,7 +1623,7 @@ int main() {
1556
1623
1557
1624
> {{ icon.fun }} ` BIND ` 这个名字是随便取的,取这个名字是为了辱 ` std::bind ` 。
1558
1625
1559
- 为了解决 bind 不能捕获多参数重载的情况,C++17 还引入了 ` std::bind_front ` 和 ` std::bind_back ` ,他们不需要 placeholder,但只能用于参数在最前或者最后的特殊情况 。
1626
+ 为了解决 bind 不能捕获多参数重载的情况,C++17 还引入了 ` std::bind_front ` 和 ` std::bind_back ` ,他们不需要 placeholder,但只能用于要绑定的参数在最前或者最后的特殊情况 。
1560
1627
1561
1628
其中 ` std::bind_front ` 对于我们只需要把第一个参数绑定为 ` this ` ,其他参数如数转发的场景,简直是雪中送炭!
1562
1629
0 commit comments