自界说string(C++)

打印 上一主题 下一主题

主题 507|帖子 507|积分 1521

第二章、面试的根本知识(1)

1.根本知识


  • C++的根本知识,如面向对象的特性、构造函数、析构函数、动态绑定等。
  • 设计模式、UML图。
  • 内存管理。
  • 数据结构和算法、编程能力、部分数学知识,如概率、标题的分析和推理能力。
  • OS的文件利用、程序性能、多线程、程序安全等。
  • 并发控制、算法复杂度、字符串处理。
    语言面试:概念的明白,分析代码,写代码
2、C++中,有哪4种与类型转换相关的关键字?各有什么特点?应该在什么场合下使用?

C++中,四个与类型转换相关的关键字:static_cast、const_cast、reinterpret_cast、dynamic_cast。
static_cast

特点:静态转换,在编译处理期间。
应用场合:主要用于C++中内置的基本数据类型之间的转换,但是没有运行时类型的检测来包管转换的安全性。

  • 用于基类和子类之间的指针或引用之间的转换,这种转换把子类的指针或引用转换为基类表现是安全的;举行下行转换,把积累的指针或引用转换为子类表现时,由于没有进举措态类型检测,所以是不安全的。
  • 把void类型的指针转换成目的类型的指针(不安全)。
  • 不能用于两个不相关的类型转换。
  • 不能把const对象转换成非const对象。
const_cast

特点:去常转换,编译时执行。
应用场合:const_cast利用不能在不同的种类间转换。相反,它仅仅把它作用的表达式转换成常量。它可以使一个原来不是const类型的数据转换成const类型的,或者把const属性去掉。
reinterpret_cast

特点: 重表明类型转换
应用场合:它有着和c风格逼迫类型转换同样的功能;它可以转化任何的内置数据类型为其他的类型,同时它也可以把任何类型的指针转化为其他的类型;它的机理是对二进制举行重新的表明,不会改变原来的格式。
dynamic_cast < type-id > ( expression )

该运算符将expression转换成type_id类型的对象。type_id必须是类的指针,类的引用或者空类型的指针。
a、如果type_id是一个指针类型,那么expression也必须是一个指针类型,如果type_id是一个引用类型,那么expression也必须是一个引用类型。
b、如果type_id是一个空类型的指针,在运行的时间,就会检测expression的实际类型,结果是一个由expression决定的指针类型。
c、如果type_id不是空类型的指针,在运行的时间指向expression对象的指针能否可以转换成type_id类型的指针。
  d、在运行的时间决定真正的类型,如果向下转换是安全的,就返回一个转换后的指针,若不安全,则返回一个空指针。
e、主要用于上下行之间的转换,也可以用于类之间的交叉转换。上行转换时和static_cast结果一样,下行转换时,具有检测功能,比static_cast更安全。
3、strlen和sizeof区别



  • sizeof是运算符,并不是函数,结果在编译时得到而非运行中获得;strlen是字符处理的库函数。
  • sizeof参数可以是任何数据的类型或者数据(sizeof参数不退化);strlen的参数只能是字符指针且结尾是’\0’的字符串。
  • 因为sizeof值在编译时确定,所以不能用来得到动态分配(运行时分配)存储空间的大小。
4、界说一个空类型,里面没有任何成员变量和成员函数。



  • 对该类型求解sizeof,得到的结果是1,而不是0,声明类型实例必须占用一定的内存空间,由编译器决定占多大内存。在Visual Studio中,每个空类型的实例占用一个字节的空间。
  • 在该类中添加一个构造函数和析构函数,sizeof还是1,调用它们只需知道函数地址即可,地址与类型相关,与类型的实例无关。
  • 当析构函数标志为虚函数后,C++编译器一旦发现一个类型中有虚函数,就会为该类型生成虚函数表,并在该类型的每一个实例中添加一个指向虚函数表的指针。32位机器上,sizeof为4,64位为8.
5、面试题1:赋值运算符函数

如下为类型CMyString的声明,请为该类型添加赋值运算符函数.
  1. class CMyString
  2. {
  3. public:
  4.         CMyString(char* pData = NULL);
  5.         CMyString(const CMyString& str);
  6.         ~CMyString(void);
  7.    
  8. private:
  9.         char* m_pData;
  10. };
复制代码
注意:

