传统弱校HFUT的蒟蒻,真相只有一个

c++ 基础复习 整合

  1 一、编译和链接装载库
  2 https://github.com/huihut/interview#%EF%B8%8F-%E9%93%BE%E6%8E%A5%E8%A3%85%E8%BD%BD%E5%BA%93
  3 
  4 '''
  5 1、c++内存类型(5+1)
  6 '''
  7 堆:由程序员维护分配和释放,如果没有释放,程序结束时由操作系统回收。容纳程序动态分配的内存。
  8 栈:由操作系统自动分配和释放,包含:函数的返回地址和参数、存放函数的非static变量、维护函数调用的上下文(调用前后需要保持不变的寄存器)
  9 程序代码区:存放二进制代码
 10 自由存储区:c++ new delete 动态分配和释放
 11 静态/全局存储区:保存static对象、全局变量,对象使用前分配,程序结束时销毁
 12 常量存储区:存放常量不允许修改
 13 '''
 14 2、关于堆的分配算法
 15 '''
 16 #堆的分配算法包括:空闲链表、位图
 17 位图:用0标记空闲,用1标记占用,每个内存单元对应于位图中的一位。如把k个内存分配单元调入内存时,在位图中寻找k个连续0串(耗时)
 18 空闲链表:维护一个已分配内存段和空闲内存段的链表,分配方法有:首次适配、下次适配、最佳适配
 19 '''
 20 3、段错误 or 内存地址不能 read/write
 21 '''
 22 由非法的指针引起的错误,指针指向了不允许读写的地址,程序试图利用指针读写该地址
 23 产生原因:指针初始化为NULL后没有赋值、没有初始化栈中的指针(一般是随机数)
 24 '''
 25 4、不同平台的动态库、静态库格式
 26 '''
 27 windows:动态库ddl、静态库lib、可执行文件exe
 28 linux  :动态库so、静态库a、可执行文件out or elf
 29 '''
 30 5、c++程序编译的过程
 31 '''
 32 #源程序main.c “预编译器” -> 修改的源程序main.i “编译器” -> 汇编程序main.s “汇编器” -> 可重定位目标程序main.o ”链接器“ -> 可执行程序main
 33 预编译:处理如“#include” “#define” 等预编译指令,生成.i
 34 编译器:进行词法分析、语法分析、语义分析、中间代码生成、目标代码生成、优化,生成.seek
 35 汇编器:把汇编码翻译成机器码,生成.o
 36 链接器:进行地址和空间分配、符号决议、重定位,生成可执行程序
 37 #编译器编译后的.i文件叫做目标文件,已经是可执行文件的格式只是还没有经过链接符号和地址。可执行文件.exe/.elf,.dll/.so,.lib/.a都是目标文件格式存储的
 38 #目标文件的存储结构
 39 FILE header:描述文件的属性是否可执行、是静态链接还是动态链接、入口地址、操作系统
 40 text section:代码段,语句编译成的机器代码
 41 data section:数据段,已初始化的全局变量和静态变量
 42 bbs  section:BBS段,未初始化的全局变量和静态变量
 43 read only section:只读数据段,存放const
 44 comment section:注释信息段,包括编译器版本信息
 45 stack section:堆栈提示段
 46 '''
 47 6、符号
 48 '''
 49 链接的接口称为符号,目标文件之间的拼合实际上是目标文件之间对函数和变量地址的引用,函数和变量统称为符号,函数名和变量名统称为符号名。
 50 '''
 51 7、Linux共享库
 52 '''
 53 linux下的共享库是ELF共享对象,命名:libname.so.x.y.z 
 54 x:主版本号,不同版本号间不兼容需要重编译
 55 y:次版本号,高版本向后兼容
 56 z:发布版本号,不对接口更改,完全兼容
 57 #
 58 系统目录/lib:存放基础和关键共享库,数学库、动态链接器等
 59 系统目录/usr/lib:非关键库,开发库
 60 系统目录/usr/local/lib:第三方应用库
 61 #环境变量
 62 LD_LIBARY_PATH:临时改变某个程序共享库的查找路径不影响其他程序。
 63 LD_PRELOAD:预装载共享库或目标文件
 64 LD_DEBUG:打开动态链接器调制功能
 65 #so 共享库的编写
 66 CMakeLists
 67 '''
 68 8、Windows动态链接库DLL
 69 '''
 70 创建DLL实际上是创建一个可供可执行模块调用的函数;使用c与c++混编时要使用extern "C"修饰
 71 加载windows程序的搜索顺序:可执行文件目录->windows系统目录System->进程的当前目录->PATH环境变量列出的目录
 72 DUMPBIN -export ...\my.dll 查看DLL导出变量、函数、类名的符号、相对虚拟地址
 73 '''
 74 8、运行库 Runtime Library —— 典型程序运行的步骤
 75 '''
 76 1、操作系统创建进程,把控制权交给程序的入口(运行库中的某个入口函数)
 77 2、入口函数对运行库和程序运行环境初始化(堆、I/O、线程、全局变量等)
 78 3、调用main函数,正式开始执行程序主体部分
 79 4、返回到入口函数进行清理工作(全局变量析构、堆销毁、关闭I/O),系统调用结束进程
 80 '''
 81 9、c语言标准库头
 82 '''
 83 标准输入输出、文件操作:stdio.h
 84 字符操作:ctype.h
 85 字符串操作:string.h
 86 数学函数:math.h
 87 资源管理、格式转换:stdlib.h
 88 时间日期:time.h
 89 断言:assert.h
 90 
 91 
 92 二、C++基础知识
 93 
 94 '''
 95 1、const (被 const 修饰(在 const 后面)的值不可改变)
 96 '''
 97 修饰变量,说明该变量不可以被改变;
 98 修饰指针,分为指向常量的指针(pointer to const)和自身是常量的指针(常量指针,const pointer);
 99 修饰引用,指向常量的引用(reference to const),用于形参类型,即避免了拷贝,又避免了函数对值的修改;
