C++笔记整理

    • 类和对象

类:类是对同一类对象的抽象总结,是一个概念

对象:按照类的规定创建的实体

程序员就是面向对象编程世界中的“上帝”,因此需要先写类的代码,才能按照这段代码创建对应的对象。参考类来创建对象的过程被称为“实例化”。因此,对象无法脱离对应的类存在。

一个类主要包括:

  • 属性

用来描述对象的数据元素,通常是一个名词变量,例如:身高、体重、价格等,也称为“成员变量”或“数据成员”。

  • 行为

用来描述对象执行的具体操作,通常对属性进行操作,以动词函数的方式存在,例如:吃饭、睡觉、运行等,也称为“成员函数”或“成员方法”。

成员变量和成员函数统称为“成员”。

类是一个抽象的概念,因此需要按照这个概念创建对应的对象实体,C++中有两种类型的对象:

  • 栈内存对象

在生命周期结束(所在的花括号执行完)后,自动被销毁。

栈内存对象使用 . 调用成员。

  • 堆内存对象

需要使用new关键字创建,使用delete关键字销毁,如果不销毁,则会持续存在,容易导致内存泄漏的问题,内存泄漏最终可能会导致程序卡顿,甚至卡死。

堆内存对象通常使用指针来保存堆内存对象的地址。

堆内存对象使用 -> 调用成员,在Qt Creator下,直接打.会转换成 ->

    • C语言和C++异同点

C++是一门面向对象的编程语言,注重对象;C语言是面向过程的一门语言,注重过程;C++是以“谁来解决问题为核心”,C语言是以“怎么解决问题为核心”。

    • 封装

在C++中为保证数据安全性可在类中封装函数,设置特定接口去访问数据。

封装的写法并不唯一,在实际的过程中会结合业务需求而定,下面的以一个最基础的案例进行编写。通常先对属性私有化,使属性隐藏,然后根据当前属性的需求,通过getter函数和setter函数对类外分别公开读和写的功能。

#include <iostream>

using namespace std;

class MobilePhone
{
private: // 私有权限:只有类内部可访问
    string brand; // 可读可写
    string model; // 只写
    int weight = 188; // 初始值

public: // 公开接口
    string get_brand() // getter:读属性
    {
        return brand;
    }

    void set_brand(string b) // setter:写属性
    {
        brand = b;
    }

    void set_model(string m) // setter
    {
        model = m;
    }

    int get_weight() // getter
    {
        return weight;
    }

};


int main()
{
    MobilePhone mp1;
    mp1.set_brand("小米");
    mp1.set_model("13 Pro");
    cout << mp1.get_brand() << " " << mp1.get_weight() << endl;

    MobilePhone* mp2 = new MobilePhone;
    mp2->set_brand("红米");
    mp2->set_model("K60 Pro");
    cout << mp2->get_brand() << " " << mp2->get_weight() << endl;

    delete mp2;

    return 0;
}
    • 构造函数

4.1 概念

构造函数类内一种特殊的函数,用来创建一个对象。如果一个类中,程序员不手动编写构造函数,编译器会为这个类自动添加一个无参构造函数,且此函数的函数体为空;如果程序员手写了任意一个构造函数,编译器就不再自动添加构造函数了。

构造函数要求函数名必须与类名完全一致,且构造函数无需写返回值。

#include <iostream>
 
using namespace std;
 
class MobilePhone
{
private:
    string brand;
    string model;
    int weight;
 
public:
//    MobilePhone(){} // 编译器自动添加的无参构造函数
    MobilePhone() // 手写构造函数
    {
        // 可以设定属性的默认值
        brand = "山寨";
        model = "8848钛金手机";
        weight = 188;
        cout << "生产了一部手机" << endl;
    }
 
    string get_brand()
    {
        return brand;
    }
 
    void set_brand(string b)
    {
        brand = b;
    }
 
    void set_model(string m)
    {
        model = m;
    }
 
    int get_weight()
    {
        return weight;
    }
 
};
 
 
int main()
{
    MobilePhone mp1; // 调用了无参构造函数
    mp1.set_brand("小米");
    mp1.set_model("13 Pro");
    cout << mp1.get_brand() << " " << mp1.get_weight() << endl;
 
    MobilePhone* mp2 = new MobilePhone; // 调用了无参构造函数
    mp2->set_brand("红米");
    mp2->set_model("K60 Pro");
    cout << mp2->get_brand() << " " << mp2->get_weight() << endl;
 
    delete mp2;
 
    return 0;
}

