前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【笔记】《C++Primer》—— 第一部分:C++基础

【笔记】《C++Primer》—— 第一部分:C++基础

作者头像
ZifengHuang
发布2020-07-29 16:07:34
1.4K2
发布2020-07-29 16:07:34
举报
文章被收录于专栏:未竟东方白未竟东方白

说好的总结就是这个了,基本上就是再回看了下之前的7篇笔记并且重新翻翻书梳理了一下,也在此每小节补上了一开始没写的小标题。这篇写起来还是比较轻松的,基本都是从前面的章节复制来的,长度较长,不熟悉的话看起来可能不会很轻松。

下篇就是第二部分,主要是对标准库一些常用小组件的介绍,泛型和C11非常重要的特性“智能指针”,第二部分开始就进入正题了。

1 开始

  1. 命令行中想要知道程序的返回值可以使用echo命令
  2. 很多时候手动输入IO流的参数是很麻烦的,我们可以在运行程序时增加命令行参数" <infile >outfile"来重定向输入输出流,这样就可以直接利用文件中写好的参数进行自动调试
  3. 使用/**/来进行多行注释时要注意不能多个注释符嵌套
  4. IO流对象本身也是有返回值的,如istream在遇到文件结束符(EOF)时会返回一个无效对象,也就是会使得条件判断为false。运用好这点可以简化循环的编写
  5. std::endl的具体效果除了换行,还会将缓冲区中的所有数据刷到设备中,可以保证当前的输出不会只停留在内存缓冲区中
  6. 在Windows中,文件结束符通过在cmd界面按Ctrl+Z和回车键触发,在UNIX系统(包括MacOS)中,使用Ctrl+D触发
  7. C语言的头文件在C++中格式为将.h去掉,再在头部增加"c",如<string.h>变为<cstring>
2 变量和基本类型
  1. wchar_t :确保可以存放机器最大扩展字符集中任意一个字符的“宽字符”,最小尺寸16位
  2. char16_t 和char32_t :负责处理Unicode的字符类型
  3. 当明确知道数值是正的时,选无符号型
  4. 整数一般使用int,否则使用longlong
  5. 浮点类型最荐使用double
  6. 浮点转整数会舍去小数部分
  7. 两个字符串之间若没有逗号等运算符隔开,将会自动合并为一个字符串,这一点可以用来让cout在代码中分行
  8. 在分离式编译时,需要在不同文件中共享一个变量就需要声明它。如果想要声明而不定义一个变量(例如头文件中的变量),那就给它加上extern。但extern标记的变量将不能进行初始化
  9. 自定义标识符(起名字)时,不能连续出现两个下划线,且开头的下划线不能紧接着大写字母,函数体外的标识符不能以下划线开头(单个下划线作为名字是合法的)
  10. 引用即别名,必须被初始化,只能绑定在有确认内存地址的目标上
  11. 在很多情况下(几乎所有情况下),使用nullptr来代替NULL都是更好的选择
  12. void*是一种特殊的指针,可以存放任意类型的对象的地址,称为无类型指针。对于void*来说,它指向的内存空间仅仅是内存空间,没有任何的类型意义。也就是基于这样的想法,不能直接操作void*指针所指的对象,需要进行类型转换
  13. 指针和引用的阅读方法:从右向左进行阅读,左边的符号是用于修饰右边的,一层层向外解析。于是将*和&符紧贴变量名是不错的写法,更方便阅读。但实际上两者并没有什么高低之处
  14. const大多数时候比define要更好,速度也更快
  15. 当需要在多文件中共享一个常量时,最好的做法是在一个单独的文件中定义一个const并加上extern符,然后在需要使用的文件中声明这个const且也加上const符
  16. C11中引入了别名声明(alias declaration),关键字using,其作用是将等号左侧的名字规定为右侧类型的别名,绝大多数时候using与typedef都是相同的
  17. 自动推断类型的关键字auto会顺便进行变量的赋值且会忽视顶层const,所以auto必须要初始化
  18. *和&是属于标识符而不是数据类型,会被auto忽视
  19. 只想推导数据类型而不需要赋值,可以使用decltype关键字
  20. 如果表达式是decltype((x)),则返回的类型永远是引用,而没有双层括号时才是按照x本身状态来判断

