天行健 发表于 2025-1-27 13:00:39

C++基础知识07.类和对象

# 类和对象

主要学习最基本的语法

- (抽象)、封装、继承、多态
- 类的定义
- 类成员访问控制
- 对象的声明
- 类的成员函数

## 基本特点

<b>抽象</b>

抽象是指对具体问题或对象进行概括,抽出其公共性质并加以描述的过程。一般情况抽象分为数据抽象和行为抽象,其中数据抽象是指一个对象区别于另一个对象的特征,行为抽象是指某类对象的共同特征。

<b>封装</b>

封装是指将抽象得到的数据和行为相结合,隐藏对象属性和实现细节且仅对外提供公共访问方式的过程。

<b>继承</b>

继承是指通过代码复用在保持原有类特性的基础上对子类进行具体说明的过程,通俗来说继承就是精简重复代码的手段。

<b>多态</b>

多态是指一段程序能够处理多种类型对象的能力,函数重载就是实现多态的一种手段。<b>在 C++ 语言中多态可分为强制多态、重载多态、类型参数化多态和包含多态。</b>

## 类的定义

语法规则

```cpp
class 类的名称{
public:
    // 外部接口
protected:
        // 保护性成员
private:
    // 私有成员
}
```

代码示例

```cpp
#include<iostream>
using namespace std;

class area{
public:
    double width;
    double length;
    // this 是一个指向当前对象的指针,c++中指针通过 -> 访问类中的成员
    // 类的方法可以定义在内部,也可以使用 :: 来定义
    void setName(string name){
      this->name = name;
    };
    string getName(){
      return name;
    };
    double getArea(){
      return width*length;
    };
private:
    string name;
};

int main(){
    // area obj;// 定义对象。
    area *obj = new area;

    obj->width = 10;
    obj->length = 20;
    obj->setName("圆");
    cout<<obj->getName()<<endl;
    cout<<obj->getArea()<<endl;
}
```

> 符号`.和->`的作用和区别

- A.B 则 A 为对象或者结构体; 点号(.):左边必须为实体。
- A->B 则 A 为指针,-> 是成员提取,A->B 是提取 A 中的成员 B,A 只能是指向类、结构、联合的指针; 箭头(->):左边必须为指针;

## 对象

定义了类及其对象后,则可访问对象的成员。访问<b>数据成员</b>基本形式为:

```cpp
#include<iostream>
using namespace std;
// g++ -sdt=c++11 demo.cpp -o demo
class area{
public:
    // c++ 11 才支持非静态字段初始化
    // non-static data member initializers
    string name="hello";
};
// 类外面定义具体的方法。
void area::say(){
    cout<<"hello"<<endl;
};

int main(){
    area a1; // 这样定义对象使用 . 来访问
    area *a2 = new area; // 指针的话则用 -> 访问
    cout<<a1.name<<endl; // hello
    cout<<a2->name<<endl; // hello
    a2->say(); // hello
}
```

习题,计算体积

```cpp
#include<iostream>
using namespace std;

class volume{
public:
    double width=3;
    double length=4;
    double high=5.1;
    double getVolume();
};

double volume::getVolume(){
    return this->width*this->length*this->high;
};

int main(){
    volume *v = new volume;
    cout<<v->getVolume()<<endl;
}
```

## 成员函数

类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。

类的成员函数可以定义在类的内部,或者单独使用 <b>范围解析运算符::</b> 来定义。

> 定义一个 Box 类,包含 len、height 属性和 getArea 方法。

```cpp
#include<iostream>
using namespace std;

class Box{
private:
    double len;
    double height;
public:
    Box();
    Box(double len,double height);
    ~Box();
    double getArea();
};

Box::Box(){}
Box::Box(double len, double height){
    this->len = len;
    this->height = height;
}

Box::~Box(){
    cout<<"执行了析构函数"<<endl;
}

double Box::getArea(){
    return this->len*this->height;
}

int main(){
    Box b1; // 使用 . 访问成员
    Box *b2 = new Box(10.0,5.0); // 指针则是使用 -> 访问成员
    cout<<b1.getArea()<<endl; // 0,默认初始化为 0
    cout<<b2->getArea()<<endl; // 50
}
```

