C++面向对象知识总结

借我怦然心动如往昔,借我安适的清晨与傍晚。

面向对象三大特性?

1.封装性:数据和代码捆绑在一起,避免外界干扰和不确定性访问。
2.继承性:让某种类型对象获得另一个类型对象的属性和方法。
3.多态性:同一事物表现出不同事物的能力,即向不同对象发送同一消息,不同的对象在接收时会产生不同的行为(重载实现编译时多态,虚函数实现运行时多态)

public/protected/private的区别?

public的变量和函数在类的内部外部都可以访问。
protected的变量和函数只能在类的内部和其派生类中访问。
private修饰的元素只能在类内访问。

对象存储空间?

1.非静态成员的数据类型大小之和。
2.编译器加入的额外成员变量(如指向虚函数的指针)
3.为了边缘对齐优化加入的padding

C++空类有哪些成员函数

1.首先,空类对象大小为1字节。
2.默认函数有

  • 构造函数 A();
  • 析构函数 ~A(void);
  • 拷贝构造函数 A(const A &a);
  • 赋值运算符 A& operate =(const A &a);

构造函数能否为虚函数,析构函数呢?

析构函数:

1.析构函数可以为虚函数,并且一般情况下基类析构函数要定义为虚函数。
2.只有在基类析构函数定义为虚函数时,调用操作符delete销毁指向对象的基类指针时,才能准确调用派生类的析构函数(从该级向上按序调用虚函数),才能准确销毁数据。
3.析构函数可以是纯虚函数,含有纯虚函数的类是抽象类,此时不能被实例化。但派生类中可以根据自身需求重新改写基类中的纯虚函数。

构造函数:

1.构造函数不能定义为虚函数。虚函数对应一个虚函数表(vtable),可是这个vtable其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。

构造函数和析构函数能否调用虚函数?

1.从语法上讲,调用完全没有问题。但是从效果上看,往往不能达到需要的目的。
2.假设一个基类A的构造函数中调用了一个虚函数。派生类B继承自A 。当用构造函数创建一个B类对象时,先调用基类A的构造函数,而此时编译器认为正在创建的对象的类型是A,所以虚函数是A类的虚函数。析构时同理,派生类成员先被析构了,当进入基类的析构函数时,就认为对象是基类对象调用基类的虚函数。

构造函数调用顺序,析构函数呢?

1.首先,基类的构造函数:如果有多个基类,先调用纵向上最上层基类构造函数,如果横向继承了多个类,调用顺序为派生表从左到右顺序。
2.其次,成员类对象的构造函数:如果类的变量中包含其他类(类的组合),需要在调用本类构造函数前先调用成员类对象的构造函数,调用顺序遵照在类中被声明的顺序。
3.最后,派生类的构造函数。

4.析构函数与之相反。

拷贝构造函数中深拷贝和浅拷贝区别?

1.深拷贝时,当被拷贝对象存在动态分配的存储空间时,需要先动态申请一块存储空间,然后逐字节拷贝内容。
2.浅拷贝仅仅是拷贝指针字面值。
当使用浅拷贝时,如果原来的对象调用析构函数释放掉指针所指向的数据,则会产生空悬指针。因为所指向的内存空间已经被释放了。

拷贝构造函数和赋值运算符重载的区别?

1.拷贝构造函数是函数,赋值运算符是运算符重载。

2.拷贝构造函数会生成新的类对象,赋值运算符不能。

3.拷贝构造函数是直接构造一个新的类对象,所以在初始化对象前不需要检查源对象和新建对象是否相同;赋值运算符需要上述操作并提供两套不同的复制策略,另外赋值运算符中如果被赋值对象有内存分配则需要先把内存释放掉。

4.形参传递是调用拷贝构造函数(调用的被赋值对象的拷贝构造函数),但并不是所有出现”=”的地方都是使用赋值运算符,如下:

Student s;
Student s1 = 2;    // 调用拷贝构造函数
Student s2;
s2 = s;    // 赋值运算符操作(没有新对象产生)

5.类中有指针变量指向动态分配的内存资源时,要重写析构函数、拷贝构造函数和赋值运算符

拷贝构造函数在什么时候会被调用?

假设Person是一个类。
Person p(q) //使用拷贝构造函数来创建实例p;
Person p = q; //使用拷贝构造函数来定义实例p时初始化p
f(p) //p参数进行值传递时,会调用复制构造函数创建一个局部对象

虚函数和纯虚函数区别?

1.虚函数是为了实现动态编联产生的,目的是通过基类类型的指针指向不同对象时,自动调用相应的、和基类同名的函数(使用同一种调用形式,既能调用派生类又能调用基类的同名函数)