4.2 传参

可以给构造函数增加参数,使用参数给属性赋予初始值,使对象的创建更灵活。

#include <iostream>
 
using namespace std;
 
class MobilePhone
{
private:
    string brand;
    string model;
    int weight;
 
public:
    MobilePhone(string b,string m,int w)
    {
        brand = b;
        model = m;
        weight = w;
    }
 
    string get_brand()
    {
        return brand;
    }
 
    void set_brand(string b)
    {
        brand = b;
    }
 
    void set_model(string m)
    {
        model = m;
    }
 
    int get_weight()
    {
        return weight;
    }
 
};
 
 
int main()
{
    MobilePhone mp1("小米","13Pro",190); // 调用了三参构造函数
    cout << mp1.get_brand() << " " << mp1.get_weight() << endl;
 
    MobilePhone* mp2 = new MobilePhone("红米","K60Pro",199); // 调用了三参构造函数
    cout << mp2->get_brand() << " " << mp2->get_weight() << endl;
 
    delete mp2;
 
    return 0;
}

4.3 重载

构造函数也支持函数重载,遵守之前函数重载的规则。

#include <iostream>
 
using namespace std;
 
class MobilePhone
{
private:
    string brand;
    string model;
    int weight;
 
public:
    MobilePhone() // 重载
    {
        brand = "8848";
        model = "钛金手机";
        weight = 199;
    }
 
    MobilePhone(string b,string m,int w) // 重载
    {
        brand = b;
        model = m;
        weight = w;
    }
 
    string get_brand()
    {
        return brand;
    }
 
    void set_brand(string b)
    {
        brand = b;
    }
 
    void set_model(string m)
    {
        model = m;
    }
 
    int get_weight()
    {
        return weight;
    }
 
};
 
 
int main()
{
    MobilePhone mp1("小米","13Pro",190); // 调用了三参构造函数
    MobilePhone* mp2 = new MobilePhone("红米","K60Pro",199); // 调用了三参构造函数
    delete mp2;
    MobilePhone mp3; // 调用无参构造
    MobilePhone* mp4 = new MobilePhone; // 调用无参构造
    delete mp4;
 
    return 0;
}

4.4 参数默认值

构造函数也支持之前的参数默认值设定。

#include <iostream>
 
using namespace std;
 
class MobilePhone
{
private:
    string brand;
    string model;
    int weight;
 
public:
    MobilePhone(string b = "8848",
                string m="钛金手机",
                int w = 199) 
    {
        brand = b;
        model = m;
        weight = w;
    }
 
    string get_brand()
    {
        return brand;
    }
 
    void set_brand(string b)
    {
        brand = b;
    }
 
    void set_model(string m)
    {
        model = m;
    }
 
    int get_weight()
    {
        return weight;
    }
 
};
 
 
int main()
{
    MobilePhone mp1("小米","13Pro",190); // 调用了三参构造函数
    MobilePhone* mp2 = new MobilePhone("红米","K60Pro",199); // 调用了三参构造函数
    delete mp2;
    MobilePhone mp3; // 调用无参构造
    MobilePhone* mp4 = new MobilePhone; // 调用无参构造
    delete mp4;
 
    return 0;
}

4.5 构造初始化列表

构造初始化列表是一种简便的写法,可以用于给属性赋予初始值。

#include <iostream>
 
using namespace std;
 
class MobilePhone
{
private:
    string brand;
    string model;
    int weight;
 
public:
    MobilePhone(string b = "8848",
                string m="钛金手机",
                int w = 199):brand(b),model(m),weight(w)
    {}
 
   void show() // 展示所有属性值
   {
       cout << brand << " " << model << " "
            << weight << endl;
   }
};
 
 
int main()
{
    MobilePhone mp1("小米","13Pro",190);
    mp1.show();
 
    return 0;
}
在现有阶段,构造初始化列表可以根据实际需求自行决定是否采用。

4.6 拷贝构造函数

4.6.1 概念

