C++笔记(二)

目录

 一、类

1.C++中的类

2.声明一个类

3.封装

二、C++中类和结构体的区别

三、this指针

四、类中特殊的成员函数

1.构造函数

         1.1 功能

         1.2 格式

        1.3 调用时机

         1.4 构造函数支持重载

1.5 构造函数的初始化列表

1.6 必须使用初始化列表的场景

2.析构函数

        2.1 作用

        2.2 格式

        2.3 调用时机           

        2.4 默认析构函数

3.拷贝构造函数

         3.1 格式

        3.2 调用时机

4.拷贝赋值函数

 4.1 格式     

4.2 调用时机

八、运算符重载

1.概念

2 .重载的方法

3.操作数

4 运算符重载函数的调用

5 注意事项

6 运算符重载的格式

6.1算术运算符重载

6.2关系运算符重载

6.3赋值运算符重载

6.4单目运算符重载

6.5自增自减运算符重载

6.6 插入>>和提取<<

6.7 不能重载的运算符

九、静态成员

1.静态成员变量

2.静态成员函数

十、单例模式


 一、类

1.C++中的类

        是由结构体演化而来的。

2.声明一个类

 class 类名{
            private: //后面是冒号
                //私有的成员变量(数据/属性)和成员函数(方法/行为)
            protected:
                //受保护的成员变量(数据/属性)和成员函数(方法/行为)
            public:
                //共有的成员变量(数据/属性)和成员函数(方法/行为)
};//别忘了写分号

例:

#include <iostream>
using namespace std;

//圆类
class Circel{
    public:
        double r;//半径
        double l;//周长
        double get_l(){
            l = 2 * 3.14 * r;
            return l;
        }
};

int main()
{
    //使用类定义变量的过程我们称之为:实例化类对象
    class Circel c1;//此处的class可以不写
    c1.r = 2;
    cout<<c1.get_l()<<endl;//12.56

    Circel c2;  //c1 和 c2 都叫做Circel类的对象 是相互独立的
    c2.r = 3;
    cout<<c2.get_l()<<endl;//18.84

    return 0;
}

3.封装

        面向对象的三大特征(封装、继承、多态),如果有四,加一个抽象

        我们对类的定义,就属于封装的范畴。

        封装:将和一类事物相关的完事万物都封装成类,通过类对象去做事儿。

        访问控制权限:

                public: 修饰的成员 在类内、子类中、类外都可以直接访问

                protected: 修饰的成员 在类内和子类中可以访问 类外不能直接访问

                private: 修饰的成员 只能在类内访问 子类中和类外都不能直接访问

         

        类的成员函数可以访问类的成员变量(public,protected,private都可以访问)

        即使是通过函数传参传递过来的本类的对象的私有成员,也能访问。

        也就是说,访问控制针对的是整个类,而不是某个对象而言的。

        访问控制一旦出现,后面所有的成员都受该权限的控制

        直到出现新的访问控制权限,或者类的定义结束

      

        访问控制权限允许多从出现和使用,但是一般情况下,

        我能都通过对成员的归类,一般一种权限只写一次。

        如果类中没有写访问控制权限,默认的所有成员都是被private控制的。

        之所以有访问控制权限,是因为,类的设计者认为:

        类的使用者不一定按照正常的方式去使用他,如上面的圆类中,半径可能被赋成负值,不合理

#include <iostream>
using namespace std;

//圆类
class Circel{
    private:
        //一般情况下 类的成员变量都是私有的,不允许在类外直接访问
        double r;//半径
        double l;//周长
    public:
        //一般会提供public权限的成员函数给使用者 方便对成员变量的操作
        int set_r(int value){
            //类的设计者可以在对外体中的成员函数中
            //对参数做检查,防止使用者乱用
            if(value < 0){
                cout<<"半径不合理,设置失败"<<endl;
                return -1;
            }
            r = value;//成员函数可以访问成员变量
            return 0;
        }
        double get_l(){
            l = 2 * 3.14 * r;
            return l;
        }
};

int main()
{
    //Circel c1;
    //c1.r = -2;//由于 成员r权限是 private
                //在类外无法直接访问
    //cout<<c1.get_l()<<endl;//12.56

    Circel c2;
    c2.set_r(2);//通过成员函数访问private的成员变量 是允许的 
    cout<<c2.get_l()<<endl;

    Circel c3;
    c3.set_r(-10);//不按照规则使用 是无法操作类的成员变量的

    return 0;
}

练习1:

        设计一个长方体的类

        成员变量:长、宽、高

        成员函数:获取体积

        实例化一个对象 调用并测试

练习2:

        在上一题的基础上,添加一个全局函数,功能:判断两个长方体是否相等。

        条件:长==长 宽==宽 高==高

#include <iostream>
#include "03.h"
using namespace std;

