本次回答邀请boolean,由他来分享自己在学习C++过程中踩过的那些坑~相信对自学C++的朋友会有帮助~

如果,将编程语言比作武功秘籍,C++无异于《九阴真经》。《九阴真经》威力强大、博大精深,经中所载内功、轻功、拳、掌、腿、刀法、剑法、杖法、鞭法、指爪、点穴密技、疗伤法门、闭气神功、移魂大法等等,无所不包,C++亦如是。C++跟《九阴真经》一样,如果使用不当,很容易落得跟周芷若、欧阳锋、梅超风等一样走火入魔。这篇文章总结了在学习C++过程中容易走火入魔的一些知识点。为了避免篇幅浪费,太常见的误区(如指针和数组、重载、覆盖、隐藏等)在本文没有列出,文中的知识点也没有前后依赖关系,各个知识点基本是互相独立,并没有做什么铺垫,开门见山直接进入正文。

目录

1 函数声明和对象定义

2 静态对象初始化顺序

3 类型转换

3.1 隐式转换

3.2 显示转换

4 inline内联

5 名称查找

5.1 受限名称查找

5.2 非受限名称查找

6 智能指针

6.1 std::auto_ptr

6.2 std::shared_ptr

6.3 std::unique_ptr

7 lambda表达式

1 函数声明和对象定义

对象定义写成空的初始化列表时,会被解析成一个函数声明。可以采用代码中的几种方法定义一个对象。

如何自学C++?w1.jpg (23.64 KB, 下载次数: 0)

2020-3-18 16:25 上传

2 静态对象初始化顺序

在同一个编译单元中,静态对象的初始化次序与其定义顺序保持一致。对于作用域为多个编译单元的静态对象,不能保证其初始化次序。如下代码中,在x.cpp和y.cpp分别定义了变量x和y,并且双方互相依赖。

如何自学C++?w2.jpg (4.41 KB, 下载次数: 0)

2020-3-18 16:25 上传

x.cpp中使用变量y来初始化x

如何自学C++?w3.jpg (4.16 KB, 下载次数: 0)

2020-3-18 16:25 上传

y.cpp中使变量x来初始化y

如何自学C++?w4.jpg (10.76 KB, 下载次数: 0)

2020-3-18 16:25 上传

如果初始化顺序不一样,两次执行的结果输出不一样,如下所示:

如何自学C++?w5.jpg (5.13 KB, 下载次数: 0)

2020-3-18 16:25 上传

如何自学C++?w6.jpg (5.39 KB, 下载次数: 0)

2020-3-18 16:25 上传

如果我们需要指定依赖关系,比如y依赖x进行初始化,可以利用这样一个特性来实现:函数内部的静态对象在函数第一次调用时初始化,且只被初始化一次。使用该方法,访问静态对象的唯一途径就是调用该函数。改写后代码如下所示:

如何自学C++?w7.jpg (7.53 KB, 下载次数: 0)

2020-3-18 16:25 上传

getX()函数返回x对象

如何自学C++?w8.jpg (10.69 KB, 下载次数: 0)

2020-3-18 16:25 上传

y对象使用x对象进行初始化

如何自学C++?w9.jpg (9.87 KB, 下载次数: 0)

2020-3-18 16:25 上传

打印x和y值。通过这种方式,就保证了x和y的初始化顺序。

如何自学C++?w10.jpg (10.8 KB, 下载次数: 0)

2020-3-18 16:25 上传

3 类型转换

这里只描述自定义类的类型转换,不涉及如算数运算的类型自动提升等。

3.1 隐式转换

C++自定义类型在以下两种情况会发生隐式转换:

1) 类构造函数只有一个参数或除第一个参数外其他参数有默认值;2) 类实现了operator type()函数;

如何自学C++?w11.jpg (23.29 KB, 下载次数: 0)

2020-3-18 16:25 上传

