C++类的总结

来源:互联网 发布:淘宝评价赚钱 编辑:程序博客网 时间:2024/06/15 03:06

类的两项基本能力:

(1) 数据抽象,即定义数据成员和函数成员的能力。
(2) 封装,即保护类的成员不被随意访问的能力。通过将类的实现细节设为private,我们就能完成类的封装。类可以将其他类或者函数设为友元,这样它们就能访问类的非公有成员了。

1. 如何设计一个类,如何定义类及类的成员

(1)类可以在它的第一个访问说明符之前定义成员,对这种成员的访问权限依赖于类定义的方式。如果使用struct关键字,则定义在第一个访问说明符之前的成员是public的;相反,如果使用class关键字,则这些成员是private的。

// 使用struct关键字定义类struct 类名 {// 访问说明符之前的行为或属性默认是public的public:    //公有的行为或属性  private:    //私有的行为或属性};// 使用class关键字定义类class 类名 {// 访问说明符之前的行为或属性默认是private的public:    //公有的行为或属性  private:    //私有的行为或属性};

Note: 类定义结束后的那个分号不能省略。

(2)尽管所有成员函数都必须在类的内部声明,但是成员函数体可以定义在类内也可以定义在类外。当我们在类的外部定义成员函数时,成员函数的定义必须与它的声明匹配。也就是说,返回类型、参数列表和函数名都得与类内部的声明保持一致。如果成员被声明成常量成员函数,那么它的定义也必须在参数列表后明确指定 const 属性。同时,类外部定义的成员名字必须包含它所属的类名。

