[c++基础] 类的四个基本函数

来源:互联网 发布:酷派5263更改网络 编辑:程序博客网 时间:2024/05/20 17:07

1.面向对象的基本概念

所谓面向过程的编程思想,就是分析解决问题的步骤,将这些步骤用一个个函数实现,最后一个个调用。

所谓面向对象的编程思想,就是将任何事物都看成一个对象,对象有各种属性(attribute)和行为(behavior),在解决问题的过程中,将系统分解,使之模块化。

优势:可维护性,可扩展性,可复用性

面向对象的程序设计有四个主要特点:抽象、封装、继承、多态

        抽象(abstract):就是将不同对象的共性归纳、集中。在C++中,类是对象的抽象,对象是类的实例(instance)。

        封装(encapsulation):就是将对象的内部实现和外部行为分隔开来,人们通过接口来进行外部控制,而不用关心内部细节。

        继承(inheritance):在一个已存在的类的基础上建立一个新的类,新的类具有它所继承的类的全部特性,且可以增加一些新的特性。继承可以说是面向对象的程序设计最重要的特点。它实现了软件的可重用性(reuseability)。

        多态(polymorphism):接口的多种不同的实现方式即为多态当向不同的对象发送同一消息时,不同的对象在接收到消息后会产生不同的行为。即,每个对象可以用自己的方法去响应共同的消息。

面向对象和基于对象的区别: “面向对象”和“基于对象”都实现了“封装”的概念,但是面向对象实现了“继承和多态”,而“基于对象”没有实现这些。


对于一个C++的空类,编译器默认产生4个成员函数:默认构造函数,析构函数,复制构造函数,赋值函数。


2.类和结构体

区别:struct中也可以有构造函数、析构函数、之间也可以有继承。但struct中默认的访问控制是public,而class中的默认访问控制是private


3.成员变量

  • 静态成员函数可以在一个类的所有实例间共享数据。
  • 如果想限制对静态成员变量的访问,则必须把它们声明为保护型或私有型,然后通过共有静态成员函数访问。
  • 设定了静态成员变量,要给静态成员变量赋初值。
               


  • 构造函数的初始化列表是根据数据成员的声明顺序进行初始化的,和初始化列表的顺序无关。

   

      

解析:本题想要得到的结果是“98,98”。但是成员变量的声明是先m_i,然后是m_j;初始化列表的初始化变量顺序是根据成员变量的声明顺序来执行的,因此m_i会被赋予一个随机值。更改一下成员变量的声明顺序可以得到预想的结果。


  • 常量必须在构造函的初始化列表里面初始化或者设置成static
  

4.构造函数和析构函数


我们可以先构造一个类如下:

class Base{public:~CBase() {}}; class CChild : public Base{public:~CChild() {}}; int main() {CBase * pBase; CChild c; pBase = &c; }

解析:pBase指针被撤销时,调用的是CBase的析构函数还是CChild的呢?显然是CBase的(静态联编)析构函数。

但如果把CBase类的析构函数改成virtual型,当pBase指针被撤销时,就会先调用CChild类的析构函数,再调用CBase类的析构函数。如果CChild类的构造函数在堆中分配了内存,而其析构函数又不是virtual型的,那么撤销pBase时,将不会调用CChild::~CChild(),从而不会释放CChild::CChild()占据的内存,造成内存泄露。

  • 将CObject的析构函数设为virtual型,这保证了在任何情况下,不会出现由于析构函数未被调用而导致的内存泄露。
  • 构造函数不能为虚。因为虚函数采用一种虚调用的方法,虚调用是一种允许我们调用一个只知道接口而不知道其准确对象类型的函数。但是如果要创建一个对象,你势必要知道对象的准确类型。
  • 每个虚函数的对象都必须维护一个虚表,因此在使用虚函数的时候都会产生一个系统开销。
  • 析构函数可以是内联函数。
  • 单个参数的构造函数如果不添加explicit关键字,会定义一个隐含的类型转换(从参数的类型转换到自己的类型);添加explicit关键字会消除这种隐含转换。
