外贸购物网站开发,怎么样用html做asp网站,html5移动端手机网站开发流程图,昆山建设招投标网站俺来写笔记了#xff0c;哈哈哈#xff0c;浅浅介绍类和对象的知识点#xff01; 1.类的6个默认成员函数
俺们定义一个空类#xff1a;
class N
{};
似乎这个类N里面什么都没有#xff0c;其实不是这样子的。这个空类有6个默认的成员函数 。
默认成员函数#xff1a…俺来写笔记了哈哈哈浅浅介绍类和对象的知识点 1.类的6个默认成员函数
俺们定义一个空类
class N
{};
似乎这个类N里面什么都没有其实不是这样子的。这个空类有6个默认的成员函数 。
默认成员函数用户没有显式实现编译器会生成的成员函数称为默认成员函数。 其实不单止是空类任何类在什么默认成员函数都不写时编译器会自动生成以上6个默认成员函数。任何类如果没有显式实现上面的6个默认成员函数的某些个默认成员函数编译器就会自动生成那些没有显式实现的默认成员函数。
如果懵懵的没关系下面鼠鼠会详细介绍
2.构造函数
注意构造函数是默认成员函数之一。
2.1.构造函数的概念
我们创建对象很多时候都希望对象使用前能被初始化如果还是用之前学的Init方法初始化如下
class people
{const char* _name;int _age;
public:void Init(const char*name,int age){this-_name name;this-_age age;}
};
int main()
{people HD;HD.Init(HD, 20);people LCD;LCD.Init(LCD, 20);return 0;
}
每次创建对象都要调用Init太麻烦所以C的类有了构造函数
构造函数简单来说就是初始化用的构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象类的实例化或者对象实例化时由编译器自动调用以保证每个数据成员都有一个合适的初始值并且在对象整个生命周期内只调用一次。而且C规定对象实例化必须调用构造函数。 构造函数是特殊的成员函数需要注意的是构造函数虽然名称叫构造但是构造函数的主要任 务并不是开空间创建对象而是初始化对象。开空间创建对象那是类的实例化或者说对象实例化。
附类的实例化具体知识请看【C】类和对象1.0
2.2.构造函数的特性 特性如下有7点
特性1函数名与类名相同。特性2构造函数可以重载可以有参数也可以没有参数。特性3无返回值这里不是void是就不需要写返回值。特性4对象实例化也就是类的实例化时编译器自动调用对应的构造函数。
举个栗子来印证上面4点特性
class people
{const char* _name;int _age;
public:people()//无参的构造函数{this-_name HD;this-_age 20;}people(const char* name,int age)//带参的构造函数{this-_name name;this-_age age;}
};
int main()
{people HD;//调用无参的构造函数people LCD(LCD,20);//调用带参的构造函数return 0;
}
我们看people类里面本鼠显示实现了2个构造函数一个无参、另一个带参 函数名与类名people相同无返回值这2个构造函数构成函数重载。对象实例化时自动调用相应的构造函数。
附函数重载知识请看【C】C入门1.0
同志们不相信对象实例化时自动调用相应的构造函数的话大可去调试一下。本鼠就将调试模式下的监视窗口打开给同志们看看可以看到确实初始化了 我们再注意主函数内的写法
int main()
{people HD;//调用无参的构造函数people LCD(LCD,20);//调用带参的构造函数return 0;
}
奇奇怪怪的吧但是语法就是这样的。 第一条语句类实例化对象HD时会自动调用无参的构造函数因为没有像第二条语句那样给参数。第二条语句类实例化对象LCD时会自动调用带参的构造函数因为对象名后面跟了(LCD20)这个东西就是传递给带参的构造函数做形参的。
注意如果通过无参构造函数创建对象时对象后面不用跟括号否则就成了函数声明。
错误写法
class people
{const char* _name;int _age;
public:people()//无参的构造函数{this-_name HD;this-_age 20;}
};
int main()
{people HD();//warning C4930: “people HD(void)”: 未调用原型函数(是否是有意用变量定义的?)return 0;
}
其实上面的2个构造函数我们可以合成1个有全缺省参数的构造函数功能也是一模一样的如下
附缺省参数知识请看【C】C入门1.0
class people
{const char* _name;int _age;
public:people(const char* nameHD, int age20)//全缺省参数的构造函数{this-_name name;this-_age age;}
};
int main()
{people HD;people LCD(LCD,20);return 0;
} 需要注意的是全缺省参数的构造函数和无参的构造函数理论上是构成函数重载是可以同时存在的。但实际上它们2个函数却不能同时存在因为对象实例化时可能存在歧义。比如
class people
{const char* _name;int _age;
public:people(const char* name HD, int age 20)//全缺省参数的构造函数{this-_name name;this-_age age;}people(){this-_name HD;this-_age 20;}
};
int main()
{people HD;//error C2668: “people::people”: 对重载函数的调用不明确people LCD(LCD, 20);return 0;
}
编译报错因为实例化对象HD时编译器不知道该调用哪一个构造函数 特性5如果类中没有显式定义任何一个构造函数则C编译器会自动生成一个无参的默认构造函数一旦用户显式定义了任何一个构造函数编译器将不再生成。
class people
{const char* _name;int _age;
public:people(const char* name , int age ){this-_name name;this-_age age;}
};
int main()
{people HD;//error C2512: “people”: 没有合适的默认构造函数可用return 0;
}
看到这里报错了已经显示定义了一个构造函数那么编译器不再生成无参的默认构造函数。导致对象HD实例化时没有合适的默认构造函数可用。 特性6无参的构造函数和全缺省参数的构造函数都称为默认构造函数所以默认构造函数有3个无参的构造函数、全缺省参数的构造函数、我们没写编译器默认生成的构造函数无参的特性5介绍过。并且默认构造函数只能有一个。
为什么默认构造函数只能有一个 前面我们介绍了全缺省参数的构造函数和无参的构造函数不能共存并且一旦用户显式定义了任何一个构造函数编译器不再生成默认的构造函数。那么这3个默认构造函数两两互斥所以只能有一个。 那么我们来探讨一下编译器生成的默认构造函数对于对象内不同类型的成员会有什么行为
特性7编译器生成的默认构造函数对于对象内内置类型成员不做处理不排除有些编译器会去处理对象内内置类型成员处理方式未知对于对象内自定义类型成员会去调用它的默认构造函数俺可没说对于对象自定义类型成员会去调用它的编译器生成的默认构造函数哈俺说的是会去调用它的默认构造函数。
补充知识C把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型如int/char...自定义类型就是我们使用class/struct/union等自己定义的类型。
class Time
{int _hour;int _minute;int _second;
public:Time(){this-_hour 12;this-_minute 20;this-_second 5;}
};
class Date
{//内置类型int _year;int _month;int _day;//自定义类型Time _t;
};
int main()
{Date d;return 0;
} 同志们看好了。编译器生成的默认构造函数对于对象d的内置类型成员不做处理所以可以看到_year、_month、_day被初始化成了随机值对于自定义类型成员_t回去调用了它的默认构造函数Time()所以可以看到_hour、_minute、_second被初始成了12、20、5。
注意C11 中针对对象内置类型成员不做处理的缺陷又打了补丁即内置类型成员变量在类中声明时可以给默认值缺省值。
class Time
{int _hour;int _minute;int _second;
public:Time(){this-_hour 12;this-_minute 20;this-_second 5;}
};
class Date
{//内置类型int _year 2024;int _month 6;int _day 17;//自定义类型Time _t;
};
int main()
{Date d;return 0;
} 同志们看因为内置类型成员在类中声明时给了默认值所以编译器生成的默认构造函数将对象内置类型成员初始化成了默认值。。。 介绍完构造函数我们要明白需要具体分析对象的初始需求需要我们自己显式定义构造函数我们就自己定义不需要就让编译器自动生成。但是大多数情况下都是要我们显式定义的。
2.3.构造函数的调用顺序
对象先定义的先构造后定义的后构造。
比如
#includeiostream
using namespace std;
class Date
{int _year;
public:Date(int n)//构造函数{cout Date: n endl;}
};
void Test()
{static Date d9(9);Date d10(10);
}
const Date d1(1);
static Date d2(2);
Date d3(3);
static Date d4(4);
int main()
{Date d5(5);const Date d6(6);static Date d7(7);Date d8(8);Test();const Date d11(11);return 0;
}
看结果没问题的意料之中 3.析构函数
注意析构函数是默认成员函数之一。
3.1.析构函数的概念
析构函数与构造函数功能相反析构函数不是完成对对象本身的销毁局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数完成对象中资源的清理工作。
对象的销毁是当它的生命周期结束时编译器销毁的。
3.2.析构函数的特性
特性如下有6点 特性1析构函数名是在类名前加上字符 ~。 特性2无参数无返回值类型。这里的返回值不是void就是不需要写。 特性3一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数。注意析构函数不能重载。 特性4对象生命周期结束时C编译系统系统自动调用析构函数。
俺拿之前介绍过的栈举例印证以上4点特性
#includestdlib.h
typedef int Stdatatype;
class Stack
{int _top;int _capacity;Stdatatype* _a;
public:Stack()//构造函数{this-_a nullptr;this-_capacity this-_top 0;}void Push(const Stdatatype n)//入栈{if (this-_top this-_capacity){int newcapacity (this-_capacity 0) ? 4 : (this-_capacity * 2);Stdatatype* tmp (Stdatatype*)realloc(this-_a, newcapacity * sizeof(Stdatatype));if (tmp nullptr){perror(realloc fail);exit(-1);}this-_capacity newcapacity;this-_a tmp;}this-_a[this-_top] n;}//Pop、Top、Size……函数略~Stack()//析构函数{free(this-_a);this-_a nullptr;this-_capacity this-_top 0;}
};
int main()
{Stack s;s.Push(1);s.Push(2);return 0;
} 同志们请看当s.Push(2);执行完成后对象s的成员如图
当执行完return 0;后对象s的成员资源就被清理掉了比如realloc在堆区申请的空间就被free掉了 其实说白了这个析构函数的功能大抵上就跟前面博客介绍过的栈的销毁栈一样。析构函数本身就是清理资源的不同的是析构函数当对象生命周期结束时C编译系统系统自动调用析构函数但是销毁栈的StackDestroy函数却需要手动调用。。。 特性5编译器自动生成的析构函数当我们没有显式定义时会生成跟编译器生成的默认构造函数类似对于对象内内置类型成员不做处理对于对象内自定义类型成员会去调用它的析构函数。
看这个代码
#includeiostream
using namespace std;
class Time
{
public:~Time()//析构函数{cout ~Time() endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year 1970;int _month 1;int _day 1;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
} 运行结果打印了~Time()。就说明了编译器自动生成的析构函数对象内自定义类型成员会去调用它的析构函数。注意析构函数当对象生命周期结束时C编译系统系统自动调用析构函数。 同志们请看编译器自动生成的析构函数对对象内内置类型成员销毁时不需要资源清理最后系统直接将其内存回收即可。 特性6如果类中没有申请资源时析构函数可以不写显式定义直接使用编译器生成的默认析构函数。有资源申请时一定要写否则会造成资源泄漏。
本鼠举一个不用显式定义析构函数的栗子
class Date
{int _year;int _month;int _day;
public:Date(int year2024, int month6, int day17){this-_year year;this-_month month;this-_day day;}
};
int main()
{Date d1;Date d2(203, 12, 12);return 0;
}
这个Date类中并没有申请资源我们用编译器生成的默认析构函数就行不用自己写。
再举一个需要显式定义析构函数的栗子
#includestdlib.h
typedef int Stdatatype;
class Stack
{int _top;int _capacity;Stdatatype* _a;
public:Stack()//构造函数{this-_a nullptr;this-_capacity this-_top 0;}void Push(const Stdatatype n)//入栈{if (this-_top this-_capacity){int newcapacity (this-_capacity 0) ? 4 : (this-_capacity * 2);Stdatatype* tmp (Stdatatype*)realloc(this-_a, newcapacity * sizeof(Stdatatype));if (tmp nullptr){perror(realloc fail);exit(-1);}this-_capacity newcapacity;this-_a tmp;}this-_a[this-_top] n;}//Pop、Top、Size……函数略~Stack()//析构函数{free(this-_a);this-_a nullptr;this-_capacity this-_top 0;}
};
int main()
{Stack s;s.Push(1);s.Push(2);return 0;
}
这里为什么要自己写析构函数 我们来看上面代码映射到内存上的想象图就很明白了 如果我们不手动写析构函数那么编译器就会自动生成默认的析构函数。当对象s生命周期结束时会调用编译器自动生成的析构函数。该函数对对象内内置类型成员销毁时不需要资源清理最后系统直接将其内存回收那么对象s在栈区的空间的使用权就还给操作系统了但是堆区的资源还没有释放这样子不就造成内存泄漏了吗 3.3.析构函数的调用顺序
析构函数的调用顺序满足局部非静态对象先定义的后析构相对的——局部静态对象先定义的后析构绝对的——全局对象先定义的后析构绝对的。举例证明
#includeiostream
using namespace std;
class Date
{int _year;
public:~Date()//析构函数{cout this-_year endl;}Date(int n)//构造函数{_year n;}
};
void Test()
{static Date d7(7);Date d1(1);
}
const Date d12(12);
static Date d11(11);
Date d10(10);
static Date d9(9);
int main()
{Date d5(5);const Date d4(4);static Date d8(8);Date d3(3);Test();const Date d2(2);static Date d6(6);return 0;
}
意料之中多观察可以发现规律的 4.拷贝构造函数
注意这是构造函数之一也是默认成员函数之一。
4.1.拷贝构造函数的概念
拷贝构造函数的大概作用是在创建新对象时使得新对象与已经存在的对象一模一样。
拷贝构造函数只有单个形参该形参是对本类类型对象的引用(一般常用const修饰)在用已存 在的类类型对象创建新对象时由编译器自动调用。
4.2.拷贝构造函数的特性
特性如下有4点 特性1拷贝构造函数是构造函数的一个重载形式。也就是说拷贝构造函数也是一种构造函数形象的说拷贝构造函数是构造函数的一个子集。特性2拷贝构造函数的参数只有一个且必须是类类型对象的引用条件允许的情况下最好用常引用使用传值方式编译器直接报错 因为会引发无穷递归调用。
class Date
{int _year;int _month;int _day;
public:Date(int year2024, int month6, int day17)//全缺省构造函数{this-_year year;this-_month month;this-_day day;}Date(const Date d)//拷贝构造函数{this-_year d._year;this-_month d._month;this-_day d._day;}
};
int main()
{Date d1;//调用全缺省构造函数Date d2(d1);//调用拷贝构造函数写法1Date d3 d2;//调用拷贝构造函数写法2return 0;
}
因为拷贝构造函数是构造函数的一个子集所以拷贝构造函数拥有构造函数的共性 函数名与类名相同、无返回值、创建新对象时由编译器自动调用。
想通过拷贝构造函数创建对象有两种写法如上代码主函数内可见。要注意这两种写法所面临的同类型对象都是一个已经存在的对象和一个正在创建的对象。
调试结果如图 至于特性2所说的拷贝构造函数的参数使用传值方式编译器直接报错 因为会引发无穷递归调用。为什么会引发无穷递归调用呢 因为C规定自定义类型的拷贝都会通过调用拷贝构造函数来拷贝。如果将拷贝构造函数的参数写成传值方式传值的方式中形参是实参的一份临时拷贝。那么当同志们想要调用拷贝构造函数就要先传参传参又要拷贝实参给形参既然是自定义类型拷贝又要调用拷贝构造函数调用拷贝构造函数又要传参传参又要拷贝实参给形参……这不就无穷递归调用了吗
而如果拷贝构造函数的参数使用传引用传参就不存在形参是实参的拷贝问题就不会无穷递归调用。 特性3若未显式定义编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。
注意在编译器生成的默认拷贝构造函数中内置类型是按照字节方式直接拷贝的而自定义类型是调用其拷贝构造函数完成拷贝的。
举个栗子编译器生成默认的拷贝构造函数完成浅拷贝的栗子
#includeiostream
using namespace std;
class Time
{int _hour;int _minute;int _second;
public:Time(){this-_hour 14;this-_minute 57;this-_second 30;}Time(const Time d){this-_hour d._hour;this-_minute d._minute;this-_second d._second;cout 真的调用了这个函数捏 endl;}
};
class Date
{//内置类型int _year 2024;int _month 6;int _day 18;//自定义类型Time _t;
};
int main()
{Date d1;//调用编译器生成的默认构造函数Date d2 d1;//调用编译器生成的默认拷贝构造函数return 0;
}
本鼠先浅浅介绍一下这个代码
看主函数第一条语句创建Date类类型的对象d1由于Date类中没有显式定义构造函数就通过编译器生成的默认构造函数创建对象d1由于内置类型成员变量在类的声明中给了缺省值这个编译器生成的默认构造函数就将d1的内置类型成员变量初始化成了缺省值对于d1的自定义类型成员变量_t去调用它的默认构造函数Time()本鼠显式定义了所以d1的自定义类型成员变量_t的成员变量分别被初始化成了14、57、30。
看主函数第二条语句通过拷贝构造函数创建与已存在对象d1一模一样的同类型对象d2由于Date没有显式定义拷贝构造函数就通过编译器生成的默认拷贝构造函数创建对象d2这个编译器生成的默认拷贝构造函数对对象d1的内置类型成员浅拷贝给新对象d2的内置类型成员对于对象d1的自定义类型成员_t去调用它的拷贝构造函数Time(const Time)本鼠显示定义了进行浅拷贝所以d2的自定义类型成员变量_t的成员变量分别为14、57、30。
第三条语句结束后编译器生成的默认析构函数先后作用于d2和d1。 同志们请看确实是完成了浅拷贝 同志们请看也确实调用了内置类型成员变量_t的拷贝构造函数 问题又来了编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了还需要自己显式实现吗
答案是有些需要有些不需要。类中如果没有涉及资源申请时拷贝构造函数是否写都可以一旦涉及到资源申请时则拷贝构造函数是一定要写的否则就是浅拷贝会出问题。
看下面的代码
#includestdlib.h
typedef int Stdatatype;
class Stack
{int _top;int _capacity;Stdatatype* _a;
public:Stack()//构造函数{this-_a nullptr;this-_capacity this-_top 0;}void Push(const Stdatatype n)//入栈{if (this-_top this-_capacity){int newcapacity (this-_capacity 0) ? 4 : (this-_capacity * 2);Stdatatype* tmp (Stdatatype*)realloc(this-_a, newcapacity * sizeof(Stdatatype));if (tmp nullptr){perror(realloc fail);exit(-1);}this-_capacity newcapacity;this-_a tmp;}this-_a[this-_top] n;}//Pop、Top、Size……函数略~Stack()//析构函数{free(this-_a);this-_a nullptr;this-_capacity this-_top 0;}
};
int main()
{Stack s1;s1.Push(1);Stack s2(s1);return 0;
} 编译运行如图为什么会这样子呢
其实就是我们没有显式定义拷贝构造函数导致通过编译器生成的默认拷贝构造函数创建对象s2这个编译器生成的默认拷贝构造函数是对s1浅拷贝给s2的我们可以调试看看浅拷贝 浅拷贝在这里导致对象s1和对象s2的成员变量_a都指向同一块空间想象一下 这里浅拷贝显然不是我们想要的。而且当析构函数作用于对象s2时堆区动态开辟的那块空间已经被free掉了此时s1._a就是野指针再当析构函数于对象s1时那块空间再次被free同一块空间被free两次导致报错
所以像这种类中如果涉及资源申请的我们需要自己写拷贝构造函数以实现深拷贝而不是使用编译器自动生成的默认拷贝构造函数正确写法如下
#includestdlib.h
#includestring.h
typedef int Stdatatype;
class Stack
{int _top;int _capacity;Stdatatype* _a;
public:Stack()//构造函数{this-_a nullptr;this-_capacity this-_top 0;}void Push(const Stdatatype n)//入栈{if (this-_top this-_capacity){int newcapacity (this-_capacity 0) ? 4 : (this-_capacity * 2);Stdatatype* tmp (Stdatatype*)realloc(this-_a, newcapacity * sizeof(Stdatatype));if (tmp nullptr){perror(realloc fail);exit(-1);}this-_capacity newcapacity;this-_a tmp;}this-_a[this-_top] n;}//Pop、Top、Size……函数略Stack(const Stack s)//拷贝构造函数{Stdatatype* tmp (Stdatatype*)malloc(sizeof(Stdatatype) * s._capacity);if (tmp NULL){perror(malloc fail);exit(-1);}memcpy(tmp, s._a, sizeof(Stdatatype) * s._top);this-_a tmp;this-_capacity s._capacity;this-_top s._top;}~Stack()//析构函数{free(this-_a);this-_a nullptr;this-_capacity this-_top 0;}
};
int main()
{Stack s1;s1.Push(1);Stack s2(s1);return 0;
}
自己写的拷贝构造函数完成深拷贝想象图如下 显然这样的拷贝才是我们想要的。。 特性4拷贝构造函数典型调用场景
使用已存在对象创建新对象函数参数类型为类类型对象函数返回值类型为类类型对象
#includeiostream
using namespace std;
class Date
{
public:Date(int year, int minute, int day)//构造函数{cout Date(int,int,int): this endl;}Date(const Date d)//拷贝构造函数{cout Date(const Date d): this endl;}~Date()//析构函数{cout ~Date(): this endl;}
private:int _year;int _month;int _day;
};
Date Test(Date d)
{Date temp(d);return temp;
}
int main()
{Date d1(2022, 1, 13);Test(d1);return 0;
}
在x86环境下运行结果 运行结果打印了6条语句因为
第1条语句主函数第1条语句Date d1(2022,1,13)调用构造函数创建对象d1
第2条语句主函数第2条语句Test(d2)d1作为实参以传值方式传递给Test函数的形参调用了拷贝构造函数
第3条语句Test函数第1条语句Date temp(d)调用构造函数创建对象temp
第4条语句析构函数作用于Test函数中的对象temp
第5条语句析构函数作用于Test函数返回时创建的临时对象
第6条语句析构函数作用于主函数中的对象d1。
我们看到Test函数以传值返回按道理说应该调用拷贝构造函数拷贝以temp为蓝本的临时对象用于返回但是这里没有调用拷贝构造函数因为这个过程被编译器优化了检测到构建的局部变量temp用于返回直接就在上一个函数栈构造这个对象用于返回这样就只调用了一次构造而不需要进行拷贝构造。 5.赋值运算符重载 注意赋值运算符重载是默认成员函数之一。
赋值运算符重载是运算符重载的一种所以本鼠先要介绍运算符重载的知识
5.1.运算符重载
5.1.1.运算符重载 我们来看一个代码
#includeiostream
using namespace std;
int main()
{int a 10;int b 20;cout (a b) endl;//优先级高于所以使用()控制return 0;
}
内置类型的对象想要使用运算符实现运算符的功能可以直接使用如本代码中想要知道a是否小于b就直接用运算符呗。运行结果如图 但是自定义类型对象却无法直接使用这些现成的运算符比如
#includeiostream
using namespace std;
class Date
{int _year;int _month;int _day;
};
int main()
{Date a;Date b;cout (a b) endl;return 0;
}
编译绝对会报错因为操作数a和b对于操作符来说该有怎么样的行为是不明确的。换句话说操作符不知道该如何比较a和b。
但是我们不乏需要比较自定义类型对象的行为我们当然可以定义一个函数来比较比如
#includeiostream
#includestdbool.h
using namespace std;
class Date
{
public:int _year;int _month;int _day;Date(int year,int month,int day){this-_year year;this-_month month;this-_day day;}
};
bool Lessthan(const Date a, const Date b)
{if (a._year b._year){return true;}else if (a._year b._year){if (a._month a._month){return true;}else if (a._month b._month){return a._day b._day;}}return false;
}
int main()
{Date a(2003, 12, 12);Date b(2003, 12, 5);cout Lessthan(a, b) endl;return 0;
}
我们看运行结果就可以知道a“不小于”b 可是这样子的代码可读性就不是很好了。我们要分析Lessthan函数的行为才知道原来这个函数是检测形参a是否小于形参b的。如果我们对于自定义类型对象能直接使用操作符例如就好了一眼定真就知道在干啥。 同志们所以C有了运算符重载请看 C为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其 返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似。
函数名字为关键字operator后面接需要重载的运算符符号。
函数原型返回值类型 operator操作符(参数列表)。
一个使用运算符重载的栗子
#includeiostream
#includestdbool.h
using namespace std;
class Date
{
public:int _year;int _month;int _day;Date(int year,int month,int day){this-_year year;this-_month month;this-_day day;}
};
bool operator(const Date a, const Date b)
{if (a._year b._year){return true;}else if (a._year b._year){if (a._month a._month){return true;}else if (a._month b._month){return a._day b._day;}}return false;
}
int main()
{Date a(2003, 12, 12);Date b(2003, 12, 5);cout operator(a, b) endl;//正常的函数显示调用cout (a b) endl;//在编译器看来与上一条语句一模一样效果相同。return 0;
}
运行结果 通过运算符重载对于自定义类型对象可以直接使用操作符比如代码可读性大大提升
对于这个使用运算符重载的栗子我们还可以分析
这里会发现运算符重载成全局的函数就需要成员变量是公有的不然受到访问限定符的作用无法访问类对象的成员变量那么问题来了封装性如何保证
这里其实可以用以下办法解决
办法1友元声明解决本鼠后面博客会介绍的。办法2或者干脆重载成类成员函数。 办法3或者定义一些类成员函数用于“调出”成员变量。
使用办法3本鼠举例
#includeiostream
#includestdbool.h
using namespace std;
class Date
{int _year;int _month;int _day;
public:int Getyear(){return _year;}int Getmonth(){return _month;}int Getday(){return _day;}Date(int year,int month,int day){this-_year year;this-_month month;this-_day day;}
};
bool operator( Date a, Date b)
{if (a.Getyear()b.Getyear()){return true;}else if (a.Getyear() b.Getyear()){if (a.Getmonth() a.Getmonth()){return true;}else if (a.Getmonth() b.Getmonth()){return a.Getday() b.Getday();}}return false;
}
int main()
{Date a(2003, 12, 12);Date b(2003, 12, 5);cout operator(a, b) endl;cout (a b) endl;//在编译器看来与上一条语句一模一样效果相同。return 0;
}
运行结果 运算符重载有几个注意事项
不能通过连接其他符号来创建新的操作符比如operator就是说重载的操作符必须是C/C语法存在的。重载操作符必须有一个类类型参数就是说不能通过运算符重载来改变运算符对内置类型的行为。用于内置类型的运算符其含义不能改变例如内置的整型不 能改变其含义虽然语法上可以编译通过但是这样子的话可读性还不如之前了。作为类成员函数重载时其形参看起来比操作数数目少1因为成员函数的第一个参数为隐藏的this指针。.* 和::和 sizeof和 ?: 和. 这5个运算符不能重载。
举个栗子
#includeiostream
using namespace std;
class exmple
{int _data1;int _data2;
public:exmple (int data1,int data2){_data1 data1;_data2 data2;}exmple operator(const exmple n){exmple tmp(0, 0);tmp._data1 this-_data1 - n._data1;tmp._data2 this-_data2 - n._data2;return tmp;}void Print(){cout _data1 _data2;}
};
int main()
{exmple a(10, 10);exmple b(5, 5);exmple c a b;//this指针传递a形参n是b的引用c.Print();return 0;
}
我们看类成员函数operator的形参为啥只有一个那是因为隐藏的this指针帮忙传递了一个。 看结果
结果是两个5。上面的代码就是一个错误示范我们想要重载运算符但是内部逻辑却写成了-导致可读性大大降低犯了注意事项3的错误。 5.1.2.和--重载
为什么要强调这两个运算符的重载呢
拿来说前置和后置都是一样的运算符那么运算符重载的时候如何形成正确重载函数名都写成operator的话无法区分啊
所以C规定后置重载时多增加一个int类型的参数但当我们不显示调用函数时该参数不用传递编译器自动传递。这个参数的参数名我们甚至可以不显示写。
#includeiostream
using namespace std;
class exmple
{int _data1;int _data2;
public:exmple operator()//前置{_data1;_data2;return *this;}exmple operator(int)//后置没显示写参数名也可以写成exmple operator(int n){exmple tmp *this;//拷贝构造*this;return tmp;}exmple(int data1, int data2)//构造函数{_data1 data1;_data2 data2;}void Print(){cout _data1 _data2 endl;}
};
int main()
{exmple a(10, 10);exmple b(10, 10);exmple c a;exmple d b;c.Print();d.Print();return 0;
} 上面有一些注意事项
如果我们打算显示调用后置这个函数那么一定要传入一个任意int整形以区分前置不然编译器就会去调用前置了。
显示调用后置时正确写法
exmple c a.operator(10);//相当于exmple c a; 后置是先使用后1因此需要返回1之前的旧值故需在实现时需要用临时对象先将*this保存 一份然后给*this1返回临时对象。因为返回是临时对象因此只能以值的方式返回不能返回引用。
前置--和后置--同理不解释了
5.2.赋值运算符重载 介绍了运算符重载那么赋值运算符重载的意思就很简单了。赋值运算符重载格式
参数类型const T传递引用可以提高传参效率T是自定义类型下同。返回值类型T返回引用可以提高返回的效率有返回值目的是为了支持连续赋值。检测是否自己给自己赋值可有可无最好有。返回*this 要复合连续赋值的含义。
我们看赋值运算符应该支持连续赋值 int x, y, z;x y z 10;//连续赋值
这里连续赋值的结合性是从右向左的 表达式z10返回值是z本身左操作数作为返回值返回值z再作为表达式yz的右操作数表达式yz返回值是y本身……
#includestdio.h
int main()
{int z 10;int x 20;printf(%p\n, z);printf(%p\n, (z x));return 0;
} 真的是返回左操作数本身啊
所以赋值运算符重载后也要支持连续赋值要返回左操作数本身其实返回其拷贝也行。
#includeiostream
using namespace std;
class exmple
{int _data1;int _data2;
public:exmple operator(const exmple n)//赋值运算符重载{if (this ! n)//if用于检测是否自己给自己赋值{this-_data1 n._data1;this-_data2 n._data2;return *this;}}exmple(int data1, int data2)//构造函数{_data1 data1;_data2 data2;}void Print(){cout _data1 _data2 endl;}
};
int main()
{exmple a(10, 10);exmple b(20, 20);exmple c(30, 30);a b c;//连续赋值a.Print();b.Print();c.Print();return 0;
} 赋值运算符重载所面临的同类型对象都是已经存在的对象。而拷贝构造函数所面临的对象一个是已经存在的对象另一个是正在创建的对象。 还有
赋值运算符只能重载成类的成员函数不能重载成全局函数。原因赋值运算符如果在类中不显式实现编译器会生成一个默认的。此时用户再在类外自己实现 一个全局的赋值运算符重载就和编译器在类中生成的默认赋值运算符重载冲突了故赋值运算符重载只能是类的成员函数。用户没有显式实现时编译器会生成一个默认赋值运算符重载以值的方式逐字节拷贝浅拷贝。注意内置类型成员变量是直接赋值的而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。跟拷贝构造函数类似不解释了。如果类中未涉及到资源管理赋值运算符是否实现都可以一旦涉及到资源管理则必须要实现否则浅拷贝会出现一大堆问题跟拷贝构造函数类似不解释了。
6.const成员函数
看一个代码
#includeiostream
using namespace std;
class exmple
{int _data110;int _data220;
public:void Print(){cout _data1 _data2 endl;}
};
int main()
{const exmple a;a.Print();//error C2662: “void exmple::Print(void)”: 不能将“this”指针从“const exmple”转换为“exmple ”return 0;
}
编译不通过为什么 很简单对象a是被const修饰的常对象 调用Print函数隐藏的this指针接收a的地址。如果编译通过就可以通过this指针类型是exmple*间接修改常对象a权限被放大了。
怎么解决呢很简单用const修饰this指针被修饰后的this指针类型是const exmple*这样指针指向的值就不可改了。 所以将const修饰的“成员函数”称之为const成员函数const修饰类成员函数实际修饰该成员函数隐含的this指针表明在该成员函数中不能对类的任何成员进行修改。
可是this指针是隐藏的我们如何将其用const修饰呢很简单举个栗子
#includeiostream
using namespace std;
class exmple
{int _data1 10;int _data2 20;
public:void Print()const//const成员函数this指针类型是const exmple *{cout _data1 _data2 endl;}
};
int main()
{const exmple a;a.Print();return 0;
} 没问题吧
编译器对于const成员函数的处理用下代码举例 需要注意的是
如果类中const成员函数的声明和定义分离了我们在声明和定义处都要”加上“const。不是所有成员函数的第一个形参this指针都能被const修饰的。如果是一个对成员变量要进行读写访问的成员函数不能”加“const因为如果this指针被const修饰了就不能对类的任何成员进行修改了。
7.取地址及const取地址操作符重载
注意取地址操作符重载和const取地址操作符重载都是默认成员函数之一。
这两个运算符一般不需要重载使用编译器生成的默认取地址的重载即可因为无非就是返回对象地址罢了。只有特殊情况才需要重载比如想让别人获取到指定的内容 取地址操作符重载举例
#includeiostream
using namespace std;
class exmple
{int _data1 10;int _data2 20;
public:exmple* operator()//取地址操作符重载{return this;}
};
int main()
{exmple a;cout a endl;//a相当于a.operator()return 0;
} const取地址操作符重载
我们要知道运算符重载本质是一个函数。所有取地址操作符重载也是一个函数。const取地址操作符重载的意思是取地址操作符重载的第一个形参this指针被const修饰了const取地址操作符重载是一个const成员函数。
const取地址操作符重载举例
#includeiostream
using namespace std;
class exmple
{int _data1 10;int _data2 20;
public:const exmple* operator()const//const取地址操作符重载{return this;}
};
int main()
{exmple a;cout a endl;//a相当于a.operator()return 0;
} 为啥const取地址操作符重载的返回值也要被const修饰呢因为this指针是被const修饰的将this指针返回那么返回值当然要被const修饰了不然就是权限的放大编译会报错的。。 感谢阅读如有错误恳请斧正