上面定义了一个Integer类,Integer(int)构造函数可以将int隐式转换为Integer类型。operator int()函数可以将Integer类型隐式转换为int。从下面代码和输出中可以看出确实发生了隐式的类型转换。

如何自学C++?w12.jpg (27.84 KB, 下载次数: 0)

2020-3-18 16:25 上传

隐式类型转换在某些场景中确实比较方便,如:

a、运算符重载中的转换,如可以方便的使Integer类型和内置int类型进行运算

如何自学C++?w13.jpg (14.51 KB, 下载次数: 0)

2020-3-18 16:25 上传

b、条件和逻辑运算符中的转换,如可以使智能指针像原生裸指针一样进行条件判断

如何自学C++?w14.jpg (16.17 KB, 下载次数: 0)

2020-3-18 16:25 上传

隐式类型转换在带来便利性的同时也带来了一些坑,如下所示:

如何自学C++?w15.jpg (21.18 KB, 下载次数: 0)

2020-3-18 16:25 上传

构造函数隐式转换带来的坑。上述代码定义了一个Array类,并重载了operator==运算符。本意是想比较两个数组,但是if(arr1 == arr2)误写成了f(arr1 == arr2[0]),编译器不会抱怨,arr2[0]会转换成一个临时Array对象然后进行比较。

如何自学C++?w16.jpg (21.18 KB, 下载次数: 0)

2020-3-18 16:25 上传

operator type()带来的坑。上述String类存在到const char *的隐式转换,strcat函数返回时String隐身转换成const char *,而String对象已经被销毁,返回的const char *指向无效的内存区域。这也是std::string不提提供const char *隐式转换而专门提供了c_str()函数显示转换的原因。

3.2 显示转换

正是由于隐式转换存在的坑,C++提供explicit关键字来阻止隐式转换,只能进行显示转换,分别作用域构造函数和operator(),如下所示:

1) explicit Ctor(const type &);2) explicit operator type();

如何自学C++?w17.jpg (25.48 KB, 下载次数: 0)

2020-3-18 16:25 上传

用explicit改写Integer类后,需要进行显示转换才能与int进行运算,如下:

如何自学C++?w18.jpg (21.09 KB, 下载次数: 0)

2020-3-18 16:25 上传

为了保持易用性,C++11中explicit operator type()在条件运算中,可以进行隐式转换,这就是为什么C++中的智能指针如shared_ptr的operator bool()加了explicit还能直接进行条件判断的原因。下面代码来自shared_ptr源码。

如何自学C++?w19.jpg (10.45 KB, 下载次数: 0)

2020-3-18 16:25 上传

4 inline内联

内联类似于宏定义,在调用处直接展开被调函数,以此来代替函数调用,在消除宏定义的缺点的同时又保留了其优点。内联有以下几个主要特点:

a、内联可以发生在任何时机,包括编译期、链接期、运行时等;

b、编译器很无情,即使你加了inline,它也可能拒绝你的inline;

c、编译器很多情,即使你没有加inline,它也可能帮你实施inline;

d、不合理的inline会导致代码臃肿。

使用内联时,需要注意以下几个方面的误区:

1)inline函数需显示定义,不能仅在声明时使用inline。类内实现的成员函数是inline的。

如何自学C++?w20.jpg (24.47 KB, 下载次数: 0)

2020-3-18 16:25 上传

2)通过函数指针对inline函数进行调用时,编译器有可能不实施inline

如何自学C++?w21.jpg (15.22 KB, 下载次数: 0)

2020-3-18 16:25 上传

3)编译器可能会拒绝内联虚函数,但可以静态确定的虚函数对象,多数编译器可以inline

如何自学C++?w22.jpg (22.59 KB, 下载次数: 0)

2020-3-18 16:25 上传

4)inline函数有局部静态变量时,可能无法内联

如何自学C++?w23.jpg (10.65 KB, 下载次数: 0)

2020-3-18 16:25 上传

5)直接递归无法inline,应转换成迭代或者尾递归。下面分别以递归和迭代实现了二分查找。

