C++ Primer 学习笔记_15_类与数据抽象(一)_类的定义和声明

C++ Primer 学习笔记_15_类与数据抽象(1)_类的定义和声明

C++ Primer 学习笔记_15_类与数据抽象(1)_类的定义和声明

在C++中,用类来定义自己的抽象数据类型。通过定义类型来对应所要解决的问题中的各种概念,可以使我们更容易编写、调试和修改程序。可以使得自己定义的数据类型用起来与内置类型一样容易和直观。

看一下Sales_item类:

class Sales_item
{
private:
    std::string isbn;
    unsigned units_sold;
    double revenue;
 
public:
    double ave_price() const;
    bool same_isbn(constSales_item &rhs) const
    {
        return isbn ==rhs.isbn;
    }
 
   Sales_item():units_sold(0),revenue(0) {}
};
 
double Sales_item::ave_price() const
{
    if (!units_sold)
    {
        return 0;
    }
    return revenue /units_sold;
}

一、类定义:扼要重述

简单来说,类就是定义了一个新的类型和一个新的作用域。

 

1、类成员

一个类可以包含若干公有的、私有的和受保护的部分。我们已经使用过public 和private访问标号:在public部分定义的成员可被使用该类型的所有代码访问:在private部分定义的成员可被其他类成员访问。

所有成员必须在类的内部声明,一旦类定义完成之后,就没有任何方式可以增加成员了。

 

2、构造函数

在创建一个类对象时,编译器会自动使用一个构造函数来初始化该对象,也就是说:构造函数用于给每个数据成员设定适当的初始值。

构造函数一般使用一个构造函数初始化列表,来初始化对象的数据成员。构造函数初始化类表由成员名和带括号的初始值组成,跟在构造函数的形参表之后,并以冒号(:)开头。

比如:Sales_item():units_sold(0),revenue(0){}

 

3、成员函数

在类内部定义的函数默认为内联函数(inline)。

而在类的外部定义的函数必须指明它们是在类的作用域中。

就const关键字加在形参表之后,就可以将成员函数声明为常量,如:double avg_price() const;

const必须同时出现在声明和定义中,若只出现在一处,则会出现编译时错误!

//类内
double avg_price() const;
//类外
double Sales_item::ave_price()  //error
{
    if (!units_sold)
    {
        return 0;
    }
    return revenue /units_sold;
}

二、数据抽象和封装

数据抽象是一种依赖于接口和实现分离的编程技术:类的设计者必须关心类是如何实现的,但使用该类的程序员不必了解这些细节

封装是一项将低层次的元素组合起来形成新的、高层次实体的技术!

1、访问标号实施抽象和封装

1)程序的所有部分都可以访问带有public标号的成员。类型的数据抽象视图由其public成员定义。

2)使用类的代码不可以访问带有private标号的成员。private封装了类型的实现细节。

 

【class与struct的区别】

class默认的是私有的,struct默认是公有的!

 

2、编程角色的不同类别

好的类的设计者会定义直观和易用的类接口,而用户(此处可以指程序员)则只需关心类中影响他们使用的那部分实现。

注意,C++程序员经常会将应用程序的用户和类的使用者都称为“用户”。

 

【关键概念:数据抽象和封装的好处】

1)避免类内部出现无意的、可能破坏对象状态的用户级错误。

2)随着时间的推移,可以根据需求改变或缺陷报告来完善类实现,而无需改变用户级代码。

【注解】

改变头文件中的类定义可有效地改变包含该头文件的每个源文件的程序文本,所以,当类发生改变时,使用该类的代码必须重新编译

 

三、关于类定义的更多内容

1、同一类型的多个数据成员

普通的声明:

class Screen
{
public:
    //...
 
private:
    std::string contents;
    std::string::size_type cursor;
    std::string::size_typeheight,width;
};


2、使用类型别名来简化类

出了定义数据和函数成员之外,类还可以定义自己的局部类型名字。如果为std::string::size_type提供一个类型别名,那么Screen类将是一个更好的抽象(将index定义放在public部分):

class Screen
{
public:
   //...
   typedef std::string::size_type index;
 
private:
   std::string contents;
   index cursor;
   index height,width;
};


3、成员函数可以被重载

成员函数只能重载本类的其他成员函数。

重载的成员函数和普通函数应用相同的规则:两个重载成员的形参数量和类型不能完全相同。调用非成员重载函数所用到的函数匹配过程也应用于重载成员函数的调用。


4、定义重载函数

class Screen
{
public:
   typedef std::string::size_type index;
   //重载函数get
   char get() const
   {
        return contents[cursor];
   }
   char get(index ht,index wd) const;
 
private:
   std::string contents;
   index cursor;
   index height,width;
};