## 访问权限修饰符

- 使用 <b>public</b> 关键字声明的公有成员可在类外访问,即公有类型成员定义了外部接口。
- 使用 <b>protected</b> 关键字声明的受保护成员可在子类中访问。
- 使用 <b>private</b> 关键字声明的私有成员只能被本类的成员函数访问

> 演示 protected -- 注意,因为 C++ 是多继承的,super 关键字会导致语义不清楚,因此 C++ 中没有 super 关键字,访问父类成员的语法为 `父类::成员`

```cpp
#include<iostream>
using namespace std;

class area{
protected:
    string name;
};

class sonArea : area{
public:
    string getName(){
      return area::name;
    };
    void setName(string name){
      area::name = name;
    }
};

int main(){
    sonArea *obj2 = new sonArea;
    obj2->setName("sonArea");
    cout<<obj2->getName()<<endl; // sonArea
}
```

## 构造&析构

<b>构造函数</b>

类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。

构造函数的名称与类的名称是完全相同的,并且不会返回任何类型(实际上会返回创建出的对象的指针),也不会返回 void。构造函数可用于为某些成员变量设置初始值。

构造函数的作用和类型与其他语言类似,没有创建的话会编译器默认创建一个无参构造,自己定义了构造函数就不会创建默认的。

<b>析构函数</b>

在每次删除所创建的对象时执行。析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

```cpp
#include<iostream>
using namespace std;

class Program{
private:
    /* data */
    void *point;
public:
    Program();
    ~Program();
};

Program::Program(/* args */){
    cout<<"成功申请 300MB 的内存空间"<<endl;
}

Program::~Program(){
    cout<<"释放 300MB 的内存空间"<<endl;
}

int main(){
    Program *program = new Program;
    delete program;
}
```

<span style="color:red">如果程序里没有构造函数和析构函数,编译器在编译的时候会自动生成构造函数和析构函数,只是函数内没有任何操作。</span>

> 析构函数的几种调用情况

①<span style="color:red">在一个函数中定义了一个对象</span>,当函数调用结束时,对象应当被释放,对象释放之前编译器会调用析构函数释放资源。

②对于 static 修饰的对象和全局对象,只有在程序结束时编译器才会调用析构函数。

③对于 new 运算符创建的对象,在调用 delete 释放时,编译器会调用析构函数释放资源

## 拷贝构造函数

和他的名字一样,用于拷贝对象。来看一个简单的例子。

```cpp
#include<iostream>
using namespace std;

class Line{
private:
    /* data */
public:
    Line(const Line &obj);
    Line();
    int ptr = 10;
};

Line::Line(const Line &obj){
    cout<<"执行了拷贝构造函数"<<endl;
    this->ptr = 100;
    cout<<this->ptr<<endl;
}

Line::Line(){}

int main(){
    Line line;
    Line line2 = line; // 执行了拷贝构造函数

    // Line *line = new Line; // 效果和上面一样
    // Line *line2 = new Line(*line); // 执行了拷贝构造函数
    /*
    执行了拷贝构造函数
    100
    0x7fffffffdb30:0x7fffffffdb34
    10:100
    */
    cout<<&line<<":"<<&line2<<endl;
    cout<<line.ptr<<":"<<line2.ptr<<endl;
}
```

1)拷贝构造函数的形参必须是该类对象的引用,最好是 const 引用;(否则编译错误,因为会进入死循环)

2)拷贝分为浅拷贝和深拷贝,深拷贝必须重新定义拷贝构造函数。如果类中有指针类型的变量,不重写拷贝构造方法的话,就是多个对象操作的指针变量是同一个。