如何自学C++?w24.jpg (24.53 KB, 下载次数: 0)

2020-3-18 16:25 上传

二分查找的递归方式实现。

如何自学C++?w25.jpg (25.66 KB, 下载次数: 0)

2020-3-18 16:25 上传

二分查找的迭代方式实现。

如何自学C++?w26.jpg (17.48 KB, 下载次数: 0)

2020-3-18 16:25 上传

分别调用二分查找的递归和迭代实现,开启-O1优化,通过查看汇编代码和nm查看可执行文件可执行文件符号,只看到了递归版本的call指令和函数名符号,说明递归版本没有内联,而迭代版本实施了内联展开。

如何自学C++?w27.jpg (41.12 KB, 下载次数: 0)

2020-3-18 16:25 上传

6)构造函数和析构函数可能无法inline,即使函数体很简单

如何自学C++?w28.jpg (23.78 KB, 下载次数: 0)

2020-3-18 16:25 上传

表面上构造函数定义为空且是inline,但编译器实际会生成如右侧的伪代码来构造基类成分和成员变量,从而不一定能实施inline。

5 名称查找

C++中名称主要分为以下几类:

a) 受限型名称:使用作用域运算符(::)或成员访问运算符(.和->)修饰的名称。如:::std、std::sort、penguin.name、this->foo等。

b) 非受限型名称:除了受限型名称之外的名称。如:name、foo

c) 依赖型名称:依赖于形参的名称。如:vector::iterator

d) 非依赖型名称:不属于依赖型名称的名称。如:vector::iterator

5.1 受限名称查找

受限名称查找是在一个受限作用域进行的,查找作用域由限定的构造对象决定,如果查找作用域是类,则查找范围可以到达基类。

如何自学C++?w29.jpg (12.61 KB, 下载次数: 0)

2020-3-18 16:25 上传

5.2 非受限名称查找

5.2.1 普通查找:由内向外逐层查找,存在继承体系时,先查找该类,然后查找基类作用域,最后才逐层查找外围作用域

如何自学C++?w30.jpg (18.34 KB, 下载次数: 0)

2020-3-18 16:25 上传

5.2.2 ADL(argument-dependent lookup)查找:又称koenig查找,由C++标准委员会Andrew Koenig定义了该规则——如果名称后面的括号里提供了一个或多个类类型的实参,那么在名称查找时,ADL将会查找实参关联的类和命名空间。

如何自学C++?w31.jpg (13.71 KB, 下载次数: 0)

2020-3-18 16:25 上传

根据类型C的实参c,ADL查找到C的命名空间ns,找到了foo的定义。

了解了ADL,现在来看个例子,下面代码定义了一个Integer类和重载了operator

如何自学C++?w32.jpg (32.71 KB, 下载次数: 0)

2020-3-18 16:25 上传

上面的代码输出什么? 1 1 5 10吗。上面的代码无法编译通过,提示如下错误

如何自学C++?w33.jpg (16.64 KB, 下载次数: 0)

2020-3-18 16:25 上传

operator

如何自学C++?w34.jpg (19.14 KB, 下载次数: 0)

2020-3-18 16:25 上传

把operator

如何自学C++?w35.jpg (12.63 KB, 下载次数: 0)

2020-3-18 16:25 上传

再来看一个名称查找的例子。

如何自学C++?w36.jpg (21.53 KB, 下载次数: 0)

2020-3-18 16:25 上传

这段代码编译时提示如下错误,我们用int *实例化D1的模板参数并给m_value赋值,编译器提示无法将int *转换成int类型,也就是m_value被实例化成了int而不是int *。

如何自学C++?w37.jpg (32.97 KB, 下载次数: 0)

2020-3-18 16:25 上传

我们将代码改动一下,将D2继承B改为B,代码可以顺利编译并输出。

如何自学C++?w38.jpg (21.83 KB, 下载次数: 0)

2020-3-18 16:25 上传

如何自学C++?w39.jpg (11.68 KB, 下载次数: 0)

