以下为个人学习笔记整理。参考书籍《C++ Primer Plus》

# 对象和类

# 抽象和类

# 类型是什么🤔

类型的三个特性:

  • 决定了数据对象所需的内存数量。
  • 决定了如何解释内存内的数据(long 和 float 在内存中占用位数相同,但转化为的值不相同。)
  • 决定了数据对象能够执行的操作和操作的内容。

# C++ 中的类

类的规范由两个部分组成:

  • 类声明:描述类的数据成员,描述类的成员函数(接口)。
  • 类方法定义:描述类的成员函数实现。
// 头文件
class Stock{
private:
    char company[30];
    int shares;
    double share_val;
    double total_val;
    void set_tot(){ total_val = shares * share_val; }
public:
    void acquire(const char* co, int n, double pr);
    void buy(int num, double price);
    void sell(int num, double price);
    void update(double price);
    void show();
};

image-20210304104308849

# 访问控制

用于控制程序直接访问类成员称为访问控制。

# 控制成员的访问:公有还是私有

类成员的定义默认都是 private 的,因此声明成员时不指定访问控制类型,则默认都是 private

# 类和结构

C++ 的类和结构实际上非常的相似。唯一的区别在于:

  • 结构的默认访问类型是 public
  • 类的默认访问类型是 private

一般情况下,C++ 程序习惯用类来实现类描述,而结构只用作数据存储。

# 实现类成员函数

为类的函数原型提供相应的实现。

  • 定义成员函数时,使用作用域解析运算符(::)来标识函数所需的类;
  • 类方法可用访问类的 private 组件(可以是其他私有方法或变量)。
// 通过 作用域解析运算符 定义函数所属类。
void Stock::acquire(const char* co, int n, double pr){
	company = co; // 访问私有组件
    // ...
}

# 内联方法

如果函数的定义放在类的声明中,那么该函数将默认视作内联函数看待:

class Stock{
void set_tot(){ total_val = shares * share_val; } // 内联函数
};

或者在类的实现加上 inline 关键字,这两种定义等价。

class Stock{
void set_tot();
};
// 内联函数
inline void Stock::set_tot(){
	// ...
}

内联函数的特殊规则要求使用其的文件中,必须对内联函数进行定义。所以尽量把内联函数的定义放在文件头。

# 成员函数的调用

  • 类对象们共用一组成员函数。
  • 调用成员函数时,将使用当前调用的类对象作为操作主体(访问的成员组件都来自该类对象)。

image-20210304111426660

# 类的构造函数和析构函数

# 声明和定义构造函数

声明类对象时,将自动调用构造函数。

// 声明
Stock(const char* co, int n = 0,double pr = 0.0);
// 定义
Stock::Stock(const char* co, int n = 0,double pr = 0.0){
    // ....
}

# 使用构造函数

  • 显式调用构造函数: stock s = Stock("alle", 10, 1.1);
  • 隐式调用构造函数: Stock t("ell", 1, 2.2);

接受一个参数的构造函数允许使用赋值语法对对象进行初始化 Classname obj = val;

# 默认构造函数

// 默认构造函数可能如下
Stock::Stock(){ }
// 调用默认构造函数
Stock k;

# 析构函数

在对象即将被销毁之前,程序会自动调用对象的析构函数来释放一些在构造函数内申请的内存,或者做一些其他想做的事情。

析构函数的原型:

~Stock(); // 需要在函数之前加上~

# 析构函数何时会被调用

析构函数通常由编译器自动调用,但也可以显式的调用。析构函数的调用时机和对象的生命周期有关:

  • 如果创建的类是静态存储类对象。那么析构函数会在程序结束之前调用。
  • 如果对象是通过 new 创建的,那么在执行 delete 时,会执行其析构函数。
  • 临时对象(临时变量),会在函数退出时调用其析构函数。

# C++11 列表初始化

C++11 中,类的初始化同样可用通过「大括号」+「参数」的形式初始化。

Stock st {"jock", 11, 1.1};

# 任意长度参数 ——std::initialize_list

initialize_list 可用用作函数参数或者方法参数类型,可用表示任意长度的同类型列表。

# const 成员函数

对于 const 的类对象,其函数也必须声明为 const。

const Stock land = Stock{"lose"}
void Stock::show() const // 声明为 const 成员函数
land.show();

# this 指针

this 指针指向用来调用成员函数的对象。

image-20210304144626998

如果想要获得调用对象的引用,那么可用通过 *this 来获取。

# 对象数组

对象数组和标准数组的使用类似:

Stock stock_lst[4] = {
	Stock("1", 1, 1.1),
	Stock("2", 2, 2.2),
	Stock("3", 3, 3.3),
	Stock("4", 4, 4.4),
}

# 类作用域

在类中定义的名称(类数据成员名 && 类函数成员名)的作用域都为整个类。

# 作用域为类的常量

在类的声明中定义常量是行不通的,因为类的声明并不会创建对象。

class Stock{
private:
    const int Months = 12; // is fail
    double costs[Months];
}

解决办法是在类的声明中定义枚举(enum),用这种方式声明类时不会创建数据成员,如果在类作用域中遇到 Months ,编译器将会用 12 来替换它。

class Stock{
private:
    enum {Months = 12};
    double costs[Months];
}

或者用关键字 static 来声明一个静态变量。这样会创建一个 Months 的静态变量,并且被所有的 Stock 类对象共享。

class Stock{
private:
    static const int Months = 12;
    double costs[Months];
}

# 作用域内枚举(C++11)

传统枚举的定义经常有可能发生枚举量的冲突,引发异常:

enum egg {small, medium, big};
enum t_shirt {small, medium, big}; // 和上面的定义冲突

C++11 提供了新的枚举,其作用域为类:

enum class egg {small, medium, big}; // same: enum struct egg{small, medium, big};
enum class t_shirt {small, medium, big};
// use
egg size = egg::small;

传统枚举类型有时会被隐式转换为 int 类型。而新的枚举则不会,不过可用通过显示类型转换变更为 int 类型。

默认情况下枚举的类型为 int ,C++11 还提供了对枚举数据类型指定的功能:

enum class : short egg {small, medium, big}; // enum egg is short

# 抽象数据类型

通过抽象数据和操作实现两者的分离。如下代码,把操作数据抽象为 Item 类型。 Item 类型可用为任何对象,但是不会影响栈的各项操作( poppush )。

typedef unsigned long Item;
class Stack{
private:
    enum { MAX = 10};
    Item items[MAX];
    int top;
public:
    Stack();
    bool isempty() const;
    bool isfull() const;
    bool push(const Item& item);
    bool pop(Item& item);
}