如果程序员在一个类中不手动编写拷贝构造函数,编译器会为这个类自动添加一个拷贝构造函数,拷贝构造函数与普通构造函数也是函数重载的关系。

对象和对象之间是独立存在的实体,数据也是相互独立的,拷贝构造函数可以把一个对象的属性值拷贝到新创建的对象中。

#include <iostream>
 
using namespace std;
 
class MobilePhone
{
private:
    string brand;
    string model;
    int weight;
 
public:
    MobilePhone(string b,string m,int w)
        :brand(b),model(m),weight(w)
    {}
 
    // 编译器自动添加的拷贝构造函数
//    MobilePhone(const MobilePhone& mp)
//    {
//        brand = mp.brand;
//        model = mp.model;
//        weight = mp.weight;
//    }
 
    void show()
    {
        cout << brand << " " << model << " "
             << weight << endl;
        cout << &brand << " " << &model << " "
             << &weight << endl;
    }
};
 
 
int main()
{
    MobilePhone mp1("小米","13Pro",190);
    mp1.show();
    // 拷贝构造函数
    MobilePhone mp2(mp1);
    mp2.show();
 
    return 0;
}

4.6.2 浅拷贝与深拷贝

当类中的成员变量出现指针,默认的浅拷贝,代码如下:

#include <iostream>
#include <string.h>
 
using namespace std;
 
class Dog
{
private:
    char* name; // 特例
 
public:
    Dog(char* n)
    {
        name = n;
    }
 
    void show_name()
    {
        cout << name << endl;
    }
};
 
 
int main()
{
    char c[20] = "wangcai";
 
    Dog d1(c); // d1.name → c
    Dog d2(d1); // d2.name → c
 
    strcpy(c,"tiedan");
 
    d1.show_name(); // tiedan
    d2.show_name(); // tiedan
 
    return 0;
}

此时需要手写构造函数,为每个对象的name属性单独开辟一个内存区域,且当前对象独享这个区域。

#include <iostream>
#include <string.h>
 
using namespace std;
 
class Dog
{
private:
    char* name; // 特例
 
public:
    Dog(char* n)
    {
        name = new char[20];
        // 只拷贝内容
        strcpy(name,n);
    }
 
    Dog(const Dog& d)
    {
        name = new char[20];
        // 只拷贝内容
        strcpy(name,d.name);
    }
 
    void show_name()
    {
        cout << name << endl;
    }
};
 
 
int main()
{
    char c[20] = "wangcai";
 
    Dog d1(c); // d1.name → 堆区1
    Dog d2(d1); // d2.name → 堆区2
 
    strcpy(c,"tiedan");
 
    d1.show_name(); // wangcai
    d2.show_name(); // wangcai
 
    return 0;
}

其实在实际开发中,也可以采用屏蔽拷贝构造函数这种简单粗暴的方式解决浅拷贝问题。屏蔽某个构造函数只需要把这个构造函数的代码从public区域移动到private区域。

5. 析构函数

析构函数是与构造函数对立的函数。

构造函数

析构函数

手动调用

在对象被销毁时自动调用

通常用于在对象创建时初始化

通常用于在对象销毁时回收资源

可以被重载

不可以重载,因为没有参数

函数名是类名

函数名是~类名

#include <iostream>
 
using namespace std;
 
class Cat
{
public:
    ~Cat()
    {
        cout << "猫挂了" << endl;
    }
};
 
 
int main()
{
    cout << "主函数开始" << endl;
 
//    Cat c1;
 
    Cat* c2 = new Cat;
    delete c2;
 
    cout << "主函数结束" << endl;
    return 0;
}

5.6.2节的深拷贝代码中,两个构造函数中都new出了name属性对应的堆内存空间,但是并没有回收,因此需要给Dog类增加析构函数,在析构函数中回收name占用的堆内存空间。

#include <iostream>
#include <string.h>
 
using namespace std;
 
class Dog
{
private:
    char* name; // 特例
 
public:
    Dog(char* n)
    {
        name = new char[20];
        // 只拷贝内容
        strcpy(name,n);
    }
 
    Dog(const Dog& d)
    {
        name = new char[20];
        // 只拷贝内容
        strcpy(name,d.name);
    }
 
    // 析构函数
    ~Dog()
    {
        delete name;
    }
 
