C++必修:类与对象(三)

Betty’s Sweet 2024-08-27 14:05:03 阅读 64

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C++学习

贝蒂的主页:Betty’s blog

1. 隐式类型转换

在学习C语言时我们就明白,当我们进行赋值时,如果赋值两边的类型不同时就可能发生隐式类型转换

1.1. 内置类型

在发生隐式类型转换时,如果都是内置类型就会先开辟一个临时变量,再将右操作数强制类型转换为左操作数的类型,最后用这个临时变量对左操作数进行赋值。注意:这个临时变量具有常性,不可修改。

img

<code>int main()

{ -- -->

double j = 1.1;

int i = j;//隐式类型转换

int& a = j;//error

const int& b = j;//ok

return 0;

}

因为临时变量具有常性,所以无法被修改。如果赋值给普通引用就会造成权限的放大,所以只能用常引用。

1.2. 自定义类型

如果将一个内置赋值给自定义类型,那么编译器也会先创造一个自定义类型的**临时变量,**然后用这个内置类型调用构造函数对临时变量初始化,最后用这个临时变量对左操作数进行拷贝构造。。注意:这个临时变量也具有常性,不可修改。

img

<code>class Date

{ -- -->

public:

Date(int year = 1, int month = 1, int day = 1)

{

_year = year;

_month = month;

_day = day;

}

Date(const Date& d)

{

_year = d._year;

_month = d._month;

_day = d._day;

}

void Print()

{

cout << _year << "-" << _month << "-" << _day << endl;

}

private:

int _year;

int _month;

int _day;

};

int main()

{

Date d1 = 2023;//发生隐式类型转换

Date d2 = { 2023,2 };

d1.Print();

d2.Print();

return 0;

}

1.3. explicit关键字

用explicit修饰构造函数,将会禁止构造函数的隐式转换。

class Date

{

public:

explicit Date(int year = 1, int month = 1, int day = 1)

{

_year = year;

_month = month;

_day = day;

}

Date(const Date& d)

{

_year = d._year;

_month = d._month;

_day = d._day;

}

void Print()

{

cout << _year << "-" << _month << "-" << _day << endl;

}

private:

int _year;

int _month;

int _day;

};

int main()

{

Date d1 = 2023;//发生隐式类型转换

Date d2 = { 2023,2 };

d1.Print();

d2.Print();

return 0;

}

img

2. 类的静态成员

2.1. 定义

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数

<code>class A

{ -- -->

public:

static int Print()//静态成员函数

{

cout << "Print()" << endl;

}

private:

static int _a;//静态成员变量

};

2.2. 注意

静态成员也是类的成员,受public、protected、private 访问限定符的限制。静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区。所以可以通过类名::静态成员或者 对象.静态成员来访问。

class A

{

public:

static void Print()//静态成员函数

{

cout << "Print()" << endl;

}

private:

static int _a;//静态成员变量

};

int main()

{

A a;

a.Print();

A::Print();

return 0;

}

img

静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明。

<code>int A::_a = 1;//类外定义

静态成员函数没有隐藏的this指针,不能访问任何非静态成员。比如说:静态函数就无法访问非静态函数。

static void Print()//静态成员函数

{ -- -->

cout << "Print()" << endl;

Add(1, 2);//无法访问非静态成员

}

int Add(int x, int y)

{

return x + y;

}

但是非静态成员函数能访问静态成员函数。

static void Print()//静态成员函数

{

cout << "Print()" << endl;

}

int Add(int x, int y)

{

return x + y;

Print();

}

3. 友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

友元分为:友元函数和友元类。

3.1. 友元函数

有时在类外我们需要访问类中的数据,但由于访问限定符的限制并不能访问私有成员。这时如果一定要访问的话就需要借助我们的友元函数,它的用法十分简单,只用在类中加入friend+该函数的声明

class Date

{

friend void Print(const Date& d);//友元函数

public:

Date(int year = 1, int month = 1, int day = 1)

{

_year = year;

_month = month;

_day = day;

}

Date(const Date& d)

{

_year = d._year;

_month = d._month;

_day = d._day;

}

private:

int _year;

int _month;

int _day;

};

void Print(const Date&d)

{

cout << d._year << "-" << d._month << "-" << d._day << endl;

}

当然我们也能通过在类中声明获取私有元素的返回函数实现:

class Date