3 字符串,向量和数组

  1. 命名空间实际上可以使用using namespace::name;的格式来单独引入几个想要的命名空间声明
  2. 一个小编程规范,不要在头文件中使用using,因为头文件会被拷贝到引用的文件中,可能导致这个using与其他名称冲突
  3. string可以用大于号小于号来比较,是按照字典排序比较的,a>A
  4. string的构造函数的参数可以是(num,char),从而得到重复了num次的char字符串
  5. size_type格式的,是一个机器无关的足以放下任何string的无符号整形数
  6. append()的效率要比+稍好一些
  7. 不用执意用char*来代替string,它们的效率差距远没有那么大
  8. C11增加的range-for,括号中冒号前的是从后面的序列中得到的一个对象,会自动对支持的对象进行遍历
  9. string也是连续内存,一样支持随机访问
  10. vector的特点是可以非常方便地在结尾增加长度,且同样有效率很高的遍历方法,可以理解为变长数组(内部也是连续内存,不是链表实现),可以作为任何需要有序列的对象(不能是引用这种无实体的对象)的容器使用
  11. vector提供了往末尾增加元素的push_back()函数,效率非常高
  12. 和matlab中的矩阵不同,vector不能给不存在的元素赋值
  13. 迭代器(iterator)的操作思路是和C的指针一致的,但是又要比指针安全方便很多
  14. 迭代器的类型比较复杂,一般是容器名<元素类型>::iterator,还有常量迭代器,表示内容是只读的,类型是容器名<元素类型>::const_iterator。由于写起来比较麻烦,大多数时候使用auto来表示会更舒服
  15. 容器的迭代器可以使用支持的容器自带的函数begin()和end(),想要得到常量迭代器的话就使用cbegin()和cend()
  16. 千万不要在使用迭代器的时候改变容器的结构(增减元素),这会使迭代器失效
  17. 迭代器做差得到的返回值是迭代器的距离,类型difference_type,是个有符号数
  18. 数组的索引类型是size_t,也是无符号数
  19. 数组和指针的使用,引入了std::begin(),std::end()函数,可以给数组使用并返回类似上面迭代器的指针
  20. 两个指针相减得到地址差的类型是ptrdiff_t的类型,也是有符号数。可以对指针进行地址的加减来移动指针
  21. 指针可以使用下标符来取值且下标是可以为负数的
  22. c_str()得到的char*实际上是string里的指针,所以不允许修改
  23. 多维数组也就是数组的数组,可以使用多层的花括号来初始化,未初始化的元素遵循之前的规律
  24. 类型别名来给多维数组起别名也可以简化代码
  25. 如果遇到了复杂的多维数组,最好的阅读方式还是对括号就从内向外阅读
4 表达式
  1. 表达式的值有左值右值等复杂区分,详细可以回到4.1看,主要是用来对一些平时比较直觉的操作进行原理性的解释
  2. 表达式最终的值依赖于值组合方式,按照运算符高优先级>低优先级,相同时按照结合律顺序,再相同时从左向右组合对象的值,括号可以无视优先级和结合律,括号内的内容都会当作一个新的单独的表达式进行求值
  3. 一条表达式里的函数是以什么顺序运行的其实是不确定的
  4. 给小类型的值赋值了大数的话会溢出,溢出后具体是卷绕还是其他操作都是未定义的
  5. 利用短路求值的特性,可以用if(s.empty()||s[s.size()-1]=='.')这样的写法,不用担心后半部分是否可以被取值因为前半部分会进行校验
  6. 比较运算中除非比较的对象是bool值否则绝对不要用bool值进行比较,因为bool会被转换为0和1
  7. &&的优先级高于||
  8. 指针不能自动转换为int,即便指针的值为0
  9. 赋值运算符的优先级是很低的,可以利用这一点要增加括号才能简化循环的操作。while((i=getvalue())!=42){;},这样又完成了赋值又完成了检验还增强了可读性
  10. 复合赋值运算符,也就是+=,-=之类的符号,只会进行一次赋值求值,效率比两行赋值符高一点点
  11. 自增自减有前置和后置两个版本,其中后置版本会返回原来的值然后将值加/减1,这导致了性能比前置版本稍差,如无必要全部递增减都应该使用前置版本来提高性能表现
  12. 条件运算符?:优先级很低且效率比if低
  13. 移位操作如何处理符号位是未定义的
  14. sizeof返回的是size_t类型的字节数,因此对char进行sizeof得到的字节是1
  15. sizeof不进行实际运算的类型推断,可对类成员进行推断大小但是返回的大小只会是那个类的默认的固定大小
  16. 可对数组进行sizeof推断可以返回整个数组的大小,但是当这个数组被作为参数传递后这个效果会消失,数组会被转为指针,只能返回指针本身大小了
  17. 逗号运算符比较少用到,它的优先级是所有运算符中最低的。逗号运算符会从左到右对表达式进行运算,最终返回最右边表达式的结果
  18. C++推荐使用的写法是命名的强制类型转换,形式为:cast-name<type>(expr); 这里cast-name是显示写出了需要进行的强制转换的类型
  19. static_cast是最常用最基础的转换,我们平时使用的强制类型转换都可以改成这个。它还可以将编译器无法自动执行的类型进行转换,例如将void*转为其他的指针类型
  20. const_cast比较危险,可以强制去除对象的const,要注意的是const_cast只能改变const性质,无法改变表达式的类型
  21. reinterpret_cast非常危险,它可以将任何指针类型重新指向,例如将char*改为int*,这会很容易引发难以追踪的错误

