C++ 类与对象Oop

发布时间:2024年01月18日

同Java一样,C++具备这面向对象的概念。我们也可以和java对比着学习,发现他们的不同:
此处是我的java博客的链接:
我在VScode学Java类与对象(Java的类与对象、this关键字)

先了解下面的概念

过程性编程(Procedural programming)是一种编程范式,它将程序分解为一系列的步骤或过程。这些过程按照顺序执行,通常涉及函数和指令的调用。过程性编程强调程序的执行过程,以及数据和功能之间的分离。

面向对象编程(Object-oriented programming)是另一种编程范式,它将数据和功能组合成对象。对象可以包含数据(称为属性或成员变量)和操作数据的方法(称为方法或成员函数)。面向对象编程强调数据和功能的封装,继承和多态性。

一、类:用户定义的数据类型,用于封装数据和方法

类是一种用户定义的数据类型,用于封装数据和方法。
它可以包含成员变量(数据)和成员函数(方法),并且可以通过实例化创建对象。
提供了一种组织和管理代码的方式,以及实现数据抽象和封装的能力。

在面向对象编程中,类的定义通常包括类声明和类方法定义两部分。

类声明描述了类的数据部分,通常以数据成员的方式描述。同时,它也描述了类的公有接口,即类的方法或成员函数。这部分提供了类的蓝图,定义了类的结构和公有接口。

类方法定义描述了如何实现类的成员函数,即方法的具体实现细节。这部分提供了类成员函数的具体实现,包括方法的功能和操作。

#include <iostream>

using namespace std;

// Class declaration
class MyClass {
private:
    int data; // Data member

public:
    void setData(int value); // Member function declaration
    int getData(); // Member function declaration
};

// Class method definition
void MyClass::setData(int value) {
    data = value;
}

int MyClass::getData() {
    return data;
}

int main() {
    MyClass obj;
    obj.setData(42);
    cout << "Data: " << obj.getData() << endl;
    return 0;
}

1.1 对比结构体

C++中的结构体(struct)和类(class)都可以用来定义自定义数据类型。它们的主要区别在于默认的访问权限和成员函数。结构体的默认访问权限是公共的(public),而类的默认访问权限是私有的(private)。
此外,类可以包含成员函数和构造函数,而结构体不能包含成员函数,但可以包含构造函数。在实际使用中,结构体通常用于简单的数据聚合,而类用于更复杂的数据抽象和封装。

结构体示例
// 结构体示例
#include <iostream>
using namespace std;

struct Person {
    string name;
    int age=123;
};

int main() {
    Person person1;
    person1.name = "Alice";
    person1.age = 25;

    cout << "Name: " << person1.name << ", Age: " << person1.age << endl;

    return 0;
}
类示例

#include <iostream>
using namespace std;

class Person {
public:
    string name;
    int age;
    float score;
};

int main() {
    Person person1;
    person1.name = "Bob";
    person1.age = 30;
    person1.score = 89.5;
    cout << "Name: " << person1.name << ", Age: " << person1.age <<"score" <<person1.score<<endl;

    return 0;
}

警告–>主要目的:初始化

Clang-Tidy 提示 “Constructor does not initialize these fields: age” 是因为在代码中没有显式地在构造函数中初始化类的成员变量 age。而对于 name,因为它是一个 std::string 类型的成员变量,它有一个默认的构造函数,因此即使没有显式初始化,它也会被默认初始化为空字符串。

所以写成:

class Person {
public:
    string name;
    int age{};
    float score{};
};

1.2 定义类的过程并定义一个对象

1.2.1 定义类

class MyClass {
    // class members and methods go here
};

定义一个类的过程就是使用关键字class后面跟着类名,然后在大括号内定义类的成员变量和方法。明确我们需要的成员属性,在给定我们需要方法使得类更佳完整。

例子
class Cube {
private:
    int L, W, H;
public:
//    setter 和 getter
    void setL(int l) {
        L = l;
    }

    int getL() const {
        return L;
    }

    void setW(int w) {
        W = w;
    }

    int getW() const {
        return W;
    }

    void setH(int h) {
        H = h;
    }

    int getH() const {
        return H;
    }

//    表面积
    int area() const {
        return 2 * (L * W + L * H + W * H);
    }

//    体积
    int volume() const {
        return L * W * H;
    }
}

1.2.2 定义一个对象

//    在C++中
Cube c{};

// 使用大括号初始化语法对对象进行值初始化。这确保了对象的所有成员变量都被初始化为其默认值,
// 即int类型的成员变量会被初始化为0。
// 这种初始化方式在C++11标准中引入,它提供了更加一致和可靠的初始化语法,尤其是在涉及到类类型的初始化时。

//    在C++中,使用*操作符可以用来解引用指针。在你提供的代码中,
 Cube cube = *new Cube();

// 在C++中,使用*操作符可以用来解引用指针。在你提供的代码中,
// *new Cube()创建了一个新的Cube对象,并返回指向该对象的指针。
// 然后,*操作符被用来解引用这个指针,以便将指针指向的对象赋值给cube变量。
//简而言之,new Cube()创建了一个新的Cube对象,并操作符用来解引用指针,以便将指针指向的对象赋值给cube变量。

