首页 > 编程语言 >为什么C++难?

为什么C++难?

时间:2023-01-10 15:35:47浏览次数:54  
标签:std 为什么 string C++ position Teacher name

C++难就难在:在C++中你找不到任何一件简单的事。

C++学习经历:

1个月: 都说C++难,但聪明如我,一个月就读完了C++ primer,我看这C++也不过如此.
3个月: 原来之前一直在用C++语法写C… 开始正式学习C++…
1年: 今天花一下午一定要琢磨明白这段代码啥意思…
2年: C++太他妈难了, 发明C++的可以吃X去了
3年: 这辈子绝对不能继续搞C++了,珍惜生命,远离C++
4年: ……
5年: 终于神功初成, 大家好,我是C++专家,疑难杂症请问我
7年: 我比较擅长XXX这块,其他方面我是外行
10年: 我有一定的C++基础…

对于多数初学者来说,C++ 的难度并不在语法语意等语言层面,而是没学过 C++ 所支持的面向对象、泛型编程、元编程、函数式编程等不同编程范式,以至于标准模板库(STL)里关于数据结构和算法的知识,甚至一些部分与计算机架构、编译原理、操作系统等知识相关。学习 C++ 可以同时学习、实践这些相关知识。

有人把C++和物理作类比。我很同意。理论物理是一场无尽的旅程,总有最前沿的东西。我对神经科学很感兴趣,也有幸与一个神经科学相关专业的学生交流过,她还给我发过资料,我很感激。然而我在知乎上看到过一个相关的讨论。一个人说“我小时候就想知道大脑是如何工作的,于是我学了神经科学,如今我已经是神经科学博士,依然不知道大脑是如何工作的”。所以我的求知欲只能暂且到此为止。C++亦是如此。

扯远了,我们来说C++有多难吧。

我们只谈构造函数。假如我们有一个类Teacher。

1 2 3 4 5 6 class Teacher { private:     std::string name;     std::string position; };


我们考虑给Teacher类加上构造函数。

1 2 3 4 5 6 7 8 9 10 class Teacher { private:     std::string name;     std::string position;   public:     Teacher(const std::string& n, const std::string& p)         : name(n), position(p) {} };


虽然语义正确,但是如果我们的实参只为了传递给Teacher,传递之后而没有其他作用的话,那么这个实现是效率低下的。字符串的拷贝花销可观(关于std::string的COW,SSO,view的讨论是另一个故事了)。我们在C++11里面有右值引用move语义,所以呢,我们可以改成这样。

1 2 3 4 5 6 7 8 9 10 11 12 13 class Teacher { private:     std::string name;     std::string position;   public:     Teacher(const std::string& n, const std::string& p)         : name(n), position(p) {}       Teacher(std::string&& n, std::string&& p)         : name(std::move(n)), position(std::move(p)) {}; };


你可能觉得这样也已经不错了。不过我们还有可能第一个参数右值,第二个参数左值。或者第一个参数左值,第二个参数右值。所以实际上我们需要四个函数的重载。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Teacher { private:     std::string name;     std::string position;   public:     Teacher(const std::string& n, const std::string& p)         : name(n), position(p) {}       Teacher(std::string&& n, std::string&& p)         : name(std::move(n)), position(std::move(p)) {};       Teacher(const std::string&& n, const std::string& p)         : name(std::move(n)), position(p) {}       Teacher(const std::string& n, const std::string&& p)         : name(n), position(std::move(p)) {} };


代码有点多。我们有没有什么方法写一个通用的函数来实现这四个函数呢?有。我们在C++11中有完美转发

1 2 3 4 5 6 7 8 9 10 11 class Teacher { private:     std::string name;     std::string position;   public:     template <typename S1, typename S2>     Teacher(S1&& n, S2&& p)         : name(std::forward<S1>(n)), position(std::forward<S2>(p)) {}; };


完成了。美滋滋。然而事情没有这么简单。如果我们的position有默认值,然后我们写如下代码的话。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Teacher { private:     std::string name;     std::string position;   public:     template <typename S1, typename S2 = std::string>     Teacher(S1&& n, S2&& p = "lecturer")         : name(std::forward<S1>(n)), position(std::forward<S2>(p)) {}; };     int main() {     Teacher t1 = { "david", "assistant" };     Teacher t2{ t1 }; }

我们出现了编译期错误。因为Teacher t2{ t1 };重载决议的最佳匹配是我们的模板,而不是默认的拷贝构造函数,因为拷贝构造函数要求t1是const的。所以,我们可能需要SFINAEtype traits来修改我们的代码。注意,默认函数参数不能类型推导,所以我们才需要的S2的默认模板参数。

1 2 3 4 5 6 7 8 9 10 11 12 class Teacher { private:     std::string name;     std::string position;   public:     template <typename S1, typename S2 = std::string,     typename = std::enable_if_t<!std::is_same_v<S1, Teacher>>>     Teacher(S1&& n, S2&& p = "lecturer")         : name(std::forward<S1>(n)), position(std::forward<S2>(p)) {}; };


仍然不对哦,因为我们的完美转发有引用折叠机制,我们应该判断的S1是Teacher&而不是Teacher。其次,如果有类继承我们的Teacher的话,拷贝的时候依然会出现这个问题,所以我们需要的不是is_same而是is_convertible。然而,如果我们直接写std::is_convertible_v<S1, Teacher>的话,我们实际上判定是不是可以转换的时候,还是会去看我们的构造函数。也就是说我们自己依赖了自己,无穷递归。所以我们需要的是std::is_convertible_v<S1, std::string>。所以,我们修改我们的代码。