2020-3-18 16:25 上传

D1和D2唯一的区别就是D1继承自B,D2继承自B。实例化后,为何D1.m_value类型是int,而D2.m_value类型是int *。在分布式事务领域有二阶段提交,在并发编程设计模式中二阶段终止模式。在C++名称查找中也存在一个二阶段查找。

二阶段查找(two-phase lookup):首次看到模板定义的时候,进行第一次查找非依赖型名称。当实例化模板的时候,进行第二次查找依赖型名称。

D1中查找T时,基类B是非依赖型名称,无需知道模板实参就确定了T的类型。

D2中查找T时,基类B是依赖型名称,在实例化的时候才会进行查找。

6 智能指针

6.1 std::auto_ptr

std::auto_ptr是C++98智能指针实现,复制auto_ptr时会转移所有权给目标对象,导致原对象会被修改,因此不具备真正的复制语义,不能将其放置到标准容器中。auto_ptr在c++11中已经被标明弃用,在c++17中被移除。

如何自学C++?w40.jpg (31.15 KB, 下载次数: 0)

2020-3-18 16:25 上传

6.2 std::shared_ptr

std::shared_ptr采用引用计数共享指针指向对象所有权的智能指针,支持复制语义。每次发生复制行为时会递增引用计数,当引用计数递减至0时其管理的对象资源会被释放。但shared_ptr也存在以下几个应用方面的陷阱。

1)勿通过传递裸指针构造share_ptr

如何自学C++?w41.jpg (10.86 KB, 下载次数: 0)

2020-3-18 16:25 上传

这段代码通过一个裸指针构造了两个shared_ptr,这两个shared_ptr有着各自不同的引用计数,导致原始指针被释放两次,引发未定义行为。

2)勿直接将this指针构造shared_ptr对象

如何自学C++?w42.jpg (16.53 KB, 下载次数: 0)

2020-3-18 16:25 上传

这段代码使用同一个this指针构造了两个没有关系的shared_ptr,在离开作用域时导致重复析构问题,和1)是一个道理。当希望安全的将this指针托管到shared_ptr时,目标对象类需要继承std::enable_shared_from_this模板类并使用其成员函数shared_from_this()来获得this指针的shared_ptr对象。如下所示:

如何自学C++?w43.jpg (20.24 KB, 下载次数: 0)

2020-3-18 16:25 上传

3)请勿直接使用shared_ptr互相循环引用,如实在需要请将任意一方改为weak_ptr。

如何自学C++?w44.jpg (22.76 KB, 下载次数: 0)

2020-3-18 16:25 上传

如何自学C++?w45.jpg (6.92 KB, 下载次数: 0)

2020-3-18 16:25 上传

代码运行结果,没有看到打印任何内容,析构函数没有被调用。最终你我都没有jump,完美的结局。但是现实就是这么残酷,C++的世界不允许他们不jump,需要将其中一个shared_ptr改为weak_ptr后资源才能正常释放。

如何自学C++?w46.jpg (18.46 KB, 下载次数: 0)

2020-3-18 16:25 上传

如何自学C++?w47.jpg (9.25 KB, 下载次数: 0)

2020-3-18 16:25 上传

4)优先使用make_shared而非直接构造shared_ptr。make_shared主要有以下几个优点:

a、可以使用auto自动类型推导。

shared_ptr sp(new Object());

auto sp = make_shared();

b、减少内存管理器调用次数。shared_ptr的内存结构如下图所示,包含了两个指针:一个指向其所指的对象,一个指向控制块内存。

如何自学C++?w48.jpg (18.01 KB, 下载次数: 0)

2020-3-18 16:25 上传

如何自学C++?w49.jpg (4.37 KB, 下载次数: 0)

2020-3-18 16:25 上传

这条语句会调用两次内存管理器,一次用于创建Object对象,一次用于创建控制块。如果使用make_shared会一次性分配内存同时保存Object和控制块。

c、防止内存泄漏。

