C++面试总结(二)类

1.struct和class的区别

C++中的struct对C中的struct进行了扩充,它已经不再是一个包含不同数据类型的数据结构了。

struct与class 的区别是默认的访问控制。

默认的继承访问权限,struct是public的,class是private的。                                                                                                                 此外,struct和class的另一个较大的区别是"class"关键字还用于定义模板参数,就像"typename"。但关键字"struct"不用于定义模板参数。

2.c++三大特性是什么,谈谈你的理解?

(1)封装:突破了C语言的函数概念,封装可以隐藏实现细节,使得代码模块化。

(2)继承:继承可以扩展已存在的代码模块;实现代码重用的目的。

(3)多态:接口重用,多态可以使用未来,即当前的框架不需要改变也可以使用后来的代码。

3.c++中三种继承的方法:public继承/private继承/protected继承的区别?

public:可以被任意实体访问

protected:只允许子类及本类的成员函数访问

privated:只允许本类的成员函数访问 

继承后的组合结果:

1.public继承不改变基类成员的访问权限。 

2.private继承使得基类所有成员在子类中的访问权限变为private。

3.protected继承将基类中public成员变为子类的protected,其他成员的访问权限不变。 

4.基类中的private成员不受继承方式的影响,子类永远无权访问。 

4.C++的空类含有哪些成员函数?                                                                                                                                                     (1)缺省构造函数 (2)缺省拷贝构造函数 (3)缺省的析构函数 (4)缺省的赋值运算符 (5)缺省的取址运算符 (6)缺省的取址运算符const

class Empty
{
    public:         
          Empty();
          Empty(const Empty&);
          ~Empty();
          Empty& operator=(const Empty& );
          Empty* operator&();//取址运算符
          const Empty* operator&() const;
}

5.谈谈你对拷贝构造函数和赋值操作符的认识?

拷贝构造函数和赋值运算符重载有以下两个不同之处:

(1)拷贝构造函数生成新的类对象,而赋值运算不能

(2)由于拷贝构造函数是直接构造一个新的类对象,所以在初始化这个对象之前不用检查源对象是否与新对象相同,而赋值运算符则需要这个操作,另外赋值运算符中如果原来的对象中内存分配要先把内存注释掉。

注意:当类中有指针类型的成员变量时,一定要重写拷贝构造函数和赋值操作符。

6.构造函数在哪几种情况下被调用?

(1)当类的对象去初始化该类的另一个对象时;

(2)如果函数的形参是类的对象,调用函数进行形参和实参结合时;

(3)如果函数的返回值是类的对象,函数调用完成返回时;

7.什么时候必须重写拷贝构造函数?

如果默认拷贝构造函数可以解决问题,则不需要重写拷贝构造函数。如果对象没有指针成员,或运行时分配的资源,则使用默认拷贝构造函数。

下面是一些关于构造函数 的一些通用规则:

(1)如果默认拷贝构造函数可以处理,则不需要自定义拷贝构造函数;

(2)如果需要自定义拷贝构造,则同样需要自定义析构函数和赋值操作符;

(3)拷贝构造函数必须为const的引用;

8.类成员初始化列表和构造函数体的区别?什么时候必须使用类初始化列表?

成员初始化列表是在数据成员定义的同时赋初值,而构造函数的函数体是采用先定义后赋值的方式来做。

必须采用初始化列表的情况:

(1)需要初始化的成员是对象的情况

(2)需要初始化const修饰的类成员或初始化初始化引用的成员数据;

(3)子类初始化父类的私有成员

9.为什么拷贝构造函数的类对象的形参必须是引用类型? 

若不是引用类型:为了调用拷贝构造函数,必须拷贝它的实参,为了拷贝它的实参,我们又需要调用拷贝构造函数,如此无限循环。则我们的调用永远不会成功。

10.C++的多态性? 
多态性可以简单地概括为“一个接口,多种方法”。 
分为编译时多态性和运行时多态性。 
编译时多态性:通过重载函数实现 。重载允许有多个同名的函数,而这些函数的参数列表不同。编译器会根据实参类型来选定相应的函数。 静态多态:函数重载和泛型编程
运行时多态性:通过虚函数实现。虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者重写。动态多态:虚函数

11.多态的实现原理?

(1)当类中存在虚函数时,编译器会在类中自动生成一个虚函数表
(2)虚函数表是一个存储类成员函数指针的数据结构
(3)虚函数表由编译器自动生成和维护
(4)virtual 修饰的成员函数会被编译器放入虚函数表中
(5)存在虚函数时,编译器会为对象自动生成一个指向虚函数表的指针(通常称之为 vptr 指针)