/*class tangle{
    private:
        double lenth;
        double width;
        double height;
    public:
        void init(double l,double w,double h){
            if(l<0 || w<0  || h<0){
                cout<<"输入参数不合理,请重新输入"<<endl;
                exit(-1);
            }
            lenth = l;
            width = w;
            height = h;
        }
        double getvol(double l,double w,double h){
            double v=lenth*width*height;
            return v;
        }
        double getchang(){
            return lenth;
        }
        double getkuan(){
            return width;
        }
        double getgao(){
            return height;
        }
        bool Isequalclass(tangle &t2){
            if(lenth==t2.lenth && width==t2.width && height==t2.height){
                return 1;
            }else{
                return 0;
            }
        } 
};*/
bool Isequal(tangle &t1,tangle &t2){
    if((t1.getchang()==t2.getchang()) && (t1.getkuan()==t2.getkuan()) && (t1.getgao()==t2.getgao()) ){
        return true;
    }else {
        return false;}
}
int main()
{
    /*double l=10;
    double w=2;
    double h=-3;
    tangle t;
    cout<<t.getvol(l,w,h)<<endl;*/
    tangle t1;
    t1.init(10,20,30);
    tangle t2;
    t2.init(10,10,30);
    /*全局变量版
    if(Isequal(t1,t2)){
        cout<<"相等"<<endl;
    }else {
        cout<<"不相等"<<endl;
    }*/
    
    if(t1.Isequalclass(t2)){
        cout<<"相等"<<endl;
    }else {
        cout<<"不相等"<<endl;
    }
    return 0;
}

多文件编程

.h

#ifndef  _03_H_
#define  _03_H_
#include <iostream>
using namespace std;
class tangle{
    private:
        double lenth;
        double width;
        double height;
    public:
        void init(double l,double w,double h);
        double getvol(double l,double w,double h);
        double getchang();
        double getkuan();
        double getgao();
        bool Isequalclass(tangle &t2);
};


#endif/*_03_H_*/

.cpp

#include <iostream>
#include "03.h"
using namespace std;


bool Isequal(tangle &t1,tangle &t2){
    if((t1.getchang()==t2.getchang()) && (t1.getkuan()==t2.getkuan()) && (t1.getgao()==t2.getgao()) ){
        return true;
    }else {
        return false;}
}
int main()
{
    /*double l=10;
    double w=2;
    double h=-3;
    tangle t;
    cout<<t.getvol(l,w,h)<<endl;*/
    tangle t1;
    t1.init(10,20,30);
    tangle t2;
    t2.init(10,10,30);
    /*全局变量版
    if(Isequal(t1,t2)){
        cout<<"相等"<<endl;
    }else {
        cout<<"不相等"<<endl;
    }*/
    
    if(t1.Isequalclass(t2)){
        cout<<"相等"<<endl;
    }else {
        cout<<"不相等"<<endl;
    }
    return 0;
}

二、C++中类和结构体的区别

唯一的区别,就是默认的访问控制权限不同:

        类中成员的默认的访问权限:private

        结构体中成员的默认的访问权限:public

#include <iostream>
using namespace std;

class A{
    int m;
    int n;
    void show(){
        cout<<m<<"  "<<n<<endl;
    }
};

struct B{
    int m;
    int n;
    void show(){
        cout<<m<<"  "<<n<<endl;
    }
};

int main()
{
    A hqyj1;
    #if 0
    //class的默认访问权限是 private 在类外无法访问成员
    hqyj1.m = 10;
    hqyj1.n = 20;
    hqyj1.show();
    #endif

    //struct的默认访问权限是 public 在类外可以访问成员
    B hqyj2;
    hqyj2.m = 10;
    hqyj2.n = 20;
    hqyj2.show();

    return 0;
}

既然C++中结构体和类区别不大,什么时候使用类,什么时候使用结构体呢?

一般情况下,定义数据节点的时候,都使用结构体 如链表的节点

而封装的逻辑比较多的时候,一般都是用类

如果分不清,就都是用class

三、this指针

        this指针是成员函数中隐藏的一个形参,

        哪个对象调,就指向谁

        复习:const修饰指针

                const int *p;//不能通过p修改指向的空间的内容

                int const *p;//不能通过p修改指向的空间的内容

                int * const p;//不能修改p的指向

                const int * const p;//都不能修改

        this指针类型:类名 * const this 

        注:

                1.不能在成员函数形参中使用this指针

                2.不能再构造函数的初始化表中使用this指针

                3.在成员函数的函数体中可以使用this指针

例:

#include <iostream>
using namespace std;

class Test{
    private:
        int a;
        int b;
    public:
        void init(int x, int y){
            //在成员函数内部 不加任何修饰直接访问成员变量
            //都是通过this指针访问的
            //a = x;
            //b = y;
            this->a = x;
            this->b = y;
        }
        bool compare(Test &x){
            if(this->a == x.a && this->b == x.b){
            //下面的用法也可以 
            //在成员函数内部 不加任何修饰直接访问成员变量
            //都是通过this指针访问的
            //if(a == x.a && b == x.b){
                return true;
            }
            return false;
        }
};

int main()
{
    Test t1;
    t1.init(10, 20);
    Test t2;
    t2.init(30, 40);
    cout<<(t1.compare(t2) ? "相等" : "不相等")<<endl;
    return 0;
}

