C++ 类与对象入门:基础知识与定义

引言:

  本来打算用一篇介绍清楚C++中的类与对象,再三考虑后觉得不妥:第一,知识点实在太多;第二,对于从刚学完C并打算过渡到C++的朋友来说,学的太深较有难度…

  总而言之,我打算用三到四篇文章去介绍,难度由易到难,希望可以帮助各位能全面且无压力的学习该内容
  本文也会不断的更新,比如加入一些图片或者动图来帮助大家理解,如有好想法希望各位能够多多批评指正

第一部分:类的基本概念

类的定义

  在C++中,类是一种用户自定义的数据类型(类似于C语言中的struct),它将数据(属性)和操作这些数据的函数(方法)封装在一起。

  类是面向对象编程的核心概念之一,它允许我们以一种结构化的方式组织代码,提高代码的可重用性可维护性

定义一个类

类的定义通常包含以下几个部分:

  1. 类名:类的名字应该具有描述性,以便其他开发者能够理解该类的用途。
  2. 成员变量:类内部的数据,也称为属性。
  3. 成员函数:类内部的函数,用于操作成员变量。

对比C语言来看,"内容"不过多了一个成员函数(但是大有讲头)

下面是一个简单的类定义示例:

#include <iostream>
using namespace std;

class Person {
    // 成员变量
    string name;
    int age;

public:
    // 成员函数
    void setName(string n) {
        name = n;
    }

    void setAge(int a) {
        age = a;
    }

    void display() {
        cout << "Name: " << name << ", Age: " << age << endl;
    }
};

int main() {
    Person p;
    p.setName("Alice");
    p.setAge(30);
    p.display();

    return 0;
}

在这个例子中:依旧是经典的Person类

  • Person 是类名。
  • nameage 是成员变量。
  • setNamesetAgedisplay 是成员函数。

访问控制

C++ 中提供了三种访问控制关键字:publicprivateprotected。这些关键字用于控制类成员的可见性和可访问性。

  1. public:公共成员,可以在类的外部直接访问。
  2. private:私有成员,只能在类的内部访问。
  3. protected:受保护成员,只能在类的内部和派生类中访问。

访问控制示例

#include <iostream>
using namespace std;

class Person {
private:
    string _name;
    int _age;

public:
    void setName(string n) {
        _name = n;
    }

    void setAge(int a) {
        _age = a;
    }

    void display() {
        cout << "Name: " << _name << ", Age: " << _age << endl;
    }
};

int main() {
    Person p;
    p.setName("李四");
    p.setAge(18);
    p.display();

    // 下面这行代码会编译错误,因为 _name 是私有的
    // p._name = "王五";

    return 0;
}

在这个例子中,_name_age 被声明为 private,因此不能在类的外部直接访问它们。只有通过 setNamesetAge 这样的公共成员函数才能修改这些私有成员。

💡tips:建议各位在声明私有变量时在前方加入符号’_’

类域

  类定义了一个作用域,类内的所有成员都在类的作用域;若在类外定义成员函数,需加入::此时将不再是内联函数(inline详细见历史文章)
  类影响的是编译的规则,如果不加person::,则默认全局查找,加入就会告知编译profession函数是person类的成员函数

void person::profession()
{
	cout << "类外定义"<<endl;
}

类的实例化

可理解为图纸 -> 实物class_type -> obj)的过程

person p1;

用person实例化出一个对象,person充当图纸,实物充当实物。

类对象的大小

在文章开头提到 C++classCstruct类似,各位也能感受出来。

class中成员变量也是需要对齐的,法则与结构体一致

各位可以注意到,class中有成员函数的存在,也需要对齐吗,如果这样每个对象都需要开出这么多空间吗?我们可以创建两个变量,然后对比两者成员函数的地址

在这里插入图片描述
显而易见,两对象的成员函数地址一致,该类的成员函数共用一块内存


在这里插入图片描述

构造函数与析构函数

构造函数和析构函数是特殊的成员函数,它们分别在对象创建和销毁时自动调用。

