C++Prime笔记

  1. 开始
  1. 每个C++程序都包含一个或多个函数,其中一个必须命名为main。操作系统通过调用main来运行c++程序。
  2. 函数定义包含四部分:返回类型、函数名、形参列表和函数体。
  3. 标准库定义了四个IO对象。用于处理输入的标准输入:名为cin的istream类型的对象;用于处理输出的ostream类型的对象;用于输出错误和警告信息:名为cerr和clog的ostream对象。
  4. 编译器会忽略注释,因此注释对程序的行为和性能没有任何影响(错误的注释比没有注释更糟糕)。
  5. C++有两种注释符:单行注释和界定符对注释。注释界定符可以放置于任何允许放置制表符、空格符或者换行符的位置。注释界定符通常用于多行解释,双斜线注释常用于单行注释和半行注释。
  6. 注释界定符不能嵌套。
  7. while语句反复执行一段代码,直到给定条件为假为止。
  8. while(condition)
  9.     statement
  10. while语句的执行过程就是交替的检测condition条件和执行关联的statement语句,直至condition条件为假时停止。所谓条件是一个产生结果为真或为假的表达式。只要condition条件为真,statement语句就会被执行。
  11. block语句块就是用花括号包围的零条或多条语句的序列。语句块是语句的一种,在任何要求使用语句的地方都可以使用语句块。
  12. for语句包含两部分,循环头和循环体。循环头控制循环体的执行次数,它包含三部分:一个init-statement初始化语句,一个condition循环条件,一个expression表达式。
  13. for(init-statement;condition;expression)
  14.     statement
  15. 初始化语句只在for循环入口执行一次,循环体每次执行前都会先检查循环条件,当条件为真时执行循环体。表达式在循环体之后执行。
  16. if语句用来支持条件执行。
  17. 成员函数member function是被定义为类的一部分的函数,有时也被称为方法method。
  18. 点运算符.只能用于类类型的对象struct或class。
  19. 调用运算符()可以用来调用一个函数,调用运算符是一对圆括号,括号内放置argument实参列表。

  1. 变量和基本类型
  1. 数据类型是程序的基础,他告诉我们数据的意义以及我们能在数据上执行的操作。
  2. C++定义的基本类型包含算数类型和空类型。其中算数类型包含字符、整型数、布尔值和浮点数。
  3. 算数类型包含两大类:浮点型和整型(包含字符、布尔类型)。
  4. 不同算数类型的数据范围

类型

含义

最小尺寸

bool

布尔类型

未定义

char

字符

8位

wchar_t

宽字符

16位

char16_t

unicode字符

16位

char32_t

unicode字符

32位

short

短整型

16位

int

整型

16位

long

长整型

32位

long long

长整型

64位

float

单精度浮点数

6位有效数字

double

双精度浮点数

10位有效数字

long double

扩展精度浮点数

10位有效数字

  1. C++语言规定,一个int至少和一个short一样大,一个long至少和一个int一样大,一个long long至少和一个long一样大。
  2. 大多数计算机以2的整数次幂个比特作为块来处理,可寻址的最小内存块被称为byte字节,存储的基本单元被称为word字,他通常由几个字节组成。大多数计算机的字节由8比特构成,字则由32或者64比特构成。
  3. 除去布尔型和扩展的字符型以外,其他整型可以划分为signed带符号的和unsigned无符号的两种。带符号的可以表示正数,0和负数,不带符号的只能表示大于等于0的数。
  4. 基本字符型被分为三种:char,signed char 和 unsigned char。字符型的表现形式为两种,带符号的和无符号的。char表现为哪种形式是由编译器决定的。
  5. 如需使用char来存储一个不大的整数,需要明确是signed char或者unsigned char类型。
  6. 执行浮点数运算建议使用double,因为float精度经常不够,且计算效率和double相当。
  7. 当我们赋给带符号类型一个超出他表示范围的值时,结果是未定义的。
  8. 把负数转换成无符号数类似于直接给无符号数赋一个负值,结果等于这个负数加上无符号数的模。
  9. 一个形如42的值被称为字面值常量。字面值常量的形式和值决定了他的数据类型。
  10. 有两类字符程序员不能直接使用:一种是不可打印字符,如退格或其他控制字符,因为他们没有可视的图符;另一类是在C++中有特殊含义的字符(单双引号、反斜线、问号)。在这些情况下需要用到转义序列,转义序列均已反斜杠\开始。
  11. 指定字面值的类型

前缀

含义

类型

 

u

Unicode16字符

char16_t

 

U

Unicode32字符

char32_t

 

L

宽字符

wchar_t

 

u8

UTF-8(仅用作字符串字面值常量)

char

 

整型字面值

浮点型字面值

后缀

最小匹配类型

后缀

类型

u or U

unsigned

f or F

float

l or L

long

l or L

long double

ll or LL