100 修饰成员函数,说明该成员函数内不能修改成员变量。
101 #指针
102 指向常量的指针(pointer to const)
103 自身是常量的指针(常量指针,const pointer)
104 #引用
105 指向常量的引用(reference to const)
106 没有 const reference,因为引用本身就是 const pointer
107 //108 class A
109 {
110 private:
111     const int a;                // 常对象成员,只能在初始化列表赋值
112 
113 public:
114     // 构造函数
115     A() : a(0) { };
116     A(int x) : a(x) { };        // 初始化列表
117 
118     // const可用于对重载函数的区分
119     int getValue();             // 普通成员函数
120     int getValue() const;       // 常成员函数,不得修改类中的任何数据成员的值
121 };
122 
123 void function()
124 {
125     // 对象
126     A b;                        // 普通对象,可以调用全部成员函数、更新常成员变量
127     const A a;                  // 常对象,只能调用常成员函数
128     const A *p = &a;            // 指针变量,指向常对象
129     const A &q = a;             // 指向常对象的引用
130 
131     // 指针
132     char greeting[] = "Hello";
133     char* p1 = greeting;                // 指针变量,指向字符数组变量
134     const char* p2 = greeting;          // 指针变量,指向字符数组常量(const 后面是 char,说明指向的字符(char)不可改变)
135     char* const p3 = greeting;          // 自身是常量的指针,指向字符数组变量(const 后面是 p3,说明 p3 指针自身不可改变)
136     const char* const p4 = greeting;    // 自身是常量的指针,指向字符数组常量
137 }
138 
139 // 函数
140 void function1(const int Var);           // 传递过来的参数在函数内不可变
141 void function2(const char* Var);         // 参数指针所指内容为常量
142 void function3(char* const Var);         // 参数指针为常量
143 void function4(const int& Var);          // 引用参数在函数内为常量
144 
145 // 函数返回值
146 const int function5();      // 返回一个常数
147 const int* function6();     // 返回一个指向常量的指针变量,使用:const int *p = function6();
148 int* const function7();     // 返回一个指向变量的常指针,使用:int* const p = function7();
149 '''
150 2、static
151 '''
152 修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在 main 函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它。
153 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命名空间里的函数重名,可以将函数定位为 static。
154 修饰成员变量,修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员。
155 修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是在 static 函数内不能访问非静态成员。
156 '''
157 3、this指针
158 '''
159 this 指针是一个隐含于每一个非静态成员函数中的特殊指针。它指向调用该成员函数的那个对象。
160 当对一个对象调用成员函数时,编译程序先将对象的地址赋给 this 指针,然后调用成员函数,每次成员函数存取数据成员时,都隐式使用 this 指针。
161 当一个成员函数被调用时,自动向它传递一个隐含的参数,该参数是一个指向这个成员函数所在的对象的指针。
162 this 指针被隐含地声明为: ClassName *const this,这意味着不能给 this 指针赋值;在 ClassName 类的 const 成员函数中,this 指针的类型为:const ClassName* const,这说明不能对 this 指针所指向的这种对象是不可修改的(即不能对这种对象的数据成员进行赋值操作);
163 this 并不是一个常规变量,而是个右值,所以不能取得 this 的地址(不能 &this)。
164 在以下场景中,经常需要显式引用 this 指针:
165 为实现对象的链式引用;
166 为避免对同一对象进行赋值操作;
167 在实现一些数据结构时,如 list。
168 '''
169 4、内联函数inline
170 '''
171 相当于把内联函数里面的内容写在调用内联函数处;
172 相当于不用执行进入函数的步骤,直接执行函数体;
173 相当于宏,却比宏多了类型检查,真正具有函数特性;
174 编译器一般不内联包含循环、递归、switch 等复杂操作的内联函数;
175 在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。
176 // 声明1(加 inline,建议使用)
177 inline int functionName(int first, int second,...);
178 
179 // 声明2(不加 inline)
180 int functionName(int first, int second,...);
181 
182 // 定义
183 inline int functionName(int first, int second,...) {/****/};
184 
185 // 类内定义,隐式内联
186 class A {
187     int doA() { return 0; }         // 隐式内联
188 }
189 
190 // 类外定义,需要显式内联
191 class A {
192     int doA();
193 }
194 inline int A::doA() { return 0; }   // 需要显式内联
195 #编译器对 inline 函数的处理步骤
196 将 inline 函数体复制到 inline 函数调用点处;
197 为所用 inline 函数中的局部变量分配内存空间;
198 将 inline 函数的的输入参数和返回值映射到调用方法的局部变量空间中;
199 如果 inline 函数有多个返回点,将其转变为 inline 函数代码块末尾的分支(使用 GOTO)。
200 #优点
201 内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度。
202 内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会。
203 在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。
204 内联函数在运行时可调试,而宏定义不可以。
205 #缺点
206 代码膨胀。内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
207 inline 函数无法随着函数库升级而升级。inline函数的改变需要重新编译,不像 non-inline 可以直接链接。
208 是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器。
209 '''
210 5、虚函数(virtual)可以是内联函数(inline)吗
211 '''
212 虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。
213 内联是在编译期建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
214 inline virtual 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 Base::who()),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。
215 #虚函数内联使用
216 #include <iostream>  
217 using namespace std;
218 class Base
219 {
220 public:
221     inline virtual void who()
222     {
223         cout << "I am Base\n";
224     }
225     virtual ~Base() {}
226 };
227 class Derived : public Base
228 {
229 public:
230     inline void who()  // 不写inline时隐式内联
231     {
232         cout << "I am Derived\n";
233     }
234 };
235 int main()
236 {
237     // 此处的虚函数 who(),是通过类(Base)的具体对象(b)来调用的,编译期间就能确定了,所以它可以是内联的,但最终是否内联取决于编译器。 
238     Base b;
239     b.who();
240 
241     // 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。  
242     Base *ptr = new Derived();
243     ptr->who();
244 
245     // 因为Base有虚析构函数(virtual ~Base() {}),所以 delete 时,会先调用派生类(Derived)析构函数,再调用基类(Base)析构函数,防止内存泄漏。
246     delete ptr;
247     ptr = nullptr;
248 
249     system("pause");
250     return 0;
251 } 
252 '''
253 6、volatile
254 '''
255 volatile int i = 10; 
256 volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素(操作系统、硬件、其它线程等)更改。所以使用 volatile 告诉编译器不应对这样的对象进行优化。
257 volatile 关键字声明的变量,每次访问时都必须从内存中取出值(没有被 volatile 修饰的变量,可能由于编译器的优化,从 CPU 寄存器中取值)
258 const 可以是 volatile (如只读的状态寄存器)
259 指针可以是 volatile,一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时
260 简单地说就是防止编译器对代码进行优化,编译器在用到这个变量时必须每次都小心地从内存重新读取这个变量的值,而不是使用保存在寄存器里的备份
261 '''
262 7、assert()
263 '''
264 断言,是宏,而非函数。assert 宏的原型定义在 <assert.h>(C)、<cassert>(C++)中,其作用是如果它的条件返回错误,则终止程序执行。可以通过定义 NDEBUG 来关闭 assert,但是需要在源代码的开头,include <assert.h> 之前。
265 assert() 使用
266 #define NDEBUG          // 加上这行,则 assert 不可用
267 #include <assert.h>
268 assert( p != NULL );    // assert 不可用
269 '''
270 8、sizeof()
271 '''
272 sizeof 对数组,得到整个数组所占空间大小。
273 sizeof 对指针,得到指针本身所占空间大小。
274 '''
275 9、#pragma pack(n)
276 '''
277 设定结构体、联合以及类成员变量以 n 字节方式对齐
278 其实之所以有内存字节对齐机制,就是为了最大限度的减少内存读取次数。我们知道CPU读取速度比内存读取速度快至少一个数量级,所以为了节省运算花费时间,只能以牺牲空间来换取时间了。
279 如强制按照2字节进行对齐,可以理解成所有的内容都是按照2字节进行读取,其他所有的数据成员都是2字节的整数倍,所以也就不用进行内存对其,各个成员在内存中就按照实际顺序进行排列,那么int e就得二次才能读到CPU中
280 #pragma pack(n) 使用
281 #pragma pack(push)  // 保存对齐状态
282 #pragma pack(4)     // 设定为 4 字节对齐
283 struct test
284 {
285     char m1;
286     double m4;
287     int m3;
288 };
289 #pragma pack(pop)   // 恢复对齐状态
290 '''
291 Bit 位域(一个bytes(字节)是8 bit(Bit))
292 '''
293 有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。
294 Bit mode: 2;    // mode 占 2295 类可以将其(非静态)数据成员定义为位域(bit-field),在一个位域中含有一定数量的二进制位。当一个程序需要向其他程序或硬件设备传递二进制数据时,通常会用到位域。
296 位域在内存中的布局是与机器有关的
297 位域的类型必须是整型或枚举类型,带符号类型中的位域的行为将因具体实现而定
298 取地址运算符(&)不能作用于位域,任何指针都无法指向类的位域
299 struct bs 
300 { 
301 int a:8; 
302 int b:2; 
303 int c:6; 
304 }data; 
305 说明data为bs变量,共占两个字节
306 struct bs 
307 { 
308 unsigned a:4 
309 unsigned :0  
310 unsigned b:4  
311 unsigned c:4 
312 } 
313 这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位
314 '''
315 extern "C"
316 '''
317 extern "C" 的作用是让 C++ 编译器将 extern "C" 声明的代码当作 C 语言代码处理,可以避免 C++ 因符号修饰导致代码不能和C语言库中的符号进行链接的问题。
318 extern "C" 使用
319 #ifdef __cplusplus
320 extern "C" {
321 #endif
322 void *memset(void *, int, size_t);
323 #ifdef __cplusplus
324 }
325 #endif
326 '''
327 10、struct 、 class
328 '''
329 #总的来说,struct 更适合看成是一个数据结构的实现体,class 更适合看成是一个对象的实现体。
330 #区别:struct 作为数据结构的实现体,它默认的数据访问控制是 public 的,而 class 作为对象的实现体,它默认的成员变量访问控制是 private 的。
331 '''
332 11、union
333 '''
334 union是一种节省空间的特殊的类,一个 union 可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值。当某个成员被赋值后其他成员变为未定义状态
335 默认访问控制符为 public
336 可以含有构造函数、析构函数
337 不能含有引用类型的成员
338 不能继承自其他类,不能作为基类
339 不能含有虚函数
340 匿名 union 在定义所在作用域可直接访问 union 成员
341 匿名 union 不能包含 protected 成员或 private 成员
342 全局匿名联合必须是静态(static)的
343 #include<iostream>
344 union UnionTest {
345     UnionTest() : i(10) {};
346     int i;
347     double d;
348 };
349 static union {
350     int i;
351     double d;
352 };
353 int main() {
354     UnionTest u;
355 
356     union {
357         int i;
358         double d;
359     };
360 
361     std::cout << u.i << std::endl;  // 输出 UnionTest 联合的 10
362 
363     ::i = 20;
364     std::cout << ::i << std::endl;  // 输出全局静态匿名联合的 20
365 
366     i = 30;
367     std::cout << i << std::endl;    // 输出局部匿名联合的 30
368 
369     return 0;
370 }
371 '''
372 12、C 实现 C++ 类
373 '''
374 封装:使用函数指针把属性与方法封装到结构体中
375 继承:结构体嵌套
376 多态:父类与子类方法的函数指针不同
377 '''
378 13、explicit(显式)关键字
379 '''
380 explicit 修饰构造函数时,可以防止隐式转换和复制初始化
381 explicit 修饰转换函数时,可以防止隐式转换,但 按语境转换 除外
382 struct A
383 {
384     A(int) { }
385     operator bool() const { return true; }
386 };
387 struct B
388 {
389     explicit B(int) {}
390     explicit operator bool() const { return true; }
391 };
392 void doA(A a) {}
393 void doB(B b) {}
394 int main()
395 {
396     A a1(1);        // OK:直接初始化
397     A a2 = 1;        // OK:复制初始化
398     A a3{ 1 };        // OK:直接列表初始化
399     A a4 = { 1 };        // OK:复制列表初始化
400     A a5 = (A)1;        // OK:允许 static_cast 的显式转换 
401     doA(1);            // OK:允许从 int 到 A 的隐式转换
402     if (a1);        // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
403     bool a6(a1);        // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
404     bool a7 = a1;        // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
405     bool a8 = static_cast<bool>(a1);  // OK :static_cast 进行直接初始化
406 
407     B b1(1);        // OK:直接初始化
408     B b2 = 1;        // 错误:被 explicit 修饰构造函数的对象不可以复制初始化
409     B b3{ 1 };        // OK:直接列表初始化
410     B b4 = { 1 };        // 错误:被 explicit 修饰构造函数的对象不可以复制列表初始化
411     B b5 = (B)1;        // OK:允许 static_cast 的显式转换
412     doB(1);            // 错误:被 explicit 修饰构造函数的对象不可以从 int 到 B 的隐式转换
413     if (b1);        // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
414     bool b6(b1);        // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
415     bool b7 = b1;        // 错误:被 explicit 修饰转换函数 B::operator bool() 的对象不可以隐式转换
416     bool b8 = static_cast<bool>(b1);  // OK:static_cast 进行直接初始化
417     return 0;
418 }
419 '''
420 14、friend 友元类和友元函数
421 '''
422 能访问私有成员
423 破坏封装性
424 友元关系不可传递
425 友元关系的单向性
426 友元声明的形式及数量不受限制
427 #使用友元函数计算两点之间的距离
428 class Point{
429 public:
430     Point(int xx = 0, int yy = 0) { X = xx; Y = yy;}
431     int GetX() {return X;}
432     int GetY() {return Y;}
433     friend float fDist( Point &a, Point &b );
434 private:
435     int X, Y;
436 };
437 float fDist(Point &p1, Point &p2){
438     double x = double(p1.X - p2.X);//通过对象访问私有数据成员,而不是必须使用Getx()函数
439     double y = double(p1.Y - p2.Y);
440     return float(sqrt(x*x + y*y));
441 #友元类
442 class A{
443 public:
444     int GetX() { return x; }
445     friend class B;//B类是A类的友元类
446     //其它成员略
447 private:
448     int x;
449 };
450 class B{
451 public:
452     void set(int i);
453     //其他成员略
454 private:
455     A a;
456 };
457 void B :: set(int i){
458     a.x = i;//由于B类是A类的友元类,所以在B的成员函数中可以访问A类对象的私有成员
459 }
460 '''
461 15、:: 范围解析运算符
462 '''
463 全局作用域符(::name):用于类型名称(类、类成员、成员函数、变量等)前,表示作用域为全局命名空间
464 类作用域符(class::name):用于表示指定类型的作用域范围是具体某个类的
465 命名空间作用域符(namespace::name):用于表示指定类型的作用域范围是具体某个命名空间的
466 '''
467 16、enum 枚举类型
468 '''
469 枚举型是预处理指令#define的替代,枚举和宏其实非常类似,宏在预处理阶段将名字替换成对应的值,枚举在编译阶段将名字替换成对应的值
470 '''
471 17、左值和右值
472 '''
473 右值引用就是必须绑定到右值(一个临时对象、将要销毁的对象)的引用,一般表示对象的值
474 void Print(string& s){
475     cout << s;
476 }
477 int main(){
478     string s="abc";
479     Print(s); // OK
480     Print("abc"); // Error!!!
481 }
482 原因在于s是左值,"abc"是右值,改成void Print(const string& s)就没问题,原因:const为"abc"构造一个临时对象
483 #c++11
484 bool Login(User& user) {
485     //处理左值传入
486     user.count++;
487     return true
488 }
489 bool Login(User&& user) {
490     //处理右值传入 &&右值引用
491     user.count++;
492     return true
493 }
494 #修改,万能引用——模板类 
495 template<class T>
496 bool Login(T&& user) {
497     // 万能引用
498     user.count++;
499     return true
500 }
501 #&&已经不是右值引用了,而是被称为万能引用,传入右值时,模板特化成bool Login(User&& user),当传入左值时,模板特化成bool Login(User& user)
502 '''
503 18、成员初始化列表
504 '''
505 更高效:少了一次调用默认构造函数的过程。
506 有些场合必须要用初始化列表:
507 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
508 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
509 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化
510 '''
511 19、面向对象
512 '''
513 面向对象三大特征 —— 封装、继承、多态
514 #封装:
515 把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。关键字:public, protected, private。不写默认为 private。
516 #继承:
517 基类(父类)——> 派生类(子类)
518 #多态:
519 多态,即多种状态(形态)。简单来说,我们可以将多态定义为消息以多种形式显示的能力。
520 多态是以封装和继承为基础的。
521 C++ 多态分类及实现:
522 重载多态(Ad-hoc Polymorphism,编译期):函数重载、运算符重载
523 子类型多态(Subtype Polymorphism,运行期):虚函数
524 参数多态性(Parametric Polymorphism,编译期):类模板、函数模板
525 强制多态(Coercion Polymorphism,编译期/运行期):基本类型转换、自定义类型转换
526 '''
527 20、多态
528 '''
529 #静态多态:(编译期/早绑定)函数重载
530 class A
531 {
532 public:
533     void do(int a);
534     void do(int a, int b);
535 };
536 #动态多态(运行期期/晚绑定)
537 虚函数:用 virtual 修饰成员函数,使其成为虚函数
538 普通函数(非类成员函数)不能是虚函数
539 静态函数(static)不能是虚函数
540 构造函数不能是虚函数(因为在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调用完成后才会形成虚表指针)
541 内联函数不能是表现多态性时的虚函数,解释见:虚函数(virtual)可以是内联函数(inline)吗?
542 class Shape                     // 形状类
543 {
544 public:
545     virtual double calcArea()
546     {
547         ...
548     }
549     virtual ~Shape();
550 };
551 class Circle : public Shape     // 圆形类
552 {
553 public:
554     virtual double calcArea();
555     ...
556 };
557 class Rect : public Shape       // 矩形类
558 {
559 public:
560     virtual double calcArea();
561     ...
562 };
563 int main()
564 {
565     Shape * shape1 = new Circle(4.0);
566     Shape * shape2 = new Rect(5.0, 6.0);
567     shape1->calcArea();         // 调用圆形类里面的方法
568     shape2->calcArea();         // 调用矩形类里面的方法
569     delete shape1;
570     shape1 = nullptr;
571     delete shape2;
572     shape2 = nullptr;
573     return 0;
574 }
575 '''
576 21、虚函数(virtual)可以是内联函数(inline)吗
577 '''
578 虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。
579 内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
580 inline virtual 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 Base::who()),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。
581 '''
582 22、虚(析构)函数
583 '''
584 虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。
585 class Shape
586 {
587 public:
588     Shape();                    // 构造函数不能是虚函数
589     virtual double calcArea();
590     virtual ~Shape();           // 虚析构函数
591 };
592 class Circle : public Shape     // 圆形类
593 {
594 public:
595     virtual double calcArea();
596     ...
597 };
598 int main()
599 {
600     Shape * shape1 = new Circle(4.0);
601     shape1->calcArea();    
602     delete shape1;  // 因为Shape有虚析构函数,所以delete释放内存时,先调用子类析构函数,再调用基类析构函数,防止内存泄漏。
603     shape1 = NULL;
604     return 0;
605 }
606 #纯虚函数
607 纯虚函数是一种特殊的虚函数,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。
608 类里如果声明了虚函数,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被覆盖(override),这样的话,编译器就可以使用后期绑定来达到多态了。纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。
609 虚函数在子类里面可以不重写;但纯虚函数必须在子类实现才可以实例化子类。
610 虚函数的类用于 “实作继承”,继承接口的同时也继承了父类的实现。纯虚函数关注的是接口的统一性,实现由子类完成。
611 带纯虚函数的类叫抽象类,这种类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。抽象类被继承后,子类可以继续是抽象类,也可以是普通类。
612 虚基类是虚继承中的基类,具体见下文虚继承。
613 #虚函数指针、虚函数表
614 虚函数指针:在含有虚函数类的对象中,指向虚函数表,在运行时确定。
615 虚函数表:在程序只读数据段(.rodata section,见:目标文件存储结构),存放虚函数指针,如果派生类实现了基类的某个虚函数,则在虚表中覆盖原本基类的那个虚函数指针,在编译时根据类的声明创建。
616 (虚函数表是一个指针数组,虚函数指针是指向指针数组的指针)
617 虚函数表很好的讲解:https://blog.twofei.com/496/
618 '''
619 23、虚继承
620 '''
621 虚继承用于解决多继承条件下的菱形继承问题(浪费存储空间、存在二义性)。
622 底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。
623 实际上,vbptr 指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。
624 #虚继承 vs 虚函数
625 相同之处:都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)
626 #不同之处:
627 虚继承:虚基类依旧存在继承类中,只占用存储空间、虚基类表存储的是虚基类相对直接继承类的偏移
628 虚函数:虚函数不占用存储空间、虚函数表存储的是虚函数地址
629 #注:普通类
630 空的类是会占用内存空间的,而且大小是1,原因是C++要求每个实例在内存中都有独一无二的地址。
631 普通的变量:是要占用内存的,但是要注意对齐原则(这点和struct类型很相似)。
632 static修饰的静态变量:不占用内容,原因是编译器将其放在全局变量区。
633 #类内部的成员函数
634 普通函数:不占用内存。
635 虚函数:要占用4个字节,用来指定虚函数的虚拟函数表的入口地址。所以一个类的虚函数所占用的地址是不变的,和虚函数的个数是没有关系的
636 
637 三、面试题汇总
638 参考:https://blog.csdn.net/fakine/article/details/51321544
639 '''
640 1、new、delete、malloc、free关系
641 '''
642 malloc和free是标准库函数,new和delete是c++运算符,都可以用于申请动态内存和释放内存,只有malloc和free不能满足动态对象的要求,对象在创建时自动执行构造函数、消亡时自动执行析构函数,malloc和free是库函数不是运算符,不能执行构造函数和析构函数,因此c++需要能够自动完成构造函数和析构函数的new和delete。
643 '''
644 2、delete与 delete []区别
645 '''
646 delete与new配套,delete []与new []配套,delete只会调用一次析构函数,delete[]调用每一个成员的析构函数。
647 MemTest *mTest1=new MemTest[10];
648 MemTest *mTest2=new MemTest;
649 Int *pInt1=new int [10];
650 Int *pInt2=new int;
651 delete[]pInt1; //-1-
652 delete[]pInt2; //-2-
653 delete[]mTest1;//-3-
654 delete[]mTest2;//-4-
655 对于内建简单数据类型,delete和delete[]功能是相同的(1、2);对于自定义的复杂数据类型(3、4),delete和delete[]不能互用。delete[]删除一个数组,delete删除一个指针。简单来说,用new分配的内存用delete删除;用new[]分配的内存用delete[]删除。
656 '''
657 3、子类析构时要调用父类的析构函数吗
658 '''
659 定义一个对象时先调用基类的构造函数,再调用派生类的构造函数、析构时先调用派生类的析构函数再调用基类的析构函数。
660 '''
661 4、多态是什么
662 '''
663 多态是不同对象接收相同信息产生不同的动作,c++的多态性体现在运行和编译两方面,程序运行时的多态性通过继承和虚函数实现,编译时的多态通过函数和运算符重载实现。
664 纯虚函数的作用:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。作为接口而存在 纯虚函数不具备函数的功能,一般不能直接被调用。
665 从基类继承来的纯虚函数,在派生类中仍是虚函数。如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。
666 抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。但仍可使用指向抽象类的指针支持运行时多态性。
667 '''
668 5、什么是“引用”?申明和使用“引用”要注意哪些问题?
669 '''
670 引用就是目标变量的别名,申明一个引用的时候切记要对它初始化,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。
671 '''
672 6、将“引用”作为函数参数有哪些特点
673 '''
674 (1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
675 (2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
676 (3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
677 '''
678 7、常引用
679 '''
680 如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式:const 类型标识符 &引用名=目标变量名;
681 '''
682 8、“引用”作为函数返回值类型的格式、好处和需要遵守的规则?
683 '''
684 好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error! 
685 (1)不能返回局部变量的引用(局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。)、(2)不能返回函数内部new分配的内存的引用。(3)可以返回类成员的引用,但最好是const
686 #流操作符重载返回值申明为“引用”的作用:
687 流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。 
688 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。
689 '''
690 9、重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?
691 '''
692 重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!
693 重写:是指子类重新定义父类虚函数的方法。和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。
694 '''
695 10、有哪几种情况只能用intialization list 而不能用assignment?
696 '''
697 当类中含有const、reference 成员变量时它们只能够被初始化而不能做赋值操作;还有一种情况就是,基类无默认构造函数时,有参的构造函数都需要初始化。
698 '''
699 11、C++是不是类型安全的?
700 '''
701 不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。
702 '''
703 12、main 函数执行以前,还会执行什么代码?
704 '''
705 全局对象的构造函数会在main 函数之前执行。
706 '''
707 13、描述内存分配方式以及它们的区别
708 '''
709 1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
710 2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
711 3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。
712 '''
713 14、const与#define 相比,有何优点
714 '''
715 const作用:定义常量、修饰函数参数、修饰函数返回值三个作用。被Const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
716 1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
717 2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。
718 '''
719 15、特殊
720 '''
721 int (*s[10])(int) 表示的是什么 :函数指针数组
722 int id[sizeof(unsigned long)];这个对吗?为什么? 正确 这个 sizeof是编译时运算符,编译时就确定了  ,可以看成和机器有关的常量。
723 #复杂声明
724 void * ( * (*fp1)(int))[10];
725 float (*(* fp2)(int,int,int))(int);
726 int (* ( * fp3)())[10]();
727 分别表示什么意思?
728 1.void * ( * (*fp1)(int))[10];   fp1是一个指针,指向一个函数,这个函数的参数为int型,函数的返回值是一个指针,这个指针指向一个数组,这个数组有10个元素,每个元素是一个void*型指针。
729 2.float (*(* fp2)(int,int,int))(int);   fp2是一个指针,指向一个函数,这个函数的参数为3个int型,函数的返回值是一个指针,这个指针指向一个函数,这个函数的参数为int型,函数的返回值是float型。
730 3.int (* ( * fp3)())[10]();   fp3是一个指针,指向一个函数,这个函数的参数为空,函数的返回值是一个指针,这个指针指向一个数组,这个数组有10个元素,每个元素是一个指针,指向一个函数,这个函数的参数为空,函数的返回值是int型。
731 '''
732 16、栈内存与文字常量区
733 '''
734 char str1[] = "abc";
735 char str2[] = "abc";
736 const char str3[] = "abc";
737 const char str4[] = "abc";
738 const char *str5 = "abc";
739 const char *str6 = "abc";
740 char *str7 = "abc";
741 char *str8 = "abc";
742 cout << ( str1 == str2 ) << endl;//0  分别指向各自的栈内存
743 cout << ( str3 == str4 ) << endl;//0  分别指向各自的栈内存
744 cout << ( str5 == str6 ) << endl;//1指向文字常量区地址相同
745 cout << ( str7 == str8 ) << endl;//1指向文字常量区地址相同
746 str1,str2,str3,str4是数组变量,它们有各自的内存空间;而str5,str6,str7,str8是指针,它们指向相同的常量区域。
747 '''
748 17、将程序跳转到指定内存地址
749 '''
750 要对绝对地址0x100000赋值,我们可以用(unsigned int*)0x100000 = 1234;那么要是想让程序跳转到绝对地址是0x100000去执行,应该怎么做?
751 *((void (*)( ))0x100000 ) ( );
752 首先要将0x100000强制转换成函数指针,即:
753 (void (*)())0x100000
754 然后再调用它:*((void (*)())0x100000)();
755 '''
756 18、引用和指针区别
757 '''
758 1) 引用必须被初始化,指针不必。
759 2) 引用初始化以后不能被改变,指针可以改变所指的对象。
760 3) 不存在指向空值的引用,但是存在指向空值的指针。
761 '''
762 19、基类的析构函数不是虚函数,会带来什么问题?
763 '''
764 当用基类操作派生类,在析构时防止只析构基类而不析构派生类的状况发生
765 派生类操作派生类没有影响
766 '''
767 20、全局变量和局部变量有什么区别?是怎么实现的?操作系统和编译器是怎么知道的
768 '''
769 生命周期不同:
770 全局变量随主程序创建,随主程序销毁;局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在;
771 使用方式不同:通过声明后全局变量程序的各个部分都可以用到;局部变量只能在局部使用;分配在栈区。 
772 操作系统和编译器通过内存分配的位置来知道的,全局变量分配在全局数据段并且在程序开始运行的时候被加载。局部变量则分配在堆栈里面 。
773 '''
774 21、宏定义和内联函数的区别
775 '''
776 1)宏定义不是函数,预处理器用赋值宏代码的方式代替函数的调用,省去了函数压栈退栈过程。
777 内联函数本质是一个函数,不能包含复杂的控制,如果内敛函数体过大,编译器会自动把这个内联函数变成普通函数
778 内联函数是在编译的时候进行代码插入,编译器在调用内联函数的地方直接把内联函数内容展开。省去函数的调用开销。
779 2)宏定义没有类型检查。内联函数在编译的时候进行类型检查
780 3)宏定义和内联都是进行diamagnetic展开。不同点,宏定义在预编译的时候把所有宏名字替换,内联函数在编译阶段把所有调用内敛函数的地方进行内联函数插入,省去函数压栈清栈
781 '''
782 
783 '''
784 
785 '''
786 
787 '''
788 
789 '''
790 
791 '''

 

posted @ 2020-09-23 10:45  未名亚柳  阅读(306)  评论(0编辑  收藏  举报