必须使用this指针的情况

        成员函数形参名和成员变量相同时(也可以使用 构造函数 的初始化表解决)

#include <iostream>
using namespace std;

class Test{
    private:
        int a;
        int b;
    public:
        void init(int a, int b){
            //成员函数的形参和成员变量重名时
            //在成员函数内部 不加修饰 默认使用的是形参
            //a = a;//形参自己给自己赋值
            //b = b;//形参自己给自己赋值
            //这是可以使用this指针区分
            this->a = a;
            this->b = b;
            cout<<"init: "<<a<<" "<<b<<endl;//10 20
        }
        void show(){
            cout<<"show: "<<a<<" "<<b<<endl;//10 20
        }
};

int main()
{
    Test t1;
    t1.init(10, 20);
    t1.show();
    return 0;
}

另一种必须使用this指针的场景:

 拷贝赋值函数需要返回自身的引用时,只能通过this指针访问。

四、类中特殊的成员函数

1.构造函数

         1.1 功能

                 实例化对象的时候给成员变量申请空间,完成成员变量初始化

                还可以执行一些分配内存、打开文件等操作

         1.2 格式

                函数名与类名相同,

                不关注返回值,

                构造函数一般受public权限控制

        1.3 调用时机

                在类实例化对象的过程中,会自动调用构造函数。

                构造函数不能手动调用

                栈区:

                        类名 变量名(构造函数的实参表);//调用构造函数

                堆区:

                        类名 *指针名//这个过程不会调用构造函数

                        指针名 =new  类名构造函数的实参表); //这个过程才会调用构造函数

       

#include <iostream>
#include <string>
using namespace std;

class student{
    string name;
    int *age;
    //构造函数
    public:
        student(string n,int a){
            cout << "我是构造函数" << n <<endl;
            name=n;
            age=new int(a);
        }
        void show(){
            cout<<name<<" "<<*age<<endl;
        }
};
int main()
{
    //栈区实例化对象
    Student s1("zhangsan", 10);//调用构造函数
    s1.show();

    //堆区实例化对象
    Student *p1;
    p1 = new Student("lisi", 20);//调用构造函数
    p1->show();

    //malloc不会调用构造函数
    Student *p2;
    p2 = (Student *)malloc(sizeof(Student));

    return 0;
}

练习:

把前一天长方体的类中init函数,该成构造函数,调用并测试。

#include <iostream>
using namespace std;
class tangle{
    private:
        double lenth;
        double width;
        double *height;
    public:
        /*tangle(double l,double w,double h){
            if(l<0 || w<0  || h<0){
                cout<<"输入参数不合理,请重新输入"<<endl;
                exit(-1);
            }
            cout << "我是构造函数" <<endl;
            lenth = l;
            width = w;
            height = h;
        }*/
        tangle(double l,double w,double h);//类内声明
        void show(){
            cout<<lenth<<" "<<width<<" "<<height<<endl;
        }
        //析构函数
        ~tangle(){
            if(height !=NULL){
                delete height;
            }
        }     
};
//类外定义
tangle::tangle(double l,double w,double h){
            if(l<0 || w<0  || h<0){
                cout<<"输入参数不合理,请重新输入"<<endl;
                exit(-1);
            }
            cout << "我是构造函数" <<endl;
            lenth = l;
            width = w;
            height = new double(h);
        }
int main()
{
    tangle t1(10,20,30);
    tangle *t2;
    t2=new tangle(10,20,20);
    return 0;
}

         1.4 构造函数支持重载

                默认构造函数:

                        如果类型没有显性的定义构造函数

                        编译器会提供一个默认的构造函数,

                        形参列表为void,函数体为空,用来给实例化对象的过程使用。

                        如果类中显性的定义了构造函数,那么编译器就不再提供默认的版本了

                        所以,在这种场景下,如果想要使用无参的构造函数,也需要显性手动定义

#include <iostream>
#include <string>
using namespace std;
class student{
    private:
        string name;
        int age;
    public:
        //无参
        student(){cout<<"我是无参构造函数"<<endl;}
        //有参
        student(string n,int a){
            cout<<"我是you参构造函数1"<<endl;
            name=n;
            age=a;
        }
        student(string n){
            cout<<"我是you参构造函数2"<<endl;
            name=n;
        }
        void show(){
            cout<<name<<"  "<<age<<endl;
        }
};
int main()
{
    student s1("zhangsan", 10);//调用有参构造函数1
    s1.show();

    student s2;//无参构造函数
    s2.show();

    student s3("hello");//调用有参构造函数2
    s3.show();
    return 0;
}

1.5 构造函数的初始化列表

        可以在定义构造函数的时候,使用 冒号的方式 引出构造函数的初始化列表

        格式:

        类名(构造函数的形参表):成员1(初值1),成员2(初值2){

                构造函数的函数体;

        }