    void show_name()
    {
        cout << name << endl;
    }
};
 
 
int main()
{
    char c[20] = "wangcai";
 
    Dog d1(c); // d1.name → 堆区1
    Dog d2(d1); // d2.name → 堆区2
 
    strcpy(c,"tiedan");
 
    d1.show_name(); // wangcai
    d2.show_name(); // wangcai
 
    return 0;
}

6 类内声明,类外定义

对于类中的成员也可以声明定义分离,如果声明定义分离,通常在类内声明,在类外定义,类外的定义需要结合作用域限定符使用。

#include <iostream>
 
using namespace std;
 
class Test
{
private:
    string str = "随便";
 
public:
    // 类内声明
    Test(); // 无参构造
    string get_str(); // getter
};
 
// 类外定义
string Test::get_str()
{
    return str; // 也属于类内
}
 
Test::Test()
{
    cout << "构造函数" << endl;
}
 
int main()
{
    Test t;
    cout << t.get_str() << endl;
 
    return 0;
}

7. this指针

7.1概念

this指针是一个特殊的指针,保存的是当前类的对象首地址。

#include <iostream>
 
using namespace std;
 
class Test
{
public:
    void test_this()
    {
        cout << this << endl;
    }
};
 
int main()
{
    Test t1;
    cout << &t1 << endl; // 0x61fe8b
    t1.test_this(); // 0x61fe8b
 
    Test* t2 = new Test;
    cout << t2 << endl; // 0xae2350
    t2->test_this(); // 0xae2350
    delete t2;
 
    return 0;
}

实际上可以通过下面的方法判断this的指向:

this所在的函数是哪个对象的,this指向的就是这个对象。

7.2 原理

在类内调用此类的成员,虽然不用手写this指针,但是编译器都会使用this指针来调用成员,因为成员只能由对象来调用,而this指针指向的就是当前类的对象。

#include <iostream>
 
using namespace std;
 
class Student
{
private:
    string name;
 
public:
    Student(string s)
    {
        this->name = s;
    }
 
    string get_name()
    {
        return this->name;
    }
 
    void show()
    {
        cout << this->get_name() << endl;
    }
};
 
int main()
{
    Student s("张三");
    s.show(); // 张三
 
    return 0;
}

7.3 应用

利用this指针的原理,其应用有:

  • 区分重名的成员变量与局部变量

  • 链式调用

  • 多态传参

7.3.1 区分重名变量

当成员变量与局部变量重名时,可以使用this指针调用成员变量。

#include <iostream>
 
using namespace std;
 
class Student
{
private:
    string name;
 
public:
    Student(string name)
    {
        this->name = name;
    }
 
    string get_name()
    {
        return name;
    }
};
 
int main()
{
    Student s("张三");
    cout << s.get_name() << endl;
 
    return 0;
}

7.3.2 链式调用

如果一个函数的返回值是当前类的引用,那么通常此函数需要返回一个*this,并且此函数支持链式调用。

#include <iostream>
 
using namespace std;
 
class Value
{
private:
    int data = 0;
 
public:
    int get_data()
    {
        return data;
    }
 
    /**
     * @brief add 加法
     * @param i 增加的数值
     * @return
     */
    Value& add(int i)
    {
        data += i;
        return *this; // *this表示的是当前类的对象本身
    }
};
 
int main()
{
    Value v;
    //  链式调用  0+1+2+3+4+5
    v.add(1).add(2).add(3).add(4).add(5);
    cout << v.get_data() << endl; // 15
    // 相当于
    v.add(1);
    v.add(2);
    v.add(3);
    v.add(4);
    v.add(5);
    cout << v.get_data() << endl; // 30
 
    return 0;
}

8. static关键字(掌握)

static关键字在类内有以下几种用法:

  • 静态局部变量

  • 静态成员变量

  • 静态成员函数

8.1 静态局部变量

使用static关键字修饰局部变量就是静态局部变量。

#include <iostream>
 
using namespace std;
 
class Test
{
public:
    void test_static()
    {
        int a = 1;
        // 静态局部变量
        static int b = 1;
        cout << ++a << " " << ++b << endl;
        cout << &a << " " << &b << endl;
    }
};
 
int main()
{
    Test t1;
    t1.test_static();
    cout << endl;
    t1.test_static();
 
    return 0;
}