5、显式指定inline函数

在类内部定义的成员函数,默认就是inline函数。但是也可以显式的将成员函数指定为inline:

class Screen
{
public:
   typedef std::string::size_type index;
 
   //类内的函数默认就是inline函数
   char get() const
   {
        return contents[cursor];
   }
   inline char get(index ht,index wd) const;
   index get_cursor() const;
 
private:
   std::string contents;
   index cursor;
   index height,width;
};
 
//已经在类体中声明为inline了,就没必要再次声明
char Screen::get(index r,index c)const
{
   index row = r * width;
   return contents[row + c];
}
 
//即使没有在类体中声明,也可以在外面补上
inline Screen::indexScreen::get_cursor() const
{
   return cursor;
}

在声明和定义处指定inline都是合法的。在类的外部定义 inline 的一个好处是可以使得类比较容易阅读。

 

【注解】

像其他inline一样,inline成员函数的定义必须在调用该函数的每个源文件中是可见的。不在类定义体内定义的inline成员函数,其定义通常应放在有类定义的同一头文件中

 

四、类声明和类定义

在一个给定的源文件中,一个类只能被定义一次。如果在多个文件中定义一个类,那么每个文件中的定义必须是完全相同的

可以声明一个类而不定义它:

//前向声明
class Screen;

在声明之后、定义之前,类Screen是一个不完全类型,即:已知Screen是一个类型,但不知道包含哪些成员。

不完全类型(incompletetype)只能以有限方式使用不能定义该类型的对象。不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。

在创建类的对象之前,必须完整的定义该类。必须是定义类,而不是声明类,这样,编译器就会给类的对象预定相应的存储空间。同样的,在使用引用或指针访问类的成员之前,必须已经定义类

 

1、为类的成员使用类声明:

只有当类定义已经在前面出现过,数据成员才能被指定为该类类型。如果该类型是不完全类型,那么数据成员只能是指向该类类型的指针引用

因为只有当类定义体完成后才能定义类,因此类不能具有自身类型的数据成员。然而,只要类名一出现就可以认为该类已声明。因此,类的数据成员可以是指向自身类型的指针或引用:

class LinkScreen

{

   Screen Window;

   LinkScreen *next;

   LinkScreen *prev;

};

 

五、类对象

定义一个类时,也就是定义了一个类型。一旦定义了类,就可以定义该类型的对象。定义对象时,将为其分配内存空间,但(一般而言)定义类型时不进行存储分配。一旦定义了对象,编译器则为其分配足够容纳一个类对象的存储空间

每个对象具有自己的类数据成员的副本。修改其中一个对象不会改变其他该类对象的数据成员。

 

1、定义类类型的对象

定义了一个类类型之后,可以按以下两种方式使用。

1)将类的名字直接用作类型名。

2)指定关键字class或struct,后面跟着类的名字:

   Screen scr;
   //两条语句作用相同
   class Screen scr;

2、为什么类的定义以分号结束

因为在类定义之后可以接一个对象定义列表,所以,定义必须以分号结束,可以给一个《实践:求主元素》

//vs2012测试代码
#include<iostream>
#include<vector>
 
usingnamespace std;
 
#definen 5
 
//方法一:每找出两个不同的element,就成对删除即count--,最终剩下的一定就是所求的。时间复杂度:O(n)
classSolution {
public:
    int majorityElement(vector<int>&num) {
              int element;
        int length = num.size();
              int count = 0;
              for(int i = 0; i < length; i++)
              {
                     if(count == 0)
                     {
                            element = num[i];
                            count = 1;
                     }
                     else
                     {
                            if(num[i] ==element)
                                   count++;
                            else
                                   count--;
                     }
              }
              count = 0;
              for(int i = 0; i < length; i++)
              {
            if(num[i] == element)
                count++;
              }
 
        if(count > length / 2)
            cout << element <<endl;
        else
            cout << "can not findthe majority element" << endl;
 
              return 0;
    }
}lin, lin2;
 
int main()
{
       int a;
       vector<int> num;
       for(int i=0; i<n; i++)
       {
              cin>>a;
              num.push_back(a);
       }
       lin.majorityElement(num);
}

运行结果:

输入:23 2 3 2

2

 

【注解】

通常,将对象定义成类定义的一部分是个坏主意!!!这样做,会使所发生的操作难以理解。对读者而言,将两个不同的实体(类和变量)组合在一个语句中,也会令人迷惑不解。

 

版权声明:本文为博主原创文章,未经博主允许不得转载。