如何自学C++?w50.jpg (13.93 KB, 下载次数: 0)

2020-3-18 16:25 上传

这段代码可能发生内存泄漏。一般情况下,这段代码的调用顺序如下:

new Handler()     ① 在堆上创建Handler对象

shared_ptr()        ②创建shared_ptr

getData()             ③调用getData()函数

但是编译器可能不按照上述①②③的顺序来生成调用代码。可能产生①③②的顺序,此时如果③getData()产生异常,而new Handler对象指针还没有托管到shared_ptr中,于是内存泄漏发生。使用make_shared可以避免这个问题。

如何自学C++?w51.jpg (5.01 KB, 下载次数: 0)

2020-3-18 16:25 上传

这条语句在运行期,make_shared和getData肯定有一个会先调用。如果make_shared先调用,在getData被调用前动态分配的Hander对象已经被安全的存储在返回的shared_ptr对象中,接着即使getData产生了异常shared_ptr析构函数也能正常释放Handler指针对象。如果getData先调用并产生了异常,make_shared则不会被调用。

但是make_shared并不是万能的,如不能指定自定义删除器,此时可以先创建shared_ptr对象再传递到函数中。

如何自学C++?w52.jpg (6.22 KB, 下载次数: 0)

2020-3-18 16:25 上传

6.3 std::unique_ptr

std::unique_ptr是独占型智能指针,仅支持移动语义,不支持复制。默认情况下,unique_ptr有着几乎和裸指针一样的内存开销和指令开销,可以替代使用裸指针低开销的场景。

1)与shared_ptr不同,unique_ptr可以直接指向一个数组,因为unique_ptr对T[]类型进行了特化。如果shared_ptr指向一个数组,需要显示指定删除器。

如何自学C++?w53.jpg (10.93 KB, 下载次数: 0)

2020-3-18 16:25 上传

2)与shared_ptr不同,unique_ptr指定删除器时需要显示指定删除器的类型。

如何自学C++?w54.jpg (19.57 KB, 下载次数: 0)

2020-3-18 16:25 上传

7 lambda表达式

1)捕获了变量的lambda表达式无法转换为函数指针。

如何自学C++?w55.jpg (11.68 KB, 下载次数: 0)

2020-3-18 16:25 上传

2)对于按值捕获的变量,其值在捕获的时候就已经确定了(被复制到lambda闭包中)。而对于按引用捕获的变量,其传递的值等于lamdba调用时的值。

如何自学C++?w56.jpg (25.14 KB, 下载次数: 0)

2020-3-18 16:25 上传

3)默认情况下,lambda无法修改按值捕获的变量。如果需要修改,需要使用mutable显示修饰。这其实也好理解,lambda会被编译器转换成operator() const的函数对象。

如何自学C++?w57.jpg (5.72 KB, 下载次数: 0)

2020-3-18 16:25 上传

4)lambda无法捕捉静态存储的变量。

如何自学C++?w58.jpg (5.94 KB, 下载次数: 0)

2020-3-18 16:25 上传

