长沙哪家做网站设计好,网站一级域名和二级域名,公司名称打分,销售外包前言
以下问题以QA形式记录#xff0c;基本上都是笔者在初学一轮后#xff0c;掌握不牢或者频繁忘记的点
QA的形式有助于学习过程中时刻关注自己的输入与输出关系#xff0c;也适合做查漏补缺和复盘。
本文对读者可以用作自查#xff0c;答案在后面#xff0…前言
以下问题以QA形式记录基本上都是笔者在初学一轮后掌握不牢或者频繁忘记的点
QA的形式有助于学习过程中时刻关注自己的输入与输出关系也适合做查漏补缺和复盘。
本文对读者可以用作自查答案在后面需要时自行对照。 问题集
Q1C中是否有相关设计类似实现“接口”的功能
Q2当我们在Class A类中定义了一个纯虚函数的时候A类还可以被实例化吗为什么
Q3我们如何合规地使用C中的“接口”
Q4虚函数中 virtual 关键字对程序起到的实质性的作用注意区分虚继承
Q5override的书写在函数多态中可有可无
Q6虚函数有哪些额外的开销运行时成本
Q8release和debug版本的具体区别 Q9C中默认的隐式转换可以做几次
Q10explicit关键字是用来干什么的
Q11以下4句语句的 Animal a 对象各自分配在堆还是栈上 在栈和堆上各自作用域/生存周期有何不同 Q12作用域内变量自动销毁的特性若想利用可以有什么好的实践看看即可 Q13对于unique_ptr为什么不能通过以下语法实现复制
Q14对于智能指针为什么不建议通过new对象获得指针而是使用 std::make_xx 方法
Q15关于强制转换C风格的方法和C风格的方法有何不同
Q16static_cast、dynamic_cast、const_cast 分别最主要用在什么地方
Q17dynamic_cast 比 static_cast 慢为什么 参考解答
Q1C中是否有相关设计类似实现“接口”的功能
A1纯虚函数pure virtual function与java和C#中的接口定义比较接近 定义一个没有实现的函数强制要求继承所属类的派生类去实现它。 其格式是 virtual ... func() 0;
class Animal{
public:int age 10;static const int sex 1;virtual void speak() 0; // 声明纯虚函数
}; Q2当我们在Class A类中定义了一个纯虚函数的时候A类还可以被实例化吗为什么
A2不可以A类的纯虚函数被要求必须指定一个子类去实现其接口。重写这个当做“蓝图”的纯虚函数 以上文中的例子 Animal *a new Animal; // 是不合法的语法 不能实例化含有纯虚函数的类对象的原因有以下几点 接口不完整含有纯虚函数的类定义了一个不完整的接口。这意味着基类本身并没有提供足够的实现细节来创建一个完整的对象。 强制实现纯虚函数的存在是为了确保所有派生类都实现了这些函数。如果允许实例化含有纯虚函数的类那么这种强制实现的约束就被破坏了。 Q3我们如何合规地使用C中的“接口”
A3谨慎地多重继承。实际上很多其他语言都有关键字interface但是C只有class 主要的代码可以是
class InterFace{ ... 内有一个纯虚函数 }
...
class 需要这个接口的类 public Base, InterFace{ ... 之后在这里重写纯虚函数 } Q4虚函数中 virtual 关键字对程序起到的实质性的作用注意区分虚继承 A4告诉编译器“Hey为我的这个函数创造一个vftable吧”。 如果这个函数被重写且被子类对象调用了就按照重写虚函数指针所指的函数执行。 Q5override的书写在函数多态中可有可无
A5雀食可以省略但是还是建议养成好习惯坚持写上去增加可读性
class Cat : public Animal{void speak() override {cout Cat-speak endl;}
}; Q6虚函数有哪些额外的开销运行时成本
A61我们需要额外的内存来存储v表以分配到正确的函数这会在Base类中多一个 *vfptr 的指针
2在调用时我们需要遍历这个表来确定要映射到哪个函数
一般来说开销不会特别大所以尽管用 Q7我们在VS上写一个 HelloWorld 的时候这个HelloWorld默认格式是string吗
A7不是诶是 const char [ ] 类型 在C中字符串字面量如 Hello, World!会被编译器识别为 const char[] 类型但是当使用标准库中的输入输出流时如std::cout它会隐式地将const char*即 const char[] 的指针类型转换为 std::string Q8release和debug版本的具体区别
A81编译器优化力度有所区别 2调试信息debug版本通常更丰富一些 3再者初始化的变量值可能不同 在debug版本下有的未初始化变量可能是 0xcccccccc。 在release版本下为了考虑性能这个部分内容应该是0x212B421F这样的随机脏数据 4断言Debug版本中断言assertions通常是启用的以在开发过程中捕获潜在的错误。而在Release版本中断言可能被禁用以避免影响程序性能。 Q9C中默认的隐式转换可以做几次 我们假设有以下情境 1这里的 Cherno 是一个 char 数组 2程序中有 class Entity 带构造函数构造函数可以构造属性 string Name 输入 A9我们在调用 PrintEntity 函数的时候编译器试图在进行两次隐式转换 即char[] 到 std::string 再到 Entity 但是隐式转换在C中默认只能自动进行一次所以这里产生了错误。 解决方式选择以下写法中的任意一种 实际上在这里我们使用了一种 “隐式构造函数” 也就是经过隐式转化的构造函数可以简化代码但最好避免使用。 Q10explicit关键字是用来干什么的
A10主要用在构造函数上要求显式类型调用构造函数也就是强制要求禁止使用隐式转换。确保输入参数的类型安全
例如要使用构造这个Dog对象则必须显式调用此构造函数 这个语法主要是影响这种表达
Dog e 22; // 调用的是dog的有参构造函数相当于 e.age (int)22// 显然这种方式会产生一次隐式转换, // err:不存在从 int 转换到 Dog 的适当构造函数C/C(415)
然而以下方式定义就不会有问题
Dog e(22); 或者 Dog* e new Dog(22); 原因是他们不会将构造函数的入参弄成 int(22)
根据 Cherno 是一个 char 数组也要注意尽可能用 std::string 作为输入而不要用默认 堆上数据和栈上数据的生存周期问题重要 Q11以下4句语句的 Animal a 对象各自分配在堆还是栈上 在栈和堆上各自作用域/生存周期有何不同 A11
1 Animal a;分配在栈空间上他的作用域是整个main函数因此会打印 create
2 {Animal a;}分配在栈空间上不过他的作用域是他所在的代码块因此会打印 createdestory 注意这里 a 因为结束了代码块的执行作用域一过自动被释放
3{ Animal a Animal(); } 分配在栈空间上作用域是他所在的代码块打印 createdestory
4{Animal* a new Animal();}分配在堆空间上作用域是他所在的代码块但会长期存在打印 create 栈是一种会自动默认跟随生命周期管理的数据结构但是堆不会。堆的特点就是只能手动申请和释放 虽然有些时候编译器会帮我们去保留一段时间但是完全不建议这么做。
尤其是这种代码 Q12作用域内变量自动销毁的特性若想利用可以有什么好的实践看看即可 A12 注意这些应用都只针对作用域
1作用域指针ScopedPtr大概意思是使用一个类其对象含有一个private的 Entity *类型有构造和析构 当我们即使是使用new申请堆空间的时候也可以利用析构函数达到自动释放的效果。 这就是智能指针smart ptr或是unique_ptr做的最基本的事情
2Timer计时器对象构造时创建析构时输出打印。你只需要在函数开头写一行代码那么整个作用域会被计时
3自动mutex在作用域内锁住出作用域时解锁 这里简单补充一点智能指针的知识
1unique_ptr作用域指针 不允许被复制不会计数会自动销毁 出作用域时会自动释放。不能被复制也就不会导致有2个ptr指向同一块内存进而引发释放错误
格式std::unique_ptr类名 ptr std::make_unique类名();
效果最终不会得到一个没有引用的悬空指针 从而造成内存泄露
完整程序 class Animal{
public:Animal() {cout create endl;}~Animal() {cout destroy endl;}
};int main(){{std::unique_ptrAnimal ptr std::make_uniqueAnimal();}while (1);return 0;
}
2共享指针 shared_ptr 允许被复制会计数会自动销毁 shared ptr的工作方式是通过引用计数可以跟踪计数有多少个引用。当计数0时才删除。有点文件系统里面 inode或者信号量 的意思
因为shared ptr需要分配另一块内存叫做控制块用来存储引用计数 3弱指针 weak_ptr 允许被复制不会计数会自动销毁 Q13对于unique_ptr为什么不能通过以下语法实现复制 std::unique_ptrEntity ptr_new ptr;
A13编译器会报错因为 std::unique_ptr 没有拷贝构造函数。 Q14对于智能指针为什么不建议通过new对象获得指针而是使用 std::make_xx 方法 在 unique_ptr 中不直接调用new的原因是因为异常安全。 当使用 new 创建对象时如果对象的构造函数抛出异常而程序员忘记使用智能指针来管理这个对象那么这个对象将不会被正确地删除导致资源泄漏。std::make_xx() 方法在内部处理了这些异常情况确保了即使构造函数失败资源也会被正确释放。 在shared_ptr中std::make_xx() 主要是用于共享指针需要获得控制块变量以便计数。 另外std::make_xx() 提供了一致的创建智能指针的方式。增加可读性避免维护困难。 Q15关于强制转换C风格的方法和C风格的方法有何不同
A15
C风格的方法 int a (int)c
C风格的方法
1静态转换用于非多态类型的转换如基本数据类型编译时而非运行时进行检查 int a 10; double b static_castdouble(a); // 将int转换为double
2动态转换用于处理多态性只能在含有虚函数的类层次结构中使用 class Base { virtual void dummy() {} } class Derived : public Base {}; Base* basePtr new Derived(); Derived* derivedPtr dynamic_cast Derived*(basePtr); if (derivedPtr) { // 转换成功 }
3const_cast 使得不能改写的const类型数据变得可以修改 const int* a new int(10); int* b const_castint*(a); // 移除const限定符 Q16static_cast、dynamic_cast、const_cast 分别最主要用在什么地方
A16 static_cast 可用于基本数据类型之间的转换如将 int 转换为 char 或将 float 转换为 int。 可用于类层次结构中的向上转型从派生类向基类转换因为这是安全的编译器知道所有相关的信息 dynamic_cast 主要用于处理多态性即在运行时确定对象的实际类型。只能用于包含虚函数的类的指针或引用类型的向下转型从基类向派生类转换。如果转换失败指针类型的 dynamic_cast 返回 nullptr引用类型的转换会抛出 std::bad_cast 异常。 const_cast 主要用来修改类型的 const 限定符。可以用来将 const 类型的指针或引用转换为非 const 类型或者反之。转换不会改变对象本身是否是常量只是改变指针或引用的 const 属性。示例代码 const int* constIntPtr new int(5);
int* modifiableIntPtr const_castint*(constIntPtr);
*modifiableIntPtr 10; // 现在可以修改值 Q17dynamic_cast 比 static_cast 慢为什么
A17因为它需要在运行时检查对象的实际类型