在这里插入图片描述

1.2.3 注意事项+例子

在C++中定义类时需要注意以下几点:

  1. 类的成员默认是私有的,需要使用public、protected或者private关键字来指定访问权限。
  2. 类的成员函数可以在类内部定义,也可以在类外部定义。
  3. 类的成员变量和方法可以通过对象的实例来访问。
// 2023/11/13日创建
#include <iostream>

using namespace std;

//PointClass
class Point {
private:
    // x,y坐标
    int X;
    int Y;
public:
    void setX(int x) { // 设置X坐标
        X = x;
    }

    int getX() { // 获取X坐标
        return X;
    }

    void setY(int y) { // 设置Y坐标
        Y = y;
    }

    int getY() { // 获取Y坐标
        return Y;
    }
};

//CircleClass
class Circle {
private:
    int C_R; // 圆的半径
    Point C_Center; // 圆心坐标
public:
    void setC_R(int r) { // 设置圆的半径
        C_R = r;
    }

    int getC_R() { // 获取圆的半径
        return C_R;
    }

    void setC_Center(Point p) { // 设置圆心坐标
        C_Center = p;
    }

    Point getC_Center() { // 获取圆心坐标
        return C_Center;
    }

    bool isPointInside(Point p) { // 判断点是否在圆内部
        int dx = p.getX() - C_Center.getX();
        int dy = p.getY() - C_Center.getY();
        int distanceSquared = dx * dx + dy * dy;
        int radiusSquared = C_R * C_R;
        return distanceSquared <= radiusSquared;
    }
};

int main() {
    // 创建Point对象
    Point p;
    p.setX(3); // 设置X坐标为3
    p.setY(4); // 设置Y坐标为4
    cout << "Point坐标: (" << p.getX() << ", " << p.getY() << ")" << endl;

    // 创建Circle对象
    Circle c;
    c.setC_R(5); // 设置圆的半径为5
    c.setC_Center(p); // 设置圆心坐标为Point对象p
    cout << "Circle半径: " << c.getC_R() << endl;
    cout << "Circle圆心坐标: (" << c.getC_Center().getX() << ", " << c.getC_Center().getY() << ")" << endl;

    // 判断点是否在圆内部
    Point testPoint;
    testPoint.setX(2);
    testPoint.setY(3);
    if (c.isPointInside(testPoint)) {
        cout << "点在圆内部" << endl;
    } else {
        cout << "点不在圆内部" << endl;
    }

    return 0;
}

在这里插入图片描述

1.2.4 分成头文件和源文件的方式

(0)注意事项

#ifndef和#define是C/C++中的预处理指令,用于防止头文件被多次包含。

#ifndef <标识>
#define <标识>
...
#endif

当头文件被包含到多个源文件中时,防止多次定义同一个标识符。如果标识符已经被定义过,则#ifndef和#define之间的代码会被忽略,直到#endif。

这样可以避免由于重复包含头文件而导致的重复定义错误。

(1)Point.h
// Point.h
#ifndef POINT_H
#define POINT_H

class Point {
private:
    int X;
    int Y;
public:
    void setX(int x);
    int getX();
    void setY(int y);
    int getY();
};

#endif
(2) Point.cpp
// Point.cpp
#include "Point.h"

void Point::setX(int x) {
    X = x;
}

int Point::getX() {
    return X;
}

void Point::setY(int y) {
    Y = y;
}

int Point::getY() {
    return Y;
}
(3) Circle.h
// Circle.h
#ifndef CIRCLE_H
#define CIRCLE_H
#include "Point.h"

class Circle {
private:
    int C_R;
    Point C_Center;
public:
    void setC_R(int r);
    int getC_R();
    void setC_Center(Point p);
    Point getC_Center();
    bool isPointInside(Point p);
};

#endif
(4) Circle.cpp
// Circle.cpp
#include "Circle.h"

void Circle::setC_R(int r) {
    C_R = r;
}

int Circle::getC_R() {
    return C_R;
}

void Circle::setC_Center(Point p) {
    C_Center = p;
}

Point Circle::getC_Center() {
    return C_Center;
}

bool Circle::isPointInside(Point p) {
    int dx = p.getX() - C_Center.getX();
    int dy = p.getY() - C_Center.getY();
    int distanceSquared = dx * dx + dy * dy;
    int radiusSquared = C_R * C_R;
    return distanceSquared <= radiusSquared;
}
(5)main.cpp
// main.cpp
#include <iostream>
#include "Point.h"
#include "Circle.h"

using namespace std;

int main() {
    Point p;
    p.setX(3);
    p.setY(4);
    cout << "Point coordinates: (" << p.getX() << ", " << p.getY() << ")" << endl;

    Circle c;
    c.setC_R(5);
    c.setC_Center(p);
    cout << "Circle radius: " << c.getC_R() << endl;
    cout << "Circle center coordinates: (" << c.getC_Center().getX() << ", " << c.getC_Center().getY() << ")" << endl;

    Point testPoint;
    testPoint.setX(2);
    testPoint.setY(3);
    if (c.isPointInside(testPoint)) {
        cout << "Point is inside the circle" << endl;
    } else {
        cout << "Point is not inside the circle" << endl;
    }

    return 0;
}

