C++笔记(面试总结二)

C++笔记(面试总结二)

1.编写类String的构造函数、拷贝构造函数、析构函数和赋值函数

class String

{
public:
    String(const char *str=NULL);// 普通构造函数 
    String (const String &other);// 拷贝构造函数 
    ~String(void);//析构函数 
    String & operator = (const String &other);// 赋值函数 
private:
    char *m_data; // 用于保存字符串 
};

//编写类String的构造函数、析构函数和赋值函数,已知类String的原型为:
class String
{
public:
    String(const char *str=NULL);// 普通构造函数
    String (const String &other);// 拷贝构造函数
    ~String(void);//析构函数
    String & operator = (const String &other);// 赋值函数
private:
    char *m_data; // 用于保存字符串
};

//1、构造函数
/*

1、构造函数在构造对象时使用;
  2、传入参数的判断;
  3、对象的初始化问题。
*/

String::String(const char *str)
{
    cout << "构造" <<endl;
    if(str == NULL)
    {
        m_data = new char[1];
        *m_data = '\0';
    }
    else
    {
        int len =strlen(str);
        m_data = new char[len+1];
        strcpy(m_data,str);
    }
}

//2、拷贝构造函数
/*
  1、拷贝构造函数必须在构造对象时使用,即定义对象时;
  2、对象初始化问题。
*/

String::String(const String &other)
{
    cout << "拷贝构造" <<endl;
    int len = strlen(other.m_data);
    m_data=new char[len+1];
    strcpy(m_data,other.m_data);
}

//3、赋值函数
/*
   1、赋值函数使用时,对象肯定已经建立;
  2、赋值前,判断是否是自我赋值;
  3、赋值前,内存空间的准备:
     由于赋值前,对象已占有一定大小内存,但是赋值对象所占内存大小与
    对象已占的内存大小不一定一致;
     先释放对象已占的内存,然后分配心内存。
   4、正常赋值
*/

String & String::operator=(const String &other )
{
    cout << "赋值" <<endl;
    if(&other==this)
        return *this;
    delete [] m_data ;
    int len =strlen(other.m_data);
    m_data = new char[len+1];
    strcpy(m_data,other.m_data);
    return *this;
}

//4、析构函数
/*
  资源的释放
*/

String ::~String(void)
{
    cout << "析构" <<endl;
    delete [] m_data;
}
int main()
{
    String a("hello");
    String b("world");
    String c(a);
    c=b;
    return 0;
}

2.多态的实现

C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。

1:用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。

2:存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。

3:多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性。

4:多态用虚函数来实现,结合动态绑定.

5: 纯虚函数是虚函数再加上 = 0;

6:抽象类是指包括至少一个纯虚函数的类。

7:两种多态:编译时多态,运行时多态
编译时多态:函数模板

template <typename T>
T add (T a,T b){
   T c;
   c=a+b;
   return c;
   }

注意:函数重载和多态无关
那么刚刚代码中提到了typename,那么它和class有什么区别呢?
首先,typename可以用来定义模板,就像上面那样,此时是可以与class替换的,他只不过是引入与class区分的关键字而已。
第二:就是typename可以使用为嵌套依赖类型

class MyArray      
   {      
   public:
       typedef   int   LengthType;
       .....
   }

   template<class T>
   void MyMethod( T myarr )  
   {          
       typedef typename T::LengthType LengthType;        
       LengthType length = myarr.GetLength;  
   }

这个时候typename的作用就是告诉c++编译器,typename后面的字符串为一个类型名称,而不是成员函数或者成员变量,这个时候如果前面没有typename,编译器没有任何办法知道T::LengthType是一个类型还是一个成员名称(静态数据成员或者静态函数),所以编译不能够通过。

纯虚函数:virtual void fun()=0;即抽象类!必须在子类实现这个函数,即先有名称,没有内容,在派生类实现内容。

从编译的角度来看:

c++编译器在编译的时候,要确定每个对象调用的函数(非虚函数)的地址,这称为早期绑定,当我们将Son类的对象son的地址赋给pFather时,c++编译器进行了类型转换,此时c++编译器认为变量pFather保存的就是Father对象的地址,当在main函数中执行pFather->Say(),调用的当然就是Father对象的Say函数。

那么如何定位虚表呢?编译器另外还为每个对象提供了一个虚表指针(即vptr),这个指针指向了对象所属类的虚表,在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向了所属类的虚表,从而在调用虚函数的时候,能够找到正确的函数,对于第二段代码程序,由于pFather实际指向的对象类型是Son,因此vptr指向的Son类的vtable,当调用pFather->Son()时,根据虚表中的函数地址找到的就是Son类的Say()函数.

正是由于每个对象调用的虚函数都是通过虚表指针来索引的,也就决定了虚表指针的正确初始化是非常重要的,换句话说,在虚表指针没有正确初始化之前,我们不能够去调用虚函数,那么虚表指针是在什么时候,或者什么地方初始化呢?

答案是在构造函数中进行虚表的创建和虚表指针的初始化,在构造子类对象时,要先调用父类的构造函数,此时编译器只“看到了”父类,并不知道后面是否还有继承者,它初始化父类对象的虚表指针,该虚表指针指向父类的虚表,当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。

一个空的虚函数,具体的函数在子类中分别定义,程序一般运行时,找到类,如果它有基类,再找到它的基类,最后运行的是基类中的函数,这时,它在基类中找到的是virtual标识的函数,它就会再回到子类中找同名函数,派生类也叫子类,基类也叫父类,这就是虚函数的产生,和类的多态性的体现。

**3. 头文件种的ifndef/define/endif **

  防止头文件被重复包含

猜你喜欢

转载自blog.csdn.net/weixin_42927264/article/details/88427602