运行结果如下:

虽然上面的结果中,变量a和变量b的地址始终都没有变化,但是变量a在这个地址上创建了两次,而变量b只在此函数第一次调用时创建了一次。

实际上,此类中所有的对象都共享一个静态变量,把上面的代码主函数更改为:

int main()
{
    Test t1;
    t1.test_static();
 
    cout << endl;
 
    Test t2;
    t2.test_static();
 
    return 0;
}

运行结果不变。

静态局部变量所在的函数第一次被调用时,静态局部变量创建,在程序结束时才销毁。

8.2 静态成员变量

成员变量使用static修饰就是静态成员变量,静态成员变量具有以下特点:

  • 此类的所有对象共用此变量

  • 非const的静态成员变量通常需要类内声明,类外初始化

  • 静态成员变量可以直接使用类名::来调用,更推荐使用此方式

  • 静态成员变量在程序运行时创建,在程序结束时销毁

#include <iostream>
 
using namespace std;
 
class Test
{
public:
    static int a; // 类内声明
};
 
int Test::a = 1; // 类外初始化
 
int main()
{
    cout << Test::a << " " << &Test::a << endl; // 1 0x408004
 
    Test t1;
    cout << ++t1.a << " " << &t1.a << endl; // 2 0x408004
 
    Test t2;
    cout << t2.a << " " << &t2.a << endl; // 2 0x408004
 
    return 0;
}

8.3 静态成员函数

成员函数使用static修饰就是静态成员函数,静态成员函数的特点有:

  • 静态成员函数不能访问此类中非静态成员,因为没有this指针

  • 静态成员函数只能调用本类中静态的成员

  • 非静态成员函数可以调用静态成员

  • 除了可以使用当前类对象调用静态成员函数外,也可以直接使用类名::调用,推荐后者

  • 如果静态成员函数声明与定义分离,只需要在声明处使用static修饰

9. const关键字(掌握)

在C++中,虽然认为const表示常量的意思,但是严格地讲,const并不是常量。因为C++中const只能在程序的运行期间只读,即在编译期可以改变数值。

#include <iostream>

using namespace std;

class Test
{
public:
    static int a;

    void method()
    {
        cout << "非静态成员" << endl;
    }

    static void func()
    {
//        method(); 错误
        cout << a << endl;
    }

    static void func2(); // 声明
};

int Test::a = 1;

void Test::func2()
{
    cout << "静态成员函数" << endl;
}

int main()
{
    Test::func();
    Test::func2();

    Test t;
    t.func();

    return 0;
}

9.1 常成员函数

const修饰的成员函数,表示常成员函数,这种函数的特点是:

  • 可以调用本类中非const的成员变量,但是不能修改其数值

  • 不能调用非const的成员函数

建议成员函数只要不修改成员变量值就写为常成员函数,例如getter

#include <iostream>

using namespace std;

class Test
{
private:
    int a = 1;

public:
    void set_a(int a)
    {
        this->a = a;
    }

    int get_a() const
    {
        return a;
    }

    void func() const // 常成员函数
    {
//        a++; 错误
        cout << a << endl;
//        set_a(6); 错误
        cout << get_a() << endl;
    }

};


int main()
{
    Test t;
    t.func();

    return 0;
}

9.2 常量对象

const修饰对象,表示该对象为常量对象,其特点有:

  • 常量对象的任何属性值不能被修改

  • 常量对象不能调用任何非const的成员函数

const修饰对象时,const关键字可以写在类名前面,也可以类名后面。

#include <iostream>

using namespace std;

class Test
{
public:
    int a = 1;

    void set_a(int a)
    {
        this->a = a;
    }

    int get_a() const
    {
        return a;
    }
};


int main()
{
    // 常量对象
    const Test t1;
    Test const t2;

    cout << t1.a << endl; // 1
//    cout << ++t2.a << endl; 错误
    cout << t1.get_a() << endl; // 1
//    t2.set_a(2); 错误

    return 0;
}

9.3 常成员变量

使用const修饰成员变量,表示该成员变量为常成员变量,其特点有:

  • 程序运行时,常成员变量的值不可变

  • 不能在函数体中赋值,只能通过直接赋值或构造初始化列表赋值

#include <iostream>

using namespace std;

