C++必知必会 类和对象


类的成员属性

1.私有成员(private):

  • 只能被同一个类的成员函数所访问,外部函数或其它类不能访问。也就是说类的对象不能直接访问,只能通过成员函数进行访问
  • 私有成员不能被继承
  • private的作用就是保护数据

2.公共成员(public):

  • 可以被任何函数访问,包括类的成员函数、外部函数和其他类的成员函数,但是访问要是要通过类的对象来实现的
  • 提供对外的接口

3.保护成员(protected):

  • 只能被同一个类的成员函数所访问,外部函数或其它类不能访问,对象也只能通过成员函数进行访问
  • 存在意义就是有些数据不希望被外部所访问,但是希望它可以被继承到子类中

类的继承方式

1.私有继承:父类公共和保护属性到子类中全部变成私有属性
2.公共继承:父类公共和保护属性到子类中任保留原来的属性
3.保护继承:父类公共和保护属性到子类中全部变为保护属性
私有成员不能被继承

类内声明、类外初始化

通常情况下为了保持类的简洁,对成员函数进行类内声明、类外初始化

class StudentMsg()
{
private:
	int id_;
public:
	int getId();//类内声明
};
int StudentMsg::getId(){}//类外初始化

类自带的函数

只有未显示定义时,编译器才会为我们添加这些函数
1.默认构造函数
2.析构函数
3.拷贝构造函数
4.移动构造函数
4.拷贝赋值运算符重载函数
5.移动赋值运算符重载函数

class Example {
public:
    // 默认构造函数
    Example() {
        
    }

    // 析构函数
    ~Example() {
        
    }

    // 拷贝构造函数
    Example(const Example& other) {
        
    }

    // 移动构造函数,加了const会导致对象无法被修改,即资源无法被转移
    Example(Example&& other) noexcept {
        
    }
    
     // 拷贝赋值运算符
    Example& operator=(const Example& other) {
        std::cout << "Copy assignment operator called" << std::endl;
        return *this;
    }

    // 移动赋值运算符
    Example& operator=(Example&& other) noexcept {
        std::cout << "Move assignment operator called" << std::endl;
        return *this;
    }
};

int main() {
    // 创建对象
    Example obj1;

    // 拷贝构造
    Example obj2(obj1);

    // 拷贝赋值
    Example obj3;
    obj3 = obj2;

    // 移动构造
    Example obj4(std::move(obj3));

    // 移动赋值
    Example obj5;
    obj5 = std::move(obj4);

    return 0;
}

深拷贝和浅拷贝

1.浅拷贝:浅拷贝是指将一个对象的内容复制到另一个对象,但是如果有指针指向堆区内存,会出现两个指针指向同一块内存,释放时会造成堆区内容重复释放。
2.深拷贝:会在堆区重新开辟一块空间接收源对象的堆区内容,并创建新的指针指向指向这块内存,避免浅拷贝问题。
如果类中不定义深拷贝构造函数,默认的拷贝构造函数将执行浅拷贝,所以解决浅拷贝的方式就是自己实现拷贝构造函数,对堆区内存进行接收

拷贝构造和拷贝赋值详解

  • 使用场景
    当一个对象被创建并初始化的时候,编译器为它调用拷贝构造函数,当一个存在对象被里另一个对象赋值时,调用的是拷贝赋值。
拷贝构造
StudentMessage student2 = student1; // 调用拷贝构造函数
StudentMessage foo();//函数以值传递方式返回对象
void foo(StudentMessage obj);//对象以值传递的方式传给函数

拷贝赋值
StudentMessage student3(2, 22, "Jane Doe");
student3 = student1; // 调用拷贝赋值运算符
  • 使用实例
    主要是介绍一下编写规范
class StudentMEssage{
private:
    int id_;
    int *age_;
    string name_;
public:
    StudentMEssage(int id,int age,string name)
        :id_(id),age_(new int(age)),name_(name) {}
        
    //实现深拷贝
    StudentMEssage(const StudentMEssage& other)
        :id_(other.id_),age_(new int(*other.age_)),name_(other.name_) {}
    
    //实现拷贝赋值操作符
    StudentMEssage& operator=(const StudentMEssage& other){
    	//自检代码,如果是自己赋值给自己,直接返回this指针,否则会出现释放对象自身的资源,再试图复制同一个对象,这会导致错误。
        if(this == &other){
            return *this;
        }
        //释放已有资源
        delete age_;

        //深拷贝其他对象资源
        id_ = other.id_;
        age_ = new int(*other.age_);
        name_ = other.name_;

        return *this;
    }
};

运算符重载

基本语法:

返回类型 operator运算符(参数列表) {
    // 实现运算符的操作
}
示例:
// 重载 + 运算符
    MyClass operator+(const MyClass& other) {
        MyClass result;
        result.value = this->value + other.value;
        return result;
    }

重载、重写和覆盖(隐藏)

1.重载:指的是同一作用域下的同名函数,仅参数列表不同,和返回类型无关
2.重写:子类对父类虚函数的重新实现
3.覆盖:父类和子类中有同名函数时,优先调用子类中的函数,父类函数被隐藏

友元