```cpp
#include<iostream>
using namespace std;

class Object{};

class Line{
private:
    /* data */
public:
    Line(const Line &obj);
    Line();
    Object *ptr = new Object;
};

Line::Line(const Line &obj){}
Line::Line(){}

int main(){
    Line *line = new Line;
    Line *line2 = new Line(*line); // 执行了拷贝构造函数
    // line->ptr = new Object;
    cout<<line->ptr<<":"<<line2->ptr<<endl;

    // 重写了拷贝构造函数
    // 0x555555614e90:0x555555614ed0

    // 没有重写拷贝构造函数
    // 0x555555614e90:0x555555614e90
}
```

## 友元函数

类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。

友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。

如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend,如下所示:

```cpp
#include<iostream>
using namespace std;

class Box{
private:
    double width = 20.5f;
public:
    double len;
    friend void printWidth(Box *box);
};

void printWidth(Box *box){
    cout<<box->width<<endl;
}

int main(){
    Box *box = new Box;
    // printWidth(box); // 正常访问 box 的私有成员
    // cout<<box->width<<endl; // 报错,没有访问权限
}
```

## 内敛函数

C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。

对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。

如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 `inline`,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。

在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。(确定吗?应该是有限定条件的吧?)

## 静态成员

与 Java 一样。但是 C++ 可以用静态局部变量。

```cpp
#include<iostream>
using namespace std;

void f(){
    static int a = 10;
    a++;
    cout<<a<<endl;
}

int main(){
    f(); // 11 - 没有初始化,则初始化
    f(); // 12 - 初始化了,则用之前的值,在 11 的基础上++
    f(); // 13 - 同上
}
```

# 继承

## 基本语法

继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行时间的效果。

当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。

语法:`class derived-class: access-specifier base-class`

```cpp
#include<iostream>
using namespace std;

class Shape{
public:
    void setWidth(int w){
      this->width = w;
    }
protected:
    int width;
};

// 公有继承
class Rectrangle : public Shape{
public:
    int getArea(){
      return this->width*20;
    };
};

int main(){
    Rectrangle *r = new Rectrangle;

    r->setWidth(10);
    cout<<r->getArea()<<endl;
}
```

> 继承的种类

- 公有继承:子类继承父类的所有非私有成员(所继承的成员访问权限和父类的一致),父类的私有成员可以通过父类的非私有方法访问。
- 受保护继承:将父类的所有非私有成员继承,所继承的成员访问权限变为 protected;
- 私有继承:将父类的所有非私有成员继承,所继承的成员访问权限变为 private;

<b>公有继承:可以直接访问父类的非 private 成员,继承到的成员访问权限同父类中声明的一致。</b>

```cpp
#include<iostream>

using namespace std;

class Shape {
public:
    void setWidth(int w) {
      this->width = w;
    }

protected:
    int width;
};

// Rectrangle 继承了父类 Shape 的 setWidth 方法,依旧为 public 放啊发
// 继承了父类的 width 方法,访问权限依旧为 protected
class Rectrangle : public Shape {
public:
    int getArea() {
      return this->width * 20;
    };
};

int main() {
    Rectrangle *r = new Rectrangle;
    r->setWidth(10);
    // r->width = 20;,报错,无访问权限,无法访问。
    cout << r->getArea() << endl;
}
```

<b>受保护的继承:从父类继承的 public 和 protected 成员统一降级为 protected 访问权限。</b>

```cpp
#include<iostream>

using namespace std;

class Shape {
public:
    void setWidth(int w) {
      this->width = w;
    }

protected:
    int width;
};

// 继承到了 width 和 setWidth,但是他们的访问权限降级为 protected 了
class Rectangle : protected Shape {
public:
    int getArea() {
      return this->width * 20;
    };
};

int main() {
    Rectangle *r = new Rectangle;
    // r->setWidth(10); // 无法在非本类(非子类)中访问 protected 修饰的成员。
    cout << r->getArea() << endl;
}
```

<b>私有的继承:从父类继承的 public 和 protected 成员统一降级为 private 访问权限。</b>