long long

 

 

  1. 变量提供一个具名的,可供程序操作的存储空间。C++中每个变量都有它的数据类型,数据类型决定着变量所占内存空间的大小和布局方式、该空间能存储的值的范围,以及变量能参与的运算。
  2. 定义变量的基本形式:首先是类型说明符,随后紧跟一个或多个变量名组成的列表,其中变量名以逗号隔开,最后以分号结尾。
  3. 通常情况下,对象是指一块能存储数据并具有某种类型的内存空间。
  4. 当对象在创建时获得了一个特定的值,我们就说这个对象被初始化了,用于初始化变量的值可以是任意复杂的表达式。
  5. 在C++中,初始化和赋值是两个完全不同的操作。初始化的含义是在创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值代替。
  6. 创建变量时,使用花括号来进行初始化的方式被称为列表初始化。
  7. 如果定义变量时没有指定默认值,则变量被默认初始化,此时变量被赋予了“默认值”。定义于任何函数体之外的变量被初始化为0,定义在函数体内部的内置变量可能不被初始化,这种情况下它的值是未定义的。
  8. 默认情况下,类型修饰符是从右至左依次绑定的。
  9. C++语言支持分离式编译机制,允许将程序分割成若干个文件,每个文件可被独立编译。
  10. 声明使得名字为编译器所知,一个文件如果想使用别的文件定义的名字则必须包含对那个名字的声明。定义负责创建与名字关联的实体。变量能且只能被定义一次,但可以被多次声明。
  11. C++的标识符由字母、数字和下划线组成,必须以字母或者下划线开头。标识符长度没有限制,但对大小写字母敏感。用户自定义标识符不能连续出现两个下划线,也不能以下划线紧跟大写字母开始。定义在函数体外部的标识符不能以下划线开头。
  12. 作用域是程序的一部分。C++语言中大多数作用域都是以花括号作为分隔的。
  13. 作用域可以彼此嵌套,被包含的作用域称为内层作用域,包含着别的作用域的作用域称为外层作用域。
  14. 复合类型是指基于其他类型定义的类型。
  15. 引用为对象起了另一个名字,引用类型引用另外一种类型。为引用赋值,实际上是把值赋给了与引用绑定的对象。获取引用的值,实际上是获取了与引用绑定的对象的值。
  16. 定义引用时,程序把引用和初始值绑定在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和他的初始值对象一直绑定在一起。因为无法令引用重新绑定到另一个对象,所以绑定必须初始化。
  17. 指针是“指向”另一种类型的复合类型。指针本身就是一个对象,允许对指针拷贝和赋值,在指针的生命周期中,它可以先后指向几个不同的对象。
  18. 指针存放某个对象的地址,要想要获取该地址,需要使用取地址符(操作符&)。如果指针指向了一个对象,则允许使用解引用符(操作符*)来访问该对象。
  19. 试图拷贝或以任何方式访问无效指针的值都将引发错误。
  20. 空指针不指向任何对象,得到空指针最直接的方法是用字面值nullptr来初始化指针,nullptr可以被转换成任意其他的指针类型。也可以使用字面值0来生成空指针。NULL是一个可用于初始化指针的值为0的预处理变量。
  21. void*是一种特殊的指针类型,可用于存放任意类型的地址。
  22. 存在对指针的引用。
  23. 对于被const声明的对象,我们只能对他执行不改变其内容的操作。
  24. 把引用绑定到const对象时,我们称之为对常量的引用。对常量的引用不能被用作修改他所绑定的对象。
  25. 指向常量的指针不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针。指向常量的指针没有规定其所指对象也必须是一个常量。指向常量的指针只要求不能通过该指针改变对象的值。
  26. 常量指针必须初始化,当他初始化完成后,则他的值(存放在指针中的那个地址)不能再被改变。
  27. 顶层const表示指针本身是个常量,底层const表示指针指向的对象是一个常量。
  28. const int *p;指的是一个指向const int类型的地址,是底层const。
  29. int *const p;指的是一个指向int类型的常量指针,是顶层const。
  30. 当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。
  31. 常量表达式指的是值不会改变并且在编译阶段就能得到结果的表达式。常见的常量表达式有字面值,以及用常量表达式初始化的const对象。
  32. 允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。
  33. 定义在函数体之外的对象其地址固定不变,能用来初始化constexpr指针;但在函数体内定义的变量,其地址并不是固定不变的,所以不能用来初始化constexpr指针。
  34. 在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关。
  35. 类型别名是一个名字。使用关键字typedef作为声明语句中基本数据类型的一部分出现。含有typedef的语句定义的是类型别名,这里的的声明符可以包含类型修饰。
  36. C++对于定义类型别名引入了一个新方法,使用别名声明来定义类型的别名,这种方法使用关键字using作为别名声明的开始,后接别名和等号,其作用是把等号左侧的名字换成等号右侧的名字。
  37. 用auto类型说明符可以让编译器替我们分析表达式所属的类型。由于auto通过使用初始值来推算变量的类型,所以auto定义的变量必须有初始值。
  38. auto一般会忽略顶层const,所以如果希望推导出的类型是一个顶层const,则需要明确指出。
  39. 设置一个类型为auto的引用时,初始值中的顶层常量属性仍然保留。
  40. 类型说明符decltype的作用是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到他的类型,但是不实际计算表达式的值。
  41. 如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用)。如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型。如果表达式内容是解引用操作,则decltype返回的是引用类型。
  42. 对于decltype而言,如果使用的表达式加了一对括号,则得到的类型与不加括号会有些区别。如果decltype使用的是不加括号的变量,得到的就是该变量的类型;如果给变量加了一层或多层括号,得到的类型为该变量的引用。
  43. C++11允许为数据成员提供一个类内初始值。创建对象时,类内初始值将用于初始化数据数据成员,没有初始值的成员将被默认初始化。

  1. 字符串、向量和数组
  1. 内置数组是一种基本的数据类型,string和vector都是它的某种抽象。
  2. 可以通过using声明引入命名空间,但位于头文件中的代码应尽量避免使用using声明。using声明的形式如下:using namespace name;。
  3. string对象的初始化方式:

格式

含义

string s1

默认初始化,s3是一个空字串

string s2(s1)

s2是s1的副本

string s2 = s1

等价于s2(s1),s2是s1的一个副本

string s3("value")

s3是字面值value的一个副本,除了字面值最后那个空字符

string s3 = "value"

等价于上一条

string s4(n, 'c')

把s4初始化为由连续的n个字符c组成的字符串

  1. 初始化变量时,如果使用的是等号,实际上执行的是拷贝初始化,编译器会把等号右边的初始值拷贝到新创建的对象中。如果不使用等号,则执行的是直接初始化。
  2. 使用getline可以读取一整行数据(不存入换行符)。Getline只要一遇到换行符就会结束读取操作并返回结果,如果输入时一开始就是换行符,那么所得结果是一个空字符串。
  3. 用于存放string类的size函数返回值的变量,都应该是string::size_type类型的。
  4. 由于size函数返回的是一个无符号数,所以如果在表达式中混用了带符号数和无符号数,可能会产生意想不到的结果。
  5. string类型的对象使用相等运算符比较时,只有两个对象完全一样才是相等的。当使用关系运算符比较时,运算均按照大小写敏感的字典序进行。
  6. 下表运算符[]接收的输入参数是string::size_type类型的值,这个参数表示要访问的元素的位置,返回值是该位置上字符的引用。string对象的下标必须大于等于0而小于string::size。
  7. 下标的值被称为下标或索引,任何表达式只要它的值是一个整型值就能作为索引。但是如果某个索引是带符号的值将自动转换为由string::size_type表达的无符号类型。
  8. 常见的字符运算函数,位于cctype头文件:

函数名称

返回值

isalnum()

如果参数是字母数字,即字母或数字,该函数返回true

isalpha()

如果参数是字母,该函数返回真

isblank()

如果参数是空格或水平制表符,该函数返回true

iscntrl()

如果参数是控制字符,该函数返回true

isdigit()

如果参数是数字(0~9),该函数返回true

isgraph()

如果参数是除空格之外的打印字符,该函数返回true

islower()

如果参数是小写字母,该函数返回true

isprint()

如果参数是打印字符(包括空格),该函数返回true

ispunct()

如果参数是标点符号,该函数返回true

isspace()

如果参数是标准空白字符,如空格、进纸、换行符、回车、水平制表符或者垂直制表符,该函数返回true

isupper()

如果参数是大写字母,该函数返回true

isxdigit()

如果参数是十六进制的数字,即0~9、a~f、A~F,该函数返回true

tolower()

如果参数是大写字符,则返回其小写,否则返回该参数

toupper()