如果希望一个类或一个函数可以访问自己的私有成员变量,那么就将其在内部声明为friend,例如

friend class B;
friend void printWidth(Box box); // 声明友元函数

菱形继承问题

一个类从两个基类进行继承,而这两个基类又同时继承于一个类,这样会造成子孙类中有两份祖宗类的数据成员,出现二义性和行为不一致的问题
解决这个问题的方法就是虚继承,确保类中只有一份虚基类的成员

class animal{};//该类被称为虚基类
class sheep:virtual public animal{};
class tuo:virtual public animal{};
class sheeptuo:public sheep, public tuo{};

但是虚继承会引入虚基类表(virtual base table)来跟踪虚基类在派生类中的偏移量,确保只有一个实例被共享。因此,虚继承会略微增加内存开销和访问成员的开销,只有考虑多继承、菱形继承的情况下再使用虚继承

纯虚函数和抽象类

纯虚函数是在虚函数后面加上=0,如virtual void func() = 0,拥有纯虚函数的类被称作抽象类,继承抽象类的子类必须重写这个纯虚函数。纯虚函数的意义在于定义一个接口标准,便于框架或模型的扩展

多态

1.静态多态(编译时)

  • 通过模板和函数重载实现
  • 编译阶段确定函数地址

2.动态多态

  • 存在继承关系,派生类重写父类的虚函数
  • 实现方式是父类指针或引用指向子类对象,并调用对应子类的函数
  • 原理:子类对象的地址被赋给了父类对象指针,虽然使用的是父类对象的指针,但是其调用的虚函数表指针(vptr)已经是子类的了,因此调用的虚函数也是子类的
  • 意义:在一些带有决策性业务代码逻辑中,运行时多态可以让程序根据用户的行为,或是执行过程的某些结果,来执行对应的函数
class Animal{
public:
    virtual void makeSound(){
        cout << "Some generic animal sound" << endl;
    }

    virtual ~Animal(){}
};

class Dog : public Animal{
public:
    void makeSound() override{
        cout<<"dog"<<endl;
    }
};

class Cat : public Animal{
public:
    void makeSound() override{
        cout<<"cat"<<endl;
    }
};

int main()
{
    vector<Animal*> animals;
    animals.push_back(new Cat());
    animals.push_back(new Dog());

    for(const auto& animal:animals){
        animal->makeSound();
    }

    for(auto& animal:animals){
        delete(animal);
    }
    system("pause");
    return 0;
}

override

overrid 关键字用于显式地表明一个成员函数重写了基类中的虚函数。这有助于编译器进行检查,确保派生类中的函数确实覆盖了基类的虚函数,并且函数的签名正确。

void makeSound() override{
 }

虚析构

1.目的:避免内存泄漏,在发生多态的时候,子类析构函数如果不是虚析构,无法完成与基类指针的动态绑定,这就意味着子类的析构函数不会被调用,子类对象的空间无法正确被释放,虚析构要定义在基类中
2.语法:virtual ~类名(){}

static在类中的作用

1.静态成员变量

  • 类内声明类外初始化
  • 编译阶段分配内存
  • 所有对象共享一个变量,有一个对象对其修改,其它对象所拥有的值也随之改变

2.静态成员函数

  • 没有this指针
  • 静态成员函数能访问的成员变量只有静态成员变量,因为它们都属于类
  • 可以直接通过类名来访问,通常我们不希望通过对象来调用的时候就定义成static,详细的可以看我这篇文章static的使用

this指针

this 指针是一个特殊的指针,它指向调用该成员函数的对象,是一个被自动传递给每个非静态成员函数的隐式参数。对于每个非静态成员函数,可以用this指针访问当前对象的地址

使用:

  • 访问成员变量和方法:在成员函数内部,当局部变量的名称与成员变量重名时,可以使用 this->成员变量名来区分和访问类的成员。

  • 实现链式调用:通过在成员函数中返回 *this,可以实现方法的链式调用。这种技术常用于各种流式接口和构建者(Builder)模式。

  • 返回自身的引用或指针:
    成员函数可以通过返回 *this 来返回当前对象的引用,或返回 this 来返回当前对象的指针。这使得成员函数可以传递当前对象的实例。

#include <iostream>

class Box {
public:
    int width, height, depth;

    // 构造函数
    Box(int width, int height, int depth) {
        this->width = width;
        this->height = height;
        this->depth = depth;
    }

    // 设置宽度并返回对象引用以支持链式调用
    Box& setWidth(int width) {
        this->width = width;
        return *this;
    }

    // 设置高度并返回对象引用以支持链式调用
    Box& setHeight(int height) {
        this->height = height;
        return *this;
    }

    // 计算体积
    int volume() const {
        return this->width * this->height * this->depth;
    }

    // 显示尺寸
    void display() const {
        std::cout << "Width: " << this->width
                  << ", Height: " << this->height
                  << ", Depth: " << this->depth << std::endl;
    }
};

int main() {
    Box box(10, 20, 30);
    box.setWidth(50).setHeight(60);  // 链式调用
    box.display();
    std::cout << "Volume: " << box.volume() << std::endl;
    return 0;
}

  • 12
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值