C++类的原理与应用

一.C++中的类概述

1.C++中的类(class)是面向对象编程(OOP)的核心概念之一,它提供了一种封装数据和方法以实现特定功能的机制。

2.C++类的原理

(1)封装:

 类通过封装将相关的数据(成员变量)和操作这些数据的方法(成员函数)组合在一起,形成一个独立的实体。

 封装隐藏了数据的具体实现细节,只对外公开必要的接口(即成员函数),从而提高了代码的安全性和可维护性。

 C++通过访问说明符(如public、private、protected)来控制类成员的访问权限,进一步增强了封装性。

(2)继承:

 继承是面向对象编程中的一个重要特性,它允许一个类(派生类)继承另一个类(基类)的属性和方法。

 通过继承,可以实现代码的重用和扩展。派生类可以继承基类的所有public和protected成员,并可以在此基础上添加新的功能或修改已有的功能。

(3)多态:

 多态性允许在派生类对象上执行基类中定义的操作,但可以有不同的行为。

 这通常通过虚函数和动态绑定来实现。在C++中,当通过基类指针或引用来调用虚函数时,会根据对象的实际类型来调用相应的函数版本。

3.C++类的作用

(1)数据抽象:

 类通过定义成员变量和成员函数来描述一个对象的属性和行为,实现了对现实世界中的抽象概念建模。

 数据抽象使得代码更加直观、易于理解和组织。

(2)代码重用:

 通过类的继承机制,可以实现代码的重用。派生类可以继承基类的属性和方法,避免了重复编写相同的代码。

(3)模块化:

 一个类就是一个模块,它封装了相关的数据和方法。这使得我们可以很方便地将多个模块组装起来,完成目标功能。

 在软件更新升级时,只要将类对象修改为最新版本即可,无需对每个方法的调用都进行修改。

(4)提高代码的可维护性:

 封装使得代码更加模块化,降低了模块间的耦合度,提高了代码的可维护性。

 当需要修改某个类的实现时,只需在该类内部进行修改,不会影响到其他模块。

(5)提高代码的安全性:

 通过访问说明符控制类成员的访问权限,可以保护类的内部数据不被随意修改,从而提高了代码的安全性。

3.类的实现

(1)定义类

 使用关键字class来定义类,后面跟着类名(遵循C++的命名规则,如使用驼峰命名法或下划线分隔)。

 类定义中可以包含数据成员(即属性)和成员函数(即方法)。

 成员可以是公有的(public)、私有的(private)或受保护的(protected),以控制访问权限。

(2)成员函数实现

 成员函数可以在类定义时直接实现(称为内联函数),也可以在类定义外部实现。

 在类外部实现成员函数时,需要使用作用域解析运算符::来指定该函数属于哪个类。

(3)构造函数和析构函数

 构造函数是一种特殊的成员函数,用于在创建对象时初始化其成员变量。构造函数没有返回类型,且与类名相同。

 析构函数也是特殊的成员函数,用于在对象销毁时执行清理工作。析构函数以波浪号~开头,且与类名相同,没有返回类型和参数。

4.可见性

(1).private

     只能在该类的内部访问,子类也不行。只能提供类中的函数进行调用与修改,在其他地方不能使用

(2)protected

     自己与子类(派生类)可以访问

(3)public

     任何地方都可以访问。

     

5.构造函数和析构函数

  构造函数:构造函数是一种特殊的成员函数,用于创建对象时进行初始化。它与类同名,没有返回类型,并且可以有参数。当我们创建一个对象时,构造函数被自动调用,用于初始化对象的数据成员。

  

  析构函数:析构函数也是一种特殊的成员函数,用于在对象销毁时执行清理工作。它与类同名,前面加上波浪号(~),没有返回类型和参数。当对象超出作用域、程序结束或者使用delete操作符释放动态分配的对象时,析构函数被自动调用。

     

二.C++中类的基本应用

  以下是一个简单的C++类示例,演示了类的定义、成员函数的实现以及对象的创建和使用:

#include <iostream>  

using namespace std;

class Point {

public:

Point(int x = 0, int y = 0) : xPos(x), yPos(y) { cout << "x = " << xPos << ", y = " << yPos << endl; } // 构造函数  

void setPoint(int x, int y) { xPos = x; yPos = y; } // 设置点坐标  

void printPoint() const { cout << "x = " << xPos << ", y = " << yPos << endl; } // 打印点坐标  

int xPointOffset(int x);

~Point(); // 析构函数声明  

private:

int xPos, yPos; // 点的坐标  

};

Point::~Point() {

cout << "Point 析构函数被调用" << endl;

// 清理代码...  

}

int Point::xPointOffset(int x)

{

xPos = x + 10;

return x + 10;

}

int main() {

Point p1; // 创建Point对象p1,使用默认构造函数  

p1.setPoint(10, 20); // 设置p1的坐标  

p1.printPoint(); // 打印p1的坐标  

Point p2(30, 40); // 创建Point对象p2,并初始化坐标  

p2.xPointOffset(60);

p2.printPoint(); // 打印p2的坐标  

return 0;

}

输出:

x = 0, y = 0

x = 10, y = 20

x = 30, y = 40

x = 70, y = 40

Point 析构函数被调用   //p1释放

Point 析构函数被调用   //p2释放

三.虚函数 virtual及应用

1.用于实现多态,子类可以重写父类的虚函数。

(1)原理:利用了动态联编,通过虚函数表来在运行时确定调用的函数。

