C++学习笔记

C++学习——类和对象

具有相同性质的对象,可以抽象为

1.1封装
1.1.1封装的意义

  • 将属性和行为作为一个整体,表现生活中的事物

  • 将属性和行为加以权限控制
    访问权限有三种:

    • public 公共权限,类内可以访问,类外可以访问
    • protected 保护权限,类内可以访问,类外不可以访问(子类可以访问父类中的保护内容)
    • private 私有权限,类内可以访问,类外不可以访问(子类不可以访问父类中的私有内容)
class 类名
{
    //访问权限
public://公共权限

protected://保护权限

private://私有权限

    //类中的属性和行为,称为成员。类中可以让另一个类作为本类中的成员

    //属性(用变量)(成员属性、成员变量)

    //行为(用函数)(成员函数、成员方法)

};

实例化:通过一个类,创建一个对象的过程。

1.1.2 strcut与class的区别

  • 唯一的区别在于默认的访问权限不同
    • struct默认权限为公共
    • class默认权限为私有

1.1.3成员属性设置为私有

  • 优点1:将所有成员属性设置为私有,可以自己控制读写权限
  • 优点2:对于写权限,我们可以检测数据的有效性

操作方法是通过声明public的函数(方法)作为功能接口,实现控制读写权限/检测数据的有效性

class Person
{
public:
    //写操作
    void setName(string setname)
    {
        name = setname;
    }
private:
    string name;
    string lover;
};

1.2对象的初始化和清理
1.2.1构造函数与析构函数

  • 一个对象或者变量没有初始状态,对其使用后结果未知
  • 使用完一个对象或者变量,没有及时清理,也会造成安全问题

C++利用构造函数析构函数来解决上述问题,这两个函数会被编译器自动调用,完成对象的初始化和清理。如果我们不提供构造函数和析构函数,编译器会提供,但是是空实现

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用。
    • 语法:类名(){}
    • 没有返回值也不写void
    • 函数名称与类名相同
    • 可以有参数,因此可以重载
    • 程序在调用对象时会自动调用,而且只会调用一次
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
    • 语法:~类名(){}
    • 没有返回值也不写void
    • 函数名与类名相同,前面加上~
    • 不可以有参数,因此不可以发生重载
    • 程序在对象销毁前会自动调用,且只会调用一次

1.2.2构造函数的分类及调用

  • 分类
    • 按参数分为:有参构造和无参构造(默认构造)
    • 按类型分为:普通构造和拷贝构造

拷贝构造函数:类名(const 类名 &变量名){}

  • 调用方式
    • 括号法
      注:调用无参构造函数时不能加(),否则会被编译器当成函数声明
    • 显示法
      • 类名 变量名;
      • 类名 变量名 = 类名(参数);//有参构造
      • 类名 变量名 = 类名(变量名)//拷贝构造
      • 注:类名(参数)这是一个匿名对象,当前行执行结束后系统会自动收回匿名对象。不要用拷贝构造来初始化匿名对象
    • 隐式转换法

1.2.3拷贝构造函数的调用时机

  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值
  • 以值方式返回局部对象

1.2.4构造函数的调用规则
默认情况下,C++编译器至少给一个类添加三个函数

  • 默认构造函数(无参,函数体为空)
  • 默认析构函数(无参,函数体为空)
  • 默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则

  • 如果用户定义有参构造,C++不再提供默认无参构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,C++就不会提供其他构造函数

1.2.5深拷贝与浅拷贝

  • 浅拷贝:简单的赋值拷贝操作。带来的问题就是堆区内存的重复释放

默认拷贝构造函数就会进行浅拷贝,如果一个指针指向堆区,那么该堆区会被两个指针指向。最后释放内存时就会导致堆区内存的重复释放。

  • 深拷贝:在堆区重新申请空间,进行拷贝操作

自己写一个拷贝构造函数实现,对于指针的拷贝,不能直接用指针赋值(浅拷贝如此做),而是要重新开一段堆区让指针指向它。m_Height = new int(*p.m_Height);(案例)

1.2.6初始化列表
语法:构造函数():属性1(值1),属性2(值2)…{}

class Person
{
public:
    //传统的初始化方式
    Person(int a, int b, int c){
        m_A = a;
        m_B = b;
        m_C = c;
    }
    //初始化列表方式初始化
    Person(int a, int b, int c):m_A(a),m_B(b),m_C(c){}

    int m_A;
    int m_B;
    int m_C;
};

1.2.7类对象作为类成员

C++类中成员可以使另一个类的对象,称该成员为对象成员
当其他类对象作为本类成员,构造时先构造类对象,再构造自身;析构的顺序与构造相反。

1.2.8静态成员

静态成员就是在成员变量和成员函数前加关键字static

  • 静态成员变量(也是有访问权限的)
    • 所有对象共享同一份数据
    • 在编译阶段分配内存
    • 类内声明,类外初始化
    • 两种访问方式
      • 通过对象进行访问Person p; p.m_A;
      • 通过类名进行访问Person::m_A;
  • 静态成员函数(也是有访问权限的)
    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量
    • 两种访问方式
      • 通过对象进行访问Person p; p.func();
      • 通过类名进行访问Person::func();

1.3C++对象模型和this指针
1.3.1成员变量和成员函数分开存储

在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上(体现在sizeof(类的对象))

C++编译器会为每个空对象分配一个字节空间,为了区分空对象占内存的位置

1.3.2this指针