2.虚函数需要在基类中加上virtual修饰符修饰,因为virtual会被隐式继承,所以子类中相同函数都是虚函数。当一个成员函数被声明为虚函数之后,其派生类中同名函数自动成为虚函数,在派生类中重新定义此函数时要求函数名、返回值类型、参数个数和类型全部与基类函数相同。
3.纯虚函数只是相当于一个接口名,含有纯虚函数的类不能够实例化。

虚函数机制带来的开销有哪些?

主要是虚表的存储开销、函数通过指针使用带来的时间开销。

覆盖、隐藏和重载的区别?

重写和重载主要有以下几点不同

1.范围的区别:被重写的和重写的函数在两个类中,而重载和被重载的函数在同一个类中。
2.参数的区别:被重写函数和重写函数的参数列表一定相同,而被重载函数和重载函数的参数列表一定不同。
3.virtual 的区别:重写的基类中被重写的函数必须要有 virtual 修饰,而重载函数和被重载函数可以被virtual 修饰,也可以没有。

隐藏和重写、重载有以下几点不同

1.与重载的范围不同:和重写一样,隐藏函数和被隐藏函数不在同一个类中
2.参数的区别:隐藏函数和被隐藏的函数的参数列表可以相同,也可不同,但是函数名肯定要相同。当参数不相同时,无论基类中的参数是否被 virtual 修饰,基类的函数都是被隐藏,而不是被重写

说明:虽然重载和覆盖都是实现多态的基础,但是两者实现的技术完全不相同,达到的目的也是完全不同的,覆盖是动态绑定的多态,而重载是静态绑定的多态。

继承方式

public   父类的访问级别不变
protected 父类的public成员在派生类编程protected,其余的不变
private 父类的所有成员变成private

#include <iostream>
using namespace std;
class base
{
  public:
    void printa()
    { cout <<"base"<< endl; }
  protected:
    void printhello()
    { cout <<"helo"<< endl; }
  private:
    void printnohello()
    { cout <<"no hello"<< endl; }
};
class derived : public base
{
  public:
    void printb() { printhello(); }
    // void printc() { printnohello(); } //printnohello是父类的私有函数,不可访问
};
int main()
{
  base a;
  a.printa();
  derived b;
  b.printb();
  //a.printhello(); //printhello是类base的protected函数,不可访问。
}

简述多态的实现原理

1.编译器发现一个类中有虚函数,便会立即为此类生成虚函数表vtable。虚函数表的各表项为指向对应虚函数的指针。编译器还会在此类中隐含插入一个指针 vptr指向虚函数表。调用此类的构造函数时,在类的构造函数中,编译器会隐含执行 vptr 与 vtable 的关联代码,将 vptr 指向对应的 vtable,将类与此类的 vtable 联系了起来。另外在调用类的构造函数时,指向基础类的指针此时已经变成指向具体的类的 this 指针,这样依靠此 this 指针即可得到正确的 vtable。

2.如此才能真正与函数体进行连接,这就是动态联编,实现多态的基本原理。

c++空类的成员函数

缺省的构造函数
缺省的拷贝构造函数
缺省的赋值运算符
缺省的析构函数
缺省的取址运算符
缺省的取址运算符const

注意:只有当实际使用这些函数的时候,编译器才会去定义它们。

在main执行之前执行的代码可能是什么?

全局对象的构造函数。

哪几种情况必须用到初始化成员列表?

1.类中有const成员。
2.类中有reference成员。
3.调用一个基类的构造函数,而该函数有一组参数。
4.调用一个数据成员对象的构造函数,而该函数有一组参数。

什么是虚指针?

1.虚指针或虚函数指针是虚函数的实现细节。
2.虚指针指向虚表结构。

重载和函数模板的区别?

1.重载需要多个函数,这些函数彼此之间函数名相同,但参数列表中参数数量和类型不同。在区分各个重载函数时我们并不关心函数体。
2.模板函数是一个通用函数,函数的类型和形参不直接指定而用虚拟类型来代表。但只适用于参数个数相同类型不同的函数。

this指针是什么?

1.this指针是类的指针,指向对象的首地址。
2.this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给this。所以this指针只能在成员函数中使用。在静态成员函数中不能用this。
3.this指针只有在成员函数中才有定义,且存储位置会因编译器不同有不同存储位置。

类模板是什么?

1.用于解决多个功能相同、数据类型不同的类需要重复定义的问题。
2.在建立类时候使用template及任意类型标识符T,之后在建立类对象时,会指定实际的类型,这样才会是一个实际的对象。
3.类模板是对一批仅数据成员类型不同的类的抽象,只要为这一批类创建一个类模板,即给出一套程序代码,就可以用来生成具体的类。

构造函数和析构函数调用时机?

1.全局范围中的对象:构造函数在所有函数调用之前执行,在主函数执行完调用析构函数。
2.局部自动对象:建立对象时调用构造函数,函数结束时调用析构函数。
3.动态分配的对象:建立对象时调用构造函数,调用释放时调用析构函数。
4.静态局部变量对象:建立时调用一次构造函数,主函数结束时调用析构函数。