```cpp
#include<iostream>

using namespace std;

class Shape {
public:
    void setWidth(int w) {
      this->width = w;
    }

protected:
    int width;
};

class Rectangle : private Shape {
public:
    int getArea() {
      return this->width * 20;
    };
};

int main() {
    Rectangle *r = new Rectangle;
    cout << r->getArea() << endl;
}
```

## 访问控制权限

派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。


|   访问   | public | protected | private |
| :--------: | :------: | :---------: | :-------: |
| 同一个类 |yes|    yes    |   yes   |
|派生类|yes|    yes    |   no   |
| 外部的类 |yes|    no    |   no   |

一个派生类继承了所有的基类方法,但下列情况除外:

- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。

## 多继承

C++ 支持多继承,语法如下:

```cpp
#include<iostream>
using namespace std;

class Shape{};

class Color{}

class Rectangle : public Shape, public Color{};
```

构造函数调用顺序,按继承顺序指向构造函数,先继承的先执行。

```cpp
#include<iostream>

using namespace std;

class Shape {
public:
    Shape() {
      cout << "Shape" << endl;
    }
};

class Color {
public:
    Color() {
      cout << "Color" << endl;
    }
};

class Rectangle : public Shape, public Color {
public:
    Rectangle() {
      cout << "Rectangle" << endl;
    }
};

int main() {
    // Shape、Color、Rectangle;按继承顺序执行的构造函数
    Rectangle *r = new Rectangle;
}
```

# 重载

- 允许重载操作符,运算符的重载方式与 Python 类似。
- 允许重载函数,重载方式与 Java 类似

## 函数重载

```cpp
#include<iostream>

using namespace std;

class OverLoadFunction {
public:
    void consume(int i) { cout << "consume int" << endl; };

    void consume(double i) { cout << "consume double" << endl; };

    void consume(char i) { cout << "consume char" << endl; };
};

int main() {
    OverLoadFunction *obj = new OverLoadFunction;
    obj->consume(1);
    obj->consume(1.1);
    obj->consume('a'); // 识别为 char
}
/*
consume int
consume double
consume char
*/
```

## 操作符重载

C++ 允许重载大多数的内置运算符。例如,重载对象 + 的操作。

```cpp
#include<iostream>

using namespace std;

class OverLoadFunction {
public:
    void consume(int i) { cout << "consume int" << endl; };

    void consume(double i) { cout << "consume double" << endl; };

    void consume(char i) { cout << "consume char" << endl; };
};


class OverLoadOp {
public:
    int width = 10;

    OverLoadOp operator+(const OverLoadOp &b) {
      OverLoadOp op;
      op.width = this->width + b.width;
      return op;
    }
};

void testOverLoadFunction() {
    OverLoadFunction *obj = new OverLoadFunction;
    obj->consume(1);
    obj->consume(1.1);
    obj->consume('a'); // 识别为 char
}

int main() {
    OverLoadOp op1;
    OverLoadOp op2;
    OverLoadOp op3 = op1 + op2;
    cout << op3.width << endl; // 20
}
```

可重载的运算符


| 双目算术运算符 | + (加),-(减),*(乘),/(除),% (取模)                                 |
| :--------------- | :------------------------------------------------------------------------ |
| 关系运算符   | ==(等于),!= (不等于),< (小于),> (大于>,<=(小于等于),>=(大于等于)   |
| 逻辑运算符   | \|\|(逻辑或),&&(逻辑与),!(逻辑非)                                     |
| 单目运算符   | + (正),-(负),*(指针),&(取地址)                                       |
| 自增自减运算符 | ++(自增),--(自减)                                                      |
| 位运算符       | \| (按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移) |
| 赋值运算符   | =, +=, -=, *=, /= , % = , &=,\|=, ^=, <<=, >>=                        |
| 空间申请与释放 | new, delete, new[ ] , delete[]                                          |
| 其他运算符   | ()(函数调用),->(成员访问),,(逗号),\[\](下标)                         |

AO1199 发表于 昨天 09:37

学习学习!!!
页: [1]
查看完整版本: C++基础知识07.类和对象