class Test
{
public:
    const int a = 1; // 直接赋予初始值
    const int b;
    const int c = 3;

    Test(int b):b(b){} // 构造初始化列表赋值
    Test(int b,int c):b(b),c(c){}
};


int main()
{
    Test t(2);
    cout << t.a << endl; // 1
//    t.a++; 错误
    cout << t.b << endl; // 2
//    t.b++; 错误

    Test t2(4,4);
    cout << t2.c << endl; // 4

    return 0;
}

9.4 修饰局部变量

类似于之前给引用参数增加const修饰,函数的局部变量都可以使用const修饰,表示常量。

#include <iostream>

using namespace std;

class Test
{
public:
    void func(const int a)
    {
        cout << a << endl;
//        a++; 错误
        const int i = 1;
        cout << i << endl;
//        i++; 错误
    }
};


int main()
{
    Test t;
    t.func(2);

    return 0;
}

四、运算符重载

1. 友元

1.1 概念(掌握)

类实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,仅能通过类的成员函数才能读写。如果数据成员定义为公共的,则又破坏了封装性。但是某些情况下,需要频繁读写类的数据成员,特别是在对某些成员函数多次调用时,由于参数传递、类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。

友元是一种定义在类外部的普通函数,但他需要在类内进行说明,为了和该类的成员函数加以区别,在说明时前面加以关键字friend

学习友元最终的目的是把友元运用在运算符重载上,其它情况下不要使用友元,因为会破坏面向对象的特性。

友元主要分为以下几种使用情况:

  • 友元函数

  • 友元类

  • 友元成员函数

1.2 友元函数(重点)

友元函数是一种类外的函数,但是需要在类内结合friend关键字进行说明(非声明),需要注意以下几点:

  • 因为友元函数不是类内的函数,因此没有this指针,因此需要给友元函数增加一个传递对象的参数位,用此对象来调用类中的成员。

  • 友元函数不属于类,因此不受类中权限修饰符的影响,即友元函数的说明可以放在类中的任意位置。

  • 一个友元函数可以访问多个类的成员,只需要在多个类中分别说明。

#include <iostream>

using namespace std;

class Test
{
private:
    int a = 1;

    // 友元函数的说明,放在类中的任意位置都可以
    friend void test_friend(Test& t);
};

// 友元函数在类外
void test_friend(Test& t)
{
    t.a++;
    cout << t.a << endl;
}

// 不是友元函数
void test(Test& t)
{
//    t.a++; 错误
//    cout << t.a << endl; 错误
}

int main()
{
    Test t;
    test_friend(t); // 2

    return 0;
}

1.3 友元类(熟悉)

当一个类B成为了另一个类A的“朋友”时,类A的所有成员就可以被类B访问,此时类B是类A的友元类。

#include <iostream>

using namespace std;

class A
{
private:
    int i = 1;

    // 进行友元类的说明
    friend class B;
};

class B
{
public:
    void func(A& a) // func函数中的this指针是B对象
    {
        a.i++;
        cout << a.i << endl;
    }
};

int main()
{
    A a;
    B b;
    b.func(a); // 2

    return 0;
}

需要注意的是:

  • 友元关系是单向的,不具有交换性。

  • 友元关系不具有传递性。

  • 友元关系不能被继承。

1.4 友元成员函数(熟悉)

可以使类B中的某一个成员函数成为类A的友元成员函数,这样类B中只有这个成员函数可以访问类A的所有成员。

#include <iostream>

using namespace std;

// 第三步
class A;

// 第二步
class B
{
public:
    void func(A& a); // 先只声明
};

// 第一步
class A
{
private:
    int i = 1;

    // 说明友元成员函数
    friend void B::func(A& a);
};

// 第四步,补齐友元函数的定义
void B::func(A &a)
{
    a.i++;
    cout << a.i << endl;
}

int main()
{
    A a;
    B b;
    b.func(a); // 2

    return 0;
}

2. 运算符重载(重点)

2.1 概念

函数可以重载,运算符也是一种特殊的函数,因此运算符也可以重载。

函数的组成部分:

  • 名称

  • 输入参数

  • 函数体

  • 返回值

上述的每个组成部分运算符都拥有。