编译器发现一个类中有虚函数, 便会立即为此类生成虚函数表 vtable。 虚函数表的各表项为指向对应虚函数的指针。编译器还会在此类中隐含插入一个指针 vptr(对 vc 编译器来说,它插在类的第一个位置上) 指向虚函数表。 调用此类的构造函数时,在类的构造函数中,编译器会隐含执行 vptr 与 vtable 的关联代码,将 vptr 指向对应的 vtable, 将类与此类的 vtable 联系了起来。 另外在调用类的构造函数时,指向基础类的指针此时已经变成指向具体的类的 this 指针,这样依靠此 this 指针即可得到正确的 vtable。如此才能真正与函数体进行连接,这就是动态联编,实现多态的基本原理。
 

12.类成员函数的重载、覆盖(重写)和隐藏(重定义)区别? 
a.成员函数被重载的特征: 
(1)相同的范围(在同一个类中); 
(2)函数名字相同; 
(3)参数不同; 
(4)virtual 关键字可有可无。 
b.覆盖是指派生类函数覆盖基类函数,特征是: 
(1)不同的范围(分别位于派生类与基类); 
(2)函数名字相同; 
(3)参数相同; 
(4)基类函数必须有virtual 关键字。 
c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下: 
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。 
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)

13.虚函数的定义及功能? 
定义:在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的非static成员函数。 
功能:实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。

14.动态绑定是如何实现的? 
当基类的指针或者引用指向一个派生类的对象时,在调用相应的虚函数时,会发生动态绑定。直到运行时才能够确定调用的是哪个版本的虚函数,判断的依据是引用或指针所绑定的对象的真实类型。

15.虚函数,虚函数表里面内存如何分配?                                                                                                                                         编译时若基类中有虚函数,编译器为该的类创建一个一维数组的虚表,存放是每个虚函数的地址。基类和派生类都包含虚函数时,这两个类都建立一个虚表。构造函数中进行虚表的创建和虚表指针的初始化。在构造子类对象时,要先调用父类的构造函数,初始化父类对象的虚表指针,该虚表指针指向父类的虚表。执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。每一个类都有虚表。虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。当用一个指针/引用调用一个函数的时候,被调用的函数是取决于这个指针/引用的类型。即如果这个指针/引用是基类对象的指针/引用就调用基类的方法;如果指针/引用是派生类对象的指针/引用就调用派生类的方法,当然如果派生类中没有此方法,就会向上到基类里面去寻找相应的方法。这些调用在编译阶段就确定了。当涉及到多态性的时候,采用了虚函数和动态绑定,此时的调用就不会在编译时候确定而是在运行时确定。不在单独考虑指针/引用的类型而是看指针/引用的对象的类型来判断函数的调用,根据对象中虚指针指向的虚表中的函数的地址来确定调用哪个函数。

16.虚函数内存分配? 
C++的编译器保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到的虚函数表有最高的性能——如果有多层继承或是多重继承的情况下)。 
分为以下几种情况: 
对于无虚函数覆盖的继承:虚函数按照其声明顺序放在虚函数表中;父类的虚函数在其子类的虚函数的前面。 
对于有虚函数覆盖的继承:派生类中起覆盖作用的虚函数放在原基类虚函数的位置;没有被覆盖的虚函数依旧。 
对于无虚函数覆盖的多重继承:每个父类都有自己的虚函数表;派生类的虚函数被放在了第一个父类的虚函数表中(按照声明顺序排序)。 
对于有虚函数覆盖的多重继承:派生类中起覆盖作用的虚函数放在原基类虚函数的位置;没有被覆盖的虚函数依旧。

17.抽象类的特点? 
含有纯虚拟函数的类称为抽象类,它不能生成对象。 纯虚函数是虚函数再加上=0,如virtual void fun() = 0。
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。如动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。 
抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。

 

18. C++中哪些不能是虚函数?

1)普通函数只能重载,不能被重写,因此编译器会在编译时绑定函数。
2)构造函数是知道全部信息才能创建对象,然而虚函数允许只知道部分信息。
3)内联函数在编译时被展开,虚函数在运行时才能动态绑定函数。
4)友元函数 因为不可以被继承。
5)静态成员函数 只有一个实体,不能被继承。父类和子类共有

19.实现自己的string类

  mystring.h

#ifndef MYSTRING_H
#define MYSTRING_H