{

public:

Date(int year = 1, int month = 1, int day = 1)

{

_year = year;

_month = month;

_day = day;

}

Date(const Date& d)

{

_year = d._year;

_month = d._month;

_day = d._day;

}

int GetYear()const

{

return _year;

}

int GetMonth()const

{

return _month;

}

int GetDay()const

{

return _day;

}

private:

int _year;

int _month;

int _day;

};

void Print(const Date& d)

{

cout << d.GetYear() << "-" << d.GetMonth() << "-" << d.GetDay() << endl;

}

注意:

友元函数是可访问类的私有和保护成员,但不是类的成员函数友元函数不能用const修饰友元函数可以在类定义的任何地方声明,不受类访问限定符限制一个函数可以是多个类的友元函数友元函数的调用与普通函数的调用原理相同

3.2. 友元类

除了友元函数外,还有一种友元类。友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。与友元函数用法类似:在一个类中声明friend+class+类名

class Time

{

friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类

public:

Time(int hour = 0, int minute = 0, int second = 0)

: _hour(hour)

, _minute(minute)

, _second(second)

{ }

private:

int _hour;

int _minute;

int _second;

};

class Date

{

public:

Date(int year = 1900, int month = 1, int day = 1)

: _year(year)

, _month(month)

, _day(day)

{ }

void SetTimeOfDate(int hour, int minute, int second)

{

// 直接访问时间类私有的成员变量

_t._hour = hour;

_t._minute = minute;

_t._second = second;

}

private:

int _year;

int _month;

int _day;

Time _t;

};

注意:

**友元关系是单向的,不具有交换性。**比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。**友元关系不能传递。**如果C是B的友元, B是A的友元,则不能说明C时A的友元。友元关系不能继承。在继承位置再给大家详细介绍。

4. 内部类

4.1. 定义

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。并且内部类就是外部类的友元类。

class A

{

public:

class B//B是A的友元

{

private:

int _m;

};

private:

int _a;

int _b;

};

4.2. 注意

内部类可以定义在外部类的public、protected、private都是可以的。注意内部类可以直接访问外部类中的static成员,不需要外部类的对象 / 类名。

class A

{

private:

static int k;

int h;

public:

class B // B天生就是A的友元

{

public:

void foo()

{

cout << k << endl;//OK

}

};

};

int A::k = 1;

int main()

{

A::B b;

b.foo();

return 0;

}

img

外部类的大小与内部类的大小没有关系,sizeof(外部类) = 外部类。

<code>class A

{ -- -->

private:

int k;

int h;

public:

class B // B天生就是A的友元

{

public:

int _a;

};

};

int main()

{

A a;

cout << sizeof(a) << endl;

return 0;

}

img

5. 匿名对象

匿名对象与C语言中的匿名结构体类似,只有类名,作用域只在匿名对象声明的一行。

<code>class Date

{ -- -->

public:

Date(int year = 1, int month = 1, int day = 1)

{

cout << "Date" << endl;

_year = year;

_month = month;

_day = day;

}

Date(const Date& d)

{

cout << "Date(const Date& d)" << endl;

_year = d._year;

_month = d._month;

_day = d._day;

}

void Print()

{

cout << _year << "/" << _month << "/" << _day << endl;

}

~Date()

{

cout << "~Date()" << endl;

_year = _month = _day = 0;

}

private:

int _year;

int _month;

int _day;

};

int main()

{

Date();//匿名对象

Date d;

return 0;

}

img

但是我们也能通过引用延长匿名对象的生命周期。

<code>int main()

{ -- -->

const Date& dc = Date();//匿名对象也具有常性

Date d;

return 0;

}

img

6. 编译器的一些优化

下面我们将介绍一些编译器对自定义类型常见的优化,我们还是以日期类举例:

<code>class Date

{ -- -->

public:

Date(int year = 1, int month = 1, int day = 1)

{

cout << "Date" << endl;

_year = year;

_month = month;

_day = day;

}

Date(const Date& d)

{

cout << "Date(const Date& d)" << endl;

_year = d._year;

_month = d._month;

_day = d._day;

}

void Print()

{

cout << _year << "/" << _month << "/" << _day << endl;

}

~Date()

{

cout << "~Date()" << endl;

_year = _month = _day = 0;

}

private:

int _year;

int _month;

int _day;

};

直接构造+拷贝构造=>直接构造

img

直接构造+拷贝构造+拷贝构造=>直接构造

img

直接构造+直接构造+拷贝构造+赋值重载=>直接构造+直接构造

img

7. 日期类的模拟实现

下面让我们来实现一个功能比较丰富的日期类。

7.1. 项目功能

