YaoCheng8667 的个人博客 YaoCheng8667 的个人博客

记录精彩的程序人生

目录
《C++ Primer》 第七章:类
/    

《C++ Primer》 第七章:类

1.基本概念

1.1 类类型

每个类定义了唯一的类型,两个类即使成员相同,类型也不同。
可将类名作为类型名使用。

Sales_data item1;
class Sales_data item2;        
//两种等价的声明对象方式,第二种继承于C。

1.2 类声明

类的声明和定义也可以分开。只是声明而未定义的类称为前向声明。以 Screen 类为例,在 Screen 声明而未定义之前的类型为不完全类型。

这种类型只能在非常有限的场景下使用:使用指针或引用,定义以该类型为参数或返回值的函数。

在创建某个类对象之前,类必须被定义过。

2. 构造函数

2.1 简介

  • 构造函数没有返回类型,不可被声明成 const。
  • 若未定义构造函数,由编译器隐式地定义一个默认构造函数。
  • C++ 11 可以使用 " = default "请求编译器生成默认构造函数。
Sales_data() = default;

2.2 构造函数初始值列表

例如:

Sales_data(const std::string &s) : booNo(s){  }

Sales_data(const std::string &s , unsigned n , double p) : bookNo(s) , units_sold(n), revenue(p*n) {  }

在冒号和花括号之间的部分称为构造函数初始值列表,它负责为创建对象的一个或几个数据成员赋初值。

2.3 const,引用的构造函数初始值

如果成员是 const,引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些函数提供初始值。

class ConstRef {
public:
    ConstRef(int ii);
private:
    int i;
    const int ci;
    int &ri;
}

//错误:ci和ri必须被初始化
ConstRef::ConstRef(int ii) {
    i = ii;           //正确
    ci = ii;          //错误,不可以给const赋值
    ri = i;           //错误,ri未被初始化
}

//正确写法
ConstRef::ConstRef(int ii): i(ii), ci(ii), ri(ii){ }

2.4 委托构造函数

C++11 新标准扩展了构造函数初始值的功能,可以定义委托构造函数。

class Sales_data{
public:
    //非委托构造函数  
    Sales_data(std::string s, unsigned cnt , double price): bookNo(s) , units_sold(cnt), revenue(cnt*price){ }
    //委托构造函数
    Sales_data(): Sales_data("",0,0){}
    Sales_data(std::string s): Sales_data(s,0,0){}
    Sales_data(std::istream &is):Sales_data(){
      //两层委任,当三个参数的非委托构造函数执行完之后,执行read();
      read(is,*this);
    }
}

2.5 隐式的类类型转换

如果构造函数只接受一个实参,它实际上定义了转化为此类型的隐式转换机制。这种构造函数称为转换构造函数。

string null_book = "9-999-99999-9";

item.combine(null_book);
//combine接受的是一个Sales_data的const的引用,此时传入字符串将调用一个单参数的构造函数生成一个临时变量。

隐式类型转换只能存在一步。

item.combine("9-999-99999-9");
//错误,存在两步隐式类型转换,首先将字符串字面值转换成string,再将string转换为Sales_data.

抑制隐式类型转换:

在需要使用隐式类型转换的上下文中,可以将构造函数声明为 explicit 加以阻止。该关键字只对含一个实参的构造函数有效。

class Sales_data{
    Sales_data() = default;
    explicit Sales_data(const std::string &s): bookNo(s){ }
}

explicit 构造函数只能用于直接初始化。当需要类型转换时,只可使用显式类型转换。

item.combine(Sales_data(null_book));

3.拷贝、赋值和析构

  • 编译器可以默认完成这些操作:但某些类来说,这种方式无法工作,尤其是类需要分配类对象之外的资源时。
  • vector,string 可以使用合成拷贝的方式。

4.访问控制和封装

4.1 struct 和 class 的区别

