当前位置: 首页 > news >正文

哪个网站音乐做的最好开发app开发公司

哪个网站音乐做的最好,开发app开发公司,网站个别页面做seo,gowers wordpress com目录 1.part1 C基础 1 C特点 2 说说C语言和C的区别 3 说说 C中 struct 和 class 的区别 4 include头文件的顺序以及双引号和尖括号的区别 5 说说C结构体和C结构体的区别 6 导入C函数的关键字是什么#xff0c;C编译时和C有什么不同#xff1f; 7…目录 1.part1 C基础 1 C特点 2 说说C语言和C的区别 3 说说 C中 struct 和 class 的区别 4 include头文件的顺序以及双引号和尖括号的区别 5 说说C结构体和C结构体的区别 6 导入C函数的关键字是什么C编译时和C有什么不同 7 C从代码到可执行二进制文件的过程 8 说说 static关键字的作用 9 数组和指针的区别 10 说说什么是函数指针如何定义函数指针有什么使用场景 11 静态变量什么时候初始化 12 nullptr调用成员函数可以吗为什么 13 说说什么是野指针怎么产生的如何避免 14 说说静态局部变量全局变量局部变量的特点以及使用场景 15 说说内联函数和宏函数的区别 17 说说new和malloc的区别各自底层实现原理。 18 说说const和define的区别。 19 说说C中函数指针和指针函数的区别。 20 说说const int *a, int const *a, const int a, int *const a, const int *const a分别是什么有什么特点。 21 说说使用指针需要注意什么 22 说说内联函数和函数的区别内联函数的作用。 23 简述C有几种传值方式之间的区别是什么 24 简述const星号和星号const的区别 2.C内存  1 简述堆和栈的区别 2 简述C的内存管理 3. 内存泄露及解决办法 3 malloc和局部变量分配在堆还是栈 4 程序有哪些section分别的作用程序启动的过程怎么判断数据分配在栈上还是堆上 5 初始化为0的全局变量在bss还是data 6 简述C中内存对齐的使用场景 3 面向对象 1 简述一下什么是面向对象 2 简述一下面向对象的三大特征 3简述一下 C 的重载和重写以及它们的区别 4 说说 C 的重载和重写是如何实现的 5 说说构造函数有几种分别什么作用 6 只定义析构函数会自动生成哪些构造函数 7 说说一个类默认会生成哪些函数 8 说说 C 类对象的初始化顺序有多重继承情况下的顺序 9 简述下向上转型和向下转型 10 简述下深拷贝和浅拷贝如何实现深拷贝 11 简述一下 C 中的多态 12 说说为什么要虚析构为什么不能虚构造 14 说说类继承时派生类对不同关键字修饰的基类方法的访问权限 15 简述一下移动构造函数什么库用到了这个函数 16 构造函数为什么不能被声明为虚函数 17 简述一下什么是常函数有什么作用 18 说说什么是虚继承解决什么问题如何实现 19 简述一下虚函数和纯虚函数以及实现原理 21 说说C中虚函数与纯虚函数的区别 22 说说 C 中什么是菱形继承问题如何解决 23 请问构造函数中的能不能调用虚方法 24 请问拷贝构造函数的参数是什么传递方式为什么 25 如何理解抽象类 26 什么是多态除了虚函数还有什么方式能实现多态 27 简述一下虚析构函数什么作用 28 说说什么是虚基类可否被实例化 29 简述一下拷贝赋值和移动赋值 30 仿函数了解吗有什么作用 31 C 中哪些函数不能被声明为虚函数 32 解释下 C 中类模板和模板类的区别 32 虚函数表里存放的内容是什么时候写进去的 1.part1 C基础 1 C特点 1. C 在 C 语言基础上引入了 面对对象 的机制同时也 兼容 C 语言 。 2. C 有三大特性 1封装。 2继承。 3多态 3. C 语言编写出的程序结构清晰、易于扩充程序 可读性好 。 4. C 生成的代码 质量高 效率高 5. C 更加安全增加了 const 常量、引用、四类 cast 转换 static_cast 、 dynamic_cast 、 const_cast 、 reinterpret_cast 、智能指针、 try—catch 等等 6. C 可复用性 高 C 引入了 模板 的概念标准模板库 STL Standard Template Library 。 2 说说C语言和C的区别 1. C 语言是 C 的子集 C 可以很好兼容 C 语言。但是 C 又有很多 新特性 如引用、智能指针、 auto 变量等。 2. C 是 面对对象 的编程语言 C 语言是 面对过程 的编程语言。 3. C 语言有一些不安全的语言特性如指针使用的潜在危险、强制转换的不确定性、内存泄露等。而 C 对此增加了不少新特性来 改善安全性 如 const 常量、引用、 cast 转换、智能指针、 try—catch 等等 4. C 可复用性 高 C 引入了 模板 的概念后面在此基础上实现了方便开发的标准模板库 STL 。 C 的 STL 库相对于 C 语言的函数库 更灵活、更通用 。 3 说说 C中 struct 和 class 的区别 1. struct 一般用于描述一个 数据结构集合 而 class 是对一个 对象数据的封装 2. struct 中默认的访问控制权限 是 public 的 而 class 中默认的访问控制权限 是 private 的 。 3. 在继承关系中 struct 默认是 公有继承 而 class 是 私有继承 4. class 关键字可以用于定义模板参数就像 typename 而 struct 不能用于定义模板参数 4 include头文件的顺序以及双引号和尖括号的区别 1. 区别 1尖括号 的头文件是 系统文件 双引号 的头文件是 自定义文件 。 2编译器预处理阶段查找头文件的路径不一样。 2. 查找路径 1使用尖括号 的头文件的查找路径编译器设置的头文件路径 -- 系统变量。 2使用双引号 的头文件的查找路径当前头文件目录 -- 编译器设置的头文件路径 -- 系统变 量。 5 说说C结构体和C结构体的区别 区别 1 C 的结构体内 不允许有函数存在 C 允许有内部成员函数且允许该函数是虚函数 。 2 C 的结构体对内部成员变量的 访问权限只能是 public 而 C 允许 public,protected,private 三种 。 3 C 语言的结构体是 不可以继承的 C 的结构体 可继承 。 4 C 中 使用结构体需要加上 struct 关键字 而 C 中可以省略 struct 关键字直接使用。 6 导入C函数的关键字是什么C编译时和C有什么不同 1. 关键字 在 C 中导入 C 函数的关键字是 extern 表达形式为 extern “C” extern C 的主要作 用就是为了能够正确实现 C 代码调用其他 C 语言代码。加上 extern C 后会指示编译器这部分代 码按 C 语言 的进行编译而不是 C 的。 2. 编译区别 由于 C 支持函数重载因此编译器编译函数的过程中会将函数的 参数类型 也加到编译 后的代码中而不仅仅是 函数名 而 C 语言并不支持函数重载因此编译 C 语言代码的函数时不会带 上函数的参数类型一般只包括 函数名 7 C从代码到可执行二进制文件的过程 C 和 C 语言类似一个 C 程序从源码到执行文件有四个过程 预编译、编译、汇编、链接 。 预编译 这个过程主要的处理操作如下 将所有的 #define 删除并且展开所有的宏定义处理所有的条件预编译指令如#if 、 #ifdef 处理#include 预编译指令将被包含的文件插入到该预编译指令的位置。 过滤所有的注释 添加行号和文件名标识。 编译 这个过程主要的处理操作如下 词法分析将源代码的字符序列分割成一系列的记号。 语法分析对记号进行语法分析产生语法树。 语义分析判断表达式是否有意义。 代码优化。 目标代码生成生成汇编代码。 目标代码优化。 汇编 这个过程主要是将汇编代码转变成机器可以执行的指令。 链接 将不同的源文件产生的目标文件进行链接从而形成一个可以执行的程序。 链接分为 静态链接和动态链接 。 静态链接 是在链接的时候就已经把要调用的函数或者过程链接到了生成的可执行文件中就算你在去 把静态库删除也不会影响可执行程序的执行生成的静态链接库Windows 下以 .lib 为后缀 Linux 下 以.a 为后缀。 动态链接 是在链接的时候没有把调用的函数代码链接进去而是在执行的过程中再去找要链接的函 数生成的可执行文件中没有函数代码只包含函数的重定位信息所以当你删除动态库时可执行程 序就不能运行。生成的动态链接库Windows下以 .dll 为后缀 Linux 下以 .so 为后缀。 8 说说 static关键字的作用 1. 定义全局静态变量和局部静态变量 初始化的静态变量会在数据段分配内存未初始化的静态变量 会在BSS 段分配内存。直到程序结束静态变量始终会维持前值。 2. 定义静态函数 静态函数只能在 本源文件 中使用如 static void func() ; 3. 定义静态变量 。 静态变量只能在本源文件中使用 4. 定义 类中的静态成员变量 使用静态数据成员它既可以被当成全局变量那样去存储但又被隐藏 在类的内部。类中的static 静态数据成员拥有一块单独的存储区而不管创建了多少个该类的对 象。所有这些对象的静态数据成员都 共享 这一块静态存储空间。 5. 定义类中的静态成员函数 如静态成员函数也是类的一部分而不是对象的一部分。所有这些对象 的静态数据成员都共享 这一块静态存储空间。 此外 当调用一个对象的非静态成员函数时系统会把该对象的起始地址赋给成员函数的this 指针。而 静态成 员函数不属于任何一个对象因此 C 规定静态成员函数没有 this 指针 。既然它没有指向某一对象也 就无法对一个对象中的非静态成员进行访问。 9 数组和指针的区别 1. 概念 1数组数组是用于 储存多个相同类型数据的集合 。 数组名是首元素的地址。 2指针指针相当于一个 变量 它存放的是其它变量在 内存中的地址 。 指针名指向了内存的首 地址。 2. 区别 1 赋值 同类型指针变量可以相互赋值数组不行只能一个一个元素的赋值或拷贝 2 存储方式 数组数组在内存中是 连续存放的 数组的存储空间不是在静态区就是在栈上。 指针指针很灵活它可以指向任意类型的数据。指针的类型说明了它所指向地址空间的内存。由 于指针本身就是一个变量再加上它所存放的也是变量所以指针的存储空间不能确定。 3 求 sizeof 数组所占存储空间的内存大小 sizeof 数组名 /sizeof 数据类型 在32 位平台下无论指针的类型是什么 sizeof 指针名都是 4 在 64 位平台下无论指针的类型是什么sizeof 指针名都是 8 。 10 说说什么是函数指针如何定义函数指针有什么使用场景 1. 概念 函数指针就是 指向函数 的指针变量。每一个函数都有一个入口地址该入口地址就是函数指 针所指向的地址。 2. 定义 形式如下          int func(int a); int (*f)(int a); f func; 1. 函数指针的 应用场景 回调 callback 。我们调用别人提供的 API 函数 (Application Programming Interface, 应用程序编程接口 ) 称为 Call 如果别人的库里面调用我们的函数就叫 Callback 。 11 静态变量什么时候初始化 对于 C 语言的全局和静态变量 初始化发生在任何代码执行之前 属于编译期初始化。 而 C 标准规定全局或静态对象当且仅当对象 首次用到时才进行构造 。 12 nullptr调用成员函数可以吗为什么 能。 原因因为在 编译时对象 就绑定了 函数地址 和指针空不空没关系。 13 说说什么是野指针怎么产生的如何避免 1. 概念 野指针就是指针指向的位置是不可知的随机的、不正确的、没有明确限制的 2. 产生原因 释放内存后指针不及时置空野指针依然指向了该内存那么可能出现非法访问的 错误。这些我们都要注意避免。 3. 避免办法 1初始化置 NULL 2申请内存后判空 3指针释放后置 NULL 4使用智能指针 14 说说静态局部变量全局变量局部变量的特点以及使用场景 1. 首先从作用域考虑 C 里作用域可分为 6 种全局局部类语句命名空间和文件作用域。 全局变量全局作用域可以通过 extern 作用于其他非定义的源文件。 静态全局变量 全局作用域 文件作用域所以无法在其他文件中使用。 局部变量局部作用域比如函数的参数函数内的局部变量等等。 静态局部变量 局部作用域只被初始化一次直到程序结束。 2. 从所在空间考虑 除了局部变量在栈上外其他都在静态存储区。因为静态变量都在静态存储区 所以下次调用函数的时候还是能取到原来的值。 3. 生命周期 局部变量在栈上出了作用域就回收内存而全局变量、静态全局变量、静态局部变量 都在静态存储区直到程序结束才会回收内存。 15 说说内联函数和宏函数的区别 区别 1. 宏定义不是函数 但是使用起来像函数。预处理器用复制宏代码的方式代替函数的调用省去了函 数压栈退栈过程提高了效率而内联函数本质上是一个函数 内联函数一般用于函数体的代码比 较简单的函数不能包含复杂的控制语句while 、 switch 并且内联函数本身不能直接调用自 身。 2. 宏函数 是在预编译的时候把所有的宏名用宏体来替换简单的说就是字符串替换 而内联函数 则是 在编译的时候进行代码插入编译器会在每处调用内联函数的地方直接把内联函数的内容展开这 样可以省去函数的调用的开销提高效率 3. 宏定义 是 没有类型检查的 无论对还是错都是直接替换 而内联函数 在编译的时候会进行类型的检 查内联函数满足函数的性质比如有返回值、参数列表等。 内联函数使用的条件 内联是以代码膨胀复制为代价仅仅省去了函数调用的开销从而提高函数的执行效率。如果 执行函数体内代码的时间相比于函数调用的开销较大那么效率 的收获会很少。另一方面每一 处内联函数的调用都要复制代码将使程序的总代码量增大消耗更多的内存空间。以下情况不宜使用内联 1如果函数体内的代码比较长使用内联将导致内存消耗代价较高。 2如果函数体内出现循环那么执行函数体内代码的时间要比函数调用的开销大。 内联不是什么时候都能展开的一个好的编译器将会根据函数的定义体自动地取消不符合要求的 内联。 17 说说new和malloc的区别各自底层实现原理。 1. new 是操作符而 malloc 是函数。 2. new 在调用的时候 先分配内存在调用构造函数释放的时候调用析构函数 而 malloc 没有构造函 数和析构函数。 3. malloc 需要给定申请内存的大小 返回的指针需要强转 new 会调用构造函数不用指定内存的大 小返回指针不用强转。 4. new 可以被重载 malloc 不行 5. new 分配内存更直接和安全 。 6. new 发生错误抛出异常 malloc 返回 null malloc 底层实现 当开辟的空间小于 128K 时调用 brk 函数当开辟的空间大于 128K 时调用 mmap。 malloc 采用的是内存池的管理方式以减少内存碎片。先申请大块内存作为堆区然后将 堆区分为多个内存块。当用户申请内存时直接从堆区分配一块合适的空闲快。采用隐式链表将所有空闲块每一个空闲块记录了一个未分配的、连续的内存地址。 new 底层实现 关键字 new 在调用构造函数的时候实际上进行了如下的几个步骤 1. 创建一个新的对象 2. 将构造函数的作用域赋值给这个新的对象因此 this 指向了这个新的对象 3. 执行构造函数中的代码为这个新对象添加属性 4. 返回新对象 18 说说const和define的区别。 const 用于定义 常量 而 define 用于 定义宏 而宏也可以用于定义常量。都用于常量定义时它们的区别有 1. const 生效于编译的阶段 define 生效于预处理阶段。 2. const 定义的常量在 C 语言中 是存储在内存中、需要额外的内存空间的 define 定义的常量运行时 是直接的操作数并不会存放在内存中 。 3. const 定义的常量 是带类型的 define 定义的常量 不带类型 。因此 define 定义的常量不利于类型检 查。 19 说说C中函数指针和指针函数的区别。 参考回答 1. 定义不同 指针函数本质是一个函数其返回值为指针。 函数指针本质是一个指针其指向一个函数。 2. 写法不同 指针函数int *fun(int x,int y); 函数指针int (*fun)(int x,int y); 3. 用法不同 指针函数可以在函数体内通过 return语句返回指向动态分配的内存或全局/静态变量的指针。这样的函数可以方便地返回复杂的数据结构如数组、字符串等。例如 int* createArray(int size) {int* arr (int*)malloc(size * sizeof(int));// 对 arr 进行初始化或其他操作return arr; } 函数指针可以在程序运行时动态地指向不同的函数。这在使用回调函数callback时非常有用可以根据需要在不同的上下文中调用不同的函数。例如 nt (*operation)(int, int); // 函数指针的声明 operation add; // 将函数指针指向 add 函数 int result operation(3, 4); // 调用 add 函数进行计算 总之指针函数和函数指针在定义、写法和用法上存在一些区别。指针函数是一个返回指针的函数而函数指针是一个指向函数的指针可以在运行时动态地指向不同的函数。这些概念在C和C等语言中非常常见可以提供更大的灵活性和可扩展性。 20 说说const int *a, int const *a, const int a, int *const a, const int *const a分别是什么有什么特点。 1. const int a; //指的是a是一个常量不允许修改。 2. const int *a; //a指针所指向的内存里的值不变即*a不变 3. int const *a; //同const int *a; 4. int *const a; //a指针所指向的内存地址不变即a不变 5. const int *const a; //都不变即*a不变a也不变 21 说说使用指针需要注意什么 1. 定义指针时先初始化为 NULL 。 2. 用 malloc 或 new 申请内存之后应该 立即检查 指针值是否为 NULL 。防止使用指针值为 NULL 的内 存。 3. 不要忘记为数组和动态内存 赋初值 。防止将未被初始化的内存作为右值使用。 4. 避免数字或指针的下标 越界 特别要当心发生 “ 多 1” 或者 “ 少 1” 操作 5. 动态内存的申请与释放必须配对防止 内存泄漏 6. 用 free 或 delete 释放了内存之后立即将指针 设置为 NULL 防止 “ 野指针 ” 22 说说内联函数和函数的区别内联函数的作用。 1. 内联函数比普通函数多了关键字 inline 2. 内联函数避免了函数调用的 开销 普通函数有调用的开销 3. 普通函数在被调用的时候需要 寻址函数入口地址 内联函数不需要寻址。 4. 内联函数有一定的限制内联函数体要求 代码简单 不能包含复杂的结构控制语句普通函数没有这个要求。 内联函数的作用 内联函数在调用时是将调用表达式用内联函数体来替换。避免函数调用的开销。 23 简述C有几种传值方式之间的区别是什么 参考回答 传参方式有这三种 值传递、引用传递、指针传递 1. 值传递形参即使在函数体内值发生变化也不会影响实参的值 2. 引用传递形参在函数体内值发生变化会影响实参的值 3. 指针传递在指针指向没有发生改变的前提下形参在函数体内值发生变化会影响实参的值 值传递用于对象时整个对象会拷贝一个副本这样效率低而引用传递用于对象时不发生拷贝 行为只是绑定对象更高效指针传递同理但不如引用传递安全。 24 简述const星号和星号const的区别          //const* 是指针常量 *const 是常量指针 int const * a ; //a 指针所指向的内存里的值不变即 *a 不变指针可变 int * const a ; //a 指针所指向的内存地址不变即 a 不变其所指内存值可变 2.C内存  1 简述堆和栈的区别 区别 1. 堆栈空间分配不同 。 栈由操作系统自动分配释放 存放函数的参数值局部变量的值等 堆一般由 程序员分配释放。 2. 堆栈缓存方式不同 。栈使用的是一级缓存 它们通常都是被调用时处于存储空间中调用完毕立即释放堆则是存放在二级缓存中速度要慢些。 3. 堆栈数据结构不同 。堆类似数组结构栈类似栈结构先进后出。 2 简述C的内存管理 1. 内存分配方式 在 C 中内存分成 5 个区他们分别是 堆、栈、自由存储区、全局 / 静态存储区和常量存储区 。 栈 在执行函数时函数内局部变量的存储单元都可以在栈上创建函数执行结束时这些存储单元 自动被释放。 堆 就是那些由 new 分配的内存块一般一个 new 就要对应一个 delete 。 自由存储区 就是那些由 malloc 等分配的内存块和堆是十分相似的不过是用 free 来结束自己的 生命。 全局 / 静态存储区 全局变量和静态变量被分配到同一块内存中 常量存储区 这是一块比较特殊的存储区里面 存放的是常量不允许修改。 2. 常见的内存错误及其对策 1内存分配未成功却使用了它。 2内存分配虽然成功但是尚未初始化就引用它。 3内存分配成功并且已经初始化但操作越过了内存的边界。 4忘记了释放内存造成内存泄露。 5释放了内存却继续使用它。 对策 1定义指针时先初始化为 NULL 。 2用 malloc 或 new 申请内存之后应该 立即检查 指针值是否为 NULL 。防止使用指针值为 NULL 的内存。 3不要忘记为数组和动态内存 赋初值 。防止将未被初始化的内存作为右值使用。 4避免数字或指针的下标 越界 特别要当心发生 “ 多 1” 或者 “ 少 1” 操作 5 动态内存的申请与释放必须配对防止 内存泄漏 6 用 free 或 delete 释放了内存之后立即将指针 设置为 NULL 防止 “ 野指针 ” 7使用智能指针。 3. 内存泄露及解决办法 什么是内存泄露 简单地说就是 申请了一块内存空间使用完毕后没有释放掉 。 1 new 和 malloc 申请资源使用后没有用 delete 和 free 释放 2子类继承父类时父类析构函数不是虚函数。 3 Windows 句柄资源使用后没有释放。 怎么检测 第一 良好的编码习惯使用了内存分配的函数一旦使用完毕 , 要记得使用其相应的函数释放掉 。 第二 将分配的内存的指针以链表的形式自行管理 使用完毕之后从链表中删除程序结束时可检 查改链表。 第三 使用智能指针。 第四一些常见的工具插件如 ccmalloc 、 Dmalloc 、 Leaky 、 Valgrind 等等。 3 malloc和局部变量分配在堆还是栈 malloc 是在 堆上分配内存 需要程序员自己回收内存局部变量是在 栈中分配内存 超过作用域就自动回收。 4 程序有哪些section分别的作用程序启动的过程怎么判断数据分配在栈上还是堆上 一个程序有哪些 section 如上图 从低地址到高地址一个程序由代码段、数据段、 BSS 段组成。 1. 数据段 存放程序中 已初始化的 全局变量和静态变量的一块内存区域。 2. 代码段 存放程序执行代码的一块内存区域。只读代码段的头部还会包含一些只读的常数变量。 3. BSS 段存放程序中 未初始化的 全局变量和静态变量的一块内存区域。 4. 可执行程序在运行时又会多出两个区域堆区和栈区。 堆区 动态申请内存用。堆从低地址向高地址增长。 栈区 存储局部变量、函数参数值。栈从高地址向低地址增长。是一块连续的空间。 5. 最后还有一个 文件映射区 位于堆和栈之间。 堆 heap 由 new 分配的内存块其释放由程序员控制一个 new 对应一个 delete 栈 stack 是那些编译器在需要时分配在不需要时自动清除的存储区。存放局部变量、函数参数。 常量存储区 存放常量不允许修改。 程序启动的过程 1. 操作系统 首先创建相应的进程并分配私有的进程空间 然后操作系统的 加载器负责把可执行文件的 数据段和代码段映射到进程的虚拟内存空间中。 2. 加载器读入可执行程序的导入符号表 根据这些符号表可以查找出该可执行程序的所有依赖的动态 链接库。 3. 加载器针对该程序的每一个动态链接库调用 LoadLibrary 1查找对应的动态库文件加载器为该动态链接库确定一个合适的基地址。 2加载器读取该动态链接库的导入符号表和导出符号表比较应用程序要求的导入符号是否匹 配该库的导出符号。 3针对该库的导入符号表查找对应的依赖的动态链接库如有跳转则跳到 3 4调用该动态链接库的初始化函数 4. 初始化应用程序的全局变量对于全局对象自动调用构造函数 。 5. 进入应用程序入口点函数开始执行。 怎么判断数据分配在栈上还是堆上 首先局部变量分配在栈上而通过 malloc 和 new 申请的空间是在堆上。 5 初始化为0的全局变量在bss还是data BSS 段通常是指用来存放程序中 未初始化的或者初始化为 0 的 全局变量和静态变量的一块内存区域。特点是可读写的 在程序执行之前 BSS 段会自动清 0 。 6 简述C中内存对齐的使用场景 内存对齐应用于三种数据类型中 struct/class/union struct/class/union 内存对齐原则有四个 1. 数据成员对齐规则 结构 (struct) 或联合 (union) 的数据成员 第一个数据成员放在 offset 为 0 的地 方 以后每个数据成员存储的 起始位置要从该成员大小或者成员的子成员大小的整数倍开始 。 2. 结构体作为成员 : 如果一个结构里有某些结构体成员 , 则结构体成员要从其 内部 最宽基本类型成 员 的整数倍地址开始存储。 (struct a 里存有 struct b,b 里有 char,int ,double 等元素 , 那 b 应该从 8 的 整数倍开始存储 ) 。 3. 收尾工作 : 结构体的总大小也就是 sizeof 的结果 必须是其内部最大成员的 最宽基本类型成员 的 整数倍。不足的要补齐。 ( 基本类型不包括 struct/class/uinon) 。 4. sizeof(union) 以结构里面 size 最大元素为 union 的 size 因为在某一时刻 union 只有一个成员真 正存储于该地址。  什么是内存对齐 那么什么是字节对齐在 C 语言中结构体是一种复合数据类型其构成元素既可以是基本数据类型 如int 、 long 、 float 等的变量也可以是一些复合数据类型如数组、结构体、联合体等的数据单 元。在结构体中编译器为结构体的每个成员按其自然边界 alignment 分配空间。 各个成员按照它 们被声明的顺序在内存中顺序存储第一个成员的地址和整个结构体的地址相同。 为了使 CPU 能够对变量进行快速的访问变量的起始地址应该具有某些特性 即所谓的 “ 对齐 ” 比如 4 字 节的 int 型其起始地址应该位于 4 字节的边界上即起始地址能够被 4 整除 也即 “ 对齐 ” 跟数据在内存中 的位置有关。如果一个变量的内存地址正好位于它长度的整数倍他就被称做自然对齐。 为什么要字节对齐 为了快速准确的访问若没有字节对齐则会出现多次访问浪费时间。 举例说明定义一个 char 和 int 型数据不按照字节对齐存储的情况需要多次访问 3 面向对象 1 简述一下什么是面向对象 1. 面向对象是一种编程思想把一切东西看成是一个个对象 把这些对象拥有的属性变量和操作这些 属性变量的函数打包成一个类来表示 2. 面向过程和面向对象的区别 面向过程根据业务逻辑从上到下写代码 面向对象 将数据与函数绑定到一起进行封装 加快开发程序减少重复代码的重写过程。 2 简述一下面向对象的三大特征   面向对象的三大特征是 封装、继承、多态 1. 封装 将数据和操作数据的方法进行有机结合隐藏对象的属性和实现细节仅对外公开接口来和对象进行 交互。 封装 本质上是一种管理 不想给别人看到的我们使用 protected/private 把成员 封装起来。开放一些共有的成员函数对成员合理的访问。 2. 继承 可以使用现有类的所有功能 并在无需重新编写原来的类的情况下对这些功能进行扩展。 三种继承方式。 3. 多态 用父类型别的指针指向其子类的实例然后通过父类的指针调用实际子类的成员函数 。实现多态有二种方式 重写重载 。   3简述一下 C 的重载和重写以及它们的区别 1. 重写 是指 派生类中存在重新定义的函数。其函数名参数列表返回值类型所有都必须同基类中被重 写的函数一致。只有函数体不同花括号内 派生类对象调用时会调用派生类的重写函数不会 调用被重写函数。重写的基类中被重写的函数必须有 virtual 修饰。 #includebits/stdc.h using namespace std; class A { public: virtual void fun() { cout A; } }; class B :public A { public: virtual void fun() { cout B; } }; int main(void) { A* a new B(); a-fun();//输出BA类中的fun在B类中重写 } 1. 重载 我们在平时写代码中会用到几个函数但是他们的实现功能相同但是有些细节却不同。 函数重载是 指同一可访问区内被声明的几个具有不同参数列参数的类型个数顺序不同的同名函数根 据参数列表确定调用哪个函数重载不关心函数返回类型。 #includebits/stdc.h using namespace std; class A { void fun() {}; void fun(int i) {}; void fun(int i, int j) {}; void fun1(int i,int j){}; }; 4 说说 C 的重载和重写是如何实现的 1. 重载 C 利用命名倾轧 name mangling 技术来改名函数名区分参数不同的同名函数。命 名倾轧是在编译阶段完成的。 编译时将参数类型加入以区分不同。 2. 重写 在 基类的函数前加上 virtual 关键字在派生类中重写该函数运行时将会根据对象的实际类 型来调用相应的函数 。如果 对象类型是派生类就调用派生类的函数如果对象类型是基类就调 用基类的函数。 虚函数需要注意的地方 用 virtual 关键字申明 的函数叫做虚函数虚函数肯定是类的成员函数。 存在虚函数的类都有一个一维的虚函数表叫做虚表 类的对象有一个指向虚表开始的虚指针。 虚表是和类对应的虚表指针是和对象对应的。 多态性是一个接口多种实现 是面向对象的核心分为类的多态性和函数的多态性。 重写用虚函数来实现结合动态绑定 。 纯虚函数 是虚函数再加上 0 。 抽象类 是指包括至少一个 纯虚函数的类 。 纯虚函数 virtual void fun()0 。即抽象类 必须在子类实现这个函数即先有名称没有内容 在 派生类实现内容。 5 说说构造函数有几种分别什么作用 C 中的构造函数可以分为 4 类默认构造函数、初始化构造函数、拷贝构造函数、移动构造函数。 1. 默认构造函数和初始化构造函数。 在定义类的对象的时候完成对象的初始化工作。 注意 有了有参的构造了编译器就不提供默认的构造函数。 class Student { public: //默认构造函数 Student() { num1001; age18; } //初始化构造函数 Student(int n,int a):num(n),age(a){} private: int num; int age; }; int main() { //用默认构造函数初始化对象S1 Student s1; //用初始化构造函数初始化对象S2 Student s2(1002,18); return 0; } 2. 拷贝构造函数 #include stdafx.h #include iostream.h class Test { int i; int *p; public: Test(int ai,int value) { i ai; p new int(value); } ~Test() { delete p; } Test(const Test t) { this-i t.i; this-p new int(*t.p); } }; //复制构造函数用于复制本类的对象 int main(int argc, char* argv[]) { Test t1(1,2); Test t2(t1);//将对象t1复制给t2。注意复制和赋值的概念不同 return 0; } 赋值构造函数默认实现的是值拷贝浅拷贝 。 3. 移动构造函数。用于将其他类型的变量隐式转换为本类对象。 下面的转换构造函数将 int 类型的 r转换为 Student 类型的对象对象的 age 为 r num 为 1004. Student(int r)int num1004 int age r6 只定义析构函数会自动生成哪些构造函数 只定义了析构函数编译器将自动为我们生成 拷贝构造函数和默认构造函数 。 默认构造函数和初始化构造函数。 在定义类的对象的时候完成对象的初始化工作。 class Student { public: //默认构造函数 Student() { num1001; age18; } //初始化构造函数 Student(int n,int a):num(n),age(a){} private: int num; int age; }; int main() { //用默认构造函数初始化对象S1 Student s1; //用初始化构造函数初始化对象S2 Student s2(1002,18); return 0; } 有了有参的构造了编译器就不提供默认的构造函数。拷贝构造函数 #include stdafx.h #include iostream.h class Test { int i; int *p; public: Test(int ai,int value) { i ai; p new int(value); } ~Test() { delete p; } Test(const Test t) { this-i t.i; this-p new int(*t.p); } }; //复制构造函数用于复制本类的对象 int main(int argc, char* argv[]) { Test t1(1,2); Test t2(t1);//将对象t1复制给t2。注意复制和赋值的概念不同。 return 0; } 赋值构造函数默认实现的是值拷贝浅拷贝。 答案解析 示例如下 class HasPtr { public: HasPtr(const string s string()) :ps(new string(s)), i(0) {} ~HasPtr() { delete ps; } private: string * ps; int i; }; 如果类外面有这样一个函数          HasPtr f(HasPtr hp) { HasPtr ret hp; ///... 其他操作 return ret; } 当函数执行完了之后将会调用 hp 和 ret 的析构函数将 hp 和 ret 的成员 ps 给 delete 掉但是由于 ret 和 hp指向了同一个对象因此该对象的 ps 成员被 delete 了两次这样产生一个未定义的错误所以说如 果一个类定义了析构函数那么它要定义自己的拷贝构造函数和默认构造函数。 7 说说一个类默认会生成哪些函数 定义一个空类 class Empty { }; 默认会生成以下几个函数无参的构造函数在定义类的对象的时候完成对象的初始化工作。 Empty() { } 拷贝构造函数 拷贝构造函数用于复制本类的对象 Empty(const Empty copy) { } 赋值运算符 Empty operator (const Empty copy) { } 析构函数非虚 ~Empty() { } 8 说说 C 类对象的初始化顺序有多重继承情况下的顺序 1. 创建派生类的对象基类的构造函数优先被调用 也优先于派生类里的成员类 2. 如果类里面有成员类成员类的构造函数优先被调用 ( 也优先于该类本身的构造函数 3. 基类构造函数如果有多个基类则构造函数的调用顺序是某类在类派生表中出现的顺序 而不是它们 在成员初始化表中的顺序 4. 成员类对象构造函数如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序 而 不是它们出现在成员初始化表中的顺序 5. 派生类构造函数作为一般规则派生类构造函数应该不能直接向一个基类数据成员赋值而是把值传递给适当的基类构造函数。 6. 综上可以得出初始化顺序 父类构造函数 – 成员类对象构造函数 – 自身构造函数 其中成员变量的初始化与声明顺序有关构造函数的调用顺序是类派生列表中的顺序。 析构顺序和构造顺序相反 。 9 简述下向上转型和向下转型 1. 子类转换为父类向上转型使用 dynamic_cast (expression) 这种转换相对来说 比较安全 不会有数据的丢失 2. 父类转换为子类向下转型 可以使用强制转换这种转换时 不安全的会导致数据的丢失 原因是 父类的指针或者引用的内存中可能不包含子类的成员的内存 。 10 简述下深拷贝和浅拷贝如何实现深拷贝 1. 浅拷贝 又称值拷贝 将源对象的值拷贝到目标对象中去 本质上来说 源对象和目标对象共用一份实体 只是所引用的变量名不同地址其实还是相同的 。举个简单的例子你的小名叫西西大名 叫冬冬当别人叫你西西或者冬冬的时候你都会答应这两个名字虽然不相同但是都指的是你。 2. 深拷贝 拷贝的时候先 开辟出和源对象大小一样的空间然后将源对象里的内容拷贝到目标对象中去 这样两个 指针就指向了不同的内存位置。并且里面的内容是一样的 深拷贝情况下不会出现重复释放同一块内存的错误。          11 简述一下 C 中的多态 由于 派生类重写基类方法然后用基类引用指向派生类对象调用方法时候会进行动态绑定这就是多态 。 多态分为 静态多态和动态多态 1. 静态多态 编译器在编译期间完成的编译器会根据实参类型来推断该调用哪个函数 如果有对应的函数就调用没有则在编译时报错。比如一个简单的加法函数 includeiostream using namespace std; int Add(int a,int b)//1 { return ab; } char Add(char a,char b)//2 { return ab; } int main() { coutAdd(666,888)endl;//1 coutAdd(1,2);//2 return 0; } 显然第一条语句会调用函数 1 而第二条语句会调用函数 2 这绝不是因为函数的声明顺序不信 你可以将顺序调过来试试。 2. 动态多态 其实要实现动态多态需要几个条件 —— 即动态绑定条件 1. 虚函数。 基类中必须有虚函数在派生类中必须重写虚函数。 2. 通过 基类类型的指针或引用来调用虚函数 。 12 说说为什么要虚析构为什么不能虚构造 1. 虚析构 将可能会被继承的父类的析构函数设置为虚函数可以保证当我们 new 一个子类然后使用基类指针指向该子类对象释放基类指针时可以释放掉子类的空间防止内存泄漏 。如果基类的 析构函数不是虚函数 在特定情况下会 导致派生类无法被析构 。 1. 用派生类类型指针绑定派生类实例析构的时候不管基类析构函数是不是虚函数都会正常 析构 2. 用基类类型指针绑定派生类实例析构的时候如果基类析构函数不是虚函数则只会析构基 类不会析构派生类对象从而造成内存泄漏。为什么会出现这种现象呢个人认为析构的时 候如果没有虚函数的动态绑定功能就只根据指针的类型来进行的而不是根据指针绑定的对 象来进行所以只是调用了基类的析构函数如果基类的析构函数是虚函数则析构的时候就 要根据指针绑定的对象来调用对应的析构函数了。 C 默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针占用额外的内存。 2. 不能虚构造 1. 从存储空间角度 虚函数对应一个 vtale, 这个表的地址是存储在对象的内存空间的 。如果将构 造函数设置为虚函数就需要到 vtable 中调用 可是对象还没有实例化没有内存空间分配如何调用 。悖论 2. 从实现上看 vbtl 在构造函数调用后才建立因而构造函数不可能成为虚函数。 13 说说模板类是在什么时候实现的 1. 模板实例化模板的实例化分为显示实例化和隐式实例化前者是研发人员明确的告诉模板应该使用什么样的类型去生成具体的类或函数后者是在编译的过程中由编译器来决定使用什么类型来实例化一个模板不管是显示实例化或隐式实例化最终生成的类或函数完全是按照模板的定义来实现的 2. 模板具体化当模板使用某种类型类型实例化后生成的类或函数不能满足需要时可以考虑对模板进行具体化。具体化时可以修改原模板的定义当使用该类型时按照具体化后的定义实现具体化相当于对某种类型进行特殊处理。 3. 代码示例 #include iostream using namespace std; // #1 模板定义 templateclass T struct TemplateStruct { TemplateStruct() { cout sizeof(T) endl; } }; // #2 模板显示实例化 template struct TemplateStructint; // #3 模板具体化 template struct TemplateStructdouble { TemplateStruct() { cout --8-- endl; } }; int main() { TemplateStructint intStruct; TemplateStructdouble doubleStruct; // #4 模板隐式实例化 TemplateStructchar llStruct; }   14 说说类继承时派生类对不同关键字修饰的基类方法的访问权限 类中的成员可以分为三种类型分别为 public 成员、 protected 成员、 public 成员。类中可以直接访问自己类的public 、 protected 、 private 成员但 类对象只能访问自己类的 public 成员 。 1. public 继承 派生类可以访问基类的 public 、 protected 成员不可以访问基类的 private 成员 派生 类对象可以访问基类的 public 成员 不可以访问 基类的 protected 、 private 成员。 2. protected 继承 派生类可以访问基类的 public 、 protected 成员 不可以访问基类的 private 成员 派生类对象不可以访问 基类的 public 、 protected 、 private 成员。 3. private 继承派生类可以访问基类的 public 、 protected 成员不可以访问基类的 private 成员 派生类对象不可以访问 基类的 public 、 protected 、 private 成员。 15 简述一下移动构造函数什么库用到了这个函数 移动也使用一个对象的值设置另一个对象的值。 移动实现的是对象值真实的转移 源对象到目的对 象 源对象将丢失其内容其内容将被目的对象占有 。移动操作的发生的时候是当移动值的对象是未命名的对象的时候。这里未命名的对象就是那些临时变量甚至都不会有名称。典型的未命名对象就是函数的返回值或者类型转换的对象。 16 构造函数为什么不能被声明为虚函数 1. 从存储空间角度虚函数对应一个 vtale, 这个表的地址是存储在对象的内存空间的。如果将构造函数设置为虚函数就需要到vtable 中调用可是对象还没有实例化没有内存空间分配如何调 用。悖论 2. 从使用角度虚函数主要用于在信息不全的情况下能使重载的函数得到对应的调用。构造函数本身就是要初始化实例那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的不可能通过父类的指针或者引用去调用因此也就规定构造函数不能是虚函数。 3. 从实现上看 vbtl 在构造函数调用后才建立因而构造函数不可能成为虚函数。从实际含义上看在调用构造函数时还不能确定对象的真实类型因为子类会调父类的构造函数而且构造函数的作用是提供初始化在对象生命期只执行一次不是对象的动态行为也没有太大的必要成为虚函数。 17 简述一下什么是常函数有什么作用 类的成员函数后面加 const 表明这个函数不会对这个类对象的数据成员准确地说是非静态数据成员作任何改变。 在设计类的时候一个 原则就是对于不改变数据成员的成员函数都要在后面加 const 而对于改变数据成员的成员函数不能加 const 。所以 const 关键字对成员函数的行为作了更明确的限定有 const 修饰的成员函数指 const 放在函数参数表的后面而不是在函数前面或者参数表内只能读取数据成员不能改变数据成员没有 const 修饰的成员函数对数据成员则是可读可写的。除此之外在类的成员函数后面加 const 还有什么好处呢那就是常量即 const 对象可以调用 const成员函数而不能调用非const 修饰的函数。正如非 const 类型的数据可以给 const 类型的变量赋值一样反之则不成立。 #includeiostream using namespace std; class CStu { public: int a; CStu() { a 12; } void Show() const { //a 13; //常函数不能修改数据成员 cout a I am show() endl; } }; int main() { CStu st; st.Show(); system(pause); return 0; } 18 说说什么是虚继承解决什么问题如何实现 虚继承是 解决 C 多重继承问题的一种手段从不同途径继承来的同一基类会在子类中存在多份拷贝 。这将存在两个问题其一 浪费存储空间 第二 存在二义性问题 通常可以将派生类对象的地址赋值给基类对象实现的具体方式是将基类指针指向继承类继承类有基类的拷贝中的基类对象的地址但是多重继承可能存在一个基类的多份拷贝这就出现了二义性。虚继承可以解决多种继承前面 提到的两个问题。 #includeiostream using namespace std; class A{ public: int _a; }; class B :virtual public A { public: int _b; }; class C :virtual public A { public: int _c; }; class D :public B, public C { public: int _d; }; //菱形继承和菱形虚继承的对象模型 int main() { D d; d.B::_a 1; d.C::_a 2; d._b 3; d._c 4; d._d 5; cout sizeof(D) endl; return 0; } 分别从菱形继承和虚继承来分析菱形继承中A 在 B,C,D, 中各有一份虚继承中 A 共享。上面的虚继承表实际上是一个指针数组。B 、 C 实际上是虚基表指针指向虚基表。虚基表存放相对偏移量用来找虚基类。 19 简述一下虚函数和纯虚函数以及实现原理 1. C 中的虚函数的作用主要是实现了多态的机制。关于多态简而言之就是用父类型的指针指向其子类的实例然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“ 多种形态” 这是一种泛型技术。如果调用非虚函数则无论实际对象是什么类型都执行基类类型所定义的函数。非虚函数总是在编译时根据调用该函数的对象引用或指针的类型而确定。如果调用 虚函数则直到运行时才能确定调用哪个函数运行的虚函数是引用所绑定或指针所指向的对象所。 属类型定义的版本。虚函数必须是基类的非静态成员函数。虚函数的作用是实现动态联编也就是在程序的运行阶段动态地选择合适的成员函数在定义了虚函数后可以在基类的派生类中对虚函数重新定义在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型。以实现统一的接口不同定义过程。如果在派生类中没有对虚函数重新定义则它继承其基类的虚函数。 class Person{ public: //虚函数 virtual void GetName(){ coutPersonName:xiaosiendl; }; }; class Student:public Person{ public: void GetName(){ coutStudentName:xiaosiendl; }; }; int main(){ //指针 Person *person new Student(); //基类调用子类的函数 person-GetName();//StudentName:xiaosi } 虚函数 Virtual Function 是通过一张虚函数表 Virtual Table 来实现的。简称为 V-Table 。在 这个表中主是要一个类的虚函数的地址表这张表解决了继承、覆盖的问题保证其容真实反应 实际的函数。这样在有虚函数的类的实例中这个表被分配在了这个实例的内存中所以当我们 用父类的指针来操作一个子类的时候这张虚函数表就显得由为重要了它就像一个地图一样指 明了实际所应该调用的函数。 2. 纯虚函数是在基类中声明的虚函数它在基类中没有定义但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“0” virtualvoid GetName() 0 。在很多情况下基类本身生成对象是不合情理的。例如动物作为一个基类可以派生出老虎、孔雀等子类但动物本身生成对象明显不合常理。为了解决上述问题将函数定义为纯虚函数则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类它不能生成对象。这样就很好地解决了上述两个问题。将函数定义为纯虚函数能够说明该函数为后代类型提供了可以覆盖的接口但是这个类中的函数绝不会调用。声明了纯虚函数的类是一个抽象类。所以用户不能创建类的实例只能创建它的派生类的实例。必须在继承类中重新声明函数不要后面的0 否则该派生类也不能实例化而且它们在抽象类中往往没有定义。定义纯虚函数的目的在于使派生类仅仅只是继承函数的接口。纯虚函数的意义让所有的类对象主要是派生类对象都可以执行纯虚函数的动作但类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者“ 你必须提供一个纯虚函数的实现但我不知道你会怎样实现它 ” 。 //抽象类 class Person{ public: //纯虚函数 virtual void GetName()0; }; class Student:public Person{ public: Student(){ }; void GetName(){ coutStudentName:xiaosiendl; }; }; int main(){ Student student; } 20 说说纯虚函数能实例化吗为什么派生类要实现吗为什么 参考回答 1. 纯 虚函数不可以实例化但是可以用其派生类实例化 示例如下 class Base { public: virtual void func() 0; }; #includeiostream using namespace std; class Base { public: virtual void func() 0; }; class Derived :public Base { public: void func() override { cout 哈哈 endl; } }; int main() { Base *b new Derived(); b-func(); return 0; } 2. 虚函数的原理采用 vtable 。类中含有纯虚函数时其 vtable 不完全有个空位。 即 “ 纯虚函数在类的 vftable 表中对应的表项被赋值为 0 。也就是指向一个不存在的函数。由于编译器 绝对不允许有调用一个不存在的函数的可能所以该类不能生成对象。在它的派生类中除非重写 此函数否则也不能生成对象。 ” 所以纯虚函数不能实例化。 3. 纯虚函数是在基类中声明的虚函数它要求任何派生类都要定义自己的实现方法以实现多态性 。 4. 定义纯虚函数是为了实现一个接口用来规范派生类的行为也即规范继承这个类的程序员必须实 现这个函数。派生类仅仅只是继承函数的接口。纯虚函数的意义在于让所有的类对象主要是派生类对象都可以执行纯虚函数的动作但基类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者“ 你必须提供一个纯虚函数的实现但我不知道你会怎样实现它” 。 21 说说C中虚函数与纯虚函数的区别 参考回答 1. 虚函数和纯虚函数可以定义在同一个类中含有纯虚函数的类被称为抽象类而只含有虚函数的类不能被称为抽象类。 2. 虚函数可以被直接使用也可以被子类重载以后以多态的形式调用而纯虚函数必须在子类中实现该函数才可以使用因为纯虚函数在基类有声明而没有定义。 3. 虚函数和纯虚函数都可以在子类中被重载以多态的形式被调用。 4. 虚函数和纯虚函数通常存在于抽象基类之中被继承的子类重载目的是提供一个统一的接口。 5. 虚函数的定义形式 virtual{} ; 纯虚函数的定义形式 virtual { } 0 ; 在虚函数和纯虚函数 的定义中不能有 static 标识符原因很简单被 static 修饰的函数在编译时要求前期绑定 , 然而虚函数 却是动态绑定而且被两者修饰的函数生命周期也不一样。 答案解析 1. 我们举个虚函数的例子 class A { public: virtual void foo() { coutA::foo() is calledendl; } }; class B:public A { public: void foo() { coutB::foo() is calledendl; } }; int main(void) { A *a new B(); a-foo(); // 在这里a虽然是指向A的指针但是被调用的函数(foo)却是B的! return 0; } 这个例子是虚函数的一个典型应用通过这个例子也许你就对虚函数有了一些概念。它虚就虚在 所谓 “ 推迟联编 ” 或者 “ 动态联编 ” 上一个类函数的调用并不是在编译时刻被确定的而是在运行时 刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数所以 被成为 “ 虚 ” 函数。虚函数只能借助于指针或者引用来达到多态的效果。 2. 纯虚函数是在基类中声明的虚函数它在基类中没有定义但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“0” virtual void funtion1()0 为了方便使用多态特性我们常常需要在基类中定义虚拟函数。 在很多情况下基类本身生成对象是不合情理的。例如动物作为一个基类可以派生出老虎、孔雀 等子类但动物本身生成对象明显不合常理。 为了解决上述问题引入了纯虚函数的概念将函数定义为纯虚函数方法 virtual ReturnType Function() 0; 则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类它不能生成对象。这样就很好地解决了上述两个问题。声明了纯虚函数的类是一个抽象类。所以用户不能创建类的实例只能创建它的派生类的实例。纯虚函数最显著的特征是它们必须在继承类中重新声明函数不要后面的0 否则该派生类也不能实例化而且它们在抽象类中往往没有定义。定义纯虚函数的目的在于使派生类仅仅只是继承函数的接口。纯虚函数的意义让所有的类对象主要是派生类对象都可以执行纯虚函数的动作但类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者“ 你必须提供一个纯虚函数的实现但我不知道你会怎样实现它” 。 22 说说 C 中什么是菱形继承问题如何解决 导致问题 菱形继承会导致数据重复和产生歧义 解决办法 使用虚继承可确保每份数据自继承一次 23 请问构造函数中的能不能调用虚方法 参考回答 1. 不要在构造函数中调用虚方法从语法上讲调用完全没有问题但是从效果上看往往不能达到需要的目的。 派生类对象构造期间进入基类的构造函数时对象类型变成了基类类型而不是派生类类型。 同样进入基类析构函数时对象也是基类类型。所以虚函数始终仅仅调用基类的虚函数如果是基类调用虚函数不能达到多态的效果所以放在构造函数中是没有意义的而且往往不能达到本来想要的效果。 24 请问拷贝构造函数的参数是什么传递方式为什么 参考回答 1. 拷贝构造函数的参数必须使用引用传递 2. 如果拷贝构造函数中的参数不是一个引用即形如 CClass(const CClass c_class) 那么就相当于采用了传值的方式(pass-by-value) 而传值的方式会调用该类的拷贝构造函数从而造成无穷递归地调用拷贝构造函数。因此拷贝构造函数的参数必须是一个引用。需要澄清的是传指针其实也是传值如果上面的拷贝构造函数写成CClass(const CClass* c_class)也是不行的。事实上只有传引用不是传值外其他所有的传递方式都是传值。 25 如何理解抽象类 参考回答 1. 抽象类的定义如下 纯虚函数是在基类中声明的虚函数它在基类中没有定义但要求任何派生类都要定义自己的实现 方法。 在基类中实现纯虚函数的方法是在函数原型后加 “0” 有虚函数的类就叫做抽象类。 2. 抽象类有如下几个特点 1 抽象类只能用作其他类的基类不能建立抽象类对象 。 2 抽象类不能用作参数类型、函数返回类型或显式转换的类型。 3 可以定义指向抽象类的指针和引用此指针可以指向它的派生类进而实现多态性。 26 什么是多态除了虚函数还有什么方式能实现多态 参考回答 1. 多态是面向对象的重要特性之一它是一种行为的封装就是不同对象对同一行为会有不同的状 态。 ( 举例 : 学生和成人都去买票时 , 学生会打折 , 成人不会 ) 2. 多态是以封装和继承为基础的。在 C 中多态分为静态多态早绑定和动态多态晚绑定两 种其中动态多态是通过虚函数实现静态多态通过函数重载实现代码如下 class A { public: void do(int a); void do(int a, int b); }; 27 简述一下虚析构函数什么作用 参考回答 1. 虚析构函数是将基类的析构函数声明为 virtual 举例如下 class TimeKeeper { public: TimeKeeper() {} virtual ~TimeKeeper() {} }; 2. 虚析构函数的 主要作用是防止内存泄露 。 定义一个基类的指针 p 在 delete p 时如果基类的析构函数是虚函数这时只会看 p 所赋值的对 象如果 p 赋值的对象是派生类的对象就会调用派生类的析构函数毫无疑问在这之前也会先 调用基类的构造函数在调用派生类的构造函数然后调用派生类的析构函数基类的析构函数 所谓先构造的后释放如果 p 赋值的对象是基类的对象就会调用基类的析构函数这样就不会 造成内存泄露。 如果基类的析构函数不是虚函数在delete p 时调用析构函数时只会看指针的数据类型而不 会去看赋值的对象这样就会造成内存泄露。 答案解析 我们创建一个 TimeKeeper 基类和一些及其它的派生类作为不同的计时方法。 class TimeKeeper { public: TimeKeeper() {} ~TimeKeeper() {} //非virtual的 }; //都继承与TimeKeeper class AtomicClock :public TimeKeeper{}; class WaterClock :public TimeKeeper {}; class WristWatch :public TimeKeeper {}; 如果客户想要在程序中使用时间不想操作时间如何计算等细节这时候我们可以设计 factory 工 厂函数让函数返回指针指向一个计时对象。该函数返回一个基类指针这个基类指针是指向于 派生类对象的。 TimeKeeper* getTimeKeeper() { //返回一个指针指向一个TimeKeeper派生类的动态分配对象 } 因为函数返回的对象存在于堆中因此为了 在不使用时我们需要使用释放该对象 delete TimeKeeper* ptk getTimeKeeper(); delete ptk; 此处基类的析构函数是非 virtual 的因此 通过一个基类指针删除派生类对象是错误的 解决办法 将基类的析构函数改为 virtual 就正确了。 class TimeKeeper { public: TimeKeeper() {} virtual ~TimeKeeper() {} }; 声明为 virtual 之后通过 基类指针删除派生类对象就会释放整个对象基类 派生类 28 说说什么是虚基类可否被实例化 1. 在被继承的类前面加上virtual关键字这时被继承的类称为虚基类代码如下 class A class B1:public virtual A; class B2:public virtual A; class D:public B1,public B2; 2. 虚继承的类可以被实例化举例如下 class Animal {/* ... */ }; class Tiger : virtual public Animal { /* ... */ }; class Lion : virtual public Animal { /* ... */ } int main( ) { Liger lg ; /*既然我们已经在Tiger和Lion类的定义中声明了virtual关键字于是下面的代码编译OK */ int weight lg.getWeight(); } 29 简述一下拷贝赋值和移动赋值 参考回答 1. 拷贝赋值是通过拷贝构造函数来赋值在创建对象时使用同一类中之前创建的对象来初始化新创建的对象。 2. 移动赋值是通过移动构造函数来赋值二者的主要区别在于 1 拷贝构造函数的形参是一个左值引用而移动构造函数的形参是一个右值引用 2 拷贝构造函数完成的是整个对象或变量的拷贝而移动构造函数是生成一个指针指向源对象或 变量的地址接管源对象的内存相对于大量数据的拷贝节省时间和内存空间。 30 仿函数了解吗有什么作用 参考回答 1. 仿函数 functor 又称为函数对象 function object 是一个能行使函数功能的类。仿函数的语 法几乎和我们普通的函数调用一样不过作为仿函数的类都必须重载 operator() 运算符举个例 子 class Func{ public: void operator() (const string str) const { coutstrendl; } }; Func myFunc; myFunc(helloworld!); helloworld! 1. 仿函数既能想普通函数一样传入给定数量的参数还能存储或者处理更多我们需要的有用信息。我们可以举个例子 假设有一个 vectorstring 你的任务是统计长度小于 5 的 string 的个数如果使用 count_if 函 数的话你的代码可能长成这样 bool LengthIsLessThanFive(const string str) { return str.length()5; } int rescount_if(vec.begin(), vec.end(), LengthIsLessThanFive); 其中 count_if 函数的第三个参数是一个函数指针返回一个 bool 类型的值。一般的如果需要将 特定的阈值长度也传入的话我们可能将函数写成这样 bool LenthIsLessThan(const string str, int len) { return str.length()len; } 这个函数看起来比前面一个版本更具有一般性但是他不能满足 count_if 函数的参数要求 count_if 要求的是 unary function 仅带有一个参数作为它的最后一个参数。如果我们使用仿 函数是不是就豁然开朗了呢 class ShorterThan { public: explicit ShorterThan(int maxLength) : length(maxLength) {} bool operator() (const string str) const { return str.length() length; } private: const int length; }; 31 C 中哪些函数不能被声明为虚函数 参考回答 常见的不不能声明为虚函数的有 普通函数非成员函数静态成员函数内联成员函数构造函 数友元函数。 1. 为什么 C 不支持普通函数为虚函数 普通函数非成员函数只能被 overload 不能被 override 声明为虚函数无意义 因此编译器会 在编译时绑定函数。 2. 为什么 C 不支持构造函数为虚函数 这个原因很简单主要是从语义上考虑所以不支持。因为构造函数本来就是为了明确初始化对象 成员才产生的然而 virtual function 主要是为了再不完全了解细节的情况下也能正确处理对象。另 外 virtual 函数是在不同类型的对象产生不同的动作现在对象还没有产生如何使用 virtual 函数 来完成你想完成的动作 。这不就是典型的悖论 构造函数用来创建一个新的对象 , 而虚函数的运行是建立在对象的基础上 , 在构造函数执行时 , 对象尚 未形成 , 所以不能将构造函数定义为虚函数 3. 为什么 C 不支持内联成员函数为虚函数 其实很简单那 内联函数就是为了在代码中直接展开减少函数调用花费的代价虚函数是为了在 继承后对象能够准确的执行自己的动作 这是不可能统一的。再说了 inline 函数在编译时被展 开 虚函数在运行时才能动态的绑定函数 内联函数是在编译时期展开 , 而虚函数的特性是运行时才动态联编 , 所以两者矛盾 , 不能定义内联函数 为虚函数 4. 为什么 C 不支持静态成员函数为虚函数 这也很简单静态成员函数对于每个类来说只有一份代码所有的对象都共享这一份代码他也没 有要动态绑定的必要性。 静态成员函数属于一个类而非某一对象 , 没有 this 指针 , 它无法进行对象的判别 5. 为什么 C 不支持友元函数为虚函数 因为 C 不支持友元函数的继承对于没有继承特性的函数没有虚函数的说法 。 32 解释下 C 中类模板和模板类的区别 参考回答 1. 类模板是模板的定义不是一个实实在在的类定义中用到通用类型参数 2. 模板类是实实在在的类定义是类模板的实例化。类定义中参数被实际类型所代替。 答案解析 1. 类模板的类型参数可以有一个或多个每个类型前面都必须加 class 如 template class T1,class T2class someclass{…}; 在定义对象时分别代入实际的类型名如 someclassint,double obj; 2. 和使用类一样使用类模板时要注意其作用域只能在其有效作用域内用它定义对象。 3. 模板可以有层次一个类模板可以作为基类派生出派生模板类. 32 虚函数表里存放的内容是什么时候写进去的 参考回答 1. 虚函数表是一个存储虚函数地址的数组 , 以 NULL 结尾。 虚表 vftable 在编译阶段生成对象内存空间开辟以后写入对象中的 vfptr 然后调用构造函数。即虚表在构造函数之前写入 2. 除了在构造函数之前写入之外我们还需要考虑到 虚表的二次写入机制通过此机制让每个对象的虚表指针都能准确的指向到自己类的虚表为实现动多态提供支持。
http://www.pierceye.com/news/194308/