#include <iostream>
using namespace std;

class Mystring
{
    friend ostream& operator<<(ostream& out, Mystring& s);
    friend istream& operator>>(istream& in, Mystring& s);
public:
    //构造函数
    Mystring(int len);
    Mystring(char* p);
    //拷贝构造函数
    Mystring(const Mystring& s1);
    //析构函数
    ~Mystring();
public:
    //重载= s1=s2 s1 = "aabbcc"
    Mystring &operator=(Mystrig& s1);
    Mystring &operator=(char* p);
    //重载[]
    char& operator(int index);
public: 
    bool operator==(const Mystring &s) const;
    bool operator==(const char *p) const;
    bool operator!=(const Mystring &s) const;
    bool operator!=(const char *p) const;
public:
    int operator>(const char *p) const;
    int operator>(const Mystring &s1) const;
    int operator<(const char *p) const;
    int operator<(const Mystring &s1) const;
public:
    char *c_str()
    {
        return m_space;
    }

    int length()
    {
        return m_len;
    }

private:
    int m_len;
    char *m_space;
}
#endif

mystring.cpp

#include "Mystring.h"
#include <string>

ostream& operator<<(ostream &out, Mystring& s)
{
    out<<s.m_space;
    return out;
}
istream& operator>>(ostream &cin, Mystring& s)
{
    cin>>s.m_space;
    return cin;
}

//Mystring(10)
Mystring::Mystring(int len)
{
    if(len < 0)
    {
        m_len = 0;
        m_space = new char[m_len+1];
        strcpy(m_space, "");
    }
    else
    {
        m_lenn = len;
        m_space = new char[m_len+1];
        memset(m_space, 0, m_len);
    }
}

////Mystring s1 = "123"
Mystring::Mystring(char* p)
{
    if(p == NULL)
    {
        m_len = 0;
        m_space = new char[m_len+1];
        strcpy(m_space, "");
    }
    else
    {
        m_len = strlen(p);
        m_space = new char[m_len+1];
        strcpy(m_space, p);
        
    }
}

//拷贝构造函数  Mystring s1 = s2
Mystring::Mystring(const Mystring& s1)
{
    m_len = s1.m_len;
    //开辟自己的新内存
    m_space = new char[m_len+1];
    strcpy(m_space, s1.m_space);
}

Mystring::~Mystring()
{
	if (m_space != NULL)
	{
		delete[] m_space;
		m_space = NULL;
		m_len = 0;
	}
}

//重载赋值操作符
Mystring & Mystring::operator=(Mystring& s1)
{
    //1.释放掉原内存
    if(m_space != NULL)
    {
        m_len = 0;
        delete [] m_space;
    }
    //2.分配新的内存空间
    m_len = s1.m_len;
    m_space = new char[m_len+1];
    //3.拷贝新的内容到新的空间中
    strcpy(m_space, s1.m_space);

    return *this;
}

Mystring& Mystring::operator=(char* p)
{
	if (p == NULL)
	{
		m_len = 0;
		delete[] m_space;
		strcpy(m_space, "");
	}
	else
	{
		m_len = strlen(p);
		m_space = new char[m_len + 1];
		strcpy(m_space, p);
	}

	return *this;
}

//重载[]
char& Mystring::operator[](int index)
{
	return m_space[index];
}

//==
bool Mystring::operator==(const char *p) const
{
	if (p == NULL)
	{
		if (m_len == 0)
			return true;
		else
			return false;
	}
	else
	{
		if (m_len == strlen(p))
			return !strcmp(m_space, p);
		else
			return false;
	}

}

bool Mystring::operator==(const Mystring &s) const
{
	if (m_len == s.m_len)
		return !strcmp(m_space, s.m_space);
	else
		return false;
}

//!=,调用已经重载的==
bool Mystring::operator!=(const char *p) const
{
	return !(*this == p);
}

bool Mystring::operator!=(const Mystring &s) const
{
	return !(*this == s);
}

// > <
int Mystring::operator>(const char *p) const
{
	return strcmp(m_space, p);
}
int Mystring::operator>(const Mystring &s1) const
{
	return strcmp(m_space, s1.m_space);
}

int Mystring::operator<(const char *p) const
{
	return strcmp(m_space, p);
}

int Mystring::operator<(const Mystring &s1) const
{
	return strcmp(m_space, s1.m_space);
}

猜你喜欢

转载自blog.csdn.net/qq_36086861/article/details/84667919
今日推荐