C++中的运算符默认的操作类型只支持基本数据类型,例如+支持整型浮点型等类型的运算,但是对于很多用户自定义的类型(Dog类、Cat类、Test类等)的对象也需要支持运算符,例如 狗+狗。此时可以在代码中重载运算符,赋予这些运算符处理新的类型的功能。

可以被重载的运算符:

算术运算符:+、-、*、/、%、++、--

位操作运算符:&、|、~、^(位异或)、<<(左移)、>>(右移)

逻辑运算符:!、&&、||

比较运算符:<、>、>=、<=、==、!=

赋值运算符:=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=

其他运算符:[]、()、->、,、new、delete、new[]、delete[]

不被重载的运算符:

成员运算符 .、指针运算符 *、三目运算符 ? :、sizeof、作用域 ::

2.2 友元函数运算符重载

可以使用友元函数进行运算符重载。

#include <iostream>

using namespace std;

/**
 * @brief The Integer class
 * 整型类
 */
class Integer
{
private:
    int value;

public:
    Integer(int value):value(value){}

    int get_value() const
    {
        return value;
    }

    // 友元函数的说明
    friend Integer operator +(const Integer& i1,
                              const Integer& i2); // 双目
    friend Integer operator ++(Integer& i); // 单目,前置
    friend Integer operator ++(Integer& i,int); // 单目,后置
};

Integer operator +(const Integer& i1,
                   const Integer& i2)
{
    return i1.value+i2.value;
}

Integer operator ++(Integer& i)
{
    return ++i.value;
}

Integer operator ++(Integer& i,int)
{
    return i.value++;
}

int main()
{
    Integer i1(1);
    Integer i2(2);
    Integer i3 = i1+i2;
    cout << i3.get_value() << endl; // 3

    cout << (i1++).get_value() << endl; // 1
    cout << i1.get_value() << endl; // 2
    cout << (++i2).get_value() << endl; // 3

    return 0;
}

2.3 成员函数运算符重载

也可以使用成员函数进行运算符重载,成员函数的运算符函数对应的输入参数比同样使用友元函数实现的友元函数的参数少一个。

#include <iostream>

using namespace std;

/**
 * @brief The Integer class
 * 整型类
 */
class Integer
{
private:
    int value;

public:
    Integer(int value):value(value){}

    int get_value() const
    {
        return value;
    }

    // 成员函数运算符重载
    Integer operator +(const Integer& i);
    Integer operator ++(); // 前置
    Integer operator ++(int); // 后置
};

Integer Integer::operator +(const Integer& i)
{
    // this表示运算数1
    return this->value+i.value;
}

Integer Integer::operator ++()
{
    return ++this->value;
}

Integer Integer::operator ++(int)
{
    return this->value++;
}

int main()
{
    Integer i1(1);
    Integer i2(2);
    Integer i3 = i1+i2;
    cout << i3.get_value() << endl; // 3

    cout << (i1++).get_value() << endl; // 1
    cout << i1.get_value() << endl; // 2
    cout << (++i2).get_value() << endl; // 3

    return 0;
}

2.4 其它运算符重载

2.4.1 赋值运算符重载

如果写一个空类,按照目前所学内容,编译器会自动添加:

  • 无参构造函数

  • 析构函数

  • 拷贝构造函数

除了上述三者外,编译器还会增加若干内容,其中包括赋值运算符重载函数。

赋值运算符重载函数只支持成员函数运算符重载,不支持友元函数运算符重载的方式。

#include <iostream>

using namespace std;

class Teacher
{
public:
    string subject;

    Teacher(string sub)
    {
        subject = sub;
    }

    // 编译器自动添加赋值运算符重载函数
    Teacher& operator =(const Teacher& right)
    {
        this->subject = right.subject;
        cout << "赋值运算符:" << subject << endl;
        return *this;
    }
};

int main()
{
    Teacher t1("C++");
    Teacher t2("C#");
    t2 = t1; // 赋值操作

    cout << (t2 = t1).subject << endl; // 支持链式调用
    cout << t2.subject << endl; // C++

    t2.subject = "C";
    Teacher t3 = t2; // 隐式调用拷贝构造函数
    cout << t3.subject << endl; // C

    return 0;
}

通常无需手动编写赋值运算符重载,以下情况需要手动编写,编译器不在自动添加赋值运算符重载函数:

  • 当前类的成员变量出现指针

  • 屏蔽赋值运算符的使用(权限为private)