相关文章:

  • 网站空间便宜网站的信息管理建设的必要性
  • 校级特色专业建设网站博达站群网站建设教程
  • 有没有做任务的网站吗网站首页开发
  • 公司名字变了网站备案济南网站建设公司哪个好点呢
  • 图书馆网站建设的规章制度企业免费招聘网站
  • 效果图网站大全系统优化的例子
  • 京东的网站建设介绍网站开发要源码多少钱
  • 东莞网站制作公司报价企业定制
  • 创同盟做网站生成拼贴的网站
  • 网站备案号查电话号码商场网站开发
  • 手机网站建站教育模板下载泰州公司注册
  • 如何做商业网站推广西安市城乡建设管理局网站的公示栏
  • 上海做兼职哪个网站腾讯企业邮箱域名是什么
  • 霸州网站制作棋牌网站建设源码
  • 茶叶网站制作模板网页设计在安阳工资多少
  • 网站建设项目验收方案自己做捕鱼网站能不能挣钱
  • 微信网页网站怎么做我为群众办实事实践活动
  • 建设银行发卡银行网站福州 网站设计
  • 网站备案号码舟山高端网站建设
  • 买奢侈品代工厂做的产品的网站名建立网站 英语怎么说
  • 网站访问者qq计算机等级培训机构
  • 可以让外国人做问卷调查的网站济南优化seo网站建设公司
  • odoo做网站创建企业需要什么条件
  • 山西省旅游网站建设分析wordpress 个人介绍
  • 山东高级网站建设赚钱
  • 做网站大概要多少钱新建网站的外链多久生效
  • 天河区建设网站品牌网站建设小8蝌蚪
  • 深圳市企业网站seo点击软件小程序游戏开发公司
  • 南宁企业网站设计公怎么进wordpress
  • 商务网站建设一万字做视频剪辑接私活的网站