谭永浩 c语言pdf,如何自学C++?相关推荐

  1. 郭惠,吴迅.单片机c语言程序设计完全自学手册出版时间,基于单片机的多路温度采集器设计...

    时永宝 摘 要:伴随着时代的快速发展,智能将会成为今后的主流方向,按照设定好的思路,在特定的环境里自主工作,不需要人为的操控.在本次研究中,以STC89C51单片机作为核心控制器,用DS18B20温度 ...

  2. 程序设计工程师c语言,《C语言程序设计》自学百问.doc

    <C语言程序设计>自学百问.doc <C语言程序设计> 自学百问 张克军 前 言 为了便于同学们利用业余时间自学<C语言程序设计>课程,按照教学大纲和信息产业部的程 ...

  3. C语言编程新手自学手册下载,C语言编程新手自学手册

    C语言编程新手自学手册 出版时间:2012年01月 定 价:69.80 I S B N :9787111358343 所属分类: 计算机•网络 &nbsp计算机•网络  >  编程语言与 ...

  4. sql语言实践之自学SQL网(SQL Lesson12)

    sql语言实践之自学SQL网(SQL Lesson12) 这是一个目录 sql语言实践之自学SQL网(SQL Lesson12) SQL Lesson 12: 查询执行顺序 练习 do it - 请完 ...

  5. sql语言实践之自学SQL网(SQL Lesson 0-7)

    sql语言实践之自学SQL网(SQL Lesson 0-7) 这是一个目录 sql语言实践之自学SQL网(SQL Lesson 0-7) SQL Lesson 0: SQL Lesson 0: 让我给 ...

  6. c语言应如何自学,C语言如何自学

    答案:C语言自学方法(2) (2) 分支结构 分支结构与顺序结构不同,其执行是依据一定的条件选择执行路径,而不是严格按照语句出现的物理顺序.分支结构的程序设计方法的关键在于构造合适的分支条件和分析程序 ...

  7. 高级语言程序设计与c语言,高级语言程序设计-C语言.pdf

    非计算机专业计算机公共课系列教材 高级语言程序设计 --C 语言 主 编:汪同庆 张 华 参 编:关焕梅 侯梦雅 刘 珺 王 鹃 谭明新 武 汉 大 学 出 版 社 3 非计算机专业计算机公共课 系列 ...

  8. 全网首发,华为出品的495页《看漫画学Python》全彩PDF,自学转行,零基础首选。

    说起编程语言,Python 也许不是使用最广的,但一定是现在被谈论最多的.随着近年大数据.人工智能的兴起,Python 越来越多的出现在人们的视野中. 在各家公司里,Python 还常被用来做快速原型 ...

  9. 谭志强c语言电子书txt,少儿编程while语句试讲

    while 是什么意思 刚学编程 while(表达式){ } while作为编程语言里的一种循环语句,当while后面的括号中表达式成立则进入循环体进行循环,如果不成立则跳过整个while循环语句 用 ...

最新文章

  1. zabbix监控Tomcat
  2. 远程桌面与本地桌面实现文件传输
  3. 小容量单片机生成pdf文件
  4. 「软件项目管理」一文详解软件项目管理概述
  5. mysql group by 规则_mysql 的group by 满足的规则要求:
  6. 当前用户安装anaconda,不需要sudo权限
  7. python整数逆位运算_Python这些位运算的妙用,绝对让你大开眼界
  8. python函数——形参中的:*args和**kwargs
  9. HDU1874 畅通工程续【Dijkstra算法】
  10. 一位区域销售经理百条经验手记
  11. 图书信息管理系统(MVC设计模式)
  12. 傅里叶变换对照表_实验室仪器中英文及缩写对照表
  13. keychron的机械键盘
  14. Vimdiff 使用
  15. 个人永久性免费-Excel催化剂功能第98波-零代码零距离轻松接触并拥有金融大数据...
  16. 2023阿里云账号注册流程(多种注册方法随便选)
  17. 你以为SSL是安全的吗?
  18. php 实现邮件定时发送,php定时发送邮件解决方案
  19. pytorch多gpu DataParallel 及梯度累加解决显存不平衡和显存不足问题
  20. 数据库查询_同时选修了两门课的学生姓名、学号

热门文章

  1. 俄罗斯的顶级数学家,到底有多恐怖?
  2. Django博客系统(博客保存)
  3. Flask-RESTful之响应处理
  4. Flask的HelloWorld程序
  5. Selenium提取数据之标签对象提取文本内容和属性值
  6. 关闭浏览器当前页面:Ctrl+W
  7. ACMNO.40 C语言-子串 有一字符串,包含n个字符。写一函数,将此字符串中从第m个字符开始的全部字符复制成为另一个字符串
  8. 深度学习必备---用Keras和直方图均衡化---数据增强
  9. 机器视觉应用软件开发步骤及流程
  10. Visual Studio中断NPM套件库服务