如果参数是小写字母,则返回其大写,否则返回该参数

  1. C++11提供了范围for语句。这种语句会遍历给定序列中的每个元素,并对序列中的每个值执行某种操作。基本语法形式如下:
  2. for (declaration : erpression)
  3.     statement
  4. expression是一个对象,用于表示一个序列。declaration部分负责定义一个变量,该变量将被用于访问该序列中的基础元素。每次迭代,declaration部分的变量都会被初始化为expression部分的下一个元素值。
  5. 标准库类型vector表示对象的集合,常被称为容器,其中所有对象的类型都相同。集合中每一个对象都有一个与之对应的索引,所引用于访问对象。
  6. 编译器根据模板创建类或函数的过程成为实例化,当使用模板时,需要指出编译器应该把类或函数实例化成何种类型。vector并非类型,也是模板,由vector生成的类型必须包含vector中元素的类型,例如vector。
  7. 在某些老的编译器中,如果需要嵌套vector,则必须在外层vector对象的右边尖括号和其元素类型之间添加一个空格,例如vector  >。
  8. vector对象的初始化方法:

格式

含义

vector v1

v1是一个空vector,它的元素是T类型的,执行默认初始化

vector v2(v1)

v2中包含有v1中所以元素的副本

vector v2 = v1

等价于上一条

vector v3(n, val)

v3中包含有n个元素,每个元素的值都是val

vector v4(n)

v4包含了n个重复的执行了值初始化的对象

vector v5{a, b, c …}

v5包含了初始值个数的元素,每个元素被赋予相应的初始值

vector v5 = {a,b,c…}