class Sales_data {// 友元声明,后面会讲friend Sales_data add(const Sales_data&, const Sales_data&);friend std::ostream &print(std::ostream&, const Sales_data&);friend std::istream &read(std::istream&, Sales_data&);public:    // 构造函数,采用参数初始化表    Sales_data(): units_sold(0), revenue(0.0) {     }    Sales_data(const std::string &s):            bookNo(s), units_sold(0), revenue(0.0) {    }    Sales_data(const std::string &s, unsigned n, double p):           bookNo(s), units_sold(n), revenue(p*n) {    }    // 成员函数的声明    Sales_data(const std::string &book, const unsigned num,              const double sellp, const double salep);    Sales_data(std::istream &is);    // 类的内部定义成员函数    std::string isbn() const {        return bookNo;     }    Sales_data &combine(const Sales_data &rhs) {        units_sold += rhs.units_sold;   // 把rhs的成员加到this对象的成员上        revenue += rhs.revenue;                 return *this;                   //返回调用该函数的对象    }    double avg_price() const {        if (units_sold) {            return revenue/units_sold;        } else {            return 0;        }    }private:    std::string bookNo;         // 书籍编号,隐式初始化为空串    unsigned units_sold = 0;    // 销售量,显式初始化为0    double sellingprice = 0.0;  // 原始价格    double saleprice = 0.0;     // 实售价格    double discount = 0.0;      // 折扣    double revenue;             // 收入};// 类的外部定义成员函数,在类的内部已经做过声明Sales_data::Sales_data(std::istream &is) {    read(is, *this);    // read函数的作用是从is中读取一条交易信息然后                        // 存入this对象中}Sales_data::Sales_data(const std::string &book, const unsigned num, const double sellp, const double salep) {    bookNo = book;    units_sold = num;    sellingprice = sellp;    saleprice = salep;    if (sellingprice == 0) {        discount = saleprice / sellingprice;        // 计算实际折扣    }}Sales_data add(const Sales_data &lhs, const Sales_data &rhs) {    Sales_data sum = lhs;    sum.combine(rhs);    return sum;}std::istream &read(std::istream &is, Sales_data &item) {    is >> item.bookNo >> item.units_sold >> item.sellingprice >> item.saleprice;    return is;}std::ostream &print(std::ostream &os, const Sales_data &item) {    os << item.isbn() << " " << item.units_sold << " " << item.sellingprice     << " " << item.saleprice << " " << item.discount;    return os;}

(3)令成员作为内联函数: 在类中,常有一些规模较小的函数适用于被声明成内联函数,定义在类内部的成员函数是自动 inline 的。在类外定义内联函数时,可以在类的内部把 inline 作为声明的一部分显式地声明成员函数,在类的外部用 inline 关键字修饰函数的定义:

inlinebool compareIsbn(const Sales_data &lhs, const Sales_data &rhs) {    return lhs.isbn() < rhs.isbn();}

Note: 虽然在声明和定义的地方同时说明 inline 是合法的,但最好只在类外部定义的地方说明 inline,这样可以使类更容易理解。

内联函数作用: 内联函数在编译的时候将不进行函数调用,编译器将内联函数的代码粘贴在调用(形式上调用)处,可以提高效率。内联函数只能是代码很少很简单的函数,如果一个很大很复杂的函数即使被设为内联,编译器也将自动设置该函数为非内联。

(4)重载成员函数: 如果同一作用域的几个函数名字相同但形参列表不同,我们称之为重载(overloaded)函数。和非成员函数一样,成员函数也可以被重载,只要函数之间在参数的数量和/或类型上有所区别就行。

// 构造函数的重载Sales_data(){}Sales_data(std::istream &is){};Sales_data(const std::string &s){}Sales_data(const std::string &s, unsigned n, double p){}

Note: 不能通过函数的返回值类型不同来定义重载函数,只能通过形参列表的不同。

2. 类成员的访问权限

C++通过 public、protected、private 三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符。所谓访问权限,就是你能不能操作该类中的成员。
在类的内部(定义类的代码内部),无论成员被声明为 public、protected 还是 private,都是可以互相访问的,没有访问权限的限制。
在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问 public 属性的成员,不能访问 private、protected 属性的成员。

Note: C++ 中的 public、private、protected 只能修饰类的成员,不能修饰类,C++中的类没有公有私有之分。

3. 构造函数和析构函数

(1)构造函数: 在C++中,有一种特殊的成员函数,它的名字和类名相同,没有返回值,不需要用户显式调用(用户也不能调用),而是在创建对象时自动执行。这种特殊的成员函数就是构造函数(Constructor)。
(2)构造函数的重载: 和普通成员函数一样,构造函数是允许重载的。一个类可以有多个重载的构造函数,创建对象时根据传递的实参来判断调用哪一个构造函数。构造函数的调用是强制性的,一旦在类中定义了构造函数,那么创建对象时就一定要调用,不调用是错误的。如果有多个重载的构造函数,那么创建对象时提供的实参必须和其中的一个构造函数匹配;反过来说,创建对象时只有一个构造函数会被调用。
(3)析构函数: 析构函数(Destructor)是一种特殊的成员函数,没有返回值,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动执行。构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~符号。

注意:析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。如果用户没有定义,编译器会自动生成一个默认的析构函数。

// constructor.cpp#include <iostream>#include <string>using namespace std;class Sales_data {public:    // 构造函数    Sales_data(): units_sold(0), revenue(0.0) {        cout << "constructor1 executed" << endl;    }    // 构造函数的重载    Sales_data(const std::string &s):            bookNo(s), units_sold(0), revenue(0.0) {        cout << "constructor2 executed" << endl;    }    // 析构函数    ~Sales_data() {        cout << "destructor executed" << endl;    }private:    std::string bookNo;         unsigned units_sold;      double revenue;         };int main(int argc, char *argv[]){    Sales_data sale1;     Sales_data sale2("978-7-121-15535-2");    return 0;}

这里写图片描述

由运行结果知,创建对象时系统会自动调用构造函数进行初始化工作,程序即将结束时系统会自动调用析构函数进行清理工作。

4. 如何声明并使用友元

(1)类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元(friend)。如果类想把一个函数作为它的友元,只需要增加一条 friend 关键字开始的函数声明语句即可。

friend Sales_data add(const Sales_data&, const Sales_data&);

友元声明只能出现在类定义的内部,但是在类内出现的具体位置不限。友元不是类的成员也不受它所在区域访问控制级别的约束。

(2)类还可以把其他的类定义成友元,也可以把其他类(之前定义过的)的成员函数定义成友元。此外,友元函数能定义在类的内部,这样的函数是隐式内联的。

class Screen {// Window_mgr的成员可以访问Screen类的私有部分friend class Window_mg;private:    unsigned height = 0, width = 0;    unsigned cursor = 0;    string contents;public:    Screen() = default;         // 默认构造函数    Screen(unsigned ht, unsigned wd, char c) : height(ht), width(wd),        contents(ht * wd, c) {  }};class Window_mgr {public:    // 窗口中每个屏幕的编号    using ScreenIndex = std::vector<Screen>::size_type;    // 按照编号将指定的Screen重置为空白    void clear(ScreenIndex);private:    std::vector<Screen> screens{Screen(24, 80, ' ')};};void Window_mgr::clear(ScreenIndex i) {    // s是一个Screen的引用,指向我们想清空的那个屏幕    Screen &s = screens[i];    // 将那个选定的Screen重置为空白    s.contents = string(s.height * s.width, ' ');} 

如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员。首先把s定义成screens vector中第i个位置上的Screen的引用,随后利用Screen的height和width成员计算出一个新的string对象,并令其含有若干个空白字符,最后我们把这个含有很多空白的字符串赋给contents成员。如果clear不是Screen的友元,上面的代码将无法通过编译,因为此时clear将不能访问Screen的height、width和contents成员。而当Screen将Window_mgr指定为其友元之后,Screen的所有成员对于Window_mgr就都变成可见的了。

Note: 友元关系不具有传递性,也就是说,如果Window_mgr有它自己的友元,则这些友元并不能理所当然地具有访问Screen的特权,每个类负责控制自己的友元类或友元函数。

使用友元的利弊:当非成员函数确实需要访问类的私有成员时,我们可以把它声明成该类的友元。此时,友元可以“工作在类的内部”,像类的成员一样访问类的所有数据和函数。但是一旦使用不慎(比如随意设定友元),就有可能破坏类的封装性。

5. 类的静态成员和静态函数

有时候类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联。例如,一个银行账户类可能需要一个数据成员来表示当前的基准利率。在此例中,我们希望利率与类关联,而非与类的每个对象关联。从实现效率的角度来看,没必要每个对象都存储利率信息。而且更加重要的是,一旦利率浮动,我们希望所有的对象都能使用新值。

声明静态成员
我们通过在成员的声明之前加上关键字 static 使得其与类关联在一起。和其他成员一样静态成员可以是 public 的或 private 的。静态数据成员的类型可以是常量、引用、指针、类类型等。

class Account {public:    void calculate() {        amount += amount * interestRate;    }    static double rate() {        return interestRate;    }    static void rate(double);private:    std::string owner;    double amount;    static double interestRate;    static double initRate();};

static 成员变量属于类,不属于某个具体的对象,即使创建多个对象,也只为静态成员变量分配一份内存,所有对象使用的都是这份内存中的数据。当某个对象修改了静态成员变量,也会影响到其他对象。

static 成员变量既可以通过对象来访问,也可以通过类来访问。

//通过类类访问 static 成员变量Account::interestRate = 2.735;//通过对象来访问 static 成员变量Account account("hp", 10000);account.interestRate = 2.735;//通过对象指针来访问 static 成员变量Account *pa = new Account("hp", 10000);pa -> interestRate = 2.735;

因为静态数据成员不属于类的任何一个对象,所以它们并不是在创建类的对象时被定义的。这意味着它们并不是由类的构造函数初始化的。而且一般来说,我们不能在类的内部初始化静态成员。相反地,必须在类的外部定义和初始化每个静态成员。一个静态数据成员只能定义一次,并且它将一直存在于程序的整个生命周期中。

我们定义静态数据成员的方式和在类的外部定义成员函数差不多。我们需要指定对象的类型名,然后是类名、作用域运算符以及成员自己的名字:

// 定义并初始化一个静态成员double Account::interestRate = initRate();

这条语句定义了名为 interestRate 的对象,该对象是类 Account 的静态成员,其类型是 double。从类名开始,这条定义语句的剩余部分就都位于类的作用域之内了。因此,我们可以直接使用 initRate 函数。注意,虽然 initRate 是私有的,我们也能用它初始化 interestRate。和其他成员的定义一样,interestRate 的定义也可以访问类的私有成员。

Note: static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。反过来说,没有在类外初始化的 static 成员变量不能使用。static 成员变量不占用对象的内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问。具体来说,static 成员变量和普通的 static 变量类似,都在内存分区中的全局数据区分配内存。

使用 static 成员变量而不是全局变量的优点:
(1) static 成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突。
(2) 可以实施封装。static 成员可以是私有成员,而全局对象不可以。
(3) 通过阅读程序容易看出 static 成员是与特定类关联的,这种可见性可清晰地显示程序员的意图。

定义静态成员函数
在类的内部声明函数时需要添加 static 关键字,但是在类外部定义函数时就不需要了。
static 成员函数是类的组成部分但不是任何对象的组成部分,它有以下几个特点:
(1) static 函数没有 this 指针。
(2) static 成员函数不能被声明为 const (将成员函数声明为 const 就是承诺不会修改该函数所属的对象)。
(3) static 成员函数也不能被声明为虚函数。

和其他的成员函数一样,我们既可以在类的内部也可以在类的外部定义静态成员函数。当在类的外部定义静态成员时,不能重复 static 关键字,该关键字只出现在类内部的声明语句:

void Account::rate(double newRate) {    interestRate = newRate;}

静态成员函数与普通成员函数的根本区别:
(1)普通成员函数有 this 指针,可以访问类中的任意成员。
(2)而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
Note: 在C++中,静态成员函数的主要目的是访问静态成员,静态成员函数可以通过类来调用(一般都是这样做),也可以通过对象来调用。

0 0
原创粉丝点击