1、把返回值的类型声明为该类型的引用,并在该函数竣事前返回实例自身的引用(即*this),只有返回一个引用,才可以允许一连赋值
2、把传入的参数的类型声明为常量引用。如果传入的参数不是引用而是实例,那么从形参到实参就会调用一次复制构造函数。把参数声明为引用就可以避免这样的无谓消耗,进步代码的效率。同时我们在赋值运算符函数内不会改变传入的实例的状态,因此应该为传入的应用参数加上const关键字。
3、释放实例本身本身的内存。如果我们忘记在分配内存之前释放自身已有的空间,程序将出现内存泄漏。
4、判断传入的参数和当前的实例(this)是不是同一个实例。如果是同一个,则不举行赋值利用,直接返回。如果事先不判断就举行赋值,那么在释放实例自身的内存时就会导致严重的标题:当this和传进来的参数是同一个实例时,那么一旦释放了自身的内存,传入的参数的内存也同时被释放了,因此再也找不到须要赋值的内容了。
以下代码我实现了完备的自界说string。(cout部分可以注释掉,仅仅是为了测试,便于明白)
涉及C++知识点:赋值构造,拷贝构造,移动语义,深拷贝,浅拷贝,引用,内存泄漏,C++异常安全等。
  1. #include <string.h>
  2. #include <cstdio>
  3. #include <iostream>
  4. using std::cout;
  5. using std::cin;
  6. using std::endl;
  7. class CMyString
  8. {
  9. public:
  10.     CMyString();
  11.     CMyString(const char* m_pDate);
  12.     CMyString(const CMyString& str);//深拷贝
  13.     CMyString(CMyString && str);//右值
  14.     CMyString& operator=(const CMyString &str);//深拷贝
  15.     CMyString& operator=(CMyString && str); //右值
  16.     ~CMyString(void);
  17.     void print() {
  18.         if(m_pDate) {
  19.             cout << m_pDate << endl;
  20.         }
  21.     }
  22.     void swap(CMyString &str) {
  23.         std::swap(m_pDate,str.m_pDate);
  24.     }
  25.     size_t length() const {
  26.         return strlen(m_pDate);
  27.     }
  28.     const char* c_str() const {
  29.         return m_pDate;
  30.     }
  31.    
  32. private:
  33.     char* m_pDate;
  34. };
  35. CMyString::CMyString()
  36. :m_pDate(new char[1]())
  37. {
  38.     cout << "CMyString()"<< endl;
  39. }
  40. CMyString::CMyString(const char* pData)
  41. :m_pDate(new char[strlen(pData)+1]())
  42. {
  43.     strcpy(m_pDate,pData);
  44.     cout << "CMyString(const char*)" << endl;
  45. }
  46. CMyString::CMyString(const CMyString &str)
  47. :m_pDate(new char[str.length()+1]())
  48. {
  49.     strcpy(m_pDate,str.m_pDate);
  50.     cout << "CMyString(const CMyString&)" << endl;
  51. }
  52. CMyString::CMyString(CMyString && str)
  53. :m_pDate(str.m_pDate)
  54. {
  55.     str.m_pDate = nullptr;
  56.     cout << "CMyString(CMyString &&)" << endl;
  57. }
  58. CMyString& CMyString::operator=(const CMyString &str)
  59. {
  60.     if(this != &str) {
  61.         delete [] m_pDate;
  62.         m_pDate = new char[str.length()+1]();
  63.         strcpy(m_pDate,str.m_pDate);
  64.     }
  65.     return *this;
  66. }
  67. CMyString& CMyString::operator=(CMyString && str)
  68. {
  69.     if(this != &str) {
  70.         delete [] m_pDate;
  71.         m_pDate = str.m_pDate;
  72.         str.m_pDate = nullptr;
  73.     }
  74.     return *this;
  75. }
  76. CMyString::~CMyString()
  77. {
  78.     if(m_pDate) {
  79.         delete [] m_pDate;
  80.         m_pDate = nullptr;
  81.     }
  82.     cout << "~CMyString()" << endl;
  83. }
  84. void test0()
  85. {
  86.     CMyString s1;
  87.     cout << 1;
  88.     s1.print();
  89.     CMyString s2 = "hello";
  90.     cout << 2;
  91.     s2.print();
  92.     CMyString s3(s2);
  93.     cout << 3;
  94.     s3.print();
  95.     CMyString s4 ="world";
  96.     s3.swap(s4);//C++异常安全通过 copy-and-swap 方法往往可以实现”强烈保证“,
  97.     cout << 4;
  98.     s3.print();
  99.     s4.print();
  100.     CMyString s5(std::move(s2));//CMyString s5("hello");
  101.     cout << 5;
  102.     s5.print();
  103.    
  104.     CMyString s7,s8;
  105.     s7 = s8 = s3;//连续赋值
  106. }
  107. int main(void)
  108. {
  109.      test0();
  110.      return 0;
  111. }