等价于上一条

  1. C++11允许通过列表初始化的方式为vector对象赋值。
  2. 进行vector初始化时,大多数情况下不同方式可以等价的使用,但有如下三个例外:1.使用拷贝初始化时,只能提供一个初始值;2.如果提供的是一个类内初始值,则只能使用拷贝初始化或者使用花括号的形式进行;3.如果提供的是初始元素值的列表,则只能使用花括号进行列表初始化。
  3. 通常情况下,可以只提供vector的元素数量而略去初始值。此时标准库会创建一个值初始化的元素初值,并把它赋给容器中的所有元素。这个初始值由vector中元素的类型决定。
  4. vector中的元素执行默认初始化时有两个特殊限制:1.如果vector中的元素的类型不支持默认初始化,我们就必须提供初始的元素值;2.如果只提供了元素的数量而没有设定初始值,只能使用直接初始化。
  5. vector进行初始化时,如果使用的是圆括号,则可以说提供的值是用来构造vector对象的;如果使用的是花括号,可以表述成我们想列表初始化该vector对象。
  6. 对于vector对象来说,直接初始化的方式适合三种情况:初始值已知且数量较少、初始值是另一个vector对象的副本、所有元素的初始值都一样。
  7. 在对vector对象使用范围for语句时,不能在循环体中增删元素。
  8. vector对象的下标运算符可用于访问已经存在的元素,但是不能用于添加元素。
  9. 容器对象的end成员返回一个尾后迭代器,该迭代器指向的是一个本不存在的尾后元素,他只是一个标记,没有什么实际的含义。
  10. 所有标准库容器的迭代器都定义了==和!=,但只有少部分定了了运算符,所以如果养成使用迭代器时使用==和!=进行操作,就不需要对迭代器类型做过多的关注。
  11. 迭代器这个名词有三种不同的含义:1.迭代器这个概念本身;2.指容器定义的迭代器类型;3.指迭代器对象。
  12. C++11引入了新的用于获取迭代器对象的函数,cbegin和cend。
  13. string和vector的迭代器提供了更多的运算符,一方面允许迭代器每次移动或者跨过多个元素,另一方面也支持迭代器进行关系运算,所有这些运算被称为迭代器运算。string和vector支持的额外运算符是iter+n、iter-n、iter+=n、iter-=n、iter1-iter2、>、>=、、。
  14. 如果两个迭代器指向的是同一个容器对象中的元素,则它们相减时会得到一个difference_type类型的带符号整型数。string和vector都定义了这个类型,这个类型的对象的值代表了两个元素间的距离。
  15. 数组的大小是固定的,因此数组的运行时性能较好,但是灵活性不足。
  16. 对数组的元素可以进行列表初始化,此时允许忽略数组的维度。如果在声明时没有指明维度,则编译器会根据初始值的数量计算维度;如果指明了维度,则初始化列表中的元素数量应不大于数组维度,当元素数量小于数组维度时,没有提供初始值的元素将被初始化成默认值。
  17. 字符数组在初始化时有一种额外的形式,即用字符串字面值进行初始化,此时在字符串末尾会存在一个空字符。
  18. 不能用数组的内容拷贝给其他数组做其初始值,也不能用数组给其他数组赋值。
  19. 数组下标的类型为名为size_t的无符号类型,存在于头文件stddef.h中。
  20. 很多在用到数组名字的地方,编译器都会自动将其替换成一个指向数组首元素的指针。当使用decltype关键字时,上述转换不会自动发生。
  21. 可以通过标准库函数begin和end获取一个数组的首尾迭代器。
  22. 两个指针相减的结果是一种类型名为ptrdiff_t的有符号类型,定义在stddef.h头文件中。
  23. 数组内置的下标运算符可以处理负值。
  24. 字符串字面值是C风格字符串,这种风格不属于类型,只是一种写法。使用这种写法时,该字符数组将以空字符结尾。
  25. 如果将关系运算符用于C风格字符串上,实际上比较的将是指针而非字符串。
  26. 在string类型对象的加法运算中允许使用字符数组作为其中一个运算对象;在string对象的复合赋值运算中允许使用字符数组作为右侧的运算对象。
  27. 严格来说,C++中不存在多维数组,只存在数组的数组。
  28. 对于二维数组来说,通常把第一个维度称为行,第二个维度称为列。
  29. 可以使用下标运算符来访问多维数组中的元素,此时数组的每个维度对应一个下标运算符。如果表达式中含有的下标运算符和数组维度一样多,则表达式结果是一个给定类型的元素;如果下标运算符小于数组维度,则表达式的结果是一个给定索引处的内层数组。
  30. 对多维数组使用范围for语句时,应该将外层数组的循环控制变量全部声明为引用类型,否则数组可能被自动转换为指针。

  1. 表达式
  1. 表达式由一个或者多个运算对象组成,对表达式求值将得到一个结果。字面值和变量时最简单的表达式,其结果就是字面值和变量的值。把一个运算符和一个或者多个运算对象组合起来可以生成较复杂的表达式。
  2. C++定义了一元运算符、二元运算符和三元运算符。
  3. 小整数类型(如bool、char、short)在运算时通常会被提升为较大的整数类型。
  4. C++语言定义了作用于内置类型和复合类型的运算对象时所执行的操作。当运算符作用于类类型的运算对象时,用户可以自定义其含义。因为这种自定义的过程事实上是为已经存在的运算符赋予了另一层含义,所以称之为运算符重载。
  5. 我们使用重载的运算符时,其包括运算对象的类型和返回值的类型,都是由该运算符定义的,但是运算对象的个数、运算符的优先级和结合律都是无法改变的。
  6. C++表达式的结果只能是左值或右值,左值可以位于赋值语句的左侧,右值则不能。
  7. 当一个对象被用作右值的时候,用的是对象的值;当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。
  8. 常见的几种需要用到左值的运算符:
  9. 赋值运算符需要一个(非常量)左值作为其左侧的运算对象,得到的结果也是一个左值。
  10. 取地址符作用于一个左值运算对象,返回一个指向该对象的指针,这个指针是一个右值。
  11. 内置解引用、下标运算符、迭代器解引用运算符、string和vector的下标运算符的结果都是左值。
  12. 内置类型和迭代器的递增递减运算符作用于左值对象;其前置版本所得的结果是左值。
  13. 使用关键字decltype时,如果表达式的求值结果是左值,decltype作用于该表达式(不是变量)得到一个引用类型。
  14. 算术运算符的运算对象和运算结果都是右值。
  15. 算术运算符满足左结合律,意味着如果运算符优先级相同,则将按照从左至右的顺序组合运算对象。
  16. 括号无视优先级和结合律。
  17. IO相关的运算符满足左结合律。
  18. 前置递增运算符和解引用运算符优先级相同,且都比算数运算符的优先级高。
  19. 优先级规定了运算对象的组合方式,但没有规定运算对象按什么顺序求值。
  20. 有四种运算符明确规定了运算对象的求职顺序。逻辑与(&&)运算符规定了先求左侧运算对象的值,只有当左侧的值为真时才继续求右侧运算对象的值。另外三种是逻辑或(||)运算符、条件(?:)运算符和逗号(,)运算符。
  21. 一元运算符优先级最高,其次是乘法和除法,优先级最低的是加法和减法。
  22. 一元正号运算符、加法运算符和减法运算符都能作用于指针。
  23. 算数表达式有可能产生未定义的结果。比如除数为0或者数据溢出。
  24. 数据溢出有可能导致某对象的值发生环绕:符号位本来是0,由于数据溢出变成了1,导致数据变为负数。
  25. 关系运算符可以作用于算数类型和指针类型,逻辑运算符可作用于任何能转换成布尔值的类型。逻辑运算符和关系运算符的返回值都是布尔类型,它们要求的运算对象和求值结果都是右值。
  26. 类似于逻辑或和逻辑与这样的,当且仅当左侧对象无法确定表达式的结果时才会计算右侧对象的值的策略被称为短路求值。
  27. 关系运算符都满足左结合律,但由于关系运算符的结果是布尔值,所以多个关系运算符同时作用于同一条语句时可能产生意想不到的结果。
  28. 赋值运算符满足右结合律。
  29. 赋值运算符的左侧对象必须是一个可修改的左值。
  30. 赋值运算的结果是它的左侧对象,并且是一个左值。
  31. C++11运行使用花括号括起来的初始化列表作为赋值语句的右侧运算对象。
  32. 赋值运算符的优先级低于关系运算符。
  33. 复合赋值运算符(比如a += b)相比于拆开的两个运算符(比如a=a+b)的区别是:使用复合赋值运算符只求值一次,使用普通的运算符需要求值两次。普通求值运算符的两次求值为:1.一次作为右边表达式的一部分求值,另一次是作为赋值运算符的左侧对象求值。
  34. 递增和递减运算符都有前置和后置两个版本。前置运算符先计算对象的值,然后将改变后的对象作为求值结果;后置版本也会对对象求值,但求值结果是该运算对象改变前的副本。这几种运算符都需要作用于左值运算对象。前置版本将对象本身作为左值返回,后置版本则将对象原始值的副本作为右值返回(相对于前置版本,后置版本开销较大)。
  35. 后置递增运算符的优先级高于解引用运算符。
  36. 大多数运算符并没有规定运算对象的求职顺序。如果在一条语句中多次用到并修改了某个对象的值,很可能会产生未定义的结果。
  37. 点运算符用于获取类对象的一个成员,箭头运算符与点运算符相关。表达式ptr->mem等价于(*ptr).mem。
  38. 点运算符的优先级高于解引用运算符。
  39. 箭头运算符作用于一个指针类型的运算对象,结果是一个左值。点运算符的结果有两种情况:如果成员所属的对象是左值,结果为左值;如果成员所属的对象是右值,则结果为右值。
  40. 条件运算符满足右结合律,且允许互相嵌套。
  41. 当条件运算符的两个表达式都是左值或者能转换成同一种左值类型时,运算的结果是左值,否则运算的结果为右值。
  42. 位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。
  43. 移位运算符满足左结合律。运算优先级比算术运算符低,但比关系运算符、赋值运算符和条件运算符高。
  44. 左移运算符在右侧插入值为0的二进制位。右移运算符的行为则依赖于左侧运算对象的类型:对于无符号类型,则在左侧插入值为0的二进制位;对于有符号类型,则在左侧插入符号位的副本或者值为0的二进制位,如何选择需要看具体环境。
  45. 位求反运算符(~)将运算对象逐位求反后生成一个新值,将1置为0,将0置为1。
  46. 对于位与运算符来说,同1为1,否则为0;对位或运算符来说,有一个为1则为1,否则为0;对于位异或运算符来说,有且只有一个1为1,否则为0。
  47. sizeof运算符满足右结合律,其所得的值是一个size_t类型的常量表达式,sizeof的运算结果可以用来声明数组的维度。sizeof的运算对象有两种形式:
  48. sizeof(type)
  49. sizeof expr
  50. 在第二种形式中,sizeof返回的是表达式结果类型的大小。与众不同的是,sizeof并不实际计算其运算对象的值。
  51. C++11新标准允许我们使用作用域来获取类成员的大小。
  52. sizeof运算符的结果部分的依赖于其作用的类型:
  53. 逗号运算符含有两个运算对象,按照从左至右的顺序依次求值。逗号运算符先对左侧表达式求值,然后将左侧表达式的结果丢掉,最后计算右侧表达式的值,逗号表达式真正的结果是右侧表达式的结果。如果右侧的运算对象时左值,则逗号运算符的运算结果是左值。
  54. C++语言不会将两个不同类型的值直接相加,而是先根据类型转换规则设法将运算对象的类型统一后再求值。
  55. 无需程序员介入,自动执行的类型转换被称作隐式转换。
  56. 在如下情况下,将会发生隐式转换:
  57. 表达式中的整型提升。
  58. 在条件中的非布尔类型转换成布尔类型。
  59. 初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧运算对象的类型。
  60. 算数运算符或者关系运算符中的多个运算对象类型不同时,将会发生隐式转换。
  61. 函数调用时的特殊情况。
  62. 算数转换的含义是把一种算数类型转换成另一种算数类型。一般情况下,运算符的运算对象将会转换成运算对象中最宽的类型。
  63. 整型提升负责把小整数类型转换成较大的整数类型。对于bool、char、signed char、unsigned char、short和unsigned short来说,一般会提升成int型,如果int型不能存入原有类型中所有的值,则会提升成unsigned int型。较大的char(wchar_t、char16_t、char32_t)类型则会提升为int、unsigned int、long、unsigned long、long long、unsigned long long中能存入原有类型的所有值的最小的一个。
  64. 在大多数用到数组的表达式中,数组将自动转换成指向数组首元素地址的指针。当数组被用作decltype关键字的参数,或者作为取地址符、sizeof、typeid等运算符的运算对象时,上述转换将不会发生。如果使用一个引用来初始化数组时,上述转换也不会发生。
  65. 常量整数值0或者字面值nullptr可以转换成任意的指针类型;指向任意非常量的指针能被转换成void*;指向任意类型的指针能被转换成const void*。
  66. 算数类型或指针类型转换成布尔类型时,如果原数据为0,则转换成bool类型的值是false,否则为true。
  67. 允许将任意非常量的指针转换成常量指针,对于引用也是如此。
  68. 类类型能定义由编译器自动执行的转换,不过编译器每次只能执行一种类类型的转换。
  69. 强制类型转换具有如下形式:cast_name(expression)
  70. type是转换的目标类型,expression是要转换的值,cast_name是static_cast、dynamic_cast、const_cast和reinterpret_cast中的一种。
  71. 任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。我们可以使用static_cast找回存在于void*中指针的值。
  72. const_cast只能改变运算对象的底层const。
  73. reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。

  1. 语句
  1. C++语言中大多数语句以分号结尾。一个语句末尾加上分号就成了表达式语句。
  2. 最简单的语句是空语句,该语句中只含有一个单独的分号。使用空语句时应该加上注释。
  3. 复合语句是指的用花括号括起来的语句和声明的序列,复合语句也称为块。一个块就是一个作用域,在块中引入的名字只能在块内和该块的字块中访问。
  4. 如果在程序的某个地方,语法中需要一条语句,但是逻辑上需要多条语句,则应该使用复合语句。
  5. 可以在if、switch、while和for语句的控制结构内定义变量。定义在控制结构内的变量只能在相应语句的内部可见,一旦语句结束,变量就超出其作用范围了。
  6. C++提供了两种条件执行语句。一种是if语句,它根据条件决定控制流;另一种是switch语句,它计算一个表达式的值,然后根据这个值从几条执行路径中选择一条。
  7. if语句的作用是:判断一个指定的条件是否为真,根据判断结果决定是否执行下一条语句。if语句的语法形式如下:
  8. if (condition)
  9.     statement
  10. else
  11.     statement2
  12. 其中,condition可以是一个表达式,也可以是一个初始化了的变量声明。但是不论是哪种情况,都需要其可以转换成布尔类型。
  13. 悬垂else指的是if/else语句相互嵌套时,存在的else数量少于if数,这种情况下else会和最靠近它的未匹配的if进行匹配。
  14. switch语句提供了一条便利的途径使得我们能够在若干固定选项中做出选择。switch语句首先会对括号里的表达式求值,该表达式紧跟在关键字switch的后面,也可以是一个初始化了的变量声明。然后将表达式的值转换成整数类型,和每个case标签的值作比较。如果表达式和某个case标签的值匹配成功,程序会从该标签之后的第一条语句开始执行,直到到达了switch语句的结尾或者遇到了break语句为止。如果表达式的值和所有的case标签都没有匹配上,程序将执行紧跟在default标签后面的语句;如果该switch语句中没有default标签,则直接跳转到switch语句后的第一条语句。
  15. case关键字和它对应的值一起被称为case标签,case标签必须是整型常量表达式。
  16. 如果某个case标签匹配成功,程序将从该标签开始往后顺序执行所有的case分支,除非程序通过break语句显示的中断了这一过程,否则直到switch语句的结尾才会停下来。
  17. 迭代语句通常称为循环。它重复的执行操作直到某个条件满足后才停下来。
  18. MARK165-169
  19. C++提供了4种跳转语句,分别是break,continue,goto和return。
  20. break语句负责终止离它最近的while、do while、for或者switch语句,并从这些语句后面的第一条语句开始继续执行。break语句只能出现在迭代语句或者switch语句内部,其作用范围仅限于离它最近的循环或者switch语句。
  21. continue语句终止最近的循环中的当前迭代并立即开始下一次迭代。continue语句只能出现在迭代语句内部。
  22. goto语句的作用是从goto语句无条件的跳转到同一函数内的另一条语句。goto语句的语法形式如下:
  23. goto label;
  24. label是用于标识一条语句的标识符。带标签语句是一种特殊的语句,在它之前有一个标识符和冒号。标签标示符独立于变量或者其他标示符的名字,标签标识符可以和程序中的其他实体的标示符使用同一个名字而不会相互干扰。goto语句不能将程序的控制权从变量的作用域之外转移到作用域之内。
  25. 异常是指存在于运行时的反常行为,这些行为超出了函数正常功能的范围。
  26. 当程序的某部分检测到了一个他无法处理的问题是,需要用到异常处理。异常处理机制为程序中异常检测和异常处理这两部分的协作提供了支持。
  27. C++语言中的异常处理包括以下部分:
  28. throw表达式:异常检测部分使用throw表达式来表示它遇到了无法处理的问题。我们说throw引发了异常。
  29. try语句块:异常处理部分使用try语句块处理异常。try语句块以try开始,并以一个或者多个catch子句结束。try语句块中代码抛出的异常通常会由某个catch子句处理,由于catch子句处理异常,所以他们也被称为异常处理代码。
  30. 一套异常类:用于在throw表达式和相关的catch子句之间传递异常的具体信息。
  31. 程序的异常检测部分使用throw表达式引发一个异常。throw表达式包含关键字throw和紧随其后的一个表达式,其中表达式的类型就是抛出的异常的类型。throw通常后接一个分号,从而构成一个表达式语句。
  32. try语句的通用语法形式如下:
  33. try{
  34.     program-statement
  35. }
  36. catch(exception)
  37. {
  38.     handler-statements
  39. }…
  40. try语句块的一开始的关键字为try,随后紧跟一个块,跟在try块后面的是一个或多个catch子句。每个catch子句包含三部分:关键字catch,括号内一个(可能是未命名的)对象的声明(称作异常声明)以及一个块。当选中了某个catch子句处理异常后,执行与之对应的块,catch子句执行一旦完成,就会跳转到try语句最后一个catch子句的后面的语句继续执行。
  41. try语句块内部声明的变量在块外部无法访问,即使在catch子句内也无法访问。
  42. 每个标准库异常类都定义了名为what的成员函数,这个函数没有参数,返回值为C风格字符串,返回的内容为初始化一个具体对象时所用的string对象的副本。
  43. 寻找异常处理代码的过程和函数调用链相反。异常被抛出后,首先搜索抛出该异常的函数,如果没有找到匹配的catch子句,则终止该函数,并在调用该函数的函数中继续寻找,如果依然没有对应的catch子句,则继续寻找。以此类推,沿着程序的执行路径逐层回退,直到找到匹配的catch子句。如果最终还是没有找到任何匹配的catch子句,程序会转到名为terminate的标准库函数。一般情况下,执行该函数会导致程序非正常退出。对于那些没有任何try语句定义的异常,也会按照上述方式进行处理,如果一段程序没有try语句块且发生了异常,则系统会调用terminate函数并终止当前函数的执行。
  44. 编写异常安全的代码比较困难:首先,异常中断了程序的正常流程;其次,在处理异常时,我们需要清楚异常何时发生,异常发生后程序应该如何确保对象有效、资源无泄漏、程序处于合理状态等。
  45. exceotion头文件定义了最通用的异常类exception。它只报告异常的发生,不提供任何额外信息。
  46. stdexcept头文件定义了几种常见的异常类:

stdexcept定义的异常类

类型

描述

exception

最常见的问题

runtime_error

只有在运行时才能检测到的问题

range_error

运行时错误:生成的结果超出了有意义的值的范围

overflow_error

运行时错误:计算上溢

underflow_error

运行时错误:计算下溢

logic_error

程序逻辑错误

domain_error

逻辑错误:参数对应的结果值不存在

invalid_argument

逻辑错误:无效参数

length_error

逻辑错误:试图创建一个超出该类型最大长度的对象

out_of_range

逻辑错误:使用一个超出有效范围的值

  1. new头文件定义了bad_alloc异常类型。
  2. type_info头文件定义了bad_cast异常类型。
  3. 标准库异常类只定义了几种运算,包括创建或拷贝异常类型的对象,以及为异常类型的对象赋值。
  4. exception、bad_alloc和bad_cast类型的对象只能通过默认初始化的方式进行初始化,不允许为这些对象提供初始值。其他的类型应该使用string对象或者C风格字符串进行初始化,不允许使用默认初始化的方式,当创建此类对象时,必须提供初始值,该初始值含有错误相关的信息。
  5. what函数返回的C风格字符串的内容与异常对象的类型有关。如果异常类型有一个字符串初始值,则what返回该字符串。对于其他无初始值的异常类型来说,what返回的内容由编译器决定。

  1. 函数
  1. 函数定义包含四部分:返回类型、函数名、由0个或多个形参组成的列表和函数体。其中,形参以逗号隔开,形参的列表位于一对圆括号之内。函数执行的操作在语句块中说明,该语句块被称为函数体。
  2. 我们通过调用运算符来执行函数。调用运算符的形式是一对圆括号,它作用于一个表达式,该表达式是函数或者指向函数的指针;圆括号之内是一个用逗号隔开的实参列表,我们用实参初始化函数的形参。调用表达式的类型就是函数的返回类型。
  3. 函数的调用完成两项工作:一是用实参初始化函数对应的形参,二是将程序的控制权从主调函数转移给被调用函数,此时,主调函数的执行被暂时中断,被调函数开始执行。
  4. 实参是形参的初始值。形参和实参存在对应关系,但是实参的求值顺序没有明确定义。
  5. 执行函数的第一步是隐式的定义并初始化它的形参,然后执行函数体,当遇到一条return语句时函数结束执行过程。当执行return语句时也会完成两项工作:一是返回return语句中的值,二是将控制权从被调函数转移给主调函数。
  6. 如需定义一个不带形参的函数,最常见的方式是书写一个空的形参列表(隐式的定义空形参列表)。不过为了和C语言兼容,也可以使用关键字void表示函数没有形参(显示的定义空形参列表)。
  7. 形参列表中的形参通常用逗号隔开,其中每个形参都是含有一个声明符的声明。
  8. 函数的返回类型不能是数组类型或者函数类型。
  9. 名字有作用域,对象有生命周期。名字的作用域是程序文本的一部分,名字在其中可见。对象的生命周期是程序执行过程中该对象存在的一段时间。
  10. 形参和函数体内部定义的变量统称为局部变量。他们仅在函数的作用域中可见,同时局部变量还会隐藏在外层作用域中同名的其他所有声明中。
  11. 对于普通局部变量对应的对象来说,当函数的控制路径经过变量定义语句时创建该对象,当达到定义所在的块末尾时销毁他。我们把只存在于块执行期间的对象称为自动对象。当块的执行结束后,块中创建的自动对象的值就变成未定义的了。
  12. 形参也属于自动对象。
  13. 对于局部变量对应的自动对象,其初始化方式分为两种情况:如果变量定义本身含有初始值,则使用该初始值进行初始化;如果变量定义本身不含有初始值,则执行默认初始化。
  14. 将局部变量定义成static类型时将会获得局部静态对象,局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,直到程序终止时才被销毁,在此期间即使对象所在的函数结束也不会影响该对象。
  15. 如果局部静态变量没有显式的初始值,它将被执行值初始化,内置类型的局部静态变量初始化为0。
  16. 函数的名字必须在使用前声明。函数可以定义一次,但是可以声明多次。如果一个函数没有被调用过,则可以只有函数声明而没有函数定义。
  17. 函数的声明和定义非常类似,唯一的区别是声明无须函数体,用一个分号替代。因为函数声明没有函数体,所以函数声明中的形参可以只有声明符而没有变量名称。
  18. 函数的三要素(返回类型、函数名、形参列表)描述了函数的接口,说明了调用该函数所需的全部信息。函数声明也被称作函数原型。
  19. 定义函数的源文件应该把含有函数声明的头文件包含进来,编译器负责验证函数的定义与声明是否匹配。
  20. 形参初始化的机理与变量初始化相同,形参的类型决定了形参和实参交互的方式。如果形参是引用类型,它将绑定到对应的实参上;否则,将实参的值拷贝后赋给形参。
  21. 如果形参是引用类型,我们则说它对应的实参被引用传递或者函数被传引用调用。当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。我们说这样的实参被值传递或者函数被传值调用。
  22. 指针做形参时,其行为和其他非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针。因为指针可以使我们间接的访问它所指的对象,所以通过指针可以修改它所指对象的值。
  23. 拷贝大的类类型对象或容器对象比较低效,甚至部分类类型不允许拷贝操作,当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。
  24. 如果函数无须改变引用形参的值,最好将其声明为常量引用。
  25. 当用实参初始化形参时会忽略顶层const。当形参有顶层const时,传给它常量对象或者非常量对象都是可以的。
  26. 我们可以使用非常量对象来初始化一个底层const对象,但是反过来不行。
  27. 在C++语言中,运行我们定义若干具有相同名字的函数,不过前提是不同函数的形参列表应该有明显的区别。由于形参在初始化时会忽略顶层const,所以不能以是否为const类型来区别函数。
  28. 数组有两个特殊的性质:不允许拷贝数组;使用数组时经常会被转换成指针。
  29. 虽然不能以值传递的方式传递数组,但是可以把形参写成类似数组的形式。
  30. 管理数组的三种常见方式如下:
  31. 第一种方式是要求数组本身包含一个结束标记。然后将数组首地址作为实参传入。(void print(const char *cp))
  32. 第二种方式是传递指向数组首元素和尾后元素的指针。(void print(const int *beg,const int *end))
  33. 第三种方式是专门定义一个表示数组大小的形参,只要传递给函数的数组的size值不超过实际的大小,函数就是安全的。(void print(const int ia[],size_t size))(const int ia[]等价于const int *ia)
  34. C++允许将变量定义为数组的引用,由此可知,形参也可以是数组的引用。(void print(int (&arr)[10]))
  35. 为了编写能够处理不同数量实参的函数,C++11提供了两种主要的方法:如果所有的实参类型相同,则可以传递一种名为initializer_list的标准库类型;如果实参的类型不同,我们可以使用可变参数模板来编写特殊的函数。
  36. initializer_list用于表示某种特定类型的值和数组,initializer_list定义在同名的头文件中。