2.4.2 类型转换运算符重载

可以使自定义类型的对象自动转换为任意类型,此函数也只能使用成员函数运算符重载。

#include <iostream>

using namespace std;

class Teacher
{
public:
    string subject;

    Teacher(string sub)
    {
        subject = sub;
    }

    operator string() // 格式比较特殊
    {
        return subject;
    }
};

int main()
{
    Teacher t("语文");
    string s = t;
    cout << s << endl; // 语文

    return 0;
}

2.5 注意事项

  • 运算符重载限制在C++已有的运算符范围内,不允许创建新的运算符。

  • 运算符重载也是函数重载,运算符也是函数。

  • 重载之后的运算符不能改变优先级和结合性。

  • 重载之后的运算符不能改变操作数和语法结构。

  • 运算符重载不能改变该运算符用于基本数据类型的含义,但是可以把基本数据类型与自定义类型一起运算,或者都使用自定义类型。

  • 运算符重载是针对新类型数据的实际需要对原有运算符的功能进行扩充,因此重载之后的功能应该与原有的功能类似,避免没有目的地使用运算符重载。

  • 通常建议单目运算符使用成员函数运算符重载,双目运算符使用友元函数运算符重载。

3. 字符串类 std::string(熟悉)

再学习此类,主要涉及此类的一些构造函数和成员函数。

std::string是一种特殊容器的类型,用于操作字符序列。

#include <iostream>
#include <string.h>

using namespace std;


int main()
{
    string s; // 创建一个内容为空的字符串对象
    // 是否为空
    cout << s.empty() << endl; // 1
    // 隐式调用构造函数
    string s1 = "Abc";
    // 相当于
    string s2("Abc");
    cout << (s1 == s2) << endl; // 1

    // 拷贝构造函数
    string s3 = s2;
    string s4(s3);

    cout << s3 << " " << s4 << endl; // Abc Abc

    // 参数1:源字符串 char*
    // 参数2:从前往后保留的字符数
    string s5("ABCDEFG",3);
    cout << s5 << endl; // ABC

    // 参数1:源字符串 string
    // 参数2:从前往后不保留的字符数
    s = "ABCDEFG";
    string s6(s,3);
    cout << s6 << endl; // DEFG

    // 参数1:字符数量
    // 参数2:字符 char
    string s7(5,'A');
    cout << s7 << endl; // AAAAA

    // 交换
    swap(s6,s7);
    cout << s6 << " " << s7 << endl; // AAAAA DEFG

    s1 = "ABCD";
    // 向后追加字符串,支持链式调用
    s1.append("EF").append("GHI").append("ZZZ"); // ABCDEFGHIZZZ
    cout << s1 << endl;

    // 字符串连接
    s1 = s6+s7;
    cout << s1 << endl; // AAAAADEFG

    // 向后追加一个字符
    s1.push_back('*');
    cout << s1 << endl; // AAAAADEFG*

    // 参数1:插入的位置
    // 参数2:插入的内容
    s1.insert(1,"###");
    cout << s1 << endl; // A###AAAADEFG*

    // 参数1:替换的起始位置
    // 参数2:替换的字符数量
    // 参数3:替换的新内容
    s1.replace(0,7,"!!!!");
    cout << s1 << endl; // !!!!ADEFG*

    // 参数1:删除的起始位置
    // 参数2:删除的字符数
    s1.erase(4,3);
    cout << s1 << endl; // !!!!FG*

    // 清空
    s1.clear();
    cout << s1.length() << endl; // 0

    // C字符串→C++字符串
    char c[20] = "hello";
    s = c;
    cout << s << endl; // hello

    // C++字符串→C字符串
    s = "Good afternoon!";
//    c = s; 错误
    s.copy(c,s.size()); // 全拷贝
    cout << c << endl; // Good afternoon!
    // 参数1:源字符串
    // 参数2:拷贝的数量
    // 参数3:拷贝的起始位置
    s.copy(c,9,5);
    cout << c << endl; // afternoonrnoon!

    // 还可以使用下面的函数完成
    char d[20];
    strcpy(d,s.c_str()); // c_str()返回一个临时的const char*
    cout << d << endl; // Good afternoon!

    return 0;
}
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值