外贸建个网站多少钱,室内设计专业招聘信息,网页设计与网站开发的卷子,wordpress发邮件插件【C笔记】类和对象的深入理解(三) #x1f525;个人主页#xff1a;大白的编程日记
#x1f525;专栏#xff1a;C笔记 文章目录 【C笔记】类和对象的深入理解(三)前言一.日期类的实现1.1声明和定义分离1.2日期类整数1.3日期类整数1.4日期类-整数1.5日期类-日期1.6复用对…【C笔记】类和对象的深入理解(三) 个人主页大白的编程日记
专栏C笔记 文章目录 【C笔记】类和对象的深入理解(三)前言一.日期类的实现1.1声明和定义分离1.2日期类整数1.3日期类整数1.4日期类-整数1.5日期类-日期1.6复用对比1.7日期类日期类1.8日期类日期类1.9比较复用1.10前置和后置1.11日期类-日期类1.12日期类IO 二.取地址运算符重载2.1const成员函数2.2取地址运算符重载 三.再探构造函数四.类型转换五.static成员六.友元七.内部类八.匿名对象后言 前言 哈喽各位小伙伴大家好上期我们讲了类和对象的更深入的内容。今天我们就给类和对象收尾。话不多说咱们进入正题向大厂冲锋 一.日期类的实现
为了加深我们前面对类和对象的理解。我们现在来试着手搓一个日期类。
1.1声明和定义分离
我们想让.h放函数的声明。.cpp放函数的定义。
#pragma once#includeiostream
using namespace std;
#includeassert.hclass Date
{friend istream operator(istream in, Date d);friend ostream operator(ostream out, const Date d);//友元声明
public:Date(int year ,int month ,int day );void Print();// Ĭinlineint GetMonthDay(int year, int month){assert(month 0 month 13);static int monthDayArray[13] { -1, 31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31 };if (month 2 ((year % 4 0 year % 100 ! 0) || (year % 400 0))){return 29;}return monthDayArray[month];}int GetYear();int GetMonth();int GetDay();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;bool CheckDate();Date operator(int day) const;Date operator(int day);Date operator(int);//后置Date operator();//前置Date operator--(int);//后置Date operator--();//前置Date operator-(int day);Date operator-(int day) const;int operator-(const Date d) const;
private:int _year;int _month;int _day;
};
istream operator(istream in, Date d);
ostream operator(ostream out, const Date d);我们日期类主要实现他的比较和日期类之间的相互运算。
1.2日期类整数
这里我们的思路是先将整数加到天数上。然后如果天数不超过当月天数。直接返回即可。否则说明日期比当月的所有日期都大。则向下月借位。天数减去当月天数之和以此类推知道天数不超过当月的最大天数位置。如果12借位则年份1,月份改为1即可。
int GetMonthDay(int year, int month)
{assert(month 0 month 13);static int monthDayArray[13] { -1, 31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31 };if (month 2 ((year % 4 0 year % 100 ! 0) || (year % 400 0))){return 29;}return monthDayArray[month];
}
Date Date::operator(int day)
{_day day;while (_day GetMonthDay(_year, _month)){_day - GetMonthDay(_year, _month);_month;if (_month 13){_year;_month 1;}}return *this;
}1.3日期类整数
现在我们实现日期类整数也就是不改变日期类。返回日期类整数的结果。那我们可以复用日期类整数的逻辑。为了不修改日期类。我们对日期类拷贝一份tmp。让tmp进行的逻辑这样改变就不会日期类。
Date Date::operator(int day) const
{Date tmp*this;tmp day;return tmp;
}1.4日期类-整数
这里思路和我们前面实现的思路一样。都是通过借位的思想。我们向当天数-day。 然后当天数!0时向上一个月借位天数上个月的天数(注意是上个月的因为当月的天数已经被减过了)。如果是1月的话借去年12月的天数。让月份12然后年份–。
Date Date::operator-(int day)
{_day - day;while (_day 0){if (_month 1){_month 12;_day GetMonthDay(--_year, _month);}else{_day GetMonthDay(_year, --_month);}}return *this;
}1.5日期类-日期
日期类-日期我们就复用-即可。
Date Date::operator-(int day) const
{Date tmp*this;tmp - day;return tmp;
}1.6复用对比 1.7日期类日期类
判断两个日期类很简单。我们只需要判断是否他们的年月日都相等即可。
bool Date::operator(const Date d) const
{if (_year d._year _month d._month _day d._day){return true;}else{return false;}
}1.8日期类日期类
判断两个日期类。 先比较年。 返回true。 继续比较月。月返回true。 返回比较天。 剩下的情况都是false的情况。直接返回false.
bool Date::operator(const Date d) const
{if (_year d._year)//比较年{return true;}else if (_year d._year)//年相等{if (_month d._month)//判断月{return true;}else if (_month d._month)//月相等{return _day d._day; //判断天}}return false;//年不相等
}1.9比较复用
剩下的我们通过前面实现的和复用即可。
bool Date::operator!(const Date d) const
{return !(*this d);
}
bool Date::operator(const Date d) const
{return *this d || *this d;
}
bool Date::operator(const Date d) const
{return !(*this d)!(*thisd);
}
bool Date::operator(const Date d) const
{return !(*this d);
}1.10前置和后置
前置我们拷贝一份*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;
}
Date Date::operator--()
{*this - 1;return *this;
}1.11日期类-日期类
两个日期类相减得出他们之间相差的天数。 我们的思路就是直接让小的天数一直相加直到相加到小的日期和大的日期相等即可。每次日期就用一个变量记录天数即可。
int Date::operator-(const Date d) const
{int sum 0;int flag 1;Date max, min;max *this;min d;if (*this d)//假设法{swap(max, min);flag -1;}while (min!max){sum; min;}sum *flag;return sum;
}1.12日期类IO
因为类的成员函数默认*this抢占了第一个位置。和我们平时的写法不一样。所以我们把日期类的输出和输出放在类外面。那放在类外面我们如何访问类的成员变量呢那我们就可以加一个友元函数声明。
istream operator(istream in, Date d)
{cout请依次输入年月日:endl;in d._year d._month d._day;return in;
}
ostream operator(ostream out, const Date d)
{out d._year 年 d._month月 d._day日;return out;
}为了实现连续输入或输出。我们需要返回istrem或ostrem对象。 同时注意流对象不支持拷贝。所以istrem或ostrem必须用引用。
二.取地址运算符重载
2.1const成员函数 定义 将const修饰的成员函数称之为const成员函数const修饰成员函数放到成员函数参数列表的后面。 const修饰 const实际修饰该成员函数隐含的this指针表明在该成员函数中不能对类的任何成员进行修改。const 修饰Date类的Print成员函数Print隐含的this指针由 Date* const this 变为const Date* const this。 不写加const。
加上const。 同时非const成员也可以调用const成员函数。因为权限可以缩小。所以加上const可以防止我们的程序不小心篡改(原本不应该修改却不小心修改会报错)同时也让我们的的成员函数传参更宽泛const成员也可以调用。所以能加尽加。
2.2取地址运算符重载 取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载⼀般这两个函数编译器自动生成的就可以够我们⽤了不需要去显示实现。除非⼀些很特殊的场景比如我们不想让别⼈取到当前类对象的地址就可以自己实现⼀份胡乱返回⼀个地址。 class Date
{
public:Date* operator(){return this;// return nullptr;}const Date* operator()const{return this;// return nullptr;}
private:int _year; // 年int _month; // ⽉int _day; // ⽇
};一般这两个成员函数都不需要我们显示的写。因为他们两个是默认成员函数。
按理说我们只写const成员函数即可。因为不论是const成员还是非const成员都可以调用。但是const成员调用返回的是const this*。所以还需要写两份。并且编译器调用时会调用最匹配的。
三.再探构造函数
初始化列表 之前我们实现构造函数时初始化成员变量主要使用函数体内赋值构造函数初始化还有⼀种方式就是初始化列表初始化列表的使用方式是以⼀个冒号开始接着是⼀个以逗号分隔的数据成员列表每个成员变量后面跟⼀个放在括号中的初始值或表达式。
大概形式如下 Date(int x, int year 1, int month 1, int day 1):_year(year),_month(month),_day(day),_t(12),_ref(x),_n(1){// error C2512: “Time”: 没有合适的默认构造函数可⽤
// error C2530 : “Date::_ref” : 必须初始化引⽤
// error C2789 : “Date::_n” : 必须初始化常量限定类型的对象
}定义的位置 每个成员变量在初始化列表中只能出现⼀次语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。因为初始化列表是定义的地方所以只能初始化一次。否则就会变成多次定义。 定义初始化 引用成员变量const成员变量没有默认构造的类类型变量必须放在初始化列表位置进行初始化否则会编译报错。
class Time
{
public:Time(int hour0):_hour(hour){cout Time() endl;}
private:int _hour;
};
class Date
{
public:Date(int x,int year 1, int month 1, int day 1):_year(year), _month(month), _day(day),a(1),_ref(x)//成员定义{// error C2512: “Time”: 没有合适的默认构造函数可⽤// error C2530 : “Date::_ref” : 必须初始化引⽤// error C2789 : “Date::_n” : 必须初始化常量限定类型的对象}void Print() const{cout _year - _month - _day endl;}
private://声明int _year;int _month;int _day; int main();int _ref;const int a;Time _t;
};引用成员变量const成员变量必须在初始化列表初始化。因为他们必须在定义的地方初始化。
没有默认构造的类类型也必须在初始化列表初始化因为他必须手动传参调用构造函数。 那初始化列表和函数体内赋值可以混着用吗 可以因为有些场景必须混着用。
Date(int x,int year 1, int month 1, int day 1):_year(year), _month(month), _day(day),a(1),_ref(x),_ptr((int*)malloc(size(int)//成员定义
{if (_ptr nullptr){perror(malloc fail!);}
}例如有个指针我们malloc以后需要检查是否失败。那就必须在函数体内检查。
缺省值 C11支持在成员变量声明的位置给缺省值这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。
private://声明int _year1;int _month1;int _day1; int* _ptr((int*)malloc(sizeof(int)));在声明的位置给一个缺省值。 总结 尽量使用初始化列表初始化因为那些你不在初始化列表初始化的成员也会走初始化列表如果这个成员在声明位置给了缺省值初始化列表会用这个缺省值初始化。如果你没有给缺省值对于没有显⽰在初始化列表初始化的内置类型成员是否初始化取决于编译器C并没有规定。对于没有显示在初始化列表初始化的⾃定义类型成员会调⽤这个成员类型的默认构造函数如果没有默认构造会编译错误。 所以初始化列表能写尽写因为就算你不写他也会走初始化列表。 初始化顺序 初始化列表中按照成员变量在类中声明顺序进行初始化跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持⼀致。 大家看一下这道题选什么。 可能很多同学都会觉得选A。但实际不是这样的。因为初始化列表初始化的顺序是声明的顺序。
声明是顺序是他们在内存存放的顺序。初始化是按照内存中的顺序初始化。
四.类型转换
class A
{
public://构造函数explicit就不再⽀持隐式类型转换// explicit A(int a1)A(int a1):_a1(a1){}//explicit A(int a1, int a2)A(int a1, int a2):_a1(a1), _a2(a2){}void Print(){cout _a1 _a2 endl;}
private:int _a1 1;int _a2 2;
};
int main()
{// 1构造⼀个A的临时对象再⽤这个临时对象拷⻉构造aa3//编译器遇到连续构造拷⻉构造-优化为直接构造A aa1 1;aa1.Print();const A aa2 1;// C11之后才⽀持多参数转化A aa3 { 2,2 };return 0;
}隐式类型转化 C支持内置类型隐式类型转换为类类型对象需要有相关内置类型为参数的构造函数。 这里我们可以验证一下。
这里报错是为啥 因为隐式类型转化生成是临时对象临时对象具有常性。所以要加上const引用。 那隐式类型转化有什么用
class Stack
{
public:void Push(const A aa){}
private:int top;
};
int main()
{Stack s;A aa 1;s.Push(aa);s.Push(1);return 0;
}可以发现隐式类型转化可以让我们写代码更加方便简洁。
explicit 如果不想发生隐式类型转化 构造函数前面加explicit就不再支持隐式类型转换。 五.static成员
静态成员变量 用static修饰的成员变量称之为静态成员变量静态成员变量⼀定要在类外进行初始化。共享 静态成员变量为所有类对象所共享不属于某个具体的对象不存在对象中存放在静态区。 静态成员函数 用static修饰的成员函数称之为静态成员函数静态成员函数没有this指针。 访问权限 静态成员函数中可以访问其他的静态成员但是不能访问非静态的因为没有this指针。非静态的成员函数可以访问任意的静态成员变量和静态成员函数。 指定类域 突破类域就可以访问静态成员可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数。 访问限定符 静态成员也是类的成员受public、protected、private访问限定符的限制。 缺省值 静态成员变量不能在声明位置给缺省值初始化因为缺省值是个构造函数初始化列表的静态成员变量不属于某个对象不走构造函数初始化列表。 练习一 题目求123…n
这道题目把常规的方法都限制了。但是这道题可以用我们的静态成员解决。 这道题主要修饰构造出循环这个条件。所以我们可以用一个类数组来构造这个条件。 定义一个n大小的类数组。那就会调用n次构造。我们只需要定义两个静态成员。然后每次构造把当前的值累加即可。
class Sum {public:Sum() {_ret _i;_i;}static int GetRet() {return _ret;}private:static int _i;static int _ret;
};
int Sum::_i 1;
int Sum::_ret 0;
class Solution {public:int Sum_Solution(int n) {//变⻓数组Sum arr[n];return Sum::GetRet();}
}; 练习二
C c;
int main()
{A a;B b;static D d;return 0;
}六.友元
定义 友元提供了⼀种突破类访问限定符封装的方式友元分为友元函数和友元类在函数声明或者类声明的前面加friend并且把友元声明放到⼀个类的里面。
class A
{// 友元声明friend void func(const A aa, const B bb);
private:int _a1 1;int _a2 2;
};
void func(const A aa, const B bb){cout aa._a1 endl;cout bb._b1 endl;}例如这里func是A的好朋友func就可以访问A的成员。 声明 外部友元函数可访问类的私有和保护成员友元函数仅仅是⼀种声明他不是类的成员函数。 声明位置 友元函数可以在类定义的任何地方声明不受类访问限定符限制。 多个友元 ⼀个函数可以是多个类的友元函数。
class A
{// 友元声明friend void func(const A aa, const B bb);
private:int _a1 1;int _a2 2;
};
class B
{// 友元声明friend void func(const A aa, const B bb);
private:int _b1 3;int _b2 4;
};func既是A的友元又是B的友元。
友元类 友元类中的成员函数都可以是另⼀个类的友元函数都可以访问另⼀个类中的私有和保护成员
class A
{// 友元声明friend class B;
private:int _a1 1;int _a2 2;
};
class B
{
public:void func1(const A aa){cout aa._a1 endl;cout _b1 endl;}void func2(const A aa){cout aa._a2 endl;cout _b2 endl;}
private:int _b1 3;int _b2 4;
};
int main()
{A aa;B bb;bb.func1(aa);bb.func1(aa);return 0;
}B是A的友元类。B的所有成员函数都可以访问A的成员变量。 单向性 友元类的关系是单向的不具有交换性比如A类是B类的友元但是B类不是A类的友元。 传递性 友元类关系不能传递如果A是B的友元B是C的友元但是A不是C的友元。 使用 有时提供了便利。但是友元会增加耦合度破坏了封装所以友元不宜多用。
七.内部类
定义 如果⼀个类定义在另⼀个类的内部这个内部类就叫做内部类。内部类是⼀个独立的类跟定义在全局相比他只是受外部类类域限制和访问限定符限制所以外部类定义的对象中不包含内部类。
class A
{
private:static int _k;int _h 1;
public:class B // B默认就是A的友元{public:void foo(const A a){cout _k endl; //OKcout a._h endl; //OK}private:int _b;};
};
int A::_k 1;
int main()
{cout sizeof(A) endl;A::B b;A aa;b.foo(aa);
} 所以刚刚OJ的代码也可以改成内部类
class Solution {// 内部类class Sum{public:Sum(){_ret _i;_i;}};static int _i;static int _ret;public:int Sum_Solution(int n) {// 变⻓数组Sum arr[n];return _ret;}默认友元 内部类默认是外部类的友元类。封装 内部类本质也是⼀种封装当A类跟B类紧密关联A类实现出来主要就是给B类使用那么可以考虑把A类设计为B的内部类如果放到private/protected位置那么A类就是B类的专属内部类其他地方都用不了。
八.匿名对象
定义 用类型(实参)定义出来的对象叫做匿名对象相比之前我们定义的类型对象名(实参)定义出来的叫有名对象。
匿名对象让我们写代码更方便。
生命周期 匿名对象的生命周期只在当前⼀行⼀般临时定义⼀个对象当前用⼀下即可就可以定义匿名对象。 可以看到未打印1之前A就析构了。因为匿名对象声明周期只在当前行。
后言 这就是类和对象的深入理解。今天讲了很多内容。大家自己好好消化。感谢各位的耐心垂阅咱们下期见拜拜~