#include <iostream>#include <string>#include <vector>using namespace std; class B{private:int data; public:B(){cout << "default constructor" << endl; }~B(){cout << "destructed" << endl; }B(int i) : data(i){cout << "constructed by parameter " << data << endl;}}; B Play(B b){return b; }int main(int argc, char *argv[]) {B temp = Play(5); return 0; }

输出结果为:

constructed by parameter 5 // 在Play(5)处,5通过隐含的类型转换调用了B::B(int i)
destructed // Play(5)返回时,参数的析构函数被调用
destructed // temp的析构函数调用;temp的构造函数调用的是编译器生成的拷贝构造函数


输出:0 1 2


输出:~ B ~A ~A ~A

传送门


为多态基类声明virtual析构函数:

当基类的指针指向派生类的对象的时候,当我们使用完,对其调用delete的时候,其结果将是未有定义——基类成分通常会被销毁,而派生类的充分可能还留在堆里。这可是形成资源泄漏.

消除以上问题的做法很简单:给基类一个virtual析构函数。此后删除派生类对象就会如你想要的那般。
任何类只要带有virtual函数都几乎确定应该也有一个virtual析构函数。
如果一个类不含virtual函数,通常表示它并不意图被用做一个基类,当类不企图被当做基类的时候,令其析构函数为virtual往往是个馊主意。因为实现virtual函数,需要额外的开销(指向虚函数表的指针vptr)。


决不让构造和析构过程中调用virtual函数:

因为:基类的构造函数的执行要早于派生类的构造函数,当基类的构造函数执行时,派生类的成员变量尚未初始化。派生类的成员变量没初始化,即为指向虚函数表的指针vptr没被初始化又怎么去调用派生类的virtual函数呢?析构函数也相同,派生类先于基类被析构,又如何去找派生类相应的虚函数?


5.复制构造函数和赋值函数

一般情况下,对于任意一个类A,如果程序员不显示的声明和定义上述函数,C++编译器将会自动的为A产生4个public inline 的默认函数,这4个函数最常见的形式为:
A() //默认构造函数
A(const A&) //默认拷贝构造函数
~A() //默认析构函数
A& operator = (const A &) //默认赋值函数。

关于拷贝构造函数,请看 传送门


拷贝构造函数和赋值函数的区别

拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。

String a("A");

String b("B");

String c = a; // 调用了拷贝构造函数,最好写成c(a);

c = b; // 调用了赋值函数



编写类String的构造函数、析构函数和赋值函数。

构造函数:

 

析构函数:

 

复制构造函数:

 

赋值函数:

  


6. 赋值和初始化区别

C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。所以应将成员变量的初始化置于构造函数的初始化列表中。
     
ABEntry::ABEntry(conststd::string& name, const std::string& address,const std::list<PhoneNumber>& phones) { 
           theName = name;               //这些都是赋值,而非初始化
           theAddress = address;          //这些成员变量在进入函数体之前已调用默认构造函数,接着又调用赋值函数
           thePhones = phones;           //即要经过两次的函数调用。            
           numTimesConsulted = 0;
    } 

    ABEntry::ABEntry(conststd::string& name, const std::string& address,const std::list<PhoneNumber>& phones) 

        : theName(name),                  //这些才是初始化 
       theAddress(address),               //这些成员变量只用相应的值进行拷贝构造函数,所以通常效率更高
       thePhones(phones),
       numTimesConsulted(0)
        {    } 


7.多态的概念

  • 多态的本质就是将子类类型的指针赋给父类类型的指针。”一个接口,多种方法“允许你将父对象设置成一个或更多的它的子对象相等的技术,赋值后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
  • 多态性在c++中通过虚函数实现
  • 虚函数就是允许被其子类重新定义的成员函数
  • 覆盖(override),是指子类重新定义父类的虚函数的做法。而重载(overload),是指允许存在多个同名函数,而这些函数的参数表不同。重载与多态无关,与面向对象无关。真正与多态相关的是“覆盖”。
  • 封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了代码重用。而多态则是为了实现另一个目的——接口重用
  • 宏,内联函数,模板都可以在编译时解析,唯独虚函数不行,它必须在运行时才能确定

8. 友元

  • 友元是一种定义在类外部的普通函数,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend.
  • 友元不是成员函数,但是可以访问类中的私有成员




From《程序员面试宝典》



0 0
原创粉丝点击