02~04-C++类和对象 |类的分离定义|this指针|类默认函数|运算符重载operator|const成员函数|初始化列表|友元函数|匿名对象| (复习笔记)

02-类与对象(上篇)

类的分离定义

类定义了一个新的作用域,类的所有成员都在类的作用域中在类体外定义成员时,需要使用::作用域操作符指明成员属于哪个类域。

class Person
{
public:
 void PrintPersonInfo();
private:
 char _name[20];
 char _gender[3];
 int  _age;
 static int _count;
};

int Person::_count = 1;
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
 cout << _name << " "<< _gender << " " << _age << endl;
}

问题:C++中struct和class的区别是什么?

解答:C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。注意:在继承和模板参数列表位置,struct和class也有区别,后序给大家介绍。

类对象模型

类对象中存储成员变量,成员函数存储在公共代码段

计算类的大小:

// 类中既有成员变量,又有成员函数:4
class A1 {
public:
    void f1(){}
private:
    int _a;
};

// 类中仅有成员函数:1
class A2 {
public:
   void f2() {}
};

// 类中什么都没有-空类:1
class A3
{};

结论:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐

注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。

this指针

一般人都知道成员函数中的变量是this指针指向的,所以可以不写

const修饰的this指针——const成员函数

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改,在纯输出的成员函数可以保护内部数据

问题:this指针存在哪里

解答:this的本质是一个隐藏的形参,形参在函数调用时压栈做临时变量,所以this指针存储在栈中。this指针会频繁调用,部分编译器将this指针优化存储在寄存器ECX中。

Date* ptr = nullptr;

//正常运行
ptr->func();
(*ptr).func();

//程序崩溃
ptr->Init(2022, 2, 2);

解释:
成员函数存储在公共代码段中,所以不需要解引用this指针,即使为空也能正常调用。
但当调用的成员函数中需要获取this指针指向的变量时,程序就会崩溃

03-类与对象(中篇)

类的6个默认成员函数

构造函数

构造函数的作用:不是开辟对象空间,而是创建对象后编译器自动调用来将对象赋初值的函数。

特征:函数名与类名相同、无返回值、无参数构造函数和全缺省参数构造函数只能有一个

问题:构造函数自己不定义,编译器也会自动生成构造函数,那为什么还要自己定义?

解答:编译器生成的构造函数非常鸡肋,默认给内置成员变量赋随机值(自定义成员变量调用它自己的构造函数)

//补丁:C++11支持在成员变量声明时可以给默认值
class Date
{
private:
 // 基本类型(内置类型)
 int _year = 1970;
 int _month = 1;
 int _day = 1;
 // 自定义类型
 Time _t;
};

析构函数

析构函数的作用是:当对象被编译器销毁时完成资源的清理工作。

特征:函数名是在类名之前加~、无返回值、析构函数唯一

拷贝构造函数

拷贝构造函数搭配作用是:以已有对象为模板创建新对象

特征:拷贝构造函数是构造函数的重载形式,参数只有const 类对象的引用,

拷贝构造函数的参数只有一个必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

编译器生成的默认拷贝构造函数是浅拷贝,如果涉及到资源申请的必须自己写拷贝构造函数(malloc)

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

运算符重载

class Date
{ 
public:
 Date(int year = 1900, int month = 1, int day = 1)
   {
        _year = year;
        _month = month;
        _day = day;
   }
    
    // bool operator==(Date* this, const Date& d2)
    // 这里需要注意的是,左操作数是this,指向调用函数的对象
    // 将重载函数直接封装在类的内部,参数中隐含this指针
    bool operator==(const Date& d2)
 {
        return _year == d2._year;
            && _month == d2._month
            && _day == d2._day;
 }

    Date& operator=(const Date& d)
 {
       if(this != &d)
       {
            _year = d._year;
            _month = d._month;
            _day = d._day;
       }
       return *this;
 }

private:
 int _year;
 int _month;
 int _day;
};

赋值重载函数

1、编译器会自动生成默认的赋值重载函数,所以赋值运算符只能重载成类的成员函数而不能重载成全局函数

2、编译器自动生成的赋值重载函数是浅拷贝,涉及资源管理的函数必须自己手搓拷贝构造

前置++和后置++重载
//前置++
Date& operator++()
 {
     _day += 1;
     return *this;
 }



//后置++
Date operator++(int)
 {
     Date temp(*this);
     _day += 1;
     return temp;
 }
 // C++规定:后置++重载时多增加一个int类型的参数
 // 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
一份,然后给this+1
 // temp是临时对象,因此只能以值的方式返回,不能返回引用

取地址及const取地址操作符重载

&和const &一般由编译器自动生成,不需要重载

04-类和对象(下篇)

再谈构造函数

前言:虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值

初始化列表

在构造函数之后以冒号开头,以逗号分割,每一个成员变量后面跟一个放在括号内的初始值

class Date
{
public:
    Date(int year, int month,int day)
     : _year(year)
     , _month(month)
     , _day(day)
    {}

private:
    int _year;
    int _month;
    int _day;
};

解释:const成员只能在初始化时赋初始值,一旦实例化无法改变

尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。

static成员

static成员函数就是无需实例化对象就能调用的函数

static成员变量就是在所有对象中的值都一样的静态变量,静态成员变量必须在类外声明

class A
{
public:
    A(int x)
    {
        cout << "A()" << endl;
    }

    ~A()
    {
        cout << "~A()" << endl;
    }

    static void test()
    {
        _num++;
    }

    int getvalue()
    {
        return _num;
    }

private:
    static int _num;
};
int A::_num = 0;


int main()
{
    A a1;
    A a2;
    cout << a1.getvalue() << endl;
    cout << a2.getvalue() << endl;
    A::test();
    A::test();
    A::test();
    A::test();
    A::test();
    cout << a1.getvalue() << endl;
    cout << a2.getvalue() << endl;
    return 0;
}

输出:
A()
A()
0
0
5
5
~A()
~A()

【问题】

1. 静态成员函数可以调用非静态成员函数吗?

答:可以,但是静态成员函数不带this指针,需要在参数中传入类对象的地址,在静态函数内部通过地址解引用调用函数

2. 非静态成员函数可以调用类的静态成员函数吗?

答:显然可以

友元函数

引入:我们希望自定义cout输出类时的内容,需要重载operator <<,如果将重载函数写在类的内部,则第一个参数默认为this指针,但是cout的使用习惯将第一个参数为cout,所以我们必须在函数外实现对cout的重载,同时还要访问类对象内的成员变量进行打印。

像这样,为了满足因为使用参数习惯要在类外部定义、同时要能访问类内部私有变量的函数,引入了友元函数

#include<iostream>
using namespace std;

class Date
{
    friend ostream& operator<<(ostream& _cout, Date& _this);

public:
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void printline()
    {
        cout << _year << " " << _month << ' ' << _day << endl;
    }

private:
    int _year = 2000;
    int _month = 1;
    int _day = 1;
};

ostream& operator<<(ostream& _cout, Date& _this)
{
    _cout << _this._year << " " << _this._month << ' ' << _this._day << endl;
    return _cout;
}

int main()
{
    Date d1 = {2024,2,17};
    cout << d1 << endl;
    return 0;
}

注意:友元函数只是能访问类成员变量的普通函数,不属于任何类的成员函数,但要在类中加friend函数声明

匿名对象

int main()
{
 // 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
 //A aa1();
 // 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
 // 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
 A();
 A aa2(2);
 // 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
 Solution().Sum_Solution(10);
 //只想使用成员函数,就可以借助匿名对象调用内部函数
 return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值