类的定义:?
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面也有分号。类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。
C语言结构体不支持成员函数,但C++结构体支持,class与struct本质没有区别,唯一区别??在于默认时class的访问属性为私有,struct为公有
类的访问限定符:
public修饰的成员在类外可以直接被访问 protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的) 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
基类保护成员在子类可以直接被访问
基类私有成员在子类中不能被访问
基类共有成员在子类和对象外都可以直接访问
类的成员函数定义有三种方法:
//方法一直接类内实现
class englishbook {
public:
void initcount(int n) {
count = n;
}
int getcount() {
return count;
}
private:
int count;
char name;
};
//方法二,类内声明,类外实现,加作用域限定
class englishbook {
public:
void initcount(int n);
int getcount();
private:
int count;
char name;
};
void englishbook::initcount(int n) {
count = n;
}
int englishbook::getcount() {
return count;
}
int main()
{
englishbook book1;//实例化了一个englishbook类对象book1
book1.initcount(3);
cout << "book1count:" << book1.getcount() << endl;
return 0;
}
第三种方法:
?主函数放在另一个源文件
类对象的大小:
成员占对象的空间,一个类的大小仅由成员变量决定,和结构体的内存对齐规则相同,空类占一个字节
类的方法占用系统资源空间(公共代码区),为了防止一个类有多个实例时调用相同的方法却创造了多个相同大小的相同方法的空间
this指针:
?如果一个类定义了两个实例化对象,比如上面的englishbook book1,englishbook book2,在book1初始化调用intitcount函数时,该函数是如何知道应该设置book1对象,而不是设置s2对象呢?
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
?所以:this的意思就是当前对象(哪个实例化对象调用的该函数此时的this就是指向该对象)
class englishbook {
public:
/*值得注意的是看是可以看成下面,但是写形参的时候不能像这样自己把this指针写出来
void initcount(englishbook* const this,int n){
this->count = n;
}*/
void initcount(int n) {
count = n;
}
//但是可以这样自己调用this
void initcount(int n) {
this->count = n;
}
private:
int count;
char name;
};
int main()
{
englishbook book1;//实例化了一个englishbook类对象book1
englishbook book2;
book1.initcount(3);
book2.initcount(4);
//可以理解为book1.initcount(&book1,3);book2.initcount(&book2,3)
//这样,函数就知道该操作的是哪个的对象的成员了
return 0;
}
而且this指针是一个常量,(定义时是:类名* const this),所以在整个成员函数作用域内this指针是不能被修改的,就更不能为空了(this=nullptr)
成员函数的其它参数正常都是存放在栈中,而this指针参数则是存放在寄存器中.
必用this指针的情况:
形参名和成员变量名相同时
void initcount(int count) {
count = count;
}
如果直接这样,相当于count自己给自己复制,并没有得到实参的值.
void initcount(int count) {
this->count = count;
}
这样写才能初始化count
类的六个默认成员函数:
? ? ? ? 构造函数:
构造函数没有返回值,函数名与类名相同,可以有多个参数,没自己声明实现构造函数时,会自动调用一个什么都不做的空的默认构造函数,但要是自己声明实现了一个构造函数,就不会调用空的默认构造函数了.?
构造函数可以被重载,但一个类中不允许有两个及以上的默认构造函数(无参或全缺省参数的构造函数都叫默认构造函数),因为此时实例化一个没给参数的test t1就会产生二义性出错.如下:
class test {
public:
test() {
_a = 10;
}
test(int d = 0) {
_a = d;
}
private:
int _a;
};
int main(){
test t1;
}
所以我们一般默认只写一个全缺省参数的构造函数,此时实例化对象时test t1;test t2(10)括号内,等号后不是对象时等价于test t2=10;这三种实例化对象都可以,但给出参数(),全括号内缺没给实参是不行的,所以我们默认不写test t3()这种没有实参的对象.因为这样实例化对导致t3成为一个返回值类型为test类的函数,而不是实例化t3了.如下:
class test {
public:
test(int d = 0) {
_a = d;
}
private:
int _a;
};
int main(){
test t1;//ok
test t2(100);//等价于test t2=10;ok
test t3();//no ok,这是一个函数
}
????????析构函数:
析构函数也没有返回值,函数名是类名前加一个取反符号~,是不允许重载的.
一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数.对象生命周期结束时,C++编译系统系统自动调用析构函数.? ?析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
调用析构的顺序:先实例化构造全局对象再在主函数内从上到下实例化对象,再从下到上析构对象,但主函数静态static的例外,主函数内静态的的对象最后析构(同是static也是先构造的后析构).如下:?
? ? ? ? 拷贝构造函数:
在用已存在的类类型对象创建新对象时(三种情况:直接旧对象创建新对象时,对象作为实参初始化非引用的形参对象时,函数内的局部变量作返回值时也会调用拷贝构造函数,拷贝给无名临时空间)由编译器自动调用拷贝构造函数。
1. 拷贝构造函数是构造函数的一个重载形式. 2. 拷贝构造函数的参数只有一个且必须使用引用传参(而且一般用const修饰,防改原对象的值),引用是原对象的别名不会创建新的空间,使用传值方式会引发无穷递归调用,因为实参d1赋值给形参date也是对象初始化对象又会调用拷贝构造函数(Date date=d1),然后无线递归下去:
?
3.若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。但有些类是浅拷贝会崩溃的,这时候就需要用到之后的深拷贝.
? ? ? ? 赋值运算符重载函数:
运算符重载中只有赋值赋值运算符重载是默认成员函数!
一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值赋值。
.* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
以赋值操作符(即等号"=")重载为例了解会调用操作符重载函数的情况:
test t1;
t1=100;//编译器会先用常量100调用构造函数构造一个无名test类,再调用赋值重载函数给t1赋值,
//t1.operator=(test(100));
test t2;
t2=t1;//常规的调用赋值重载函数.
//t2.operator=(t1);
///其实就是 operator= 相当于函数名 fun,其他操作符重载同理.
//t2.fun(t1);
//相当于调用名为operator=的成员函数,所以该成员函数定义实现那显式看到的形参是t1的.
//隐式的this指针是指向t2的,因为是t2调用的该操作符重载函数.
在了解运算符重载前我们先了解:
普通函数的参数和返回值类型的问题:
第一种:直接用类作形参会导致调用拷贝构造方法 , 类作返回值类型返回时又会再调用拷贝构造把tmp拷贝给无名临时变量.
第二种:引用作形参obj不需要拷贝构造是t1的别名,由临时空间变量给t2赋值.
?
?第三种:引用作返回值,由于引用的是tmp的别名,而tmp已到生命周期释放,此时再用来赋值给t2是不合理的!(但是如果类内部的成员函数,返回的是*this的时候是用引用返回的,因为*this是一个实例化的对象,函数结束了*this还存在!而这里的tmp是函数内部局部变量函数结束他的生命就结束了!? *this用引用返回是因为this也是个形参,隐式定义的形参,函数结束this这个类指针变量也是要释放的,如果不用引用返回,返回时会调用拷贝构造函数,用*this拷贝构造出临时空间,用引用返回*this就不需要了)
?第四种:fun的返回对象直接初始化t2,先进入fun函数内,由于编译器的优化会让临时空间直接做t2,跳过省去了临时空间变量再调用拷贝构造给t2.
(? test t2=fun(t1)? 等价于 test t2(fun(t1))? ? ? ?),属于初始化
?第五种:终极优化后,直接返回无名临时变量,编译器直接优化返回的临时变量成为t2 . 不创建中间变量tmp,省去构造tmp和拷贝构造.
?
经过上述问题,我们应该了解到需视情况而定操作符重载函数的形参,返回值类型:
= 重载函数用 类的引用 返回是为了能够连等,如果返回类型是void是不能t3=t2=t1
即? ?t3.operator=(t2.operator=(t1))? 这样操作的.返回类和返回类的引用区别 见 上面<第三种:>
+= 操作符重载操作的是自己所以我们选择返回自己(引用)
+ 操作符重载,下面日期类我们希望不更改自身而得到新的日期类所以不返回引用
#include <iostream>
class Date{
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month){
int days[13] = { 0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int day = days[month];
if (month == 2&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){
++day;
}
return day;
}
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1){
_year = year;
_month = month;
_day = day;
}
// 拷贝构造函数, d2(d1)
Date(const Date& d){
_year = d._year;
_month = d._month;
_day = d._day;
}
// 赋值运算符重载, d2 = d3 -> d2.operator=(&d2, d3)
//返回引用
Date& operator=(const Date& d){
//不是自己给自己赋值才能赋值操作
if (*this != d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
// 析构函数
~Date(){}
// 日期+=天数
Date& operator+=(int day){
if (_month!=12) {
while (day > 365) {
_year++;
day = day - 365;
}
if ((_day + day) > this->GetMonthDay(_year, _month)) {
_day = _day + day;
while (_day > this->GetMonthDay(_year, _month)) {
_day -= this->GetMonthDay(_year, _month);
_month++;
}
}
else {
_day += day;
}
}
else{
while (day > 365) {
_year++;
day = day - 365;
}
if ((_day + day) > 31) {
_day = _day + day-31;
_year++;
_month = 1;
while (_day > this->GetMonthDay(_year, _month)) {
_day -= this->GetMonthDay(_year, _month);
_month++;
}
}
else {
_day += day;
}
}
return *this;
}
// 日期+天数
Date operator+(int day){
int oldyear = _year;
int oldmonth = _month;
int oldday = _day;
if (_month != 12) {
while (day > 365) {
_year++;
day = day - 365;
}
if ((_day + day) > this->GetMonthDay(_year, _month)) {
_day = _day + day;
while (_day > this->GetMonthDay(_year, _month)) {
_day -= this->GetMonthDay(_year, _month);
_month++;
}
}
else {
_day += day;
}
}
else {
while (day > 365) {
_year++;
day = day - 365;
}
if ((_day + day) > 31) {
_day = _day + day - 31;
_year++;
_month = 1;
while (_day > this->GetMonthDay(_year, _month)) {
_day -= this->GetMonthDay(_year, _month);
_month++;
}
}
else {
_day += day;
}
}
Date tmp = *this;
_year = oldyear ;
_month = oldmonth ;
_day = oldday;
return tmp;
}
// 日期-天数
Date operator-(int day){
int oldyear = _year;
int oldmonth = _month;
int oldday = _day;
if (_month != 1) {
while (day > 365) {
_year--;
day -= 365;
}
if (_day - day < 1) {
_day = _day - day;
while (_day < 0) {
_day += this->GetMonthDay(_year, _month);
_month--;
}
}
else {
_day -= day;
}
}
else {
while (day > 365) {
_year--;
day -= 365;
}
if (_day - day < 1) {
_day = _day - day + 31;
_year--;
_month = 12;
while (_day < 0) {
_month--;
_day += this->GetMonthDay(_year, _month);
}
}
else {
_day -= day;
}
}
Date tmp = *this;
_year = oldyear;
_month = oldmonth;
_day = oldday;
return tmp;
}
// 日期-=天数
Date& operator-=(int day){
if (_month != 1) {
while (day>365) {
_year--;
day -= 365;
}
if (_day-day<1) {
_day = _day - day;
while (_day < 0 ) {
_day += this->GetMonthDay(_year, _month);
_month--;
}
}
else {
_day -= day;
}
}
else {
while (day > 365) {
_year--;
day -= 365;
}
if (_day - day < 1) {
_day = _day - day+31;
_year--;
_month = 12;
while (_day < 0) {
_month--;
_day += this->GetMonthDay(_year, _month);
}
}
else {
_day -= day;
}
}
return *this;
}
// 前置++
Date& operator++(){
++_day;
return *this;
}
// 后置++
Date operator++(int){
Date tmp=*this;
_day++;
return tmp;
}
// >运算符重载
bool operator>(const Date& d){
if (_year == d._year) {
if (_month == d._month) {
if (_day == d._day) {
return false;
}
else
return _day > d._day ? true : false;
}
else
return _month > d._month ? true : false;
}
else
return _year > d._year ? true : false;
}
// ==运算符重载
bool operator==(const Date& d){
if (_year == d._year && _month == d._month && _day == d._day)
return true;
else
return false;
}
// >=运算符重载
inline bool operator >= (const Date& d){
if (_year == d._year) {
if (_month == d._month) {
if (_day == d._day) {
return true;
}
else
return _day > d._day ? true : false;
}
else
return _month > d._month ? true : false;
}
else
return _year > d._year ? true : false;
}
// <运算符重载
bool operator < (const Date& d){
if (_year == d._year) {
if (_month == d._month) {
if (_day == d._day) {
return false;
}
else
return _day < d._day ? true : false;
}
else
return _month < d._month ? true : false;
}
else
return _year < d._year ? true : false;
}
// <=运算符重载
bool operator <= (const Date& d){
if (_year == d._year) {
if (_month == d._month) {
if (_day == d._day) {
return true;
}
else
return _day < d._day ? true : false;
}
else
return _month < d._month ? true : false;
}
else
return _year < d._year ? true : false;
}
// !=运算符重载
bool operator != (const Date& d){
if (_year == d._year && _month == d._month && _day == d._day)
return false;
else
return true;
}
// 日期-日期 返回天数
int operator-( Date& d){
int n = 0;
int oldyear = d._year;
int oldmonth = d._month;
int oldday = d._day;
if (*this < d) {
printf("输入日期有误,请重新输入\n");
return -1;
}
else {
while (*this != d) {
if (_month == d._month&&_year==d._year) {
n = n+(_day - d._day);
d._year = oldyear;
d._month = oldmonth;
d._day = oldday;
return n;
}
n += d.GetMonthDay(d._year, d._month) - d._day+1;
d._month++;
d._day = 1;
}
}
}
private:
int _year;
int _month;
int _day;
};
int main(){
Date day1(2021,10,3);
Date day2(2021,11,25);
int a = day2 - day1;
return 0;
}
取地址及const取地址操作符重载:
其他操作符重载函数都不是默认的成员函数!
这两一般视为上面四大默认函数之外的两个默认成员函数.
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
//等价于const Date* operator&(const Date* this)
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
int main(){
Date t1;
Date* pt1=&t1;//调用第一个
const Date t2;
const Date* pt2=&t2;//调用第二个,常对象只能给常对象
return 0;
}
|