重拾C++(2) 类与面向对象

375 阅读4分钟

2.1 class

类与结构体有所不同,类的所有方法与属性默认是私有的,而结构体中默认所有方法与属性都可以公共访问(在C语言中,结构体不能包含函数。在C++中,对结构体进行了扩展,C++的结构体可以包含函数,这样,C++的结构体也具有类的功能,与class不同的是,结构体包含的函数默认为public,而不是private。

class Foo {
    int attribute;
    int function( void ) { };
};

struct Bar {
    int attribute;
    int function( void ) { };
};

Foo foo;
foo.attribute = 1; // WRONG

Bar bar;
bar.attribute = 1;  // OK

2.2 构造函数

构造函数是一个名字与class名称相同的函数,可以为类指定零个、一个或多个构造函数。构造函数在class实例化时被调用,并且没有返回类型❗❗❗❗❗❗(不是void类型),可以设置多个构造函数,利用函数重载。

class Foo{
public:
    Foo(){
        std::cout<<"Foo1 called"<<std::endl;
    }
    Foo(int value){
        std::cout<<"Foo2 called"<<std::endl;
    }
}

2.3 析构函数

特点:

  1. 形式上:~类名(){...}
  2. 没有返回类型
  3. 每个class只能有一个
#include <iostream>

class Foo {
public:
    ~Foo( void )
    { std::cout << "Foo destructor called" << std::endl; }
}
int main( int argc, char **argv )
{
    Foo foo();
    return 0;
}

2.4 访问权限

  1. private:私有成员变量或函数不能直接访问,甚至不能从类外部查看只有在类中和friend函数可以访问私有成员,类以外通过get、set函数访问,修改。C++里默认对象成员是private属性。
#include <iostream>
 
using namespace std;
 
class Box {
   public:
      double length;
      void setWidth( double wid );
      double getWidth( void );
 
   private:
      double width;
};
 
// Member functions definitions
double Box::getWidth(void) {
   return width ;
}
 
void Box::setWidth( double wid ) {
   width = wid;
}
 
// Main function for the program
int main() {
   Box box;
 
   // set box length without member function
   box.length = 10.0; // OK: because length is public
   cout << "Length of box : " << box.length <<endl;
 
   // set box width without member function
   // box.width = 10.0; // Error: because width is private
   box.setWidth(10.0);  // Use member function to set it.
   cout << "Width of box : " << box.getWidth() <<endl;
 
   return 0;
}
  1. public:访问没有限制,类以外任何地方都可以访问
#include <iostream>
 
using namespace std;
 
class Line {
   public:
      double length;
      void setLength( double len );
      double getLength( void );
};
 
// Member functions definitions
double Line::getLength(void) {
   return length ;
}
 
void Line::setLength( double len) {
   length = len;
}
 
// Main function for the program
int main() {
   Line line;
 
   // set line length
   line.setLength(6.0); 
   cout << "Length of line : " << line.getLength() <<endl;
 
   // set line length without member function
   line.length = 10.0; // OK: because length is public
   cout << "Length of line : " << line.length <<endl;
   
   return 0;
}
  1. protected:受保护的成员变量或函数与私有成员非常相似,但是它可以在称为派生类的子类中访问它们。 protected成员可以在以下情况被访问:
  • 声明该成员的class内部
  • 声明该成员的class的派生类内部
  • 声明该成员的class的友元函数或友元类内部

例子:

// keyword_protected.cpp
#include <iostream>

using namespace std;
class X {
public:
   void setProtMemb( int i ) { m_protMemb = i; }
   void Display() { cout << m_protMemb << endl; }
protected:
   int  m_protMemb;
   void Protfunc() { cout << "\nAccess allowed\n"; }
} x;

class Y : public X {
public:
   void useProtfunc() { Protfunc(); }
} y;

int main() {
   // x.m_protMemb;         error, m_protMemb is protected
   x.setProtMemb( 0 );   // OK, uses public access function
   x.Display();
   y.setProtMemb( 5 );   // OK, uses public access function
   y.Display();
   // x.Protfunc();         error, Protfunc() is protected
   y.useProtfunc();      // OK, uses public access function
                        // in derived class
}

2.5 初始化列表

用途:用于初始化类的数据成员,当非静态const类型数据成员需要初始化时必须使用初始化列表 www.geeksforgeeks.org/when-do-we-…

语法:: member1(参数), member2(参数)冒号开始,逗号分隔

class CMyClass {
    CMyClass(int x, int y);
    int m_x;
    int m_y;
};

CMyClass::CMyClass(int x, int y) : m_y(y), m_x(m_y)
{
};

2.6 继承

什么是继承

提供一种从现有类创建新类的方法。

基类与派生类(又称父类与子类)

基类被派生类所继承

class Student{ //base class基类
 //body
}; 

class UnderGrad : public Student{  //derived class 派生类
  //body
};

子类的对象具有:

  • 子类中定义的所有成员。

  • 在父类中声明的所有成员。

子类的对象可以使用(公有继承的情况):

  • 子类中定义的所有公共成员。

  • 父类中定义的所有公共成员。

继承的语法

声明一个派生类:

class derived-class: access-specifier base-class

如果未使用访问说明符(access-specifier 即public private protected),则默认情况下为私有继承。

多重继承

一个c++类可以从多个类继承成员

多重继承语法实例:

#include <iostream>
 
using namespace std;

// Base class Shape
class Shape {
   public:
      void setWidth(int w) {
         width = w;
      }
      void setHeight(int h) {
         height = h;
      }
      
   protected:
      int width;
      int height;
};

// Base class PaintCost
class PaintCost {
   public:
      int getCost(int area) {
         return area * 70;
      }
};

// Derived class多重继承
class Rectangle: public Shape, public PaintCost {
   public:
      int getArea() {
         return (width * height); 
      }
};

int main(void) {
   Rectangle Rect;
   int area;
 
   Rect.setWidth(5);
   Rect.setHeight(7);

   area = Rect.getArea();
   
   // Print the area of the object.
   cout << "Total area: " << Rect.getArea() << endl;

   // Print the total cost of painting
   cout << "Total paint cost: $" << Rect.getCost(area) << endl;

   return 0;
}

公共继承 私有继承 保护继承

公共继承: 从公共基类派生一个类时,基类的公共成员成为派生类的公共成员基类的受保护成员成为派生类的受保护成员。 不能直接从派生类访问基类的私有成员,但可以通过调用基类的公共成员和受保护成员来访问它们。 公有继承的派生类的对象也是其基类对象。

受保护的继承: 从受保护的基类派生时,基类的公共成员和受保护成员成为派生类的受保护成员。

私有继承: 从私有基类派生时,基类的公共成员和受保护成员成为派生类的私有成员❗❗注意私有继承的派生类的对象仅仅是继承了基类的成员,其对象不是基类的对象。 可以视为一种组合(composition),组合大于继承的组合。

一篇深入解析私有继承的文章:www.bogotobogo.com/cplusplus/p…

2.7 多态

多态Polymorphism, 在C ++中,多态意味着如果我们调用成员函数,则可能导致执行不同的函数,具体取决于调用它的对象的类型。

虚函数

虚函数是成员函数,它在基类中使用关键字virtual声明,并由派生类重新定义(overriden)。通过它我们可以实现多态。

案例1 不使用虚函数

#include <iostream>
using namespace std;

// Base class
class Shape {
   public:
      Shape(int l, int w){length = l; width=w;} //default constructor
      int get_Area(){cout << "This is call to parent class area"<<endl;}
  protected:
      int length,width;
};

// Derived class
class Square: public Shape {
   public:
      Square(int l=0, int w=0) : Shape(l,w) {} //declaring and initializing derived class constructor 
      int get_Area(){
        cout << "Square area: " << length*width << endl;
        return (length * width);  
      }
};
// Derived class
class Rectangle: public Shape {
   public:
      Rectangle(int l=0, int w=0) : Shape(l,w) {} //declaring and initializing derived class constructor 
      int get_Area(){ cout << "Rectangle area: " << length*width << endl;return (length * width); }
};

int main(void) {
   Shape *s;
   Square sq(5,5); //making object of child class Sqaure
   Rectangle rec(4,5); //making object of child class Rectangle
   
   s = &sq;
   s->get_Area();
   s= &rec;
   s->get_Area();
  

   return 0;
}

该案例代码中,Shape作为基类,Square,Rectangle作为派生类,基类,派生类均存在名为int get_Area()的成员函数,我们期望s指针能够分别调用对应的派生类中的get_Area(),实际结果却是调用了基类的get_Area()。 这是由于静态链接导致的,编译器仅设置了一次对get_Area()的调用(调用基类中的get_Area())。

使用虚函数

如果在基类中定义了虚函数,而在派生类中定义了另一个版本,则将向编译器发出信号,表明我们不需要该函数的静态链接。

#include <iostream>
using namespace std;

// Base class
class Shape {
   public:
      Shape(int l, int w){length = l; width=w;} //default constructor
      //changing get_Area() to virtual type function
      virtual int get_Area(){cout << "This is call to parent class area"<<endl;} 
  protected:
      int length,width;
};

// Derived class
class Square: public Shape {
   public:
      Square(int l=0, int w=0) : Shape(l,w) {} //declaring and initializing derived class constructor 
      int get_Area(){
        cout << "Square area: " << length*width << endl;
        return (length * width);  
      }
};
// Dreived class
class Rectangle: public Shape {
   public:
      Rectangle(int l=0, int w=0) : Shape(l,w) {} //declaring and initializing derived class constructor 
      int get_Area(){ cout << "Rectangle area: " << length*width << endl;return (length * width); }
};

int main(void) {
   Shape *s;
   Square sq(5,5); //making object of child class Sqaure
   Rectangle rec(4,5); //making object of child class Rectangle
   
   s = &sq;
   s->get_Area();
   s= &rec;
   s->get_Area();
  

   return 0;
}

输出: 可见使用虚函数可以实现依据对象调用函数。

2.8 抽象

数据抽象是指只向外部提供必要的信息,而隐藏它们的背景细节,在C ++中,类提供了较高级别的数据抽象。 它们向外界提供了足够的公共方法来操作对象的数据(即状态)而实际上并不知道内部如何实现类。例如,您的程序可以调用sort()函数,而不知道该函数实际使用什么算法对给定的值进行排序。实际上,排序功能的底层实现可能会在库的不同版本之间发生变化,只要接口保持不变,函数调用就仍然可以工作。

#include <iostream>
using namespace std;

class Adder {
   public:
      // constructor
      Adder(int i = 0) {
         total = i;
      }
      
      // interface to outside world
      void addNum(int number) {
         total += number;
      }
      
      // interface to outside world
      int getTotal() {
         return total;
      };
      
   private:
      // hidden data from outside world
      int total;
};

int main() {
   Adder a;
   
   a.addNum(10);
   a.addNum(20);
   a.addNum(30);

   cout << "Total " << a.getTotal() <<endl;
   return 0;
}

Adder类将数字相加,然后返回总和。 公共成员addNumgetTotal是与外界的接口,用户需要了解它们才能使用该类。 私有成员total是用户不需要了解的,但对于类的正常运行是必需的。

抽象将代码分离为接口和实现。因此,在设计组件时,必须保持接口独立于实现,这样,如果更改了底层实现,则接口将保持不变。

2.9 封装

封装是一种面向对象的编程概念,它将数据和操作数据的函数绑定在一起:

class Box {
   public:
      double getVolume(void) {
         return length * breadth * height;
      }

   private:
      double length ;      // Length of a box
      double breadth;     // Breadth of a box
      double height ;      // Height of a box
};

length breadth height 数据与getVolume方法绑定在一起,封装到Box类。 数据封装是一种绑定数据与使用它们的函数的机制,而数据抽象是一种只公开接口并对用户隐藏实现细节的机制

3.0 抽象类(C++的接口)

接口描述了C ++类的行为或功能,而无需承诺该类的特定实现。

C ++接口是使用抽象类实现的,这些抽象类不应与数据抽象混淆,数据抽象是将实现细节与关联数据分开的概念。

通过将至少一个函数声明为纯虚函数,可以使一个类抽象。 通过在声明中放置“ = 0”来指定纯虚函数,如下所示:

class Box {
   public:
      // pure virtual function 纯虚函数
      virtual double getVolume() = 0;
      
   private:
      double length;      // Length of a box
      double breadth;     // Breadth of a box
      double height;      // Height of a box
};

抽象类 例子

#include <iostream>
 
using namespace std;
 
// Base class
class Shape {
   public:
      // pure virtual function providing interface framework.
      virtual int getArea() = 0;
      void setWidth(int w) {
         width = w;
      }
   
      void setHeight(int h) {
         height = h;
      }
   
   protected:
      int width;
      int height;
};
 
// Derived classes
class Rectangle: public Shape {
   public:
      int getArea() { 
         return (width * height); 
      }
};

class Triangle: public Shape {
   public:
      int getArea() { 
         return (width * height)/2; 
      }
};
 
int main(void) {
   Rectangle Rect;
   Triangle  Tri;
 
   Rect.setWidth(5);
   Rect.setHeight(7);
   
   // Print the area of the object.
   cout << "Total Rectangle area: " << Rect.getArea() << endl;

   Tri.setWidth(5);
   Tri.setHeight(7);
   
   // Print the area of the object.
   cout << "Total Triangle area: " << Tri.getArea() << endl; 

   return 0;
}

当编译并执行上述代码时,会产生以下结果

Total Rectangle area: 35
Total Triangle area: 17

可以看到抽象类是如何根据getArea()定义接口的,另外两个类实现了相同的函数,但使用不同的算法来计算特定于形状的区域。

3.1 友元

友元函数(friend function)

类的friend函数定义在类的作用域之外,但它有权访问类的所有私有和受保护的成员。尽管friend函数的原型出现在类定义中,但friend不是成员函数。

#include <iostream>
 
using namespace std;
 
class Box {
   double width;
   
   public:
      friend void printWidth( Box box );
      void setWidth( double wid );
};

// Member function definition
void Box::setWidth( double wid ) {
   width = wid;
}

// Note: printWidth() is not a member function of any class.
void printWidth( Box box ) {
   /* Because printWidth() is a friend of Box, it can
   directly access any member of this class */
   cout << "Width of box : " << box.width <<endl;
}
 
// Main function for the program
int main() {
   Box box;
 
   // set box width without member function
   box.setWidth(10.0);
   
   // Use friend function to print the wdith.
   printWidth( box );
 
   return 0;
}

友元类

Friend类可以访问其他类的私有和受保护的成员,例如在下面的代码中,一个LinkedList类可以被允许访问Node类的私有成员

_none
edit
play_arrow

brightness_4
class Node { 
private: 
    int key; 
    Node* next; 
  
    /* Other members of Node Class */
    friend int LinkedList::search(); 
    // Only search() of linkedList 
    // can access internal members 
}; 

和Friend类一样,Friend函数可以被授予特殊权限来访问私有和受保护的成员。一个友元函数可以是:

  • 另一个类的方法
  • 一个全局函数
class Node { 
private: 
    int key; 
    Node* next; 
  
    /* Other members of Node Class */
    friend int LinkedList::search(); 
    // Only search() of linkedList 
    // can access internal members 
}; 

注意点:

  1. 友元只能用于有限的目的。 太多的函数或外部类被声明为具有受保护或私有数据的类的朋友,这降低了在面向对象编程中封装单独类的价值。

  2. 友元不是相互的。 如果A类是B的朋友,那么B不会自动成为A的朋友。

  3. 友元不是继承的