邯郸做网站找哪家好,公司网站流程,智能营销型网站,网站设计制作策划前言
Hello, 小伙伴们#xff0c;我们今天继续c的学习#xff0c;我们上期有介绍到c的部分特性#xff0c;以及一些区别于c语言的地方#xff0c;今天我们将继续深入了解c的类和对象#xff0c;探索c的奥秘。
好#xff0c;废话不多说#xff0c;开始我们今天的学习。…前言
Hello, 小伙伴们我们今天继续c的学习我们上期有介绍到c的部分特性以及一些区别于c语言的地方今天我们将继续深入了解c的类和对象探索c的奥秘。
好废话不多说开始我们今天的学习。
1.类默认成员函数
默认成员函数就是用户没有显示实现编译器会自动生成的函数称为默认成员函数。一个类我们不写的情况下编译器会默认生成6个默认函数需要注意的是这六个函数中重要的是前4个最后两个取地址重载不重要我们稍微了解一下就好。其次c11以后还会增加两个默认的函数移动构造和移动赋值这个我们后面再讲解。默认成员函数十分的重要也比较复杂我们要从两个方面去学习 第一我们不写默认函数时 编译器默认的函数行为是什么是否能满足我们的要求。 第二编译器默认生成的函数不满足我们的要求时我们要自己去实现符合要求的函数。 1.1构造函数
构造函数就是特别的成员函数需要注意的是构造函数虽然名为构造但是构造函数的主要任务不是开辟空间创建对象我们之前使用的局部对象是栈帧创建时就开辟好的而对象的实例化时初始化对象。构造函数的本质就是要替代我们之前在实现Stack 和 Queue类中的Init函数功能构造函数自动调用的特点就完美的替代了Init函数。
构造函数的特点 1.函数名与类名相同。会自动生成一个无参数的默认构造函数一旦用户显示定义编译器就不会再生成默认的 2.无返回值返回值啥的都不需要给也不需要void 也不需要纠结 c的一定就是如此。 3.对象实例化时西永会和全缺省函数也是默认构造函数总结一下就是不传实参就可 自动调用构造函数。 4.构造函数可以重载。 5.如果类中没有显示定义的构造函数则c编译器 构造函数。 6.无参的构造函数、全缺省构造函数、我们不写构造函数时编译器自己默认生成的构造函数都叫做默认构造函数。但着三个函数有且只有一个存在于一个类中不能同时存在。无参构造函数和全缺省函数虽然构成函数重载但是调用时产生歧义。要注意的是很多的同学会认为默认构造函数是编译器默认生成的但实际上无参构造函数 以调用的构造函数就叫默认构造。 7.我们不写编译器默认生成的构造对内置类型的成员变量初始化没有要求也就是说是否初始化是不确定的看编译器。对于自定义类型成员变量要求调用这个成员变量的默认构造函数初始化。如果这个成员便变量没有默认构造函数那么就会报错我们要初始化这个变量需要用初始化列表才能解决初始化列表我们到了后面会详细介绍 说明 c把类型分为内置类型基本类型和自定义类型。内置类型就是语言原生数据类型如int/char/double/指针等类型自定义类型就是我们使用class和struct等关键字自己定义的类型。
我们来看看下面的代码实例
#includeiostream
using namespace std;
class Date
{public :// 1.⽆参构造函数Date(){_year 1;_month 1;_day 1;} // 2.带参构造函数Date(int year, int month, int day){_year year;_month month;_day day;} // 3.全缺省构造函数/*Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}*/void Print(){cout _year / _month / _day endl;}
private:int _year;int _month;int _day;
};
int main()
{// 如果留下三个构造中的第⼆个带参构造第⼀个和第三个注释掉// 编译报错error C2512: “Date”: 没有合适的默认构造函数可⽤Date d1; // 调⽤默认构造函数Date d2(2025, 1, 1); // 调⽤带参的构造函数// 注意如果通过⽆参构造函数创建对象时对象后⾯不⽤跟括号否则编译器⽆法// 区分这⾥是函数声明还是实例化对象// warning C4930: “Date d3(void)”: 未调⽤原型函数(是否是有意⽤变量定义的?)Date d3();d1.Print();d2.Print();return 0;
}
1.2析构函数
析构函数与构造函数的功能相反析构函数不是完成对对象本身的销毁比如局部对象是存在栈帧的函数结束栈帧销毁他释放了我们就不需要管c规定对象在销毁前会自动调用析构函数完成对象中资源的清理释放工作。析构函数的功能类好比我们之前的Stack 实现的Destroy功能反而Date没有Destroy其实就是没有资源的释放没有动态的申请内存空间所以严格上说Date是不需要析构函数的
析构函数的特点 析构函数名就是类名前面加上字符~。无参数返回值。这里和构造函数十分的相似也不需要加 void.一个类只能有一个析构函数。若没有显示定义系统就会自动生成默认的析构函数。对象的生命周期结束时系统会自动调用析构函数。跟构造函数类似我们不写编译器就会自动生成析构函数对内置类型成员不做处理自定义类型成员就会调用他的析构函数。还需要注意的是我们显示写析构函数对于自定义类型成员也会调用他的析构也就是说自定义类型成员无论在什么样的情况下都会自动调用自己的析构函数如果类中没有申请资源析构函数就可以不写直接使用编译器生产的默认函数如Date如果默认生成的析构函数就够用我们就不需要再显示其结构 比如MyQueue但是有资源的申请时一定要自己写析构函数否则会造成资源泄露如Stack。一个局部域的多个对象c规定后定义的先析构。 我们来看下面的用例
#includeiostream
using namespace std;
typedef int STDataType;
class Stack
{public :Stack(int n 4){_a (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr _a){perror(malloc申请空间失败);return;} _capacity n;_top 0;} ~Stack(){cout ~Stack() endl;free(_a);_a nullptr;_top _capacity 0;}
private:STDataType* _a;size_t _capacity;size_t _top;
};
// 两个Stack实现队列
class MyQueue
{public ://编译器默认⽣成MyQueue的析构函数调⽤了Stack的析构释放的Stack内部的资源// 显⽰写析构也会⾃动调⽤Stack的析构
/*~MyQueue()
{}*/
private:Stack pushst;Stack popst;
};
int main()
{Stack st;MyQueue mq;return 0;
} 看一下用c实现栈的结构再对比一下我们自己之前用C语言实现的栈结构我们可以明显的观察到c相较于C语言明显的优势。
我们发现有了析构函数和构造函数确实方便了很多也不会在担心忘记写Init函数和Destroy函数了我们写代时也方便了不少!!
1.3拷贝构造函数
如果一个构造函数的第一个参数是自身类类型的引用且任何额外的参数都是默认值则此构造函数也叫拷贝构造函数也就是说拷贝构造是一个特殊的构造函数。
拷贝构造的特点 拷贝构造函数是构造函数的一个重载。拷贝构造函数的第一个函数必须是类类型对象的引用使用传值方式编译器直接报错因为语法逻辑上会引起无穷递归调用、拷贝构造函数也可以有多个参数但是第一个参数必须是类类型对象的应用 后面的参数必须有缺省值。c规定的自定义类型对象进行拷贝行为必须调用靠别构造函数所以这里自定义类型传值传参和传值返回都会调用拷贝调用完成。若显示未定义拷贝构造编译器会生成自动的拷贝构造函数自动生成的拷贝构造对内置类型成员变量完成值拷贝/浅拷贝一个字节一个字节的拷贝对自定义类型成员变量会调用它的拷贝构造。像Date这样的类成员变量全是内置类型且没有指向那个资源编译器自动生成的拷贝构造就可以完成值的拷贝所以不需要我们显示拷贝构造。但是像Stack这样的类虽然也是内置类型但是_a指向的资源编译器自动生成拷贝构造就不符合我们现在的要求了所以需要我们自己来实现深拷贝对指向的资源也进行拷贝。像MyQueue这样的类型内部主要是自定义类型Stack成员编译器自动生成的拷贝构造。这里有一个小技巧如果一个类显示实现了析构并释放资源那么他就需要显示拷贝构造否则就不需要。传值返回会产生一个临时对象调用拷贝构造传值引用返回返回的是返回对象的别名引用没有产生拷贝。但是如果返回对象是一个当前函数局部域的局部对象函数结束就销毁了那么使用引用返回就有问题这时的引用相当于一个野引用类似一个野指针一样。传引用返回可以减少拷贝但是一定要确保返回对象在当前函数结束后还在才能用引用返回。 我们来看下面的例子
#define _CRT_SECURE_NO_WARNINGS 1
#includeiostream
using namespace std;
class Date
{public :Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;} // 编译报错error C2652 : “Date”: ⾮法的复制构造函数: 第⼀个参数不应是“Date”//Date(Date d)Date(const Date d){_year d._year;_month d._month;_day d._day;} Date(Date * d){_year d-_year;_month d-_month;_day d-_day;}void Print();private:int _year;int _month;int _day;
};
void Date:: Print()
{cout _year - _month - _day endl;
}
void Func1(Date d)
{cout d endl;d.Print();
}
// Date Func2()
Date Func2()
{Date tmp(2024, 7, 5);tmp.Print();return tmp;
}
int main()
{Date d1(2024, 7, 5);// C规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造所以这⾥传值传参要调⽤拷⻉构造// 所以这⾥的d1传值传参给d要调⽤拷⻉构造完成拷⻉传引⽤传参可以较少这⾥的拷⻉Func1(d1);cout d1 endl;// 这⾥可以完成拷⻉但是不是拷⻉构造只是⼀个普通的构造Date d2(d1);d1.Print();d2.Print();//这样写才是拷⻉构造通过同类型的对象初始化构造⽽不是指针Date d3(d1);d2.Print();// 也可以这样写这⾥也是拷⻉构造Date d4 d1;d2.Print();// Func2返回了⼀个局部对象tmp的引⽤作为返回值// Func2函数结束tmp对象就销毁了相当于了⼀个野引⽤Date ret Func2();ret.Print();return 0;
} 2.赋值运算符的重载
2.1运算符的重载 当运算符被用于类类型的对象时c语言允许我们通过对运算符重载的形式指定其新的含义。c规定类类型对象使用运算符时必须转换调用对应运算符的重载若没有对饮的运算符重载则会编译报错。 运算符重载是具有特殊名字的函数他的名字是有operaor和后面的要定义的运算符共同构成的。和其他函数一样它具有其返回类型和参数列表以及函数体。重载运算符的参数个数和该运算对象的数量一致。一元运算符有一个参数二元运算符有两个参数二元运算符的左侧运算对象传给第一个参数右侧运算对象传给第二个参数。如果一个重载运算符函数是成员函数则它的第一个运算对象默认传给隐士指针---this指针因此运算符重载作为成员函数时参数比运算对象少一个。运算符重载后其优先性和结核性与对应的内置类型运算符保持一致。不能通过连接语法中没有的符号来创建新的操作符比如operator。.* :: sizeof ?: . 注意前面的操作符不能重载。重载操作符至少有一个类类型参数不能通过运算符重载改变内置类型对象的含义一个类需要重载那些运算符是看运动算符在重载后承载了什么样的意义。重载运算符时有前置,和后置而运算重载函数都是通过operator来实现的没有办法很好的来区分c规定后置的重载时增加一个int形参跟前置构成函数重载方便区分。重载和时需要重载为全局函数因为重载成员函数时this指针默认会抢占形参的第一个位置第一个形参位置是左侧运算对象调用时变成了cout不符合使用习惯和可读性。重载为全局函数把ostram和istream1放到第一个形参位置就i行了第二个形参位置当类类型对象。 比如我们来看下面的代码
#includeiostream
using namespace std;
// 编译报错“operator ”必须⾄少有⼀个类类型的形参
int operator(int x, int y)
{return x - y;
}
class A
{
public :
void func()
{
cout A::func() endl;
}
};
typedef void(A::* PF)(); //成员函数指针类型
int main()
{// C规定成员函数要加才能取到函数指针PF pf A::func;A obj;//定义ob类对象temp// 对象调⽤成员函数指针时使⽤.*运算符(obj.*pf)();return 0;
}
3.日期类的实现
接下来我们实现一个日期类应用的底层逻辑结合上面的知识我们来试着实现一下把
首先我们还是要创建三个文件
接下来我们展示代码
Date.h #pragma once
#includeiostream
#includeassert.h
using namespace std;
class Date
{
public:Date(int year 2023, int month 8, int day 10);//~Date();void Print();bool operator(const Date d);bool operator(const Date d);bool operator(const Date d);bool operator(const Date d);bool operator(const Date d);bool operator!(const Date d);Date operator(int x);Date operator(int x);Date operator-(int x);Date operator-(int x);//dDate operator();//dDate operator(int);//两个日期相减隔开的天数int operator-(const Date d);int GetMonthDay(int month, int year){assert(month 0 month 12);static int monthday[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 monthday[month];}
private:int _year;int _month;int _day;
}; Date.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#includeDate.h
Date::Date(int year, int month, int day)
{_year year;_month month;_day day;
}
void Date:: Print()
{cout _year / _month / _day endl;
}
bool Date::operator(const Date d)
{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;return false;
}
bool Date::operator(const Date d)
{return !(*this d);
}
bool Date::operator(const Date d)
{return *this d || *this d;
}
bool Date::operator(const Date d)
{return *this d || *this d;
}
bool Date::operator(const Date d)
{return _year d._year _month d._month _day d._day;
}
bool Date::operator!(const Date d)
{return !(*this d);
}
Date Date::operator(int x)
{if (x 0){return *this - -x;}_day x;while (_day GetMonthDay( _month,_year)){_day - GetMonthDay(_month, _year);_month;if (_month 12){_year;_month 1;}}return *this;
}
Date Date::operator(int x)
{Date tmp *this;tmp x;return tmp;
}
Date Date::operator - (int x)
{if (x 0){return(*this) -x;}_day - x;while (_day 0){_day GetMonthDay(_month, _year);_month--;if (_month 0){_year--;_month 12;}}return *this;
}
Date Date::operator-(int x)
{Date tmp *this;tmp - x;return tmp;
}//d
Date Date::operator()
{*this 1;return *this;
}
//d
Date Date::operator(int)
{Date tmp *this;*this 1;return tmp;
}
//使用逐一的方法来判断相差的天数
int Date::operator-(const Date d)
{Date big *this;Date small d;int flag 1;if (*this d){big d;small *this;flag -1;}int count 0;while (big ! small){small;count;}return count * flag;
}Test.cpp
#define _CRT_SECURE_NO_WARNINGS 1#includeDate.h
int main()
{Date d1(2023, 4, 5);Date d2 d1 100;d2.Print();d1.Print();
// d1 100;d1.Print();d1 - 5;d1.Print();(d1 (-100)).Print();(d1 - (-100)).Print();//cout (d3 ? true : false) endl;cout endl;d1.Print();d2.Print();cout d1 - d2 endl;return 0;
}
好今天的学习就到这里我们下期再见