this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用
this指针是指针常量,指向不可修改

  • this指针的用途:
    • 当形参和成员变量同名时,可用this指针区分
    • 在类的非静态成员函数中返回对象本身,可使用return *this
    class Person
    {
    public:
         Person(int age)
         {
             this->age = age;
         }
    
         Person & PersonAddAge(Person p)
         {
             this->age += p.age;
             //返回对象本身
             return *this;
         }
         int age;
    };
    Person p1(10);
    Person p2(10);
    //链式编程思想
    p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);  
    

1.3.3空指针访问成员函数

C++中空指针是可以调用成员函数的,但是要注意是否用到this指针,如果用到this指针,需要加以判断保证代码(判断是否为NULL)的健壮性

1.3.4const修饰成员函数

  • 常函数

    • 成员函数后加const之后的函数称为常函数void showPerson() const {},const修饰的是this指向,让指针指向的值也不可以修改。
    • 常函数内不可以修改成员属性
    • 成员属性声明时加关键字mutable后,在常函数中依然可以修改
  • 常对象

    • 声明对象前加const后的对象称为常对象const Person p;
    • 常对象只能调用常函数

1.4友元

在程序里,有些私有的属性想让类外特殊的一些函数或者类进行访问,需要用到友元
友元的目的是让一个函数或者类访问另一个类中的私有成员
友元的关键字为friend

  • 友元的三种实现方法
    • 全局函数作友元
      friend void goodgay(Building *building);放在类中可以告诉编译器goodgay可以访问类中的私有内容
    • 类作友元
      friend class goodgay;
    • 成员函数作友元
      friend void GoodGay::visit();

1.5运算符重载

运算符重载:对已有运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。

1.5.1加号运算符重载

实现两个自定义数据类型相加的运算

  • 通过成员函数重载
    //在类内的成员函数
    Person operator+(const Person &p){
      Person temp;
      temp.m_A = this->m_A + p.m_A;
      temp.m_B = this->m_B + p.m_B;
      return temp;
    } 
    
  • 通过全局函数重载
    Person operator+(const Person &p1, const Person &p2){
      Person temp(0,0);
      temp.m_A = p1.m_A + p2.m_A;
      temp.m_B = p1.m_B + p2.m_B;
      return temp;
    }
    

1.5.2左移运算符重载

通常不会利用成员函数实现左移运算符,因为无法实现cout在<<左侧

ostream & operator<<(ostream &cout, Person &p){
    cout << "m_A= " << p.m_A << "m_B= " << p.m_B;
    return cout;
}

1.5.3函数调用运算符重载

  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定的写法

1.6继承
1.6.1继承的基本语法
class 子类: 继承方式 父类,如class A: public B

子类也称为派生类,派生类中的成员包含两大部分:①从基类继承过来的②自己增加的成员
父类也称为基类

1.6.2继承方式

class A
{
public:
        int a;
protected:
        int b;
private:
        int c;
}
  • 公共继承
class B: public A
{
public:
        int a;
protected:
        int b;
不可访问:
        int c;
}
  • 保护继承
class B: protected A
{
protected:
        int a;
        int b;
不可访问:
        int c;
}
  • 私有继承
class B: private A
{
private:
        int a;
        int b;
不可访问:
        int c;
}

1.6.3继承中的对象模型

父类中的所有非静态成员属性都会被子类继承下去
父类中的私有成员属性,是被编译器隐藏了,因此是访问不到,但是确实被继承

1.6.4继承中构造和析构顺序

先调用父类的构造函数再调用子类的构造函数析构的顺序相反

1.6.5继承同名成员的处理方式

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域(即:: )

如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数。如果想访问到父类中被隐藏的同名成员函数,需要加作用域。

1.6.6继承同名静态成员处理方式

静态成员和非静态成员出现同名,处理方式一致

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域(即:: )

1.6.7多继承语法

C++允许一个类继承多个类

语法:class 子类: 继承方式 父类1, 继承方式 父类2, ···

多继承可能会引发父类中有同名成员出现,需要加作用域区分

1.6.8菱形继承

概念:两个派生类继承同一个基类,又有某个类同时继承两个派生类,这种继承称为菱形继承

利用虚继承可以解决菱形继承的问题,在继承之前加上关键字virtual

1.7多态
1.7.1多态的基本概念

  • 多态可分为两类
    • 静态多态:函数重载运算符重载属于静态多态,复用函数名
    • 动态多态:派生类虚函数实现运行时多态
      • 动态多态满足:①有继承关系②子类重写父类的虚函数(重写是指函数返回值、函数名、参数列表均相同)
      • 动态多态的使用:父类的指针或者引用 指向子类对象
  • 静态多态与动态多态的区别
    • 静态多态的函数地址早绑定–编译阶段确定函数地址
    • 动态多态的函数地址晚绑定–运行阶段确定函数地址
  • 多态带来的好处
    • 组织结构清晰
    • 可读性强
    • 对于前期后期的扩展和可维护性高

1.7.2纯虚函数和抽象类

在多态中,父类中虚函数的实现通常无意义,主要调用子类重写的内容。因此可以将虚函数改为纯虚函数。类中有了纯虚函数,则该类称为抽象类

纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;

  • 抽象类特点
    • 无法实例化对象
    • 子类必须重写抽象类的纯虚函数,否则也属于抽象类

1.7.3虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构

  • 虚析构和纯虚析构的共性
    • 可以解决父类指针释放子类对象
    • 都需要有具体的函数实现
  • 虚析构和纯虚析构的区别
    • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:virtual ~类名(){};
纯虚析构语法:virtual ~类名() = 0;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值