名称作用
构造函数用于初始化对象。构造函数的名称与类名相同,且没有返回值类型。
析构函数用于清理对象占用的资源。析构函数的名称与类名相同,但在前面加上波浪线 ~,且没有参数和返回值类型

声明

  • 在类名前加~
  • 无参数无返回值
  • 一个类只能有一个析构函数若未显式定义,则由系统默认生成
  • 对象生命周期结束时,编译器自动调用析构函数
  • 跟构造函数相似,对内置类型不做处理,自定义类型会调用其析构函数
  • 我们显示编写析构函数,自定义类型被销毁时一样会调用析构函数
~person(){
		delete[] _name;
		free(this->ps);
		ps = nullptr;
	}

如果该类有其他类的对象会怎样呢?

#include <iostream>
using namespace std;

class Test {
private:
    int socre;
public:
    Test(int score=0){}

    ~Test() {
        cout << "Test析构函数" << endl;
    }
};

class Person {
private:
    string _name;
    int _age;
    Test _t1;

public:
    void setName(string n) {
        _name = n;
    }

    void setAge(int a) {
        _age = a;
    }

    void display() {
        cout << "Name: " << _name << ", Age: " << _age << endl;
    }

    ~Person() {
        cout << "Person类析构函数" << endl;
    }
};

int main() {
    Person p;

    return 0;
}
Person类析构函数
Test析构函数

运行结果如上,在 C++ 中,对象的析构顺序与其构造顺序相反。具体来说:

构造顺序先构造 Person 对象 p。在构造 Person 对象时,会构造其成员对象 _t1
析构顺序当 Person 对象 p 的生命周期结束时,先调用 Person 类的析构函数 ~Person()。在 Person 类的析构函数中,会销毁其成员对象 _t1,因此调用 Test 类的析构函数 ~Test()。

因此,Person 类的析构函数先调用,然后才是 Test 类的析构函数。这是因为 Person 对象的整体生命周期结束后,才会销毁其成员对象。

拷贝构造函数
  • 拷贝构造函数是构造函数的一种重载

  • 拷贝构造函数的参数必须是类型对象的引用传值方式会导致无穷递归调用

  • 自定义类型对象进行拷贝时必须调用拷贝构造函数,因此传值传参和传值返回都会调用拷贝构造。

  • 若未显式定义拷贝构造函数,编译器会生成一个默认的拷贝构造函数

  • 对内置类型成员变量进行浅拷贝对自定义类型成员变量调用其拷贝构造函数

  • 如果一个类显式实现了析构函数并释放资源,那么也需要显式实现拷贝构造函数。

  • 传值返回会产生临时对象并调用拷贝构造函数,而传引用返回不会产生拷贝,但要确保返回的对象在函数结束后仍然有效,否则会返回野引用。传引用可以减少不必要的拷贝,但要确保引用的有效性。

对于第二条各位可能不太好理解,假如我们这么定义

//以下为错误示范,请勿模仿
Person(Person source) { 
    _name = source._name; 
    _age = source._age;
}
//.....
int main(){
	Person p1("张三","18");
	Person p2(p1);
}

由于是传值操作,相当于把p1拷贝了一份并且该变量也是个Person类对象,也需要借助拷贝构造进行拷贝,因此会造成无限递归,十分危险
在这里插入图片描述
正确示范

class Person {
private:
    string _name;
    int _age;

public:
    // 默认构造函数
    Person(_name = nullptr,_age = 0) :{}

    // 拷贝构造函数
    Person(const Person& other) {
    	strcpy(_name,name);
    	_age = age;
    	}

    void setName(string n) {
        _name = n;
    }

    void setAge(int a) {
        _age = a;
    }

    void display() {
        cout << "Name: " << name << ", Age: " << age << endl;
    }
};

int main() {
    Person p1;
    p1.setName("张三");
    p1.setAge(18);

    Person p2(p1);  // 调用拷贝构造函数
    p2.display();

    return 0;
}

在这个例子中,_name 是一个动态分配的字符串指针。析构函数负责释放 _name 指向的内存,防止内存泄漏。
默认析构函数不会进行delete (free) 操作,需要手动释放内存

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值