5 语句

  1. 用花括号括起来的多条语句称为复合语句或语句块,在花括号中的变量有同个作用域,空块相当于空语句
  2. 我们同样也可以在if和switch中定义变量
  3. default标签会在没有任何case匹配时触发,即使不需要这种情况也建议写上一个空的default并加上注释
  4. switch语句由于会进行语句跳转,要求不能跳过变量的初始化而使用变量,且每个case都有内部的一个作用域
  5. 只要控制结构中为真while便会不断执行循环体,如果在while的控制结构或循环体中定义变量的话,这个变量将会在每次迭代中创建又销毁
  6. range-for语句最重要的要求就是expression部分必须是序列,也就是可以得到begin-end成员的序列,且序列中每个元素都要和declaration符合,为保证符合最好的方法是使用auto
  7. do-while语句使得无论条件部分如何都会至少执行一次do中的代码,但是要注意的就是由于这个特点我们不能在do的控制体中定义变量
  8. break语句只能终止最接近的一层循环或者switch
  9. goto语句与swicth类似,不能将程序的运行凌驾在变量的作用域上
  10. C++的异常类在标准库中有定义,分别在stdexcept,exception,new,type_inifo四个头文件中,每个异常我们使用它们都有的what函数来得到异常的信息,详细回到5.6可以查看

6 函数

  1. 建议函数的声明与定义要分开来写,因为函数可以声明多次但只能定义一次,声明建议写在头文件中
  2. 函数形参可以是引用类型,此时传入的实参称为引用传递或传引用调用,传引用形参是实参的别名,要比C风格的指针形参更加有效实用
  3. 函数初始化形参是需要进行拷贝的,这个过程比较低效,所以建议使用引用来避免拷贝
  4. 若需要避免函数对引用参数的修改,则使用常量引用来保证安全性,定义为常量引用是更好的习惯
  5. 传递数组的引用时,注意由于引用必须要有实体,所以需要保证输入的数组大小与形参指定的大小相同
  6. main函数可以带有两个参数,argc和argv,其中argc是命令行调用此程序时附带传入的参数数量,argv是各个参数的字符串形式,要注意若调用为类似这样:prog -d -o a b,此时argc为5,实际参数只有4个,而argv有六个元素。这是由于argv的第一个元素固定为程序调用时所输入的程序名,最后一个元素固定为0
  7. 有几种方法来传入可变数量的实参:一种是当数量未知而类型固定时,使用C11标准库的initializer_list来作为形参,其使用类似于列表,可用其size(),begin(),end()函数来遍历,实参输入时将对应的内容写在花括号中传入;另一种是用到varargs的C标准库功能,常在C风格代码见到,形参列表结尾写省略号“...”,表示忽视多余的实参
  8. 不要返回不可拷贝的局部变量,也不要返回对局部变量的引用或指针
  9. C11规定可以使用花括号,利用vector类型来返回列表值
  10. 返回数组指针时,要注意保持好正确的写法:先看括号,从括号内往括号外看,然后数组的中括号对应的是前面紧接着的数组名,数组的具体元素类型要看数组前面的类型名,用括号来使星号和引用号与类型名相隔离
  11. C11增加了一种更加清晰的返回类型声明方法称为尾置返回类型,方法是写一个返回类型为auto的函数,然后在声明后面用箭头号->指出真正的返回类型
  12. const_cast类型转换在重载中非常有效,主要用于先将函数主干用const写完,然后重载一个普通版本的函数,其中传入的参数都利用const_cast转换为const带给主干函数,运算完再cast后传出。这样既保证了安全性又满足了灵活性
  13. 小作用域中的同名函数会对大作用域中的函数进行隐藏而不是重载,所以需要重载时一定要将函数们写在同个作用域中
  14. inline关键字可指定某函数为内联函数,使得效率变高一些
  15. 内联函数和constexpr由于需要在调用处随时展开,所以需要多次定义,最方便的方法就是将他们的定义写在头文件中
  16. 重载函数的判定问题,具体来说就是函数匹配问题,分为候选函数算则,可行函数选择和寻找最佳匹配三步,具体步骤在6.6
  17. 函数指针的写法比较简单,声明一个函数,然后将函数名改写为(*name)即可,要注意此处括号不可省略因为这会影响星号是与返回类型匹配还是与名称匹配
  18. 使用重载函数指针必须保证函数指针与目标重载函数精确匹配
  19. 需要使用预处理器语句来防止头文件的重复引用造成数据的重复定义
  20. 有些编译器(如vs)支持#progma once语句,但是更通用的方法是使用#ifndef NAME配合#endif预处理符,这对符号只看字面意思就能明白其作用机理了
  21. assert预处理宏是需要依赖于assert.h这个C头文件的,适用于对一个我们可以明确预知的关键表达式进行求值检验,当检验结果为假时,程序输出信息并终止
  22. assert宏依赖与一个叫NDEBUG的预处理变量的状态
  23. 为了方便调试,预处理器还定义了几个非常使用的常量,分别是:存放当前调试的函数名的_ _func_ _,存放调用的文件名的_ _FILE_ _,存放报错时当前行号的_ _LINE_ _,存放文件编译时间的_ _TIME_ _,存放文件编译日期的_ _DATE_ _