#include <iostream>
#include <string>
using namespace std;
class student{
    private:
        string name;
        int age;
    public:
        //无参
        student(){cout<<"我是无参构造函数"<<endl;}
        //有参
        student(string n,int a):name(n),age(a){
            cout<<"我是you参构造函数1"<<endl;
        }
        student(string n):name(n){
            cout<<"我是you参构造函数2"<<endl;
        }
        void show(){
            cout<<name<<"  "<<age<<endl;
        }
};
int main()
{
    student s1("zhangsan", 10);//调用有参构造函数1
    s1.show();

    student s2;//无参构造函数
    s2.show();

    student s3("hello");//调用有参构造函数2
    s3.show();
    return 0;
}

1.6 必须使用初始化列表的场景

        场景1.成员函数形参名和成员变量名相同(也可以使用this指针解决)

#include <iostream>
using namespace std;
#include <string>
#include <cstdlib>

class Student{
    private:
        string name;
        int *age;
    public:
        Student():name(""),age(new int){
            cout<<"我是无参构造函数"<<endl;
        }
        //重名了 可以使用初始化表来区分
        Student(string name, int age):name(name), age(new int(age)){
            cout << "我是有参构造函数" <<endl;
        }
        void show(){
            cout<<name<<"  "<<*age<<endl;
        }
};

int main()
{
    //栈区实例化对象
    Student s1("zhangsan", 10);//调用有参构造函数1
    s1.show();

    Student s2;//无参构造函数
    s2.show();

    return 0;
}

        场景2.

                类中有引用作为成员变量的时候

#include <iostream>
using namespace std;
#include <iostream>
#include <string>
using namespace std;
class student{
    private:
        string name;
        int &age;
    public:
        //有参
        student(string n,int &a):name(n),age(a){
            //int &a=num;
            //int &age=a;
            cout<<"我是you参构造函数1"<<endl;
        }
        void show(){
            cout<<name<<"  "<<age<<endl;
        }
};
int main()
{
    //student s1("zhangsan", 10);//调用有参构造函数1
    int num=10;
    student s1("zhangsan", num);//调用有参构造函数1
    s1.show();

    return 0;
}

        场景3.

                类中有const修饰的变量

#include <iostream>
using namespace std;
#include <iostream>
#include <string>
using namespace std;
class student{
    private:
        string name;
        const int age;
    public:
        //有参
        student(string n,int a):name(n),age(a){
            //如果不采用成员初始化列表的话,后续也无法给age赋值了,没有意义
            cout<<"我是you参构造函数1"<<endl;
        }
        void show(){
            cout<<name<<"  "<<age<<endl;
        }
};
int main()
{
    //student s1("zhangsan", 10);//调用有参构造函数1
    int num=10;
    student s1("zhangsan", num);//调用有参构造函数1
    s1.show();

    return 0;
}

        场景4.

                当类中有成员子对象(其他类的对象)时