复制代码
执行结果

拓展:C++异常安全

一个函数如果说是“异常安全”的,必须同时满意以下两个条件:1.不泄漏任何资源;2.不允许粉碎数据。
C++中’异常安全函数”提供了三种安全等级:

  • 基本承诺:如果异常被抛出,对象内的任何成员仍旧能保持有用状态,没有数据的粉碎及资源泄漏。但对象的现实状态是不可估计的,即不一定是调用前的状态,但至少包管符合对象正常的要求。
  • 强烈包管:如果异常被抛出,对象的状态保持不变。即如果调用乐成,则完全乐成;如果调用失败,则对象依然是调用前的状态。
  • 不抛异常包管:函数承诺不会抛出任何异常。一般内置类型的所有利用都有不抛异常的包管。
如果一个函数不能提供上述包管之一,则不具备异常安全性。
对于资源泄漏标题,解决方法很容易,即用对象来管理资源。RAII技术之前介绍过,这里就不再赘述。我们在函数中不直接对互斥锁mutex举行利用,而是用到一个管理互斥锁的对象MutexLock ml。函数的新实现如下:
  1. void Type::Func()  
  2. {  
  3.     MutexLock ml(&mutex);  
  4.     DoSomething();  
  5. }  
复制代码
对象ml初始化后,主动对mutex上锁,然后做该做的事。末了我们不用负责释放互斥锁,因为ml的析构函数主动为我们释放了。这样,即时DoSomething()中抛出异常,ml也总是要析构的,就不用担心互斥锁不被正常释放的标题了。
对于第二个标题,一个经典的策略叫“copy and swap”。原则很简朴:即先对原对象做出一个副本(copy),在副本上做须要的修改。如果出现任何异常,原对象依然能包管不变。如果修改乐成,则通过不抛出任何异常的swap函数将副本和原对象举行互换(swap)。函数的新实现如下:
  1. Type& Type::operator = (const Type &t)  
  2. {  
  3.     Type tmp(t);  
  4.     swap(m_t,tmp->m_t);  
  5.     return *this;  
  6. }  
复制代码
先创建一个被复制对象t的副本tmp,此时原对象尚未有任何修改,这样纵然申请资源时有异常抛出,也不会影响到原对象。如果创建乐成,则通过swap函数对临时对象的资源和原对象资源举行互换,标准库的swap函数承诺不抛出异常的,这样原对象将乐成酿成对象 t 的复制版本。对于这个函数,我们可以以为它是”强烈包管“异常安全的。
当然,提供强烈包管并不是总是可以或许实现的。一个函数可以或许提供的异常安全性等级,也取决于它的实现。考虑以下例子:
  1. void Func()  
  2. {  
  3.     f1();  
  4.     f2();  
  5. }  
复制代码
如果f1和f2都提供了”强烈包管“,则显然Func函数是具有”强烈包管“的安全等级。但是如果f1或f2中有一个不能提供,则Func函数将不再具备”强烈包管“等级,而是取决于f1和f2中安全等级最低的谁人。
为了让代码具有更好的异常安全性,首先是”用对象来管理资源“,以避免资源的泄漏。其次,在异常安全性等级上,应该尽可能地往更高的等级上来限定。通过 copy-and-swap 方法往往可以实现”强烈包管“。但是我们也应该知道,”强烈包管“并不是对所有的情况都可实现,这取决于你在实现中用到的函数。函数提供的异常安全性的最高等级只能是你实现中调用的各个函数中异常安全性等级最低的谁人。
头脑导图


未完。。。请继承查看后序条记更新。头脑导图在本章末给出。
1和f2中安全等级最低的谁人。
为了让代码具有更好的异常安全性,首先是”用对象来管理资源“,以避免资源的泄漏。其次,在异常安全性等级上,应该尽可能地往更高的等级上来限定。通过 copy-and-swap 方法往往可以实现”强烈包管“。但是我们也应该知道,”强烈包管“并不是对所有的情况都可实现,这取决于你在实现中用到的函数。函数提供的异常安全性的最高等级只能是你实现中调用的各个函数中异常安全性等级最低的谁人。
头脑导图

[外链图片转存中…(img-ISvygwbY-1669551236218)]
未完。。。请继承查看后序条记更新。头脑导图在本章末给出。
须要头脑导图或其他资料的小伙伴关注加私聊

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

徐锦洪

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表