delete this

1.类的成员函数中能不能调用delete this?
可以。假设一个成员函数release,调用了delete this。那么这个对象在调用release方法后,还可以进行其他操作,比如调用其他方法。前提是:被调用的方法不涉及这个对象的数据成员和虚函数,否则会出现不可预期的问题。

2.为什么是不可预期的问题?
这涉及到操作系统的内存管理策略。delete this释放了类对象的内存空间,但是内存空间却并不是马上被回收到系统中,可能是缓冲或者其他什么原因,导致这段内存空间暂时并没有被系统收回。但是其中的值是不确定的。

3.类的析构函数中调用delete this,会发生什么?
导致栈溢出。delete的本质是为将被释放的内存调用一个或多个析构函数,然后,释放内存。显然,delete this会去调用本对象的析构函数,而析构函数中又调用delete this,形成无限递归,造成堆栈溢出,系统崩溃。

当把一个派生类对象指针赋值给其基类指针时会发生什么样的行为

当使用基类的指针指向一个派生类的对象时,编译器会安插相应的代码,调整指针的指向,使基类的指针指向派生类对象中其对应的基类子对象的起始处。

在类的构造函数里面直接使用 memset(this,0,sizeof(*this)) 来初始化整个类里会发生什么?

将所有非静态成员变量置0。当有虚函数的时候,虚函数表指针vptr会被置成空。

多继承有什么问题?

1.多继承比单继承复杂,引入了歧义的问题( 如果基类的成员函数名称相同,匹配度相同, 则会造成歧义)
2.菱形的多继承,导致虚继承的必要性;但虚继承在大小、速度、初始化/赋值的复杂性上有不小的代价,当虚基类中没有数据时还是比较合适的。

析构函数能抛出异常吗?

1.不能,也不应该抛出。
2.如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
3.通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。

为什么内联函数,构造函数,静态成员函数不能为virtual函数

1.内联函数
内联函数是在编译时期展开,而虚函数的特性是运行时才动态联编,所以两者矛盾,不能定义内联函数为虚函数。

2.构造函数
构造函数用来创建一个新的对象,而虚函数的运行是建立在对象的基础上,在构造函数执行时,对象尚未形成,所以不能将构造函数定义为虚函数

3.静态成员函数
静态成员函数属于一个类而非某一对象,没有this指针,它无法进行对象的判别。

4.友元函数
C++不支持友元函数的继承,对于没有继承性的函数没有虚函数

如何定义一个只能在堆上生成对象的类

1.只能在堆上,析构函数设为protected。
编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。

2.类中必须提供一个destroy函数,调用delete this,来进行内存空间的释放。类对象使用完成后,必须调用destroy函数。

3.用new建立对象,destroy销毁对象很奇怪。可以用一个静态成员函数将new封装起来。同时将构造函数设为protected。

class A{
protected:
    A(){};
    ~A(){};
public:
    static A* create(){
        return new A();
    }
    void destroy(){
        delete this;
    }
};

如何定义一个只能在栈上生成对象的类?

只有使用new运算符,对象才会建立在堆上,因此,只要禁用new运算符就可以实现类对象只能建立在栈上。将operator new()设为私有即可。

class A{
public:
    A(){};
    ~A(){};
private:
     void * operator  new ( size_t  t){}      // 注意函数的第一个参数和返回值都是固定的
      void  operator  delete ( void * ptr){}  // 重载了new就需要重载delete
};

类的静态成员

class A
{
  private:
    static int s_value;
};

1.在cpp中必须对它进行初始化:int A::s_value = 0; // 注意,这里没有static的修饰!

2.类的静态成员是该类所有实例的共用成员,也就是在该类的范畴内是个全局变量,也可以理解为是一个名为A::s_value的全局变量,只不过它是带有类安全属性的;道理很简单,因为它是在程序初始化的时候分配的,所以只分配一次,所以就是共用的;

3.类中只是声明,在cpp中才是初始化,可以在初始化的代码上放个断点,在程序执行main的第一条语句之前就会先走到那;如果你的静态成员是个类,那么就会调用到它的构造函数;

类的静态函数

class A
{
  private:
  static void func(int value);
};

1.实现的时候也不需要static的修饰,因为static是声明性关键字;
2.类的静态函数是在该类的范畴内的全局函数,不能访问类的私有成员,只能访问类的静态成员,不需要类的实例即可调用;实际上,它就是增加了类的访问权限的全局函数:void A::fun(int);
3.静态成员函数可以继承和覆盖,但无法是虚函数;

posted @ 2021-12-12 17:24  煊奕  阅读(255)  评论(0编辑  收藏  举报