日期之间的比较(>,<,>=,<=,==,!=)。日期的递增与递减。日期+天数(+,+=)。日期-天数(-,-=)。日期-日期。流输入与流输出。打印日期。

7.2. 功能实现

7.2.1. 构造与析构函数

在写构造函数之前我们需要写一个函数判断当前日期是否合法。

<code>int Date:: GetMonDay(int year, int month)//或许当月天数

{ -- -->

int months[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };

int day = months[month];

if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))

{

day++;

}

return day;

}

构造函数与拷贝构造与赋值重载。注意:缺省参数在定义时不需要书写

Date::Date(int year, int month, int day)//构造

{

assert(month > 0 && month < 13);

int days = GetMonDay(year, month);

if (day >= 0 && day <= days)

{

_year = year;

_month = month;

_day = day;

}

else

{

cout << "输入日期不合法" << endl;

}

}

Date::Date(const Date& d)//拷贝构造

{

_year = d._year;

_month = d._month;

_day = d._day;

}

Date& Date::operator=(const Date& d)//赋值重载

{

if (this != &d)

{

_year = d._year;

_month = d._month;

_day = d._day;

}

return *this;

}

析构函数:

Date::~Date()

{

_year = _month = _day = 0;

}

7.2.2. 两个日期之间的比较

bool Date::operator==(const Date& d) const//判断是否相当

{

return _year == d._year

&& _month == d._month

&& _day == d._day;

}

bool Date::operator!=(const Date& d) const//判断是否不相等

{

return !(*this == d);

}

bool Date::operator>(const Date& d) const//大于

{

if (_year > d._year)

{

return true;

}

else if (_year == d._year && _month > d._month)

{

return true;

}

else if (_year == d._year && _month == d._month && _day > d._day)

{

return true;

}

else

{

return false;

}

}

bool Date::operator>=(const Date& d) const//大于等于

{

return *this == d || *this > d;

}

bool Date::operator<(const Date& d)const//小于

{

return !(*this >= d);

}

bool Date::operator<=(const Date& d)const//小于等于

{

return !(*this > d);

}

7.2.3. 自增与自减

为了区分前置与后置的区别,C++规定在重载时前置并不需要额外的参数,后置需要一个额外的int参数。

Date& Date::operator++()//前置++

{

*this += 1;

return *this;

}

Date Date::operator++(int)//后置++

{

Date tmp(*this);

*this += 1;

return tmp;

}

Date& Date::operator--()//前置--

{

*this -= 1;

return *this;

}

Date Date::operator--(int)//后置--

{

Date tmp(*this);

*this -= 1;

return tmp;

}

7.2.4. 日期与天数直接的计算

Date& Date::operator+=(int day)//+=

{

if (day < 0)

{

*this -= -day;

return *this;

}

_day += day;

while (_day > GetMonDay(_year, _month))

{

_day -= GetMonDay(_year, _month);

++_month;

if (_month > 12)

{

_month = 1;

_year++;

}

}

return *this;

}

Date Date::operator+(int day)const//+

{

Date tmp(*this);

tmp += day;

return tmp;

}

Date& Date::operator-=(int day)//-=

{

if (day < 0)

{

*this += -day;

return *this;

}

while (day <= 0)

{

--_month;

if (_month == 0)

{

--_year;

_month = 12;

}

_day += GetMonDay(_year, _month);

}

return *this;

}

Date Date::operator-(int day)const//-

{

Date tmp(*this);

tmp -= day;

return tmp;

}

int Date::operator-(const Date& d)const//相差

{

Date max = *this;

Date min = d;

int flag = 1;

if (*this < d)

{

max = d;

min = *this;

flag = -1;

}//求最大

int n = 0;

while (min != max)

{

++min;

++n;

}

return flag * n;

}

7.2.5. 打印

void Date::Print()const//打印

{

cout<< _year << "/" << _month << "/" << _day;

}

7.2.6. 流插入与流提取

因为重载后的运算符顺序与参数顺序相同,为了符合习惯,我们只好在类外定义。并且因为函数较短,可以声明为了内联函数。

inline ostream& operator<<(ostream& out, const Date& d)//流输出

{

out << d._year << "/" << d._month << "/" << d._day <<endl;

return out;

}

inline istream& operator>>(istream& in, Date& d)//流输入

{

in >> d._year >> d._month >> d._day;

return in;

}

7.3. 完整代码

7.3.1. Date.h

#pragma once

#include<iostream>

#include<assert.h>

using namespace std;

class Date