#include <iostream>
using namespace std;
#include <iostream>
#include <string>
using namespace std;
class student{
    private:
        string name;
        int age;
    public:
        student(){cout << "Student 无参构造函数" <<endl;}
        //有参
        student(string n,int a):name(n),age(a){
            cout<<"我是you参构造函数1"<<endl;
        }
        void show(){
            cout<<name<<"  "<<age<<endl;
        }
};
class teacher{
    private:
        string name;
        int age;
        student stu;
    public:
        teacher(){cout << "Student 无参构造函数" <<endl;}
        teacher(string n,int a,string sn,int sa):name(n),age(a),stu(sn,sa){
            cout<<"我是you参构造函数1"<<endl;
            //name=n;
            //age=a;
            //stu.Student(n2, a2);//错误的 构造函数不能手动调用
        }
        void show(){
            cout<<name<<" "<<age<<" "<<endl;
            //cout<<stu.name<<" "<<stu.age<<endl;//错误的 name 和 age 是 Student类中的私有的
            stu.show();
        }
};
int main()
{
    //栈区实例化对象
    //调用Teacher的有参构造函数 调用Student的有参构造
    teacher c1("zhangsan", 20, "xiaoming", 18);
    c1.show();

    //调用Teacher的无参构造函数 调用Student的无参构造
    teacher c2;

    return 0;

2.析构函数

        2.1 作用

                在对象消亡的时候,用来做释放空间等善后工作的。

        2.2 格式

                ~类名(void){} //析构函数是没有参数的 所以不能重载

        2.3 调用时机           

                对象消亡时,自动调用。 (析构函数可以手动调用 但是一般不这样做)

                栈区:声明周期结束时

                堆区:手动调用delete时

        2.4 默认析构函数

                如果类中没有显性的定义析构函数,编译器会默认提供一个函数体为空的析构函数,

                用来消亡对象使用的,如果显性定义了,默认的版本就不提供了。

                例:

#include <iostream>
using namespace std;
#include <string>

class Student{
    private:
        string name;
        int *age;
    public:
        Student():name(""), age(NULL){cout<<"无参构造"<<endl;};
        Student(string n, int a):name(n), age(new int(a)){
            cout<<"有参构造"<<endl;
        };
        #if 0
        //类内定义的写法
        ~Student(void){
            cout<<"析构函数"<<endl;
            if(age != NULL){
                delete age;
                age = NULL;
            }
        }
        #endif
        //类内声明
        ~Student(void);
};

//类外定义的写法
Student::~Student(void){
    cout<<"析构函数"<<endl;
    if(age != NULL){
        delete age;
        age = NULL;
    }
}

int main()
{
    Student s1;//无参构造
    Student s2("zhangsan", 20);//有参构造

    Student *p1 = new Student;//无参构造
    Student *p2 = new Student("lisi", 18);//有参构造
    //堆区的对象 需要delete时 自动调用析构函数 完成善后工作
    delete p1;//如果不手动调用delete 析构函数不会被调用
    delete p2;

    //栈区的对象 在声明周期结束时,自动调用析构函数 完成善后工作

    return 0;
}

                构造函数和析构函数调用的顺序?                

                1.对于堆区的对象:

                        先new哪个对象,就先构造哪个对象,

                        先delete哪个对象,就先析构哪个对象

                        所以,一般不考虑堆区的构造函数和析构函数的调用顺序

                2.对于栈区的对象:

                        构造函数的调用顺序:按顺序调用

                        析构函数的调用顺序:逆序调用

                        ----先构造的后析构,栈的顺序

3.拷贝构造函数

         3.1 格式

                函数名:与类同名

                返回值:没有返回值

                形参: const 类名 &

                类名 (const 类名 &other){}

        3.2 调用时机

                用一个已经初始化的对象初始化一个新的对象时,会自动调用拷贝构造函数             

                类名 对象1(构造函数的实参表); // 有参构造函数

                类名 对象2(对象1); //拷贝构造函数

                类名 对象3 = 对象1; //拷贝构造函数

                类名 *指针名 = new 类名(对象3);//拷贝构造函数

                        //类名 对象2(对象1);----从编译器的角度理解 相当于 对象2.类名(对象1);

#include <iostream>
using namespace std;
#include <string>

class Student{
    private:
        string name;
        int age;
    public:
        Student(){cout<<"无参构造"<<endl;}
        Student(string n, int a):name(n), age(a){
            cout<<"有参构造"<<endl;
        };
        #if 0
        Student(const Student &other){
            cout<<"拷贝构造"<<endl;
            name = other.name;
            age = other.age;
        }
        #endif
        //拷贝构造函数 也是构造函数,也可以使用初始化表
        Student(const Student &other):name(other.name), age(other.age){
            cout<<"拷贝构造"<<endl;
        }

        ~Student(void){cout<<"析构函数"<<endl;}
        void show(){
            cout<<name<<" "<<age<<endl;
        }
};

int main()
{
    Student s1("zhangsan", 20);//有参构造函数
    s1.show();
    
    Student s2(s1);//拷贝构造
    s2.show();

    Student s3 = s1;//拷贝构造
    s3.show();

    Student *p1 = new Student(s1);//拷贝构造
    p1->show();
    delete p1;

    Student s4;//无参构造
    s4 = s1;//调用拷贝赋值函数  不会调用拷贝构造

    return 0;
}

笔试面试题:

C++中浅拷贝和深拷贝的区别?

浅拷贝:

        如果不手动提供一个拷贝构造函数,则编译器默认会给一个拷贝构造函数,编译器提供的拷贝构造函数只会进行简单的赋值。如果有指针成员变量也只会简单的赋值,将指针变量里的值赋值给新的对象,这样两个指针变量指向的就是同一块空间,那么在析构的时候会出现double free的问题,此时就需要深拷贝来解决问题。

深拷贝:

         由程序员手动的写一个拷贝构造函数,给对象里的指针变量重新开辟一块空间,将原来对象指针里的内容拷贝给新对象的指针,这样析构的时候,释放的就是各自的空间,可以有效避免double free的问题。

4.拷贝赋值函数

 4.1 格式     

                类名 &operator=(const 类名 &other){

                        if(this!=&other){

                               retutn *this;

                        }

4.2 调用时机

两个已经完成初始化的类对象之间相互赋值的时候,会自动调用拷贝赋值函数。

例:

        string s1("hello"); //有参构造

        string s2(s1); //拷贝构造

        string s3; //无参构造

        s3 = s1; //调用拷贝赋值函数

                ----从编译器的角度 相当于 s3.operator=(s1);

例:

#include <iostream>
using namespace std;
#include <string>

class Student{
    private:
        string name;
        int age;
    public:
        Student(){cout<<"无参构造"<<endl;}
        Student(string n, int a):name(n), age(a){
            cout<<"有参构造"<<endl;
        };
        Student(const Student &other):name(other.name), age(other.age){
            cout<<"拷贝构造"<<endl;
        }
        ~Student(void){cout<<"析构函数"<<endl;}
        #if 0
        //类内定义的写法
        Student &operator=(const Student &other){
            cout<<"拷贝赋值"<<endl;
            if(this != &other){
                name = other.name;
                age = other.age;
            }
            return *this;//返回自身的引用
        }
        #endif
        //类内声明
        Student &operator=(const Student &other);
        void show(){
            cout<<name<<" "<<age<<endl;
        }
};

//类外定义的写法
Student &Student::operator=(const Student &other){
    cout<<"拷贝赋值"<<endl;
    if(this != &other){
        name = other.name;
        age = other.age;
    }
    return *this;//返回自身的引用
}

int main()
{
    Student s1("zhangsan", 20);//有参构造函数
    s1.show();

    Student s2;//无参构造
    s2 = s1;//拷贝赋值
    s2.show();

    Student s3;
    s3 = s2 = s1;//返回的自身的引用是为了级联使用时用到的  从右到左结合
            //从编译器的角度  s3.operator=(s2.operator=(s1))
    s3.show();

    return 0;
}

如果类中没有显性的给定拷贝赋值函数,编译器会提供一个默认的拷贝赋值函数

完成两个对象的成员之间的简单赋值(浅拷贝),如果类中有指针成员,就得自己显性

定义深拷贝的写法,写法如下面的例子:

#include <iostream>
using namespace std;
#include <string>

class Student{
    private:
        string name;
        int *age;
    public:
        Student():name(""), age(new int){cout<<"无参构造"<<endl;}
        Student(string n, int a):name(n), age(new int (a)){
            cout<<"有参构造"<<endl;
        };
        Student(const Student &other):name(other.name), age(new int (*(other.age))){
            cout<<"拷贝构造"<<endl;
        }
        ~Student(void){
            cout<<"析构函数"<<endl;
            if(NULL != age){
                delete age;
                age = NULL;
            }
        }
        //深拷贝
        Student &operator=(const Student &other){
            if(this != &other){
                this->name = other.name;
                //先释放指针原来指向的空间
                delete this->age;
                //再根据实际需求重新分配对应大小的新的空间
                age = new int(*(other.age));
                //我的这个例子中  不重新分配也可以 因为两个对象的age都是int *
                //而如果char *指针 用来保存字符串 那么就需要根据字符串的长度重新分配了
            }
            return *this;
        }

        void show(){
            cout<<name<<" "<<*age<<endl;
        }
};

int main()
{
    Student s1("zhangsan", 20);//有参构造函数
    s1.show();

    Student s2("lisi", 18);//有参构造函数
    s2 = s1;//拷贝赋值
    s2.show();

    return 0;
}

笔试面试题:

        C++中一个空类Test,默认会提供哪些函数,请指明并写出声明的格式。

                Test(){}

                ~Test(){}

                Test (const Test &other){}

                Test &operator=(){}

八、运算符重载

1.概念

所谓的运算符重载,就是给运算符赋予一个新的含义,让本来只能做基本类型运算的运算符,

也能做类对象之间的运算,如果没有运算符重载,类对象之前是不能直接做运算的。

2 .重载的方法

运算符重载本质也是函数重载,我们需要重新定义函数 operator# (#表示运算符)

3.操作数

个数:

由要重载的运算符本身决定: + 需要两个操作需 ++就只需要一个操作数

类型:

是由我们自己定义的

4 运算符重载函数的调用

左调右参,一般是由左操作数来调用运算符重载函数,右操作数作为参数。

如:s1 = s2 ---> 从编译器的角度 s1.operator=(s2)

5 注意事项

1.运算符重载的格式是约束好的,但是逻辑是我们自己规定的,但是我们实现时,

一般都要尽量保留运算符的含义,不要乱写,如+重载中 写减法的逻辑

2.运算符重载可以实现成员函数版,也可以实现全局函数版,

但是为了访问私有成员方便,我们一般都写成成员函数版

注意:全局函数版和成员函数版只能实现一个,否则调用时,有歧义,会报错

6 运算符重载的格式

6.1算术运算符重载

+ - * / %

        表达式:L # R (L 左操作数 # 运算符 R 右操作数)

        左操作数:既可以是左值,又可以是右值  a+b 1+2(左右操作数都可以既是常量,又是变量)

        右操作数:既可以是左值,又可以是右值  

        表达式的结果:只能是右值   c=L+R    (L+R=c不行)

成员函数版:

        从编译器的角度:L.operator#(R)

        const 类名 operator#(const 类名 &R)const//不引用,返回的是L+R的和

全局函数版:

        从编译器的角度:operator#(L,R)

        friend const 类名 operator#(const 类名 &L,const 类名 &R)--友元是为了访问私有成员方便

//类内
friend Complex const operator+(const Complex &L,const Complex &R);//类名前加const:返回值是一个右值,不能被改变
const Complex operator-(const Complex &R)const{//const修饰this指针,确保L的值不能被修改
            Complex temp;
            temp.real=real-R.real;
            temp.vir=vir-R.vir;
            return temp;
        }


//类外
const Complex operator+(const Complex &L,const Complex &R){
    Complex temp;
    temp.real=L.real+R.real;
    temp.vir=L.vir+R.vir;
    return temp;
}

6.2关系运算符重载

>   <   >=   ==   !=

        表达式:L # R (L 左操作数 # 运算符 R 右操作数)

        左操作数:既可以是左值,又可以是右值  a>b 1<2(左右操作数都可以既是常量,又是变量)

        右操作数:既可以是左值,又可以是右值  

        表达式的结果:只能是右值   比较的结果是布尔类型,是常量

成员函数版:

        从编译器的角度:L.operator#(R)

        const bool 类名 operator>=(const 类名 &R)const;

全局函数版:

        从编译器的角度:operator#(L,R)

        friend const bool 类名 operator>=(const 类名 &L,const 类名 &R);

//类内
friend const bool operator<(const Complex&L,const Complex &R);
        const bool operator>(const Complex &R)const{
            if(real>R.real &&vir>R.vir){
                return true;
            }
            return false;
        }

//类外
const bool operator<(const Complex&L,const Complex &R){
     if(L.real<R.real &&L.vir<R.vir){
                return true;
            }
            return false;
}

6.3赋值运算符重载

=   +=   -=   *=   /= ...

        表达式:L # R (L 左操作数 # 运算符 R 右操作数)

        左操作数:只能是左值  

        右操作数:既可以是左值,又可以是右值  

        表达式的结果:左操作数

成员函数版:

        从编译器的角度:L.operator#(R)

       类名 &operator+=(const 类名 &R);

全局函数版:

        从编译器的角度:operator#(L,R)

       friend 类名 &operator+=(类名 &L,const 类名 &R);

        注意:赋值类运算符中的 = ,只能实现成员函数版

        因为编译器默认提供的特殊的成员函数 拷贝赋值函数 本质就是=运算符的重载

//类内
friend Complex& operator-=(Complex&L, const Complex &R);
Complex &operator+=(const Complex &R){
            real+=R.real;
            this->vir+=R.vir;
            return *this;
 }

//类外
Complex& operator-=(Complex &L, const Complex &R){
    L.real-=R.real;
    L.vir-=R.vir;
    return L;
}

6.4单目运算符重载

-(负)   !(非)   ~(取反)

        表达式:#O (# 运算符 O操作数)

        操作数:左值

        表达式的结果:右值

成员函数版:

        从编译器的角度:O.operator#()

       const 类名 operator-()const;

全局函数版:

        从编译器的角度:operator#(&O)

       friend const 类名 &operator+=(const 类名 &O);

//类内
friend const Complex operator-(const Complex &O);
const Complex operator-()const{
            Complex temp;
            temp.real=-real;
            temp.vir=-(this->vir);
            return temp;
        }

//类外
const Complex operator-(const Complex &O){
    Complex temp;
    temp.real=-O.real;
    temp.vir=-(O.vir);
    return temp;
}

6.5自增自减运算符重载

6.5.1 前缀的自增自减运算符

++a --a(常量不能自增自减)

        表达式:# O (# 运算符 O操作数)

        操作数:左值

        表达式的结果:左值

成员函数版:

        从编译器的角度:O.operator++()

        类名 &operator++();

全局函数版:

        从编译器的角度:

        friend 类名 &operator++(类名 &O);

//类内
 friend Complex &operator--(Complex &O);
        //++
        Complex &operator++(){
            this->real++;
            this->vir++;
            return *this;
        }

//类外
Complex &operator--(Complex &O){
    O.real--;
    O.vir--;
    return O;
}

6.5.2 后缀的自增自减运算符

a++ a--(常量不能自增自减)

        表达式:# O (# 运算符 O操作数)

        操作数:左值

        表达式的结果:右值

成员函数版:

        从编译器的角度:O.operator++()

        const 类名 operator++(int ); //后置++与前置++仅返回值不同,无法构成重载,需要哑元

全局函数版:

        从编译器的角度:

        friend const 类名 operator++(类名 &O,int);

//类内
 friend const Complex operator--(Complex &O,int);
        //后++
        const Complex operator++(int){
            Complex temp=*this;
            this->real++;
            this->vir++;
            return temp;
        }
//类外
//后--
const Complex operator--(Complex &O,int){
    Complex temp=O;
    O.real--;
    O.vir--;
    return temp;
}

完整代码

#include <iostream>
#include <string>
using namespace std;
class Complex{
    private:
        int real;
        int vir;
    public:
        Complex(){}
        Complex(int r,int v):real(r),vir(v){}
        ~Complex(){}
        void show()const{
            cout<<real<<"+"<<vir<<"i"<<endl;
        }
        friend Complex const operator+(const Complex &L,const Complex &R);//类名前加const:返回值是一个右值,不能被改变
        friend const bool operator<(const Complex&L,const Complex &R);
        friend const Complex operator-(const Complex &O);
        friend Complex& operator-=(Complex&L, const Complex &R);
        friend Complex &operator--(Complex &O);
        friend const Complex operator--(Complex &O,int);
        //-
        const Complex operator-(const Complex &R)const{//const修饰this指针,确保L的值不能被修改
            Complex temp;
            temp.real=real-R.real;
            temp.vir=vir-R.vir;
            return temp;
        }
        //>
        const bool operator>(const Complex &R)const{
            if(real>R.real &&vir>R.vir){
                return true;
            }
            return false;
        }

        //+=
        Complex &operator+=(const Complex &R){
            real+=R.real;
            this->vir+=R.vir;
            return *this;
        }
        //取反
        /*const Complex operator-()const{
            Complex temp;
            temp.real=-real;
            temp.vir=-(this->vir);
            return temp;
        }*/
        
        //前++
        Complex &operator++(){
            this->real++;
            this->vir++;
            return *this;
        }
        //后++
        const Complex operator++(int){
            Complex temp=*this;
            this->real++;
            this->vir++;
            return temp;
        }
};

//全局版
//前--
Complex &operator--(Complex &O){
    O.real--;
    O.vir--;
    return O;
}
//后--
const Complex operator--(Complex &O,int){
    Complex temp=O;
    O.real--;
    O.vir--;
    return temp;
}
//取反
const Complex operator-(const Complex &O){
    Complex temp;
    temp.real=-O.real;
    temp.vir=-(O.vir);
    return temp;
}
//-=
Complex& operator-=(Complex &L, const Complex &R){
    L.real-=R.real;
    L.vir-=R.vir;
    return L;
}
//+
const Complex operator+(const Complex &L,const Complex &R){
    Complex temp;
    temp.real=L.real+R.real;
    temp.vir=L.vir+R.vir;
    return temp;
}
//<
const bool operator<(const Complex&L,const Complex &R){
     if(L.real<R.real &&L.vir<R.vir){
                return true;
            }
            return false;
}
int main()
{
    Complex c1(10,20);
    Complex c2(1,2);
    ++c1;
    c1.show();
    --c1;
    c1.show();
    
    (c1++).show();
    c1.show();
    (c2--).show();
    c2.show();
    return 0;
}

6.6 插入>>和提取<<

        cin 和 cout 分别是是 istream 和 ostream 类的对象

        namespace std{

                istream cin;

                ostream cout;

        }

        int a = 10;

        cout<<a;// 从编译器的角度 cout.operator

        对于插入>>和提取<<运算符的重载,只能实现全局函数版,

        因为我们无法修改 istream 和 ostream 类

        提取<<运算符重载的格式

        friend ostream &operator<<(ostream &O,类名 &x);

        提取<<运算符重载的格式

        friend ostream &operator>>(istream &O,类名 &x);

6.7 不能重载的运算符

. 取成员运算符

:: 作用域限定符

.* 成员指针运算符

?: 三目运算符

# 预处理符

sizeof运算符

九、静态成员

1.静态成员变量

在定义成员变量的时候,前面加关键字 static 修饰,这个成员变量就是一个静态成员变量。

1.静态成员变量必须初始化,且必须在类外全局处初始化

2.所有的类对象共享同一个静态成员变量,一个类对象将其修改,

        其他对象访问时,访问的也是修改之后的值

        也就是说,静态成员变量是属于整个类的,而不是属于某个类对象的

#include <iostream>
using namespace std;
#include <string>

class Student{
    private:
        string name;
        int age;
    public:
        //静态成员变量也受访问控制权限的控制
        static int flag;//下课的标志位 10 没下课 20 下课
        Student(){}
        //构造函数的初始阿虎表中不能初始化静态成员变量
        //Student(string n, int a):name(n), age(a), flag(100){
        //    flag = 100;//构造函数的函数体中可以访问 但是已经不是初始化了 是赋值
        //}
        Student(string n, int a):name(n), age(a){}
        ~Student(){}
};

//静态成员变量必须初始化 且必须在类外全局处初始化
int Student::flag = 20;

int main()
{
    Student s1("张三", 18);
    cout<<(s1.flag==10?"下课":"没下课")<<endl;
    //访问静态成员变量的方式1:通过类对象访问
    Student s2("李四", 20);
    cout<<(s2.flag==10?"下课":"没下课")<<endl;
   
    //访问静态成员变量的方式2:通过类名直接访问
    cout<<Student::flag<<endl;

    //一个类对象改变了静态成员变量 其他类对象访问时 也是修改后的值
    s1.flag = 10;
    cout<<(s2.flag==10?"下课":"没下课")<<endl;

    return 0;
}

2.静态成员函数

在定义成员函数的时候,前面加关键字 static 修饰,这个成员函数就是一个静态成员函数。

1.静态成员变量是属于整个类的,而不是属于某个类对象的

2.静态成员函数没有this指针,不能访问普通的成员变量,只能访问静态成员变量

#include <iostream>
using namespace std;
#include <string>

class Student{
    private:
        string name;
        int age;
    public:
        static int flag;//下课的标志位 10 没下课 20 下课
        Student(){}
        Student(string n, int a):name(n), age(a){}
        ~Student(){}

        //静态成员函数  没有this指针 只能访问静态成员变量
        static void show(){
            //cout<<name<<endl;//错误的
            //cout<<age<<endl;//错误的
            cout<<flag<<endl;
        }
};

//静态成员变量必须初始化 且必须在类外全局处初始化
int Student::flag = 20;

int main()
{
    //静态成员函数的访问方式1:通过类对象访问
    Student s1("张三", 18);
    s1.show();

    //静态成员函数的访问方式2:通过类名直接访问
    Student::show();

    return 0;
}

为什么要使用静态成员变量?为了让成员变量的存在不依赖于类对象。

为什么要使用静态成员函数?为了让函数逻辑的执行不依赖于类对象。

十、单例模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值