栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

类和对象(3)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

类和对象(3)

接着日期类 void testDate6() {     Date d1;     cin >> d1; //>>是流提取,意为从流中提取,放到d1中。     cout << d1; //<<是流插入,意为把对象插入到流中。 } 这两个运算符都是需要重载的。 具体如何重载需要引入一个概念。 已知:内置类型可以直接使用运算符,cout会自动识别内置类型。 cout如何自动识别类型的?cout和cin具体又是什么? 先从下面这张图开始说明 cin和cout其实是全局的对象,包含在头文件iostream中 cin是istream类型的对象,cout是ostream类型的对象,ostream类型的对象还有cerr,clog。istream和ostream是流的类型 cout能输出各种类型就是因为cout把各种类型都重载了 cin >> a; cout << a; 变量a通过运算符重载找到对应的重载函数,返回一个ostream类型,由cout << a;可知,打印是通过cout对象实现的,所以写日期类<<运算符的重载需要ostream类型的变量;>>运算符的重载需要istream类型的变量。 void operator<<(std :: ostream& out) //ostream在std中,没有展开std的话要声明所在域 {     out << _year << "-" << _mouth << "-" << _day << endl; } 不过这样写cout << d1;会报错。因为位置不对。 因为cout << a;在调用是会转换成cout.operator<<(a);而cout << d1;会转换成cout.operator<<(d1);d1和out类型不一致,没有找到匹配的重载函数,报错。想要调用应该是d1.operator<<(cout);运算符<<重载是写在日期类中的,应该是日期类来调用。 不写成重载形式的调用方式是d1 << cout; 之前规定过,当一个运算符是双操作数时,第一个数是左操作数,第二个数是右操作数。但是流插入运算符的语法是日期类插入到流中去,重载变成了流插入到日期类对象中。 函数调用只能像d1<>(std :: istream& in, Date& d); pubilc: …… } std :: ostream& operator<<(std :: ostream& out, const Date& d) {     out << d._year << "-" << d._mouth << "-" << d._day << endl;     return out; } std :: istream& operator>>(std :: istream& in, Date& d) {     in >> d._year >> d._mouth >> d._day; //这里输入非法日期是不会报错的,所以应该再加上一个检查     return in; } 流本质上是一个类型(istream/ostream)的对象,这个对象的作用是输入/输出。在这个对象中的数据都会输出到屏幕上/存储进内存中的变量里。是由系统控制的。 到这里,日期类才算有一个完整的框架,大概实现了日期类的80%。 剩下的就是比较简单的一些知识了 在默认构造函数部分,有人会有疑惑:没有默认构造函数编译器会报错吗? 答案是不会,默认构造函数不是必须提供的。重点是有没有匹配的构造函数,没有匹配的构造函数才会报错。正常情况下,最好提供一个默认构造函数。 正常初始化方式: Date(int year = 1, int month = 1, int day = 1) {     _year = year;     _mouth = month;     _day = day; } 下面提供一个新的初始化方式: Date(int year = 1, int month = 1, int day = 1)     :_year(year)     ,_mouth(mouth)     ,_day(day) //必须是这种格式 {} 这两种初始化是分开的。 第一种称为函数体内初始化,第二种称为初始化列表初始化。 第二种初始化方式中的括号里面也可以写,两种初始化方式可以混着用。 Date(int year = 1, int month = 1, int day = 1)     :_year(year)     ,_mouth(mouth) {     _day = day; } 列表初始化方式的价值在于:初始化列表可以认为是对象成员变量定义的地方。而有些成员变量必须在定义的时候初始化。比如:const修饰的类型,引用类型。 对象创建是对象的整体的定义,对象的成员在初始化列表中定义。 int ret = 10; class Date { public:     Date(int _year, int n)         :_n(n)         ,_ret(ret)     {         _year = year;     } private:     int _year;     const int _n;     int& _ret; }; int main() {     Date d1(2022, 1);     return 0; } 还有一种情况必须在初始化时定义: class AA { public:     AA(int a)     {         _aa = a;     } private:     int _aa; }; class Date { public:     Date(int _year, int n)         :_n(n)     {         _year = year;     } private:     int _year;     const int _n;     AA _a; }; int main() {     Date d1(2022, 1);     return 0; } 当Date类中出现一个自定义类型的成员变量,又没有默认构造函数时,就会报错。或者说我需要指定一个值来进行初始化。 解决方案有两个,一个是创建默认构造函数(这样就不能指定值初始化),一个是初始化列表(可以指定值来进行初始化)。 class AA { public:     AA(int a)     {         _aa = a;     } private:     int _aa; }; class Date { public:     Date(int _year, int n, int a)         :_n(n) //列表初始化部分不支持调试         ,_a(a) //初步怀疑在这里会调用AA的构造函数,用a来初始化AA的对象_a     { //如果没有初始化列表初始化,且存在默认构造函数,在这里按f11会进入AA中的默认构造函数     //即使Date中没有AA相关的步骤,编译器还是会完成AA的初始化。         _year = year;     } private:     int _year;     const int _n;     AA _a; }; int main() {     Date d1(2022, 1, 10);     return 0; } 综上所述 1. 每个成员变量在初始化列表中 只能出现一次(初始化只能初始化一次) 2. 类中包含以下成员,必须放在初始化列表位置进行初始化: 引用成员变量 const成员变量 自定义类型成员(该类没有默认构造函数) 其他变量既可以在函数体内初始化,也可以在初始化列表中初始化。引用类型和const修饰的类型不是内置类型。 不想考虑复杂情况就都在初始化列表初始化。 class Stack { public:     Stack(int capacity = 4)     {         _a = (int*)malloc(sizeof(int) * capacity);         assert(_a);         _size = 0;         _capacity = capacity;     } private:     int* a;     int size;     int capacity; }; class myQueue { public:     myQueue() //这里写不写结果都是一样的     {} private:     Stack q1;     Stack q2;     size_t size; //这里给缺省值,缺省值是给初始化列表用的 }; int main() {     myQueue m1;     return 0; } 已知默认构造函数对自定义类型调用它的默认构造函数,对内置类型不处理。这步操作发生在初始化列表中。在构造函数的初始化列表中,内置类型不处理,自定义类型调用对应的构造函数。因此在myQueue的类中,myQueue构造函数写不写是一样的结果,初始化实际发生在初始化列表中,每个构造函数都有初始化列表。 初始化列表不显式的写就用缺省值初始化,没有缺省值就随机值初始化。显式写了就用显式写的值初始化。 public:     myQueue(int size = 10)         :_size(size)     {} 所有成员都会在初始化列表中走一遍,没有显式写也会走一遍。 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关 class A { public:     A(int a)         :_a1(a)         ,_a2(_a1)     {}     void Print()     {         cout<<_a1<<" "<<_a2<>的运算符重载,流运算符如果写成成员函数第一个参数必是this指针,<<的主体是ostream类型的变量,所以要写成外部函数,而外部函数不能访问私有成员变量。所以需要声明友元函数来访问私有成员变量。 说明:     友元函数可访问类的私有和保护成员,但不是类的成员函数,所以不需要声明属于什么类域。     友元函数不能用const修饰,在成员函数后面加const,表示this指针被const修饰。     友元函数可以在类定义的任何地方声明,不受类访问限定符限制,一般情况下定义在前面。     一个函数可以是多个类的友元函数     友元函数的调用与普通函数的调用和原理相同 友元类 友元类的所有成员函数都是另一个类的友元函数,所有成员函数都可以访问另一个类中的非公有成员。 class Date; //前置声明,编译器只会向上查找 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; }; int main() {     return 0; } 友元关系是单向的,不具有交换性。比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。 友元关系不能传递。如果B是A的友元,B可以使用A的私有成员变量,C是B的友元,C可以访问B的私有成员变量,则不能说明C时A的友元,C不能访问A的私有成员变量。 内部类——用的比较少 概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。 注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元 class A { private:     static int k;     int h; public:     //B是内部类     class B //B天生是A的友元,可以使用A的私有成员变量,但A不是B的友元。A不能使用B的私有成员变量     {     public:         void foo(const A& a)         {             cout << k << endl;             cout << a.h << endl;         }     private:         int _b;     }; }; int A::k = 1; int main() {     A a1;     cout << sizeof(a1) << endl; //a1大小为4,class B只是声明,A里面没有B对象的成员变量     A::B b1; //B要受到访问限定符限制,即创建B类型的变量要指定属于A的类域。前提是B为公有。     return 0; } 如果B类为私有,就只能在A类中使用B类,即在A类域中创建B类的对象。 类域存在向上查找逻辑,所以如果先声明私有成员变量且其中包含B类对象,再声明B类,会报错。 因为编译器编译的时候,先看到A类 ,然后看到B类 ,那在A类中没有办法用B类的对象作成员变量,因为还不知道B要开多大的空间。所以创建B类时,必须知道B类的定义。 编译器对类处理的过程:先识别的成员变量,再识别成员函数,最后对成员函数进行改写,主要就是还原this。 不使用内部类和使用内部类的比较,性能而言并没有什么优化。 在实际中编译器会对拷贝构造进行优化 先做一下铺垫,下列代码会进行几次拷贝构造函数的调用? class Weight  {     Weight()     {         cout << "Weight()"<
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1026319.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号