{

friend inline ostream& operator<<(ostream& out, const Date& d);

friend inline istream& operator>>(istream& in, Date& d);

public:

int GetMonDay(int year, int month);//或许当月天数

Date(int year = 1, int month = 1, int day = 1);//构造

Date(const Date& d);//拷贝构造

Date& operator=(const Date& d);//赋值重载

bool operator==(const Date& d) const;//判断是否相当

bool operator!=(const Date& d) const;//判断是否不相等

bool operator>(const Date& d) const;//大于

bool operator>=(const Date& d) const;//大于等于

bool operator<(const Date& d)const;//小于

bool operator<=(const Date& d)const;//小于等于

Date& operator+=(int day);//+=

Date operator+(int day)const;//+

Date& operator-=(int day);//-=

Date operator-(int day)const;//-

Date& operator++();//前置++

Date operator++(int);//后置++

Date& operator--();//前置--

Date operator--(int);//后置--

int operator-(const Date& d)const;//相差

void Print()const;//打印

~Date();

private:

int _year;

int _month;

int _day;

};

inline ostream& operator<<(ostream& out, const Date& d)//流输出

{

out << d._year << "/" << d._month << "/" << d._day <<endl;

return out;

}

inline istream& operator>>(istream& in, Date& d)//流输入

{

in >> d._year >> d._month >> d._day;

return in;

}

7.3.2. Date.cpp

#include"Date.h"

int Date:: GetMonDay(int year, int month)//或许当月天数

{

int months[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };

int day = months[month];

if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))

{

day++;

}

return day;

}

Date::Date(int year, int month, int day)//构造

{

assert(month > 0 && month < 13);

int days = GetMonDay(year, month);

if (day >= 0 && day <= days)

{

_year = year;

_month = month;

_day = day;

}

else

{

cout << "输入日期不合法" << endl;

}

}

Date::Date(const Date& d)//拷贝构造

{

_year = d._year;

_month = d._month;

_day = d._day;

}

Date& Date::operator=(const Date& d)//赋值重载

{

if (this != &d)

{

_year = d._year;

_month = d._month;

_day = d._day;

}

return *this;

}

bool Date::operator==(const Date& d) const//判断是否相当

{

return _year == d._year

&& _month == d._month

&& _day == d._day;

}

bool Date::operator!=(const Date& d) const//判断是否不相等

{

return !(*this == d);

}

bool Date::operator>(const Date& d) const//大于

{

if (_year > d._year)

{

return true;

}

else if (_year == d._year && _month > d._month)

{

return true;

}

else if (_year == d._year && _month == d._month && _day > d._day)

{

return true;

}

else

{

return false;

}

}

bool Date::operator>=(const Date& d) const//大于等于

{

return *this == d || *this > d;

}

bool Date::operator<(const Date& d)const//小于

{

return !(*this >= d);

}

bool Date::operator<=(const Date& d)const//小于等于

{

return !(*this > d);

}

Date& Date::operator+=(int day)//+=

{

if (day < 0)

{

*this -= -day;

return *this;

}

_day += day;

while (_day > GetMonDay(_year, _month))

{

_day -= GetMonDay(_year, _month);

++_month;

if (_month > 12)

{

_month = 1;

_year++;

}

}

return *this;

}

Date Date::operator+(int day)const//+

{

Date tmp(*this);

tmp += day;

return tmp;

}

Date& Date::operator-=(int day)//-=

{

if (day < 0)

{

*this += -day;

return *this;

}

while (day <= 0)

{

--_month;

if (_month == 0)

{

--_year;

_month = 12;

}

_day += GetMonDay(_year, _month);

}

return *this;

}

Date Date::operator-(int day)const//-

{

Date tmp(*this);

tmp -= day;

return tmp;

}

Date& Date::operator++()//前置++

{

*this += 1;

return *this;

}

Date Date::operator++(int)//后置++

{

Date tmp(*this);

*this += 1;

return tmp;

}

Date& Date::operator--()//前置--

{

*this -= 1;

return *this;

}

Date Date::operator--(int)//后置--

{

Date tmp(*this);

*this -= 1;

return tmp;

}

int Date::operator-(const Date& d)const//相差

{

Date max = *this;

Date min = d;

int flag = 1;

if (*this < d)

{

max = d;

min = *this;

flag = -1;

}//求最大

int n = 0;

while (min != max)

{

++min;

++n;

}

return flag * n;

}

void Date::Print()const//打印

{

cout<< _year << "/" << _month << "/" << _day;

}

Date::~Date()

{

_year = _month = _day = 0;

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。