对于非虚函数,在编译时就确定要调用哪个函数,因此调用非函数

(2)虚函数表:

当一个类包含虚函数时,会生成虚函数表,保存虚函数的地址。其派生类也会有虚函数表

创建类实例时,若有虚函数,则会生成虚函数指针,指向其虚函数表。

当一个基类的指针指向了 子类对象,则运行时,会使用子类的虚函数表指针寻找子类虚函数的地址,而不是基类的。

(3)编译器创建虚函数表的过程:

拷贝每个基类的虚函数表。

查看子类是否重写了基类的虚函数,有则修改子类的虚函数表;查看子类是否增加了虚函数,有则加入虚函数表

(4)虚函数的缺点:

增大内存的消耗

每次调用虚函数时,要遍历虚函数表

2.虚函数使用情况:

有一个基类与一个子类

class Base(){

void Print1(){// 非虚函数

cout<<"Base"<<endl;

}

virtual void Print2(){

cout<<"Base"<<endl;

}

};

class Son():public Base{

void Print1(){

cout<<"Son"<<endl;

}

void Print2() override{// 重写虚函数,加上override关键字,非必要,但可以检测错误

cout<<"Son"<<endl;

}

};

(1).基类指针指向子类,且调用非虚函数:会调用基类的函数,而不是子类的函数

int main(){

Son* s=new Son();

Base* base=s;// 基类指针指向

base->Print1();// 输出“Base”,非虚函数

}

(2).基类指针指向子类,且调用虚函数:会根据虚函数表调用子类函数,

int main(){

Son* s=new Son();

Base* base=s;// 基类指针指向

base->Print2();// 输出“Son”,虚函数

}

3.纯虚函数(接口)

基类的虚函数只是一个声明,并没有实现。子类必须实现该虚函数。

使用场景:可以用于制作模板,规定子类都必须实现某些函数

有纯虚函数的子类,不能实例化。

class Base(){

virtual void Print()=0;// 纯虚函数

};

class Son1():public Base{

void Print() override{// 实现纯虚函数,加上override关键字,非必要,但可以检测错误

cout<<"Son1"<<endl;

}

};

class Son2():public Son2{

void Print() override{// 由于基类实现了纯虚函数,所以该类可以不重写Print()

cout<<"Son2"<<endl;

}

};

void PrintName(Base* b){

b->Print();

}

int main(){

Base* base=new Base();// 会报错,不能实例化

Son1* son1=new Son1();

Son2* son2=new Son2();

PrintName(son1);// 输出“Son1”

PrintName(son2);// 输出“Son2”

}

四.静态成员函数

1.静态成员函数的特点:

不依赖于对象实例:

静态成员函数只能访问静态成员变量或其他静态成员函数;不能访问类的非静态成员(包括非静态成员变量和非静态成员函数),因为它们不依赖于任何特定的对象实例。如果静态成员函数需要访问非静态成员,它必须接收一个指向该类实例的指针或引用作为参数。

属于类:

静态成员函数是类的一部分,而不是任何特定对象的一部分。这意味着,无论创建多少个类的实例,静态成员函数都只有一份拷贝,并且这些拷贝在所有的实例之间是共享的。因此,它们不能使用this指针(在C++中)或隐式的实例引用(在某些其他面向对象的语言中),因为this指针或隐式实例引用指向的是类的实例,而静态成员函数不依赖于任何实例。

通过类名调用:

静态成员函数通常通过类名加函数名的方式调用,而不是通过类的实例调用。这有助于区分静态成员函数和非静态成员函数。

用途:静态成员函数常用于实现那些与类本身相关的功能,而不是与类的特定实例相关的功能。例如,它们可以用于实现工具函数、工厂方法或执行仅依赖于输入参数而不需要类实例状态的操作。

2.代码示例

#include <iostream>  

using namespace std;  

  

class MyClass {  

public:  

    static void staticFunction() {  

        cout << "这是一个静态成员函数" << endl;  

    }  

  

    void nonStaticFunction() {  

        cout << "这是一个非静态成员函数" << endl;  

}  

private:

int m_age; // 年龄

// 在类内部定义静态成员

static int number;

};  

// 在类外部初始化静态成员变量  

int Student::number = 1;

  

int main() {  

    // 通过类名调用静态成员函数  

    MyClass::staticFunction();  // 输出: 这是一个静态成员函数  

  

    // 尝试通过实例调用静态成员函数(虽然技术上可行,但通常不推荐这样做)  

    MyClass obj;  

    obj.staticFunction();  // 输出: 这是一个静态成员函数  

  

    // 调用非静态成员函数,必须通过实例  

    obj.nonStaticFunction();  // 输出: 这是一个非静态成员函数  

  

    return 0;  

}

.this指针的用途

   访问对象的成员:当成员函数需要访问调用它的对象的成员时,可以使用this指针。尽管在大多数情况下,你可以直接访问成员而不需要显式地使用this指针,但在某些情况下(如参数名与成员名冲突时),使用this指针可以明确指定你正在访问的是对象的成员。

   

   返回对象本身:在某些情况下,成员函数可能需要返回调用它的对象本身。这时,可以使用return *this;来实现链式调用(也称为级联调用)。

   

   作为函数参数:虽然不常见,但在某些特殊情况下,你可能需要将this指针作为参数传递给其他函数,以便这些函数能够访问调用对象的成员。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI+程序员在路上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值