使用 struct 和 class 唯一的区别就是默认访问权限。使用 struct 时,在第一个访问说明符之前的所有成员是 public 的,反之使用 class 时是 private。

4.2 友元

  • 类可以允许其他类或者函数访问他的非公有成员,方法是让其他类或者函数成为他的友元。
  • 友元函数:
    在函数声明语句之前加上 friend 关键字。
class Sales_data{
    friend Sales_data add(const Sales_data& , const Sales_data);
    friend std::istream &read (std::istream&, Sales_data& );
    public:
      ...
    private:
      ...
}

友元的声明仅仅改变了访问权限,并非一个通常意义上的函数声明。因此,如果希望某个类的用户调用某个友元函数,必须在友元声明以外再对函数进行一次声明。

  • 类之间的友元关系

    若一个类想要访问另一个类的成员函数,必须将其声明为友元。

class Screen{
    //Windows_mgr的成员可以访问screen类的私有成员。
    friend class Windows_mgr;
    //Screen剩余部分
}
  • 令成员函数作为友元

    当把成员函数定义为友元时,必须同时注明他为哪个类。

class Screen{
  friend void Windows_mgr::clear(ScresnIndex);
  //Screen剩余部分
}
  • 友元声明和作用域

    1. 类和非成员函数的声明不一定在友元友元声明之前
    2. 可以在类内部定义友元函数,但在其外部必须声明保持其可见。
    3. 若要使用该友元函数,必须保证其被声明过
struct X{
  friend void f(){      /*可以定义在类内部*/    }
  X(){  f();  }         //错误,f()未声明
  void g();
  void h();
}

void X::g(){ return f(); }//错误,f()未声明
void f();
void X::h(){ return f(); }//正确,现在f()为已声明

5.名字查找与类的作用域

  • 名字查找过程(寻找与其所用名字最匹配的声明)

    1. 在名字所在块中查找声明语句,只考虑名字使用之前出现的声明。
    2. 如果没找到,继续查找外层作用域。
    3. 最终未找到匹配的声明,程序报错。
  • 定义在类内部的成员函数,在类全部可见之后再编译其函数体。

typedef double Money;
string bal;
class Account{
public:
  Money balance(){ return bal; }
private:
  Money bal;
}

对于 balance 函数,首先检查 Money 的声明,在 balance 函数声明之前未找到,转向外层作用域。

由于整个类可见之后才解析函数体,故 balance 返回的 bal 为 Money 类型。

  • 在类中,不可重新定义外层作用域定义的类型(使用 typedef)。

  • 成员函数使用过程的名字解析

    1. 在成员函数内部查找该名字的声明。
    2. 若未找到,在类内继续寻找。
    3. 若类内还未找到,到函数定义之前的作用域内继续查找。
    4. 若成员定义在类的外部,还需到全局作用域中查找声明。

6.聚合类

聚合类使得用户可以直接访问其成员,且具有特殊的初始化语法格式。

当一个类满足以下条件时,我们称其是聚合的。

  1. 所有成员均为 public。
  2. 没有定义任何构造函数。
  3. 没有类内初始值。
  4. 没有基类,也没有 virtual 类。
    例:
struct Data{
    int ival;
    string s;
}

可以使用一个花括号括起成员初始值列表,并用其初始化聚合类数据成员。初始值顺序必须与声明一致。

Data val1 = {0, "Anna"};

7.类的静态成员

7.1 声明和使用

使用 static 关键字声明静态成员,使用作用域运算符访问静态成员。

也可以使用类的对象,引用,或指针访问。

成员函数不通过作用域运算符也可访问。

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

double r = Accout::rate();

Account ac1;
Account *ac2 = &ac1;
r = ac1.rate();
r = ac2->rate();

7.2 静态成员定义

可以在类的内部和外部定义,但 static 关键字只可用于内部声明。


标题:《C++ Primer》 第七章:类
作者:YaoCheng8667
地址:https://ycisme.xyz/articles/2019/12/05/1575527758309.html