1.3 构造函数和析构函数

对象的初始化和清除是C++中两个非常重要的操作,它们分别对应于构造函数和析构函数。

构造函数:主要作用是在创建对象是为对象的成员赋值,构造函数由编辑器自动调用,无需手动调用

构造函数用于对象的初始化,主要作用是在创建对象时为对象的成员赋值。构造函数的名称与类名相同,没有返回类型,可以有参数。当对象被创建时,构造函数会自动调用,无需手动调用。

析构函数:主要作用是在于对象销毁前系统自动调用,执行一些清除工作

析构函数用于对象的清理,主要作用是在对象销毁前执行一些清除工作,如释放动态分配的内存、关闭文件等。析构函数的名称是在类名前加上波浪号(~),没有返回类型,不接受任何参数。当对象被销毁时(例如超出作用域、delete操作符被调用),析构函数会自动调用,无需手动调用。

如果我们自己不提供构造函数或析构函数,编译器会自动生成默认的构造函数和析构函数,这些默认的函数会执行空实现。但是在某些情况下,我们可能需要自定义构造函数和析构函数来完成特定的初始化和清理工作。

1.3.1 构造函数: 类名(){}

构造函数的特点包括:

  1. 没有返回值也不写void。
  2. 构造函数的名称与类名相同。
  3. 构造函数可以有参数,因此可以发生重载。
  4. 程序在创建对象时会自动调用构造函数,无需手动调用,且只会调用一次。

1.3.2 析构函数: ~类名(){}

析构函数的特点包括:

  1. 析构函数的名称是在类名前加上波浪号(),例如ClassName。
  2. 析构函数没有返回类型,也不接受任何参数。
  3. 析构函数在对象被销毁时自动调用,用于执行对象的清理工作,如释放资源、关闭文件等。
  4. 如果用户没有显式定义析构函数,编译器会提供一个默认的空实现的析构函数。
  5. 如果类中有动态分配的资源(如使用new关键字分配的内存),通常需要在析构函数中释放这些资源,以避免内存泄漏。
  6. 每个类只能有一个析构函数,析构函数不可以有参数,因此不可以发生重载

1.3.3 示例

class Person {
public:
//    1.构造函数
/*没有返回值也不写void、和类名相同、可以有参数,因此可以发生重载*/
    Person() {
        cout << "Person()空参构造的调用" << endl;
    }
//     2. 析构函数:主要作用是在于对象销毁前系统自动调用,执行一些清除工作
/* 没有返回值也不写void、函数名称与类名相同,在名称前加上符号~、不可以有参数,因此不可以发生重载*/
    ~Person() {
        cout << "~Person()析构构造的调用" << endl;

    }

};

//构造和析构都是必须实现的,如果不成,编译器会给一个空实现
void test() {
    Person p;
/*栈上的数据,test执行完毕后,释放该对象*/
}

int main() {
// 公共作用域下
// 2.构造函数用外部函数调用
    test();
//或者这样的内部直接用方式
//Person();


//如果写在main函数中,那么main函数执行完毕,对象就释放了
    Person s;
    system("pause");


}

在这里插入图片描述

//构造和析构都是必须实现的,如果不成,编译器会给一个空实现
void test() {
    Person p;
/*栈上的数据,test执行完毕后,释放该对象*/
}
(1)这两种方式都可以创建Person对象,但它们的作用域和生命周期是不同的。
  1. test();:在test函数中创建了一个Person对象。这个对象是test函数的局部变量,所以它的作用域仅限于test函数。当test函数执行完毕后,这个对象就会被销毁,触发析构函数。

  2. Person();:这是一个匿名对象,它在创建后会立即被销毁。因为它没有名称,所以不能在后续的代码中引用它。这种方式通常用于临时对象的创建,或者作为函数参数。

这两种方式都可以创建对象,但是根据你的需求和对象的使用方式,你可能会选择其中一种。例如,如果你需要在函数中使用一个对象,然后在函数结束时自动销毁它,你可以在函数中创建对象。如果你只需要一个临时对象,你可以创建一个匿名对象。

(2)匿名对象

Person();:这行代码创建了一个匿名对象。这个匿名对象在创建后会立即被销毁,因为它没有名称,所以不能在后续的代码中引用它。这就是匿名对象的生命周期。

Person p =Person();:这行代码首先创建了一个匿名对象,然后通过拷贝构造函数将这个匿名对象的内容复制给了p对象,然后匿名对象被销毁。这样,p对象就拥有了匿名对象的数据,而且p对象的生命周期会持续到它离开其作用域。

这两种方式都可以创建对象,但是根据你的需求和对象的使用方式,你可能会选择其中一种。例如,如果你只需要一个临时对象,你可以创建一个匿名对象。如果你需要一个持久的对象,你可以创建一个具名对象。

文章来源:https://blog.csdn.net/m0_74154295/article/details/135577119
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。