initializer_list提供的操作

initializer_list lst

默认初始化,T类型元素的空列表

initializer_list lst{a,b,c,d...}

lst的元素数量和初始值一样多;lst的元素是对应列表的副本;列表中的元素是const

lst2(lst)

lst2 = lst

拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝和,原对象和副本共享元素

lst.size()

列表中的元素数量

lst.begin()

返回指向首元素的指针

lst.end()

返回指向尾后元素的指针

  1. 对于C++而言,省略符形参是为了便于C++程序访问某些特殊的C代码而设置的,这些代码使用了varargs的C标准库功能。省略符形参应该仅仅用于C和C++通用的类型。还需要注意的是,大多数类类型对象在传递给省略符形参时都无法正常拷贝。
  2. 没有返回值的return语句只能用在返回类型为void的函数中。返回类型为void的函数体内不要求必须有return语句,因为在这类函数最后一句后面会隐式的执行return。如果返回值类型不为void,则return语句的返回值和函数的返回值类型需要相同,或者能隐式的转换成函数的返回类型。
  3. 函数返回一个值的方式和初始化一个变量或者形参的方式完全一样;返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。
  4. 不要返回局部变量的引用或者指针。
  5. 调用一个返回引用的函数得到左值,其他返回类型得到右值。
  6. C++11规定函数可以返回由花括号包围的值的列表,该列表也用来对表示函数返回的临时量进行初始化。如果列表为空,则临时量执行值初始化;否则,返回的值由函数的返回类型决定。
  7. 如果函数返回的是内置类型,则花括号包围的列表最多包含一个值,而且该值所占的空间不应大于目标类型的空间。如果返回类型为类类型,则由类本身定义初始值如何使用。
  8. main函数运行没有return语句,当main函数中没有return语句时,编译器将自动插入一条返回值为0的return语句。
  9. 如果一个函数调用了它自身,不管这种调用是直接的还是间接的,都称该函数为递归函数。
  10. 在递归函数中,一定是有某条路径是不包含递归调用的,否则,程序将不断的调用它自身直到程序的栈空间耗尽为止。如果递归无限循环下去,我们有时会说这种函数含有递归循环。
  11. 由于数组不能被拷贝,所以函数的返回类型不能是数组,但是可以是数组的指针或引用。
  12. 返回数组指针的函数形式如下所示:
  13. type (*function(parameter_list)) [dimension]
  14. type表示元素的类型,dimension表示数组的大小,(*function(parameter_list))两端的括号必须存在,否则将会返回指针的数组。
  15. 在C++11中,提供了另一种返回数组指针的函数定义方式,就是使用尾置返回类型。
  16. int (*func(int i)) [10] 等同于 auto func(int i) -> int(*)[10]
  17. 当decltype关键字对应的表达式是数组时,返回的类型就是数组的类型,decltype不会自动将数组类型转换为指针。如果用在函数的返回类型时,需要在函数声明时加一个*。
  18. 如果同一个作用域中的几个函数名字相同但形参列表不同,我们称之为重载函数。
  19. main函数不能被重载。
  20. 对于重载函数来说,编译器会根据形参的类型决定调用哪个函数,所以重载函数应该在性参数量或形参类型上有所区别。不允许两个函数除了返回类型外其他的所有类型都相同。
  21. 顶层const不影响传入函数的对象,所以一个有顶层const的形参无法和另一个没有顶层const的形参区分开来。
  22. 函数匹配是指一个过程,在这个过程中我们把函数调用与一组重载函数中的某一个关联起来,函数匹配也叫重载确定。编译器首先将调用的实参与重载函数集合中每一个形参比较,然后根据比较的结果决定到底调用哪个函数。
  23. 函数匹配的步骤如下:
  24. 1.选定本次调用对应的重载函数集,集合中的函数被称为候选函数。候选函数有以下特征:与被调用的函数同名;其声明在调用点可见。
  25. 2.考察本次调用提供的实参,然后从候选函数中选出能被这组实参调用的函数,这些新选出的函数被称为可行函数。可行函数有以下特征:其形参数量与本次调用提供的实参数量相等;每个实参的类型和对应的形参类型相同,或者能转换成形参的类型。
  26. 3.从可行函数中选择与本次调用最匹配的函数,实参类型和形参类型越接近,则他们匹配的越好。
  27. 当调用重载函数时,可能有三种结果:
  28. 1.编译器找到一个与实参最佳匹配的函数,并生成调用该函数的代码。
  29. 2.找不到任意一个函数与调用的实参相匹配,此时编译器发出无匹配的错误信息。
  30. 3.有多于一个函数可以匹配,但是每一个都不是明显的最佳匹配。此时也将发生错误,称为二义性调用。
  31. 在不同的作用域中无法重载函数名。
  32. 在函数中,允许形参有默认实参。调用含有默认实参的函数时,可以包含该实参,也可以省略该实参。
  33. 对于函数的声明来说,通常的习惯是将其放在头文件中,并且一个函数只声明一次,但是多次声明同一个函数也是合法的。在给定的作用域中,一个形参只能被赋予一次默认实参。函数的后续声明只能为之前那些没有默认实参的形参添加默认实参,而且该形参右侧所有的形参都必须有默认值。
  34. 只要表达式的类型能转换成形参所需的类型,该表达式就可以作为默认实参。局部变量不能作为默认实参。
  35. 用作默认实参的名字在函数声明所在的作用域内解析,而这些名字的求值过程发生在函数调用时。
  36. 一次函数调用往往包含着一系列工作:调用前要先保存寄存器,并在返回时恢复;可能需要拷贝实参;程序转向一个新的位置继续执行。
  37. 编译器将实参类型到形参类型的转换划分为以下几个等级:
  38. 1.精确匹配:实参类型和形参类型相同;实参从数组类型或函数类型转换成对应的指针类型;向实参添加顶层const或者删除顶层const。
  39. 2.通过const转换实现的匹配。
  40. 3.通过类型提升得到的匹配。
  41. 4.通过算数类型转换或指针转换实现的匹配。
  42. 5.通过类类型转换实现的匹配。
  43. 如果函数重载的区别在于它们的引用类型的形参是否引用了const,或者指针类型的形参是否指向const,则当调用时编译器通过实参是否是常量来决定选择哪个函数。
  44. 函数指针是指向某种特定类型的函数的指针,函数的类型由它的返回类型和形参共同决定,与函数名无关。函数指针指向的是函数而非对象。
  45. 当我们把函数名作为一个值使用时,该函数自动转换为指针。
  46. 当我们使用重载函数的指针时,编译器通过指针类型决定选用哪个函数,指针类型必须与重载函数中的某一个函数精确匹配。
  47. 声明一个返回函数指针的函数的方法:
  48. int (*func(int))(int *, int)等同于int func(int) -> int (*)(int*, int)
  49. 将函数指定为内联函数,通常就是将它在每个调用点上“内联的”展开。inline关键字只是向编译器发出一个请求,编译器可以选择忽略该请求。
  50. 一般来说,内联机制常用来优化规模较小、流程直接、频繁调用的函数。很多编译器都不支持内联递归函数,而且一般超过75行代码的函数也很难在调用点展开。
  51. 和常见的函数不同,内联函数和constexpr函数可以在程序中多次定义。但是对于某个给定的内联函数或者constexpr函数来说,它的多个定义必须完全一致。
  52. assert和NDEBUG都是预处理功能。
  53. 所谓预处理宏其实就是一个预处理变量,它的行为有点类似于内联函数。
  54. assert是一种预处理宏,定义在头文件cassert头文件中。它使用一个表达式作为它的条件:
  55. assert(expr)
  56. 首先对expr求值,如果表达式为假,assert输出信息并终止程序的执行。如果表达式为真则assert什么也不做。
  57. assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查。
  58. 定义NDEBUG能避免检查各种条件所需的运行时开销,当然此时并不会执行运行时检查。因此,assert应该仅用于炎症那些确实不可能发生的事情。我们可以把assert当成调试程序的一种辅助手段,但是不能用它代替真正的运行时逻辑检查,也不能替代程序本身包含的错误检查。
  59. 常用的预处理器定义的名字:
  60. __FILE__存放文件名的字符串字面值
  61. __LINE__存放当前行号的整形字面值
  62. __TIME__存放文件编译时间的字符串字面值
  63. __DATE__存放文件编译日期的字符串字面值

  1. 类的基本思想是数据抽象和封装。数据抽象是一种依赖于接口和实现分离的编程(以及设计)技术。类的接口包括用户能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。
  2. 封装实现了类的接口和实现的分离。封装后的类隐藏了它的实现细节,它的用户只能使用接口而无法访问实现部分。
  3. 类要想实现数据抽象和封装,需要首先定义一个抽象数据类型。在抽象数据类型中,由类的设计者负责考虑类的实现过程;使用该类的程序员只需要考虑类型做了什么,而无需了解类型的工作细节。
  4. 成员函数的声明必须在类的内部,它的定义既可以在类的内部也可以在类的外部。定义在类内部的函数是隐式的inline函数。
  5. C++中运行将const关键字放到成员函数的形参列表后面,用来表示此时的this是一个指向常量的指针。像这样使用const的成员函数被称作常量成员函数。在成员函数形参列表后面的const关键字的作用是修改隐式this指针的类型。在这种情况下,由于this是指向常量的指针,所以常量成员函数不能改变调用它的对象的内容。
  6. 常量对象、指向常量对象的指针或者引用都只能调用常量成员函数。
  7. 类的成员函数的定义嵌套在类的作用域之内。当我们在类的外部定义成员函数时,成员函数的定义必须和它的声明匹配。
  8. 类通过一个或者几个特殊的成员函数来控制其初始化过程,这些函数是构造函数。构造函数的任务是初始化对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。
  9. 构造函数的名字和类名相同,且没有返回类型,但构造函数不能被声明为const的。当我们创建一个const对象时,直到构造函数完成初始化过程,对象才真正取得其“常量”属性。
  10. 类通过一个特殊的构造函数来控制默认初始化过程,这个函数叫做默认构造函数,该函数无需任何实参。
  11. 如果一个类没有显示的定义构造函数,那么编译器会为其隐式的定义一个默认构造函数,由编译器创建的默认构造函数又被称为合成的默认构造函数。合成的默认构造函数会按照如下规则初始化数据成员:如果存在类内初始值,则用它来初始化成员;否则,默认初始化该成员。
  12. 对于类内有内置类型或复合成员的类来说,合成的默认构造函数有可能执行错误的操作,除非这些成员都被赋予了默认初始值。
  13. 如果一个类中包含一个其他类型的成员且该成员的类型中没有默认构造函数,则编译器将无法初始化改成员,这种情况下需要显示定义默认构造函数,因为编译器在这种情况下无法生成默认构造函数。
  14. 在C++11中,允许通过在参数列表后面写上=default来要求编译器生成构造函数。=defautle既可以出现在类内,也可以作为定义出现在类外。
  15. 在构造函数的参数列表后,函数体之前出现的冒号和冒号之间的部分被称为构造函数初始值列表。它负责为新创建的对象的一个或多个数据成员赋初值。没有出现在构造函数初始值列表中的成员将通过相应的类内初始值初始化,如果没有类内初始值,将执行默认初始化。
  16. 如果一个类没有显示的定义拷贝、赋值和销毁操作,则编译器将主动生成这些操作。但是当类需要分配类对象以外的资源时,编译器合成的版本可能会失效。
  17. 对象在以下几种情况下会发生拷贝:我们初始化变量以及以值的方式传递或返回一个对象时和使用赋值运算符时。
  18. 在C++语言中,通过使用访问说明符来增加类的封装性。定义在public说明符之后的成员在整个程序内可被访问,public部分定义类的接口;定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装(隐藏)了类的实现细节。
  19. 一个类可以包含0个或多个访问说明符,且每种访问说明符都能多次出现。每个访问说明符指定了接下来的成员的访问级别,有效范围直到出现下一个访问说明符或者到达类尾为止。
  20. struct和class的唯一区别是默认的访问权限不同。在struct中,定义在第一个访问说明符之前的成员是public的;而在class中,它们是private的。
  21. 类可以允许其他类或函数访问它的非公有成员,方法是另其他类或者函数成为它的友元。如果类想把一个函数作为它的友元,只需要增加一条以friend关键字开头的函数声明语句即可。
  22. 友元声明只能出现在类的内部,但是具体出现的位置不限。友元不是类的成员也不受类内访问说明符的限制。
  23. 友元的声明只是指定了访问的权限,而非一个通常意义上的函数声明。如果我们希望类的用户可以调用某个友元函数,那么我们就必须在声明友元之外再专门对这个函数进行一次声明。为了使友元对类的用户可见,我们通常把友元的声明和类本身放在同一个头文件中(类的外部)。

  1. IO库
  2. 顺序容器
  3. 泛型算法
  4. 关联容器
  5. 动态内存
  6. 拷贝控制
  7. 重载运算和类型转换
  8. 面向对象程序设计
  9. 模板与泛型编程
  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值