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

【注解】

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

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

时间: 2024-10-12 07:54:06

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

C++ Primer 学习笔记_17_类与数据抽象(3)_类作用域

C++ Primer 学习笔记_17_类与数据抽象(3)_类作用域 引言: 每个类都定义了自己的新作用域与唯一的类型.即使两个类具有完全相同的成员列表,它们也是不同的类型.每个类的成员不同与任何其他类(或任何其他作用域)的成员. 一.类作用域中的名字查找 1)首先,在使用该名字的块中查找名字的声明.只考虑在该项使用之前声明的名字. 2)如果在1)中找不到,则在包围的作用域中查找. 如果找不到任何声明,则出错. 类的定义实际上是在两个阶段中处理: 1)首先,编译器声明: 2)只有在所有成员出现之后

C++ Primer 学习笔记_58_重载操作符与转换 --重载操作符的定义

重载操作符与转换 --重载操作符的定义 引言: 明智地使用操作符重载可以使类类型的使用像内置类型一样直观! 重载操作符的定义 重载操作符是具有特殊名称的函数:保留字operator后接定义的操作符符号.如: Sales_item operator+(const Sales_item &,const Sales_item &); 除了函数调用操作符之外,重载操作符的形参数目(包括成员函数的隐式this指针)与操作符的操作数数目相同.函数调用操作符可以接受任意数目的操作数. 1.重载的操作符名

C++ Primer 学习笔记_15_从C到C++(1)--bool类型、const限定符、const与#define、结构体内存对齐

欢迎大家阅读参考,如有错误或疑问请留言纠正,谢谢 一.bool类型(C语言没有) 1.逻辑型也称布尔型,其取值为true(逻辑真)和false(逻辑假),存储字节数在不同编译系统中可能有所不同,VC++中为1个字节. 2.声明方式:bool result; result=true; 3.可以当作整数用(true一般为1,false为0) 4.把其它类型的值转换为布尔值时,非零值转换为true,零值转换为false 5.示例 #include <iostream> using namespace

C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员)、拷贝构造函数

C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员).拷贝构造函数  从概念上将,可以认为构造函数分为两个阶段执行: 1)初始化阶段: 2)普通的计算阶段.计算阶段由构造函数函数体中的所有语句组成. 一.构造函数初始化列表 推荐在构造函数初始化列表中进行初始化 1.对象成员及其初始化 <span style="font-size:14px;">#include <iostream> using namespace std;

C++ Primer 学习笔记_56_类与数据抽象 --消息处理示例

复制控制 --消息处理示例 说明: 有些类为了做一些工作需要对复制进行控制.为了给出这样的例子,我们将概略定义两个类,这两个类可用于邮件处理应用程序.Message类和 Folder类分别表示电子邮件(或其他)消息和消息所出现的目录,一个给定消息可以出现在多个目录中.Message上有 save和 remove操作,用于在指定Folder中保存或删除该消息. 数据结构: 对每个Message,我们并不是在每个Folder中都存放一个副本,而是使每个Message保存一个指针集(set),set中

C++ Primer 学习笔记_57_类与数据抽象 --管理指针成员

复制控制 --管理指针成员 引言: 包含指针的类需要特别注意复制控制,原因是复制指针时只是复制了指针中的地址,而不会复制指针指向的对象! 将一个指针复制到另一个指针时,两个指针指向同一对象.当两个指针指向同一对象时,可能使用任一指针改变基础对象.类似地,很可能一个指针删除了一对象时,另一指针的用户还认为基础对象仍然存在.指针成员默认具有与指针对象同样的行为. 大多数C++类采用以下三种方法之一管理指针成员: 1)指针成员采取常规指针型行为:这样的类具有指针的所有缺陷但无需特殊的复制控制! 2)类

C++ Primer 学习笔记_55_类与数据抽象 --析构函数

复制控制 --析构函数 引言: 在构造函数中分配了资源之后,需要一个对应操作自动回收或释放资源.析构函数就是这样的一个特殊函数,它可以完成所需的资源回收,作为类构造函数的补充. 1.何时调用析构函数 撤销类对象时会自动调用析构函数: Sales_item *p = new Sales_item; { Sales_item item(*p); //调用复制构造函数 delete p; //调用指针p的析构函数 } //调用对象item的析构函数 动态分配的对象只有在指向该对象的指针被删除时才撤销,

C++ Primer 学习笔记_53_类与数据抽象 --友元、static成员

类 --友元.static成员 一.友元 友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类(对未被授权的函数或类,则阻止其访问):友元的声明以关键字friend开始,但是它只能出现在类定义的内部.友元声明可以出现在类中的任何地方:友元不是授予友元关系的那个类的成员,所以它们不受其声明出现部分的访问控制影响. [最佳实践] 通常,将友元声明成组的放在类定义的开始或结尾是个好主意! 1.友元关系:一个例子 假设一个窗口管理类Window_Mgr可能需要访问由其管理的Screen对象的内部

C++ Primer 学习笔记_54_类与数据抽象 --复制构造函数、赋值操作符

复制控制 --复制构造函数.赋值操作符 引言: 当定义一个新类型时,需要显式或隐式地指定复制.赋值和撤销该类型的对象时会发生什么– 复制构造函数.赋值操作符和析构函数的作用!      复制构造函数:具有单个形参,该形参(常用const修饰)是对该类类型的引用.当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式的使用复制构造函数:当将该类型的对象传递给函数或者从函数返回该类型的对象时,将隐式使用复制构造函数.     析构函数:作为构造函数的互补,当对象超出作用域或动态分配的对象被删除