7 类

  1. 类在C++中既可以用struct也可以用class,其区别在于默认的访问权限,struct是public的,class是private的
  2. 类内定义的函数默认是隐式的内联函数
  3. this是类的一个隐式常量指针,指向当前所用的这个实例对象,常量指针指的是我们不能改变这个指针指向的地址
  4. 可以在成员函数的参数列表后面加上const,此时的成员函数称为常量成员函数,表示此时的this是一个常量版本的常量指针。我们无法在这个函数中修改这个对象的内容
  5. 一般来说当一个函数概念上属于某个类但并不在类内,则将其与类的声明放在同一个文件中
  6. 对象是在构造初始化完成后(执行构造函数体前的瞬间)获得const或引用等属性的
  7. 当有构造函数时,编译器将不会默认创建构造函数,C11此时可以用 [类名]()=default 来要求编译器生成一个默认行为
  8. 在构造函数和函数体间用冒号连接一段以逗号分隔的调用串,调用名为函数的成员,即为构造函数的初始值列表,可以很方便地在函数体是空时完成构造函数中对值的赋值
  9. 用vector类来代劳内存分配等能避免拷贝构造时复杂的传值过程
  10. 访问控制符有效范围直到下个访问控制符出现或者到达类的结尾,控制符可以出现多次
  11. 有时需要外部的接口函数也可以访问类内的非公有成员,可以在开头加上friend关键字使之成为友元
  12. 友元声明仅仅指示了权限,而不是传统的声明,所以要在类的外部再声明一次(尽管很多编译器不要求这个额外的声明,但建议还是独立声明提高可移植性),同样为了清晰也建议声明在此类的头文件中
  13. 成员前用mutable关键字,使得这个成员必定不会变为const,即使身处const函数之中也可以被改变
  14. 友元函数没有传递性
  15. 声明重载函数为友元时,每一种重载都要分别声明一次
  16. 定义函数时,一旦遇到类名,定义的剩余部分就在类的作用域之内了,这里的剩余部分即后面的参数列表和函数体,不包含前面的返回部分
  17. 类的名字查找过程有几处不同,首先处理类的成员的声明,再当类全部可见后再编译函数体,详细过程在7.4中
  18. 强烈建议不要使函数内部的名字与类内的名字重名
  19. 类成员初始化的顺序是成员在类内声明的顺序而非参数列表的顺序。因此一方面是最好保持参数顺序与声明顺序相同以方便查找,另一方面尽量避免用某些成员来初始化其他的成员以防止初始化顺序导致的问题
  20. C11增加了“委托构造函数”,即可以在构造函数初始值列中调用非委托的构造函数了
  21. 想使用默认构造函数时,方法是初始化对象时不使用后面的调用运算符(即小括号对),如直接写Test a;
  22. 当构造函数*只接受一个*实参时,称转换构造函数,即定义了这种类型的隐式转换机制,在这种情况下我们对实参的输入编译器可以自动地进行*一步*隐式转换
  23. 这种隐式类型转换有时候我们是不希望其启用的,此时我们可以将那个那个构造函数声明为explicit(显式的),它就不会进行隐式转换
  24. explicit只要在类内的声明中写,类外定义时不需要写,且只能用于直接初始化
  25. 一个类所有成员是public,没有构造函数,没有类内初始值且没有基类和虚函数时,这个类称为聚合类
  26. 数据成员都是字面值常量的聚合类或不是聚合类但其成员都是字面值,至少含有一个constexpr构造函数,成员若有类内初始值则必由字面值常量或其自己的constexpr构造且类必须使用默认的析构函数的类,称为字面值常量类
  27. static关键字只出现在类的声明里,不能在外部重复这个关键字,最好将其定义与其他非内联函数放在一起以保证唯一次定义
  28. 由于类内的它的初始化只是声明而已,若外部的函数没有获得类的完整声明则无法使用类内的静态初始化,所以我们应该保持一个良好习惯即即便我们已经类内初始化它,也在外部进行一次定义(但不用初始化)来保证其作用域的正常
  29. 静态成员可以成为函数的默认实参
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 未竟东方白 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档