C++——重点复习
1.strlen函数总是假定其参数字符串以NULL结束,若字符串不是以NULL结束,计算的结果将不可预料。
2.多个表达式可以由逗号分开,每个表达式的值分别计算,但整个表达式的值是最后一个表达式的值。
3.二维数组转化为一维数组后,二维数组元素a[x][y] = b[x * 列数 + y],其中b是转化后的一维数组。
4.指针数组是一个装有指针的数组,定义为: int *a[10],数组指针是一个指向数组的指针,定义为: int (*a)[10]。
5.在C语言中,将数组作为函数的参数,总是会引发decay(衰退)问题,丢失数组的长度信息。
6.顺序表插入算法、删除算法、查找算法的平均时间复杂度为O(n)。
7.子串是字符串中任意个连续的字符组成的子序列,并规定空串是任意串的子串;子序列则不要求字符连续,但顺序与其在主串中相一致。
8.strcpy与memcpy的一些区别:
1) 复制的内容不同,strcpy只能复制字符串,memcpy可以复制任意内容。strcpy只用于字符串复制,并且它不仅复制字符串内容之外,还会复制字符串的结束符。而memcpy对于需要复制的内容没有限制,因此用途更广。
2) 复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符'\0'时才结束,所以容易溢出。而memcpy则是根据第三个参数决定复制的长度。
3) 用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时,则一般用memcpy。
9.串的模式匹配中,BF算法(最基础算法)的时间复杂度为O((m - n + 1) * n),而KMP算法的时间复杂度为O(m + n)。(其中,m、n代表主串和子串的长度)。
10.在字符串的一些基本函数实现上,例如strcpy的实现,一些需要注意的重点:
1) 将输入参数设置为const,以此来表明该参数的类型。
2) 函数中第一步,应对相应的参数字符串加非0断言,确保字符串不为空。
3) 实现链式操作,将相应的目的地址返回。
11.结构体类型定义应注意:
1) 结构体类型定义中不允许对结构体本身的递归定义。(可以使用指针指向本类型)。
2) 结构体定义中可以包含另外的结构体,即结构体是可以嵌套的。
3) 结构体变量可以在定义时进行初始化赋值。
12.共用体的用途之一是当数据项使用两种或更多种格式(但不会同时使用)时,可节省空间。
13. sizeof是一个运算操作符,并不是函数。它以字节形式给出了其操作数的存储大小,sizeof的计算发生在编译时刻,所以它可以被当作常量表达式使用,且会忽略其括号内的各种运算,如"sizeof(a ++);"中的++不执行。
14.sizeof可用于变量,也可用于数据类型,但需注意的是:
1) sizeof用于变量,变量名可以不用括号括住,但用于数据类型时,数据类型必须用括号括住。
15.strlen与sizeof的区别主要是:sizeof计算数据所用空间时,将'\0'计算在内,而strlen计算字符数组时,遇到'\0'结束,且不将'\0'计算在字符数内。
16.struct的空间计算较为复杂,总体上遵循两个原则:
1) 整体空间是占用空间最大的成员(的类型)所占字节数的整数倍,但在32为Linux+gcc环境下,若最大成员类型所占字节数超过4,如double是8,则整体空间是4的倍数即可。
2) 数据对齐原则——内存按结构体成员的先后顺序排列,当排到该成员变量时,其前面已摆放的空间大小必须是该成员类型大小的整数倍,如果不够则补齐,依次向后类推,但在Linux+gcc环境下,若某成员类型所占字节数超过4,如double是8,则前面已摆放的空间大小是4的整数倍即可,不够则补齐。
17.含有结构体的结构体的空间计算中,需注意子结构体与父结构体这个概念。
18.含有数组的结构体的空间计算中,数组是按照单个变量一个一个进行摆放,而不是视为整体。
19.union的空间大小就是每个成员空间大小的最大值。但也需要考虑对齐问题。
20.enum只是定义了一个常量集合,里面没有"元素",而枚举类型是当做int类型存储的,所以枚举类型的sizeof值都为4。
21.语句与表达式的区别是:表达式具有一个值,而语句没有值。
22.寻找数成对出现时缺失某一个数,可以使用^(按位异或)运算符来进行查找。
23.运算符优先级有几个简单的规则:
1) 括号,下标,-> 和 . (成员)最高。
2) 单目的比双目的高,算术双目的比其他双目的高。
3) 移位运算高于关系运算,关系运算高于按位运算,按位运算高于逻辑运算。
4) 三目的只有一个条件运算,低于逻辑运算。
5) 赋值运算仅比" , "高,且所有赋值运算符优先级相同,结构访问为从右到左。
24.由于预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,所以预处理是不做语法检查的。而且宏定义不分配内存,变量定义才会分配内存。
25.宏替换的本质很简单——文本替换。
26.关于宏定义与宏替换需注意以下几点:
1) 宏名一般用大写,宏名和参数的括号间不能有空格,宏定义末尾不加分号。
2) 宏替换只作替换,不做语法检查,不做计算,不做表达式求解。
3) 宏替换在编译前进行,不分配内存,函数调用在编译后程序运行时进行,并且分配内存。
4) 函数只有一个返回值,利用宏则可以设法得到多个值。
5) 宏替换使源程序变长,函数调用不会。
6) 宏替换不占运行时间,只占编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值)。
27.在不考虑类的情况下,static主要有三条作用:
1) 隐藏。
2) 将变量默认初始化为0.
3) 保持局部变量内容的持久。
28.BSS段是未初始化数据段,data段是初始化数据段。
29.在类定义体中对静态变量赋初值是错误的(基本整型const static数据成员可以在类的定义体中进行初始化)。
30.类中数据成员的布局是:
1) 非静态成员在类对象中的排列顺序和声明顺序一致,任何在其中间声明的静态成员都不会被放进对象布局中。
2) 静态数据成员存放在程序的全局(静态)存储中,和个别类对象无关。
31.静态成员函数无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数与访问静态数据成员。
32.由于static成员不是任何对象的组成部分,所以static成员函数不能被声明为const。
33.关于静态成员函数,可以有以下几点总结:
1) 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数。静态成员函数不能访问非静态成员函数和非静态数据成员,非静态数据成员函数可以任意地访问静态成员函数和静态数据成员。
2) 由于没有this指针的额外开销,因此静态成员函数与类的非静态成员函数相比速度上会有少许增长。
34.使用static成员变量而不是全局变量的优点:
1) static成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突。
2) 可以实施封装。static成员可以是私有成员,而全局对象不可以。
3) 通过阅读程序容易看出static成员是与特定类关联的,这种可见性可清晰的显示程序员的意图。
35.const在C与C++中的区别:
1) C中const的意思是"一个不能被改变的普通变量",在C中,它总是占用存储。C编译器不能把const视为一个编译期间的常量。
2) C中默认const是外部连接,C++中默认const是内部连接。
36.使用const比使用#define有更多有点:
1) const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误。
2) 使用常量可能比使用#define导致产生更小的目标代码,这是因为预处理器"盲目地将宏名称替换为其代替的值"可能导致目标代码出现多份值得备份,但常量就不会出现这种情况。
3) 同时const还可以执行常量折叠,编译器在编译时可以通过必要的计算把一个复杂的常量表达式缩减成较简单的。
37.实现const成员函数,使得可以对const对象产生操作,确保该成员函数可作用于const对象身上。
38.在C++中,static静态成员变量不能在类的内部初始化;const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数;
static const可以创建在整个类中都恒定的常量,并且也不能在类的内部初始化。
39.动态创建const对象必须在创建时初始化,并且一经初始化,其值就不能再修改。
40.delete时,即使操作数是指向const对象的指针,也能同样有效的回收指针所指向的内容。
41. malloc/free 和 new/delete的区别:
相同点: 都可用于申请动态内存和释放内存。
不同点:
1) malloc与free是C/C++语言的标准库函数,new/delete是C++的运算符。
2) new自动计算需要分配的空间,而malloc需要手工计算字节数。
3) new是类型安全的,而malloc不是。
4) new调用operator new 分配足够的空间,并调用相关对象的构造函数,而malloc不能调用构造函数,delete将调用该实例的析构函数,然后调用类的operator delete,以释放该实例占用的空间,而free不能调用析构函数。
5) malloc/free 需要库文件支持,而new/delete则不需要。
42.在调用函数时,传递给函数的实参必须与相应的形参类型兼容。
43.函数调用时,C里面有两种传递:
1) 值传递 2) 指针传递
(严格来说,只有一种传递,值传递,指针传递也是按值传递的,复制的是地址。)
44.函数调用时,C++里面有三种传递方法:
1) 值传递 2) 指针传递 3) 引用传递
45.给函数传递实参遵循变量初始化规则。非引用类型的形参以相应实参的副本(值)初始化。对非饮用形参的任何修改仅作用于局部副本,并不影响实参本身。为了避 免传递副本的开销,可将形参指定为引用类型。对引用形参的任何修改会直接影响实参本身。应将不需要修改相应实参的引用形参定义为const引用。
46.将引用作为函数参数有如下特点:
1) 传递引用给函数,这时,被调用函数的形参就作为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调用函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
2) 使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作,而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量时实参变量的副本,如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
3) 使用指针作为函数的参数,虽然也能达到使用引用的效果,但是,在被调用函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差,另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
47.在类中定义的成员函数全部默认为内联函数。(在类中声明时未加inline,而在类外定义时加了inline,该成员函数也是内联函数)。
48.在编译时,调用内联函数的地方,将不进行函数调用,而是使用函数体替换调用处的函数名,形式类似宏替换,这种替换称为内联展开。
49.内联扩展可以消除函数调用时的时间开销。
50.一般来说,内联机制适用于优化小的、只有几行的而且经常被调用的函数。(大多数编译器都不支持递归函数的内联。)
51.关于默认参数:
1) 默认参数只可在函数声明中设定一次。只有在无函数声明时,才可以在函数定义中设定。
2) 默认参数定义的顺序为自右到左。即如果一个参数设定了默认值时,其右边的参数都要有默认值。
3) 默认参数调用时,遵循参数调用顺序,自左到右逐个调用。
4) 默认值可以是全局变量、全局常量,甚至是一个函数,但不可以是局部变量。因为默认参数的调用时在编译时确定的,而局部变量位置与默认值在编译时无法确定。
52.函数重载要求编译器能够唯一地确定调用一个函数时,应执行哪个函数代码,即采用哪个函数实现。
53.进行函数重载时,要求同名函数在参数个数上不同,或者参数类型上不同。
54.宏定义与内联函数的区别:
1) 宏定义是在预处理阶段进行代码替换,而内联函数是在编译阶段插入代码。
2) 宏定义没有类型检查,而内联函数有类型检查。
55.递归的精髓在于能否将原始问题转换为属性相同但规模较小的问题。
56.一个有效的指针必然是以下三种状态:
1) 保存一个特定对象的地址 2) 指向某个对象后面的另一对象 3) 为0值
57.void*指针只支持几种有限的操作:
1) 与另一指针进行比较
2) 向函数传递void*指针或从函数返回void*指针
3) 给另一个void*指针赋值
58.不允许使用void*指针操纵它所指向的对象。
59.C++中规定,一旦定义了引用,就必须把它跟一个变量绑定起来,并且不能修改这个绑定。
60.不能定义引用类型的引用,其他类型均可。
61.引用与指针的区别:
1) 引用不能为空,当引用被创建时,必须初始化它;而指针可以为空,可以在任何时候被初始化。
2) 一旦一个引用被初始化为指向一个对象,它就不能被改变为对另一个对象的引用。指针则可以在任何时候指向另一对象。
3) 不可能有NULL引用。必须确保引用是和一块合法的存储单元关联。
4) "sizeof(引用)"得到的是所指向对变量(对象)的大小,而"sizeof(指针)"得到的是指针本身的大小。
5) 给引用赋值修改的是该引用所关联的对象的值,而并不是使引用与另一对象关联。
6) 引用使用时不需要解引用,而指针需要解引用,引用和指针的自增(++)操作运算意义不一样。
7) 如果返回动态分配的对象或内存,必须使用指针,引用可能引起内存泄露。
8) 当使用&运算符取一个引用的地址时,其值为所引用变量的地址,而对指针使用&运算,取的是指针变量的地址。
62.引用类型数据成员的初始化有以下特点:
1) 不能直接在构造函数里初始化,必须用到初始化列表。
2) 凡是有引用类型的数据成员的类,必须定义构造函数。
63. 类对其成员的访问形式主要有以下两种:
1) 内部访问: 由类中的成员函数对类的成员的访问。
2) 对象访问: 在类外部,通过类的对象对类的成员的访问。
64.使用类的默认构造函数初始化类中的成员:
1) 类成员: 运行该类型的默认构造函数来初始化。
2) 内置或复合类型的成员的初始值依赖于对象的作用域: 在局部作用域中这些成员不被初始化,而在全局作用域中它们被初始化为0.
65. 一个没有默认构造函数的类(NoDefault):
1) 具有NoDefault成员的每个类的每个构造函数,必须在成员初始化列表中通过传递一个初始的值给NoDefault构造函数显式的初始化NoDefault成员。
2) 编译器将不会为具有NoDefault类型成员的类合成默认构造函数,如果这样的类希望提供默认构造函数,就必须显式的定义,并且默认构造函数必须显式的初始化其NoDefault成员。
3) NoDefault类型不能做动态分配数组的元素类型。
4) NoDefault类型的静态分配数据必须为每个元素提供一个显式的初始化式。
5) 如果有一个保存NoDefault对象的容器,例如vector,就不能使用接受容器大小而没有同时提供一个元素初始化式的构造函数。
66.在C++中,成员变量的初始化顺序与变量在类型中声明顺序相同,而与它们在构造函数的初始化列表中的顺序无关。
67.从概念上讲,可以认为构造函数分两个阶段执行:
1) 初始化阶段 2) 普通的计算阶段。计算阶段由构造函数函数体中的所有语句组成。
(初始化发生在计算阶段开始之前,计算阶段是赋值操作。)
68.必须使用成员初始化列表的几种情况:
1) 没有默认构造函数的类类型的成员
2) const类型的成员变量
3) 引用类型的成员变量
69.复制构造函数、赋值操作符和析构函数总称为复制控制。
70.如果类需要析构函数,则它也需要赋值操作符和复制构造函数。这个规则常称为三法则。
71.拷贝构造函数的参数必须是一个引用,如果我们采用传值的方式,就会调用该类的拷贝构造函数,从而造成无穷递归调用拷贝构造函数。
72.注意深复制与浅复制的问题。
73.当基类指针pA指向用new运算符生成的派生类对象B时,delete基类指针时,只会运行基类的析构函数,而不会执行派生类的析构函数,派生类部分没有释放掉从而造成释放不彻底现象。(基类的析构函数不是虚函数造成的问题)
74.虚继承的继承顺序:
1) 首先调用虚基类的构造函数
2) 如果有多个虚基类,按照虚基类在类派生列表中的出现次序调用
75.赋值操作符重载的注意点:
1) 是否将返回值的类型声明为该类型的引用,并在函数结束前返回实例自身(即*this)的引用。
2) 是否将传入的参数的类型声明为常量引用。
3) 是否记得释放实例自身已有的内存。
4) 是否判断传入的参数是不是和当前实例(*this)为同一实例。
76.并不是出现"="就是调用赋值构造函数。
77.重载<<运算符时,ostream不能为const引用,因为要写入到流会改变流的状态,同时,ostream的形参必须是一个引用,因为不能复制ostream对象(ostream的拷贝构造函数访问权限不是public)。
78.operator new的特点是:
1) 只分配所要求的空间,不调用相关对象的构造函数。
2) 可以被重载。 3)重载时,返回类型必须声明为void*。 4) 重载时,第一个参数类型必须为表达要求分配空间的大小(字节),类型为size_t。
5) 重载时,可以带其它参数。
79.禁止产生堆对象的方法,就是将operator new重载为private(为了对称,最好将operator delete也重载为private)。
80.禁止产生栈对象的方法,就是将构造函数或析构函数设为private。
81.成员函数被重载的特征:
1) 相同的范围(在同一个类中)。 2) 相同的函数名字。 3) 不同的参数列表。 4) virtual关键字可有可无。
82.覆盖的特征如下:
1) 不同的范围(分别位于派生类与基类) 2) 相同的函数名字 3) 相同的参数 4)基类函数必须由virtual关键字
83.重载与覆盖的区别:
1) 覆盖是子类和父类之间的关系,是垂直关系;重载是同一个类中不同方法之间的关系,是水平关系
2) 覆盖要求参数列表相同,重载要求参数列表不同;覆盖要求返回类型相同,重载则不要求
3) 覆盖关系中,调用方法体是根据对象的类型来决定,重载关系是根据调用时的实参表与形参表来选择方法体。
84.隐藏指的是在某些情况下,派生类中的函数屏蔽了基类中的同名函数,情况如下:
1) 两个函数参数相同,但基类函数不是虚函数(和覆盖的区别在于基类函数是否是虚函数)。
2) 两个函数参数不同,无论基类函数是否是虚函数,基类函数都会被屏蔽(和重载的却别在于两个函数不在同一类中)。
85.