1 2 3 4 5 6 7 8 9 10 11 12 class Teacher { private:     std::string name;     std::string position;   public:     template <typename S1, typename S2 = std::string,     typename = std::enable_if_t<std::is_convertible_v<S1, std::string>>>     Teacher(S1&& n, S2&& p = "lecturer")         : name(std::forward<S1>(n)), position(std::forward<S2>(p)) {}; };


其次,因为我们的默认参数是字面量,字面量是const char[]类型的。我们调用构造函数的时候,也会用字面量。字面量不是std::string类型会造成很多问题。然而在C++14中,我们可以用User-defined literals来把字面量声明成std::string类型的。不过记得命名空间,这个名字空间不在std中。我们这里不再讨论了。

我们接下来讨论用构造函数初始化的问题。初始化有很多种写法,以下我列出有限的几种。

1 2 3 4 5 6 7 8 9 Teacher t1("david"s); Teacher t2 = Teacher("david"s);   Teacher t3{ "lily"s }; Teacher t4 = { "lily"s }; Teacher t5 = Teacher{ "lily"s };   auto t6 = Teacher("david"s); auto t7 = Teacher{ "lily"s };


我们用了auto。然而auto是decay的。而decltype(auto)不。所以,以下代码如果用auto的话可能不是你需要的。

1 2 const Teacher& t8 = t1; auto t10 = t8;

我们需要写const auto&

此外,我们可以看出,用小括号和大括号好像没什么区别。不过,在一些情况下会有很大的差别。我们列出一些。

1 std::vector<int> vec1(30, 5); std::vector<int> vec2{ 30, 5 };

甚至因为C++17的构造函数自动推导,我们可以写出更加疯狂的代码。

1 std::vector vec3{ vec1.begin(), vec2.end() };

这个代码是用初始化列表初始化的,也就是说我们得到的vec3中有两个iterator。

好了,我们回过头来说auto。我们可以看到好像我们所有的初始化都可以用auto。是这样吗?如果我们写atomic的代码呢?

1 auto x = std::atomic<int>{ 10 };

是可以的。因为在C++17中我们有Copy Elision。所以这里没有拷贝函数的调用,和直接定义并初始化是一致的。但是atomic初始化是有问题的。

1 std::atomic<int> x{};

这样是不能零初始化的。当然了,这显然是API的不一致,或者说错误。所以我们有LWG issue 2334。预计在C++20修复这个问题。嘻嘻。

文献提供来自知乎—————https://www.zhihu.com/question/30806886/answer/2612874443

标签:std,为什么,string,C++,position,Teacher,name
From: https://www.cnblogs.com/fiveSTAR/p/17040465.html

相关文章

  • 为什么网络I/O会被阻塞?
    摘要:I/O其实就是input和output的缩写,即输入/输出。本文分享自华为云社区《为啥网络IO会被阻塞呢》,作者:龙哥手记。我们应该都知道socket(套接字),你可以认为我们的通......
  • 为什么网络I/O会被阻塞?
    摘要:I/O其实就是input和output的缩写,即输入/输出。本文分享自华为云社区《​​为啥网络IO会被阻塞呢​​》,作者:龙哥手记。我们应该都知道socket(套接字),你可以认为我们......
  • C++ 图进阶系列之纵横对比 Bellman-Ford 和 Dijkstra 最短路径求解算法
    1.前言因无向、无加权图的任意顶点之间的最短路径由顶点之间的边数决定,可以直接使用原始定义的广度优先搜索算法查找。但是,无论是有向、还是无向,只要是加权图,最短路径长......
  • C和C++的相似而又不同(基础篇)
    前言C语言是C++的基础,在学习C语言的时候我们可以很自然地过渡到C++中,但是尽管它们如此相似,但在某些方面如果搞不清楚可能会引发致命的问题。由标题可知,这一篇我们来讨论C......
  • 【c&c++】C语言 带参数的#define中#和##的基本用法
    1、单#的作用是把参数变成字符串;2、##的作用是连接组合参数名字;废话不多说,看个简洁的例子就明白了#include<iostream.h>usingnamespacestd;#defineTEST0(arg)cou......
  • [C++] std::thread 使用重载函数
    出错代码#include<thread>#include<iostream>#include<utility>#include<vector>#include<string>charreadProcTask(conststd::string&cmd,structtimespe......
  • bzip2 C/C++ 库bzlib.h使用案例:使用实用函数进行压缩/解压缩
    bzip2提供了底层接口,高级接口以及两个实用函数(Utilityfunctions),这两个实用函数在无stdio的环境中也可以使用,它们分别是BZ2_bzBuffToBuffCompress和BZ2_bzBuffToBuffDecomp......
  • 为什么数据安全很重要?
    ​数据安全是在数据的整个生命周期中保护数据免受未经授权的访问、损坏或盗窃的做法。这个概念涵盖了信息安全的各个方面,从硬件和存储设备的物理安全到管理和访问控制,以及软......
  • 面向对象程序设计 第二章 C++简单的程序设计
    目录C++语言的特点1.兼容C语言·它保持了C的简洁、高效和接近汇编语言等特点。·对C的类型系统进行了改革和扩充。·C++也支持面向过程的程序设计,不是一个纯正的面......
  • C++ read 读取字节数与设置不一样
    当需要读取二进制文件时,C++可以采用ofstream流,并设置模式为ios::binary,就可以通过read函数进行按照字节读取了。需要注意的是:如果模式未进行设置,默认将以文本方式读......